From 4846cb1324a6a9e46bc89250cf8a289b91866a9c Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 22 Jun 2026 14:30:16 +0300 Subject: fix: skip wide spacer cells in pane reads refs #698 --- src/ghostty/mod.rs | 24 ++++++++++++++++++++++-- src/pane/terminal.rs | 21 ++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 17f9af32..2b45e3d8 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -742,9 +742,11 @@ impl Terminal { }) } - pub fn screen_graphemes(&self, x: u16, y: u32) -> Result, Error> { + pub fn screen_cell(&self, x: u16, y: u32) -> Result<(CellWide, Vec), Error> { let grid_ref = self.grid_ref(ghostty_screen_point(x, y))?; - grid_ref_graphemes(&grid_ref) + let wide = grid_ref_wide(&grid_ref)?; + let graphemes = grid_ref_graphemes(&grid_ref)?; + Ok((wide, graphemes)) } fn viewport_graphemes_and_style(&self, x: u16, y: u32) -> Result<(Vec, CellStyle), Error> { @@ -1393,6 +1395,24 @@ fn grid_ref_graphemes(grid_ref: &ffi::GhosttyGridRef) -> Result, Error> Ok(buffer) } +fn grid_ref_wide(grid_ref: &ffi::GhosttyGridRef) -> Result { + let mut raw = ffi::GhosttyCell::default(); + unsafe { + ffi::ghostty_grid_ref_cell(grid_ref, &mut raw).into_result()?; + } + + let mut wide = ffi::GhosttyCellWide_GHOSTTY_CELL_WIDE_NARROW; + unsafe { + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_WIDE, + (&mut wide as *mut ffi::GhosttyCellWide).cast(), + ) + .into_result()?; + } + Ok(CellWide::from_raw(wide)) +} + fn kitty_placement_u32( iterator: ffi::GhosttyKittyGraphicsPlacementIterator, data: ffi::GhosttyKittyGraphicsPlacementData, diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index a87b9622..ed5d1061 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -1686,7 +1686,10 @@ fn ghostty_screen_row( ) -> Result { let mut line = String::new(); for x in 0..cols { - let graphemes = core.terminal.screen_graphemes(x, y)?; + let (wide, graphemes) = core.terminal.screen_cell(x, y)?; + if wide == crate::ghostty::CellWide::SpacerTail { + continue; + } if graphemes.is_empty() || graphemes.first().copied() == Some(crate::ghostty::KITTY_UNICODE_PLACEHOLDER) { @@ -1715,6 +1718,9 @@ fn ghostty_line_from_cells( fn ghostty_cell_symbol( cells: &crate::ghostty::RowCellIter<'_>, ) -> Result { + if cells.wide()? == crate::ghostty::CellWide::SpacerTail { + return Ok(String::new()); + } let text = cells.grapheme_text()?; if text.chars().next().map(u32::from) == Some(crate::ghostty::KITTY_UNICODE_PLACEHOLDER) { return Ok(" ".to_string()); @@ -2953,6 +2959,19 @@ mod tests { assert_eq!(pane.recent_unwrapped_text(3), "ABCDEFGHIJ"); } + #[test] + fn plain_text_reads_skip_wide_character_spacer_cells() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(40, 3, 100).unwrap(); + terminal.write("日本語テスト ABC 123".as_bytes()); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + assert_eq!(pane.visible_text(), "日本語テスト ABC 123\n"); + assert_eq!(pane.recent_text(3), "日本語テスト ABC 123\n"); + assert_eq!(pane.recent_unwrapped_text(3), "日本語テスト ABC 123"); + assert_eq!(pane.detection_text(), "日本語テスト ABC 123\n"); + } + #[test] fn visible_ansi_preserves_cell_style_sequences() { let (tx, _rx) = mpsc::channel(4); -- cgit v1.2.3