aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ghostty/mod.rs15
-rw-r--r--src/pane/terminal.rs106
2 files changed, 116 insertions, 5 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index 685c0735..ef67b86f 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -865,6 +865,21 @@ impl Terminal {
}
}
+ pub fn default_palette(&self) -> Result<[RgbColor; 256], Error> {
+ let mut out = [ffi::GhosttyColorRgb::default(); 256];
+ // SAFETY: self.raw is a live terminal handle, and out is exactly the
+ // 256-entry array this data kind writes.
+ unsafe {
+ ffi::ghostty_terminal_get(
+ self.raw,
+ ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT,
+ out.as_mut_ptr().cast(),
+ )
+ .into_result()?;
+ }
+ Ok(out.map(Into::into))
+ }
+
pub fn resize(
&mut self,
cols: u16,
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs
index 58de71ce..3e43cf06 100644
--- a/src/pane/terminal.rs
+++ b/src/pane/terminal.rs
@@ -1942,6 +1942,9 @@ impl GhosttyPaneTerminal {
.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 palette_overrides = colors
+ .zip(terminal.default_palette().ok())
+ .and_then(|(colors, default)| PaletteOverrides::new(&colors.palette, &default));
let hide_kitty_placeholders = crate::kitty_graphics::is_enabled();
let mut row_iterator = match crate::ghostty::RowIterator::new() {
@@ -1976,6 +1979,7 @@ impl GhosttyPaneTerminal {
default_bg,
resolved_fg,
resolved_bg,
+ palette_overrides.as_ref(),
);
let symbol = match ghostty_buffer_symbol_into(
&cells,
@@ -2201,6 +2205,9 @@ fn ghostty_collect_dirty_patch(
.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 palette_overrides = colors
+ .zip(terminal.default_palette().ok())
+ .and_then(|(colors, default)| PaletteOverrides::new(&colors.palette, &default));
let hide_kitty_placeholders = crate::kitty_graphics::is_enabled();
let Ok(mut row_iterator) = crate::ghostty::RowIterator::new() else {
@@ -2245,6 +2252,7 @@ fn ghostty_collect_dirty_patch(
default_bg,
resolved_fg,
resolved_bg,
+ palette_overrides.as_ref(),
);
let symbol = match ghostty_buffer_symbol_into(
&cells,
@@ -2773,11 +2781,12 @@ fn ghostty_cell_style(
default_bg: Option<Color>,
resolved_fg: Option<Color>,
resolved_bg: Option<Color>,
+ palette_overrides: Option<&PaletteOverrides>,
) -> Style {
let mut fg = basic
.style
.fg_color
- .map(ghostty_cell_color)
+ .map(|color| ghostty_cell_color(color, palette_overrides))
.or_else(|| cells.fg_color().ok().flatten().map(ghostty_color))
.or(default_fg);
let mut bg = cells
@@ -2785,7 +2794,7 @@ fn ghostty_cell_style(
.ok()
.flatten()
.or(basic.style.bg_color)
- .map(ghostty_cell_color)
+ .map(|color| ghostty_cell_color(color, palette_overrides))
.or_else(|| cells.bg_color().ok().flatten().map(ghostty_color))
.or(default_bg);
if basic.style.invisible {
@@ -2807,7 +2816,11 @@ fn ghostty_cell_style(
}
let mut style = ghostty_default_style(fg, bg);
- if let Some(underline_color) = basic.style.underline_color.map(ghostty_cell_color) {
+ if let Some(underline_color) = basic
+ .style
+ .underline_color
+ .map(|color| ghostty_cell_color(color, palette_overrides))
+ {
style = style.underline_color(underline_color);
}
let mut modifiers = Modifier::empty();
@@ -3040,9 +3053,44 @@ fn terminal_theme_color(color: crate::ghostty::RgbColor) -> crate::terminal_them
}
}
-fn ghostty_cell_color(color: crate::ghostty::CellColor) -> Color {
+// Palette entries the program redefined with OSC 4. Forwarding a palette index to the
+// host makes it resolve against the host's own palette, discarding the redefinition.
+// Only overridden entries become RGB; the rest stay indexed and keep following the
+// host theme. None when nothing was redefined, which is the common case.
+struct PaletteOverrides([Option<crate::ghostty::RgbColor>; 256]);
+
+impl PaletteOverrides {
+ fn new(
+ active: &[crate::ghostty::RgbColor; 256],
+ default: &[crate::ghostty::RgbColor; 256],
+ ) -> Option<Self> {
+ let mut overrides = [None; 256];
+ let mut any = false;
+ for (index, (active, default)) in active.iter().zip(default.iter()).enumerate() {
+ if active != default {
+ overrides[index] = Some(*active);
+ any = true;
+ }
+ }
+ any.then_some(Self(overrides))
+ }
+
+ fn get(&self, index: u8) -> Option<crate::ghostty::RgbColor> {
+ self.0[usize::from(index)]
+ }
+}
+
+fn ghostty_cell_color(
+ color: crate::ghostty::CellColor,
+ palette_overrides: Option<&PaletteOverrides>,
+) -> Color {
match color {
- crate::ghostty::CellColor::Palette(index) => Color::Indexed(index),
+ crate::ghostty::CellColor::Palette(index) => {
+ match palette_overrides.and_then(|overrides| overrides.get(index)) {
+ Some(color) => ghostty_color(color),
+ None => Color::Indexed(index),
+ }
+ }
crate::ghostty::CellColor::Rgb(color) => ghostty_color(color),
}
}
@@ -3121,6 +3169,54 @@ mod tests {
}
}
+ fn rgb(r: u8, g: u8, b: u8) -> crate::ghostty::RgbColor {
+ crate::ghostty::RgbColor { r, g, b }
+ }
+
+ #[test]
+ fn palette_overrides_are_none_without_an_osc4_write() {
+ let default = [rgb(1, 2, 3); 256];
+ assert!(PaletteOverrides::new(&default, &default).is_none());
+ }
+
+ #[test]
+ fn redefined_palette_entries_render_as_rgb_and_others_stay_indexed() {
+ let default = [rgb(1, 2, 3); 256];
+ let mut active = default;
+ active[18] = rgb(169, 177, 214);
+ let overrides = PaletteOverrides::new(&active, &default).expect("index 18 differs");
+
+ assert_eq!(
+ ghostty_cell_color(crate::ghostty::CellColor::Palette(18), Some(&overrides)),
+ Color::Rgb(169, 177, 214)
+ );
+ // Untouched entries keep being forwarded, so they still follow the host theme.
+ assert_eq!(
+ ghostty_cell_color(crate::ghostty::CellColor::Palette(19), Some(&overrides)),
+ Color::Indexed(19)
+ );
+ // ...and so does everything when the program never wrote a palette at all.
+ assert_eq!(
+ ghostty_cell_color(crate::ghostty::CellColor::Palette(18), None),
+ Color::Indexed(18)
+ );
+ }
+
+ #[test]
+ fn direct_rgb_cells_are_unaffected_by_palette_overrides() {
+ let default = [rgb(1, 2, 3); 256];
+ let mut active = default;
+ active[18] = rgb(169, 177, 214);
+ let overrides = PaletteOverrides::new(&active, &default).expect("index 18 differs");
+ assert_eq!(
+ ghostty_cell_color(
+ crate::ghostty::CellColor::Rgb(rgb(122, 162, 247)),
+ Some(&overrides)
+ ),
+ Color::Rgb(122, 162, 247)
+ );
+ }
+
fn wide_text_cells(text: &str) -> [crate::ghostty::ScreenTextCell; 2] {
[
crate::ghostty::ScreenTextCell {