diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-05 00:41:12 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-05 00:42:14 +0300 |
| commit | acfbb7c85a44d28432512772e4070428df60cfd6 (patch) | |
| tree | b9adc71ef907fc865001a0c4fb976781e419c426 /src/pane | |
| parent | b4b69b3539fc02d855c9b626cb205074e1541c78 (diff) | |
fix: enable grapheme clusters in panes
refs #243
Diffstat (limited to 'src/pane')
| -rw-r--r-- | src/pane/terminal.rs | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index f2fceaa8..cf8506ba 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -2432,6 +2432,8 @@ mod tests { #[test] fn ghostty_normalize_buffer_symbol_prefers_grapheme_width_when_metadata_disagrees() { const WIDE_GRAPHEME: &str = "🙂"; + const FLAG_GRAPHEME: &str = "🇧🇷"; + const FAMILY_GRAPHEME: &str = "👨👩👧"; const VS16_GRAPHEME: &str = "⚠️"; const EMOJI_GRAPHEME: &str = "💳"; @@ -2444,6 +2446,14 @@ mod tests { " " ); assert_eq!( + ghostty_normalize_buffer_symbol(FLAG_GRAPHEME, crate::ghostty::CellWide::Wide), + FLAG_GRAPHEME + ); + assert_eq!( + ghostty_normalize_buffer_symbol(FAMILY_GRAPHEME, crate::ghostty::CellWide::Wide), + FAMILY_GRAPHEME + ); + assert_eq!( ghostty_normalize_buffer_symbol("⌨️", crate::ghostty::CellWide::Narrow), "⌨️" ); @@ -2465,6 +2475,74 @@ mod tests { ); } + fn render_cells_to_symbols( + terminal: &mut crate::ghostty::Terminal, + ) -> Vec<(crate::ghostty::CellWide, String)> { + let mut render_state = crate::ghostty::RenderState::new().unwrap(); + render_state.update(terminal).unwrap(); + + let mut row_iterator = crate::ghostty::RowIterator::new().unwrap(); + let mut rows = render_state + .populate_row_iterator(&mut row_iterator) + .unwrap(); + let mut row_cells = crate::ghostty::RowCells::new().unwrap(); + let mut grapheme_codepoints = Vec::new(); + let mut symbol_scratch = String::new(); + let mut out = Vec::new(); + + if rows.next() { + let mut cells = rows.populate_cells(&mut row_cells).unwrap(); + while cells.next() { + let wide = cells.wide().unwrap_or(crate::ghostty::CellWide::Narrow); + let symbol = ghostty_buffer_symbol_into( + &cells, + wide, + false, + &mut grapheme_codepoints, + &mut symbol_scratch, + ) + .unwrap() + .to_string(); + out.push((wide, symbol)); + } + } + + out + } + + #[test] + fn grapheme_cluster_mode_renders_flag_emoji_in_single_wide_cell() { + let mut terminal = crate::ghostty::Terminal::new(40, 1, 0).unwrap(); + terminal.enable_grapheme_cluster_mode().unwrap(); + terminal.write("🇧🇷".as_bytes()); + + let cells = render_cells_to_symbols(&mut terminal); + + assert!( + cells + .iter() + .any(|(wide, symbol)| *wide == crate::ghostty::CellWide::Wide && symbol == "🇧🇷"), + "expected a wide cell containing the full flag grapheme, got {cells:?}" + ); + } + + #[test] + fn grapheme_cluster_mode_renders_zwj_family_in_single_wide_cell() { + let mut terminal = crate::ghostty::Terminal::new(40, 1, 0).unwrap(); + terminal.enable_grapheme_cluster_mode().unwrap(); + terminal.write("👨\u{200d}👩\u{200d}👧".as_bytes()); + + let cells = render_cells_to_symbols(&mut terminal); + + assert!( + cells + .iter() + .any(|(wide, symbol)| *wide == crate::ghostty::CellWide::Wide + && symbol == "👨\u{200d}👩\u{200d}👧"), + "expected a wide cell containing the full ZWJ grapheme, got {cells:?}" + ); + } + #[test] fn pane_scrollback_controls_reach_top_without_ui_interference() { let (tx, _rx) = mpsc::channel(4); |
