From 96acb8a74e14897faa36d66cd50768c0b0978e59 Mon Sep 17 00:00:00 2001 From: dedrisian-oai Date: Wed, 8 Oct 2025 11:42:09 -0700 Subject: [PATCH] Fix transcript mode rendering issue when showing tab chars (#4911) There's a weird rendering issue with transcript mode: Tab chars bleed through when scrolling up/down. e.g. `nl -ba ...` adds tab chars to each line, which make scrolling look glitchy in transcript mode. Before: https://github.com/user-attachments/assets/631ee7fc-6083-4d35-aaf0-a0b08e734470 After: https://github.com/user-attachments/assets/bbba6111-4bfc-4862-8357-0f51aa2a21ac --- codex-rs/ansi-escape/src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/codex-rs/ansi-escape/src/lib.rs b/codex-rs/ansi-escape/src/lib.rs index 68ea5e9a..b47cf14f 100644 --- a/codex-rs/ansi-escape/src/lib.rs +++ b/codex-rs/ansi-escape/src/lib.rs @@ -3,11 +3,30 @@ use ansi_to_tui::IntoText; use ratatui::text::Line; use ratatui::text::Text; +// Expand tabs in a best-effort way for transcript rendering. +// Tabs can interact poorly with left-gutter prefixes in our TUI and CLI +// transcript views (e.g., `nl` separates line numbers from content with a tab). +// Replacing tabs with spaces avoids odd visual artifacts without changing +// semantics for our use cases. +fn expand_tabs(s: &str) -> std::borrow::Cow<'_, str> { + if s.contains('\t') { + // Keep it simple: replace each tab with 4 spaces. + // We do not try to align to tab stops since most usages (like `nl`) + // look acceptable with a fixed substitution and this avoids stateful math + // across spans. + std::borrow::Cow::Owned(s.replace('\t', " ")) + } else { + std::borrow::Cow::Borrowed(s) + } +} + /// This function should be used when the contents of `s` are expected to match /// a single line. If multiple lines are found, a warning is logged and only the /// first line is returned. pub fn ansi_escape_line(s: &str) -> Line<'static> { - let text = ansi_escape(s); + // Normalize tabs to spaces to avoid odd gutter collisions in transcript mode. + let s = expand_tabs(s); + let text = ansi_escape(&s); match text.lines.as_slice() { [] => "".into(), [only] => only.clone(),