From 8b3f5a8de755d93d33a690d630d63a999a3861c7 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Fri, 22 Feb 2019 12:38:16 -0800 Subject: [PATCH] Add CursiveLogger and DebugView --- Cargo.toml | 1 + examples/logger.rs | 17 ++++++++++++++ src/cursive.rs | 18 +++++++++++++++ src/lib.rs | 3 +++ src/logger.rs | 50 +++++++++++++++++++++++++++++++++++++++++ src/views/debug_view.rs | 50 +++++++++++++++++++++++++++++++++++++++++ src/views/mod.rs | 2 ++ 7 files changed, 141 insertions(+) create mode 100644 examples/logger.rs create mode 100644 src/logger.rs create mode 100644 src/views/debug_view.rs diff --git a/Cargo.toml b/Cargo.toml index 1b457e3..6dfccdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ xi-unicode = "0.1.0" libc = "0.2.47" term_size = { version = "0.3.1", optional = true } crossbeam-channel = "0.3.6" +lazy_static = "1.2.0" [dependencies.num] default-features = false diff --git a/examples/logger.rs b/examples/logger.rs new file mode 100644 index 0000000..81ab03d --- /dev/null +++ b/examples/logger.rs @@ -0,0 +1,17 @@ +extern crate cursive; + +#[macro_use] +extern crate log; + +fn main() { + cursive::logger::init(); + debug!("Starting!"); + + let mut siv = cursive::Cursive::default(); + siv.add_global_callback('q', cursive::Cursive::quit); + siv.add_global_callback('~', cursive::Cursive::toggle_debug_view); + siv.add_global_callback('l', |_| debug!("Wooo")); + error!("BAD!!!"); + + siv.run(); +} diff --git a/src/cursive.rs b/src/cursive.rs index 9f57f84..093404e 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -182,6 +182,24 @@ impl Cursive { Self::new(backend::dummy::Backend::init) } + /// Show the debug view, or hide it if it's already visible. + pub fn toggle_debug_view(&mut self) { + static DEBUG_VIEW_ID: &'static str = "_cursive_debug_view"; + + let stack = self.screen_mut(); + if let Some(pos) = stack.find_layer_from_id(DEBUG_VIEW_ID) { + info!("Foundit"); + stack.remove_layer(pos); + } else { + stack.add_layer( + views::Dialog::around(views::ScrollView::new( + views::IdView::new(DEBUG_VIEW_ID, views::DebugView::new()), + )) + .title("Debug console"), + ); + } + } + /// Returns a sink for asynchronous callbacks. /// /// Returns the sender part of a channel, that allows to send diff --git a/src/lib.rs b/src/lib.rs index 878a141..94c8cc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,8 @@ extern crate enumset; extern crate log; #[macro_use] extern crate crossbeam_channel; +#[macro_use] +extern crate lazy_static; #[cfg(any(feature = "ncurses", feature = "pancurses"))] #[macro_use] @@ -108,6 +110,7 @@ pub mod utils; pub mod align; pub mod direction; +pub mod logger; pub mod menu; pub mod rect; pub mod theme; diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..e6beb32 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,50 @@ +//! Logging utilities + +use std::collections::VecDeque; +use std::sync::Mutex; + +/// Saves all log records in a global deque. +/// +/// Uses a `DebugView` to access it. +struct CursiveLogger; + +static LOGGER: CursiveLogger = CursiveLogger; + +lazy_static! { + /// Circular buffer for logs. Use it to implement `DebugView`. + pub static ref LOGS: Mutex> = + Mutex::new(VecDeque::new()); +} + +impl log::Log for CursiveLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + let mut logs = LOGS.lock().unwrap(); + // TODO: customize the format? Use colors? Save more info? + if logs.len() == logs.capacity() { + logs.pop_front(); + } + logs.push_back((record.level(), format!("{}", record.args()))); + } + + fn flush(&self) {} +} + +/// Initialize the Cursive logger. +/// +/// Make sure this is the only logger your are using. +/// +/// Use a `DebugView` to see the logs. +pub fn init() { + // TODO: Configure the deque size? + LOGS.lock().unwrap().reserve(1_000); + + // This will panic if `set_logger` was already called. + log::set_logger(&LOGGER).unwrap(); + + // TODO: read the level from env variable? From argument? + log::set_max_level(log::LevelFilter::Trace); +} diff --git a/src/views/debug_view.rs b/src/views/debug_view.rs new file mode 100644 index 0000000..159f78f --- /dev/null +++ b/src/views/debug_view.rs @@ -0,0 +1,50 @@ +use logger; +use vec::Vec2; +use view::View; +use Printer; + +use unicode_width::UnicodeWidthStr; + +/// View used for debugging, showing logs. +pub struct DebugView { + // We'll want to store the formatting (line split) +// ... or 1 line per log? +} + +impl DebugView { + /// Creates a new DebugView. + pub fn new() -> Self { + DebugView {} + } +} + +impl View for DebugView { + fn draw(&self, printer: &Printer) { + let logs = logger::LOGS.lock().unwrap(); + // Only print the last logs, so skip what doesn't fit + let skipped = logs.len().saturating_sub(printer.size.y); + + for (i, &(level, ref text)) in logs.iter().skip(skipped).enumerate() { + printer.print((0, i), &format!("[{}] {}", level, text)); + } + } + + fn required_size(&mut self, _constraint: Vec2) -> Vec2 { + // TODO: read the logs, and compute the required size to print it. + let logs = logger::LOGS.lock().unwrap(); + + // The longest line sets the width + let w = logs + .iter() + .map(|&(_, ref content)| content.width() + "[ERROR] ".width()) + .max() + .unwrap_or(1); + let h = logs.len(); + + Vec2::new(w, h) + } + + fn layout(&mut self, _size: Vec2) { + // Uh? + } +} diff --git a/src/views/mod.rs b/src/views/mod.rs index a6ae1ce..1d29fdf 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -40,6 +40,7 @@ mod button; mod canvas; mod checkbox; mod circular_focus; +mod debug_view; mod dialog; mod dummy; mod edit_view; @@ -72,6 +73,7 @@ pub use self::button::Button; pub use self::canvas::Canvas; pub use self::checkbox::Checkbox; pub use self::circular_focus::CircularFocus; +pub use self::debug_view::DebugView; pub use self::dialog::{Dialog, DialogFocus}; pub use self::dummy::DummyView; pub use self::edit_view::EditView;