ricecoder_tui/
theme_registry.rs1use crate::style::Theme;
4use anyhow::{anyhow, Result};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8#[derive(Clone)]
10pub struct ThemeRegistry {
11 builtin_themes: Arc<HashMap<String, Theme>>,
13 custom_themes: Arc<RwLock<HashMap<String, Theme>>>,
15}
16
17impl ThemeRegistry {
18 pub fn new() -> Self {
20 let mut builtin = HashMap::new();
21
22 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 pub fn get(&self, name: &str) -> Option<Theme> {
38 if let Some(theme) = self.builtin_themes.get(name) {
40 return Some(theme.clone());
41 }
42
43 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 pub fn get_builtin(&self, name: &str) -> Option<Theme> {
55 self.builtin_themes.get(name).cloned()
56 }
57
58 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 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 pub fn list_all(&self) -> Result<Vec<String>> {
80 let mut names = Vec::new();
81
82 names.extend(self.builtin_themes.keys().cloned());
84
85 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 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 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 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 pub fn is_builtin(&self, name: &str) -> bool {
126 self.builtin_themes.contains_key(name)
127 }
128
129 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 pub fn builtin_count(&self) -> usize {
140 self.builtin_themes.len()
141 }
142
143 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 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 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); }
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}