aboutsummaryrefslogtreecommitdiff
path: root/src/pane
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:17 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:17 +0300
commit3ecacfddae86d603bfd0d1a0c01602b1d34b1df3 (patch)
tree277941c065295af2bd161c6be6fd7a2555c1f060 /src/pane
parenta36da363f14639085bec91d7a378d6c2eeaa6c58 (diff)
feat: add direct terminal attach stream
Diffstat (limited to 'src/pane')
-rw-r--r--src/pane/terminal.rs108
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();