Skip to content

Commit

Permalink
standard formatter appears to work as before, but using Line/Span
Browse files Browse the repository at this point in the history
  • Loading branch information
gin66 committed Jan 30, 2025
1 parent cd29805 commit 72a0b68
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 104 deletions.
11 changes: 10 additions & 1 deletion examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl App {
thread::spawn(move || input_thread(event_tx));
thread::spawn(move || progress_task(progress_tx).unwrap());
thread::spawn(move || background_task());
thread::spawn(move || background_task2());

self.run(terminal, rx)
}
Expand Down Expand Up @@ -192,13 +193,21 @@ fn background_task() {
loop {
error!(target:"background-task", "an error");
warn!(target:"background-task", "a warning");
info!(target:"background-task", "an info");
info!(target:"background-task", "a two line info\nsecond line");
debug!(target:"background-task", "a debug");
trace!(target:"background-task", "a trace");
thread::sleep(time::Duration::from_millis(1000));
}
}

/// A background task for long line
fn background_task2() {
loop {
info!(target:"background-task2", "This is a very long message, which should be wrapped on smaller screen by the standard formatter with an indentation of 9 characters.");
thread::sleep(time::Duration::from_millis(2000));
}
}

impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let progress_height = if self.progress_counter.is_some() {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,9 @@ pub use log::LevelFilter;

pub mod widget;
pub use widget::inner::TuiWidgetState;
pub use widget::logformatter::LogFormatter;
pub use widget::smart::TuiLoggerSmartWidget;
pub use widget::standard::TuiLoggerWidget;
pub use widget::logformatter::LogFormatter;

pub mod file;
pub use file::TuiLoggerFile;
Expand Down
65 changes: 6 additions & 59 deletions src/widget/logformatter.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,11 @@
use crate::ExtLogRecord;
use crate::Style;
use crate::TuiLoggerLevelOutput;
use ratatui::text::Line;

pub trait LogFormatter: Send + Sync {
fn format(&self, evt: &ExtLogRecord) -> (String, Option<Style>);
}

pub struct LogStandardFormatter {
pub style_error: Option<Style>,
pub style_warn: Option<Style>,
pub style_debug: Option<Style>,
pub style_trace: Option<Style>,
pub style_info: Option<Style>,
pub format_separator: char,
pub format_timestamp: Option<String>,
pub format_output_level: Option<TuiLoggerLevelOutput>,
pub format_output_target: bool,
pub format_output_file: bool,
pub format_output_line: bool,
}
fn min_width(&self) -> u16;

impl LogFormatter for LogStandardFormatter {
fn format(&self, evt: &ExtLogRecord) -> (String, Option<Style>) {
let mut output = String::new();
let (col_style, lev_long, lev_abbr, with_loc) = match evt.level {
log::Level::Error => (self.style_error, "ERROR", "E", true),
log::Level::Warn => (self.style_warn, "WARN ", "W", true),
log::Level::Info => (self.style_info, "INFO ", "I", true),
log::Level::Debug => (self.style_debug, "DEBUG", "D", true),
log::Level::Trace => (self.style_trace, "TRACE", "T", true),
};
if let Some(fmt) = self.format_timestamp.as_ref() {
output.push_str(&format!("{}", evt.timestamp.format(fmt)));
output.push(self.format_separator);
}
match &self.format_output_level {
None => {}
Some(TuiLoggerLevelOutput::Abbreviated) => {
output.push_str(lev_abbr);
output.push(self.format_separator);
}
Some(TuiLoggerLevelOutput::Long) => {
output.push_str(lev_long);
output.push(self.format_separator);
}
}
if self.format_output_target {
output.push_str(&evt.target);
output.push(self.format_separator);
}
if with_loc {
if self.format_output_file {
output.push_str(&evt.file);
output.push(self.format_separator);
}
if self.format_output_line {
output.push_str(&format!("{}", evt.line));
output.push(self.format_separator);
}
}
(output, col_style)
}
/// This must format any event in one or more lines.
/// Correct wrapping in next line with/without indenting must be performed here.
/// The parameter width is the available line width
fn format(&self, width: usize, evt: &ExtLogRecord) -> Vec<Line>;
}
1 change: 1 addition & 0 deletions src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod inner;
pub mod logformatter;
pub mod smart;
pub mod standard;
mod standard_formatter;
69 changes: 26 additions & 43 deletions src/widget/standard.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::widget::logformatter::{LogFormatter, LogStandardFormatter};
use crate::widget::logformatter::LogFormatter;
use crate::widget::standard_formatter::LogStandardFormatter;
use parking_lot::Mutex;
use std::sync::Arc;

Expand Down Expand Up @@ -222,6 +223,7 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
Some(fmt) => fmt,
None => {
let fmt = LogStandardFormatter {
style: self.style,
style_error: self.style_error,
style_warn: self.style_warn,
style_debug: self.style_debug,
Expand All @@ -247,14 +249,17 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
}
None => area,
};
let indent = 9;
if list_area.width < indent + 4 || list_area.height < 1 {
if list_area.width < formatter.min_width() || list_area.height < 1 {
return;
}

let mut state = self.state.lock();
let la_height = list_area.height as usize;
let mut lines: Vec<(Option<Style>, u16, String)> = vec![];
let la_left = list_area.left();
let la_top = list_area.top();
let la_width = list_area.width as usize;
//let mut lines: Vec<Line> = vec![];
let mut lines = CircularBuffer::new(la_height);
{
state.opt_timestamp_next_page = None;
let opt_timestamp_bottom = state.opt_timestamp_bottom;
Expand Down Expand Up @@ -289,14 +294,11 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
if !circular.is_empty() {
state.opt_timestamp_next_page = circular.take().first().cloned();
}
let (mut output, col_style) = formatter.format(evt);
let mut sublines: Vec<&str> = evt.msg.lines().rev().collect();
output.push_str(sublines.pop().unwrap());
for subline in sublines {
lines.push((col_style, indent, subline.to_string()));
let mut evt_lines = formatter.format(la_width, evt);
while let Some(line) = evt_lines.pop() {
lines.push(line);
}
lines.push((col_style, 0, output));
if lines.len() == la_height {
if lines.len() >= la_height {
break;
}
if opt_timestamp_prev_page.is_none() && lines.len() >= la_height / 2 {
Expand All @@ -305,44 +307,25 @@ impl<'b> Widget for TuiLoggerWidget<'b> {
}
state.opt_timestamp_prev_page = opt_timestamp_prev_page.or(state.opt_timestamp_bottom);
}
let la_left = list_area.left();
let la_top = list_area.top();
let la_width = list_area.width as usize;

// lines is a vector with bottom line at index 0
// wrapped_lines will be a vector with top line first
let mut wrapped_lines = CircularBuffer::new(la_height);
let rem_width = la_width - indent as usize;
while let Some((style, left, line)) = lines.pop() {
if line.chars().count() > la_width {
wrapped_lines.push((style, left, line.chars().take(la_width).collect()));
let mut remain: String = line.chars().skip(la_width).collect();
while remain.chars().count() > rem_width {
let remove: String = remain.chars().take(rem_width).collect();
wrapped_lines.push((style, indent, remove));
remain = remain.chars().skip(rem_width).collect();
}
wrapped_lines.push((style, indent, remain.to_owned()));
} else {
wrapped_lines.push((style, left, line));
}
}

// This apparently ensures, that the log starts at top
let offset: u16 = if state.opt_timestamp_bottom.is_none() {
0
} else {
let lines_cnt = wrapped_lines.len();
(la_height - lines_cnt) as u16
let lines_cnt = lines.len();
std::cmp::max(0, la_height - lines_cnt) as u16
};

for (i, (sty, left, l)) in wrapped_lines.iter().enumerate() {
buf.set_stringn(
la_left + left,
la_top + i as u16 + offset,
l,
l.len(),
sty.unwrap_or(self.style),
);
for (i, line) in lines.iter().rev().take(la_height).enumerate() {
line.render(
Rect {
x: la_left,
y: la_top + i as u16 + offset,
width: list_area.width,
height: 1,
},
buf,
)
}
}
}
121 changes: 121 additions & 0 deletions src/widget/standard_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::widget::logformatter::LogFormatter;
use crate::ExtLogRecord;
use crate::Style;
use crate::TuiLoggerLevelOutput;
use ratatui::text::{Line, Span};
use std::borrow::Cow;

pub struct LogStandardFormatter {
/// Base style of the widget
pub style: Style,
/// Level based style
pub style_error: Option<Style>,
pub style_warn: Option<Style>,
pub style_debug: Option<Style>,
pub style_trace: Option<Style>,
pub style_info: Option<Style>,
pub format_separator: char,
pub format_timestamp: Option<String>,
pub format_output_level: Option<TuiLoggerLevelOutput>,
pub format_output_target: bool,
pub format_output_file: bool,
pub format_output_line: bool,
}

impl LogStandardFormatter {
fn append_wrapped_line(
&self,
style: Style,
indent: usize,
lines: &mut Vec<Line>,
line: &str,
width: usize,
with_indent: bool,
) {
let mut p = 0;
let mut wrap_len = width;
if with_indent {
wrap_len -= indent;
}
let space = " ".repeat(indent);
while p < line.len() {
let linelen = std::cmp::min(wrap_len, line.len() - p);
let subline = &line[p..p + linelen];

let mut spans: Vec<Span> = Vec::new();
if wrap_len < width {
// need indent
spans.push(Span {
style: style,
content: Cow::Owned(space.to_string()),
});
}
spans.push(Span {
style: style,
content: Cow::Owned(subline.to_string()),
});
let line = Line::from(spans);
lines.push(line);

p += linelen;
// following lines need to be indented
wrap_len = width - indent;
}
}
}

impl LogFormatter for LogStandardFormatter {
fn min_width(&self) -> u16 {
9 + 4
}
fn format(&self, width: usize, evt: &ExtLogRecord) -> Vec<Line> {
let mut lines = Vec::new();
let mut output = String::new();
let (col_style, lev_long, lev_abbr, with_loc) = match evt.level {
log::Level::Error => (self.style_error, "ERROR", "E", true),
log::Level::Warn => (self.style_warn, "WARN ", "W", true),
log::Level::Info => (self.style_info, "INFO ", "I", true),
log::Level::Debug => (self.style_debug, "DEBUG", "D", true),
log::Level::Trace => (self.style_trace, "TRACE", "T", true),
};
let col_style = col_style.unwrap_or(self.style);
if let Some(fmt) = self.format_timestamp.as_ref() {
output.push_str(&format!("{}", evt.timestamp.format(fmt)));
output.push(self.format_separator);
}
match &self.format_output_level {
None => {}
Some(TuiLoggerLevelOutput::Abbreviated) => {
output.push_str(lev_abbr);
output.push(self.format_separator);
}
Some(TuiLoggerLevelOutput::Long) => {
output.push_str(lev_long);
output.push(self.format_separator);
}
}
if self.format_output_target {
output.push_str(&evt.target);
output.push(self.format_separator);
}
if with_loc {
if self.format_output_file {
output.push_str(&evt.file);
output.push(self.format_separator);
}
if self.format_output_line {
output.push_str(&format!("{}", evt.line));
output.push(self.format_separator);
}
}
let mut sublines: Vec<&str> = evt.msg.lines().rev().collect();

output.push_str(sublines.pop().unwrap());
self.append_wrapped_line(col_style, 9, &mut lines, &output, width, false);

for subline in sublines.iter().rev() {
self.append_wrapped_line(col_style, 9, &mut lines, subline, width, true);
}
lines
}
}

0 comments on commit 72a0b68

Please sign in to comment.