1use rmut_core::config::Config;
6
7use crate::style::{Color, Style, parse_color};
8
9#[derive(Clone)]
10pub struct Theme {
11 pub bar_fg: Color,
12 pub bar_bg: Color,
13 bar_reversed: bool,
14 pub deleted: Color,
15 pub flagged: Color,
16 pub tagged: Color,
17 pub header: Color,
18 pub quoted: Vec<Color>,
21 pub search: Style,
23 pub error: Style,
26}
27
28impl Theme {
29 fn preset(name: &str) -> Theme {
30 match name {
31 "mono" => Theme {
32 bar_fg: Color::Reset,
33 bar_bg: Color::Reset,
34 bar_reversed: true,
35 deleted: Color::Reset,
36 flagged: Color::Reset,
37 tagged: Color::Reset,
38 header: Color::Reset,
39 quoted: Vec::new(),
40 search: Style::new().reversed(),
41 error: Style::new().bold().reversed(),
42 },
43 _ => Theme {
45 bar_fg: Color::Black,
46 bar_bg: Color::Cyan,
47 bar_reversed: false,
48 deleted: Color::Red,
49 flagged: Color::Yellow,
50 tagged: Color::Cyan,
51 header: Color::Green,
52 quoted: vec![Color::Cyan],
53 search: Style::new().reversed(),
54 error: Style::new().fg(Color::LightRed).bold(),
55 },
56 }
57 }
58
59 pub fn from_config(cfg: &Config) -> (Theme, Vec<String>) {
60 let mut theme = Theme::preset(cfg.ui.theme.as_deref().unwrap_or("default"));
61 let mut warnings = Vec::new();
62 let mut quoted: std::collections::BTreeMap<usize, Color> =
65 std::collections::BTreeMap::new();
66 let (mut search_fg, mut search_bg) = (None, None);
67 for (key, value) in &cfg.colors {
68 let Some(color) = parse_color(value) else {
69 warnings.push(format!("unknown color {value:?}"));
70 continue;
71 };
72 if let Some(n) = key.strip_prefix("quoted")
73 && let Ok(depth) = if n.is_empty() { Ok(0) } else { n.parse() }
74 {
75 quoted.insert(depth, color);
76 continue;
77 }
78 match key.as_str() {
79 "status_fg" => {
80 theme.bar_fg = color;
81 theme.bar_reversed = false;
82 }
83 "status_bg" => {
84 theme.bar_bg = color;
85 theme.bar_reversed = false;
86 }
87 "deleted" => theme.deleted = color,
88 "flagged" => theme.flagged = color,
89 "tagged" => theme.tagged = color,
90 "header" => theme.header = color,
91 "search_fg" => search_fg = Some(color),
92 "search_bg" => search_bg = Some(color),
93 "error" => {
94 theme.error = Style::new().fg(color).bold();
95 }
96 other => warnings.push(format!("unknown color key {other:?}")),
97 }
98 }
99 if !quoted.is_empty() {
100 theme.quoted = quoted.into_values().collect();
101 }
102 if search_fg.is_some() || search_bg.is_some() {
103 let mut style = Style::new();
104 if let Some(fg) = search_fg {
105 style = style.fg(fg);
106 }
107 if let Some(bg) = search_bg {
108 style = style.bg(bg);
109 }
110 theme.search = style;
111 }
112 (theme, warnings)
113 }
114
115 pub fn bar_style(&self) -> Style {
116 if self.bar_reversed {
117 Style::new().reversed()
118 } else {
119 Style::new().fg(self.bar_fg).bg(self.bar_bg)
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn overrides_apply_and_warn() {
130 let cfg: Config =
131 toml::from_str("[colors]\ndeleted = \"blue\"\nbogus = \"red\"\nflagged = \"nope\"\n")
132 .unwrap();
133 let (theme, warnings) = Theme::from_config(&cfg);
134 assert_eq!(theme.deleted, Color::Blue);
135 assert_eq!(theme.flagged, Color::Yellow); assert_eq!(warnings.len(), 2);
137 }
138
139 #[test]
140 fn quoted_palette_and_search_override() {
141 let cfg: Config = toml::from_str(
142 "[colors]\nquoted = \"red\"\nquoted2 = \"blue\"\nsearch_bg = \"yellow\"\n",
143 )
144 .unwrap();
145 let (theme, warnings) = Theme::from_config(&cfg);
146 assert!(warnings.is_empty());
147 assert_eq!(theme.quoted, vec![Color::Red, Color::Blue]);
148 assert_eq!(theme.search, Style::new().bg(Color::Yellow));
149 let (plain, _) = Theme::from_config(&Config::default());
152 assert_eq!(plain.quoted, vec![Color::Cyan]);
153 assert_eq!(plain.search, Style::new().reversed());
154 }
155
156 #[test]
157 fn mono_preset_reverses_bar() {
158 let cfg: Config = toml::from_str("[ui]\ntheme = \"mono\"\n").unwrap();
159 let (theme, warnings) = Theme::from_config(&cfg);
160 assert!(warnings.is_empty());
161 assert_eq!(theme.bar_style(), Style::new().reversed());
162 }
163}