ricecoder_ide/
configured_rules_provider.rs

1//! Configured rules provider implementation
2//!
3//! This module implements the IdeProvider trait for custom IDE rules loaded
4//! from YAML/JSON configuration files.
5
6use crate::error::{IdeError, IdeResult};
7use crate::provider::IdeProvider;
8use crate::types::*;
9use async_trait::async_trait;
10use tracing::debug;
11
12/// Rule for IDE completion
13#[derive(Debug, Clone)]
14pub struct CompletionRule {
15    /// Pattern to match
16    pub pattern: String,
17    /// Completion items to suggest
18    pub suggestions: Vec<CompletionItem>,
19}
20
21/// Rule for IDE diagnostics
22#[derive(Debug, Clone)]
23pub struct DiagnosticsRule {
24    /// Pattern to match
25    pub pattern: String,
26    /// Diagnostics to report
27    pub diagnostics: Vec<Diagnostic>,
28}
29
30/// Rule for IDE hover
31#[derive(Debug, Clone)]
32pub struct HoverRule {
33    /// Pattern to match
34    pub pattern: String,
35    /// Hover information
36    pub hover: Hover,
37}
38
39/// Rule for IDE definition
40#[derive(Debug, Clone)]
41pub struct DefinitionRule {
42    /// Pattern to match
43    pub pattern: String,
44    /// Definition location
45    pub location: Location,
46}
47
48/// Configured rules provider
49pub struct ConfiguredRulesProvider {
50    /// Language this provider supports
51    language: String,
52    /// Completion rules
53    completion_rules: Vec<CompletionRule>,
54    /// Diagnostics rules
55    diagnostics_rules: Vec<DiagnosticsRule>,
56    /// Hover rules
57    hover_rules: Vec<HoverRule>,
58    /// Definition rules
59    definition_rules: Vec<DefinitionRule>,
60}
61
62impl ConfiguredRulesProvider {
63    /// Create a new configured rules provider
64    pub fn new(language: String) -> Self {
65        ConfiguredRulesProvider {
66            language,
67            completion_rules: Vec::new(),
68            diagnostics_rules: Vec::new(),
69            hover_rules: Vec::new(),
70            definition_rules: Vec::new(),
71        }
72    }
73
74    /// Add a completion rule
75    pub fn add_completion_rule(&mut self, rule: CompletionRule) {
76        self.completion_rules.push(rule);
77    }
78
79    /// Add a diagnostics rule
80    pub fn add_diagnostics_rule(&mut self, rule: DiagnosticsRule) {
81        self.diagnostics_rules.push(rule);
82    }
83
84    /// Add a hover rule
85    pub fn add_hover_rule(&mut self, rule: HoverRule) {
86        self.hover_rules.push(rule);
87    }
88
89    /// Add a definition rule
90    pub fn add_definition_rule(&mut self, rule: DefinitionRule) {
91        self.definition_rules.push(rule);
92    }
93
94    /// Load rules from YAML configuration
95    pub async fn load_from_yaml(language: String, yaml_content: &str) -> IdeResult<Self> {
96        debug!("Loading configured rules from YAML for language: {}", language);
97
98        let mut provider = ConfiguredRulesProvider::new(language);
99
100        // Parse YAML
101        let config: serde_yaml::Value = serde_yaml::from_str(yaml_content).map_err(|e| {
102            IdeError::config_error(format!("Failed to parse YAML rules: {}", e))
103        })?;
104
105        // Load completion rules
106        if let Some(completions) = config.get("completions").and_then(|v| v.as_sequence()) {
107            for completion in completions {
108                if let Ok(rule) = Self::parse_completion_rule(completion) {
109                    provider.add_completion_rule(rule);
110                }
111            }
112        }
113
114        // Load diagnostics rules
115        if let Some(diagnostics) = config.get("diagnostics").and_then(|v| v.as_sequence()) {
116            for diagnostic in diagnostics {
117                if let Ok(rule) = Self::parse_diagnostics_rule(diagnostic) {
118                    provider.add_diagnostics_rule(rule);
119                }
120            }
121        }
122
123        // Load hover rules
124        if let Some(hovers) = config.get("hovers").and_then(|v| v.as_sequence()) {
125            for hover in hovers {
126                if let Ok(rule) = Self::parse_hover_rule(hover) {
127                    provider.add_hover_rule(rule);
128                }
129            }
130        }
131
132        // Load definition rules
133        if let Some(definitions) = config.get("definitions").and_then(|v| v.as_sequence()) {
134            for definition in definitions {
135                if let Ok(rule) = Self::parse_definition_rule(definition) {
136                    provider.add_definition_rule(rule);
137                }
138            }
139        }
140
141        Ok(provider)
142    }
143
144    /// Load rules from JSON configuration
145    pub async fn load_from_json(language: String, json_content: &str) -> IdeResult<Self> {
146        debug!("Loading configured rules from JSON for language: {}", language);
147
148        let mut provider = ConfiguredRulesProvider::new(language);
149
150        // Parse JSON
151        let config: serde_json::Value = serde_json::from_str(json_content).map_err(|e| {
152            IdeError::config_error(format!("Failed to parse JSON rules: {}", e))
153        })?;
154
155        // Convert to YAML value for uniform processing
156        let yaml_config: serde_yaml::Value = serde_yaml::from_str(&serde_json::to_string(&config).unwrap()).map_err(|e| {
157            IdeError::config_error(format!("Failed to convert JSON to YAML: {}", e))
158        })?;
159
160        // Load completion rules
161        if let Some(completions) = yaml_config.get("completions").and_then(|v| v.as_sequence()) {
162            for completion in completions {
163                if let Ok(rule) = Self::parse_completion_rule(completion) {
164                    provider.add_completion_rule(rule);
165                }
166            }
167        }
168
169        // Load diagnostics rules
170        if let Some(diagnostics) = yaml_config.get("diagnostics").and_then(|v| v.as_sequence()) {
171            for diagnostic in diagnostics {
172                if let Ok(rule) = Self::parse_diagnostics_rule(diagnostic) {
173                    provider.add_diagnostics_rule(rule);
174                }
175            }
176        }
177
178        // Load hover rules
179        if let Some(hovers) = yaml_config.get("hovers").and_then(|v| v.as_sequence()) {
180            for hover in hovers {
181                if let Ok(rule) = Self::parse_hover_rule(hover) {
182                    provider.add_hover_rule(rule);
183                }
184            }
185        }
186
187        // Load definition rules
188        if let Some(definitions) = yaml_config.get("definitions").and_then(|v| v.as_sequence()) {
189            for definition in definitions {
190                if let Ok(rule) = Self::parse_definition_rule(definition) {
191                    provider.add_definition_rule(rule);
192                }
193            }
194        }
195
196        Ok(provider)
197    }
198
199    /// Parse a completion rule from YAML/JSON
200    fn parse_completion_rule(value: &serde_yaml::Value) -> IdeResult<CompletionRule> {
201        let pattern = value
202            .get("pattern")
203            .and_then(|v| v.as_str())
204            .ok_or_else(|| IdeError::config_error("Completion rule missing 'pattern' field"))?
205            .to_string();
206
207        let suggestions = value
208            .get("suggestions")
209            .and_then(|v| v.as_sequence())
210            .map(|seq| {
211                seq.iter()
212                    .filter_map(|item| {
213                        let label = item.get("label")?.as_str()?.to_string();
214                        let kind = item
215                            .get("kind")
216                            .and_then(|k| k.as_str())
217                            .and_then(Self::parse_completion_kind)
218                            .unwrap_or(CompletionItemKind::Text);
219                        let detail = item.get("detail").and_then(|d| d.as_str()).map(|s| s.to_string());
220                        let documentation = item
221                            .get("documentation")
222                            .and_then(|d| d.as_str())
223                            .map(|s| s.to_string());
224                        let insert_text = item
225                            .get("insertText")
226                            .and_then(|t| t.as_str())
227                            .unwrap_or(&label)
228                            .to_string();
229
230                        Some(CompletionItem {
231                            label,
232                            kind,
233                            detail,
234                            documentation,
235                            insert_text,
236                        })
237                    })
238                    .collect()
239            })
240            .unwrap_or_default();
241
242        Ok(CompletionRule { pattern, suggestions })
243    }
244
245    /// Parse a diagnostics rule from YAML/JSON
246    fn parse_diagnostics_rule(value: &serde_yaml::Value) -> IdeResult<DiagnosticsRule> {
247        let pattern = value
248            .get("pattern")
249            .and_then(|v| v.as_str())
250            .ok_or_else(|| IdeError::config_error("Diagnostics rule missing 'pattern' field"))?
251            .to_string();
252
253        let diagnostics = value
254            .get("diagnostics")
255            .and_then(|v| v.as_sequence())
256            .map(|seq| {
257                seq.iter()
258                    .filter_map(|item| {
259                        let message = item.get("message")?.as_str()?.to_string();
260                        let severity = item
261                            .get("severity")
262                            .and_then(|s| s.as_str())
263                            .and_then(Self::parse_severity)
264                            .unwrap_or(DiagnosticSeverity::Information);
265                        let source = item
266                            .get("source")
267                            .and_then(|s| s.as_str())
268                            .unwrap_or("configured-rules")
269                            .to_string();
270                        let range = Range {
271                            start: Position {
272                                line: 0,
273                                character: 0,
274                            },
275                            end: Position {
276                                line: 0,
277                                character: 0,
278                            },
279                        };
280
281                        Some(Diagnostic {
282                            range,
283                            severity,
284                            message,
285                            source,
286                        })
287                    })
288                    .collect()
289            })
290            .unwrap_or_default();
291
292        Ok(DiagnosticsRule { pattern, diagnostics })
293    }
294
295    /// Parse a hover rule from YAML/JSON
296    fn parse_hover_rule(value: &serde_yaml::Value) -> IdeResult<HoverRule> {
297        let pattern = value
298            .get("pattern")
299            .and_then(|v| v.as_str())
300            .ok_or_else(|| IdeError::config_error("Hover rule missing 'pattern' field"))?
301            .to_string();
302
303        let contents = value
304            .get("contents")
305            .and_then(|v| v.as_str())
306            .ok_or_else(|| IdeError::config_error("Hover rule missing 'contents' field"))?
307            .to_string();
308
309        let hover = Hover {
310            contents,
311            range: None,
312        };
313
314        Ok(HoverRule { pattern, hover })
315    }
316
317    /// Parse a definition rule from YAML/JSON
318    fn parse_definition_rule(value: &serde_yaml::Value) -> IdeResult<DefinitionRule> {
319        let pattern = value
320            .get("pattern")
321            .and_then(|v| v.as_str())
322            .ok_or_else(|| IdeError::config_error("Definition rule missing 'pattern' field"))?
323            .to_string();
324
325        let file_path = value
326            .get("file")
327            .and_then(|v| v.as_str())
328            .ok_or_else(|| IdeError::config_error("Definition rule missing 'file' field"))?
329            .to_string();
330
331        let line = value
332            .get("line")
333            .and_then(|v| v.as_u64())
334            .unwrap_or(0) as u32;
335
336        let character = value
337            .get("character")
338            .and_then(|v| v.as_u64())
339            .unwrap_or(0) as u32;
340
341        let location = Location {
342            file_path,
343            range: Range {
344                start: Position { line, character },
345                end: Position { line, character },
346            },
347        };
348
349        Ok(DefinitionRule { pattern, location })
350    }
351
352    /// Parse completion kind from string
353    fn parse_completion_kind(kind_str: &str) -> Option<CompletionItemKind> {
354        match kind_str.to_lowercase().as_str() {
355            "text" => Some(CompletionItemKind::Text),
356            "method" => Some(CompletionItemKind::Method),
357            "function" => Some(CompletionItemKind::Function),
358            "constructor" => Some(CompletionItemKind::Constructor),
359            "field" => Some(CompletionItemKind::Field),
360            "variable" => Some(CompletionItemKind::Variable),
361            "class" => Some(CompletionItemKind::Class),
362            "interface" => Some(CompletionItemKind::Interface),
363            "module" => Some(CompletionItemKind::Module),
364            "property" => Some(CompletionItemKind::Property),
365            "unit" => Some(CompletionItemKind::Unit),
366            "value" => Some(CompletionItemKind::Value),
367            "enum" => Some(CompletionItemKind::Enum),
368            "keyword" => Some(CompletionItemKind::Keyword),
369            "snippet" => Some(CompletionItemKind::Snippet),
370            "color" => Some(CompletionItemKind::Color),
371            "file" => Some(CompletionItemKind::File),
372            "reference" => Some(CompletionItemKind::Reference),
373            "folder" => Some(CompletionItemKind::Folder),
374            "enummember" => Some(CompletionItemKind::EnumMember),
375            "constant" => Some(CompletionItemKind::Constant),
376            "struct" => Some(CompletionItemKind::Struct),
377            "event" => Some(CompletionItemKind::Event),
378            "operator" => Some(CompletionItemKind::Operator),
379            "typeparameter" => Some(CompletionItemKind::TypeParameter),
380            _ => None,
381        }
382    }
383
384    /// Parse severity from string
385    fn parse_severity(severity_str: &str) -> Option<DiagnosticSeverity> {
386        match severity_str.to_lowercase().as_str() {
387            "error" => Some(DiagnosticSeverity::Error),
388            "warning" => Some(DiagnosticSeverity::Warning),
389            "information" | "info" => Some(DiagnosticSeverity::Information),
390            "hint" => Some(DiagnosticSeverity::Hint),
391            _ => None,
392        }
393    }
394
395    /// Check if context matches pattern (simple substring matching)
396    fn matches_pattern(&self, pattern: &str, context: &str) -> bool {
397        context.contains(pattern)
398    }
399}
400
401#[async_trait]
402impl IdeProvider for ConfiguredRulesProvider {
403    async fn get_completions(&self, params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
404        debug!(
405            "Getting completions from configured rules for language: {}",
406            self.language
407        );
408
409        let mut completions = Vec::new();
410
411        for rule in &self.completion_rules {
412            if self.matches_pattern(&rule.pattern, &params.context) {
413                completions.extend(rule.suggestions.clone());
414            }
415        }
416
417        Ok(completions)
418    }
419
420    async fn get_diagnostics(&self, params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
421        debug!(
422            "Getting diagnostics from configured rules for language: {}",
423            self.language
424        );
425
426        let mut diagnostics = Vec::new();
427
428        for rule in &self.diagnostics_rules {
429            if self.matches_pattern(&rule.pattern, &params.source) {
430                diagnostics.extend(rule.diagnostics.clone());
431            }
432        }
433
434        Ok(diagnostics)
435    }
436
437    async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
438        debug!(
439            "Getting hover from configured rules for language: {}",
440            self.language
441        );
442
443        // For now, return None
444        // In a full implementation, this would match patterns and return hover info
445        Ok(None)
446    }
447
448    async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
449        debug!(
450            "Getting definition from configured rules for language: {}",
451            self.language
452        );
453
454        // For now, return None
455        // In a full implementation, this would match patterns and return definition location
456        Ok(None)
457    }
458
459    fn is_available(&self, language: &str) -> bool {
460        language == self.language
461    }
462
463    fn name(&self) -> &str {
464        "configured-rules"
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471
472    #[test]
473    fn test_parse_completion_kind() {
474        assert_eq!(
475            ConfiguredRulesProvider::parse_completion_kind("function"),
476            Some(CompletionItemKind::Function)
477        );
478        assert_eq!(
479            ConfiguredRulesProvider::parse_completion_kind("class"),
480            Some(CompletionItemKind::Class)
481        );
482        assert_eq!(
483            ConfiguredRulesProvider::parse_completion_kind("unknown"),
484            None
485        );
486    }
487
488    #[test]
489    fn test_parse_severity() {
490        assert_eq!(
491            ConfiguredRulesProvider::parse_severity("error"),
492            Some(DiagnosticSeverity::Error)
493        );
494        assert_eq!(
495            ConfiguredRulesProvider::parse_severity("warning"),
496            Some(DiagnosticSeverity::Warning)
497        );
498        assert_eq!(
499            ConfiguredRulesProvider::parse_severity("unknown"),
500            None
501        );
502    }
503
504    #[test]
505    fn test_new_provider() {
506        let provider = ConfiguredRulesProvider::new("rust".to_string());
507        assert_eq!(provider.language, "rust");
508        assert!(provider.completion_rules.is_empty());
509    }
510
511    #[test]
512    fn test_add_completion_rule() {
513        let mut provider = ConfiguredRulesProvider::new("rust".to_string());
514        let rule = CompletionRule {
515            pattern: "fn ".to_string(),
516            suggestions: vec![CompletionItem {
517                label: "test".to_string(),
518                kind: CompletionItemKind::Function,
519                detail: None,
520                documentation: None,
521                insert_text: "test()".to_string(),
522            }],
523        };
524
525        provider.add_completion_rule(rule);
526        assert_eq!(provider.completion_rules.len(), 1);
527    }
528
529    #[test]
530    fn test_matches_pattern() {
531        let provider = ConfiguredRulesProvider::new("rust".to_string());
532        assert!(provider.matches_pattern("fn ", "fn test() {"));
533        assert!(!provider.matches_pattern("fn ", "let x = 5;"));
534    }
535
536    #[tokio::test]
537    async fn test_get_completions_with_matching_rule() {
538        let mut provider = ConfiguredRulesProvider::new("rust".to_string());
539        let rule = CompletionRule {
540            pattern: "fn ".to_string(),
541            suggestions: vec![CompletionItem {
542                label: "test".to_string(),
543                kind: CompletionItemKind::Function,
544                detail: None,
545                documentation: None,
546                insert_text: "test()".to_string(),
547            }],
548        };
549
550        provider.add_completion_rule(rule);
551
552        let params = CompletionParams {
553            language: "rust".to_string(),
554            file_path: "src/main.rs".to_string(),
555            position: Position {
556                line: 10,
557                character: 5,
558            },
559            context: "fn test".to_string(),
560        };
561
562        let result = provider.get_completions(&params).await;
563        assert!(result.is_ok());
564        assert_eq!(result.unwrap().len(), 1);
565    }
566
567    #[tokio::test]
568    async fn test_get_diagnostics_with_matching_rule() {
569        let mut provider = ConfiguredRulesProvider::new("rust".to_string());
570        let rule = DiagnosticsRule {
571            pattern: "unused".to_string(),
572            diagnostics: vec![Diagnostic {
573                range: Range {
574                    start: Position {
575                        line: 0,
576                        character: 0,
577                    },
578                    end: Position {
579                        line: 0,
580                        character: 0,
581                    },
582                },
583                severity: DiagnosticSeverity::Warning,
584                message: "unused variable".to_string(),
585                source: "configured-rules".to_string(),
586            }],
587        };
588
589        provider.add_diagnostics_rule(rule);
590
591        let params = DiagnosticsParams {
592            language: "rust".to_string(),
593            file_path: "src/main.rs".to_string(),
594            source: "let unused = 5;".to_string(),
595        };
596
597        let result = provider.get_diagnostics(&params).await;
598        assert!(result.is_ok());
599        assert_eq!(result.unwrap().len(), 1);
600    }
601
602    #[tokio::test]
603    async fn test_is_available() {
604        let provider = ConfiguredRulesProvider::new("rust".to_string());
605        assert!(provider.is_available("rust"));
606        assert!(!provider.is_available("typescript"));
607    }
608}