fix(tui): wrap fallback tables and clamp narrow stream widths

This commit is contained in:
Felipe Coury
2026-04-06 13:21:54 -03:00
committed by Felipe Coury
parent 31bd54d7a2
commit 27ed715de7
5 changed files with 48 additions and 10 deletions
+7 -1
View File
@@ -4192,7 +4192,13 @@ impl ChatWidget {
self.last_rendered_width
.get()
.or_else(|| terminal::size().ok().map(|(w, _)| usize::from(w)))
.and_then(|w| crate::width::usable_content_width(w, reserved_cols))
.and_then(|w| {
if w == 0 {
None
} else {
Some(crate::width::usable_content_width(w, reserved_cols).unwrap_or(1))
}
})
}
pub(crate) fn on_terminal_resize(&mut self, width: u16) {
@@ -113,14 +113,14 @@ async fn turn_started_uses_runtime_context_window_before_first_token_count() {
}
#[tokio::test]
async fn current_stream_width_returns_none_when_reserved_columns_exhaust_width() {
async fn current_stream_width_clamps_to_minimum_when_reserved_columns_exhaust_width() {
let (chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.last_rendered_width.set(Some(2));
assert_eq!(chat.current_stream_width(2), None);
assert_eq!(chat.current_stream_width(2), Some(1));
chat.last_rendered_width.set(Some(4));
assert_eq!(chat.current_stream_width(4), None);
assert_eq!(chat.current_stream_width(4), Some(1));
chat.last_rendered_width.set(Some(5));
assert_eq!(chat.current_stream_width(4), Some(1));
+15 -4
View File
@@ -195,11 +195,13 @@ impl TableState {
/// Rendered table output split by wrapping behavior.
///
/// `table_lines` are grid/fallback rows that are already width-laid-out and
/// should bypass paragraph wrapping. `spillover_lines` are prose rows extracted
/// from parser artifacts and should be routed through normal wrapping.
/// `table_lines` are either prewrapped grid rows (box rendering) or pipe
/// fallback rows that should still pass through normal wrapping.
/// `spillover_lines` are prose rows extracted from parser artifacts and should
/// be routed through normal wrapping.
struct RenderedTableLines {
table_lines: Vec<Line<'static>>,
table_lines_prewrapped: bool,
spillover_lines: Vec<Line<'static>>,
}
@@ -810,11 +812,17 @@ where
let RenderedTableLines {
table_lines,
table_lines_prewrapped,
spillover_lines,
} = self.render_table_lines(table_state);
let mut pending_marker_line = self.pending_marker_line;
for line in table_lines {
self.push_prewrapped_line(line, pending_marker_line);
if table_lines_prewrapped {
self.push_prewrapped_line(line, pending_marker_line);
} else {
self.push_line(line);
self.flush_current_line();
}
pending_marker_line = false;
}
self.pending_marker_line = false;
@@ -945,6 +953,7 @@ where
if column_count == 0 {
return RenderedTableLines {
table_lines: Vec::new(),
table_lines_prewrapped: true,
spillover_lines: Vec::new(),
};
}
@@ -989,6 +998,7 @@ where
&rows,
&table_state.alignments,
),
table_lines_prewrapped: false,
spillover_lines,
};
};
@@ -1014,6 +1024,7 @@ where
out.push(self.render_border_line('└', '┴', '┘', &column_widths, border_style));
RenderedTableLines {
table_lines: out,
table_lines_prewrapped: true,
spillover_lines,
}
}
+21 -1
View File
@@ -1461,6 +1461,23 @@ fn table_falls_back_to_pipe_rendering_if_it_cannot_fit() {
assert!(!lines.iter().any(|line| line.contains('┌')));
}
#[test]
fn table_pipe_fallback_rows_wrap_in_narrow_width() {
let md = "| c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 |\n|---|---|---|---|---|---|---|---|---|---|\n| 111111 | 222222 | 333333 | 444444 | 555555 | 666666 | 777777 | 888888 | 999999 | 101010 |\n";
let text = crate::markdown_render::render_markdown_text_with_width(md, Some(20));
let lines: Vec<String> = text
.lines
.iter()
.map(|line| line.spans.iter().map(|span| span.content.clone()).collect())
.collect();
assert!(lines.first().is_some_and(|line| line.starts_with('|')));
assert!(
lines.len() > 3,
"expected wrapped pipe-fallback rows at narrow width, got {lines:?}"
);
}
#[test]
fn table_pipe_fallback_escapes_literal_pipes_in_cell_content() {
let md = "| c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 |\n|---|---|---|---|---|---|---|---|---|---|\n| keep | keep | keep | keep | keep | keep | keep | keep | a \\| b | keep |\n";
@@ -1472,7 +1489,10 @@ fn table_pipe_fallback_escapes_literal_pipes_in_cell_content() {
.collect();
assert!(lines.first().is_some_and(|line| line.starts_with('|')));
assert!(lines.iter().any(|line| line.contains("a \\| b")));
assert!(
lines.iter().any(|line| line.contains("\\|")),
"expected escaped pipe marker to be preserved in wrapped fallback rows: {lines:?}"
);
}
#[test]
+2 -1
View File
@@ -306,9 +306,10 @@ impl StreamCore {
};
let mut stable_prefix_render = Vec::new();
append_markdown_agent(
append_markdown_agent_with_cwd(
&self.raw_source[..last_nonblank_start],
self.width,
Some(self.cwd.as_path()),
&mut stable_prefix_render,
);
self.rendered_lines