ricecoder_refactoring/providers/
lsp.rs

1//! LSP provider trait and implementation for external LSP server integration
2//!
3//! This module provides traits and implementations for integrating external LSP servers
4//! as the highest-priority provider in the refactoring engine.
5
6use crate::error::Result;
7use crate::types::{Refactoring, ValidationResult};
8use std::sync::Arc;
9
10/// Trait for LSP-based refactoring providers
11///
12/// LSP providers delegate refactoring operations to external Language Server Protocol servers.
13/// This enables leveraging specialized, maintained language tools instead of building them into ricecoder.
14pub trait LspProvider: Send + Sync {
15    /// Check if the LSP server is currently available
16    fn is_available(&self) -> bool;
17
18    /// Perform a refactoring operation via LSP
19    fn perform_refactoring(
20        &self,
21        code: &str,
22        language: &str,
23        refactoring: &Refactoring,
24    ) -> Result<String>;
25
26    /// Validate refactored code via LSP
27    fn validate_refactoring(
28        &self,
29        original: &str,
30        refactored: &str,
31        language: &str,
32    ) -> Result<ValidationResult>;
33
34    /// Register a callback for availability changes
35    fn on_availability_changed(&self, callback: Box<dyn Fn(bool) + Send + Sync>);
36}
37
38/// Registry for LSP providers
39///
40/// Manages LSP providers for different languages and supports hot-reload
41/// of provider availability without system restart.
42#[derive(Clone)]
43pub struct LspProviderRegistry {
44    providers: Arc<std::sync::Mutex<std::collections::HashMap<String, Arc<dyn LspProvider>>>>,
45}
46
47impl LspProviderRegistry {
48    /// Create a new LSP provider registry
49    pub fn new() -> Self {
50        Self {
51            providers: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
52        }
53    }
54
55    /// Register an LSP provider for a language
56    pub fn register(&self, language: String, provider: Arc<dyn LspProvider>) -> Result<()> {
57        let mut providers = self.providers.lock().map_err(|_| {
58            crate::error::RefactoringError::Other(
59                "Failed to acquire lock on LSP provider registry".to_string(),
60            )
61        })?;
62        providers.insert(language, provider);
63        Ok(())
64    }
65
66    /// Get an LSP provider for a language
67    pub fn get_provider(&self, language: &str) -> Option<Arc<dyn LspProvider>> {
68        if let Ok(providers) = self.providers.lock() {
69            providers.get(language).cloned()
70        } else {
71            None
72        }
73    }
74
75    /// Check if an LSP provider is available for a language
76    pub fn is_available(&self, language: &str) -> bool {
77        if let Some(provider) = self.get_provider(language) {
78            provider.is_available()
79        } else {
80            false
81        }
82    }
83
84    /// Get all registered languages with LSP providers
85    pub fn get_languages(&self) -> Result<Vec<String>> {
86        let providers = self.providers.lock().map_err(|_| {
87            crate::error::RefactoringError::Other(
88                "Failed to acquire lock on LSP provider registry".to_string(),
89            )
90        })?;
91        Ok(providers.keys().cloned().collect())
92    }
93
94    /// Unregister an LSP provider for a language
95    pub fn unregister(&self, language: &str) -> Result<()> {
96        let mut providers = self.providers.lock().map_err(|_| {
97            crate::error::RefactoringError::Other(
98                "Failed to acquire lock on LSP provider registry".to_string(),
99            )
100        })?;
101        providers.remove(language);
102        Ok(())
103    }
104
105    /// Update provider availability for a language
106    pub fn update_availability(&self, language: &str, available: bool) -> Result<()> {
107        if let Some(provider) = self.get_provider(language) {
108            if provider.is_available() != available {
109                // Provider availability changed - this would trigger callbacks
110                // In a real implementation, we'd notify watchers here
111            }
112        }
113        Ok(())
114    }
115}
116
117impl Default for LspProviderRegistry {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    struct MockLspProvider {
128        available: std::sync::Arc<std::sync::Mutex<bool>>,
129    }
130
131    impl LspProvider for MockLspProvider {
132        fn is_available(&self) -> bool {
133            self.available.lock().map(|a| *a).unwrap_or(false)
134        }
135
136        fn perform_refactoring(
137            &self,
138            code: &str,
139            _language: &str,
140            _refactoring: &Refactoring,
141        ) -> Result<String> {
142            Ok(code.to_string())
143        }
144
145        fn validate_refactoring(
146            &self,
147            _original: &str,
148            _refactored: &str,
149            _language: &str,
150        ) -> Result<ValidationResult> {
151            Ok(ValidationResult {
152                passed: true,
153                errors: vec![],
154                warnings: vec![],
155            })
156        }
157
158        fn on_availability_changed(&self, _callback: Box<dyn Fn(bool) + Send + Sync>) {
159            // Mock implementation
160        }
161    }
162
163    #[test]
164    fn test_lsp_provider_registry() -> Result<()> {
165        let registry = LspProviderRegistry::new();
166
167        let available = std::sync::Arc::new(std::sync::Mutex::new(true));
168        let provider: Arc<dyn LspProvider> = Arc::new(MockLspProvider {
169            available: available.clone(),
170        });
171
172        registry.register("rust".to_string(), provider.clone())?;
173
174        assert!(registry.is_available("rust"));
175        assert!(!registry.is_available("python"));
176
177        // Change availability
178        *available.lock().unwrap() = false;
179        assert!(!registry.is_available("rust"));
180
181        Ok(())
182    }
183
184    #[test]
185    fn test_get_languages() -> Result<()> {
186        let registry = LspProviderRegistry::new();
187
188        let available = std::sync::Arc::new(std::sync::Mutex::new(true));
189        let provider: Arc<dyn LspProvider> = Arc::new(MockLspProvider {
190            available: available.clone(),
191        });
192
193        registry.register("rust".to_string(), provider.clone())?;
194        registry.register("typescript".to_string(), provider.clone())?;
195
196        let languages = registry.get_languages()?;
197        assert_eq!(languages.len(), 2);
198        assert!(languages.contains(&"rust".to_string()));
199        assert!(languages.contains(&"typescript".to_string()));
200
201        Ok(())
202    }
203
204    #[test]
205    fn test_unregister_provider() -> Result<()> {
206        let registry = LspProviderRegistry::new();
207
208        let available = std::sync::Arc::new(std::sync::Mutex::new(true));
209        let provider: Arc<dyn LspProvider> = Arc::new(MockLspProvider {
210            available: available.clone(),
211        });
212
213        registry.register("rust".to_string(), provider)?;
214        assert!(registry.is_available("rust"));
215
216        registry.unregister("rust")?;
217        assert!(!registry.is_available("rust"));
218
219        Ok(())
220    }
221}