gnostr_asyncgit/gitui/config/
mod.rs

1use std::{collections::BTreeMap, path::PathBuf};
2
3use crate::{gitui::gitui_error::Error, gitui::menu::Menu, gitui::ops::Op, gitui::Res};
4use etcetera::{choose_base_strategy, BaseStrategy};
5use figment::{
6    providers::{Format, Toml},
7    Figment,
8};
9use ratatui::style::{Color, Modifier, Style};
10use serde::Deserialize;
11
12const DEFAULT_CONFIG: &str = include_str!("../../default_config.toml");
13
14#[derive(Default, Debug, Deserialize)]
15pub(crate) struct Config {
16    pub general: GeneralConfig,
17    pub style: StyleConfig,
18    pub bindings: BTreeMap<Menu, BTreeMap<Op, Vec<String>>>,
19}
20
21#[derive(Default, Debug, Deserialize)]
22pub struct GeneralConfig {
23    pub always_show_help: BoolConfigEntry,
24    pub confirm_quit: BoolConfigEntry,
25    pub refresh_on_file_change: BoolConfigEntry,
26    pub collapsed_sections: Vec<String>,
27}
28
29#[derive(Default, Debug, Deserialize)]
30pub struct BoolConfigEntry {
31    #[serde(default)]
32    pub enabled: bool,
33}
34
35#[derive(Default, Debug, Deserialize)]
36pub struct StyleConfig {
37    pub section_header: StyleConfigEntry,
38    pub file_header: StyleConfigEntry,
39    pub hunk_header: StyleConfigEntry,
40
41    #[serde(default)]
42    pub diff_highlight: DiffHighlightConfig,
43
44    #[serde(default)]
45    pub syntax_highlight: SyntaxHighlightConfig,
46
47    pub cursor: SymbolStyleConfigEntry,
48    pub selection_line: StyleConfigEntry,
49    pub selection_bar: SymbolStyleConfigEntry,
50    pub selection_area: StyleConfigEntry,
51
52    pub hash: StyleConfigEntry,
53    pub branch: StyleConfigEntry,
54    pub remote: StyleConfigEntry,
55    pub tag: StyleConfigEntry,
56
57    pub command: StyleConfigEntry,
58    pub active_arg: StyleConfigEntry,
59    pub hotkey: StyleConfigEntry,
60}
61
62#[derive(Default, Debug, Deserialize)]
63pub struct DiffHighlightConfig {
64    #[serde(default)]
65    pub tag_old: StyleConfigEntry,
66    #[serde(default)]
67    pub tag_new: StyleConfigEntry,
68    #[serde(default)]
69    pub unchanged_old: StyleConfigEntry,
70    #[serde(default)]
71    pub unchanged_new: StyleConfigEntry,
72    #[serde(default)]
73    pub changed_old: StyleConfigEntry,
74    #[serde(default)]
75    pub changed_new: StyleConfigEntry,
76}
77
78#[derive(Default, Debug, Deserialize)]
79pub struct SyntaxHighlightConfig {
80    #[serde(default)]
81    pub enabled: bool,
82    #[serde(default)]
83    pub attribute: StyleConfigEntry,
84    #[serde(default)]
85    pub comment: StyleConfigEntry,
86    #[serde(default)]
87    pub constant_builtin: StyleConfigEntry,
88    #[serde(default)]
89    pub constant: StyleConfigEntry,
90    #[serde(default)]
91    pub constructor: StyleConfigEntry,
92    #[serde(default)]
93    pub embedded: StyleConfigEntry,
94    #[serde(default)]
95    pub function_builtin: StyleConfigEntry,
96    #[serde(default)]
97    pub function: StyleConfigEntry,
98    #[serde(default)]
99    pub keyword: StyleConfigEntry,
100    #[serde(default)]
101    pub number: StyleConfigEntry,
102    #[serde(default)]
103    pub module: StyleConfigEntry,
104    #[serde(default)]
105    pub property: StyleConfigEntry,
106    #[serde(default)]
107    pub operator: StyleConfigEntry,
108    #[serde(default)]
109    pub punctuation_bracket: StyleConfigEntry,
110    #[serde(default)]
111    pub punctuation_delimiter: StyleConfigEntry,
112    #[serde(default)]
113    pub string_special: StyleConfigEntry,
114    #[serde(default)]
115    pub string: StyleConfigEntry,
116    #[serde(default)]
117    pub tag: StyleConfigEntry,
118    #[serde(default)]
119    #[serde(rename = "type")]
120    pub type_regular: StyleConfigEntry,
121    #[serde(default)]
122    pub type_builtin: StyleConfigEntry,
123    #[serde(default)]
124    pub variable_builtin: StyleConfigEntry,
125    #[serde(default)]
126    pub variable_parameter: StyleConfigEntry,
127}
128
129#[derive(Default, Debug, Deserialize)]
130pub struct StyleConfigEntry {
131    #[serde(default)]
132    fg: Option<Color>,
133    #[serde(default)]
134    bg: Option<Color>,
135    #[serde(default)]
136    mods: Option<Modifier>,
137}
138
139#[derive(Default, Debug, Deserialize)]
140pub struct SymbolStyleConfigEntry {
141    #[serde(default)]
142    pub symbol: char,
143    #[serde(default)]
144    fg: Option<Color>,
145    #[serde(default)]
146    bg: Option<Color>,
147    #[serde(default)]
148    mods: Option<Modifier>,
149}
150
151impl From<&StyleConfigEntry> for Style {
152    fn from(val: &StyleConfigEntry) -> Self {
153        Style {
154            fg: val.fg,
155            bg: val.bg,
156            underline_color: None,
157            add_modifier: val.mods.unwrap_or(Modifier::empty()),
158            sub_modifier: Modifier::empty(),
159        }
160    }
161}
162
163impl From<&SymbolStyleConfigEntry> for Style {
164    fn from(val: &SymbolStyleConfigEntry) -> Self {
165        Style {
166            fg: val.fg,
167            bg: val.bg,
168            underline_color: None,
169            add_modifier: val.mods.unwrap_or(Modifier::empty()),
170            sub_modifier: Modifier::empty(),
171        }
172    }
173}
174
175pub(crate) fn init_config() -> Res<Config> {
176    let config_path = config_path();
177
178    if config_path.exists() {
179        log::info!("Loading config file at {:?}", config_path);
180    } else {
181        log::info!("No config file at {:?}", config_path);
182    }
183
184    let config = Figment::new()
185        .merge(Toml::string(DEFAULT_CONFIG))
186        .merge(Toml::file(config_path))
187        .extract()
188        .map_err(Error::Config)?;
189
190    Ok(config)
191}
192
193pub fn config_path() -> PathBuf {
194    choose_base_strategy()
195        .expect("Unable to find the config directory!")
196        .config_dir()
197        .join("gitu/config.toml")
198}
199
200#[cfg(test)]
201pub(crate) fn init_test_config() -> Res<Config> {
202    let mut config: Config = Figment::new()
203        .merge(Toml::string(DEFAULT_CONFIG))
204        .extract()
205        .map_err(Error::Config)?;
206
207    config.general.always_show_help.enabled = false;
208    config.general.refresh_on_file_change.enabled = false;
209
210    Ok(config)
211}
212
213#[cfg(test)]
214mod tests {
215    use figment::{
216        providers::{Format, Toml},
217        Figment,
218    };
219    use ratatui::style::Color;
220
221    use super::{Config, DEFAULT_CONFIG};
222
223    #[test]
224    fn config_merges() {
225        let config: Config = Figment::new()
226            .merge(Toml::string(DEFAULT_CONFIG))
227            .merge(Toml::string(
228                r#"
229                [style]
230                hunk_header.bg = "light green"
231                "#,
232            ))
233            .extract()
234            .unwrap();
235
236        assert_eq!(config.style.hunk_header.bg, Some(Color::LightGreen));
237        assert_eq!(config.style.hunk_header.fg, Some(Color::Blue));
238    }
239}