jolt_theme/
loader.rs

1use crate::{Color, NamedTheme, ThemeColors, ThemeVariants};
2use serde::Deserialize;
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Deserialize)]
7struct ThemeFile {
8    name: String,
9    dark: Option<ThemeColorsToml>,
10    light: Option<ThemeColorsToml>,
11}
12
13#[derive(Debug, Deserialize)]
14struct ThemeColorsToml {
15    bg: String,
16    dialog_bg: String,
17    fg: String,
18    accent: String,
19    accent_secondary: String,
20    highlight: String,
21    muted: String,
22    success: String,
23    warning: String,
24    danger: String,
25    border: String,
26    selection_bg: String,
27    selection_fg: String,
28    graph_line: String,
29}
30
31fn convert_colors(colors: &ThemeColorsToml) -> Option<ThemeColors> {
32    Some(ThemeColors {
33        bg: Color::from_hex(&colors.bg)?,
34        dialog_bg: Color::from_hex(&colors.dialog_bg)?,
35        fg: Color::from_hex(&colors.fg)?,
36        accent: Color::from_hex(&colors.accent)?,
37        accent_secondary: Color::from_hex(&colors.accent_secondary)?,
38        highlight: Color::from_hex(&colors.highlight)?,
39        muted: Color::from_hex(&colors.muted)?,
40        success: Color::from_hex(&colors.success)?,
41        warning: Color::from_hex(&colors.warning)?,
42        danger: Color::from_hex(&colors.danger)?,
43        border: Color::from_hex(&colors.border)?,
44        selection_bg: Color::from_hex(&colors.selection_bg)?,
45        selection_fg: Color::from_hex(&colors.selection_fg)?,
46        graph_line: Color::from_hex(&colors.graph_line)?,
47    })
48}
49
50pub fn parse_theme_toml(id: &str, content: &str, is_builtin: bool) -> Option<NamedTheme> {
51    let theme_file: ThemeFile = toml::from_str(content).ok()?;
52
53    let dark = theme_file.dark.as_ref().and_then(convert_colors);
54    let light = theme_file.light.as_ref().and_then(convert_colors);
55
56    if dark.is_none() && light.is_none() {
57        return None;
58    }
59
60    Some(NamedTheme {
61        id: id.to_string(),
62        name: theme_file.name,
63        is_builtin,
64        variants: ThemeVariants { dark, light },
65    })
66}
67
68pub fn load_themes_from_dir(dir: &Path, is_builtin: bool) -> Vec<NamedTheme> {
69    if !dir.exists() {
70        return Vec::new();
71    }
72
73    let mut themes = Vec::new();
74
75    if let Ok(entries) = fs::read_dir(dir) {
76        for entry in entries.flatten() {
77            let path = entry.path();
78            if path.extension().is_some_and(|e| e == "toml") {
79                if let Ok(content) = fs::read_to_string(&path) {
80                    let id = path
81                        .file_stem()
82                        .and_then(|s| s.to_str())
83                        .unwrap_or("unknown")
84                        .to_string();
85
86                    if let Some(mut theme) = parse_theme_toml(&id, &content, is_builtin) {
87                        if !is_builtin {
88                            theme.name = format!("{} (user)", theme.name);
89                        }
90                        themes.push(theme);
91                    }
92                }
93            }
94        }
95    }
96
97    themes
98}