use std::{ collections::HashMap, path::{Path, PathBuf}, }; use anyhow::Context; use helix_core::hashmap; use log::warn; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer}; use toml::Value; pub use crate::graphics::{Color, Modifier, Style}; pub static DEFAULT_THEME: Lazy = Lazy::new(|| { toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme") }); pub static BASE16_DEFAULT_THEME: Lazy = Lazy::new(|| { toml::from_slice(include_bytes!("../../base16_theme.toml")) .expect("Failed to parse base 16 default theme") }); #[derive(Clone, Debug)] pub struct Loader { user_dir: PathBuf, default_dir: PathBuf, } impl Loader { /// Creates a new loader that can load themes from two directories. pub fn new>(user_dir: P, default_dir: P) -> Self { Self { user_dir: user_dir.as_ref().join("themes"), default_dir: default_dir.as_ref().join("themes"), } } /// Loads a theme first looking in the `user_dir` then in `default_dir` pub fn load(&self, name: &str) -> Result { if name == "default" { return Ok(self.default()); } if name == "base16_default" { return Ok(self.base16_default()); } let filename = format!("{}.toml", name); let user_path = self.user_dir.join(&filename); let path = if user_path.exists() { user_path } else { self.default_dir.join(filename) }; let data = std::fs::read(&path)?; toml::from_slice(data.as_slice()).context("Failed to deserialize theme") } pub fn read_names(path: &Path) -> Vec { std::fs::read_dir(path) .map(|entries| { entries .filter_map(|entry| { let entry = entry.ok()?; let path = entry.path(); (path.extension()? == "toml") .then(|| path.file_stem().unwrap().to_string_lossy().into_owned()) }) .collect() }) .unwrap_or_default() } /// Lists all theme names available in default and user directory pub fn names(&self) -> Vec { let mut names = Self::read_names(&self.user_dir); names.extend(Self::read_names(&self.default_dir)); names } pub fn default_theme(&self, true_color: bool) -> Theme { if true_color { self.default() } else { self.base16_default() } } /// Returns the default theme pub fn default(&self) -> Theme { DEFAULT_THEME.clone() } /// Returns the alternative 16-color default theme pub fn base16_default(&self) -> Theme { BASE16_DEFAULT_THEME.clone() } } #[derive(Clone, Debug)] pub struct Theme { // UI styles are stored in a HashMap styles: HashMap, // tree-sitter highlight styles are stored in a Vec to optimize lookups scopes: Vec, highlights: Vec