From 00f6b8c4c58b697693e74bcf3c56a064da7ce12d Mon Sep 17 00:00:00 2001 From: derek Date: Wed, 8 Oct 2025 14:23:05 +0800 Subject: [PATCH] Fix diff_buffers clear-to-end when deleting wide graphemes --- codex-rs/tui/src/custom_terminal.rs | 38 +++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/custom_terminal.rs b/codex-rs/tui/src/custom_terminal.rs index bbd8900632..5e64189903 100644 --- a/codex-rs/tui/src/custom_terminal.rs +++ b/codex-rs/tui/src/custom_terminal.rs @@ -418,14 +418,19 @@ fn diff_buffers<'a>(a: &'a Buffer, b: &'a Buffer) -> Vec> { let row = &next_buffer[row_start..row_end]; let bg = row.last().map(|cell| cell.bg).unwrap_or(Color::Reset); - let x = row - .iter() - .rposition(|cell| { - cell.symbol() != " " || cell.bg != bg || cell.modifier != Modifier::empty() - }) - .unwrap_or(0); + let mut last_nonblank: Option = None; + let mut col = 0usize; + while col < row.len() { + let cell = &row[col]; + let width = cell.symbol().width().max(1); + if cell.symbol() != " " || cell.bg != bg || cell.modifier != Modifier::empty() { + last_nonblank = Some(col + width.saturating_sub(1)); + } + col += width; + } + let x = last_nonblank.unwrap_or(0); last_nonblank_column[y as usize] = x as u16; - if x < (a.area.width as usize).saturating_sub(1) { + if x + 1 < row.len() { let (x_abs, y_abs) = a.pos_of(row_start + x + 1); updates.push(DrawCommand::ClearToEnd { x: x_abs, @@ -592,6 +597,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; use ratatui::layout::Rect; + use ratatui::style::Style; #[test] fn diff_buffers_does_not_emit_clear_to_end_for_full_width_row() { @@ -620,4 +626,22 @@ mod tests { "expected diff_buffers to update the final cell; commands: {commands:?}", ); } + + #[test] + fn diff_buffers_clear_to_end_starts_after_wide_char() { + let area = Rect::new(0, 0, 10, 1); + let mut previous = Buffer::empty(area); + let mut next = Buffer::empty(area); + + previous.set_string(0, 0, "中文", Style::default()); + next.set_string(0, 0, "中", Style::default()); + + let commands = diff_buffers(&previous, &next); + assert!( + commands + .iter() + .any(|command| matches!(command, DrawCommand::ClearToEnd { x: 2, y: 0, .. })), + "expected clear-to-end to start after the remaining wide char; commands: {commands:?}" + ); + } }