use crate::theme::key_registry::{StyleAccess, THEME_KEY_REGISTRY, ThemeFamily, ThemeKeyDef};
use crate::theme::{UiStyles, render_style_to_lx};
use super::error::ConfigError;
use super::store::config;
pub const BUILTIN_THEMES: &[&str] = &["exa", "lx-256", "lx-24bit"];
pub const BUILTIN_THEME_DESCRIPTIONS: &[(&str, &str)] = &[
("exa", "Heritage exa look in basic 8-colour ANSI"),
(
"lx-256",
"256-colour palette gradient (any 256-colour terminal)",
),
(
"lx-24bit",
"Truecolour gradient (24-bit, smooth interpolation)",
),
];
pub fn builtin_theme_description(name: &str) -> Option<&'static str> {
BUILTIN_THEME_DESCRIPTIONS
.iter()
.find(|(n, _)| *n == name)
.map(|(_, d)| *d)
}
pub fn is_builtin_theme(name: &str) -> bool {
BUILTIN_THEMES.contains(&name)
}
pub fn all_theme_names() -> Vec<String> {
let mut names: Vec<String> = BUILTIN_THEMES.iter().map(|s| (*s).to_string()).collect();
if let Some(cfg) = config() {
for name in cfg.theme.keys() {
if !names.contains(name) {
names.push(name.clone());
}
}
}
names.sort();
names
}
fn format_theme_toml(name: &str) -> Option<String> {
if is_builtin_theme(name) {
return Some(format_builtin_theme_toml(name));
}
let cfg = config()?;
let theme = cfg.theme.get(name)?;
let mut lines = vec![format!("[theme.{name}]")];
if let Some(ref description) = theme.description {
lines.push(format!("description = \"{description}\""));
}
if let Some(ref inherits) = theme.inherits {
lines.push(format!("inherits = \"{inherits}\""));
}
if let Some(ref use_style) = theme.use_style {
lines.push(format!("use-style = \"{use_style}\""));
}
let pairs: Vec<(&str, String)> = theme
.ui
.iter()
.map(|(k, v)| (k.as_str(), v.clone()))
.collect();
append_grouped_pairs(&mut lines, pairs);
Some(lines.join("\n"))
}
fn format_builtin_theme_toml(name: &str) -> String {
let ui = UiStyles::compiled(name).expect("is_builtin_theme guards this call");
let mut lines = vec![format!("[theme.{name}]")];
if let Some(description) = builtin_theme_description(name) {
lines.push(format!("description = \"{description}\""));
}
let pairs: Vec<(&str, String)> = ThemeKeyDef::dumpable()
.filter_map(|def| match def.access {
StyleAccess::Direct { get, .. } => Some((def.name, render_style_to_lx(get(&ui)))),
StyleAccess::Bulk { .. } => None,
})
.collect();
append_grouped_pairs(&mut lines, pairs);
lines.join("\n")
}
fn append_grouped_pairs(out: &mut Vec<String>, pairs: Vec<(&str, String)>) {
let mut indexed: Vec<(Option<ThemeFamily>, usize, &str, String)> = pairs
.into_iter()
.map(|(k, v)| {
let position = THEME_KEY_REGISTRY.iter().position(|d| d.name == k);
let family = position.map(|i| THEME_KEY_REGISTRY[i].family);
(family, position.unwrap_or(usize::MAX), k, v)
})
.collect();
indexed.sort_by(|a, b| (a.0, a.1, a.2).cmp(&(b.0, b.1, b.2)));
let mut last_family: Option<Option<ThemeFamily>> = None;
for (family, _pos, key, value) in indexed {
if last_family.is_some_and(|f| f != family) {
out.push(String::new());
}
out.push(format!("{key} = \"{value}\""));
last_family = Some(family);
}
}
pub fn dump_theme(name: &str) -> Result<(), ConfigError> {
if let Some(toml) = format_theme_toml(name) {
println!("{toml}");
Ok(())
} else {
Err(ConfigError::NotFound {
kind: "theme",
kind_plural: "themes",
name: name.to_string(),
candidates: all_theme_names().join(", "),
})
}
}
pub fn dump_theme_all() {
let names = all_theme_names();
let mut first = true;
for name in &names {
if let Some(toml) = format_theme_toml(name) {
if !first {
println!();
}
println!("{toml}");
first = false;
}
}
}