rush_sync_server/commands/theme/
themes.rs

1use crate::core::prelude::*;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct ThemeDefinition {
6    pub input_text: String,
7    pub input_bg: String,
8    pub cursor: String,
9    pub output_text: String,
10    pub output_bg: String,
11}
12
13/// ✅ TOML-Theme-Loader - Lädt Themes aus Config-Datei
14pub struct TomlThemeLoader;
15
16impl TomlThemeLoader {
17    /// Lädt alle Themes aus TOML-Datei
18    pub async fn load_all_themes() -> Result<HashMap<String, ThemeDefinition>> {
19        let config_paths = crate::setup::setup_toml::get_config_paths();
20
21        for path in config_paths {
22            if path.exists() {
23                match tokio::fs::read_to_string(&path).await {
24                    Ok(content) => {
25                        if let Ok(themes) = Self::parse_themes_from_toml(&content) {
26                            log::debug!(
27                                "Loaded {} themes from TOML: {}",
28                                themes.len(),
29                                path.display()
30                            );
31                            return Ok(themes);
32                        }
33                    }
34                    Err(e) => {
35                        log::debug!("Could not read config file '{}': {}", path.display(), e);
36                        continue;
37                    }
38                }
39            }
40        }
41
42        // ✅ FALLBACK: Hardcodierte Themes falls keine TOML
43        log::debug!("No TOML themes found, using fallback themes");
44        Ok(Self::get_fallback_themes())
45    }
46
47    /// Lädt ein spezifisches Theme aus TOML
48    pub async fn load_theme_by_name(theme_name: &str) -> Result<Option<ThemeDefinition>> {
49        let all_themes = Self::load_all_themes().await?;
50        Ok(all_themes.get(&theme_name.to_lowercase()).cloned())
51    }
52
53    /// Synchrone Version für Live-Updates (cached)
54    pub fn load_theme_by_name_sync(theme_name: &str) -> Option<ThemeDefinition> {
55        let config_paths = crate::setup::setup_toml::get_config_paths();
56
57        for path in config_paths {
58            if path.exists() {
59                if let Ok(content) = std::fs::read_to_string(&path) {
60                    if let Ok(themes) = Self::parse_themes_from_toml(&content) {
61                        return themes.get(&theme_name.to_lowercase()).cloned();
62                    }
63                }
64            }
65        }
66
67        // ✅ FALLBACK: Hardcodiert
68        Self::get_fallback_themes()
69            .get(&theme_name.to_lowercase())
70            .cloned()
71    }
72
73    /// Parst [theme.xyz] Sektionen aus TOML-String
74    pub fn parse_themes_from_toml(content: &str) -> Result<HashMap<String, ThemeDefinition>> {
75        let mut themes = HashMap::new();
76        let mut current_theme_name: Option<String> = None;
77        let mut current_theme_data: HashMap<String, String> = HashMap::new();
78
79        for line in content.lines() {
80            let trimmed = line.trim();
81
82            // ✅ IGNORE comments und empty lines
83            if trimmed.is_empty() || trimmed.starts_with('#') {
84                continue;
85            }
86
87            // ✅ THEME SECTION: [theme.dark]
88            if trimmed.starts_with("[theme.") && trimmed.ends_with(']') {
89                // Speichere vorheriges Theme
90                if let Some(theme_name) = current_theme_name.take() {
91                    if let Some(theme_def) = Self::build_theme_from_data(&current_theme_data) {
92                        themes.insert(theme_name, theme_def);
93                    }
94                    current_theme_data.clear();
95                }
96
97                // Extrahiere neuen Theme-Namen
98                if let Some(name) = trimmed
99                    .strip_prefix("[theme.")
100                    .and_then(|s| s.strip_suffix(']'))
101                {
102                    current_theme_name = Some(name.to_lowercase());
103                }
104            }
105            // ✅ ANDERE SECTION: Speichere aktuelles Theme
106            else if trimmed.starts_with('[')
107                && trimmed.ends_with(']')
108                && !trimmed.starts_with("[theme.")
109            {
110                if let Some(theme_name) = current_theme_name.take() {
111                    if let Some(theme_def) = Self::build_theme_from_data(&current_theme_data) {
112                        themes.insert(theme_name, theme_def);
113                    }
114                    current_theme_data.clear();
115                }
116            }
117            // ✅ THEME PROPERTY: input_text = "White"
118            else if current_theme_name.is_some() && trimmed.contains('=') {
119                if let Some((key, value)) = trimmed.split_once('=') {
120                    let clean_key = key.trim().to_string();
121                    let clean_value = value
122                        .trim()
123                        .trim_matches('"')
124                        .trim_matches('\'')
125                        .trim()
126                        .to_string();
127
128                    if !clean_value.is_empty() {
129                        current_theme_data.insert(clean_key, clean_value);
130                    }
131                }
132            }
133        }
134
135        // ✅ LETZTES THEME speichern
136        if let Some(theme_name) = current_theme_name {
137            if let Some(theme_def) = Self::build_theme_from_data(&current_theme_data) {
138                themes.insert(theme_name, theme_def);
139            }
140        }
141
142        log::debug!("Parsed {} themes from TOML", themes.len());
143        Ok(themes)
144    }
145
146    /// Baut ThemeDefinition aus geparsten Daten
147    pub fn build_theme_from_data(data: &HashMap<String, String>) -> Option<ThemeDefinition> {
148        // ✅ ALLE REQUIRED FIELDS müssen vorhanden sein
149        let input_text = data.get("input_text")?.clone();
150        let input_bg = data.get("input_bg")?.clone();
151        let cursor = data.get("cursor")?.clone();
152        let output_text = data.get("output_text")?.clone();
153        let output_bg = data.get("output_bg")?.clone();
154
155        Some(ThemeDefinition {
156            input_text,
157            input_bg,
158            cursor,
159            output_text,
160            output_bg,
161        })
162    }
163
164    /// Fallback Themes (falls TOML nicht existiert)
165    pub fn get_fallback_themes() -> HashMap<String, ThemeDefinition> {
166        let mut themes = HashMap::new();
167
168        themes.insert(
169            "dark".to_string(),
170            ThemeDefinition {
171                input_text: "White".to_string(),
172                input_bg: "Black".to_string(),
173                cursor: "White".to_string(),
174                output_text: "White".to_string(),
175                output_bg: "Black".to_string(),
176            },
177        );
178
179        themes.insert(
180            "light".to_string(),
181            ThemeDefinition {
182                input_text: "Black".to_string(),
183                input_bg: "White".to_string(),
184                cursor: "Black".to_string(),
185                output_text: "Black".to_string(),
186                output_bg: "White".to_string(),
187            },
188        );
189
190        themes.insert(
191            "matrix".to_string(),
192            ThemeDefinition {
193                input_text: "LightGreen".to_string(),
194                input_bg: "Black".to_string(),
195                cursor: "LightGreen".to_string(),
196                output_text: "Green".to_string(),
197                output_bg: "Black".to_string(),
198            },
199        );
200
201        themes.insert(
202            "blue".to_string(),
203            ThemeDefinition {
204                input_text: "LightBlue".to_string(),
205                input_bg: "Black".to_string(),
206                cursor: "LightBlue".to_string(),
207                output_text: "LightBlue".to_string(),
208                output_bg: "Black".to_string(),
209            },
210        );
211
212        themes
213    }
214
215    /// Prüft ob Theme in TOML existiert
216    pub async fn theme_exists(theme_name: &str) -> bool {
217        if let Ok(themes) = Self::load_all_themes().await {
218            themes.contains_key(&theme_name.to_lowercase())
219        } else {
220            false
221        }
222    }
223
224    /// Sync version für theme_exists
225    pub fn theme_exists_sync(theme_name: &str) -> bool {
226        Self::load_theme_by_name_sync(theme_name).is_some()
227    }
228
229    /// Gibt verfügbare Theme-Namen zurück (aus TOML)
230    pub async fn get_available_names() -> Vec<String> {
231        if let Ok(themes) = Self::load_all_themes().await {
232            let mut names: Vec<String> = themes.keys().cloned().collect();
233            names.sort();
234            names
235        } else {
236            vec![
237                "dark".to_string(),
238                "light".to_string(),
239                "matrix".to_string(),
240                "blue".to_string(),
241            ]
242        }
243    }
244}
245
246/// ✅ LEGACY COMPATIBILITY: PredefinedThemes wird zu Wrapper
247pub struct PredefinedThemes;
248
249impl PredefinedThemes {
250    /// ✅ WRAPPER: Verwendet jetzt TOML-Loader
251    pub fn get_by_name(name: &str) -> Option<ThemeDefinition> {
252        TomlThemeLoader::load_theme_by_name_sync(name)
253    }
254
255    /// ✅ WRAPPER: Verwendet jetzt TOML-Loader
256    pub fn exists(name: &str) -> bool {
257        TomlThemeLoader::theme_exists_sync(name)
258    }
259
260    /// ✅ WRAPPER: Verwendet jetzt TOML-Loader (async wrapper für sync call)
261    pub fn get_all() -> HashMap<String, ThemeDefinition> {
262        // ✅ SYNC FALLBACK - verwendet cached/sync version
263        let config_paths = crate::setup::setup_toml::get_config_paths();
264
265        for path in config_paths {
266            if path.exists() {
267                if let Ok(content) = std::fs::read_to_string(&path) {
268                    if let Ok(themes) = TomlThemeLoader::parse_themes_from_toml(&content) {
269                        return themes;
270                    }
271                }
272            }
273        }
274
275        // Fallback
276        TomlThemeLoader::get_fallback_themes()
277    }
278
279    /// ✅ WRAPPER: Get available names
280    pub fn get_available_names() -> Vec<String> {
281        Self::get_all().keys().cloned().collect()
282    }
283}