Add CursiveLogger and DebugView

This commit is contained in:
Alexandre Bury 2019-02-22 12:38:16 -08:00
parent 4852d25f51
commit 8b3f5a8de7
7 changed files with 141 additions and 0 deletions

View File

@ -29,6 +29,7 @@ xi-unicode = "0.1.0"
libc = "0.2.47" libc = "0.2.47"
term_size = { version = "0.3.1", optional = true } term_size = { version = "0.3.1", optional = true }
crossbeam-channel = "0.3.6" crossbeam-channel = "0.3.6"
lazy_static = "1.2.0"
[dependencies.num] [dependencies.num]
default-features = false default-features = false

17
examples/logger.rs Normal file
View File

@ -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();
}

View File

@ -182,6 +182,24 @@ impl Cursive {
Self::new(backend::dummy::Backend::init) 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 a sink for asynchronous callbacks.
/// ///
/// Returns the sender part of a channel, that allows to send /// Returns the sender part of a channel, that allows to send

View File

@ -69,6 +69,8 @@ extern crate enumset;
extern crate log; extern crate log;
#[macro_use] #[macro_use]
extern crate crossbeam_channel; extern crate crossbeam_channel;
#[macro_use]
extern crate lazy_static;
#[cfg(any(feature = "ncurses", feature = "pancurses"))] #[cfg(any(feature = "ncurses", feature = "pancurses"))]
#[macro_use] #[macro_use]
@ -108,6 +110,7 @@ pub mod utils;
pub mod align; pub mod align;
pub mod direction; pub mod direction;
pub mod logger;
pub mod menu; pub mod menu;
pub mod rect; pub mod rect;
pub mod theme; pub mod theme;

50
src/logger.rs Normal file
View File

@ -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<VecDeque<(log::Level, String)>> =
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);
}

50
src/views/debug_view.rs Normal file
View File

@ -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?
}
}

View File

@ -40,6 +40,7 @@ mod button;
mod canvas; mod canvas;
mod checkbox; mod checkbox;
mod circular_focus; mod circular_focus;
mod debug_view;
mod dialog; mod dialog;
mod dummy; mod dummy;
mod edit_view; mod edit_view;
@ -72,6 +73,7 @@ pub use self::button::Button;
pub use self::canvas::Canvas; pub use self::canvas::Canvas;
pub use self::checkbox::Checkbox; pub use self::checkbox::Checkbox;
pub use self::circular_focus::CircularFocus; pub use self::circular_focus::CircularFocus;
pub use self::debug_view::DebugView;
pub use self::dialog::{Dialog, DialogFocus}; pub use self::dialog::{Dialog, DialogFocus};
pub use self::dummy::DummyView; pub use self::dummy::DummyView;
pub use self::edit_view::EditView; pub use self::edit_view::EditView;