tui: fix cursor desync on line wrap

Use unicode display width (matching ratatui's Wrap behavior) instead
of chars().count() for both wrapped_height calculation and cursor
positioning. The mismatch caused the cursor to drift when input
wrapped to multiple lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kent Overstreet 2026-03-19 00:30:45 -04:00
parent f83325b44d
commit 5308c8e3a4

View file

@ -249,8 +249,9 @@ fn wrapped_height(line: &str, width: usize) -> usize {
if width == 0 || line.is_empty() { if width == 0 || line.is_empty() {
return 1; return 1;
} }
let chars = line.chars().count(); // Use unicode display width to match ratatui's Wrap behavior
((chars + width - 1) / width).max(1) let w = ratatui::text::Line::raw(line).width();
((w + width - 1) / width).max(1)
} }
/// How many visual lines a ratatui Line occupies at a given width. /// How many visual lines a ratatui Line occupies at a given width.
@ -824,9 +825,10 @@ impl App {
for line in &cursor_lines[..n - 1] { for line in &cursor_lines[..n - 1] {
visual_row += wrapped_height(line, w) as u16; visual_row += wrapped_height(line, w) as u16;
} }
let last_chars = cursor_lines[n - 1].chars().count(); // Use unicode display width to match ratatui's wrapping
let col = if w > 0 { last_chars % w } else { last_chars }; let last_width = ratatui::text::Line::raw(cursor_lines[n - 1]).width();
visual_row += if w > 0 { (last_chars / w) as u16 } else { 0 }; let col = if w > 0 { last_width % w } else { last_width };
visual_row += if w > 0 { (last_width / w) as u16 } else { 0 };
let cursor_x = col as u16 + input_area.x; let cursor_x = col as u16 + input_area.x;
let cursor_y = visual_row + input_area.y; let cursor_y = visual_row + input_area.y;
if cursor_y < input_area.y + input_area.height { if cursor_y < input_area.y + input_area.height {