Automatically adjust ANSI color contrast (#34033)

Closes #33253 in a way that doesn't regress #32175 - namely,
automatically adjusts the contrast between the foreground and background
text in the terminal such that it's above a certain threshold. The
threshold is configurable in settings, and can be set to 0 to turn off
this feature and use exactly the colors the theme specifies even if they
are illegible.

## One Light Theme Before
<img width="220" alt="Screenshot 2025-07-07 at 6 00 47 PM"
src="https://github.com/user-attachments/assets/096754a6-f79f-4fea-a86e-cb7b8ff45d60"
/>

(Last row is highlighted because otherwise the text is unreadable; the
foreground and background are the same color.)

## One Light Theme After

(This is with the new default contrast adjustment setting.)

<img width="215" alt="Screenshot 2025-07-07 at 6 22 02 PM"
src="https://github.com/user-attachments/assets/b082fefe-76f5-4231-b704-ff387983a3cb"
/>

This approach was inspired by @mitchellh's use of automatic contrast
adjustment in [Ghostty](https://ghostty.org/) - thanks, Mitchell! The
main difference is that we're using APCA's formula instead of WCAG for
[these
reasons](https://khan-tw.medium.com/wcag2-are-you-still-using-it-ui-contrast-visibility-standard-readability-contrast-f34eb73e89ee).

Release Notes:

- Added automatic dynamic contrast adjustment for terminal foreground
and background colors
This commit is contained in:
Richard Feldman 2025-07-07 18:39:11 -04:00 committed by GitHub
parent 877ef5e1b1
commit 9b7632d5f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 669 additions and 8 deletions

View file

@ -49,6 +49,7 @@ pub struct TerminalSettings {
pub max_scroll_history_lines: Option<usize>,
pub toolbar: Toolbar,
pub scrollbar: ScrollbarSettings,
pub minimum_contrast: f32,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -229,6 +230,21 @@ pub struct TerminalSettingsContent {
pub toolbar: Option<ToolbarContent>,
/// Scrollbar-related settings
pub scrollbar: Option<ScrollbarSettingsContent>,
/// The minimum APCA perceptual contrast between foreground and background colors.
///
/// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
/// especially for dark mode. Values range from 0 to 106.
///
/// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
/// https://readtech.org/ARC/tests/bronze-simple-mode/
/// - 0: No contrast adjustment
/// - 45: Minimum for large fluent text (36px+)
/// - 60: Minimum for other content text
/// - 75: Minimum for body text
/// - 90: Preferred for body text
///
/// Default: 0 (no adjustment)
pub minimum_contrast: Option<f32>,
}
impl settings::Settings for TerminalSettings {
@ -237,7 +253,18 @@ impl settings::Settings for TerminalSettings {
type FileContent = TerminalSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
sources.json_merge()
let settings: Self = sources.json_merge()?;
// Validate minimum_contrast for APCA
if settings.minimum_contrast < 0.0 || settings.minimum_contrast > 106.0 {
anyhow::bail!(
"terminal.minimum_contrast must be between 0 and 106, but got {}. \
APCA values: 0 = no adjustment, 75 = recommended for body text, 106 = maximum contrast.",
settings.minimum_contrast
);
}
Ok(settings)
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {