ricecoder_lsp/config/
manager.rs

1//! Configuration manager for loading and managing language configurations
2//!
3//! This module provides utilities for loading language configurations from files,
4//! managing provider registries, and supporting hot-reload of configurations.
5//! Integrates with ricecoder-storage for cross-platform path resolution and
6//! configuration hierarchy management.
7
8use super::loader::ConfigLoader;
9use super::types::{ConfigRegistry, ConfigResult, LanguageConfig};
10use crate::code_actions::adapters::{
11    PythonCodeActionAdapter, RustCodeActionAdapter, TypeScriptCodeActionAdapter,
12};
13use crate::diagnostics::adapters::{
14    PythonDiagnosticsAdapter, RustDiagnosticsAdapter, TypeScriptDiagnosticsAdapter,
15};
16use crate::providers::{CodeActionRegistry, DiagnosticsRegistry, SemanticAnalyzerRegistry};
17use crate::semantic::adapters::{
18    FallbackAnalyzerAdapter, PythonAnalyzerAdapter, RustAnalyzerAdapter, TypeScriptAnalyzerAdapter,
19};
20use ricecoder_storage::PathResolver;
21use std::path::PathBuf;
22use std::sync::{Arc, RwLock};
23
24/// Configuration manager for language configurations
25pub struct ConfigurationManager {
26    /// Configuration registry
27    config_registry: Arc<RwLock<ConfigRegistry>>,
28    /// Semantic analyzer provider registry
29    semantic_registry: Arc<RwLock<SemanticAnalyzerRegistry>>,
30    /// Diagnostics provider registry
31    diagnostics_registry: Arc<RwLock<DiagnosticsRegistry>>,
32    /// Code action provider registry
33    code_action_registry: Arc<RwLock<CodeActionRegistry>>,
34}
35
36impl ConfigurationManager {
37    /// Create a new configuration manager
38    pub fn new() -> Self {
39        Self {
40            config_registry: Arc::new(RwLock::new(ConfigRegistry::new())),
41            semantic_registry: Arc::new(RwLock::new(SemanticAnalyzerRegistry::new())),
42            diagnostics_registry: Arc::new(RwLock::new(DiagnosticsRegistry::new())),
43            code_action_registry: Arc::new(RwLock::new(CodeActionRegistry::new())),
44        }
45    }
46
47    /// Load default language configurations
48    pub fn load_defaults(&self) -> ConfigResult<()> {
49        // Register default semantic analyzers
50        {
51            let mut registry = self.semantic_registry.write().unwrap();
52            registry.register(Box::new(RustAnalyzerAdapter::new()));
53            registry.register(Box::new(TypeScriptAnalyzerAdapter::new()));
54            registry.register(Box::new(PythonAnalyzerAdapter::new()));
55            registry.register(Box::new(FallbackAnalyzerAdapter::new()));
56        }
57
58        // Register default diagnostics providers
59        {
60            let mut registry = self.diagnostics_registry.write().unwrap();
61            registry.register(Box::new(RustDiagnosticsAdapter::new()));
62            registry.register(Box::new(TypeScriptDiagnosticsAdapter::new()));
63            registry.register(Box::new(PythonDiagnosticsAdapter::new()));
64        }
65
66        // Register default code action providers
67        {
68            let mut registry = self.code_action_registry.write().unwrap();
69            registry.register(Box::new(RustCodeActionAdapter::new()));
70            registry.register(Box::new(TypeScriptCodeActionAdapter::new()));
71            registry.register(Box::new(PythonCodeActionAdapter::new()));
72        }
73
74        Ok(())
75    }
76
77    /// Get the LSP language configuration directory from storage
78    ///
79    /// Returns paths in priority order:
80    /// 1. Runtime configuration (if provided)
81    /// 2. Project-level configuration (.agent/lsp/languages/)
82    /// 3. User-level configuration (~/.ricecoder/lsp/languages/)
83    /// 4. Built-in configuration (from ricecoder-storage)
84    pub fn get_language_config_paths() -> ConfigResult<Vec<PathBuf>> {
85        let mut paths = Vec::new();
86
87        // Try project-level configuration
88        let project_path = PathResolver::resolve_project_path();
89        let project_lsp_path = project_path.join("lsp").join("languages");
90        if project_lsp_path.exists() {
91            paths.push(project_lsp_path);
92        }
93
94        // Try user-level configuration
95        if let Ok(global_path) = PathResolver::resolve_global_path() {
96            let user_lsp_path = global_path.join("lsp").join("languages");
97            if user_lsp_path.exists() {
98                paths.push(user_lsp_path);
99            }
100        }
101
102        // Built-in configurations are loaded separately
103        Ok(paths)
104    }
105
106    /// Load configurations from storage hierarchy
107    ///
108    /// Loads configurations in priority order:
109    /// 1. Project-level (.agent/lsp/languages/)
110    /// 2. User-level (~/.ricecoder/lsp/languages/)
111    /// 3. Built-in defaults
112    pub fn load_from_storage(&self) -> ConfigResult<()> {
113        // Load from storage paths
114        if let Ok(paths) = Self::get_language_config_paths() {
115            for path in paths {
116                if path.exists() {
117                    self.load_from_directory(&path)?;
118                }
119            }
120        }
121
122        // Always load defaults as fallback
123        self.load_defaults()?;
124
125        Ok(())
126    }
127
128    /// Load configurations from a directory
129    pub fn load_from_directory(&self, path: &std::path::Path) -> ConfigResult<()> {
130        let registry = ConfigLoader::load_directory(path)?;
131
132        // Update configuration registry
133        {
134            let mut config_reg = self.config_registry.write().unwrap();
135            for language in registry.languages() {
136                if let Some(config) = registry.get(language) {
137                    config_reg.register(config.clone())?;
138                }
139            }
140        }
141
142        // Update provider registries with configurations
143        self.update_providers_from_configs()?;
144
145        Ok(())
146    }
147
148    /// Load a single configuration file
149    pub fn load_config_file(&self, path: &std::path::Path) -> ConfigResult<()> {
150        let config = ConfigLoader::load(path)?;
151
152        // Update configuration registry
153        {
154            let mut registry = self.config_registry.write().unwrap();
155            registry.register(config.clone())?;
156        }
157
158        // Update provider registries with configuration
159        self.update_providers_from_configs()?;
160
161        Ok(())
162    }
163
164    /// Update provider registries from configurations
165    fn update_providers_from_configs(&self) -> ConfigResult<()> {
166        let config_reg = self.config_registry.read().unwrap();
167
168        for language in config_reg.languages() {
169            if let Some(config) = config_reg.get(language) {
170                // Update diagnostics providers with configuration
171                {
172                    let mut diag_reg = self.diagnostics_registry.write().unwrap();
173                    match language {
174                        "rust" => {
175                            diag_reg.register(Box::new(RustDiagnosticsAdapter::with_config(
176                                config.clone(),
177                            )));
178                        }
179                        "typescript" => {
180                            diag_reg.register(Box::new(TypeScriptDiagnosticsAdapter::with_config(
181                                config.clone(),
182                            )));
183                        }
184                        "python" => {
185                            diag_reg.register(Box::new(PythonDiagnosticsAdapter::with_config(
186                                config.clone(),
187                            )));
188                        }
189                        _ => {}
190                    }
191                }
192
193                // Update code action providers with configuration
194                {
195                    let mut action_reg = self.code_action_registry.write().unwrap();
196                    match language {
197                        "rust" => {
198                            action_reg.register(Box::new(RustCodeActionAdapter::with_config(
199                                config.clone(),
200                            )));
201                        }
202                        "typescript" => {
203                            action_reg.register(Box::new(
204                                TypeScriptCodeActionAdapter::with_config(config.clone()),
205                            ));
206                        }
207                        "python" => {
208                            action_reg.register(Box::new(PythonCodeActionAdapter::with_config(
209                                config.clone(),
210                            )));
211                        }
212                        _ => {}
213                    }
214                }
215            }
216        }
217
218        Ok(())
219    }
220
221    /// Get the configuration registry
222    pub fn config_registry(&self) -> Arc<RwLock<ConfigRegistry>> {
223        Arc::clone(&self.config_registry)
224    }
225
226    /// Get the semantic analyzer registry
227    pub fn semantic_registry(&self) -> Arc<RwLock<SemanticAnalyzerRegistry>> {
228        Arc::clone(&self.semantic_registry)
229    }
230
231    /// Get the diagnostics registry
232    pub fn diagnostics_registry(&self) -> Arc<RwLock<DiagnosticsRegistry>> {
233        Arc::clone(&self.diagnostics_registry)
234    }
235
236    /// Get the code action registry
237    pub fn code_action_registry(&self) -> Arc<RwLock<CodeActionRegistry>> {
238        Arc::clone(&self.code_action_registry)
239    }
240
241    /// Check if a language is configured
242    pub fn has_language(&self, language: &str) -> bool {
243        let registry = self.config_registry.read().unwrap();
244        registry.has_language(language)
245    }
246
247    /// List all configured languages
248    pub fn languages(&self) -> Vec<String> {
249        let registry = self.config_registry.read().unwrap();
250        registry
251            .languages()
252            .into_iter()
253            .map(|s| s.to_string())
254            .collect()
255    }
256
257    /// Get configuration for a language
258    pub fn get_config(&self, language: &str) -> Option<LanguageConfig> {
259        let registry = self.config_registry.read().unwrap();
260        registry.get(language).cloned()
261    }
262}
263
264impl Default for ConfigurationManager {
265    fn default() -> Self {
266        Self::new()
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_configuration_manager_creation() {
276        let manager = ConfigurationManager::new();
277        assert!(manager
278            .config_registry
279            .read()
280            .unwrap()
281            .languages()
282            .is_empty());
283    }
284
285    #[test]
286    fn test_load_defaults() {
287        let manager = ConfigurationManager::new();
288        assert!(manager.load_defaults().is_ok());
289
290        // Check that default providers are registered
291        let semantic_reg = manager.semantic_registry.read().unwrap();
292        assert!(semantic_reg.has_provider("rust"));
293        assert!(semantic_reg.has_provider("typescript"));
294        assert!(semantic_reg.has_provider("python"));
295        assert!(semantic_reg.has_provider("unknown"));
296    }
297
298    #[test]
299    fn test_has_language() {
300        let manager = ConfigurationManager::new();
301        manager.load_defaults().unwrap();
302
303        // After loading defaults, no configurations are registered yet
304        assert!(!manager.has_language("rust"));
305    }
306
307    #[test]
308    fn test_languages_list() {
309        let manager = ConfigurationManager::new();
310        manager.load_defaults().unwrap();
311
312        let languages = manager.languages();
313        assert!(languages.is_empty());
314    }
315}