mirror of
https://github.com/openai/codex.git
synced 2026-04-28 02:11:08 +03:00
fix(tui): promote windows terminal diff ansi16 to truecolor (#13016)
## Summary - Promote ANSI-16 to truecolor for diff rendering when running inside Windows Terminal - Respect explicit `FORCE_COLOR` override, skipping promotion when set - Extract a pure `diff_color_level_for_terminal` function for testability - Strip background tints from ANSI-16 diff output, rendering add/delete lines with foreground color only - Introduce `RichDiffColorLevel` to type-safely restrict background fills to truecolor and ansi256 ## Problem Windows Terminal fully supports 24-bit (truecolor) rendering but often does not provide the usual TERM metadata (`TERM`, `TERM_PROGRAM`, `COLORTERM`) in `cmd.exe`/PowerShell sessions. In those environments, `supports-color` can report only ANSI-16 support. The diff renderer therefore falls back to a 16-color palette, producing washed-out, hard-to-read diffs. The screenshots below demonstrate that both PowerShell and cmd.exe don't set any `*TERM*` environment variables. | PowerShell | cmd.exe | |---|---| | <img width="2032" height="1162" alt="SCR-20260226-nfvy" src="https://github.com/user-attachments/assets/59e968cc-4add-4c7b-a415-07163297e86a" /> | <img width="2032" height="1162" alt="SCR-20260226-nfyc" src="https://github.com/user-attachments/assets/d06b3e39-bf91-4ce3-9705-82bf9563a01b" /> | ## Mental model `StdoutColorLevel` (from `supports-color`) is the _detected_ capability. `DiffColorLevel` is the _intended_ capability for diff rendering. A new intermediary — `diff_color_level_for_terminal` — maps one to the other and is the single place where terminal-specific overrides live. Windows Terminal is detected two independent ways: the `TerminalName` parsed by `terminal_info()` and the raw presence of `WT_SESSION`. When `WT_SESSION` is present and `FORCE_COLOR` is not set, we promote unconditionally to truecolor. When `WT_SESSION` is absent but `TerminalName::WindowsTerminal` is detected, we promote only the ANSI-16 level (not `Unknown`). A single override helper — `has_force_color_override()` — checks whether `FORCE_COLOR` is set. When it is, both the `WT_SESSION` fast-path and the `TerminalName`-based promotion are suppressed, preserving explicit user intent. | PowerShell | cmd.exe | WSL | Bash for Windows | |---|---|---|---| |  |  |  |  | ## Non-goals - This does not change color detection for anything outside the diff renderer (e.g. the chat widget, markdown rendering). - This does not add a user-facing config knob; `FORCE_COLOR` already serves that role. ## Tradeoffs - The `has_wt_session` signal is intentionally kept separate from `TerminalName::WindowsTerminal`. `terminal_info()` is derived with `TERM_PROGRAM` precedence, so it can differ from raw `WT_SESSION`. - Real-world validation in this issue: in both `cmd.exe` and PowerShell, `TERM`/`TERM_PROGRAM`/`COLORTERM` were absent, so TERM-based capability hints were unavailable in those sessions. - Checking `FORCE_COLOR` for presence rather than parsing its value is a simplification. In practice `supports-color` has already parsed it, so our check is a coarse "did the user set _anything_?" gate. The effective color level still comes from `supports-color`. - When `WT_SESSION` is present without `FORCE_COLOR`, we promote to truecolor regardless of `stdout_level` (including `Unknown`). This is aggressive but correct: `WT_SESSION` is a strong signal that we're in Windows Terminal. - ANSI-16 add/delete backgrounds (bright green/red) overpower syntax-highlighted token colors, making diffs harder to read. Foreground-only cues (colored text, gutter signs) preserve readability on low-color terminals. ## Architecture ``` stdout_color_level() ──┐ terminal_info().name ──┤ WT_SESSION presence ──┼──▶ diff_color_level_for_terminal() ──▶ DiffColorLevel FORCE_COLOR presence ──┘ │ ▼ RichDiffColorLevel::from_diff_color_level() │ ┌──────────┴──────────┐ │ Some(TrueColor|256) │ → bg tints │ None (Ansi16) │ → fg only └─────────────────────┘ ``` `diff_color_level()` is the environment-reading entry point; it gathers the four runtime signals and delegates to the pure, testable `diff_color_level_for_terminal()`. ## Observability No new logs or metrics. Incorrect color selection is immediately visible as broken diff rendering; the test suite covers the decision matrix exhaustively. ## Tests Six new unit tests exercise every branch of `diff_color_level_for_terminal`: | Test | Inputs | Expected | |------|--------|----------| | `windows_terminal_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 + WindowsTerminal name | TrueColor | | `wt_session_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 + WT_SESSION only | TrueColor | | `non_windows_terminal_keeps_ansi16_diff_palette` | Ansi16 + WezTerm | Ansi16 | | `wt_session_promotes_unknown_color_level_to_truecolor` | Unknown + WT_SESSION | TrueColor | | `explicit_force_override_keeps_ansi16_on_windows_terminal` | Ansi16 + WindowsTerminal + FORCE_COLOR | Ansi16 | | `explicit_force_override_keeps_ansi256_on_windows_terminal` | Ansi256 + WT_SESSION + FORCE_COLOR | Ansi256 | | `ansi16_add_style_uses_foreground_only` | Dark + Ansi16 | fg=Green, bg=None | | (and any other new snapshot/assertion tests from commitsd757feeandd7c78b3) | | | ## Test plan - [x] Verify all new unit tests pass (`cargo test -p codex-tui --lib`) - [x] On Windows Terminal: confirm diffs render with truecolor backgrounds - [x] On Windows Terminal with `FORCE_COLOR` set: confirm promotion is disabled and output follows the forced `supports-color` level - [x] On macOS/Linux terminals: confirm no behavior change Fixes https://github.com/openai/codex/issues/12904 Fixes https://github.com/openai/codex/issues/12890 Fixes https://github.com/openai/codex/issues/12912 Fixes https://github.com/openai/codex/issues/12840
This commit is contained in:
@@ -249,6 +249,7 @@ pub fn terminal_info() -> TerminalInfo {
|
||||
/// type is split on whitespace to extract a program name plus optional version (for example,
|
||||
/// `ghostty 1.2.3`), while the client term name becomes the `TERM` capability string.
|
||||
/// - Otherwise, `TERM_PROGRAM` (plus `TERM_PROGRAM_VERSION`) drives the detected terminal name.
|
||||
/// This means `TERM_PROGRAM` can mask later probes (for example `WT_SESSION`).
|
||||
/// - Next, terminal-specific variables (WEZTERM, iTerm2, Apple Terminal, kitty, etc.) are checked.
|
||||
/// - Finally, `TERM` is used as the capability fallback with `TerminalName::Unknown`.
|
||||
///
|
||||
|
||||
@@ -83,6 +83,8 @@ use crate::terminal_palette::indexed_color;
|
||||
use crate::terminal_palette::rgb_color;
|
||||
use crate::terminal_palette::stdout_color_level;
|
||||
use codex_core::git_info::get_git_repo_root;
|
||||
use codex_core::terminal::TerminalName;
|
||||
use codex_core::terminal::terminal_info;
|
||||
use codex_protocol::protocol::FileChange;
|
||||
|
||||
/// Classifies a diff line for gutter sign rendering and style selection.
|
||||
@@ -110,6 +112,14 @@ enum DiffTheme {
|
||||
Light,
|
||||
}
|
||||
|
||||
/// Palette depth the diff renderer will target.
|
||||
///
|
||||
/// This is the *renderer's own* notion of color depth, derived from — but not
|
||||
/// identical to — the raw [`StdoutColorLevel`] reported by `supports-color`.
|
||||
/// The indirection exists because some terminals (notably Windows Terminal)
|
||||
/// advertise only ANSI-16 support while actually rendering truecolor sequences
|
||||
/// correctly; [`diff_color_level_for_terminal`] promotes those cases so the
|
||||
/// diff output uses the richer palette.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum DiffColorLevel {
|
||||
TrueColor,
|
||||
@@ -117,6 +127,22 @@ enum DiffColorLevel {
|
||||
Ansi16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum RichDiffColorLevel {
|
||||
TrueColor,
|
||||
Ansi256,
|
||||
}
|
||||
|
||||
impl RichDiffColorLevel {
|
||||
fn from_diff_color_level(level: DiffColorLevel) -> Option<Self> {
|
||||
match level {
|
||||
DiffColorLevel::TrueColor => Some(Self::TrueColor),
|
||||
DiffColorLevel::Ansi256 => Some(Self::Ansi256),
|
||||
DiffColorLevel::Ansi16 => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiffSummary {
|
||||
changes: HashMap<PathBuf, FileChange>,
|
||||
cwd: PathBuf,
|
||||
@@ -876,11 +902,75 @@ fn diff_theme() -> DiffTheme {
|
||||
diff_theme_for_bg(default_bg())
|
||||
}
|
||||
|
||||
/// Return the [`DiffColorLevel`] for the current terminal session.
|
||||
///
|
||||
/// This is the environment-reading adapter: it samples runtime signals
|
||||
/// (`supports-color` level, terminal name, `WT_SESSION`, and `FORCE_COLOR`)
|
||||
/// and forwards them to [`diff_color_level_for_terminal`].
|
||||
///
|
||||
/// Keeping env reads in this thin wrapper lets
|
||||
/// [`diff_color_level_for_terminal`] stay pure and easy to unit test.
|
||||
fn diff_color_level() -> DiffColorLevel {
|
||||
match stdout_color_level() {
|
||||
diff_color_level_for_terminal(
|
||||
stdout_color_level(),
|
||||
terminal_info().name,
|
||||
std::env::var_os("WT_SESSION").is_some(),
|
||||
has_force_color_override(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns whether `FORCE_COLOR` is explicitly set.
|
||||
fn has_force_color_override() -> bool {
|
||||
std::env::var_os("FORCE_COLOR").is_some()
|
||||
}
|
||||
|
||||
/// Map a raw [`StdoutColorLevel`] to a [`DiffColorLevel`] using
|
||||
/// Windows Terminal-specific truecolor promotion rules.
|
||||
///
|
||||
/// This helper is intentionally pure (no env access) so tests can validate
|
||||
/// the policy table by passing explicit inputs.
|
||||
///
|
||||
/// Windows Terminal fully supports 24-bit color but the `supports-color`
|
||||
/// crate often reports only ANSI-16 there because no `COLORTERM` variable
|
||||
/// is set. We detect Windows Terminal two ways — via `terminal_name`
|
||||
/// (parsed from `WT_SESSION` / `TERM_PROGRAM` by `terminal_info()`) and
|
||||
/// via the raw `has_wt_session` flag.
|
||||
///
|
||||
/// These signals are intentionally not equivalent: `terminal_name` is a
|
||||
/// derived classification with `TERM_PROGRAM` precedence, so `WT_SESSION`
|
||||
/// can be present while `terminal_name` is not `WindowsTerminal`.
|
||||
///
|
||||
/// When `WT_SESSION` is present, we promote to truecolor unconditionally
|
||||
/// unless `FORCE_COLOR` is set. This keeps Windows Terminal rendering rich
|
||||
/// by default while preserving explicit `FORCE_COLOR` user intent.
|
||||
///
|
||||
/// Outside `WT_SESSION`, only ANSI-16 is promoted for identified
|
||||
/// `WindowsTerminal` sessions; `Unknown` stays conservative.
|
||||
fn diff_color_level_for_terminal(
|
||||
stdout_level: StdoutColorLevel,
|
||||
terminal_name: TerminalName,
|
||||
has_wt_session: bool,
|
||||
has_force_color_override: bool,
|
||||
) -> DiffColorLevel {
|
||||
if has_wt_session && !has_force_color_override {
|
||||
return DiffColorLevel::TrueColor;
|
||||
}
|
||||
|
||||
let base = match stdout_level {
|
||||
StdoutColorLevel::TrueColor => DiffColorLevel::TrueColor,
|
||||
StdoutColorLevel::Ansi256 => DiffColorLevel::Ansi256,
|
||||
StdoutColorLevel::Ansi16 | StdoutColorLevel::Unknown => DiffColorLevel::Ansi16,
|
||||
};
|
||||
|
||||
// Outside `WT_SESSION`, keep the existing Windows Terminal promotion for
|
||||
// ANSI-16 sessions that likely support truecolor.
|
||||
if stdout_level == StdoutColorLevel::Ansi16
|
||||
&& terminal_name == TerminalName::WindowsTerminal
|
||||
&& !has_force_color_override
|
||||
{
|
||||
DiffColorLevel::TrueColor
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
@@ -908,10 +998,11 @@ fn diff_color_level() -> DiffColorLevel {
|
||||
/// Context lines intentionally leave the background unset so the terminal
|
||||
/// default shows through.
|
||||
fn style_line_bg_for(kind: DiffLineType, theme: DiffTheme, color_level: DiffColorLevel) -> Style {
|
||||
match kind {
|
||||
DiffLineType::Insert => Style::default().bg(add_line_bg(theme, color_level)),
|
||||
DiffLineType::Delete => Style::default().bg(del_line_bg(theme, color_level)),
|
||||
DiffLineType::Context => Style::default(),
|
||||
match (kind, RichDiffColorLevel::from_diff_color_level(color_level)) {
|
||||
(_, None) => Style::default(),
|
||||
(DiffLineType::Insert, Some(level)) => Style::default().bg(add_line_bg(theme, level)),
|
||||
(DiffLineType::Delete, Some(level)) => Style::default().bg(del_line_bg(theme, level)),
|
||||
(DiffLineType::Context, _) => Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,25 +1010,21 @@ fn style_context() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
fn add_line_bg(theme: DiffTheme, color_level: DiffColorLevel) -> Color {
|
||||
fn add_line_bg(theme: DiffTheme, color_level: RichDiffColorLevel) -> Color {
|
||||
match (theme, color_level) {
|
||||
(DiffTheme::Dark, DiffColorLevel::TrueColor) => rgb_color(DARK_TC_ADD_LINE_BG_RGB),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi256) => indexed_color(DARK_256_ADD_LINE_BG_IDX),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi16) => Color::Green,
|
||||
(DiffTheme::Light, DiffColorLevel::TrueColor) => rgb_color(LIGHT_TC_ADD_LINE_BG_RGB),
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi256) => indexed_color(LIGHT_256_ADD_LINE_BG_IDX),
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi16) => Color::LightGreen,
|
||||
(DiffTheme::Dark, RichDiffColorLevel::TrueColor) => rgb_color(DARK_TC_ADD_LINE_BG_RGB),
|
||||
(DiffTheme::Dark, RichDiffColorLevel::Ansi256) => indexed_color(DARK_256_ADD_LINE_BG_IDX),
|
||||
(DiffTheme::Light, RichDiffColorLevel::TrueColor) => rgb_color(LIGHT_TC_ADD_LINE_BG_RGB),
|
||||
(DiffTheme::Light, RichDiffColorLevel::Ansi256) => indexed_color(LIGHT_256_ADD_LINE_BG_IDX),
|
||||
}
|
||||
}
|
||||
|
||||
fn del_line_bg(theme: DiffTheme, color_level: DiffColorLevel) -> Color {
|
||||
fn del_line_bg(theme: DiffTheme, color_level: RichDiffColorLevel) -> Color {
|
||||
match (theme, color_level) {
|
||||
(DiffTheme::Dark, DiffColorLevel::TrueColor) => rgb_color(DARK_TC_DEL_LINE_BG_RGB),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi256) => indexed_color(DARK_256_DEL_LINE_BG_IDX),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi16) => Color::Red,
|
||||
(DiffTheme::Light, DiffColorLevel::TrueColor) => rgb_color(LIGHT_TC_DEL_LINE_BG_RGB),
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi256) => indexed_color(LIGHT_256_DEL_LINE_BG_IDX),
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi16) => Color::LightRed,
|
||||
(DiffTheme::Dark, RichDiffColorLevel::TrueColor) => rgb_color(DARK_TC_DEL_LINE_BG_RGB),
|
||||
(DiffTheme::Dark, RichDiffColorLevel::Ansi256) => indexed_color(DARK_256_DEL_LINE_BG_IDX),
|
||||
(DiffTheme::Light, RichDiffColorLevel::TrueColor) => rgb_color(LIGHT_TC_DEL_LINE_BG_RGB),
|
||||
(DiffTheme::Light, RichDiffColorLevel::Ansi256) => indexed_color(LIGHT_256_DEL_LINE_BG_IDX),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,19 +1036,17 @@ fn light_gutter_fg(color_level: DiffColorLevel) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn light_add_num_bg(color_level: DiffColorLevel) -> Color {
|
||||
fn light_add_num_bg(color_level: RichDiffColorLevel) -> Color {
|
||||
match color_level {
|
||||
DiffColorLevel::TrueColor => rgb_color(LIGHT_TC_ADD_NUM_BG_RGB),
|
||||
DiffColorLevel::Ansi256 => indexed_color(LIGHT_256_ADD_NUM_BG_IDX),
|
||||
DiffColorLevel::Ansi16 => Color::Green,
|
||||
RichDiffColorLevel::TrueColor => rgb_color(LIGHT_TC_ADD_NUM_BG_RGB),
|
||||
RichDiffColorLevel::Ansi256 => indexed_color(LIGHT_256_ADD_NUM_BG_IDX),
|
||||
}
|
||||
}
|
||||
|
||||
fn light_del_num_bg(color_level: DiffColorLevel) -> Color {
|
||||
fn light_del_num_bg(color_level: RichDiffColorLevel) -> Color {
|
||||
match color_level {
|
||||
DiffColorLevel::TrueColor => rgb_color(LIGHT_TC_DEL_NUM_BG_RGB),
|
||||
DiffColorLevel::Ansi256 => indexed_color(LIGHT_256_DEL_NUM_BG_IDX),
|
||||
DiffColorLevel::Ansi16 => Color::Red,
|
||||
RichDiffColorLevel::TrueColor => rgb_color(LIGHT_TC_DEL_NUM_BG_RGB),
|
||||
RichDiffColorLevel::Ansi256 => indexed_color(LIGHT_256_DEL_NUM_BG_IDX),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,13 +1054,23 @@ fn light_del_num_bg(color_level: DiffColorLevel) -> Color {
|
||||
/// tinted background so numbers contrast against the pastel line fill. On
|
||||
/// dark backgrounds a simple `DIM` modifier is sufficient.
|
||||
fn style_gutter_for(kind: DiffLineType, theme: DiffTheme, color_level: DiffColorLevel) -> Style {
|
||||
match (theme, kind) {
|
||||
(DiffTheme::Light, DiffLineType::Insert) => Style::default()
|
||||
match (
|
||||
theme,
|
||||
kind,
|
||||
RichDiffColorLevel::from_diff_color_level(color_level),
|
||||
) {
|
||||
(DiffTheme::Light, DiffLineType::Insert, None) => {
|
||||
Style::default().fg(light_gutter_fg(color_level))
|
||||
}
|
||||
(DiffTheme::Light, DiffLineType::Delete, None) => {
|
||||
Style::default().fg(light_gutter_fg(color_level))
|
||||
}
|
||||
(DiffTheme::Light, DiffLineType::Insert, Some(level)) => Style::default()
|
||||
.fg(light_gutter_fg(color_level))
|
||||
.bg(light_add_num_bg(color_level)),
|
||||
(DiffTheme::Light, DiffLineType::Delete) => Style::default()
|
||||
.bg(light_add_num_bg(level)),
|
||||
(DiffTheme::Light, DiffLineType::Delete, Some(level)) => Style::default()
|
||||
.fg(light_gutter_fg(color_level))
|
||||
.bg(light_del_num_bg(color_level)),
|
||||
.bg(light_del_num_bg(level)),
|
||||
_ => style_gutter_dim(),
|
||||
}
|
||||
}
|
||||
@@ -1001,26 +1096,38 @@ fn style_sign_del(theme: DiffTheme, color_level: DiffColorLevel) -> Style {
|
||||
/// Content style for insert lines (plain, non-syntax-highlighted text).
|
||||
fn style_add(theme: DiffTheme, color_level: DiffColorLevel) -> Style {
|
||||
match (theme, color_level) {
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi16) => Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(add_line_bg(theme, color_level)),
|
||||
(DiffTheme::Light, _) => Style::default().bg(add_line_bg(theme, color_level)),
|
||||
(DiffTheme::Dark, _) => Style::default()
|
||||
(_, DiffColorLevel::Ansi16) => Style::default().fg(Color::Green),
|
||||
(DiffTheme::Light, DiffColorLevel::TrueColor) => {
|
||||
Style::default().bg(add_line_bg(theme, RichDiffColorLevel::TrueColor))
|
||||
}
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi256) => {
|
||||
Style::default().bg(add_line_bg(theme, RichDiffColorLevel::Ansi256))
|
||||
}
|
||||
(DiffTheme::Dark, DiffColorLevel::TrueColor) => Style::default()
|
||||
.fg(Color::Green)
|
||||
.bg(add_line_bg(theme, color_level)),
|
||||
.bg(add_line_bg(theme, RichDiffColorLevel::TrueColor)),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi256) => Style::default()
|
||||
.fg(Color::Green)
|
||||
.bg(add_line_bg(theme, RichDiffColorLevel::Ansi256)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Content style for delete lines (plain, non-syntax-highlighted text).
|
||||
fn style_del(theme: DiffTheme, color_level: DiffColorLevel) -> Style {
|
||||
match (theme, color_level) {
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi16) => Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(del_line_bg(theme, color_level)),
|
||||
(DiffTheme::Light, _) => Style::default().bg(del_line_bg(theme, color_level)),
|
||||
(DiffTheme::Dark, _) => Style::default()
|
||||
(_, DiffColorLevel::Ansi16) => Style::default().fg(Color::Red),
|
||||
(DiffTheme::Light, DiffColorLevel::TrueColor) => {
|
||||
Style::default().bg(del_line_bg(theme, RichDiffColorLevel::TrueColor))
|
||||
}
|
||||
(DiffTheme::Light, DiffColorLevel::Ansi256) => {
|
||||
Style::default().bg(del_line_bg(theme, RichDiffColorLevel::Ansi256))
|
||||
}
|
||||
(DiffTheme::Dark, DiffColorLevel::TrueColor) => Style::default()
|
||||
.fg(Color::Red)
|
||||
.bg(del_line_bg(theme, color_level)),
|
||||
.bg(del_line_bg(theme, RichDiffColorLevel::TrueColor)),
|
||||
(DiffTheme::Dark, DiffColorLevel::Ansi256) => Style::default()
|
||||
.fg(Color::Red)
|
||||
.bg(del_line_bg(theme, RichDiffColorLevel::Ansi256)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,32 +1146,28 @@ mod tests {
|
||||
use ratatui::widgets::Paragraph;
|
||||
|
||||
#[test]
|
||||
fn dark_ansi16_add_style_has_contrast() {
|
||||
fn ansi16_add_style_uses_foreground_only() {
|
||||
let style = style_add(DiffTheme::Dark, DiffColorLevel::Ansi16);
|
||||
assert_eq!(style.fg, Some(Color::Black));
|
||||
assert_eq!(style.bg, Some(Color::Green));
|
||||
assert_ne!(style.fg, style.bg);
|
||||
assert_eq!(style.fg, Some(Color::Green));
|
||||
assert_eq!(style.bg, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_ansi16_del_style_has_contrast() {
|
||||
fn ansi16_del_style_uses_foreground_only() {
|
||||
let style = style_del(DiffTheme::Dark, DiffColorLevel::Ansi16);
|
||||
assert_eq!(style.fg, Some(Color::Black));
|
||||
assert_eq!(style.bg, Some(Color::Red));
|
||||
assert_ne!(style.fg, style.bg);
|
||||
assert_eq!(style.fg, Some(Color::Red));
|
||||
assert_eq!(style.bg, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_ansi16_sign_styles_have_contrast() {
|
||||
fn ansi16_sign_styles_use_foreground_only() {
|
||||
let add_sign = style_sign_add(DiffTheme::Dark, DiffColorLevel::Ansi16);
|
||||
assert_eq!(add_sign.fg, Some(Color::Black));
|
||||
assert_eq!(add_sign.bg, Some(Color::Green));
|
||||
assert_ne!(add_sign.fg, add_sign.bg);
|
||||
assert_eq!(add_sign.fg, Some(Color::Green));
|
||||
assert_eq!(add_sign.bg, None);
|
||||
|
||||
let del_sign = style_sign_del(DiffTheme::Dark, DiffColorLevel::Ansi16);
|
||||
assert_eq!(del_sign.fg, Some(Color::Black));
|
||||
assert_eq!(del_sign.bg, Some(Color::Red));
|
||||
assert_ne!(del_sign.fg, del_sign.bg);
|
||||
assert_eq!(del_sign.fg, Some(Color::Red));
|
||||
assert_eq!(del_sign.bg, None);
|
||||
}
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use ratatui::widgets::Wrap;
|
||||
@@ -1466,6 +1569,32 @@ mod tests {
|
||||
snapshot_diff_gallery("diff_gallery_120x40", 120, 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_snapshot_ansi16_insert_delete_no_background() {
|
||||
let mut lines = push_wrapped_diff_line_inner_with_theme_and_color_level(
|
||||
1,
|
||||
DiffLineType::Insert,
|
||||
"added in ansi16 mode",
|
||||
80,
|
||||
line_number_width(2),
|
||||
None,
|
||||
DiffTheme::Dark,
|
||||
DiffColorLevel::Ansi16,
|
||||
);
|
||||
lines.extend(push_wrapped_diff_line_inner_with_theme_and_color_level(
|
||||
2,
|
||||
DiffLineType::Delete,
|
||||
"deleted in ansi16 mode",
|
||||
80,
|
||||
line_number_width(2),
|
||||
None,
|
||||
DiffTheme::Dark,
|
||||
DiffColorLevel::Ansi16,
|
||||
));
|
||||
|
||||
snapshot_lines("ansi16_insert_delete_no_background", lines, 40, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truecolor_dark_theme_uses_configured_backgrounds() {
|
||||
assert_eq!(
|
||||
@@ -1535,6 +1664,42 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ansi16_disables_line_and_gutter_backgrounds() {
|
||||
assert_eq!(
|
||||
style_line_bg_for(
|
||||
DiffLineType::Insert,
|
||||
DiffTheme::Dark,
|
||||
DiffColorLevel::Ansi16
|
||||
),
|
||||
Style::default()
|
||||
);
|
||||
assert_eq!(
|
||||
style_line_bg_for(
|
||||
DiffLineType::Delete,
|
||||
DiffTheme::Light,
|
||||
DiffColorLevel::Ansi16
|
||||
),
|
||||
Style::default()
|
||||
);
|
||||
assert_eq!(
|
||||
style_gutter_for(
|
||||
DiffLineType::Insert,
|
||||
DiffTheme::Light,
|
||||
DiffColorLevel::Ansi16
|
||||
),
|
||||
Style::default().fg(Color::Black)
|
||||
);
|
||||
assert_eq!(
|
||||
style_gutter_for(
|
||||
DiffLineType::Delete,
|
||||
DiffTheme::Light,
|
||||
DiffColorLevel::Ansi16
|
||||
),
|
||||
Style::default().fg(Color::Black)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn light_truecolor_theme_uses_readable_gutter_and_line_backgrounds() {
|
||||
assert_eq!(
|
||||
@@ -1608,6 +1773,97 @@ mod tests {
|
||||
assert_eq!(lines[1].style.bg, Some(rgb_color(LIGHT_TC_ADD_LINE_BG_RGB)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_terminal_promotes_ansi16_to_truecolor_for_diffs() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Ansi16,
|
||||
TerminalName::WindowsTerminal,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
DiffColorLevel::TrueColor
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wt_session_promotes_ansi16_to_truecolor_for_diffs() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Ansi16,
|
||||
TerminalName::Unknown,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
DiffColorLevel::TrueColor
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_windows_terminal_keeps_ansi16_diff_palette() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Ansi16,
|
||||
TerminalName::WezTerm,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
DiffColorLevel::Ansi16
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wt_session_promotes_unknown_color_level_to_truecolor() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Unknown,
|
||||
TerminalName::WindowsTerminal,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
DiffColorLevel::TrueColor
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_wt_windows_terminal_keeps_unknown_color_level_conservative() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Unknown,
|
||||
TerminalName::WindowsTerminal,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
DiffColorLevel::Ansi16
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_force_override_keeps_ansi16_on_windows_terminal() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Ansi16,
|
||||
TerminalName::WindowsTerminal,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
DiffColorLevel::Ansi16
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_force_override_keeps_ansi256_on_windows_terminal() {
|
||||
assert_eq!(
|
||||
diff_color_level_for_terminal(
|
||||
StdoutColorLevel::Ansi256,
|
||||
TerminalName::WindowsTerminal,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
DiffColorLevel::Ansi256
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_diff_uses_path_extension_for_highlighting() {
|
||||
let mut changes: HashMap<PathBuf, FileChange> = HashMap::new();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: tui/src/diff_render.rs
|
||||
expression: terminal.backend()
|
||||
---
|
||||
"1 +added in ansi16 mode "
|
||||
"2 -deleted in ansi16 mode "
|
||||
" "
|
||||
" "
|
||||
Reference in New Issue
Block a user