ricecoder_ide/themes/
integration.rs1use super::IdeThemeManager;
7use crate::error::{IdeError, IdeResult};
8use ricecoder_storage::PathResolver;
9use tracing::{debug, info};
10
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct IdeThemeConfig {
14 pub current_theme: String,
16 pub enable_custom_themes: bool,
18 pub custom_themes_dir: Option<String>,
20}
21
22impl Default for IdeThemeConfig {
23 fn default() -> Self {
24 Self {
25 current_theme: "dark".to_string(),
26 enable_custom_themes: true,
27 custom_themes_dir: None,
28 }
29 }
30}
31
32pub struct IdeThemeIntegration {
34 theme_manager: IdeThemeManager,
35 config: IdeThemeConfig,
36}
37
38impl IdeThemeIntegration {
39 pub fn new(config: IdeThemeConfig) -> Self {
41 Self {
42 theme_manager: IdeThemeManager::new(),
43 config,
44 }
45 }
46
47 pub async fn initialize(&mut self) -> IdeResult<()> {
49 debug!("Initializing IDE theme system");
50
51 self.load_theme_preference().await?;
53
54 if self.config.enable_custom_themes {
56 self.load_custom_themes().await?;
57 }
58
59 info!(
60 "IDE theme system initialized with theme: {}",
61 self.config.current_theme
62 );
63
64 Ok(())
65 }
66
67 async fn load_theme_preference(&mut self) -> IdeResult<()> {
69 debug!("Loading theme preference from storage");
70
71 let storage_path = PathResolver::resolve_global_path()
73 .map_err(|e| IdeError::config_error(format!("Failed to get storage path: {}", e)))?
74 .join("theme.yaml");
75
76 if storage_path.exists() {
77 match tokio::fs::read_to_string(&storage_path).await {
78 Ok(content) => {
79 match serde_yaml::from_str::<IdeThemeConfig>(&content) {
80 Ok(loaded_config) => {
81 self.config = loaded_config;
82 debug!("Loaded theme preference: {}", self.config.current_theme);
83 }
84 Err(e) => {
85 debug!("Failed to parse theme config: {}", e);
86 }
88 }
89 }
90 Err(e) => {
91 debug!("Failed to read theme config: {}", e);
92 }
94 }
95 }
96
97 self.theme_manager
99 .switch_by_name(&self.config.current_theme)
100 .map_err(|e| IdeError::config_error(format!("Failed to switch theme: {}", e)))?;
101
102 Ok(())
103 }
104
105 async fn load_custom_themes(&mut self) -> IdeResult<()> {
107 debug!("Loading custom themes");
108
109 let custom_themes_dir = if let Some(dir) = &self.config.custom_themes_dir {
110 PathResolver::expand_home(&std::path::PathBuf::from(dir))
111 .map_err(|e| IdeError::config_error(format!("Failed to resolve path: {}", e)))?
112 } else {
113 IdeThemeManager::custom_themes_directory()
114 .map_err(|e| IdeError::config_error(format!("Failed to get themes dir: {}", e)))?
115 };
116
117 if custom_themes_dir.exists() {
118 match self
119 .theme_manager
120 .load_and_register_custom_themes(&custom_themes_dir)
121 {
122 Ok(themes) => {
123 info!("Loaded {} custom themes", themes.len());
124 debug!("Custom themes: {:?}", themes);
125 }
126 Err(e) => {
127 debug!("Failed to load custom themes: {}", e);
128 }
130 }
131 }
132
133 Ok(())
134 }
135
136 pub fn switch_theme(&mut self, theme_name: &str) -> IdeResult<()> {
138 debug!("Switching to theme: {}", theme_name);
139
140 self.theme_manager
141 .switch_by_name(theme_name)
142 .map_err(|e| IdeError::config_error(format!("Failed to switch theme: {}", e)))?;
143
144 self.config.current_theme = theme_name.to_string();
145
146 if let Err(e) = self.save_theme_preference() {
148 debug!("Failed to save theme preference: {}", e);
149 }
151
152 info!("Switched to theme: {}", theme_name);
153
154 Ok(())
155 }
156
157 fn save_theme_preference(&self) -> IdeResult<()> {
159 debug!("Saving theme preference to storage");
160
161 let storage_path = PathResolver::resolve_global_path()
162 .map_err(|e| IdeError::config_error(format!("Failed to get storage path: {}", e)))?
163 .join("theme.yaml");
164
165 if let Some(parent) = storage_path.parent() {
167 std::fs::create_dir_all(parent)
168 .map_err(|e| IdeError::config_error(format!("Failed to create dir: {}", e)))?;
169 }
170
171 let yaml = serde_yaml::to_string(&self.config)
172 .map_err(|e| IdeError::config_error(format!("Failed to serialize config: {}", e)))?;
173
174 std::fs::write(&storage_path, yaml)
175 .map_err(|e| IdeError::config_error(format!("Failed to write config: {}", e)))?;
176
177 debug!("Theme preference saved to {}", storage_path.display());
178
179 Ok(())
180 }
181
182 pub fn theme_manager(&self) -> &IdeThemeManager {
184 &self.theme_manager
185 }
186
187 pub fn theme_manager_mut(&mut self) -> &mut IdeThemeManager {
189 &mut self.theme_manager
190 }
191
192 pub fn config(&self) -> &IdeThemeConfig {
194 &self.config
195 }
196
197 pub fn config_mut(&mut self) -> &mut IdeThemeConfig {
199 &mut self.config
200 }
201
202 pub fn available_themes(&self) -> Vec<&'static str> {
204 self.theme_manager.available_themes()
205 }
206
207 pub fn current_theme_name(&self) -> String {
209 self.config.current_theme.clone()
210 }
211
212 pub fn list_all_themes(&self) -> IdeResult<Vec<String>> {
214 self.theme_manager
215 .list_all_themes()
216 .map_err(|e| IdeError::config_error(format!("Failed to list themes: {}", e)))
217 }
218
219 pub fn list_builtin_themes(&self) -> Vec<String> {
221 self.theme_manager.list_builtin_themes()
222 }
223
224 pub fn list_custom_themes(&self) -> IdeResult<Vec<String>> {
226 self.theme_manager
227 .list_custom_themes()
228 .map_err(|e| IdeError::config_error(format!("Failed to list custom themes: {}", e)))
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_ide_theme_config_default() {
238 let config = IdeThemeConfig::default();
239 assert_eq!(config.current_theme, "dark");
240 assert!(config.enable_custom_themes);
241 }
242
243 #[test]
244 fn test_ide_theme_integration_creation() {
245 let config = IdeThemeConfig::default();
246 let integration = IdeThemeIntegration::new(config);
247 assert_eq!(integration.current_theme_name(), "dark");
248 }
249
250 #[test]
251 fn test_ide_theme_integration_available_themes() {
252 let config = IdeThemeConfig::default();
253 let integration = IdeThemeIntegration::new(config);
254 let themes = integration.available_themes();
255 assert_eq!(themes.len(), 6);
256 }
257
258 #[test]
259 fn test_ide_theme_integration_list_builtin_themes() {
260 let config = IdeThemeConfig::default();
261 let integration = IdeThemeIntegration::new(config);
262 let themes = integration.list_builtin_themes();
263 assert_eq!(themes.len(), 6);
264 assert!(themes.contains(&"dark".to_string()));
265 assert!(themes.contains(&"light".to_string()));
266 }
267
268 #[tokio::test]
269 async fn test_ide_theme_integration_switch_theme() {
270 let config = IdeThemeConfig::default();
271 let mut integration = IdeThemeIntegration::new(config);
272
273 integration.switch_theme("light").unwrap();
274 assert_eq!(integration.current_theme_name(), "light");
275
276 integration.switch_theme("dracula").unwrap();
277 assert_eq!(integration.current_theme_name(), "dracula");
278 }
279
280 #[tokio::test]
281 async fn test_ide_theme_integration_switch_invalid_theme() {
282 let config = IdeThemeConfig::default();
283 let mut integration = IdeThemeIntegration::new(config);
284
285 let result = integration.switch_theme("invalid");
286 assert!(result.is_err());
287 }
288}