diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 0bb0837c6edb926cdcda70a54889de313cbe94f1..d1c6b324498ce50f67ed124ebb133f300bd40c39 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -550,11 +550,13 @@ impl TerminalElement { minimum_contrast: f32, ) -> TextRun { let flags = indexed.cell.flags; + let is_true_color = matches!(fg, terminal::alacritty_terminal::vte::ansi::Color::Spec(_)); let mut fg = convert_color(&fg, colors); let bg = convert_color(&bg, colors); - // Only apply contrast adjustment to non-decorative characters - if !Self::is_decorative_character(indexed.c) { + // 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) { fg = ensure_minimum_contrast(fg, bg, minimum_contrast); } @@ -1848,6 +1850,42 @@ mod tests { ); } + #[test] + fn test_true_color_red_blue_not_washed_out_on_dark_bg() { + // Red and blue have inherently low perceptual luminance in APCA. + // Pure #ff0000 only achieves Lc ~35 against #1e1e1e — below the + // default Lc 45 threshold. ensure_minimum_contrast would lighten + // them, washing out the color. This is why cell_style skips the + // adjustment for Color::Spec (24-bit true color). + let dark_bg = gpui::Hsla { + h: 0.0, + s: 0.0, + l: 0.05, + a: 1.0, + }; + + for (name, r, g, b) in [ + ("red", 225, 80, 80), + ("blue", 80, 80, 225), + ("pure red", 255, 0, 0), + ] { + let color = terminal::rgba_color(r, g, b); + let contrast = apca_contrast(color, dark_bg).abs(); + assert!( + contrast < 45.0, + "{name} should have APCA < 45 on dark bg, got {contrast}", + ); + + let adjusted = ensure_minimum_contrast(color, dark_bg, 45.0); + assert!( + adjusted.l > color.l, + "{name} would be lightened by contrast adjustment (l: {} -> {})", + color.l, + adjusted.l, + ); + } + } + #[test] fn test_white_on_white_contrast_issue() { // This test reproduces the exact issue from the bug report