From 48d8256006c7f12de7dcdeeebb4a37db4e9760f2 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 29 Apr 2026 17:29:31 -0300 Subject: [PATCH] fix(tui): keep markdown tables off wrap column --- codex-rs/tui/src/markdown_render/table.rs | 2 +- codex-rs/tui/src/markdown_render_tests.rs | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/markdown_render/table.rs b/codex-rs/tui/src/markdown_render/table.rs index bc48ea790c..ce4ee0e85e 100644 --- a/codex-rs/tui/src/markdown_render/table.rs +++ b/codex-rs/tui/src/markdown_render/table.rs @@ -94,7 +94,7 @@ pub(super) fn render_table_lines(rows: &[Vec], width: Option) -> return Vec::new(); } - let available_width = width.unwrap_or(usize::MAX / 4).max(1); + let available_width = width.unwrap_or(usize::MAX / 4).saturating_sub(1).max(1); let normalized_rows = normalize_table_rows(rows, column_count); let widths = desired_column_widths(&normalized_rows, column_count); diff --git a/codex-rs/tui/src/markdown_render_tests.rs b/codex-rs/tui/src/markdown_render_tests.rs index 23672b6714..62d49eef8d 100644 --- a/codex-rs/tui/src/markdown_render_tests.rs +++ b/codex-rs/tui/src/markdown_render_tests.rs @@ -28,6 +28,16 @@ fn plain_lines(text: &Text<'_>) -> Vec { .collect() } +fn assert_table_lines_leave_wrap_safety_column(lines: &[String], width: usize) { + assert!( + lines + .iter() + .filter(|line| line.contains('│') || line.contains('─')) + .all(|line| line.width() < width), + "table lines should not consume the final terminal column at width {width}: {lines:?}" + ); +} + fn table_fixture() -> &'static str { "| Area | Result |\n| --- | --- |\n| Streaming resize | This cell contains enough prose to wrap differently across widths. |\n| Scrollback preservation | SENTINEL_TABLE_VALUE_WITH_LONG_UNBREAKABLE_TOKEN |\n" } @@ -84,8 +94,8 @@ fn table_resize_lifecycle_renderer_uses_thin_borders_and_fits_widths() { lines .iter() .filter(|line| line.contains('│') || line.contains('─')) - .all(|line| line.width() <= width), - "table lines should fit width {width}: {lines:?}" + .all(|line| line.width() < width), + "table lines should fit inside width {width} without touching the final column: {lines:?}" ); assert!( lines.iter().all(|line| !line.starts_with("Row ")), @@ -130,6 +140,7 @@ fn table_resize_lifecycle_renderer_uses_vertical_fallback_only_at_tiny_width() { lines.iter().all(|line| !line.starts_with("Row ")), "vertical fallback should not render row headers: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 18); } #[test] @@ -149,6 +160,7 @@ fn table_readability_fallback_keeps_dense_large_table_boxed_when_wide() { lines.iter().all(|line| !line.starts_with("Row 31")), "wide large table should not use vertical fallback: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 140); } #[test] @@ -175,6 +187,7 @@ fn table_readability_fallback_uses_vertical_for_dense_large_table_when_narrow() lines.iter().all(|line| !line.starts_with("Row ")), "vertical fallback should not render row headers: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 64); } #[test] @@ -183,7 +196,7 @@ fn table_readability_fallback_wraps_vertical_values_under_value_column() { let rendered = render_markdown_text_with_width_and_cwd(markdown, Some(30), /*cwd*/ None); let lines = plain_lines(&rendered); - assert_eq!(lines[0], "┌──────────────┬─────────────┐"); + assert_eq!(lines[0], "┌──────────────┬────────────┐"); assert!( lines .iter() @@ -201,6 +214,7 @@ fn table_readability_fallback_wraps_vertical_values_under_value_column() { lines.iter().all(|line| !line.starts_with("Row ")), "vertical fallback should not render row headers: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 30); } #[test] @@ -213,6 +227,7 @@ fn table_readability_fallback_keeps_small_status_table_boxed_when_narrow() { lines.iter().any(|line| line.contains('┌')), "small status table should remain boxed at narrow widths: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 64); } #[test] @@ -225,6 +240,7 @@ fn table_readability_fallback_keeps_tiny_table_boxed() { lines.iter().any(|line| line.contains('┌')), "tiny table should remain boxed when there is enough width: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 40); } #[test] @@ -243,6 +259,7 @@ fn table_readability_fallback_uses_vertical_for_emoji_near_fit() { lines.iter().all(|line| !line.starts_with("Row ")), "emoji-heavy fallback should not render row headers: {lines:?}" ); + assert_table_lines_leave_wrap_safety_column(&lines, /*width*/ 41); } #[test]