terminal: Fix 8 bit colors (#54565)

Right now the terminal rendering code applies a minimum contrast value
(which is good, and for accessibility) to colors that appear, there was
already an exemption from this for 24bit color specification, because
the application explicitly asked for that color, so it should be
rendered that color.

My change changes that exemption to also include ansi colors 16-255,
because these are also set explicitly, the normal, non-exempt case is
now the default ansi colors, 0-15, these are set by the theme and are
usually specified as just 'red' or 'green', hence the importance of the
min contrast.

Heres what the gradient ramp from the original issue looks like now:

<img width="944" height="1123" alt="Screenshot 2026-04-22 at 6 45 43 PM"
src="https://github.com/user-attachments/assets/918d62db-ed8e-475c-9ec1-c60187ad4b5e"
/>

This matches ghostty and VSCodium from the original issue.

test prevents regressions but idk, maybe not nessecary.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #54396 

Release Notes:

- terminal: Improved 256 color ansi rendering
This commit is contained in:
Finn Eitreim authored and GitHub committed 2026-04-30 14:01:32 +00:00
1 parent 4acb9d4ac6
commit 77cfc5dd38
1 file changed
+65 -4
+65 -4
View File
@@ -538,6 +538,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,
@@ -549,13 +563,11 @@ 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 skip_contrast = Self::is_app_chosen_exact_color(&fg);
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 !skip_contrast && !Self::is_decorative_character(indexed.c) {
fg = ensure_minimum_contrast(fg, bg, minimum_contrast);
}
@@ -1819,6 +1831,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