diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ca0eaac2..d59c37abfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * support x509 commit signing [[@kaden-l-nelson](https://github.com/kaden-l-nelson)] ([#2514](https://github.com/gitui-org/gitui/issues/2514)) +* allow disabling the command bar background via `cmdbar_bg: Some(None)` so the terminal can show through ([#3011](https://github.com/gitui-org/gitui/issues/3011)) ### Changed * use [tombi](https://github.com/tombi-toml/tombi) for all toml file formatting diff --git a/THEMES.md b/THEMES.md index 52c235b496..0d79cbbbbc 100644 --- a/THEMES.md +++ b/THEMES.md @@ -88,3 +88,20 @@ This can be changed by specifying the `use_selection_fg` boolean in your `theme. ``` By default, `use_selection_fg` is set to `true`. + +## Disabling the command bar background + +The command bar at the bottom is filled with `cmdbar_bg` (`Blue` by +default). If you run a terminal with transparency or blur and want it to +show through, set the field to `Some(None)`: + +```ron +( + cmdbar_bg: Some(None), +) +``` + +The extra `Some` is required because overrides are applied as a patch: +a plain `cmdbar_bg: None` is read as "leave the default alone", so +`Some(None)` is what actually clears the fill. Setting a color still +works as before, e.g. `cmdbar_bg: Some("Red")`. diff --git a/src/ui/style.rs b/src/ui/style.rs index be21f76b9f..47cf3a511f 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -9,6 +9,38 @@ use struct_patch::Patch; pub type SharedTheme = Rc; +/// Deserializer for the `cmdbar_bg` patch field. +/// +/// The field became `Option` so a theme can disable the command +/// bar background (`cmdbar_bg: Some(None)`) and let the terminal show +/// through. `struct_patch` wraps it again, giving the patch field type +/// `Option>`, so we accept both the new nested form and +/// the legacy `Some("Color")` themes wrote before the field was +/// optional. +// `Option>` is the type `struct_patch` generates for a +// patched `Option` field, so it has to be matched here. +#[allow(clippy::option_option)] +fn deserialize_cmdbar_bg<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum CmdbarBg { + Nested(Option), + Flat(Color), + } + + Ok(Option::::deserialize(deserializer)?.map( + |v| match v { + CmdbarBg::Nested(inner) => inner, + CmdbarBg::Flat(color) => Some(color), + }, + )) +} + #[derive(Serialize, Deserialize, Debug, Clone, Patch)] #[patch(attribute(derive(Serialize, Deserialize)))] pub struct Theme { @@ -17,7 +49,11 @@ pub struct Theme { selection_bg: Color, selection_fg: Color, use_selection_fg: bool, - cmdbar_bg: Color, + #[patch(attribute(serde( + default, + deserialize_with = "deserialize_cmdbar_bg" + )))] + cmdbar_bg: Option, disabled_fg: Color, diff_line_add: Color, diff_line_delete: Color, @@ -211,12 +247,15 @@ impl Theme { } pub fn commandbar(&self, enabled: bool) -> Style { - if enabled { + let style = if enabled { Style::default().fg(self.command_fg) } else { Style::default().fg(self.disabled_fg) - } - .bg(self.cmdbar_bg) + }; + + // `cmdbar_bg: None` leaves the background unset so the + // terminal shows through (useful with opacity/blur). + self.cmdbar_bg.map_or(style, |bg| style.bg(bg)) } pub fn commit_hash(&self, selected: bool) -> Style { @@ -335,7 +374,7 @@ impl Default for Theme { selection_bg: Color::Blue, selection_fg: Color::White, use_selection_fg: true, - cmdbar_bg: Color::Blue, + cmdbar_bg: Some(Color::Blue), disabled_fg: Color::DarkGray, diff_line_add: Color::Green, diff_line_delete: Color::Red, @@ -398,4 +437,72 @@ mod tests { assert_eq!(theme.selection_fg, Color::Rgb(255, 255, 255)); assert_eq!(theme.syntax, "InspiredGitHub"); } + + fn theme_from_ron(contents: &str) -> Theme { + let mut file = NamedTempFile::new().unwrap(); + write!(file, "{contents}").unwrap(); + Theme::init(&file.path().to_path_buf()) + } + + #[test] + fn test_cmdbar_bg_default_is_filled() { + assert_eq!(Theme::default().cmdbar_bg, Some(Color::Blue)); + assert_eq!( + Theme::default().commandbar(true).bg, + Some(Color::Blue) + ); + } + + #[test] + fn test_cmdbar_bg_omitted_keeps_default() { + let theme = + theme_from_ron(r#"( selection_bg: Some("Black") )"#); + + assert_eq!(theme.cmdbar_bg, Some(Color::Blue)); + } + + #[test] + fn test_cmdbar_bg_transparent() { + let theme = theme_from_ron("( cmdbar_bg: Some(None) )"); + + assert_eq!(theme.cmdbar_bg, None); + assert_eq!(theme.commandbar(true).bg, None); + } + + #[test] + fn test_cmdbar_bg_legacy_some_color() { + // themes written before the field was optional + let theme = theme_from_ron(r#"( cmdbar_bg: Some("Red") )"#); + + assert_eq!(theme.cmdbar_bg, Some(Color::Red)); + assert_eq!(theme.commandbar(true).bg, Some(Color::Red)); + } + + #[test] + fn test_cmdbar_bg_nested_some_color() { + let theme = + theme_from_ron(r#"( cmdbar_bg: Some(Some("Red")) )"#); + + assert_eq!(theme.cmdbar_bg, Some(Color::Red)); + } + + #[test] + fn test_cmdbar_bg_roundtrips_through_patch() { + for value in [None, Some(Color::Red)] { + let mut theme = Theme::default(); + theme.cmdbar_bg = value; + + let patch = + theme.clone().into_patch_by_diff(Theme::default()); + let ron = + to_string_pretty(&patch, PrettyConfig::default()) + .unwrap(); + + let mut file = NamedTempFile::new().unwrap(); + write!(file, "{ron}").unwrap(); + let restored = Theme::init(&file.path().to_path_buf()); + + assert_eq!(restored.cmdbar_bg, value); + } + } }