mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Expand tabs when rendering TUI diffs (#32461)
## What changed - Replace tab characters in rendered diff spans with four spaces while keeping their wrapping width and style intact. - Ensure diff buffers never contain literal tab characters. ## Testing - Cover tab expansion and wrapping in `wrap_styled_spans`. - Update diff gallery snapshots at multiple terminal sizes. GitOrigin-RevId: 2159e84991b844ea8c4c57dfad52bc3fe7a9059f
This commit is contained in:
@@ -48,8 +48,10 @@ use std::path::PathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
/// Replacement for a tab character in rendered diff content.
|
||||
const TAB_REPLACEMENT: &str = " ";
|
||||
/// Display width of a tab character in columns.
|
||||
const TAB_WIDTH: usize = 4;
|
||||
const TAB_WIDTH: usize = TAB_REPLACEMENT.len();
|
||||
|
||||
// -- Diff background palette --------------------------------------------------
|
||||
//
|
||||
@@ -989,7 +991,10 @@ fn wrap_styled_spans(spans: &[RtSpan<'static>], max_cols: usize) -> Vec<Vec<RtSp
|
||||
break;
|
||||
};
|
||||
let ch_len = ch.len_utf8();
|
||||
current_line.push(RtSpan::styled(remaining[..ch_len].to_string(), style));
|
||||
current_line.push(RtSpan::styled(
|
||||
remaining[..ch_len].replace('\t', TAB_REPLACEMENT),
|
||||
style,
|
||||
));
|
||||
// Use fallback width 1 (not 0) so this branch always advances
|
||||
// even if `ch` has unknown/zero display width.
|
||||
col = ch.width().unwrap_or(if ch == '\t' { TAB_WIDTH } else { 1 });
|
||||
@@ -998,7 +1003,7 @@ fn wrap_styled_spans(spans: &[RtSpan<'static>], max_cols: usize) -> Vec<Vec<RtSp
|
||||
}
|
||||
|
||||
let (chunk, rest) = remaining.split_at(byte_end);
|
||||
current_line.push(RtSpan::styled(chunk.to_string(), style));
|
||||
current_line.push(RtSpan::styled(chunk.replace('\t', TAB_REPLACEMENT), style));
|
||||
col += chars_col;
|
||||
remaining = rest;
|
||||
|
||||
@@ -1370,6 +1375,15 @@ mod tests {
|
||||
.render_ref(f.area(), f.buffer_mut())
|
||||
})
|
||||
.expect("draw");
|
||||
assert!(
|
||||
terminal
|
||||
.backend()
|
||||
.buffer()
|
||||
.content()
|
||||
.iter()
|
||||
.all(|cell| !cell.symbol().contains('\t')),
|
||||
"diff buffer should not contain literal tabs"
|
||||
);
|
||||
assert_snapshot!(name, terminal.backend());
|
||||
}
|
||||
|
||||
@@ -2351,12 +2365,15 @@ mod tests {
|
||||
fn wrap_styled_spans_tabs_have_visible_width() {
|
||||
// A tab should count as TAB_WIDTH columns, not zero.
|
||||
// With max_cols=8, a tab (4 cols) + "abcde" (5 cols) = 9 cols → must wrap.
|
||||
let spans = vec![RtSpan::raw("\tabcde")];
|
||||
let style = Style::default().fg(Color::Green);
|
||||
let spans = vec![RtSpan::styled("\tabcde", style)];
|
||||
let result = wrap_styled_spans(&spans, /*max_cols*/ 8);
|
||||
assert!(
|
||||
result.len() >= 2,
|
||||
"tab + 5 chars should exceed 8 cols and wrap, got {} line(s): {result:?}",
|
||||
result.len()
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![
|
||||
vec![RtSpan::styled(" abcd", style)],
|
||||
vec![RtSpan::styled("e", style)],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2373,7 +2390,7 @@ mod tests {
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(line_text, vec!["abcd", "\t", "界"]);
|
||||
assert_eq!(line_text, vec!["abcd", " ", "界"]);
|
||||
|
||||
let line_width = |line: &[RtSpan<'static>]| -> usize {
|
||||
line.iter()
|
||||
|
||||
@@ -4,9 +4,9 @@ expression: terminal.backend()
|
||||
---
|
||||
"• Edited 6 files (+9 -9) "
|
||||
" └ assets/banner.txt (+3 -0) "
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(15, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(13, " "), (15, " ")]
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(18, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(16, " "), (18, " ")]
|
||||
" "
|
||||
" └ examples/new_sample.rs (+3 -0) "
|
||||
" 1 +pub fn greet(name: &str) { "
|
||||
@@ -20,8 +20,8 @@ expression: terminal.backend()
|
||||
" "
|
||||
" └ scripts/calc.txt → scripts/calc.py (+1 -1) "
|
||||
" 1 def add(a, b): "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 3 "
|
||||
" 4 print(add(1, 2)) "
|
||||
" "
|
||||
|
||||
@@ -4,9 +4,9 @@ expression: terminal.backend()
|
||||
---
|
||||
"• Edited 6 files (+9 -9) "
|
||||
" └ assets/banner.txt (+3 -0) "
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(15, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(13, " "), (15, " ")]
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(18, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(16, " "), (18, " ")]
|
||||
" "
|
||||
" └ examples/new_sample.rs (+3 -0) "
|
||||
" 1 +pub fn greet(name: &str) { "
|
||||
@@ -20,8 +20,8 @@ expression: terminal.backend()
|
||||
" "
|
||||
" └ scripts/calc.txt → scripts/calc.py (+1 -1) "
|
||||
" 1 def add(a, b): "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 3 "
|
||||
" 4 print(add(1, 2)) "
|
||||
" "
|
||||
|
||||
@@ -4,9 +4,9 @@ expression: terminal.backend()
|
||||
---
|
||||
"• Edited 6 files (+9 -9) "
|
||||
" └ assets/banner.txt (+3 -0) "
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(15, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(13, " "), (15, " ")]
|
||||
" 1 +HEADER VALUE "
|
||||
" 2 +rocket 🚀 " Hidden by multi-width symbols: [(18, " ")]
|
||||
" 3 +city 東京 " Hidden by multi-width symbols: [(16, " "), (18, " ")]
|
||||
" "
|
||||
" └ examples/new_sample.rs (+3 -0) "
|
||||
" 1 +pub fn greet(name: &str) { "
|
||||
@@ -20,8 +20,8 @@ expression: terminal.backend()
|
||||
" "
|
||||
" └ scripts/calc.txt → scripts/calc.py (+1 -1) "
|
||||
" 1 def add(a, b): "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 2 - return a + b "
|
||||
" 2 + return a + b + 42 "
|
||||
" 3 "
|
||||
" 4 print(add(1, 2)) "
|
||||
" "
|
||||
|
||||
Reference in New Issue
Block a user