Simplify theme overrides (#1652)

* Simplify theme overrides

Theme overrides are now loaded the same way key overrides are loaded.
The config file, `theme.ron`, does not have to contain a complete theme
anymore. Instead, it is possible to specify only the values that are
supposed to override their corresponding default values.

* Document breaking change in changelog
* Test that override differs from default
* Convert existing theme to patch
This commit is contained in:
Christoph Rüßler 2023-06-25 14:09:40 +02:00 committed by GitHub
parent 999912c347
commit 3c9c266c01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 47 deletions

View file

@ -29,6 +29,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking Change ### Breaking Change
* `focus_XYZ` key bindings are merged into the `move_XYZ` set, so only one way to bind arrow-like keys from now on ([#1539](https://github.com/extrawurst/gitui/issues/1539)) * `focus_XYZ` key bindings are merged into the `move_XYZ` set, so only one way to bind arrow-like keys from now on ([#1539](https://github.com/extrawurst/gitui/issues/1539))
* Do you use a custom theme?
The way themes work got changed and simplified ([see docs](https://github.com/extrawurst/gitui/blob/master/THEMES.md) for more info):
* The format of `theme.ron` has changed: you only specify the colors etc. that should differ from their default value
* Future additions of colors etc. will not break existing themes anymore
### Added ### Added
* allow reset (soft,mixed,hard) from commit log ([#1500](https://github.com/extrawurst/gitui/issues/1500)) * allow reset (soft,mixed,hard) from commit log ([#1500](https://github.com/extrawurst/gitui/issues/1500))
@ -41,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* allow `copy` file path on revision files and status tree [[@yanganto]](https://github.com/yanganto) ([#1516](https://github.com/extrawurst/gitui/pull/1516)) * allow `copy` file path on revision files and status tree [[@yanganto]](https://github.com/yanganto) ([#1516](https://github.com/extrawurst/gitui/pull/1516))
* print message of where log will be written if `-l` is set ([#1472](https://github.com/extrawurst/gitui/pull/1472)) * print message of where log will be written if `-l` is set ([#1472](https://github.com/extrawurst/gitui/pull/1472))
* show remote branches in log [[@cruessler](https://github.com/cruessler)] ([#1501](https://github.com/extrawurst/gitui/issues/1501)) * show remote branches in log [[@cruessler](https://github.com/cruessler)] ([#1501](https://github.com/extrawurst/gitui/issues/1501))
* simplify theme overrides [[@cruessler](https://github.com/cruessler)] ([#1367](https://github.com/extrawurst/gitui/issues/1367))
### Fixes ### Fixes
* fixed side effect of crossterm 0.26 on windows that caused double input of all keys [[@pm100]](https://github/pm100) ([#1686](https://github.com/extrawurst/gitui/pull/1686)) * fixed side effect of crossterm 0.26 on windows that caused double input of all keys [[@pm100]](https://github/pm100) ([#1686](https://github.com/extrawurst/gitui/pull/1686))

View file

@ -3,15 +3,27 @@
default on light terminal: default on light terminal:
![](assets/light-theme.png) ![](assets/light-theme.png)
to change the colors of the default theme you have to modify `theme.ron` file To change the colors of the default theme you need to add a `theme.ron` file that contains the colors you want to override. Note that you dont have to specify the full theme anymore (as of 0.23). Instead, it is sufficient to override just the values that you want to differ from their default values.
[Ron format](https://github.com/ron-rs/ron) located at config path. The path differs depending on the operating system:
The file uses the [Ron format](https://github.com/ron-rs/ron) and is located at one of the following paths, depending on your operating system:
* `$HOME/.config/gitui/theme.ron` (mac) * `$HOME/.config/gitui/theme.ron` (mac)
* `$XDG_CONFIG_HOME/gitui/theme.ron` (linux using XDG) * `$XDG_CONFIG_HOME/gitui/theme.ron` (linux using XDG)
* `$HOME/.config/gitui/theme.ron` (linux) * `$HOME/.config/gitui/theme.ron` (linux)
* `%APPDATA%/gitui/theme.ron` (Windows) * `%APPDATA%/gitui/theme.ron` (Windows)
Alternatively you may make a theme in the same directory mentioned above with and select with the `-t` flag followed by the name of the file in the directory. E.g. If you are on linux calling `gitui -t arc.ron` wil use `$XDG_CONFIG_HOME/gitui/arc.ron` or `$HOME/.config/gitui/arc.ron` Alternatively, you can create a theme in the same directory mentioned above and use it with the `-t` flag followed by the name of the file in the directory. E.g. If you are on linux calling `gitui -t arc.ron`, this will load the theme in `$XDG_CONFIG_HOME/gitui/arc.ron` or `$HOME/.config/gitui/arc.ron`.
Example theme override:
```
(
selection_bg: Some(Blue),
selection_fg: Some(White),
)
```
Note that you need to wrap values in `Some` due to the way the overrides work (as of 0.23).
Notes: Notes:

View file

@ -138,9 +138,7 @@ fn main() -> Result<()> {
let key_config = KeyConfig::init() let key_config = KeyConfig::init()
.map_err(|e| eprintln!("KeyConfig loading error: {e}")) .map_err(|e| eprintln!("KeyConfig loading error: {e}"))
.unwrap_or_default(); .unwrap_or_default();
let theme = Theme::init(&cliargs.theme) let theme = Theme::init(&cliargs.theme);
.map_err(|e| eprintln!("Theme loading error: {e}"))
.unwrap_or_default();
setup_terminal()?; setup_terminal()?;
defer! { defer! {

View file

@ -1,21 +1,15 @@
use anyhow::Result; use anyhow::Result;
use asyncgit::{DiffLineType, StatusItemType}; use asyncgit::{DiffLineType, StatusItemType};
use ratatui::style::{Color, Modifier, Style}; use ratatui::style::{Color, Modifier, Style};
use ron::{ use ron::ser::{to_string_pretty, PrettyConfig};
de::from_bytes,
ser::{to_string_pretty, PrettyConfig},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{fs::File, io::Write, path::PathBuf, rc::Rc};
fs::{self, File}, use struct_patch::Patch;
io::{Read, Write},
path::PathBuf,
rc::Rc,
};
pub type SharedTheme = Rc<Theme>; pub type SharedTheme = Rc<Theme>;
#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, Copy, Clone, Patch)]
#[patch_derive(Serialize, Deserialize)]
pub struct Theme { pub struct Theme {
selected_tab: Color, selected_tab: Color,
command_fg: Color, command_fg: Color,
@ -261,44 +255,46 @@ impl Theme {
.bg(self.push_gauge_bg) .bg(self.push_gauge_bg)
} }
// This will only be called when theme.ron doesn't already exists fn load_patch(theme_path: &PathBuf) -> Result<ThemePatch> {
fn save(&self, theme_file: &PathBuf) -> Result<()> { let file = File::open(theme_path)?;
let mut file = File::create(theme_file)?;
let data = to_string_pretty(self, PrettyConfig::default())?; Ok(ron::de::from_reader(file)?)
}
fn load_old_theme(theme_path: &PathBuf) -> Result<Self> {
let old_file = File::open(theme_path)?;
Ok(ron::de::from_reader::<File, Self>(old_file)?)
}
// This is supposed to be called when theme.ron doesn't already exists.
fn save_patch(&self, theme_path: &PathBuf) -> Result<()> {
let mut file = File::create(theme_path)?;
let patch = self.into_patch_by_diff(Self::default());
let data = to_string_pretty(&patch, PrettyConfig::default())?;
file.write_all(data.as_bytes())?; file.write_all(data.as_bytes())?;
Ok(()) Ok(())
} }
fn read_file(theme_file: PathBuf) -> Result<Self> { pub fn init(theme_path: &PathBuf) -> Self {
let mut f = File::open(theme_file)?; let mut theme = Self::default();
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
Ok(from_bytes(&buffer)?)
}
pub fn init(file: &PathBuf) -> Result<Self> { if let Ok(patch) = Self::load_patch(theme_path) {
if file.exists() { theme.apply(patch);
match Self::read_file(file.clone()) { } else if let Ok(old_theme) = Self::load_old_theme(theme_path)
Err(e) => { {
let config_path = file.clone(); theme = old_theme;
let config_path_old =
format!("{}.old", file.to_string_lossy());
fs::rename(
config_path.clone(),
config_path_old.clone(),
)?;
Self::default().save(file)?; if theme.save_patch(theme_path).is_ok() {
log::info!("Converted old theme to new format.");
Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", } else {
e,config_path_old,config_path.to_string_lossy())) log::warn!("Failed to save theme in new format.");
}
Ok(res) => Ok(res),
} }
} else {
Self::default().save(file)?;
Ok(Self::default())
} }
theme
} }
} }
@ -329,3 +325,32 @@ impl Default for Theme {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_smoke() {
let mut file = NamedTempFile::new().unwrap();
writeln!(
file,
r"
(
selection_bg: Some(White),
)
"
)
.unwrap();
let theme = Theme::init(&file.path().to_path_buf());
assert_eq!(theme.selection_fg, Theme::default().selection_fg);
assert_eq!(theme.selection_bg, Color::White);
assert_ne!(theme.selection_bg, Theme::default().selection_bg);
}
}