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