ricecoder_ide/
external_lsp_provider.rs

1//! External LSP provider implementation
2//!
3//! This module implements the IdeProvider trait for external LSP servers,
4//! querying ricecoder-external-lsp for semantic information and mapping
5//! responses to IDE types.
6
7use crate::error::{IdeError, IdeResult};
8use crate::provider::IdeProvider;
9use crate::types::*;
10use async_trait::async_trait;
11use std::sync::Arc;
12use tracing::debug;
13
14/// External LSP provider that queries ricecoder-external-lsp
15pub struct ExternalLspProvider {
16    /// Language this provider supports
17    language: String,
18    /// LSP server registry
19    registry: Arc<ricecoder_external_lsp::LspServerRegistry>,
20}
21
22impl ExternalLspProvider {
23    /// Create a new external LSP provider
24    pub fn new(
25        language: String,
26        registry: Arc<ricecoder_external_lsp::LspServerRegistry>,
27    ) -> Self {
28        ExternalLspProvider { language, registry }
29    }
30
31    /// Check if LSP server is available for this language
32    fn is_lsp_available(&self) -> bool {
33        self.registry
34            .servers
35            .get(&self.language)
36            .map(|servers| !servers.is_empty())
37            .unwrap_or(false)
38    }
39
40    /// Map LSP completion items to IDE completion items
41    #[allow(dead_code)]
42    fn map_lsp_completions(
43        &self,
44        lsp_completions: Vec<serde_json::Value>,
45    ) -> IdeResult<Vec<CompletionItem>> {
46        let mut items = Vec::new();
47
48        for lsp_item in lsp_completions {
49            let label = lsp_item
50                .get("label")
51                .and_then(|v| v.as_str())
52                .unwrap_or("unknown")
53                .to_string();
54
55            let kind = lsp_item
56                .get("kind")
57                .and_then(|v| v.as_u64())
58                .map(|k| self.map_lsp_completion_kind(k as u32))
59                .unwrap_or(CompletionItemKind::Text);
60
61            let detail = lsp_item
62                .get("detail")
63                .and_then(|v| v.as_str())
64                .map(|s| s.to_string());
65
66            let documentation = lsp_item
67                .get("documentation")
68                .and_then(|v| {
69                    if let Some(s) = v.as_str() {
70                        Some(s.to_string())
71                    } else if let Some(obj) = v.as_object() {
72                        obj.get("value").and_then(|v| v.as_str()).map(|s| s.to_string())
73                    } else {
74                        None
75                    }
76                });
77
78            let insert_text = lsp_item
79                .get("insertText")
80                .and_then(|v| v.as_str())
81                .unwrap_or(&label)
82                .to_string();
83
84            items.push(CompletionItem {
85                label,
86                kind,
87                detail,
88                documentation,
89                insert_text,
90            });
91        }
92
93        Ok(items)
94    }
95
96    /// Map LSP completion kind to IDE completion kind
97    #[allow(dead_code)]
98    fn map_lsp_completion_kind(&self, kind: u32) -> CompletionItemKind {
99        match kind {
100            1 => CompletionItemKind::Text,
101            2 => CompletionItemKind::Method,
102            3 => CompletionItemKind::Function,
103            4 => CompletionItemKind::Constructor,
104            5 => CompletionItemKind::Field,
105            6 => CompletionItemKind::Variable,
106            7 => CompletionItemKind::Class,
107            8 => CompletionItemKind::Interface,
108            9 => CompletionItemKind::Module,
109            10 => CompletionItemKind::Property,
110            11 => CompletionItemKind::Unit,
111            12 => CompletionItemKind::Value,
112            13 => CompletionItemKind::Enum,
113            14 => CompletionItemKind::Keyword,
114            15 => CompletionItemKind::Snippet,
115            16 => CompletionItemKind::Color,
116            17 => CompletionItemKind::File,
117            18 => CompletionItemKind::Reference,
118            19 => CompletionItemKind::Folder,
119            20 => CompletionItemKind::EnumMember,
120            21 => CompletionItemKind::Constant,
121            22 => CompletionItemKind::Struct,
122            23 => CompletionItemKind::Event,
123            24 => CompletionItemKind::Operator,
124            25 => CompletionItemKind::TypeParameter,
125            _ => CompletionItemKind::Text,
126        }
127    }
128
129    /// Map LSP diagnostics to IDE diagnostics
130    #[allow(dead_code)]
131    fn map_lsp_diagnostics(
132        &self,
133        lsp_diagnostics: Vec<serde_json::Value>,
134    ) -> IdeResult<Vec<Diagnostic>> {
135        let mut diagnostics = Vec::new();
136
137        for lsp_diag in lsp_diagnostics {
138            let message = lsp_diag
139                .get("message")
140                .and_then(|v| v.as_str())
141                .unwrap_or("Unknown diagnostic")
142                .to_string();
143
144            let severity = lsp_diag
145                .get("severity")
146                .and_then(|v| v.as_u64())
147                .map(|s| self.map_lsp_severity(s as u32))
148                .unwrap_or(DiagnosticSeverity::Information);
149
150            let source = lsp_diag
151                .get("source")
152                .and_then(|v| v.as_str())
153                .unwrap_or("lsp")
154                .to_string();
155
156            let range = self.extract_range_from_lsp(&lsp_diag).unwrap_or(Range {
157                start: Position {
158                    line: 0,
159                    character: 0,
160                },
161                end: Position {
162                    line: 0,
163                    character: 0,
164                },
165            });
166
167            diagnostics.push(Diagnostic {
168                range,
169                severity,
170                message,
171                source,
172            });
173        }
174
175        Ok(diagnostics)
176    }
177
178    /// Map LSP severity to IDE severity
179    #[allow(dead_code)]
180    fn map_lsp_severity(&self, severity: u32) -> DiagnosticSeverity {
181        match severity {
182            1 => DiagnosticSeverity::Error,
183            2 => DiagnosticSeverity::Warning,
184            3 => DiagnosticSeverity::Information,
185            4 => DiagnosticSeverity::Hint,
186            _ => DiagnosticSeverity::Information,
187        }
188    }
189
190    /// Extract range from LSP diagnostic
191    #[allow(dead_code)]
192    fn extract_range_from_lsp(&self, lsp_diag: &serde_json::Value) -> Option<Range> {
193        let range_obj = lsp_diag.get("range")?.as_object()?;
194
195        let start_obj = range_obj.get("start")?.as_object()?;
196        let start_line = start_obj.get("line")?.as_u64()? as u32;
197        let start_char = start_obj.get("character")?.as_u64()? as u32;
198
199        let end_obj = range_obj.get("end")?.as_object()?;
200        let end_line = end_obj.get("line")?.as_u64()? as u32;
201        let end_char = end_obj.get("character")?.as_u64()? as u32;
202
203        Some(Range {
204            start: Position {
205                line: start_line,
206                character: start_char,
207            },
208            end: Position {
209                line: end_line,
210                character: end_char,
211            },
212        })
213    }
214
215    /// Map LSP hover to IDE hover
216    #[allow(dead_code)]
217    fn map_lsp_hover(&self, lsp_hover: serde_json::Value) -> IdeResult<Option<Hover>> {
218        let contents = lsp_hover
219            .get("contents")
220            .and_then(|v| {
221                if let Some(s) = v.as_str() {
222                    Some(s.to_string())
223                } else if let Some(obj) = v.as_object() {
224                    obj.get("value").and_then(|v| v.as_str()).map(|s| s.to_string())
225                } else if let Some(arr) = v.as_array() {
226                    arr.first()
227                        .and_then(|v| v.as_str())
228                        .map(|s| s.to_string())
229                } else {
230                    None
231                }
232            });
233
234        match contents {
235            Some(contents) => {
236                let range = self.extract_range_from_lsp(&lsp_hover);
237                Ok(Some(Hover { contents, range }))
238            }
239            None => Ok(None),
240        }
241    }
242
243    /// Map LSP location to IDE location
244    #[allow(dead_code)]
245    fn map_lsp_location(&self, lsp_location: serde_json::Value) -> IdeResult<Option<Location>> {
246        let uri = lsp_location
247            .get("uri")
248            .and_then(|v| v.as_str())
249            .map(|s| s.to_string());
250
251        let range = self.extract_range_from_lsp(&lsp_location);
252
253        match (uri, range) {
254            (Some(uri), Some(range)) => {
255                // Convert file:// URI to path
256                let file_path = if uri.starts_with("file://") {
257                    uri.strip_prefix("file://").unwrap_or(&uri).to_string()
258                } else {
259                    uri
260                };
261
262                Ok(Some(Location { file_path, range }))
263            }
264            _ => Ok(None),
265        }
266    }
267}
268
269#[async_trait]
270impl IdeProvider for ExternalLspProvider {
271    async fn get_completions(&self, _params: &CompletionParams) -> IdeResult<Vec<CompletionItem>> {
272        debug!(
273            "Getting completions from external LSP for language: {}",
274            self.language
275        );
276
277        if !self.is_lsp_available() {
278            return Err(IdeError::lsp_error(format!(
279                "LSP server not available for language: {}",
280                self.language
281            )));
282        }
283
284        // For now, return empty completions
285        // In a full implementation, this would query the actual LSP server
286        // through ricecoder-external-lsp
287        Ok(vec![])
288    }
289
290    async fn get_diagnostics(&self, _params: &DiagnosticsParams) -> IdeResult<Vec<Diagnostic>> {
291        debug!(
292            "Getting diagnostics from external LSP for language: {}",
293            self.language
294        );
295
296        if !self.is_lsp_available() {
297            return Err(IdeError::lsp_error(format!(
298                "LSP server not available for language: {}",
299                self.language
300            )));
301        }
302
303        // For now, return empty diagnostics
304        // In a full implementation, this would query the actual LSP server
305        // through ricecoder-external-lsp
306        Ok(vec![])
307    }
308
309    async fn get_hover(&self, _params: &HoverParams) -> IdeResult<Option<Hover>> {
310        debug!(
311            "Getting hover from external LSP for language: {}",
312            self.language
313        );
314
315        if !self.is_lsp_available() {
316            return Err(IdeError::lsp_error(format!(
317                "LSP server not available for language: {}",
318                self.language
319            )));
320        }
321
322        // For now, return None
323        // In a full implementation, this would query the actual LSP server
324        // through ricecoder-external-lsp
325        Ok(None)
326    }
327
328    async fn get_definition(&self, _params: &DefinitionParams) -> IdeResult<Option<Location>> {
329        debug!(
330            "Getting definition from external LSP for language: {}",
331            self.language
332        );
333
334        if !self.is_lsp_available() {
335            return Err(IdeError::lsp_error(format!(
336                "LSP server not available for language: {}",
337                self.language
338            )));
339        }
340
341        // For now, return None
342        // In a full implementation, this would query the actual LSP server
343        // through ricecoder-external-lsp
344        Ok(None)
345    }
346
347    fn is_available(&self, language: &str) -> bool {
348        language == self.language && self.is_lsp_available()
349    }
350
351    fn name(&self) -> &str {
352        "external-lsp"
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_map_lsp_completion_kind() {
362        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
363        let provider = ExternalLspProvider::new("rust".to_string(), registry);
364
365        assert_eq!(provider.map_lsp_completion_kind(1), CompletionItemKind::Text);
366        assert_eq!(provider.map_lsp_completion_kind(2), CompletionItemKind::Method);
367        assert_eq!(provider.map_lsp_completion_kind(3), CompletionItemKind::Function);
368        assert_eq!(provider.map_lsp_completion_kind(7), CompletionItemKind::Class);
369    }
370
371    #[test]
372    fn test_map_lsp_severity() {
373        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
374        let provider = ExternalLspProvider::new("rust".to_string(), registry);
375
376        assert_eq!(provider.map_lsp_severity(1), DiagnosticSeverity::Error);
377        assert_eq!(provider.map_lsp_severity(2), DiagnosticSeverity::Warning);
378        assert_eq!(provider.map_lsp_severity(3), DiagnosticSeverity::Information);
379        assert_eq!(provider.map_lsp_severity(4), DiagnosticSeverity::Hint);
380    }
381
382    #[test]
383    fn test_extract_range_from_lsp() {
384        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
385        let provider = ExternalLspProvider::new("rust".to_string(), registry);
386
387        let lsp_diag = serde_json::json!({
388            "range": {
389                "start": { "line": 10, "character": 5 },
390                "end": { "line": 10, "character": 15 }
391            }
392        });
393
394        let range = provider.extract_range_from_lsp(&lsp_diag);
395        assert!(range.is_some());
396
397        let range = range.unwrap();
398        assert_eq!(range.start.line, 10);
399        assert_eq!(range.start.character, 5);
400        assert_eq!(range.end.line, 10);
401        assert_eq!(range.end.character, 15);
402    }
403
404    #[test]
405    fn test_map_lsp_completions() {
406        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
407        let provider = ExternalLspProvider::new("rust".to_string(), registry);
408
409        let lsp_completions = vec![serde_json::json!({
410            "label": "test_function",
411            "kind": 3,
412            "detail": "fn test_function()",
413            "documentation": "A test function",
414            "insertText": "test_function()"
415        })];
416
417        let result = provider.map_lsp_completions(lsp_completions);
418        assert!(result.is_ok());
419
420        let items = result.unwrap();
421        assert_eq!(items.len(), 1);
422        assert_eq!(items[0].label, "test_function");
423        assert_eq!(items[0].kind, CompletionItemKind::Function);
424        assert_eq!(items[0].detail, Some("fn test_function()".to_string()));
425    }
426
427    #[test]
428    fn test_map_lsp_diagnostics() {
429        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
430        let provider = ExternalLspProvider::new("rust".to_string(), registry);
431
432        let lsp_diagnostics = vec![serde_json::json!({
433            "message": "unused variable",
434            "severity": 2,
435            "source": "rust-analyzer",
436            "range": {
437                "start": { "line": 5, "character": 4 },
438                "end": { "line": 5, "character": 10 }
439            }
440        })];
441
442        let result = provider.map_lsp_diagnostics(lsp_diagnostics);
443        assert!(result.is_ok());
444
445        let diagnostics = result.unwrap();
446        assert_eq!(diagnostics.len(), 1);
447        assert_eq!(diagnostics[0].message, "unused variable");
448        assert_eq!(diagnostics[0].severity, DiagnosticSeverity::Warning);
449        assert_eq!(diagnostics[0].source, "rust-analyzer");
450    }
451
452    #[test]
453    fn test_map_lsp_hover() {
454        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
455        let provider = ExternalLspProvider::new("rust".to_string(), registry);
456
457        let lsp_hover = serde_json::json!({
458            "contents": "fn test_function() -> i32"
459        });
460
461        let result = provider.map_lsp_hover(lsp_hover);
462        assert!(result.is_ok());
463
464        let hover = result.unwrap();
465        assert!(hover.is_some());
466        assert_eq!(hover.unwrap().contents, "fn test_function() -> i32");
467    }
468
469    #[test]
470    fn test_map_lsp_location() {
471        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
472        let provider = ExternalLspProvider::new("rust".to_string(), registry);
473
474        let lsp_location = serde_json::json!({
475            "uri": "file:///home/user/project/src/main.rs",
476            "range": {
477                "start": { "line": 10, "character": 5 },
478                "end": { "line": 10, "character": 15 }
479            }
480        });
481
482        let result = provider.map_lsp_location(lsp_location);
483        assert!(result.is_ok());
484
485        let location = result.unwrap();
486        assert!(location.is_some());
487        let loc = location.unwrap();
488        assert!(loc.file_path.contains("main.rs"));
489    }
490
491    #[tokio::test]
492    async fn test_external_lsp_provider_not_available() {
493        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
494        let provider = ExternalLspProvider::new("rust".to_string(), registry);
495
496        let params = CompletionParams {
497            language: "rust".to_string(),
498            file_path: "src/main.rs".to_string(),
499            position: Position {
500                line: 10,
501                character: 5,
502            },
503            context: "fn test".to_string(),
504        };
505
506        let result = provider.get_completions(&params).await;
507        assert!(result.is_err());
508    }
509
510    #[tokio::test]
511    async fn test_external_lsp_provider_is_available() {
512        let registry = Arc::new(ricecoder_external_lsp::LspServerRegistry::default());
513        let provider = ExternalLspProvider::new("rust".to_string(), registry);
514
515        assert!(!provider.is_available("rust"));
516        assert!(!provider.is_available("typescript"));
517    }
518}