Skip to content

fix(tui): theme-aware diff backgrounds with fallback behavior#13037

Merged
etraut-openai merged 5 commits intoopenai:mainfrom
fcoury:explore/tm-theme-diff-bg
Feb 27, 2026
Merged

fix(tui): theme-aware diff backgrounds with fallback behavior#13037
etraut-openai merged 5 commits intoopenai:mainfrom
fcoury:explore/tm-theme-diff-bg

Conversation

@fcoury
Copy link
Contributor

@fcoury fcoury commented Feb 27, 2026

Problem

The TUI diff renderer uses hardcoded background palettes for insert/delete lines that don't respect the user's chosen syntax theme. When a theme defines markup.inserted / markup.deleted scope backgrounds (the convention used by GitHub, Solarized, Monokai, and most VS Code themes), those colors are ignored — the diff always renders with the same green/red tints regardless of theme selection.

Separately, ANSI-16 terminals (and Windows Terminal sessions misreported as ANSI-16) rendered diff backgrounds as full-saturation blocks that obliterated syntax token colors, making highlighted diffs unreadable.

Mental model

Diff backgrounds are resolved in three layers:

  1. Color level detectiondiff_color_level_for_terminal() maps the raw supports-color probe + Windows Terminal heuristics to a DiffColorLevel (TrueColor / Ansi256 / Ansi16). Windows Terminal gets promoted from Ansi16 to TrueColor when WT_SESSION is present.

  2. Background resolutionresolve_diff_backgrounds() queries the active syntax theme for markup.inserted/markup.deleted (falling back to diff.inserted/diff.deleted), then overlays those on top of the hardcoded palette. For ANSI-256, theme RGB values are quantized to the nearest xterm-256 index. For ANSI-16, backgrounds are None (foreground-only).

  3. Style composition — The resolved ResolvedDiffBackgrounds is threaded through every call to style_add, style_del, style_sign_*, and style_line_bg_for, which decide how to compose foreground+background for each line kind and theme variant.

A new RichDiffColorLevel type (a subset of DiffColorLevel without Ansi16) encodes the invariant "we have enough depth for tinted backgrounds" at the type level, so background-producing functions have exhaustive matches without unreachable arms.

Non-goals

  • No change to gutter (line number column) styling — gutter backgrounds still use the hardcoded palette.
  • No per-token scope background resolution — this is line-level background only; syntax token colors come from the existing highlight_code_to_styled_spans path.
  • No dark/light theme auto-switching from scope backgrounds — DiffTheme is still determined by querying the terminal's background color.

Tradeoffs

  • Theme trust vs. visual safety: When a theme defines scope backgrounds, we trust them unconditionally for rich color levels. A badly authored theme could produce illegible combinations. The fallback for None backgrounds (foreground-only) is intentionally conservative.
  • Quantization quality: ANSI-256 quantization uses perceptual distance across indices 16–255, skipping system colors. The result is approximate — a subtle theme tint may land on a noticeably different xterm index.
  • Single-query caching: resolve_diff_backgrounds is called once per render_change invocation (i.e., once per file in a diff). If the theme changes mid-render (live preview), the next file picks up the new backgrounds.

Architecture

Files changed:

File Role
tui/src/render/highlight.rs New: DiffScopeBackgroundRgbs, diff_scope_background_rgbs(), scope extraction helpers
tui/src/diff_render.rs New: RichDiffColorLevel, ResolvedDiffBackgrounds, resolve_diff_backgrounds*, quantize_rgb_to_ansi256, Windows Terminal promotion; modified: all style helpers to accept/thread ResolvedDiffBackgrounds

The scope-extraction code lives in highlight.rs because it uses syntect::highlighting::Highlighter and the theme singleton. The resolution and quantization logic lives in diff_render.rs because it depends on diff-specific types (DiffTheme, DiffColorLevel, ratatui Color).

Observability

No runtime logging was added. The most useful debugging aid is the diff_color_level_for_terminal function, which is pure and fully unit-tested — to diagnose a color-depth mismatch, log its four inputs (StdoutColorLevel, TerminalName, WT_SESSION presence, FORCE_COLOR presence).

Scope resolution can be tested by loading a custom .tmTheme with known markup.inserted / markup.deleted backgrounds and checking the diff output in a truecolor terminal.

Tests

  • Windows Terminal promotion: 7 unit tests cover every branch of diff_color_level_for_terminal (ANSI-16 promotion, WT_SESSION unconditional promotion, FORCE_COLOR suppression, conservative Unknown level).
  • ANSI-16 foreground-only: Tests verify that style_add, style_del, style_sign_*, style_line_bg_for, and style_gutter_for all return None backgrounds on ANSI-16.
  • Scope resolution: Tests verify markup.* preference over diff.*, None when no scope matches, bundled theme resolution, and custom .tmTheme round-trip.
  • Quantization: Test verifies ANSI-256 quantization of a known RGB triple.
  • Insta snapshots: 2 new snapshot tests (ansi16_insert_delete_no_background, theme_scope_background_resolution) lock visual output.

Copilot AI review requested due to automatic review settings February 27, 2026 18:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the TUI diff renderer to respect syntax theme-provided insert/delete background scopes (with sensible fallbacks), and to avoid unreadable background blocks on ANSI-16 terminals by switching to foreground-only styling.

Changes:

  • Extract diff/markup scope background RGBs from the active syntect theme (markup.* preferred, diff.* fallback).
  • Resolve diff line backgrounds once per render using theme scopes when available; quantize RGBs for ANSI-256 and disable backgrounds entirely for ANSI-16 via RichDiffColorLevel.
  • Improve terminal color-level detection with Windows Terminal promotion rules and add unit + snapshot coverage.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
codex-rs/tui/src/render/highlight.rs Adds theme-scope background extraction helpers and tests for bundled/custom theme behavior.
codex-rs/tui/src/diff_render.rs Threads resolved backgrounds through style helpers, adds ANSI-256 quantization, ANSI-16 foreground-only behavior, and Windows Terminal promotion logic with tests/snapshots.
codex-rs/tui/src/snapshots/codex_tui__diff_render__tests__theme_scope_background_resolution.snap New snapshot covering theme-scope override resolution output.
codex-rs/tui/src/snapshots/codex_tui__diff_render__tests__ansi16_insert_delete_no_background.snap New snapshot verifying ANSI-16 renders without insert/delete backgrounds.
codex-rs/core/src/terminal.rs Documentation note clarifying that TERM_PROGRAM can mask later probes like WT_SESSION.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Use syntax theme scope backgrounds for diff insert/delete rows when
available in rich color modes, preferring `markup.*` and then
`diff.*` scopes. Keep ANSI16 behavior foreground-only and fall back
to the existing palette when no matching scope background exists.

Add focused coverage for scope resolution, ansi256 quantization, and
snapshot output, plus built-in and custom `.tmTheme` background
resolution paths.
Document how diff backgrounds are resolved from syntax theme scopes
and how fallback colors are applied per color level.

Clarify helper invariants around rich color modes, ANSI-256
quantization, and ANSI-16 foreground-only behavior.
@fcoury fcoury force-pushed the explore/tm-theme-diff-bg branch from 33a8cda to a6813f7 Compare February 27, 2026 18:29
@fcoury
Copy link
Contributor Author

fcoury commented Feb 27, 2026

@codex review

@chatgpt-codex-connector
Copy link
Contributor

Codex Review: Didn't find any major issues. Keep them coming!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Precompute `DiffRenderStyleContext` once per render pass and thread it
through diff line render helpers. This avoids repeated theme and
diff-background resolution for each rendered preview line.

Update `diff_render` tests to call the context-aware helpers directly
and remove legacy wrappers, so tests exercise the same rendering path
used in production.
@etraut-openai etraut-openai merged commit c3c7587 into openai:main Feb 27, 2026
27 of 39 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Feb 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants