ricecoder_tui/
theme_registry.rs

1//! Theme registry for managing built-in and custom themes
2
3use crate::style::Theme;
4use anyhow::{anyhow, Result};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Theme registry for storing and managing themes
9#[derive(Clone)]
10pub struct ThemeRegistry {
11    /// Built-in themes (immutable)
12    builtin_themes: Arc<HashMap<String, Theme>>,
13    /// Custom themes (mutable)
14    custom_themes: Arc<RwLock<HashMap<String, Theme>>>,
15}
16
17impl ThemeRegistry {
18    /// Create a new theme registry with built-in themes
19    pub fn new() -> Self {
20        let mut builtin = HashMap::new();
21
22        // Register all built-in themes
23        builtin.insert("dark".to_string(), Theme::default());
24        builtin.insert("light".to_string(), Theme::light());
25        builtin.insert("monokai".to_string(), Theme::monokai());
26        builtin.insert("dracula".to_string(), Theme::dracula());
27        builtin.insert("nord".to_string(), Theme::nord());
28        builtin.insert("high-contrast".to_string(), Theme::high_contrast());
29
30        Self {
31            builtin_themes: Arc::new(builtin),
32            custom_themes: Arc::new(RwLock::new(HashMap::new())),
33        }
34    }
35
36    /// Get a theme by name (checks built-in first, then custom)
37    pub fn get(&self, name: &str) -> Option<Theme> {
38        // Check built-in themes first
39        if let Some(theme) = self.builtin_themes.get(name) {
40            return Some(theme.clone());
41        }
42
43        // Check custom themes
44        if let Ok(custom) = self.custom_themes.read() {
45            if let Some(theme) = custom.get(name) {
46                return Some(theme.clone());
47            }
48        }
49
50        None
51    }
52
53    /// Get a built-in theme by name
54    pub fn get_builtin(&self, name: &str) -> Option<Theme> {
55        self.builtin_themes.get(name).cloned()
56    }
57
58    /// Register a custom theme
59    pub fn register(&self, theme: Theme) -> Result<()> {
60        let mut custom = self
61            .custom_themes
62            .write()
63            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
64        custom.insert(theme.name.clone(), theme);
65        Ok(())
66    }
67
68    /// Unregister a custom theme
69    pub fn unregister(&self, name: &str) -> Result<()> {
70        let mut custom = self
71            .custom_themes
72            .write()
73            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
74        custom.remove(name);
75        Ok(())
76    }
77
78    /// List all available theme names (built-in and custom)
79    pub fn list_all(&self) -> Result<Vec<String>> {
80        let mut names = Vec::new();
81
82        // Add built-in themes
83        names.extend(self.builtin_themes.keys().cloned());
84
85        // Add custom themes
86        let custom = self
87            .custom_themes
88            .read()
89            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
90        names.extend(custom.keys().cloned());
91
92        names.sort();
93        Ok(names)
94    }
95
96    /// List all built-in theme names
97    pub fn list_builtin(&self) -> Vec<String> {
98        let mut names: Vec<_> = self.builtin_themes.keys().cloned().collect();
99        names.sort();
100        names
101    }
102
103    /// List all custom theme names
104    pub fn list_custom(&self) -> Result<Vec<String>> {
105        let custom = self
106            .custom_themes
107            .read()
108            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
109        let mut names: Vec<_> = custom.keys().cloned().collect();
110        names.sort();
111        Ok(names)
112    }
113
114    /// Check if a theme exists (built-in or custom)
115    pub fn exists(&self, name: &str) -> bool {
116        self.builtin_themes.contains_key(name)
117            || self
118                .custom_themes
119                .read()
120                .map(|custom| custom.contains_key(name))
121                .unwrap_or(false)
122    }
123
124    /// Check if a theme is built-in
125    pub fn is_builtin(&self, name: &str) -> bool {
126        self.builtin_themes.contains_key(name)
127    }
128
129    /// Check if a theme is custom
130    pub fn is_custom(&self, name: &str) -> Result<bool> {
131        let custom = self
132            .custom_themes
133            .read()
134            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
135        Ok(custom.contains_key(name))
136    }
137
138    /// Get the number of built-in themes
139    pub fn builtin_count(&self) -> usize {
140        self.builtin_themes.len()
141    }
142
143    /// Get the number of custom themes
144    pub fn custom_count(&self) -> Result<usize> {
145        let custom = self
146            .custom_themes
147            .read()
148            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
149        Ok(custom.len())
150    }
151
152    /// Reset a custom theme to its built-in default
153    pub fn reset_to_default(&self, name: &str) -> Result<()> {
154        if let Some(builtin) = self.get_builtin(name) {
155            self.register(builtin)?;
156            Ok(())
157        } else {
158            Err(anyhow!("Theme not found: {}", name))
159        }
160    }
161
162    /// Clear all custom themes
163    pub fn clear_custom(&self) -> Result<()> {
164        let mut custom = self
165            .custom_themes
166            .write()
167            .map_err(|e| anyhow!("Failed to lock custom themes: {}", e))?;
168        custom.clear();
169        Ok(())
170    }
171}
172
173impl Default for ThemeRegistry {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl std::fmt::Debug for ThemeRegistry {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        f.debug_struct("ThemeRegistry")
182            .field("builtin_count", &self.builtin_count())
183            .field("custom_count", &self.custom_count().unwrap_or(0))
184            .finish()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_registry_creation() {
194        let registry = ThemeRegistry::new();
195        assert_eq!(registry.builtin_count(), 6);
196        assert_eq!(registry.custom_count().unwrap(), 0);
197    }
198
199    #[test]
200    fn test_get_builtin_theme() {
201        let registry = ThemeRegistry::new();
202        assert!(registry.get("dark").is_some());
203        assert!(registry.get("light").is_some());
204        assert!(registry.get("monokai").is_some());
205        assert!(registry.get("dracula").is_some());
206        assert!(registry.get("nord").is_some());
207        assert!(registry.get("high-contrast").is_some());
208    }
209
210    #[test]
211    fn test_get_nonexistent_theme() {
212        let registry = ThemeRegistry::new();
213        assert!(registry.get("nonexistent").is_none());
214    }
215
216    #[test]
217    fn test_register_custom_theme() {
218        let registry = ThemeRegistry::new();
219        let custom_theme = Theme::light();
220        let mut custom = custom_theme.clone();
221        custom.name = "my-custom".to_string();
222
223        registry.register(custom).unwrap();
224        assert_eq!(registry.custom_count().unwrap(), 1);
225        assert!(registry.get("my-custom").is_some());
226    }
227
228    #[test]
229    fn test_unregister_custom_theme() {
230        let registry = ThemeRegistry::new();
231        let custom_theme = Theme::light();
232        let mut custom = custom_theme.clone();
233        custom.name = "my-custom".to_string();
234
235        registry.register(custom).unwrap();
236        assert_eq!(registry.custom_count().unwrap(), 1);
237
238        registry.unregister("my-custom").unwrap();
239        assert_eq!(registry.custom_count().unwrap(), 0);
240    }
241
242    #[test]
243    fn test_list_all_themes() {
244        let registry = ThemeRegistry::new();
245        let custom_theme = Theme::light();
246        let mut custom = custom_theme.clone();
247        custom.name = "my-custom".to_string();
248        registry.register(custom).unwrap();
249
250        let all = registry.list_all().unwrap();
251        assert_eq!(all.len(), 7); // 6 built-in + 1 custom
252    }
253
254    #[test]
255    fn test_list_builtin_themes() {
256        let registry = ThemeRegistry::new();
257        let builtin = registry.list_builtin();
258        assert_eq!(builtin.len(), 6);
259    }
260
261    #[test]
262    fn test_list_custom_themes() {
263        let registry = ThemeRegistry::new();
264        let custom_theme = Theme::light();
265        let mut custom = custom_theme.clone();
266        custom.name = "my-custom".to_string();
267        registry.register(custom).unwrap();
268
269        let custom_list = registry.list_custom().unwrap();
270        assert_eq!(custom_list.len(), 1);
271    }
272
273    #[test]
274    fn test_exists() {
275        let registry = ThemeRegistry::new();
276        assert!(registry.exists("dark"));
277        assert!(!registry.exists("nonexistent"));
278    }
279
280    #[test]
281    fn test_is_builtin() {
282        let registry = ThemeRegistry::new();
283        assert!(registry.is_builtin("dark"));
284        assert!(!registry.is_builtin("nonexistent"));
285    }
286
287    #[test]
288    fn test_is_custom() {
289        let registry = ThemeRegistry::new();
290        let custom_theme = Theme::light();
291        let mut custom = custom_theme.clone();
292        custom.name = "my-custom".to_string();
293        registry.register(custom).unwrap();
294
295        assert!(registry.is_custom("my-custom").unwrap());
296        assert!(!registry.is_custom("dark").unwrap());
297    }
298
299    #[test]
300    fn test_reset_to_default() {
301        let registry = ThemeRegistry::new();
302        let custom_theme = Theme::light();
303        let mut custom = custom_theme.clone();
304        custom.name = "dark".to_string();
305        registry.register(custom).unwrap();
306
307        registry.reset_to_default("dark").unwrap();
308        let theme = registry.get("dark").unwrap();
309        assert_eq!(theme.name, "dark");
310    }
311
312    #[test]
313    fn test_clear_custom() {
314        let registry = ThemeRegistry::new();
315        let custom_theme = Theme::light();
316        let mut custom = custom_theme.clone();
317        custom.name = "my-custom".to_string();
318        registry.register(custom).unwrap();
319
320        assert_eq!(registry.custom_count().unwrap(), 1);
321        registry.clear_custom().unwrap();
322        assert_eq!(registry.custom_count().unwrap(), 0);
323    }
324}