diff --git a/packages/cli/src/serve/ansi_buffer.rs b/packages/cli/src/serve/ansi_buffer.rs index c1e070f80c..b71d5bb9fa 100644 --- a/packages/cli/src/serve/ansi_buffer.rs +++ b/packages/cli/src/serve/ansi_buffer.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter}; /// /// This is taken from a PR on the ratatui repo (https://github.com/ratatui/ratatui/pull/1065) and /// modified to be more appropriate for our use case. -pub struct AnsiStringBuffer { +pub struct AnsiStringLine { buf: Buffer, } @@ -13,11 +13,11 @@ pub struct AnsiStringBuffer { // Not sure if we actually still need this.... const SENTINEL: &str = "✆"; -impl AnsiStringBuffer { +impl AnsiStringLine { /// Creates a new `AnsiStringBuffer` with the given width and height. - pub(crate) fn new(width: u16, height: u16) -> Self { + pub(crate) fn new(width: u16) -> Self { Self { - buf: Buffer::empty(Rect::new(0, 0, width, height)), + buf: Buffer::empty(Rect::new(0, 0, width, 1)), } } @@ -50,7 +50,6 @@ impl AnsiStringBuffer { for x in 0..self.buf.area.width { let cell = self.buf.cell((x, y)).unwrap(); if cell.symbol() == SENTINEL { - f.write_str("\n")?; break; } @@ -66,7 +65,7 @@ impl AnsiStringBuffer { } } -impl Display for AnsiStringBuffer { +impl Display for AnsiStringLine { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.write_fmt(f) } diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index d53ac94b80..7f7c96cd7c 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -1,5 +1,5 @@ use crate::{ - serve::{ansi_buffer::AnsiStringBuffer, Builder, ServeUpdate, Watcher, WebServer}, + serve::{ansi_buffer::AnsiStringLine, Builder, ServeUpdate, Watcher, WebServer}, BuildStage, BuildUpdate, DioxusCrate, Platform, ServeArgs, TraceContent, TraceMsg, TraceSrc, }; use crossterm::{ @@ -767,10 +767,9 @@ impl Output { // Render the log into an ansi string // We're going to add some metadata to it like the timestamp and source and then dump it to the raw ansi sequences we need to send to crossterm - let output_sequence = Self::tracemsg_to_ansi_string(log, term_size.width); + let lines = Self::tracemsg_to_ansi_string(log); // Get the lines of the output sequence and their overflow - let lines = output_sequence.lines().collect::>(); let lines_printed = lines .iter() .map(|line| { @@ -793,7 +792,7 @@ impl Output { .saturating_sub(frame_rect.y + frame_rect.height); // Calculate how many lines we need to push back - let to_push = max_scrollback.saturating_sub(remaining_space + 1); + let to_push = max_scrollback.saturating_sub(remaining_space); // Wipe the viewport clean so it doesn't tear crossterm::queue!( @@ -807,7 +806,7 @@ impl Output { // Ratatui will handle this rest. // FIXME(jon): eventually insert_before will get scroll regions, breaking this, but making the logic here simpler if to_push == 0 { - terminal.insert_before(lines_printed.saturating_sub(1), |_| {})?; + terminal.insert_before(lines_printed, |_| {})?; } // Start printing the log by writing on top of the topmost line @@ -847,93 +846,72 @@ impl Output { } } - fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String { + fn tracemsg_to_ansi_string(log: TraceMsg) -> Vec { + use ansi_to_tui::IntoText; + use chrono::Timelike; + let rendered = match log.content { TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(), TraceContent::Text(text) => text, }; - // Create a paragraph widget using the log line itself - // From here on out, we want to work with the escaped ansi string and the "real lines" to be printed - // - // We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them - // dark gray, just for readability. - // - // todo(jon): refactor this out to accept any widget, not just paragraphs - let paragraph = Paragraph::new({ - use ansi_to_tui::IntoText; - use chrono::Timelike; - - let mut text = Text::default(); - - for (idx, raw_line) in rendered.lines().enumerate() { - let line_as_text = raw_line.into_text().unwrap(); - let is_pretending_to_be_frame = raw_line + let mut lines = vec![]; + + for (idx, raw_line) in rendered.lines().enumerate() { + let line_as_text = raw_line.into_text().unwrap(); + let is_pretending_to_be_frame = !raw_line.is_empty() + && raw_line .chars() .all(|c| c == '=' || c == '-' || c == ' ' || c == '─'); - for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() { - let mut out_line = if idx == 0 && subline_idx == 0 { - let mut formatted_line = Line::default(); - - formatted_line.push_span( - Span::raw(format!( - "{:02}:{:02}:{:02} ", - log.timestamp.hour(), - log.timestamp.minute(), - log.timestamp.second() - )) - .dark_gray(), - ); - formatted_line.push_span( - Span::raw(format!( - "[{src}] {padding}", - src = log.source, - padding = - " ".repeat(3usize.saturating_sub(log.source.to_string().len())) - )) - .style(match log.source { - TraceSrc::App(_platform) => Style::new().blue(), - TraceSrc::Dev => Style::new().magenta(), - TraceSrc::Build => Style::new().yellow(), - TraceSrc::Bundle => Style::new().magenta(), - TraceSrc::Cargo => Style::new().yellow(), - TraceSrc::Unknown => Style::new().gray(), - }), - ); - - for span in line.spans { - formatted_line.push_span(span); - } - - formatted_line - } else { - line - }; - - if is_pretending_to_be_frame { - out_line = out_line.dark_gray(); + for (subline_idx, mut line) in line_as_text.lines.into_iter().enumerate() { + if idx == 0 && subline_idx == 0 { + let mut formatted_line = Line::default(); + + formatted_line.push_span( + Span::raw(format!( + "{:02}:{:02}:{:02} ", + log.timestamp.hour(), + log.timestamp.minute(), + log.timestamp.second() + )) + .dark_gray(), + ); + + formatted_line.push_span( + Span::raw(format!( + "[{src}] {padding}", + src = log.source, + padding = + " ".repeat(3usize.saturating_sub(log.source.to_string().len())) + )) + .style(match log.source { + TraceSrc::App(_platform) => Style::new().blue(), + TraceSrc::Dev => Style::new().magenta(), + TraceSrc::Build => Style::new().yellow(), + TraceSrc::Bundle => Style::new().magenta(), + TraceSrc::Cargo => Style::new().yellow(), + TraceSrc::Unknown => Style::new().gray(), + }), + ); + + for span in line.spans { + formatted_line.push_span(span); } - text.lines.push(out_line); + line = formatted_line; } - } - text - }); + if is_pretending_to_be_frame { + line = line.dark_gray(); + } + + let line_length: usize = line.spans.iter().map(|f| f.content.len()).sum(); + + lines.push(AnsiStringLine::new(line_length.max(100) as _).render(&line)); + } + } - // We want to get the escaped ansii string and then by dumping the paragraph as ascii codes (again) - // - // This is important because the line_count method on paragraph takes into account the width of these codes - // the 3000 clip width is to bound log lines to a reasonable memory usage - // We could consider reusing this buffer since it's a lot to allocate, but log printing is not the - // slowest thing in the world and allocating is pretty fast... - // - // After we've dumped the ascii out, we want to call "trim_end" which ensures we don't attempt - // to print extra characters as lines, since AnsiStringBuffer will in fact attempt to print empty - // cells as characters. That might not actually be important, but we want to shrink the buffer - // before printing it - let line_count = paragraph.line_count(term_width); - AnsiStringBuffer::new(3000, line_count as u16).render(¶graph) + lines } } diff --git a/packages/const-serialize/src/lib.rs b/packages/const-serialize/src/lib.rs index 5fba91db4f..a094183bc9 100644 --- a/packages/const-serialize/src/lib.rs +++ b/packages/const-serialize/src/lib.rs @@ -217,7 +217,7 @@ impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: const MAX_STR_SIZE: usize = 256; /// A string that is stored in a constant sized buffer that can be serialized and deserialized at compile time -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)] +#[derive(PartialEq, PartialOrd, Clone, Copy, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ConstStr { #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] @@ -455,6 +455,12 @@ impl ConstStr { } } +impl std::fmt::Debug for ConstStr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.as_str()) + } +} + #[test] fn test_rsplit_once() { let str = ConstStr::new("hello world");