diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:17 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:17 +0300 |
| commit | 3ecacfddae86d603bfd0d1a0c01602b1d34b1df3 (patch) | |
| tree | 277941c065295af2bd161c6be6fd7a2555c1f060 /src/pane | |
| parent | a36da363f14639085bec91d7a378d6c2eeaa6c58 (diff) | |
feat: add direct terminal attach stream
Diffstat (limited to 'src/pane')
| -rw-r--r-- | src/pane/terminal.rs | 108 |
1 files changed, 102 insertions, 6 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index f0535c7a..efe9c7ce 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -88,6 +88,7 @@ pub(crate) struct GhosttyPaneTerminal { pub(crate) struct GhosttyPaneCore { pub terminal: crate::ghostty::Terminal, pub render_state: crate::ghostty::RenderState, + pub initial_default_foreground: Option<crate::ghostty::RgbColor>, pub initial_default_background: Option<crate::ghostty::RgbColor>, pub host_terminal_theme: crate::terminal_theme::TerminalTheme, pub transient_default_color_owner_pgid: Option<u32>, @@ -263,11 +264,12 @@ impl GhosttyPaneTerminal { let mut render_state = crate::ghostty::RenderState::new().map_err(|e| std::io::Error::other(e.to_string()))?; - let initial_default_background = render_state + let initial_colors = render_state .update(&terminal) .ok() - .and_then(|_| render_state.colors().ok()) - .map(|colors| colors.background); + .and_then(|_| render_state.colors().ok()); + let initial_default_foreground = initial_colors.map(|colors| colors.foreground); + let initial_default_background = initial_colors.map(|colors| colors.background); let mut key_encoder = crate::ghostty::KeyEncoder::new().map_err(|e| std::io::Error::other(e.to_string()))?; key_encoder.set_from_terminal(&terminal); @@ -275,6 +277,7 @@ impl GhosttyPaneTerminal { core: Mutex::new(GhosttyPaneCore { terminal, render_state, + initial_default_foreground, initial_default_background, host_terminal_theme: crate::terminal_theme::TerminalTheme::default(), transient_default_color_owner_pgid: None, @@ -705,6 +708,7 @@ impl GhosttyPaneTerminal { return; }; let host_theme = core.host_terminal_theme; + let initial_default_foreground = core.initial_default_foreground; let initial_default_background = core.initial_default_background; let GhosttyPaneCore { terminal, @@ -717,7 +721,9 @@ impl GhosttyPaneTerminal { let colors = render_state.colors().ok(); let default_bg = colors .and_then(|c| ghostty_default_bg(c.background, host_theme, initial_default_background)); - let default_fg = colors.map(|c| ghostty_color(c.foreground)); + let default_fg = colors + .and_then(|c| ghostty_default_fg(c.foreground, host_theme, initial_default_foreground)); + let resolved_fg = colors.map(|c| ghostty_color(c.foreground)); let resolved_bg = colors.map(|c| ghostty_color(c.background)); let mut row_iterator = match crate::ghostty::RowIterator::new() { @@ -745,7 +751,13 @@ 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, resolved_bg); + let style = ghostty_cell_style( + &cells, + default_fg, + default_bg, + resolved_fg, + resolved_bg, + ); let symbol = match ghostty_buffer_symbol_into( &cells, wide, @@ -1063,6 +1075,7 @@ fn ghostty_cell_style( cells: &crate::ghostty::RowCellIter<'_>, default_fg: Option<Color>, default_bg: Option<Color>, + resolved_fg: Option<Color>, resolved_bg: Option<Color>, ) -> Style { let style_data = cells.style().unwrap_or_default(); @@ -1092,7 +1105,7 @@ fn ghostty_cell_style( bg = resolved_bg; } if fg.is_none() { - fg = default_fg; + fg = resolved_fg; } std::mem::swap(&mut fg, &mut bg); } @@ -1127,6 +1140,24 @@ fn ghostty_cell_style( style.add_modifier(modifiers) } +fn ghostty_default_fg( + color: crate::ghostty::RgbColor, + host_theme: crate::terminal_theme::TerminalTheme, + initial_default_foreground: Option<crate::ghostty::RgbColor>, +) -> Option<Color> { + if let Some(host_foreground) = host_theme.foreground { + if host_foreground == terminal_theme_color(color) { + None + } else { + Some(ghostty_color(color)) + } + } else if initial_default_foreground.is_some_and(|initial| initial != color) { + Some(ghostty_color(color)) + } else { + None + } +} + fn ghostty_default_bg( color: crate::ghostty::RgbColor, host_theme: crate::terminal_theme::TerminalTheme, @@ -1737,12 +1768,38 @@ mod tests { let buffer = terminal.backend().buffer(); assert_eq!(buffer[(0, 0)].symbol(), "h"); + assert_eq!(buffer[(0, 0)].style().fg, Some(Color::Reset)); assert_eq!(buffer[(0, 0)].style().bg, Some(Color::Reset)); assert_eq!(buffer[(2, 0)].symbol(), " "); + assert_eq!(buffer[(2, 0)].style().fg, Some(Color::Reset)); assert_eq!(buffer[(2, 0)].style().bg, Some(Color::Reset)); } #[test] + fn render_keeps_explicit_cell_foreground_when_host_is_unknown() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(20, 5, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + { + let mut core = pane.core.lock().unwrap(); + core.terminal.write(b"\x1b[38;2;68;85;102mhi\x1b[0m"); + } + + let backend = ratatui::backend::TestBackend::new(20, 5); + let mut terminal = ratatui::Terminal::new(backend).unwrap(); + terminal + .draw(|frame| pane.render(frame, Rect::new(0, 0, 20, 5), false)) + .unwrap(); + + let buffer = terminal.backend().buffer(); + let expected_fg = Some(Color::Rgb(0x44, 0x55, 0x66)); + assert_eq!(buffer[(0, 0)].symbol(), "h"); + assert_eq!(buffer[(0, 0)].style().fg, expected_fg); + assert_eq!(buffer[(2, 0)].symbol(), " "); + assert_eq!(buffer[(2, 0)].style().fg, Some(Color::Reset)); + } + + #[test] fn render_keeps_explicit_cell_background_when_host_is_unknown() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(20, 5, 0).unwrap(); @@ -1872,12 +1929,51 @@ mod tests { let buffer = terminal.backend().buffer(); assert_eq!(buffer[(0, 0)].symbol(), "h"); + assert_eq!(buffer[(0, 0)].style().fg, Some(Color::Reset)); assert_eq!(buffer[(0, 0)].style().bg, Some(Color::Reset)); assert_eq!(buffer[(2, 0)].symbol(), " "); + assert_eq!(buffer[(2, 0)].style().fg, Some(Color::Reset)); assert_eq!(buffer[(2, 0)].style().bg, Some(Color::Reset)); } #[test] + fn render_keeps_explicit_default_foreground_when_it_differs_from_host() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(20, 5, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let host_theme = crate::terminal_theme::TerminalTheme { + foreground: Some(crate::terminal_theme::RgbColor { + r: 0xaa, + g: 0xbb, + b: 0xcc, + }), + background: Some(crate::terminal_theme::RgbColor { + r: 0x11, + g: 0x22, + b: 0x33, + }), + }; + pane.apply_host_terminal_theme(host_theme); + { + let mut core = pane.core.lock().unwrap(); + core.terminal.write(b"\x1b]10;rgb:44/55/66\x1b\\hi"); + } + + let backend = ratatui::backend::TestBackend::new(20, 5); + let mut terminal = ratatui::Terminal::new(backend).unwrap(); + terminal + .draw(|frame| pane.render(frame, Rect::new(0, 0, 20, 5), false)) + .unwrap(); + + let buffer = terminal.backend().buffer(); + let expected_fg = Some(Color::Rgb(0x44, 0x55, 0x66)); + assert_eq!(buffer[(0, 0)].symbol(), "h"); + assert_eq!(buffer[(0, 0)].style().fg, expected_fg); + assert_eq!(buffer[(2, 0)].symbol(), " "); + assert_eq!(buffer[(2, 0)].style().fg, expected_fg); + } + + #[test] fn render_keeps_explicit_default_background_when_it_differs_from_host() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(20, 5, 0).unwrap(); |
