ricecoder_lsp/code_actions/
generic_engine.rs

1//! Generic code actions engine that works with pluggable providers
2//!
3//! This module provides a language-agnostic code actions engine that delegates
4//! to language-specific providers registered in the provider registry.
5
6use crate::providers::{CodeActionProvider, CodeActionRegistry, ProviderResult};
7use crate::types::Diagnostic;
8
9/// Generic code actions engine that delegates to pluggable providers
10pub struct GenericCodeActionsEngine {
11    registry: CodeActionRegistry,
12}
13
14impl GenericCodeActionsEngine {
15    /// Create a new generic code actions engine
16    pub fn new() -> Self {
17        Self {
18            registry: CodeActionRegistry::new(),
19        }
20    }
21
22    /// Register a code action provider
23    pub fn register_provider(&mut self, provider: Box<dyn CodeActionProvider>) {
24        self.registry.register(provider);
25    }
26
27    /// Get the provider registry
28    pub fn registry(&self) -> &CodeActionRegistry {
29        &self.registry
30    }
31
32    /// Get a mutable reference to the provider registry
33    pub fn registry_mut(&mut self) -> &mut CodeActionRegistry {
34        &mut self.registry
35    }
36
37    /// Suggest code actions using the appropriate provider or fallback
38    pub fn suggest_actions(
39        &self,
40        diagnostic: &Diagnostic,
41        code: &str,
42        language: &str,
43    ) -> ProviderResult<Vec<String>> {
44        if let Some(provider) = self.registry.get(language) {
45            provider.suggest_actions(diagnostic, code)
46        } else {
47            // Gracefully degrade to empty actions for unconfigured languages
48            tracing::debug!(
49                "No code action provider found for language '{}', returning empty",
50                language
51            );
52            Ok(Vec::new())
53        }
54    }
55
56    /// Apply a code action using the appropriate provider or fallback
57    pub fn apply_action(&self, code: &str, action: &str, language: &str) -> ProviderResult<String> {
58        if let Some(provider) = self.registry.get(language) {
59            provider.apply_action(code, action)
60        } else {
61            // Gracefully degrade to returning original code for unconfigured languages
62            tracing::debug!(
63                "No code action provider found for language '{}', returning original code",
64                language
65            );
66            Ok(code.to_string())
67        }
68    }
69
70    /// Check if a provider is registered for a language
71    pub fn has_provider(&self, language: &str) -> bool {
72        self.registry.has_provider(language)
73    }
74
75    /// List all registered languages
76    pub fn languages(&self) -> Vec<&str> {
77        self.registry.languages()
78    }
79}
80
81impl Default for GenericCodeActionsEngine {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    struct MockCodeActionProvider;
92
93    impl CodeActionProvider for MockCodeActionProvider {
94        fn language(&self) -> &str {
95            "mock"
96        }
97
98        fn suggest_actions(
99            &self,
100            _diagnostic: &Diagnostic,
101            _code: &str,
102        ) -> ProviderResult<Vec<String>> {
103            Ok(vec!["action1".to_string()])
104        }
105
106        fn apply_action(&self, code: &str, _action: &str) -> ProviderResult<String> {
107            Ok(code.to_string())
108        }
109
110        fn config(&self) -> Option<&crate::config::LanguageConfig> {
111            None
112        }
113    }
114
115    #[test]
116    fn test_generic_code_actions_engine_with_provider() {
117        let mut engine = GenericCodeActionsEngine::new();
118        engine.register_provider(Box::new(MockCodeActionProvider));
119
120        assert!(engine.has_provider("mock"));
121    }
122
123    #[test]
124    fn test_generic_code_actions_engine_fallback() {
125        use crate::types::{DiagnosticSeverity, Position, Range};
126
127        let engine = GenericCodeActionsEngine::new();
128
129        // Should gracefully degrade for unknown language
130        assert!(!engine.has_provider("unknown"));
131        let diagnostic = Diagnostic::new(
132            Range::new(Position::new(0, 0), Position::new(0, 5)),
133            DiagnosticSeverity::Error,
134            "test".to_string(),
135        );
136        let result = engine.suggest_actions(&diagnostic, "test", "unknown");
137        assert!(result.is_ok());
138        assert!(result.unwrap().is_empty());
139    }
140
141    #[test]
142    fn test_generic_code_actions_engine_languages() {
143        let mut engine = GenericCodeActionsEngine::new();
144        engine.register_provider(Box::new(MockCodeActionProvider));
145
146        let languages = engine.languages();
147        assert!(languages.contains(&"mock"));
148    }
149}