use std::{
fs::File,
ops::DerefMut,
path::{Path, PathBuf},
sync::OnceLock,
};
use anyhow::{anyhow, Result};
use nucleo::Matcher;
use parking_lot::{Mutex, MutexGuard};
use ratatui::style::Color;
use serde_yaml_ng::{from_reader, Value};
use strum::{EnumIter, IntoEnumIterator};
use syntect::{
dumps::{from_binary, from_dump_file},
highlighting::{Theme, ThemeSet},
};
use crate::{
app::{build_previewer_plugins, PreviewerPlugin},
common::{tilde, CONFIG_FOLDER, CONFIG_PATH, PREVIEWER_PATH, SYNTECT_THEMES_PATH},
config::{
read_normal_file_colorer, FileStyle, Gradient, MenuStyle, NormalFileColorer,
PreferedImager, SyntectTheme, MAX_GRADIENT_NORMAL,
},
log_info,
modes::PreviewerCommand,
};
pub static START_FOLDER: OnceLock<PathBuf> = OnceLock::new();
pub static IS_LOGGING: OnceLock<bool> = OnceLock::new();
pub static FILE_STYLES: OnceLock<FileStyle> = OnceLock::new();
pub static MENU_STYLES: OnceLock<MenuStyle> = OnceLock::new();
pub static COLORER: OnceLock<fn(usize) -> Color> = OnceLock::new();
pub static ARRAY_GRADIENT: OnceLock<[Color; MAX_GRADIENT_NORMAL]> = OnceLock::new();
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
static PREVIEWER_PLUGINS: OnceLock<Vec<(String, PreviewerPlugin)>> = OnceLock::new();
static PREVIEWER_COMMANDS: OnceLock<Vec<PreviewerCommand>> = OnceLock::new();
static PREFERED_IMAGER: OnceLock<PreferedImager> = OnceLock::new();
pub fn get_prefered_imager() -> Option<&'static PreferedImager> {
PREFERED_IMAGER.get()
}
pub fn set_previewer_plugins(plugins: Vec<(String, String)>) -> Result<()> {
let _ = PREVIEWER_PLUGINS.set(build_previewer_plugins(plugins));
Ok(())
}
pub fn get_previewer_plugins() -> Option<&'static Vec<(String, PreviewerPlugin)>> {
PREVIEWER_PLUGINS.get()
}
fn parse_previewer_commands() -> Option<Vec<PreviewerCommand>> {
let file = std::fs::File::open(tilde(PREVIEWER_PATH).as_ref()).ok()?;
let commands: Vec<PreviewerCommand> = from_reader(file).ok()?;
log_info!("Previewer commands: {commands:?}");
Some(commands)
}
pub fn set_previewer_command() -> Result<()> {
let commands = parse_previewer_commands().unwrap_or_default();
let _ = PREVIEWER_COMMANDS.set(commands);
Ok(())
}
pub fn get_previewer_command() -> Option<&'static Vec<PreviewerCommand>> {
PREVIEWER_COMMANDS.get()
}
pub fn set_syntect_theme() -> Result<()> {
let config_theme = SyntectTheme::from_config(CONFIG_PATH)?;
if !set_syntect_theme_from_config(&config_theme.name) {
set_syntect_theme_from_source_code()
}
Ok(())
}
pub fn set_prefered_imager() -> Result<()> {
let prefered_imager = PreferedImager::from_config(CONFIG_PATH)?;
let _ = PREFERED_IMAGER.set(prefered_imager);
Ok(())
}
#[derive(EnumIter, Debug)]
enum SyntectThemeKind {
TmTheme,
Dump,
}
impl SyntectThemeKind {
fn extension(&self) -> &str {
match self {
Self::TmTheme => "tmTheme",
Self::Dump => "themedump",
}
}
fn load(&self, themepath: &Path) -> Result<Theme> {
match self {
Self::TmTheme => ThemeSet::get_theme(themepath)
.map_err(|e| anyhow!("Couldn't load syntect theme {e:}")),
Self::Dump => {
from_dump_file(themepath).map_err(|e| anyhow!("Couldn't load syntect theme {e:}"))
}
}
}
}
fn set_syntect_theme_from_config(syntect_theme: &str) -> bool {
let syntect_theme_path = PathBuf::from(tilde(SYNTECT_THEMES_PATH).as_ref());
for kind in SyntectThemeKind::iter() {
if load_syntect(&syntect_theme_path, syntect_theme, &kind) {
return true;
}
log_info!("Couldn't load {syntect_theme} {kind:?}");
}
false
}
fn load_syntect(syntect_theme_path: &Path, syntect_theme: &str, kind: &SyntectThemeKind) -> bool {
let mut full_path = syntect_theme_path.to_path_buf();
full_path.push(syntect_theme);
full_path.set_extension(kind.extension());
if !full_path.exists() {
return false;
}
let Ok(theme) = kind.load(&full_path) else {
crate::log_info!("Syntect couldn't load {fp}", fp = full_path.display());
return false;
};
let name = theme.name.clone();
if SYNTECT_THEME.set(theme).is_ok() {
log_info!("SYNTECT_THEME set to {name:?}");
true
} else {
crate::log_info!("SYNTECT_THEME was already set!");
false
}
}
fn set_syntect_theme_from_source_code() {
let _ = SYNTECT_THEME.set(from_binary(include_bytes!(
"../../assets/themes/monokai.themedump"
)));
}
pub fn get_syntect_theme() -> Option<&'static Theme> {
SYNTECT_THEME.get()
}
static ICON: OnceLock<bool> = OnceLock::new();
static ICON_WITH_METADATA: OnceLock<bool> = OnceLock::new();
pub fn with_icon() -> bool {
*ICON.get().unwrap_or(&false)
}
pub fn with_icon_metadata() -> bool {
*ICON_WITH_METADATA.get().unwrap_or(&false)
}
fn set_start_folder(start_folder: &str) -> Result<()> {
START_FOLDER
.set(std::fs::canonicalize(tilde(start_folder).as_ref()).unwrap_or_default())
.map_err(|_| anyhow!("Start folder shouldn't be set"))?;
Ok(())
}
fn set_file_styles(yaml: &Option<Value>) -> Result<()> {
FILE_STYLES
.set(FileStyle::from_config(yaml))
.map_err(|_| anyhow!("File colors shouldn't be set"))?;
Ok(())
}
fn set_menu_styles(yaml: &Option<Value>) -> Result<()> {
MENU_STYLES
.set(MenuStyle::default().update(yaml))
.map_err(|_| anyhow!("Menu colors shouldn't be set"))?;
Ok(())
}
fn set_normal_file_colorer(yaml: &Option<Value>) -> Result<()> {
let (start_color, stop_color) = read_normal_file_colorer(yaml);
ARRAY_GRADIENT
.set(Gradient::new(start_color, stop_color, MAX_GRADIENT_NORMAL).as_array()?)
.map_err(|_| anyhow!("Gradient shouldn't be set"))?;
COLORER
.set(NormalFileColorer::colorer as fn(usize) -> Color)
.map_err(|_| anyhow!("Colorer shouldn't be set"))?;
Ok(())
}
fn read_yaml_bool(yaml: &Value, key: &str) -> Option<bool> {
yaml[key].as_bool()
}
fn read_icon_icon_with_metadata() -> (bool, bool) {
let Ok(file) = File::open(Path::new(&tilde(CONFIG_PATH).to_string())) else {
crate::log_info!("Couldn't read config file at {CONFIG_PATH}");
return (false, false);
};
let Ok(yaml) = from_reader::<File, Value>(file) else {
return (false, false);
};
let mut icon: bool = false;
let mut icon_with_metadata: bool = false;
if let Some(i) = read_yaml_bool(&yaml, "icon") {
icon = i;
}
if !icon {
icon_with_metadata = false;
} else if let Some(icon_with) = read_yaml_bool(&yaml, "icon_with_metadata") {
icon_with_metadata = icon_with;
}
(icon, icon_with_metadata)
}
pub fn set_icon_icon_with_metadata() -> Result<()> {
let (icon, icon_with_metadata) = read_icon_icon_with_metadata();
ICON.set(icon)
.map_err(|_| anyhow!("ICON shouldn't be set"))?;
ICON_WITH_METADATA
.set(icon_with_metadata)
.map_err(|_| anyhow!("ICON_WITH_METADATA shouldn't be set"))?;
Ok(())
}
pub fn set_configurable_static(
start_folder: &str,
plugins: Vec<(String, String)>,
theme: String,
) -> Result<()> {
let theme_yaml = read_theme(theme);
set_start_folder(start_folder)?;
set_menu_styles(&theme_yaml)?;
set_file_styles(&theme_yaml)?;
set_normal_file_colorer(&theme_yaml)?;
set_icon_icon_with_metadata()?;
set_syntect_theme()?;
set_prefered_imager()?;
set_previewer_plugins(plugins)?;
set_previewer_command()?;
Ok(())
}
fn read_theme(theme: String) -> Option<Value> {
read_yaml_value(&build_theme_path(theme))
}
fn build_theme_path(theme: String) -> PathBuf {
let config_folder = tilde(CONFIG_FOLDER);
let mut theme_path = PathBuf::from(config_folder.as_ref());
theme_path.push("themes");
theme_path.push(theme);
theme_path.set_extension("yaml");
theme_path
}
fn read_yaml_value(path: &Path) -> Option<Value> {
let Ok(file) = File::open(path) else {
return None;
};
let Ok(yaml) = from_reader::<File, Value>(file) else {
return None;
};
Some(yaml)
}
pub struct LazyMutex<T> {
inner: Mutex<Option<T>>,
init: fn() -> T,
}
impl<T> LazyMutex<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
inner: Mutex::new(None),
init,
}
}
pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
}
}
pub static MATCHER: LazyMutex<Matcher> = LazyMutex::new(Matcher::default);