use std::{fs::File, path};
use anyhow::Result;
use clap::Parser;
use ratatui::style::{Color, Style};
use serde_yaml_ng::{from_reader, Value};
use crate::common::{tilde, SYNTECT_DEFAULT_THEME};
use crate::config::{make_default_config_files, Bindings, ColorG};
use crate::io::Args;
use crate::log_info;
#[derive(Debug, Clone, Default)]
pub struct Config {
pub binds: Bindings,
pub plugins: Vec<(String, String)>,
pub theme: String,
}
impl Config {
fn update_from_config(&mut self, yaml: &Value) -> Result<()> {
self.binds.update_normal(&yaml["keys"]);
self.binds.update_custom(&yaml["custom"]);
self.update_plugins(&yaml["plugins"]["previewer"]);
self.update_theme(&yaml["theme"]);
Ok(())
}
fn update_theme(&mut self, yaml: &Value) {
if let Some(theme) = yaml.as_str() {
self.theme = theme.to_string();
log_info!("Found theme in config: {theme}");
} else {
log_info!("Couldn't find a theme in config.");
}
}
fn update_plugins(&mut self, yaml: &Value) {
let Some(mappings) = yaml.as_mapping() else {
return;
};
for (plugin_name, plugin_path) in mappings.iter() {
let Some(plugin_name) = plugin_name.as_str() else {
continue;
};
let Some(plugin_path) = plugin_path.as_str() else {
continue;
};
if path::Path::new(plugin_path).exists() {
self.plugins
.push((plugin_name.to_owned(), plugin_path.to_owned()));
} else {
log_info!("{plugin_path} is specified in config file but doesn't exists.");
}
}
log_info!("found plugins: {plugins:#?}", plugins = self.plugins);
}
}
fn ensure_config_files_exists(path: &str) -> Result<()> {
let expanded_path = tilde(path);
let expanded_config_path = path::Path::new(expanded_path.as_ref());
if !expanded_config_path.exists() {
make_default_config_files()?;
log_info!("Created default config files.");
}
Ok(())
}
pub fn load_config(path: &str) -> Result<Config> {
ensure_config_files_exists(path)?;
let mut config = Config::default();
let Ok(file) = File::open(&*tilde(path)) else {
crate::log_info!("Couldn't read config file at {path}");
return Ok(config);
};
let Ok(yaml) = from_reader(file) else {
return Ok(config);
};
let _ = config.update_from_config(&yaml);
Ok(config)
}
pub fn read_normal_file_colorer(yaml: &Option<Value>) -> (ColorG, ColorG) {
let default_pair = (ColorG::new(0, 255, 0), ColorG::new(0, 0, 255));
let Some(yaml) = yaml else {
return default_pair;
};
let Some(start) = yaml["normal_start"].as_str() else {
return default_pair;
};
let Some(stop) = yaml["normal_stop"].as_str() else {
return default_pair;
};
let Some(start_color) = ColorG::parse_any_color(start) else {
return default_pair;
};
let Some(stop_color) = ColorG::parse_any_color(stop) else {
return default_pair;
};
(start_color, stop_color)
}
macro_rules! update_style {
($self_style:expr, $yaml:ident, $key:expr) => {
if let Some(color) = read_yaml_string($yaml, $key) {
$self_style = crate::config::str_to_ratatui(color).into();
}
};
}
fn read_yaml_string(yaml: &Value, key: &str) -> Option<String> {
yaml[key].as_str().map(|s| s.to_string())
}
#[derive(Debug, Clone)]
pub struct FileStyle {
pub directory: Style,
pub block: Style,
pub char: Style,
pub fifo: Style,
pub socket: Style,
pub symlink: Style,
pub broken: Style,
}
impl FileStyle {
fn new() -> Self {
Self {
directory: Color::Red.into(),
block: Color::Yellow.into(),
char: Color::Green.into(),
fifo: Color::Blue.into(),
socket: Color::Cyan.into(),
symlink: Color::Magenta.into(),
broken: Color::White.into(),
}
}
fn update_values(&mut self, yaml: &Value) {
update_style!(self.directory, yaml, "directory");
update_style!(self.block, yaml, "block");
update_style!(self.char, yaml, "char");
update_style!(self.fifo, yaml, "fifo");
update_style!(self.socket, yaml, "socket");
update_style!(self.symlink, yaml, "symlink");
update_style!(self.broken, yaml, "broken");
}
fn update_from_config(&mut self, yaml: &Option<Value>) {
let Some(yaml) = yaml else {
return;
};
self.update_values(yaml);
}
pub fn from_config(yaml: &Option<Value>) -> Self {
let mut style = Self::default();
style.update_from_config(yaml);
style
}
}
impl Default for FileStyle {
fn default() -> Self {
Self::new()
}
}
pub struct MenuStyle {
pub first: Style,
pub second: Style,
pub selected_border: Style,
pub inert_border: Style,
pub palette_1: Style,
pub palette_2: Style,
pub palette_3: Style,
pub palette_4: Style,
}
impl Default for MenuStyle {
fn default() -> Self {
Self {
first: Color::Rgb(45, 250, 209).into(),
second: Color::Rgb(230, 189, 87).into(),
selected_border: Color::Rgb(45, 250, 209).into(),
inert_border: Color::Rgb(248, 248, 248).into(),
palette_1: Color::Rgb(45, 250, 209).into(),
palette_2: Color::Rgb(230, 189, 87).into(),
palette_3: Color::Rgb(230, 167, 255).into(),
palette_4: Color::Rgb(59, 204, 255).into(),
}
}
}
impl MenuStyle {
pub fn update(mut self, yaml: &Option<Value>) -> Self {
if let Some(menu_colors) = yaml {
update_style!(self.first, menu_colors, "header_first");
update_style!(self.second, menu_colors, "header_second");
update_style!(self.selected_border, menu_colors, "selected_border");
update_style!(self.inert_border, menu_colors, "inert_border");
update_style!(self.palette_1, menu_colors, "palette_1");
update_style!(self.palette_2, menu_colors, "palette_2");
update_style!(self.palette_3, menu_colors, "palette_3");
update_style!(self.palette_4, menu_colors, "palette_4");
}
self
}
#[inline]
pub const fn palette(&self) -> [Style; 4] {
[
self.palette_1,
self.palette_2,
self.palette_3,
self.palette_4,
]
}
#[inline]
pub const fn palette_size(&self) -> usize {
self.palette().len()
}
}
#[derive(Debug)]
pub struct SyntectTheme {
pub name: String,
}
impl Default for SyntectTheme {
fn default() -> Self {
Self {
name: SYNTECT_DEFAULT_THEME.to_owned(),
}
}
}
impl SyntectTheme {
pub fn from_config(path: &str) -> Result<Self> {
let Ok(file) = File::open(path::Path::new(&tilde(path).to_string())) else {
crate::log_info!("Couldn't read config file at {path}");
return Ok(Self::default());
};
let Ok(yaml) = from_reader::<File, Value>(file) else {
return Ok(Self::default());
};
let Some(name) = yaml["syntect_theme"].as_str() else {
return Ok(Self::default());
};
crate::log_info!("Config: found syntect theme: {name}");
Ok(Self {
name: name.to_string(),
})
}
}
#[derive(Default, Debug)]
pub enum Imagers {
#[default]
Disabled,
Ueberzug,
Inline,
}
#[derive(Debug, Default)]
pub struct PreferedImager {
pub imager: Imagers,
}
impl PreferedImager {
pub fn from_config(path: &str) -> Result<Self> {
if Args::parse().disable_images {
return Ok(Self::default());
}
let Ok(file) = File::open(path::Path::new(&tilde(path).to_string())) else {
crate::log_info!("Couldn't read config file at {path}");
return Ok(Self::default());
};
let Ok(yaml) = from_reader::<File, Value>(file) else {
return Ok(Self::default());
};
let Some(imager) = yaml["imager"].as_str() else {
return Ok(Self::default());
};
crate::log_info!("Config: found imager : {imager}");
let imager = match imager {
"Ueberzug" => Imagers::Ueberzug,
"Inline" => Imagers::Inline,
_ => Imagers::Disabled,
};
Ok(Self { imager })
}
}