1use std::{fs::File, path};
2
3use anyhow::Result;
4use clap::Parser;
5use ratatui::style::{Color, Style};
6use serde_yaml_ng::{from_reader, Value};
7
8use crate::common::{tilde, SYNTECT_DEFAULT_THEME};
9use crate::config::{make_default_config_files, Bindings, ColorG};
10use crate::io::Args;
11use crate::log_info;
12
13#[derive(Debug, Clone, Default)]
18pub struct Config {
19 pub binds: Bindings,
21 pub plugins: Vec<(String, String)>,
22 pub theme: String,
23}
24
25impl Config {
26 fn update_from_config(&mut self, yaml: &Value) -> Result<()> {
28 self.binds.update_normal(&yaml["keys"]);
29 self.binds.update_custom(&yaml["custom"]);
30 self.update_plugins(&yaml["plugins"]["previewer"]);
31 self.update_theme(&yaml["theme"]);
32 Ok(())
33 }
34
35 fn update_theme(&mut self, yaml: &Value) {
36 if let Some(theme) = yaml.as_str() {
37 self.theme = theme.to_string();
38 log_info!("Found theme in config: {theme}");
39 } else {
40 log_info!("Couldn't find a theme in config.");
41 }
42 }
43
44 fn update_plugins(&mut self, yaml: &Value) {
45 let Some(mappings) = yaml.as_mapping() else {
46 return;
47 };
48 for (plugin_name, plugin_path) in mappings.iter() {
49 let Some(plugin_name) = plugin_name.as_str() else {
50 continue;
51 };
52 let Some(plugin_path) = plugin_path.as_str() else {
53 continue;
54 };
55 if path::Path::new(plugin_path).exists() {
56 self.plugins
57 .push((plugin_name.to_owned(), plugin_path.to_owned()));
58 } else {
59 log_info!("{plugin_path} is specified in config file but doesn't exists.");
60 }
61 }
62 log_info!("found plugins: {plugins:#?}", plugins = self.plugins);
63 }
64}
65
66fn ensure_config_files_exists(path: &str) -> Result<()> {
67 let expanded_path = tilde(path);
68 let expanded_config_path = path::Path::new(expanded_path.as_ref());
69 if !expanded_config_path.exists() {
70 make_default_config_files()?;
71 log_info!("Created default config files.");
72 }
73 Ok(())
74}
75
76pub fn load_config(path: &str) -> Result<Config> {
84 ensure_config_files_exists(path)?;
85 let mut config = Config::default();
86 let Ok(file) = File::open(&*tilde(path)) else {
87 crate::log_info!("Couldn't read config file at {path}");
88 return Ok(config);
89 };
90 let Ok(yaml) = from_reader(file) else {
91 return Ok(config);
92 };
93 let _ = config.update_from_config(&yaml);
94 Ok(config)
95}
96
97pub fn read_normal_file_colorer(yaml: &Option<Value>) -> (ColorG, ColorG) {
110 let default_pair = (ColorG::new(0, 255, 0), ColorG::new(0, 0, 255));
111 let Some(yaml) = yaml else {
112 return default_pair;
113 };
114 let Some(start) = yaml["normal_start"].as_str() else {
115 return default_pair;
116 };
117 let Some(stop) = yaml["normal_stop"].as_str() else {
118 return default_pair;
119 };
120 let Some(start_color) = ColorG::parse_any_color(start) else {
121 return default_pair;
122 };
123 let Some(stop_color) = ColorG::parse_any_color(stop) else {
124 return default_pair;
125 };
126 (start_color, stop_color)
127}
128macro_rules! update_style {
129 ($self_style:expr, $yaml:ident, $key:expr) => {
130 if let Some(color) = read_yaml_string($yaml, $key) {
131 $self_style = crate::config::str_to_ratatui(color).into();
132 }
133 };
134}
135
136fn read_yaml_string(yaml: &Value, key: &str) -> Option<String> {
137 yaml[key].as_str().map(|s| s.to_string())
138}
139
140#[derive(Debug, Clone)]
143pub struct FileStyle {
144 pub directory: Style,
146 pub block: Style,
148 pub char: Style,
150 pub fifo: Style,
152 pub socket: Style,
154 pub symlink: Style,
156 pub broken: Style,
158}
159
160impl FileStyle {
161 fn new() -> Self {
162 Self {
163 directory: Color::Red.into(),
164 block: Color::Yellow.into(),
165 char: Color::Green.into(),
166 fifo: Color::Blue.into(),
167 socket: Color::Cyan.into(),
168 symlink: Color::Magenta.into(),
169 broken: Color::White.into(),
170 }
171 }
172
173 fn update_values(&mut self, yaml: &Value) {
175 update_style!(self.directory, yaml, "directory");
176 update_style!(self.block, yaml, "block");
177 update_style!(self.char, yaml, "char");
178 update_style!(self.fifo, yaml, "fifo");
179 update_style!(self.socket, yaml, "socket");
180 update_style!(self.symlink, yaml, "symlink");
181 update_style!(self.broken, yaml, "broken");
182 }
183
184 fn update_from_config(&mut self, yaml: &Option<Value>) {
185 let Some(yaml) = yaml else {
186 return;
187 };
188 self.update_values(yaml);
189 }
190
191 pub fn from_config(yaml: &Option<Value>) -> Self {
192 let mut style = Self::default();
193 style.update_from_config(yaml);
194 style
195 }
196}
197
198impl Default for FileStyle {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204pub struct MenuStyle {
206 pub first: Style,
207 pub second: Style,
208 pub selected_border: Style,
209 pub inert_border: Style,
210 pub palette_1: Style,
211 pub palette_2: Style,
212 pub palette_3: Style,
213 pub palette_4: Style,
214}
215
216impl Default for MenuStyle {
217 fn default() -> Self {
218 Self {
219 first: Color::Rgb(45, 250, 209).into(),
220 second: Color::Rgb(230, 189, 87).into(),
221 selected_border: Color::Rgb(45, 250, 209).into(),
222 inert_border: Color::Rgb(248, 248, 248).into(),
223 palette_1: Color::Rgb(45, 250, 209).into(),
224 palette_2: Color::Rgb(230, 189, 87).into(),
225 palette_3: Color::Rgb(230, 167, 255).into(),
226 palette_4: Color::Rgb(59, 204, 255).into(),
227 }
228 }
229}
230
231impl MenuStyle {
232 pub fn update(mut self, yaml: &Option<Value>) -> Self {
233 if let Some(menu_colors) = yaml {
234 update_style!(self.first, menu_colors, "header_first");
235 update_style!(self.second, menu_colors, "header_second");
236 update_style!(self.selected_border, menu_colors, "selected_border");
237 update_style!(self.inert_border, menu_colors, "inert_border");
238 update_style!(self.palette_1, menu_colors, "palette_1");
239 update_style!(self.palette_2, menu_colors, "palette_2");
240 update_style!(self.palette_3, menu_colors, "palette_3");
241 update_style!(self.palette_4, menu_colors, "palette_4");
242 }
243
244 self
245 }
246
247 #[inline]
248 pub const fn palette(&self) -> [Style; 4] {
249 [
250 self.palette_1,
251 self.palette_2,
252 self.palette_3,
253 self.palette_4,
254 ]
255 }
256
257 #[inline]
258 pub const fn palette_size(&self) -> usize {
259 self.palette().len()
260 }
261}
262
263#[derive(Debug)]
265pub struct SyntectTheme {
266 pub name: String,
267}
268
269impl Default for SyntectTheme {
270 fn default() -> Self {
271 Self {
272 name: SYNTECT_DEFAULT_THEME.to_owned(),
273 }
274 }
275}
276
277impl SyntectTheme {
278 pub fn from_config(path: &str) -> Result<Self> {
279 let Ok(file) = File::open(path::Path::new(&tilde(path).to_string())) else {
280 crate::log_info!("Couldn't read config file at {path}");
281 return Ok(Self::default());
282 };
283 let Ok(yaml) = from_reader::<File, Value>(file) else {
284 return Ok(Self::default());
285 };
286 let Some(name) = yaml["syntect_theme"].as_str() else {
287 return Ok(Self::default());
288 };
289 crate::log_info!("Config: found syntect theme: {name}");
290
291 Ok(Self {
292 name: name.to_string(),
293 })
294 }
295}
296
297#[derive(Default, Debug)]
298pub enum Imagers {
299 #[default]
300 Disabled,
301 Ueberzug,
302 Inline,
303}
304
305#[derive(Debug, Default)]
307pub struct PreferedImager {
308 pub imager: Imagers,
309}
310
311impl PreferedImager {
312 pub fn from_config(path: &str) -> Result<Self> {
313 if Args::parse().disable_images {
314 return Ok(Self::default());
315 }
316 let Ok(file) = File::open(path::Path::new(&tilde(path).to_string())) else {
317 crate::log_info!("Couldn't read config file at {path}");
318 return Ok(Self::default());
319 };
320 let Ok(yaml) = from_reader::<File, Value>(file) else {
321 return Ok(Self::default());
322 };
323 let Some(imager) = yaml["imager"].as_str() else {
324 return Ok(Self::default());
325 };
326 crate::log_info!("Config: found imager : {imager}");
327 let imager = match imager {
328 "Ueberzug" => Imagers::Ueberzug,
329 "Inline" => Imagers::Inline,
330 _ => Imagers::Disabled,
331 };
332
333 Ok(Self { imager })
334 }
335}