1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum ConfigError {
10 #[error("IO error: {0}")]
12 IoError(#[from] std::io::Error),
13
14 #[error("YAML parsing error: {0}")]
16 YamlError(#[from] serde_yaml::Error),
17
18 #[error("JSON parsing error: {0}")]
20 JsonError(#[from] serde_json::Error),
21
22 #[error("Validation error: {0}")]
24 ValidationError(String),
25
26 #[error("Missing configuration: {0}")]
28 MissingConfig(String),
29}
30
31pub type ConfigResult<T> = Result<T, ConfigError>;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct LanguageConfig {
37 pub language: String,
39
40 #[serde(default)]
42 pub extensions: Vec<String>,
43
44 pub parser_plugin: Option<String>,
46
47 #[serde(default)]
49 pub diagnostic_rules: Vec<DiagnosticRule>,
50
51 #[serde(default)]
53 pub code_actions: Vec<CodeActionTemplate>,
54}
55
56impl LanguageConfig {
57 pub fn validate(&self) -> ConfigResult<()> {
59 if self.language.is_empty() {
60 return Err(ConfigError::ValidationError(
61 "Language name cannot be empty".to_string(),
62 ));
63 }
64
65 if self.extensions.is_empty() {
66 return Err(ConfigError::ValidationError(format!(
67 "Language '{}' must have at least one file extension",
68 self.language
69 )));
70 }
71
72 Ok(())
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct DiagnosticRule {
79 pub name: String,
81
82 pub pattern: String,
84
85 pub severity: String,
87
88 pub message: String,
90
91 pub fix_template: Option<String>,
93
94 pub code: Option<String>,
96}
97
98impl DiagnosticRule {
99 pub fn validate(&self) -> ConfigResult<()> {
101 if self.name.is_empty() {
102 return Err(ConfigError::ValidationError(
103 "Rule name cannot be empty".to_string(),
104 ));
105 }
106
107 if self.pattern.is_empty() {
108 return Err(ConfigError::ValidationError(
109 "Rule pattern cannot be empty".to_string(),
110 ));
111 }
112
113 match self.severity.as_str() {
114 "error" | "warning" | "info" => {}
115 _ => {
116 return Err(ConfigError::ValidationError(format!(
117 "Invalid severity level: {}",
118 self.severity
119 )))
120 }
121 }
122
123 Ok(())
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CodeActionTemplate {
130 pub name: String,
132
133 pub title: String,
135
136 pub kind: String,
138
139 pub transformation: String,
141}
142
143impl CodeActionTemplate {
144 pub fn validate(&self) -> ConfigResult<()> {
146 if self.name.is_empty() {
147 return Err(ConfigError::ValidationError(
148 "Action name cannot be empty".to_string(),
149 ));
150 }
151
152 if self.title.is_empty() {
153 return Err(ConfigError::ValidationError(
154 "Action title cannot be empty".to_string(),
155 ));
156 }
157
158 match self.kind.as_str() {
159 "quickfix" | "refactor" | "source" => {}
160 _ => {
161 return Err(ConfigError::ValidationError(format!(
162 "Invalid action kind: {}",
163 self.kind
164 )))
165 }
166 }
167
168 Ok(())
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct CompletionConfig {
175 #[serde(default = "default_true")]
177 pub enabled: bool,
178
179 #[serde(default = "default_completion_timeout")]
181 pub timeout_ms: u64,
182
183 #[serde(default = "default_max_completions")]
185 pub max_completions: usize,
186
187 #[serde(default = "default_true")]
189 pub ghost_text_enabled: bool,
190
191 #[serde(default = "default_min_prefix_length")]
193 pub min_prefix_length: usize,
194
195 #[serde(default = "default_true")]
197 pub fuzzy_matching: bool,
198
199 #[serde(default = "default_true")]
201 pub frequency_ranking: bool,
202
203 #[serde(default = "default_true")]
205 pub recency_ranking: bool,
206
207 #[serde(default)]
209 pub providers: HashMap<String, String>,
210}
211
212impl Default for CompletionConfig {
213 fn default() -> Self {
214 Self {
215 enabled: true,
216 timeout_ms: 100,
217 max_completions: 50,
218 ghost_text_enabled: true,
219 min_prefix_length: 1,
220 fuzzy_matching: true,
221 frequency_ranking: true,
222 recency_ranking: true,
223 providers: HashMap::new(),
224 }
225 }
226}
227
228impl CompletionConfig {
229 pub fn validate(&self) -> ConfigResult<()> {
231 if self.timeout_ms == 0 {
232 return Err(ConfigError::ValidationError(
233 "Completion timeout must be greater than 0".to_string(),
234 ));
235 }
236
237 if self.max_completions == 0 {
238 return Err(ConfigError::ValidationError(
239 "Max completions must be greater than 0".to_string(),
240 ));
241 }
242
243 Ok(())
244 }
245}
246
247fn default_true() -> bool {
249 true
250}
251
252fn default_completion_timeout() -> u64 {
254 100
255}
256
257fn default_max_completions() -> usize {
259 50
260}
261
262fn default_min_prefix_length() -> usize {
264 1
265}
266
267pub struct ConfigRegistry {
269 languages: HashMap<String, LanguageConfig>,
271
272 completion_config: CompletionConfig,
274}
275
276impl ConfigRegistry {
277 pub fn new() -> Self {
279 Self {
280 languages: HashMap::new(),
281 completion_config: CompletionConfig::default(),
282 }
283 }
284
285 pub fn with_completion_config(completion_config: CompletionConfig) -> ConfigResult<Self> {
287 completion_config.validate()?;
288 Ok(Self {
289 languages: HashMap::new(),
290 completion_config,
291 })
292 }
293
294 pub fn register(&mut self, config: LanguageConfig) -> ConfigResult<()> {
296 config.validate()?;
297
298 for rule in &config.diagnostic_rules {
300 rule.validate()?;
301 }
302
303 for action in &config.code_actions {
305 action.validate()?;
306 }
307
308 self.languages.insert(config.language.clone(), config);
309 Ok(())
310 }
311
312 pub fn get(&self, language: &str) -> Option<&LanguageConfig> {
314 self.languages.get(language)
315 }
316
317 pub fn get_by_extension(&self, extension: &str) -> Option<&LanguageConfig> {
319 self.languages
320 .values()
321 .find(|config| config.extensions.contains(&extension.to_string()))
322 }
323
324 pub fn languages(&self) -> Vec<&str> {
326 self.languages.keys().map(|s| s.as_str()).collect()
327 }
328
329 pub fn has_language(&self, language: &str) -> bool {
331 self.languages.contains_key(language)
332 }
333
334 pub fn completion_config(&self) -> &CompletionConfig {
336 &self.completion_config
337 }
338
339 pub fn completion_config_mut(&mut self) -> &mut CompletionConfig {
341 &mut self.completion_config
342 }
343
344 pub fn set_completion_config(&mut self, config: CompletionConfig) -> ConfigResult<()> {
346 config.validate()?;
347 self.completion_config = config;
348 Ok(())
349 }
350}
351
352impl Default for ConfigRegistry {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_language_config_validation() {
364 let config = LanguageConfig {
365 language: "rust".to_string(),
366 extensions: vec!["rs".to_string()],
367 parser_plugin: Some("tree-sitter-rust".to_string()),
368 diagnostic_rules: vec![],
369 code_actions: vec![],
370 };
371
372 assert!(config.validate().is_ok());
373 }
374
375 #[test]
376 fn test_language_config_validation_empty_name() {
377 let config = LanguageConfig {
378 language: String::new(),
379 extensions: vec!["rs".to_string()],
380 parser_plugin: None,
381 diagnostic_rules: vec![],
382 code_actions: vec![],
383 };
384
385 assert!(config.validate().is_err());
386 }
387
388 #[test]
389 fn test_diagnostic_rule_validation() {
390 let rule = DiagnosticRule {
391 name: "unused-import".to_string(),
392 pattern: "use .*".to_string(),
393 severity: "warning".to_string(),
394 message: "Unused import".to_string(),
395 fix_template: None,
396 code: Some("unused-import".to_string()),
397 };
398
399 assert!(rule.validate().is_ok());
400 }
401
402 #[test]
403 fn test_config_registry() {
404 let mut registry = ConfigRegistry::new();
405
406 let config = LanguageConfig {
407 language: "rust".to_string(),
408 extensions: vec!["rs".to_string()],
409 parser_plugin: Some("tree-sitter-rust".to_string()),
410 diagnostic_rules: vec![],
411 code_actions: vec![],
412 };
413
414 assert!(registry.register(config).is_ok());
415 assert!(registry.has_language("rust"));
416 assert!(registry.get("rust").is_some());
417 assert!(registry.get_by_extension("rs").is_some());
418 }
419
420 #[test]
421 fn test_completion_config_default() {
422 let config = CompletionConfig::default();
423 assert!(config.enabled);
424 assert_eq!(config.timeout_ms, 100);
425 assert_eq!(config.max_completions, 50);
426 assert!(config.ghost_text_enabled);
427 }
428
429 #[test]
430 fn test_completion_config_validation() {
431 let config = CompletionConfig::default();
432 assert!(config.validate().is_ok());
433 }
434
435 #[test]
436 fn test_completion_config_validation_invalid_timeout() {
437 let mut config = CompletionConfig::default();
438 config.timeout_ms = 0;
439 assert!(config.validate().is_err());
440 }
441
442 #[test]
443 fn test_config_registry_completion_config() {
444 let mut registry = ConfigRegistry::new();
445 let completion_config = registry.completion_config();
446 assert!(completion_config.enabled);
447
448 let mut new_config = CompletionConfig::default();
449 new_config.enabled = false;
450 assert!(registry.set_completion_config(new_config).is_ok());
451 assert!(!registry.completion_config().enabled);
452 }
453}