1use crate::error::{IdeError, IdeResult};
8use crate::provider::IdeProvider;
9use crate::types::*;
10use async_trait::async_trait;
11use std::sync::Arc;
12use tracing::debug;
13
14pub struct ExternalLspProvider {
16 language: String,
18 registry: Arc<ricecoder_external_lsp::LspServerRegistry>,
20}
21
22impl ExternalLspProvider {
23 pub fn new(
25 language: String,
26 registry: Arc<ricecoder_external_lsp::LspServerRegistry>,
27 ) -> Self {
28 ExternalLspProvider { language, registry }
29 }
30
31 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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(¶ms).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}