From e6f5952a96715e476e45f5fd4948b6a3e2e04487 Mon Sep 17 00:00:00 2001 From: Finn Eitreim <48069764+feitreim@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:43:38 -0400 Subject: [PATCH 1/2] fix color ramp --- crates/terminal_view/src/terminal_element.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 1e07e1c49d43ac..4ceaad5b3ef8e9 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -550,13 +550,20 @@ impl TerminalElement { minimum_contrast: f32, ) -> TextRun { let flags = indexed.cell.flags; - let is_true_color = matches!(fg, terminal::alacritty_terminal::vte::ansi::Color::Spec(_)); + // Skip contrast adjustment when the application picked an exact color: + // 24-bit true color (`\e[38;2;R;G;Bm`) or a specific entry in the 256-color + // palette (`\e[38;5;Nm`) where N >= 16 (the 6x6x6 cube and 24-step grayscale + // ramp). Indices 0-15 still go through contrast adjustment since those map + // to theme-defined ANSI colors that can clash with the theme background. + let app_chose_exact_color = matches!( + fg, + terminal::alacritty_terminal::vte::ansi::Color::Spec(_) + | terminal::alacritty_terminal::vte::ansi::Color::Indexed(16..=255) + ); let mut fg = convert_color(&fg, colors); let bg = convert_color(&bg, colors); - // Skip contrast adjustment for true-color (24-bit RGB) foregrounds — the - // application chose that exact color. Also skip for decorative characters. - if !is_true_color && !Self::is_decorative_character(indexed.c) { + if !app_chose_exact_color && !Self::is_decorative_character(indexed.c) { fg = ensure_minimum_contrast(fg, bg, minimum_contrast); } From fddd32ae9cd4e5de7bff2ffdb1bd8f60ce1bf639 Mon Sep 17 00:00:00 2001 From: Finn Eitreim <48069764+feitreim@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:01:47 -0400 Subject: [PATCH 2/2] terminal_view: Test boundary of app-chosen-color skip Extract the contrast-skip predicate into is_app_chosen_exact_color and cover the 256-color palette boundary (15/16 and 231/232/255), true-color Spec, and theme-named variants so the chosen cutoff is locked in. --- crates/terminal_view/src/terminal_element.rs | 76 +++++++++++++++++--- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 4ceaad5b3ef8e9..8011ecc51343a1 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -539,6 +539,20 @@ impl TerminalElement { ) } + /// Whether the application explicitly picked this foreground color and does not + /// want it adjusted for contrast: 24-bit true color (`\e[38;2;R;G;Bm`) or a + /// specific entry in the 256-color palette (`\e[38;5;Nm`) where N >= 16 (the + /// 6x6x6 cube at 16..=231 and the 24-step grayscale ramp at 232..=255). + /// Indices 0..=15 still go through contrast adjustment since those map to + /// theme-defined ANSI colors that can clash with the theme background. + fn is_app_chosen_exact_color(fg: &terminal::alacritty_terminal::vte::ansi::Color) -> bool { + matches!( + fg, + terminal::alacritty_terminal::vte::ansi::Color::Spec(_) + | terminal::alacritty_terminal::vte::ansi::Color::Indexed(16..=255) + ) + } + /// Converts the Alacritty cell styles to GPUI text styles and background color. fn cell_style( indexed: &IndexedCell, @@ -550,20 +564,11 @@ impl TerminalElement { minimum_contrast: f32, ) -> TextRun { let flags = indexed.cell.flags; - // Skip contrast adjustment when the application picked an exact color: - // 24-bit true color (`\e[38;2;R;G;Bm`) or a specific entry in the 256-color - // palette (`\e[38;5;Nm`) where N >= 16 (the 6x6x6 cube and 24-step grayscale - // ramp). Indices 0-15 still go through contrast adjustment since those map - // to theme-defined ANSI colors that can clash with the theme background. - let app_chose_exact_color = matches!( - fg, - terminal::alacritty_terminal::vte::ansi::Color::Spec(_) - | terminal::alacritty_terminal::vte::ansi::Color::Indexed(16..=255) - ); + let skip_contrast = Self::is_app_chosen_exact_color(&fg); let mut fg = convert_color(&fg, colors); let bg = convert_color(&bg, colors); - if !app_chose_exact_color && !Self::is_decorative_character(indexed.c) { + if !skip_contrast && !Self::is_decorative_character(indexed.c) { fg = ensure_minimum_contrast(fg, bg, minimum_contrast); } @@ -1785,6 +1790,55 @@ mod tests { assert!(!TerminalElement::is_decorative_character(' ')); } + #[test] + fn test_is_app_chosen_exact_color() { + use terminal::alacritty_terminal::vte::ansi::{Color, NamedColor, Rgb}; + + // Indices 0..=15 are theme-overridable ANSI colors; contrast adjustment must still apply. + assert!(!TerminalElement::is_app_chosen_exact_color( + &Color::Indexed(0) + )); + assert!(!TerminalElement::is_app_chosen_exact_color( + &Color::Indexed(15) + )); + + // Boundary: index 16 is the first entry of the 6x6x6 cube — application-chosen. + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed( + 16 + ))); + // Interior of the cube. + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed( + 17 + ))); + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed( + 231 + ))); + // Grayscale ramp boundaries. + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed( + 232 + ))); + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Indexed( + 255 + ))); + + // 24-bit true color is always application-chosen. + assert!(TerminalElement::is_app_chosen_exact_color(&Color::Spec( + Rgb { + r: 10, + g: 20, + b: 30 + } + ))); + + // Named colors are theme-defined and must go through contrast adjustment. + assert!(!TerminalElement::is_app_chosen_exact_color(&Color::Named( + NamedColor::Red + ))); + assert!(!TerminalElement::is_app_chosen_exact_color(&Color::Named( + NamedColor::Foreground + ))); + } + #[test] fn test_contrast_adjustment_logic() { // Test the core contrast adjustment logic without needing full app context