diff --git a/codex-rs/tui/src/insert_history.rs b/codex-rs/tui/src/insert_history.rs index 2543b56bac..5f65ab3143 100644 --- a/codex-rs/tui/src/insert_history.rs +++ b/codex-rs/tui/src/insert_history.rs @@ -135,7 +135,9 @@ where let line_wrapped = match wrap_policy { HistoryLineWrapPolicy::Terminal => vec![line.clone()], HistoryLineWrapPolicy::PreWrap - if line_contains_url_like(line) && !line_has_mixed_url_and_non_url_tokens(line) => + if is_preformatted_box_table_line(line) + || (line_contains_url_like(line) + && !line_has_mixed_url_and_non_url_tokens(line)) => { vec![line.clone()] } @@ -246,6 +248,19 @@ where Ok(()) } +fn is_preformatted_box_table_line(line: &Line<'_>) -> bool { + let text: String = line + .spans + .iter() + .map(|span| span.content.as_ref()) + .collect(); + let trimmed = text.trim_start(); + (trimmed.starts_with('│') && trimmed.matches('│').count() >= 2) + || trimmed.starts_with('┌') + || trimmed.starts_with('├') + || trimmed.starts_with('└') +} + /// Render a single wrapped history line: clear continuation rows for wide lines, /// set foreground/background colors, and write styled spans. Caller is responsible /// for cursor positioning and any leading `\r\n`. @@ -442,6 +457,22 @@ mod tests { use ratatui::layout::Rect; use ratatui::style::Color; + #[test] + fn detects_preformatted_box_table_lines() { + assert!(is_preformatted_box_table_line(&Line::from( + "│ Link │ a (https://example.com/a) │" + ))); + assert!(is_preformatted_box_table_line(&Line::from( + "┌──────┬────────┐" + ))); + assert!(!is_preformatted_box_table_line(&Line::from( + "plain text with https://example.com" + ))); + assert!(!is_preformatted_box_table_line(&Line::from( + " │ quoted text with https://example.com" + ))); + } + #[test] fn writes_bold_then_regular_spans() { use ratatui::style::Stylize; diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 06417e31ea..8609ce66bc 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -770,7 +770,16 @@ impl Tui { } if area != terminal.viewport_area { - let clear_position = Position::new(/*x*/ 0, previous_area.y.min(area.y)); + let terminal_resized = size != terminal.last_known_screen_size; + // Terminal emulators can natively reflow visible scrollback before Codex gets the + // resize event. When that happens, ratatui's diff buffer has no record of the glyphs + // now sitting under blank cells, so repaint from a fully-cleared visible screen. + let clear_y = if terminal_resized { + 0 + } else { + previous_area.y.min(area.y) + }; + let clear_position = Position::new(/*x*/ 0, clear_y); terminal.set_viewport_area(area); terminal.clear_after_position(clear_position)?; needs_full_repaint = true;