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