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