ricecoder_ide/themes/
integration.rs

1//! IDE Theme Integration
2//!
3//! This module provides integration between the IDE and the theme system,
4//! including theme initialization, configuration loading, and persistence.
5
6use super::IdeThemeManager;
7use crate::error::{IdeError, IdeResult};
8use ricecoder_storage::PathResolver;
9use tracing::{debug, info};
10
11/// IDE Theme Configuration
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct IdeThemeConfig {
14    /// Current theme name
15    pub current_theme: String,
16    /// Enable custom themes
17    pub enable_custom_themes: bool,
18    /// Custom themes directory
19    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
32/// IDE Theme Integration Manager
33pub struct IdeThemeIntegration {
34    theme_manager: IdeThemeManager,
35    config: IdeThemeConfig,
36}
37
38impl IdeThemeIntegration {
39    /// Create a new IDE theme integration
40    pub fn new(config: IdeThemeConfig) -> Self {
41        Self {
42            theme_manager: IdeThemeManager::new(),
43            config,
44        }
45    }
46
47    /// Initialize theme system on IDE startup
48    pub async fn initialize(&mut self) -> IdeResult<()> {
49        debug!("Initializing IDE theme system");
50
51        // Load saved theme preference from storage
52        self.load_theme_preference().await?;
53
54        // Load custom themes if enabled
55        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    /// Load theme preference from storage
68    async fn load_theme_preference(&mut self) -> IdeResult<()> {
69        debug!("Loading theme preference from storage");
70
71        // Try to load from storage
72        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                            // Fall back to default
87                        }
88                    }
89                }
90                Err(e) => {
91                    debug!("Failed to read theme config: {}", e);
92                    // Fall back to default
93                }
94            }
95        }
96
97        // Apply the theme
98        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    /// Load custom themes from directory
106    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                    // Continue without custom themes
129                }
130            }
131        }
132
133        Ok(())
134    }
135
136    /// Switch to a theme by name
137    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        // Save preference
147        if let Err(e) = self.save_theme_preference() {
148            debug!("Failed to save theme preference: {}", e);
149            // Continue anyway
150        }
151
152        info!("Switched to theme: {}", theme_name);
153
154        Ok(())
155    }
156
157    /// Save theme preference to storage
158    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        // Create parent directory if needed
166        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    /// Get the current theme manager
183    pub fn theme_manager(&self) -> &IdeThemeManager {
184        &self.theme_manager
185    }
186
187    /// Get the current theme manager (mutable)
188    pub fn theme_manager_mut(&mut self) -> &mut IdeThemeManager {
189        &mut self.theme_manager
190    }
191
192    /// Get the current theme configuration
193    pub fn config(&self) -> &IdeThemeConfig {
194        &self.config
195    }
196
197    /// Get the current theme configuration (mutable)
198    pub fn config_mut(&mut self) -> &mut IdeThemeConfig {
199        &mut self.config
200    }
201
202    /// Get all available themes
203    pub fn available_themes(&self) -> Vec<&'static str> {
204        self.theme_manager.available_themes()
205    }
206
207    /// Get the current theme name
208    pub fn current_theme_name(&self) -> String {
209        self.config.current_theme.clone()
210    }
211
212    /// List all available themes (built-in and custom)
213    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    /// List all built-in themes
220    pub fn list_builtin_themes(&self) -> Vec<String> {
221        self.theme_manager.list_builtin_themes()
222    }
223
224    /// List all custom themes
225    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}