Add theme-aware semantic colors to extension UI trees (#60019)

## Why

Extension UI trees previously accepted only named terminal colors and literal RGB values. Semantic colors let extension content follow the active syntax theme and terminal color capabilities.

## What changed

- Accept `primary`, `surface`, `accent`, and `status-*` color roles for text foregrounds and row backgrounds.
- Resolve semantic colors against the active theme when rendering extension footer content. Prefer the `codex.accent` scope for `accent`, falling back to the theme's accent setting.
- Reuse status-line scope mappings and fallback colors, with terminal-default fallbacks for unavailable primary, surface, and accent colors.

## Testing

Add color serialization coverage and rendering snapshots for dark, light, and empty themes in truecolor and ANSI 16-color terminals, including nested foreground and background styles.

GitOrigin-RevId: 156f137859d74178f9ae6aa46a02eecb815e9802
This commit is contained in:
Joe Savona
2026-11-07 01:36:15 +00:00
committed by copyberry
parent 5ff78c5e66
commit 2157f98648
9 changed files with 366 additions and 47 deletions
+18 -6
View File
@@ -290,11 +290,16 @@ pub(crate) fn syntax_theme_revision() -> u64 {
/// Clone the current syntax theme (e.g. to save for cancel-restore).
pub(crate) fn current_syntax_theme() -> Theme {
with_syntax_theme(Theme::clone)
}
/// Borrow the active theme for one synchronous render operation.
pub(crate) fn with_syntax_theme<T>(f: impl FnOnce(&Theme) -> T) -> T {
let active_theme = theme_lock();
match active_theme.read() {
Ok(theme) => theme.clone(),
Err(poisoned) => poisoned.into_inner().clone(),
}
let theme = active_theme
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
f(&theme)
}
/// An explicit diff background, including the ANSI terminal-default marker.
@@ -366,11 +371,18 @@ pub(crate) fn foreground_style_for_scopes_with_theme(
theme: &Theme,
scope_names: &[&str],
) -> Option<Style> {
let highlighter = Highlighter::new(theme);
foreground_color_for_scopes(&Highlighter::new(theme), scope_names)
.map(|fg| Style::default().fg(fg))
}
pub(crate) fn foreground_color_for_scopes(
highlighter: &Highlighter<'_>,
scope_names: &[&str],
) -> Option<RtColor> {
scope_names.iter().find_map(|scope_name| {
let scope = Scope::new(scope_name).ok()?;
let fg = highlighter.style_mod_for_stack(&[scope]).foreground?;
convert_syntect_color(fg).map(|fg| Style::default().fg(fg))
convert_syntect_color(fg)
})
}