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::app::{build_plugins, PreviewerPlugin};
20use crate::config::{
21 read_normal_file_colorer, FileStyle, Gradient, MenuStyle, NormalFileColorer, PreferedImager,
22 SyntectTheme, MAX_GRADIENT_NORMAL,
23};
24
25use crate::{
26 common::{tilde, CONFIG_PATH, SYNTECT_THEMES_PATH},
27 log_info,
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 PLUGINS: OnceLock<Vec<(String, PreviewerPlugin)>> = OnceLock::new();
61
62static PREFERED_IMAGER: OnceLock<PreferedImager> = OnceLock::new();
63
64pub fn get_prefered_imager() -> Option<&'static PreferedImager> {
65 PREFERED_IMAGER.get()
66}
67
68pub fn set_previewer_plugins(plugins: Vec<(String, String)>) -> Result<()> {
70 let _ = PLUGINS.set(build_plugins(plugins));
71 Ok(())
72}
73
74pub fn get_previewer_plugins() -> Option<&'static Vec<(String, PreviewerPlugin)>> {
76 PLUGINS.get()
77}
78
79pub fn set_syntect_theme() -> Result<()> {
83 let config_theme = SyntectTheme::from_config(CONFIG_PATH)?;
84 if !set_syntect_theme_from_config(&config_theme.name) {
85 set_syntect_theme_from_source_code()
86 }
87 Ok(())
88}
89
90pub fn set_prefered_imager() -> Result<()> {
91 let prefered_imager = PreferedImager::from_config(CONFIG_PATH)?;
92 let _ = PREFERED_IMAGER.set(prefered_imager);
93 Ok(())
94}
95
96#[derive(EnumIter, Debug)]
97enum SyntectThemeKind {
98 TmTheme,
99 Dump,
100}
101
102impl SyntectThemeKind {
103 fn extension(&self) -> &str {
104 match self {
105 Self::TmTheme => "tmTheme",
106 Self::Dump => "themedump",
107 }
108 }
109
110 fn load(&self, themepath: &Path) -> Result<Theme> {
111 match self {
112 Self::TmTheme => ThemeSet::get_theme(themepath)
113 .map_err(|e| anyhow!("Couldn't load syntect theme {e:}")),
114 Self::Dump => {
115 from_dump_file(themepath).map_err(|e| anyhow!("Couldn't load syntect theme {e:}"))
116 }
117 }
118 }
119}
120
121fn set_syntect_theme_from_config(syntect_theme: &str) -> bool {
122 let syntect_theme_path = PathBuf::from(tilde(SYNTECT_THEMES_PATH).as_ref());
123 for kind in SyntectThemeKind::iter() {
124 if load_syntect(&syntect_theme_path, syntect_theme, &kind) {
125 return true;
126 }
127 log_info!("Couldn't load {syntect_theme} {kind:?}");
128 }
129 false
130}
131
132fn load_syntect(syntect_theme_path: &Path, syntect_theme: &str, kind: &SyntectThemeKind) -> bool {
133 let mut full_path = syntect_theme_path.to_path_buf();
134 full_path.push(syntect_theme);
135 full_path.set_extension(kind.extension());
136 if !full_path.exists() {
137 return false;
138 }
139 let Ok(theme) = kind.load(&full_path) else {
140 crate::log_info!("Syntect couldn't load {fp}", fp = full_path.display());
141 return false;
142 };
143 let name = theme.name.clone();
144 if SYNTECT_THEME.set(theme).is_ok() {
145 log_info!("SYNTECT_THEME set to {name:?}");
146 true
147 } else {
148 crate::log_info!("SYNTECT_THEME was already set!");
149 false
150 }
151}
152
153fn set_syntect_theme_from_source_code() {
154 let _ = SYNTECT_THEME.set(from_binary(include_bytes!(
155 "../../assets/themes/monokai.themedump"
156 )));
157}
158
159pub fn get_syntect_theme() -> Option<&'static Theme> {
161 SYNTECT_THEME.get()
162}
163
164static ICON: OnceLock<bool> = OnceLock::new();
165static ICON_WITH_METADATA: OnceLock<bool> = OnceLock::new();
166
167pub fn with_icon() -> bool {
169 *ICON.get().unwrap_or(&false)
170}
171
172pub fn with_icon_metadata() -> bool {
174 *ICON_WITH_METADATA.get().unwrap_or(&false)
175}
176
177fn set_start_folder(start_folder: &str) -> Result<()> {
178 START_FOLDER
179 .set(std::fs::canonicalize(tilde(start_folder).as_ref()).unwrap_or_default())
180 .map_err(|_| anyhow!("Start folder shouldn't be set"))?;
181 Ok(())
182}
183
184fn set_file_styles() -> Result<()> {
185 FILE_STYLES
186 .set(FileStyle::from_config())
187 .map_err(|_| anyhow!("File colors shouldn't be set"))?;
188 Ok(())
189}
190
191fn set_menu_styles() -> Result<()> {
192 MENU_STYLES
193 .set(MenuStyle::default().update())
194 .map_err(|_| anyhow!("Menu colors shouldn't be set"))?;
195 Ok(())
196}
197
198fn set_normal_file_colorer() -> Result<()> {
199 let (start_color, stop_color) = read_normal_file_colorer();
200 ARRAY_GRADIENT
201 .set(Gradient::new(start_color, stop_color, MAX_GRADIENT_NORMAL).as_array()?)
202 .map_err(|_| anyhow!("Gradient shouldn't be set"))?;
203 COLORER
204 .set(NormalFileColorer::colorer as fn(usize) -> Color)
205 .map_err(|_| anyhow!("Colorer shouldn't be set"))?;
206
207 Ok(())
208}
209
210fn read_yaml_bool(yaml: &Value, key: &str) -> Option<bool> {
211 yaml[key].as_bool()
212}
213
214fn read_icon_icon_with_metadata() -> (bool, bool) {
215 let Ok(file) = File::open(Path::new(&tilde(CONFIG_PATH).to_string())) else {
216 crate::log_info!("Couldn't read config file at {CONFIG_PATH}");
217 return (false, false);
218 };
219 let Ok(yaml) = from_reader::<File, Value>(file) else {
220 return (false, false);
221 };
222 let mut icon: bool = false;
223 let mut icon_with_metadata: bool = false;
224 if let Some(i) = read_yaml_bool(&yaml, "icon") {
225 icon = i;
226 }
227 if !icon {
228 icon_with_metadata = false;
229 } else if let Some(icon_with) = read_yaml_bool(&yaml, "icon_with_metadata") {
230 icon_with_metadata = icon_with;
231 }
232 (icon, icon_with_metadata)
233}
234
235pub fn set_icon_icon_with_metadata() -> Result<()> {
245 let (icon, icon_with_metadata) = read_icon_icon_with_metadata();
246 ICON.set(icon)
247 .map_err(|_| anyhow!("ICON shouldn't be set"))?;
248 ICON_WITH_METADATA
249 .set(icon_with_metadata)
250 .map_err(|_| anyhow!("ICON_WITH_METADATA shouldn't be set"))?;
251 Ok(())
252}
253
254pub fn set_configurable_static(start_folder: &str, plugins: Vec<(String, String)>) -> Result<()> {
258 set_start_folder(start_folder)?;
259 set_menu_styles()?;
260 set_file_styles()?;
261 set_normal_file_colorer()?;
262 set_icon_icon_with_metadata()?;
263 set_syntect_theme()?;
264 set_prefered_imager()?;
265 set_previewer_plugins(plugins)
266}
267
268pub struct LazyMutex<T> {
273 inner: Mutex<Option<T>>,
274 init: fn() -> T,
275}
276
277impl<T> LazyMutex<T> {
278 pub const fn new(init: fn() -> T) -> Self {
280 Self {
281 inner: Mutex::new(None),
282 init,
283 }
284 }
285
286 pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
290 MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
291 }
292}
293
294pub static MATCHER: LazyMutex<Matcher> = LazyMutex::new(Matcher::default);