fix: render OSC 4 palette overrides instead of forwarding the index (#2162)
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
This commit is contained in:
parent
ea047db8e4
commit
f83980db5c
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue