fm/config/
oncelock_static.rs1use std::{
2 fs::File,
3 ops::DerefMut,
4 path::{Path, PathBuf},
5 sync::OnceLock,
6};
7
8use anyhow::{anyhow, Result};
9use nucleo::Matcher;
10use parking_lot::{Mutex, MutexGuard};
11use ratatui::style::Color;
12use serde_yaml_ng::{from_reader, Value};
13use strum::{EnumIter, IntoEnumIterator};
14use syntect::{
15 dumps::{from_binary, from_dump_file},
16 highlighting::{Theme, ThemeSet},
17};
18
19use crate::{
20 app::{build_previewer_plugins, PreviewerPlugin},
21 common::{tilde, CONFIG_FOLDER, CONFIG_PATH, PREVIEWER_PATH, SYNTECT_THEMES_PATH},
22 config::{
23 read_normal_file_colorer, FileStyle, Gradient, MenuStyle, NormalFileColorer,
24 PreferedImager, SyntectTheme, MAX_GRADIENT_NORMAL,
25 },
26 log_info,
27 modes::PreviewerCommand,
28};
29
30pub static START_FOLDER: OnceLock<PathBuf> = OnceLock::new();
32
33pub static IS_LOGGING: OnceLock<bool> = OnceLock::new();
36
37pub static FILE_STYLES: OnceLock<FileStyle> = OnceLock::new();
44
45pub static MENU_STYLES: OnceLock<MenuStyle> = OnceLock::new();
47
48pub static COLORER: OnceLock<fn(usize) -> Color> = OnceLock::new();
53
54pub static ARRAY_GRADIENT: OnceLock<[Color; MAX_GRADIENT_NORMAL]> = OnceLock::new();
56
57static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
59
60static PREVIEWER_PLUGINS: OnceLock<Vec<(String, PreviewerPlugin)>> = OnceLock::new();
61static PREVIEWER_COMMANDS: OnceLock<Vec<PreviewerCommand>> = OnceLock::new();
62
63static PREFERED_IMAGER: OnceLock<PreferedImager> = OnceLock::new();
64
65pub fn get_prefered_imager() -> Option<&'static PreferedImager> {
66 PREFERED_IMAGER.get()
67}
68
69pub fn set_previewer_plugins(plugins: Vec<(String, String)>) -> Result<()> {
71 let _ = PREVIEWER_PLUGINS.set(build_previewer_plugins(plugins));
72 Ok(())
73}
74
75pub fn get_previewer_plugins() -> Option<&'static Vec<(String, PreviewerPlugin)>> {
77 PREVIEWER_PLUGINS.get()
78}
79
80fn parse_previewer_commands() -> Option<Vec<PreviewerCommand>> {
81 let file = std::fs::File::open(tilde(PREVIEWER_PATH).as_ref()).ok()?;
82 let commands: Vec<PreviewerCommand> = from_reader(file).ok()?;
83 log_info!("Previewer commands: {commands:?}");
84 Some(commands)
85}
86
87pub fn set_previewer_command() -> Result<()> {
89 let commands = parse_previewer_commands().unwrap_or_default();
90 let _ = PREVIEWER_COMMANDS.set(commands);
91 Ok(())
92}
93
94pub fn get_previewer_command() -> Option<&'static Vec<PreviewerCommand>> {
96 PREVIEWER_COMMANDS.get()
97}
98
99pub fn set_syntect_theme() -> Result<()> {
103 let config_theme = SyntectTheme::from_config(CONFIG_PATH)?;
104 if !set_syntect_theme_from_config(&config_theme.name) {
105 set_syntect_theme_from_source_code()
106 }
107 Ok(())
108}
109
110pub fn set_prefered_imager() -> Result<()> {
111 let prefered_imager = PreferedImager::from_config(CONFIG_PATH)?;
112 let _ = PREFERED_IMAGER.set(prefered_imager);
113 Ok(())
114}
115
116#[derive(EnumIter, Debug)]
117enum SyntectThemeKind {
118 TmTheme,
119 Dump,
120}
121
122impl SyntectThemeKind {
123 fn extension(&self) -> &str {
124 match self {
125 Self::TmTheme => "tmTheme",
126 Self::Dump => "themedump",
127 }
128 }
129
130 fn load(&self, themepath: &Path) -> Result<Theme> {
131 match self {
132 Self::TmTheme => ThemeSet::get_theme(themepath)
133 .map_err(|e| anyhow!("Couldn't load syntect theme {e:}")),
134 Self::Dump => {
135 from_dump_file(themepath).map_err(|e| anyhow!("Couldn't load syntect theme {e:}"))
136 }
137 }
138 }
139}
140
141fn set_syntect_theme_from_config(syntect_theme: &str) -> bool {
142 let syntect_theme_path = PathBuf::from(tilde(SYNTECT_THEMES_PATH).as_ref());
143 for kind in SyntectThemeKind::iter() {
144 if load_syntect(&syntect_theme_path, syntect_theme, &kind) {
145 return true;
146 }
147 log_info!("Couldn't load {syntect_theme} {kind:?}");
148 }
149 false
150}
151
152fn load_syntect(syntect_theme_path: &Path, syntect_theme: &str, kind: &SyntectThemeKind) -> bool {
153 let mut full_path = syntect_theme_path.to_path_buf();
154 full_path.push(syntect_theme);
155 full_path.set_extension(kind.extension());
156 if !full_path.exists() {
157 return false;
158 }
159 let Ok(theme) = kind.load(&full_path) else {
160 crate::log_info!("Syntect couldn't load {fp}", fp = full_path.display());
161 return false;
162 };
163 let name = theme.name.clone();
164 if SYNTECT_THEME.set(theme).is_ok() {
165 log_info!("SYNTECT_THEME set to {name:?}");
166 true
167 } else {
168 crate::log_info!("SYNTECT_THEME was already set!");
169 false
170 }
171}
172
173fn set_syntect_theme_from_source_code() {
174 let _ = SYNTECT_THEME.set(from_binary(include_bytes!(
175 "../../assets/themes/monokai.themedump"
176 )));
177}
178
179pub fn get_syntect_theme() -> Option<&'static Theme> {
181 SYNTECT_THEME.get()
182}
183
184static ICON: OnceLock<bool> = OnceLock::new();
185static ICON_WITH_METADATA: OnceLock<bool> = OnceLock::new();
186
187pub fn with_icon() -> bool {
189 *ICON.get().unwrap_or(&false)
190}
191
192pub fn with_icon_metadata() -> bool {
194 *ICON_WITH_METADATA.get().unwrap_or(&false)
195}
196
197fn set_start_folder(start_folder: &str) -> Result<()> {
198 START_FOLDER
199 .set(std::fs::canonicalize(tilde(start_folder).as_ref()).unwrap_or_default())
200 .map_err(|_| anyhow!("Start folder shouldn't be set"))?;
201 Ok(())
202}
203
204fn set_file_styles(yaml: &Option<Value>) -> Result<()> {
205 FILE_STYLES
206 .set(FileStyle::from_config(yaml))
207 .map_err(|_| anyhow!("File colors shouldn't be set"))?;
208 Ok(())
209}
210
211fn set_menu_styles(yaml: &Option<Value>) -> Result<()> {
212 MENU_STYLES
213 .set(MenuStyle::default().update(yaml))
214 .map_err(|_| anyhow!("Menu colors shouldn't be set"))?;
215 Ok(())
216}
217
218fn set_normal_file_colorer(yaml: &Option<Value>) -> Result<()> {
219 let (start_color, stop_color) = read_normal_file_colorer(yaml);
220 ARRAY_GRADIENT
221 .set(Gradient::new(start_color, stop_color, MAX_GRADIENT_NORMAL).as_array()?)
222 .map_err(|_| anyhow!("Gradient shouldn't be set"))?;
223 COLORER
224 .set(NormalFileColorer::colorer as fn(usize) -> Color)
225 .map_err(|_| anyhow!("Colorer shouldn't be set"))?;
226
227 Ok(())
228}
229
230fn read_yaml_bool(yaml: &Value, key: &str) -> Option<bool> {
231 yaml[key].as_bool()
232}
233
234fn read_icon_icon_with_metadata() -> (bool, bool) {
235 let Ok(file) = File::open(Path::new(&tilde(CONFIG_PATH).to_string())) else {
236 crate::log_info!("Couldn't read config file at {CONFIG_PATH}");
237 return (false, false);
238 };
239 let Ok(yaml) = from_reader::<File, Value>(file) else {
240 return (false, false);
241 };
242 let mut icon: bool = false;
243 let mut icon_with_metadata: bool = false;
244 if let Some(i) = read_yaml_bool(&yaml, "icon") {
245 icon = i;
246 }
247 if !icon {
248 icon_with_metadata = false;
249 } else if let Some(icon_with) = read_yaml_bool(&yaml, "icon_with_metadata") {
250 icon_with_metadata = icon_with;
251 }
252 (icon, icon_with_metadata)
253}
254
255pub fn set_icon_icon_with_metadata() -> Result<()> {
265 let (icon, icon_with_metadata) = read_icon_icon_with_metadata();
266 ICON.set(icon)
267 .map_err(|_| anyhow!("ICON shouldn't be set"))?;
268 ICON_WITH_METADATA
269 .set(icon_with_metadata)
270 .map_err(|_| anyhow!("ICON_WITH_METADATA shouldn't be set"))?;
271 Ok(())
272}
273
274pub fn set_configurable_static(
278 start_folder: &str,
279 plugins: Vec<(String, String)>,
280 theme: String,
281) -> Result<()> {
282 let theme_yaml = read_theme(theme);
283 set_start_folder(start_folder)?;
284 set_menu_styles(&theme_yaml)?;
285 set_file_styles(&theme_yaml)?;
286 set_normal_file_colorer(&theme_yaml)?;
287 set_icon_icon_with_metadata()?;
288 set_syntect_theme()?;
289 set_prefered_imager()?;
290 set_previewer_plugins(plugins)?;
291 set_previewer_command()?;
292 Ok(())
293}
294
295fn read_theme(theme: String) -> Option<Value> {
296 read_yaml_value(&build_theme_path(theme))
297}
298
299fn build_theme_path(theme: String) -> PathBuf {
300 let config_folder = tilde(CONFIG_FOLDER);
301 let mut theme_path = PathBuf::from(config_folder.as_ref());
302 theme_path.push("themes");
303 theme_path.push(theme);
304 theme_path.set_extension("yaml");
305 theme_path
306}
307
308fn read_yaml_value(path: &Path) -> Option<Value> {
309 let Ok(file) = File::open(path) else {
310 return None;
311 };
312 let Ok(yaml) = from_reader::<File, Value>(file) else {
313 return None;
314 };
315 Some(yaml)
316}
317
318pub struct LazyMutex<T> {
323 inner: Mutex<Option<T>>,
324 init: fn() -> T,
325}
326
327impl<T> LazyMutex<T> {
328 pub const fn new(init: fn() -> T) -> Self {
330 Self {
331 inner: Mutex::new(None),
332 init,
333 }
334 }
335
336 pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
340 MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
341 }
342}
343
344pub static MATCHER: LazyMutex<Matcher> = LazyMutex::new(Matcher::default);