From 8b38876c454a43898012cde57bf083e8a427146f Mon Sep 17 00:00:00 2001 From: kiakiraki Date: Sat, 1 Aug 2026 13:18:09 +0900 Subject: [PATCH] fix: expand copy-mode word-end window past a final wide glyph point_is_final_atom compared an atom's terminating column against a word-motion target's starting column. The two are equal for narrow cells but never for wide ones, so the read window stopped expanding and copy-mode `e` halted at the soft-wrap boundary on CJK text. --- src/pane/terminal.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 3f8d8aeae..e464554c0 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -855,14 +855,14 @@ impl RetainedTextBuffer { } fn point_is_final_atom(&self, point: TerminalTextPoint) -> bool { + // Word motion targets are atom start points, so compare against the + // final atom's start point. Comparing against `end_col` would never + // match a wide glyph, whose end column is one past its start. self.atoms .iter() .rev() .find(|atom| atom.point.is_some()) - .is_some_and(|atom| { - atom.point - .is_some_and(|start| start.row == point.row && atom.end_col == point.col) - }) + .is_some_and(|atom| atom.point == Some(point)) } } @@ -3291,6 +3291,30 @@ mod tests { ); } + #[test] + fn live_terminal_word_end_expands_through_a_long_wide_soft_wrap() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(2, 3, 200).unwrap(); + let word = "界".repeat(66); + terminal.write(word.as_bytes()); + let pane = PaneTerminal::new(GhosttyPaneTerminal::new(terminal, tx).unwrap()); + let text_match = pane.search_text_matches(&word, true)[0]; + + // The word end sits on the head cell of the final wide glyph, past the + // initial read window, so the window has to expand to reach it. + assert_eq!( + pane.word_motion_target( + text_match.start.row, + text_match.start.col, + TerminalWordMotion::NextEnd, + ), + Some(TerminalTextPoint { + row: text_match.end.row, + col: 0, + }) + ); + } + fn current_palette_color(pane: &GhosttyPaneTerminal, index: u8) -> crate::ghostty::RgbColor { let mut core = pane.core.lock().unwrap(); let GhosttyPaneCore {