diff options
| author | Alex <a.neyman17@gmail.com> | 2026-08-22 17:18:56 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-23 00:18:56 +0300 |
| commit | db6f301f1301d85ca7a4271363760836f53ac0d0 (patch) | |
| tree | 7ac8748407268190f3bea23190fb3cfb07bd4bf2 /src/app | |
| parent | 1d604329b0ded93d18fd6b27f72c7473d1fee20f (diff) | |
feat: add per-mode theme overrides (#2324)
* feat: add per-mode theme overrides
allow [theme.custom.light] and [theme.custom.dark] blocks so custom
overrides can differ per resolved appearance when auto_switch flips.
precedence: theme defaults, then [theme.custom], then the block for
the active mode. configs without the new blocks resolve unchanged.
refs #837
* docs: complete per-mode theme documentation
---------
Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/mod.rs | 90 | ||||
| -rw-r--r-- | src/app/state.rs | 62 |
2 files changed, 145 insertions, 7 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs index 85ad4fa3..ff286c3f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -337,6 +337,7 @@ fn resolve_palette_for_theme_name( name: &str, fallback_name: &str, runtime: &state::ThemeRuntimeConfig, + mode_custom: Option<&crate::config::ModeThemeColors>, ) -> state::Palette { let mut palette = state::Palette::from_name(name).unwrap_or_else(|| { tracing::warn!( @@ -353,6 +354,9 @@ fn resolve_palette_for_theme_name( if let Some(accent) = &runtime.legacy_accent { palette.accent = crate::config::parse_color(accent); } + if let Some(custom) = mode_custom { + palette = palette.with_mode_overrides(custom); + } palette } @@ -361,18 +365,30 @@ fn resolve_effective_theme( runtime: &state::ThemeRuntimeConfig, appearance: Option<crate::terminal_theme::HostAppearance>, ) -> (state::Palette, String) { - let (name, fallback) = if runtime.auto_switch { + let (name, fallback, mode_custom) = if runtime.auto_switch { match appearance.unwrap_or(crate::terminal_theme::HostAppearance::Dark) { - crate::terminal_theme::HostAppearance::Dark => (&runtime.dark_name, "catppuccin"), - crate::terminal_theme::HostAppearance::Light => { - (&runtime.light_name, "catppuccin-latte") - } + crate::terminal_theme::HostAppearance::Dark => ( + &runtime.dark_name, + "catppuccin", + runtime + .custom + .as_ref() + .and_then(|custom| custom.dark.as_ref()), + ), + crate::terminal_theme::HostAppearance::Light => ( + &runtime.light_name, + "catppuccin-latte", + runtime + .custom + .as_ref() + .and_then(|custom| custom.light.as_ref()), + ), } } else { - (&runtime.manual_name, "catppuccin") + (&runtime.manual_name, "catppuccin", None) }; ( - resolve_palette_for_theme_name(name, fallback, runtime), + resolve_palette_for_theme_name(name, fallback, runtime, mode_custom), name.clone(), ) } @@ -2807,6 +2823,17 @@ mod tests { fn theme_auto_switch_is_opt_in_and_preserves_manual_default() { let mut config = Config::default(); config.theme.name = Some("tokyo-night".to_string()); + config.theme.custom = Some(crate::config::CustomThemeColors { + light: Some(crate::config::ModeThemeColors { + accent: Some("#010203".to_string()), + ..Default::default() + }), + dark: Some(crate::config::ModeThemeColors { + accent: Some("#040506".to_string()), + ..Default::default() + }), + ..Default::default() + }); let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); let app = App::new(&config, true, None, api_rx, crate::api::EventHub::default()); @@ -2855,6 +2882,55 @@ mod tests { } #[test] + fn theme_auto_switch_layers_active_mode_overrides_last() { + let mut config = Config::default(); + config.theme.name = Some("gruvbox".to_string()); + config.theme.auto_switch = true; + config.theme.custom = Some(crate::config::CustomThemeColors { + accent: Some("#010203".to_string()), + text: Some("#040506".to_string()), + light: Some(crate::config::ModeThemeColors { + accent: Some("#070809".to_string()), + ..Default::default() + }), + dark: Some(crate::config::ModeThemeColors { + text: Some("#0a0b0c".to_string()), + sidebar_bg: Some("#0d0e0f".to_string()), + active_row_bg: Some("#101112".to_string()), + ..Default::default() + }), + ..Default::default() + }); + let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut app = App::new(&config, true, None, api_rx, crate::api::EventHub::default()); + + assert_eq!( + app.state.palette.accent, + ratatui::style::Color::Rgb(1, 2, 3) + ); + assert_eq!( + app.state.palette.text, + ratatui::style::Color::Rgb(10, 11, 12) + ); + assert_eq!( + app.state.palette.sidebar_bg, + ratatui::style::Color::Rgb(13, 14, 15) + ); + assert_eq!( + app.state.palette.active_row_bg, + ratatui::style::Color::Rgb(16, 17, 18) + ); + + app.set_host_terminal_appearance(crate::terminal_theme::HostAppearance::Light, true); + + assert_eq!( + app.state.palette.accent, + ratatui::style::Color::Rgb(7, 8, 9) + ); + assert_eq!(app.state.palette.text, ratatui::style::Color::Rgb(4, 5, 6)); + } + + #[test] fn inferred_background_appearance_does_not_override_explicit_report() { let mut config = Config::default(); config.theme.name = Some("catppuccin".to_string()); diff --git a/src/app/state.rs b/src/app/state.rs index d9dcfc9d..21ee8899 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -645,6 +645,68 @@ impl Palette { } self } + + pub fn with_mode_overrides(mut self, custom: &crate::config::ModeThemeColors) -> Self { + use crate::config::parse_color; + if let Some(c) = &custom.accent { + self.accent = parse_color(c); + } + if let Some(c) = &custom.panel_bg { + self.panel_bg = parse_color(c); + } + if let Some(c) = &custom.sidebar_bg { + self.sidebar_bg = parse_color(c); + } + if let Some(c) = &custom.active_row_bg { + self.active_row_bg = parse_color(c); + } + if let Some(c) = &custom.selection_bg { + self.selection_bg = parse_color(c); + } + if let Some(c) = &custom.surface0 { + self.surface0 = parse_color(c); + } + if let Some(c) = &custom.surface1 { + self.surface1 = parse_color(c); + } + if let Some(c) = &custom.surface_dim { + self.surface_dim = parse_color(c); + } + if let Some(c) = &custom.overlay0 { + self.overlay0 = parse_color(c); + } + if let Some(c) = &custom.overlay1 { + self.overlay1 = parse_color(c); + } + if let Some(c) = &custom.text { + self.text = parse_color(c); + } + if let Some(c) = &custom.subtext0 { + self.subtext0 = parse_color(c); + } + if let Some(c) = &custom.mauve { + self.mauve = parse_color(c); + } + if let Some(c) = &custom.green { + self.green = parse_color(c); + } + if let Some(c) = &custom.yellow { + self.yellow = parse_color(c); + } + if let Some(c) = &custom.red { + self.red = parse_color(c); + } + if let Some(c) = &custom.blue { + self.blue = parse_color(c); + } + if let Some(c) = &custom.teal { + self.teal = parse_color(c); + } + if let Some(c) = &custom.peach { + self.peach = parse_color(c); + } + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
