|
1 | 1 | use super::json_config::LanguagesConfig; |
2 | 2 | use crate::model::types::LanguageServerConfig; |
3 | | -use std::sync::OnceLock; |
| 3 | +use std::path::PathBuf; |
| 4 | +use std::sync::{Arc, Mutex}; |
| 5 | +use std::time::{Duration, Instant}; |
4 | 6 |
|
5 | | -static LANGUAGES_CONFIG: OnceLock<LanguagesConfig> = OnceLock::new(); |
| 7 | +const CONFIG_TTL: Duration = Duration::from_secs(60); // 1 minute TTL |
6 | 8 |
|
7 | | -pub struct ConfigManager; |
| 9 | +#[derive(Debug)] |
| 10 | +struct CachedConfig { |
| 11 | + config: LanguagesConfig, |
| 12 | + loaded_at: Instant, |
| 13 | +} |
| 14 | + |
| 15 | +#[derive(Debug)] |
| 16 | +pub struct ConfigManager { |
| 17 | + config_root: PathBuf, |
| 18 | + cached_config: Arc<Mutex<Option<CachedConfig>>>, |
| 19 | +} |
8 | 20 |
|
9 | 21 | impl ConfigManager { |
10 | | - /// Get the global languages configuration |
11 | | - fn get_languages_config() -> &'static LanguagesConfig { |
12 | | - LANGUAGES_CONFIG.get_or_init(|| { |
13 | | - LanguagesConfig::load().unwrap_or_else(|e| { |
14 | | - eprintln!("Failed to load languages config: {}, using defaults", e); |
15 | | - LanguagesConfig::default_config() |
16 | | - }) |
17 | | - }) |
| 22 | + /// Create a new ConfigManager with the specified config root |
| 23 | + pub fn new(config_root: PathBuf) -> Self { |
| 24 | + Self { |
| 25 | + config_root, |
| 26 | + cached_config: Arc::new(Mutex::new(None)), |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + /// Get the languages configuration (with TTL caching) |
| 31 | + pub fn get_config(&self) -> anyhow::Result<LanguagesConfig> { |
| 32 | + let mut cache = self.cached_config.lock().unwrap(); |
| 33 | + |
| 34 | + // Check if we need to reload |
| 35 | + let needs_reload = cache.as_ref() |
| 36 | + .map(|c| c.loaded_at.elapsed() > CONFIG_TTL) |
| 37 | + .unwrap_or(true); |
| 38 | + |
| 39 | + if needs_reload { |
| 40 | + let config = LanguagesConfig::get_or_create(&self.config_root)?; |
| 41 | + *cache = Some(CachedConfig { |
| 42 | + config: config.clone(), |
| 43 | + loaded_at: Instant::now(), |
| 44 | + }); |
| 45 | + Ok(config) |
| 46 | + } else { |
| 47 | + Ok(cache.as_ref().unwrap().config.clone()) |
| 48 | + } |
18 | 49 | } |
19 | 50 |
|
20 | 51 | /// Get project patterns for a specific language |
21 | | - pub fn get_project_patterns_for_language(language: &str) -> Vec<String> { |
22 | | - Self::get_languages_config().get_project_patterns_for_language(language) |
| 52 | + pub fn get_project_patterns_for_language(&self, language: &str) -> Vec<String> { |
| 53 | + self.get_config() |
| 54 | + .map(|c| c.get_project_patterns_for_language(language)) |
| 55 | + .unwrap_or_default() |
23 | 56 | } |
24 | 57 |
|
25 | 58 | /// Get language for file extension |
26 | | - pub fn get_language_for_extension(extension: &str) -> Option<String> { |
27 | | - Self::get_languages_config().get_language_for_extension(extension) |
| 59 | + pub fn get_language_for_extension(&self, extension: &str) -> Option<String> { |
| 60 | + self.get_config() |
| 61 | + .ok() |
| 62 | + .and_then(|c| c.get_language_for_extension(extension)) |
28 | 63 | } |
29 | 64 |
|
30 | 65 | /// Get all language server configurations |
31 | | - pub fn all_configs() -> Vec<LanguageServerConfig> { |
32 | | - Self::get_languages_config().all_configs() |
| 66 | + pub fn all_configs(&self) -> Vec<LanguageServerConfig> { |
| 67 | + self.get_config() |
| 68 | + .map(|c| c.all_configs()) |
| 69 | + .unwrap_or_default() |
33 | 70 | } |
34 | 71 |
|
35 | | - /// Get language server config by language name |
36 | | - pub fn get_config_by_language(language: &str) -> Result<LanguageServerConfig, String> { |
37 | | - Self::get_languages_config().get_config_by_language(language) |
| 72 | + /// Get configuration for a specific language |
| 73 | + pub fn get_config_by_language(&self, language: &str) -> Result<LanguageServerConfig, String> { |
| 74 | + self.get_config() |
| 75 | + .map_err(|e| e.to_string()) |
| 76 | + .and_then(|c| c.get_config_by_language(language)) |
38 | 77 | } |
39 | 78 |
|
40 | | - /// Get server name for language (for workspace manager mapping) |
41 | | - pub fn get_server_name_for_language(language: &str) -> Option<String> { |
42 | | - Self::get_languages_config().get_server_name_for_language(language) |
| 79 | + /// Get server name for a language |
| 80 | + pub fn get_server_name_for_language(&self, language: &str) -> Option<String> { |
| 81 | + self.get_config() |
| 82 | + .ok() |
| 83 | + .and_then(|c| c.get_server_name_for_language(language)) |
43 | 84 | } |
44 | 85 | } |
45 | 86 |
|
46 | 87 | #[cfg(test)] |
47 | 88 | mod tests { |
48 | 89 | use super::*; |
| 90 | + use tempfile::TempDir; |
| 91 | + |
| 92 | + #[test] |
| 93 | + fn test_config_manager_new() { |
| 94 | + let temp_dir = TempDir::new().unwrap(); |
| 95 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 96 | + assert_eq!(config_manager.config_root, temp_dir.path()); |
| 97 | + } |
49 | 98 |
|
50 | 99 | #[test] |
51 | 100 | fn test_get_project_patterns_for_language() { |
52 | | - let patterns = ConfigManager::get_project_patterns_for_language("typescript"); |
53 | | - assert!(!patterns.is_empty()); |
| 101 | + let temp_dir = TempDir::new().unwrap(); |
| 102 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 103 | + let patterns = config_manager.get_project_patterns_for_language("typescript"); |
| 104 | + assert!(patterns.contains(&"package.json".to_string())); |
54 | 105 | } |
55 | 106 |
|
56 | 107 | #[test] |
57 | 108 | fn test_get_language_for_extension() { |
58 | | - assert_eq!(ConfigManager::get_language_for_extension("ts"), Some("typescript".to_string())); |
59 | | - assert_eq!(ConfigManager::get_language_for_extension("rs"), Some("rust".to_string())); |
60 | | - assert_eq!(ConfigManager::get_language_for_extension("unknown"), None); |
| 109 | + let temp_dir = TempDir::new().unwrap(); |
| 110 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 111 | + assert_eq!(config_manager.get_language_for_extension("ts"), Some("typescript".to_string())); |
| 112 | + assert_eq!(config_manager.get_language_for_extension("rs"), Some("rust".to_string())); |
| 113 | + assert_eq!(config_manager.get_language_for_extension("unknown"), None); |
61 | 114 | } |
62 | 115 |
|
63 | 116 | #[test] |
64 | 117 | fn test_all_configs() { |
65 | | - let configs = ConfigManager::all_configs(); |
66 | | - assert!(!configs.is_empty()); |
| 118 | + let temp_dir = TempDir::new().unwrap(); |
| 119 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 120 | + let configs = config_manager.all_configs(); |
| 121 | + assert_eq!(configs.len(), 3); // typescript, rust, python |
67 | 122 | } |
68 | 123 |
|
69 | 124 | #[test] |
70 | 125 | fn test_get_config_by_language() { |
71 | | - let config = ConfigManager::get_config_by_language("typescript"); |
| 126 | + let temp_dir = TempDir::new().unwrap(); |
| 127 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 128 | + let config = config_manager.get_config_by_language("typescript"); |
72 | 129 | assert!(config.is_ok()); |
73 | 130 |
|
74 | | - let invalid = ConfigManager::get_config_by_language("nonexistent"); |
| 131 | + let invalid = config_manager.get_config_by_language("nonexistent"); |
75 | 132 | assert!(invalid.is_err()); |
76 | 133 | } |
77 | 134 |
|
78 | 135 | #[test] |
79 | 136 | fn test_get_server_name_for_language() { |
80 | | - assert!(ConfigManager::get_server_name_for_language("typescript").is_some()); |
81 | | - assert_eq!(ConfigManager::get_server_name_for_language("unknown"), None); |
82 | | - } |
83 | | - |
84 | | - #[test] |
85 | | - fn test_config_fallback_on_error() { |
86 | | - // Verify default config works (covers error fallback path) |
87 | | - let default_config = crate::config::json_config::LanguagesConfig::default_config(); |
88 | | - assert!(!default_config.languages.is_empty()); |
| 137 | + let temp_dir = TempDir::new().unwrap(); |
| 138 | + let config_manager = ConfigManager::new(temp_dir.path().to_path_buf()); |
| 139 | + assert!(config_manager.get_server_name_for_language("typescript").is_some()); |
| 140 | + assert_eq!(config_manager.get_server_name_for_language("unknown"), None); |
89 | 141 | } |
90 | 142 | } |
0 commit comments