feat(tui): add "Terminal" theme that fully inherits the host terminal's colors
Adds a new selectable theme `terminal` (alongside System / Whale / Whale Light / Grayscale / Catppuccin / Tokyo Night / Dracula / Gruvbox) that paints every surface with `Color::Reset` instead of any RGB so the host terminal's own background, foreground, and palette show through. The existing `system` theme only chose between two RGB themes (Whale dark or Whale Light) based on COLORFGBG / macOS appearance — useful, but it still painted brand-colored RGB surfaces. Users with custom terminal themes (Solarized, Nord, transparent backgrounds, custom Ghostty/iTerm schemes) had no way to make the TUI respect their terminal palette. Implementation: - New `TERMINAL_UI_THEME` const where every `*_bg` and most text slots are `Color::Reset`, and accents (mode_agent/yolo/plan, status_working, status_warning) use ANSI named colors so they also inherit the user's terminal palette rather than DeepSeek brand RGB. - `ThemeId::Terminal` plumbed through `from_name` / `name` / `display_name` / `tagline` / `ui_theme` / `SELECTABLE_THEMES`, and registered in `normalize_theme_name` with aliases `term`, `transparent`, `follow-terminal`, `inherit` so existing user-friendly config strings just work. - `theme_remap_active(Terminal) → true` so the existing per-cell remap in `ColorCompatBackend` rewrites every hard-coded palette constant (`DEEPSEEK_INK`, `DEEPSEEK_SLATE`, `BORDER_COLOR`, `TEXT_BODY`, …) to `Color::Reset`. Without this, the many render sites that reach for the named palette constants directly would still paint brand RGB. - `theme_green` / `theme_red` return `Color::Green` / `Color::Red` for Terminal so diff "+"/"−" stay green/red but follow the user's terminal palette. - `theme_diff_added_bg` / `theme_diff_deleted_bg` return `Color::Reset` for Terminal — diff highlight is conveyed by foreground color only. - The new theme is the second entry in `SELECTABLE_THEMES` (right after System) so it surfaces prominently in the `/theme` picker. theme_picker tests: the new theme is inserted in row 2 of `SELECTABLE_THEMES`, which shifts the indices three existing tests relied on — `arrow_down_previews_next_theme`, `enter_commits_with_persist_true`, and `digit_jumps_to_row` — so those expectations are updated to match the new ordering. No production behavior change in those tests, just index arithmetic. Default (`theme = "system"`) is unchanged; existing users see no difference. Users who want full terminal pass-through opt in via `/theme` or `theme = "terminal"` in settings.toml.
This commit is contained in:
@@ -825,6 +825,39 @@ pub const DRACULA_UI_THEME: UiTheme = UiTheme {
|
||||
tool_failed: Color::Rgb(0xff, 0x55, 0x55), // red
|
||||
};
|
||||
|
||||
/// "Terminal" theme: lets the host terminal's color scheme show through
|
||||
/// instead of painting any RGB surface. Backgrounds use `Color::Reset`
|
||||
/// (the terminal's own default bg) and most text uses `Color::Reset`
|
||||
/// (terminal's own default fg). Accents are ANSI named colors so they
|
||||
/// also inherit the user's terminal palette (Solarized, Nord, custom
|
||||
/// schemes, etc.) rather than DeepSeek brand RGB.
|
||||
pub const TERMINAL_UI_THEME: UiTheme = UiTheme {
|
||||
name: "terminal",
|
||||
// Mode is reported as Dark to avoid the dark→light cell remap kicking
|
||||
// in; the terminal-theme cell remap already normalizes everything to
|
||||
// `Color::Reset`, and we never want a second pass overwriting that.
|
||||
mode: PaletteMode::Dark,
|
||||
surface_bg: Color::Reset,
|
||||
panel_bg: Color::Reset,
|
||||
elevated_bg: Color::Reset,
|
||||
composer_bg: Color::Reset,
|
||||
selection_bg: Color::Reset,
|
||||
header_bg: Color::Reset,
|
||||
footer_bg: Color::Reset,
|
||||
mode_agent: Color::Blue,
|
||||
mode_yolo: Color::Red,
|
||||
mode_plan: Color::Yellow,
|
||||
status_ready: Color::Reset,
|
||||
status_working: Color::Cyan,
|
||||
status_warning: Color::Yellow,
|
||||
text_dim: Color::Reset,
|
||||
text_hint: Color::Reset,
|
||||
text_muted: Color::Reset,
|
||||
text_body: Color::Reset,
|
||||
text_soft: Color::Reset,
|
||||
border: Color::Reset,
|
||||
};
|
||||
|
||||
pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme {
|
||||
name: "gruvbox-dark",
|
||||
mode: PaletteMode::Dark,
|
||||
@@ -874,6 +907,7 @@ pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ThemeId {
|
||||
System,
|
||||
Terminal,
|
||||
Whale,
|
||||
WhaleLight,
|
||||
Grayscale,
|
||||
@@ -891,6 +925,7 @@ impl ThemeId {
|
||||
pub fn from_name(value: &str) -> Option<Self> {
|
||||
match normalize_theme_name(value)? {
|
||||
"system" => Some(Self::System),
|
||||
"terminal" => Some(Self::Terminal),
|
||||
"dark" => Some(Self::Whale),
|
||||
"light" => Some(Self::WhaleLight),
|
||||
"grayscale" => Some(Self::Grayscale),
|
||||
@@ -908,6 +943,7 @@ impl ThemeId {
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::System => "system",
|
||||
Self::Terminal => "terminal",
|
||||
Self::Whale => "dark",
|
||||
Self::WhaleLight => "light",
|
||||
Self::Grayscale => "grayscale",
|
||||
@@ -923,6 +959,7 @@ impl ThemeId {
|
||||
pub const fn display_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::System => "System",
|
||||
Self::Terminal => "Terminal",
|
||||
Self::Whale => "Whale (Dark)",
|
||||
Self::WhaleLight => "Whale Light",
|
||||
Self::Grayscale => "Grayscale",
|
||||
@@ -938,6 +975,9 @@ impl ThemeId {
|
||||
pub const fn tagline(self) -> &'static str {
|
||||
match self {
|
||||
Self::System => "Follow terminal background (COLORFGBG / macOS appearance)",
|
||||
Self::Terminal => {
|
||||
"Inherit terminal colors fully (transparent surfaces, ANSI accents)"
|
||||
}
|
||||
Self::Whale => "Whale dark — deep navy & gold",
|
||||
Self::WhaleLight => "DeepSeek light, paper-ish",
|
||||
Self::Grayscale => "Color-minimal high contrast",
|
||||
@@ -956,6 +996,7 @@ impl ThemeId {
|
||||
pub fn ui_theme(self) -> UiTheme {
|
||||
match self {
|
||||
Self::System => UiTheme::detect(),
|
||||
Self::Terminal => TERMINAL_UI_THEME,
|
||||
Self::Whale => UI_THEME,
|
||||
Self::WhaleLight => LIGHT_UI_THEME,
|
||||
Self::Grayscale => GRAYSCALE_UI_THEME,
|
||||
@@ -970,6 +1011,7 @@ impl ThemeId {
|
||||
/// Themes shown in the `/theme` picker, in display order.
|
||||
pub const SELECTABLE_THEMES: &[ThemeId] = &[
|
||||
ThemeId::System,
|
||||
ThemeId::Terminal,
|
||||
ThemeId::Whale,
|
||||
ThemeId::WhaleLight,
|
||||
ThemeId::Grayscale,
|
||||
@@ -1012,6 +1054,7 @@ impl UiTheme {
|
||||
pub fn normalize_theme_name(value: &str) -> Option<&'static str> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"" | "auto" | "system" | "default" => Some("system"),
|
||||
"terminal" | "term" | "transparent" | "follow-terminal" | "inherit" => Some("terminal"),
|
||||
"dark" | "whale" | "whale-dark" => Some("dark"),
|
||||
"light" | "whale-light" => Some("light"),
|
||||
"grayscale" | "greyscale" | "gray" | "grey" | "mono" | "monochrome" | "black-white"
|
||||
@@ -1189,7 +1232,11 @@ const fn theme_diff_deleted_bg(ui: &UiTheme) -> Color {
|
||||
pub const fn theme_remap_active(theme: ThemeId) -> bool {
|
||||
matches!(
|
||||
theme,
|
||||
ThemeId::CatppuccinMocha | ThemeId::TokyoNight | ThemeId::Dracula | ThemeId::GruvboxDark
|
||||
ThemeId::Terminal
|
||||
| ThemeId::CatppuccinMocha
|
||||
| ThemeId::TokyoNight
|
||||
| ThemeId::Dracula
|
||||
| ThemeId::GruvboxDark
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ mod tests {
|
||||
let mut v = ThemePickerView::new("system".to_string());
|
||||
let action = v.handle_key(key(KeyCode::Down));
|
||||
assert!(matches!(action, ViewAction::Emit(_)));
|
||||
assert_eq!(selected_name(&action), Some(ThemeId::Whale.name()));
|
||||
assert_eq!(selected_name(&action), Some(ThemeId::Terminal.name()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -337,6 +337,7 @@ mod tests {
|
||||
v.handle_key(key(KeyCode::Down));
|
||||
v.handle_key(key(KeyCode::Down));
|
||||
v.handle_key(key(KeyCode::Down));
|
||||
v.handle_key(key(KeyCode::Down));
|
||||
v.handle_key(key(KeyCode::Down)); // -> CatppuccinMocha
|
||||
let action = v.handle_key(key(KeyCode::Enter));
|
||||
match action {
|
||||
@@ -376,8 +377,8 @@ mod tests {
|
||||
#[test]
|
||||
fn digit_jumps_to_row() {
|
||||
let mut v = ThemePickerView::new("system".to_string());
|
||||
let action = v.handle_key(key(KeyCode::Char('5')));
|
||||
// Row 5 (1-indexed) -> index 4 -> CatppuccinMocha
|
||||
let action = v.handle_key(key(KeyCode::Char('6')));
|
||||
// Row 6 (1-indexed) -> index 5 -> CatppuccinMocha
|
||||
assert_eq!(
|
||||
selected_name(&action),
|
||||
Some(ThemeId::CatppuccinMocha.name())
|
||||
|
||||
Reference in New Issue
Block a user