diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 04:27:49 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 15:37:01 +0300 |
| commit | 46c65725036f1778a059f9dbf3939bc3be8c27c4 (patch) | |
| tree | ffef5a05d26eb250d79227ec24698ffc0cdd65d8 /src/pane.rs | |
| parent | 26ec677193ef7e207feaec19c3ed1504d6f23029 (diff) | |
fix: prevent ghostty pane redraw corruption on wide cells
Diffstat (limited to 'src/pane.rs')
| -rw-r--r-- | src/pane.rs | 102 |
1 files changed, 87 insertions, 15 deletions
diff --git a/src/pane.rs b/src/pane.rs index 2e014027..3a4c4864 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -13,6 +13,8 @@ use ratatui::{layout::Rect, Frame}; use tokio::sync::{mpsc, Notify}; use tracing::{debug, error, info, warn}; use tui_term::widget::PseudoTerminal; +#[cfg(feature = "ghostty-vt")] +use unicode_width::UnicodeWidthStr; use crate::detect::{Agent, AgentState}; use crate::events::AppEvent; @@ -965,22 +967,19 @@ impl GhosttyPaneTerminal { }; let mut x = 0u16; while x < area.width && cells.next() { + let wide = cells.wide().unwrap_or(crate::ghostty::CellWide::Narrow); let style = ghostty_cell_style(&cells, default_fg, default_bg); - let symbol = ghostty_cell_symbol(&cells).unwrap_or_else(|_| " ".to_string()); + let symbol = ghostty_buffer_symbol(&cells, wide) + .unwrap_or_else(|_| ghostty_blank_symbol_for_width(wide).to_string()); let cell = &mut buf[(area.x + x, area.y + y)]; + cell.reset(); cell.set_symbol(&symbol); cell.set_style(style); x += 1; } while x < area.width { let cell = &mut buf[(area.x + x, area.y + y)]; - cell.set_symbol(" "); - if let Some(bg) = default_bg { - cell.set_bg(bg); - } - if let Some(fg) = default_fg { - cell.set_fg(fg); - } + ghostty_reset_cell(cell, default_fg, default_bg); x += 1; } y += 1; @@ -988,13 +987,7 @@ impl GhosttyPaneTerminal { while y < area.height { for x in 0..area.width { let cell = &mut buf[(area.x + x, area.y + y)]; - cell.set_symbol(" "); - if let Some(bg) = default_bg { - cell.set_bg(bg); - } - if let Some(fg) = default_fg { - cell.set_fg(fg); - } + ghostty_reset_cell(cell, default_fg, default_bg); } y += 1; } @@ -1454,6 +1447,60 @@ fn ghostty_cell_symbol( } #[cfg(feature = "ghostty-vt")] +fn ghostty_blank_symbol_for_width(wide: crate::ghostty::CellWide) -> &'static str { + match wide { + crate::ghostty::CellWide::Wide => " ", + crate::ghostty::CellWide::SpacerTail => "", + crate::ghostty::CellWide::Narrow | crate::ghostty::CellWide::SpacerHead => " ", + } +} + +#[cfg(feature = "ghostty-vt")] +fn ghostty_normalize_buffer_symbol(symbol: &str, wide: crate::ghostty::CellWide) -> String { + let expected_width = match wide { + crate::ghostty::CellWide::Wide => 2, + crate::ghostty::CellWide::Narrow | crate::ghostty::CellWide::SpacerHead => 1, + crate::ghostty::CellWide::SpacerTail => 0, + }; + let actual_width = symbol.width(); + if actual_width == expected_width { + return symbol.to_string(); + } + ghostty_blank_symbol_for_width(wide).to_string() +} + +#[cfg(feature = "ghostty-vt")] +fn ghostty_buffer_symbol( + cells: &crate::ghostty::RowCellIter<'_>, + wide: crate::ghostty::CellWide, +) -> Result<String, crate::ghostty::Error> { + let symbol = match wide { + crate::ghostty::CellWide::SpacerTail => String::new(), + crate::ghostty::CellWide::SpacerHead => " ".to_string(), + crate::ghostty::CellWide::Narrow | crate::ghostty::CellWide::Wide => { + ghostty_cell_symbol(cells)? + } + }; + Ok(ghostty_normalize_buffer_symbol(&symbol, wide)) +} + +#[cfg(feature = "ghostty-vt")] +fn ghostty_reset_cell( + cell: &mut ratatui::buffer::Cell, + default_fg: Option<Color>, + default_bg: Option<Color>, +) { + cell.reset(); + cell.set_symbol(" "); + if let Some(bg) = default_bg { + cell.set_bg(bg); + } + if let Some(fg) = default_fg { + cell.set_fg(fg); + } +} + +#[cfg(feature = "ghostty-vt")] fn ghostty_cell_style( cells: &crate::ghostty::RowCellIter<'_>, default_fg: Option<Color>, @@ -2275,6 +2322,31 @@ mod tests { } #[cfg(feature = "ghostty-vt")] + #[test] + fn ghostty_normalize_buffer_symbol_uses_ratatui_width_contract() { + assert_eq!( + ghostty_normalize_buffer_symbol("🙂", crate::ghostty::CellWide::Wide), + "🙂" + ); + assert_eq!( + ghostty_normalize_buffer_symbol("a", crate::ghostty::CellWide::Wide), + " " + ); + assert_eq!( + ghostty_normalize_buffer_symbol("⌨️", crate::ghostty::CellWide::Narrow), + " " + ); + assert_eq!( + ghostty_normalize_buffer_symbol(" ", crate::ghostty::CellWide::SpacerTail), + "" + ); + assert_eq!( + ghostty_normalize_buffer_symbol("xx", crate::ghostty::CellWide::SpacerHead), + " " + ); + } + + #[cfg(feature = "ghostty-vt")] #[tokio::test] async fn focus_events_are_forwarded_when_enabled() { let (tx, mut rx) = mpsc::channel(4); |
