Browse Source

fix: line clipping and overlapping (#3291)

Jonathan Kelley 6 months ago
parent
commit
0974550c9f

+ 5 - 6
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)
     }

+ 58 - 80
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::<Vec<_>>();
         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<String> {
+        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(&paragraph)
+        lines
     }
 }

+ 7 - 1
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");