1use super::transport::LspTransport;
9use super::types::*;
10use anyhow::Result;
11use lsp_types::{
12 ClientCapabilities, CompletionContext, CompletionParams, CompletionTriggerKind,
13 DocumentSymbolParams, HoverParams, Position, TextDocumentIdentifier, TextDocumentItem,
14 TextDocumentPositionParams,
15};
16use std::collections::HashMap;
17use std::path::Path;
18use std::sync::Arc;
19use tokio::sync::RwLock;
20use tracing::{debug, info, warn};
21
22pub struct LspClient {
24 transport: LspTransport,
25 config: LspConfig,
26 server_capabilities: RwLock<Option<lsp_types::ServerCapabilities>>,
27 open_documents: RwLock<HashMap<String, i32>>,
29}
30
31impl LspClient {
32 pub async fn new(config: LspConfig) -> Result<Self> {
34 super::types::ensure_server_installed(&config).await?;
35
36 let transport =
37 LspTransport::spawn(&config.command, &config.args, config.timeout_ms).await?;
38
39 Ok(Self {
40 transport,
41 config,
42 server_capabilities: RwLock::new(None),
43 open_documents: RwLock::new(HashMap::new()),
44 })
45 }
46
47 pub async fn for_language(language: &str, root_uri: Option<String>) -> Result<Self> {
49 let mut config = get_language_server_config(language)
50 .ok_or_else(|| anyhow::anyhow!("Unknown language: {}", language))?;
51 config.root_uri = root_uri;
52 Self::new(config).await
53 }
54
55 pub async fn initialize(&self) -> Result<()> {
57 let root_uri = self.config.root_uri.clone();
58
59 let params = InitializeParams {
60 process_id: Some(std::process::id() as i64),
61 client_info: ClientInfo {
62 name: "codetether".to_string(),
63 version: env!("CARGO_PKG_VERSION").to_string(),
64 },
65 locale: None,
66 root_path: None,
67 root_uri: root_uri.clone(),
68 initialization_options: self.config.initialization_options.clone(),
69 capabilities: ClientCapabilities::default(),
70 trace: None,
71 workspace_folders: None,
72 };
73
74 let response = self
75 .transport
76 .request("initialize", Some(serde_json::to_value(params)?))
77 .await?;
78
79 if let Some(error) = response.error {
80 return Err(anyhow::anyhow!("LSP initialize error: {}", error.message));
81 }
82
83 if let Some(result) = response.result {
84 let init_result: InitializeResult = serde_json::from_value(result)?;
85 *self.server_capabilities.write().await = Some(init_result.capabilities);
86 info!(server_info = ?init_result.server_info, "LSP server initialized");
87 }
88
89 self.transport.notify("initialized", None).await?;
90 self.transport.set_initialized(true);
91
92 Ok(())
93 }
94
95 #[allow(dead_code)]
97 pub async fn shutdown(&self) -> Result<()> {
98 let response = self.transport.request("shutdown", None).await?;
99
100 if let Some(error) = response.error {
101 warn!("LSP shutdown error: {}", error.message);
102 }
103
104 self.transport.notify("exit", None).await?;
105 info!("LSP server shutdown complete");
106
107 Ok(())
108 }
109
110 pub async fn open_document(&self, path: &Path, content: &str) -> Result<()> {
112 let uri = path_to_uri(path);
113 let language_id = detect_language_from_path(path.to_string_lossy().as_ref())
114 .unwrap_or("plaintext")
115 .to_string();
116
117 let text_document = TextDocumentItem {
118 uri: parse_uri(&uri)?,
119 language_id,
120 version: 1,
121 text: content.to_string(),
122 };
123
124 let params = DidOpenTextDocumentParams { text_document };
125 self.transport
126 .notify("textDocument/didOpen", Some(serde_json::to_value(params)?))
127 .await?;
128
129 self.open_documents.write().await.insert(uri, 1);
130 debug!(path = %path.display(), "Opened document");
131
132 Ok(())
133 }
134
135 #[allow(dead_code)]
137 pub async fn close_document(&self, path: &Path) -> Result<()> {
138 let uri = path_to_uri(path);
139
140 let text_document = TextDocumentIdentifier {
141 uri: parse_uri(&uri)?,
142 };
143
144 let params = DidCloseTextDocumentParams { text_document };
145 self.transport
146 .notify("textDocument/didClose", Some(serde_json::to_value(params)?))
147 .await?;
148
149 self.open_documents.write().await.remove(&uri);
150 debug!(path = %path.display(), "Closed document");
151
152 Ok(())
153 }
154
155 #[allow(dead_code)]
157 pub async fn change_document(&self, path: &Path, content: &str) -> Result<()> {
158 let uri = path_to_uri(path);
159 let mut open_docs = self.open_documents.write().await;
160
161 let version = open_docs.entry(uri.clone()).or_insert(0);
162 *version += 1;
163
164 let text_document = VersionedTextDocumentIdentifier {
165 uri,
166 version: *version,
167 };
168
169 let content_changes = vec![super::types::TextDocumentContentChangeEvent {
170 range: None,
171 range_length: None,
172 text: content.to_string(),
173 }];
174
175 let params = DidChangeTextDocumentParams {
176 text_document,
177 content_changes,
178 };
179
180 self.transport
181 .notify(
182 "textDocument/didChange",
183 Some(serde_json::to_value(params)?),
184 )
185 .await?;
186
187 debug!(path = %path.display(), version = *version, "Changed document");
188
189 Ok(())
190 }
191
192 pub async fn go_to_definition(
194 &self,
195 path: &Path,
196 line: u32,
197 character: u32,
198 ) -> Result<LspActionResult> {
199 let uri = path_to_uri(path);
200 self.ensure_document_open(path).await?;
201
202 let params = serde_json::json!({
203 "textDocument": { "uri": uri },
204 "position": { "line": line.saturating_sub(1), "character": character.saturating_sub(1) },
205 });
206
207 let response = self
208 .transport
209 .request("textDocument/definition", Some(params))
210 .await?;
211
212 parse_location_response(response, "definition")
213 }
214
215 pub async fn find_references(
217 &self,
218 path: &Path,
219 line: u32,
220 character: u32,
221 include_declaration: bool,
222 ) -> Result<LspActionResult> {
223 let uri = path_to_uri(path);
224 self.ensure_document_open(path).await?;
225
226 let params = ReferenceParams {
227 text_document: TextDocumentIdentifier {
228 uri: parse_uri(&uri)?,
229 },
230 position: Position {
231 line: line.saturating_sub(1),
232 character: character.saturating_sub(1),
233 },
234 context: ReferenceContext {
235 include_declaration,
236 },
237 };
238
239 let response = self
240 .transport
241 .request(
242 "textDocument/references",
243 Some(serde_json::to_value(params)?),
244 )
245 .await?;
246
247 parse_location_response(response, "references")
248 }
249
250 pub async fn hover(&self, path: &Path, line: u32, character: u32) -> Result<LspActionResult> {
252 let uri = path_to_uri(path);
253 self.ensure_document_open(path).await?;
254
255 let params = HoverParams {
256 text_document_position_params: TextDocumentPositionParams {
257 text_document: TextDocumentIdentifier {
258 uri: parse_uri(&uri)?,
259 },
260 position: Position {
261 line: line.saturating_sub(1),
262 character: character.saturating_sub(1),
263 },
264 },
265 work_done_progress_params: Default::default(),
266 };
267
268 let response = self
269 .transport
270 .request("textDocument/hover", Some(serde_json::to_value(params)?))
271 .await?;
272
273 parse_hover_response(response)
274 }
275
276 pub async fn document_symbols(&self, path: &Path) -> Result<LspActionResult> {
278 let uri = path_to_uri(path);
279 self.ensure_document_open(path).await?;
280
281 let params = DocumentSymbolParams {
282 text_document: TextDocumentIdentifier {
283 uri: parse_uri(&uri)?,
284 },
285 work_done_progress_params: Default::default(),
286 partial_result_params: Default::default(),
287 };
288
289 let response = self
290 .transport
291 .request(
292 "textDocument/documentSymbol",
293 Some(serde_json::to_value(params)?),
294 )
295 .await?;
296
297 parse_document_symbols_response(response)
298 }
299
300 pub async fn workspace_symbols(&self, query: &str) -> Result<LspActionResult> {
302 let params = WorkspaceSymbolParams {
303 query: query.to_string(),
304 };
305
306 let response = self
307 .transport
308 .request("workspace/symbol", Some(serde_json::to_value(params)?))
309 .await?;
310
311 parse_workspace_symbols_response(response)
312 }
313
314 pub async fn go_to_implementation(
316 &self,
317 path: &Path,
318 line: u32,
319 character: u32,
320 ) -> Result<LspActionResult> {
321 let uri = path_to_uri(path);
322 self.ensure_document_open(path).await?;
323
324 let params = serde_json::json!({
325 "textDocument": { "uri": uri },
326 "position": { "line": line.saturating_sub(1), "character": character.saturating_sub(1) },
327 });
328
329 let response = self
330 .transport
331 .request("textDocument/implementation", Some(params))
332 .await?;
333
334 parse_location_response(response, "implementation")
335 }
336
337 pub async fn completion(
339 &self,
340 path: &Path,
341 line: u32,
342 character: u32,
343 ) -> Result<LspActionResult> {
344 let uri = path_to_uri(path);
345 self.ensure_document_open(path).await?;
346
347 let params = CompletionParams {
348 text_document_position: TextDocumentPositionParams {
349 text_document: TextDocumentIdentifier {
350 uri: parse_uri(&uri)?,
351 },
352 position: Position {
353 line: line.saturating_sub(1),
354 character: character.saturating_sub(1),
355 },
356 },
357 work_done_progress_params: Default::default(),
358 partial_result_params: Default::default(),
359 context: Some(CompletionContext {
360 trigger_kind: CompletionTriggerKind::INVOKED,
361 trigger_character: None,
362 }),
363 };
364
365 let response = self
366 .transport
367 .request(
368 "textDocument/completion",
369 Some(serde_json::to_value(params)?),
370 )
371 .await?;
372
373 parse_completion_response(response)
374 }
375
376 pub async fn diagnostics(&self, path: &Path) -> Result<LspActionResult> {
378 self.ensure_document_open(path).await?;
379 tokio::time::sleep(std::time::Duration::from_millis(250)).await;
380
381 let uri = path_to_uri(path);
382 let snapshot = self.transport.diagnostics_snapshot().await;
383 let diagnostics = snapshot
384 .get(&uri)
385 .cloned()
386 .unwrap_or_default()
387 .into_iter()
388 .map(|diagnostic| DiagnosticInfo::from((uri.clone(), diagnostic)))
389 .collect();
390
391 Ok(LspActionResult::Diagnostics { diagnostics })
392 }
393
394 async fn ensure_document_open(&self, path: &Path) -> Result<()> {
396 let uri = path_to_uri(path);
397 if !self.open_documents.read().await.contains_key(&uri) {
398 let content = tokio::fs::read_to_string(path).await?;
399 self.open_document(path, &content).await?;
400 }
401 Ok(())
402 }
403
404 #[allow(dead_code)]
406 pub async fn capabilities(&self) -> Option<lsp_types::ServerCapabilities> {
407 self.server_capabilities.read().await.clone()
408 }
409
410 #[allow(dead_code)]
412 pub fn handles_file(&self, path: &Path) -> bool {
413 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
414 self.config.file_extensions.iter().any(|fe| fe == ext)
415 }
416
417 #[allow(dead_code)]
419 pub fn handles_language(&self, language: &str) -> bool {
420 let extensions = match language {
421 "rust" => &["rs"][..],
422 "typescript" => &["ts", "tsx"],
423 "javascript" => &["js", "jsx"],
424 "python" => &["py"],
425 "go" => &["go"],
426 "c" => &["c", "h"],
427 "cpp" => &["cpp", "cc", "cxx", "hpp", "h"],
428 _ => &[],
429 };
430
431 extensions
432 .iter()
433 .any(|ext| self.config.file_extensions.iter().any(|fe| fe == *ext))
434 }
435}
436
437fn path_to_uri(path: &Path) -> String {
439 let absolute = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
440 format!("file://{}", absolute.display())
441}
442
443fn parse_uri(uri_str: &str) -> Result<lsp_types::Uri> {
445 uri_str
446 .parse()
447 .map_err(|e| anyhow::anyhow!("Invalid URI: {e}"))
448}
449
450fn parse_location_response(response: JsonRpcResponse, _operation: &str) -> Result<LspActionResult> {
452 if let Some(error) = response.error {
453 return Ok(LspActionResult::Error {
454 message: error.message,
455 });
456 }
457
458 let Some(result) = response.result else {
459 return Ok(LspActionResult::Definition { locations: vec![] });
460 };
461
462 if let Ok(loc) = serde_json::from_value::<lsp_types::Location>(result.clone()) {
463 return Ok(LspActionResult::Definition {
464 locations: vec![LocationInfo::from(loc)],
465 });
466 }
467
468 if let Ok(locs) = serde_json::from_value::<Vec<lsp_types::Location>>(result.clone()) {
469 return Ok(LspActionResult::Definition {
470 locations: locs.into_iter().map(LocationInfo::from).collect(),
471 });
472 }
473
474 if let Ok(links) = serde_json::from_value::<Vec<lsp_types::LocationLink>>(result) {
475 return Ok(LspActionResult::Definition {
476 locations: links
477 .into_iter()
478 .map(|link| LocationInfo {
479 uri: link.target_uri.to_string(),
480 range: RangeInfo::from(link.target_selection_range),
481 })
482 .collect(),
483 });
484 }
485
486 Ok(LspActionResult::Definition { locations: vec![] })
487}
488
489fn parse_hover_response(response: JsonRpcResponse) -> Result<LspActionResult> {
491 if let Some(error) = response.error {
492 return Ok(LspActionResult::Error {
493 message: error.message,
494 });
495 }
496
497 let Some(result) = response.result else {
498 return Ok(LspActionResult::Hover {
499 contents: String::new(),
500 range: None,
501 });
502 };
503
504 if result.is_null() {
505 return Ok(LspActionResult::Hover {
506 contents: "No hover information available".to_string(),
507 range: None,
508 });
509 }
510
511 let hover: lsp_types::Hover = serde_json::from_value(result)?;
512
513 let contents = match hover.contents {
514 lsp_types::HoverContents::Scalar(markup) => match markup {
515 lsp_types::MarkedString::String(s) => s,
516 lsp_types::MarkedString::LanguageString(ls) => ls.value,
517 },
518 lsp_types::HoverContents::Array(markups) => markups
519 .into_iter()
520 .map(|m| match m {
521 lsp_types::MarkedString::String(s) => s,
522 lsp_types::MarkedString::LanguageString(ls) => ls.value,
523 })
524 .collect::<Vec<_>>()
525 .join("\n\n"),
526 lsp_types::HoverContents::Markup(markup) => markup.value,
527 };
528
529 Ok(LspActionResult::Hover {
530 contents,
531 range: hover.range.map(RangeInfo::from),
532 })
533}
534
535fn parse_document_symbols_response(response: JsonRpcResponse) -> Result<LspActionResult> {
537 if let Some(error) = response.error {
538 return Ok(LspActionResult::Error {
539 message: error.message,
540 });
541 }
542
543 let Some(result) = response.result else {
544 return Ok(LspActionResult::DocumentSymbols { symbols: vec![] });
545 };
546
547 if result.is_null() {
548 return Ok(LspActionResult::DocumentSymbols { symbols: vec![] });
549 }
550
551 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::DocumentSymbol>>(result.clone()) {
552 return Ok(LspActionResult::DocumentSymbols {
553 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
554 });
555 }
556
557 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::SymbolInformation>>(result) {
558 return Ok(LspActionResult::DocumentSymbols {
559 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
560 });
561 }
562
563 Ok(LspActionResult::DocumentSymbols { symbols: vec![] })
564}
565
566fn parse_workspace_symbols_response(response: JsonRpcResponse) -> Result<LspActionResult> {
568 if let Some(error) = response.error {
569 return Ok(LspActionResult::Error {
570 message: error.message,
571 });
572 }
573
574 let Some(result) = response.result else {
575 return Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] });
576 };
577
578 if result.is_null() {
579 return Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] });
580 }
581
582 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::SymbolInformation>>(result.clone())
583 {
584 return Ok(LspActionResult::WorkspaceSymbols {
585 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
586 });
587 }
588
589 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::WorkspaceSymbol>>(result) {
590 return Ok(LspActionResult::WorkspaceSymbols {
591 symbols: symbols
592 .into_iter()
593 .map(|s| {
594 let (uri, range) = match s.location {
595 lsp_types::OneOf::Left(loc) => {
596 (loc.uri.to_string(), Some(RangeInfo::from(loc.range)))
597 }
598 lsp_types::OneOf::Right(wl) => (wl.uri.to_string(), None),
599 };
600 SymbolInfo {
601 name: s.name,
602 kind: format!("{:?}", s.kind),
603 detail: None,
604 uri: Some(uri),
605 range,
606 container_name: s.container_name,
607 }
608 })
609 .collect(),
610 });
611 }
612
613 Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] })
614}
615
616fn parse_completion_response(response: JsonRpcResponse) -> Result<LspActionResult> {
618 if let Some(error) = response.error {
619 return Ok(LspActionResult::Error {
620 message: error.message,
621 });
622 }
623
624 let Some(result) = response.result else {
625 return Ok(LspActionResult::Completion { items: vec![] });
626 };
627
628 if result.is_null() {
629 return Ok(LspActionResult::Completion { items: vec![] });
630 }
631
632 if let Ok(list) = serde_json::from_value::<lsp_types::CompletionList>(result.clone()) {
633 return Ok(LspActionResult::Completion {
634 items: list
635 .items
636 .into_iter()
637 .map(CompletionItemInfo::from)
638 .collect(),
639 });
640 }
641
642 if let Ok(items) = serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result) {
643 return Ok(LspActionResult::Completion {
644 items: items.into_iter().map(CompletionItemInfo::from).collect(),
645 });
646 }
647
648 Ok(LspActionResult::Completion { items: vec![] })
649}
650
651pub struct LspManager {
653 clients: RwLock<HashMap<String, Arc<LspClient>>>,
654 linter_clients: RwLock<HashMap<String, Arc<LspClient>>>,
657 root_uri: Option<String>,
658 lsp_settings: Option<crate::config::LspSettings>,
660}
661
662impl LspManager {
663 pub fn new(root_uri: Option<String>) -> Self {
665 Self {
666 clients: RwLock::new(HashMap::new()),
667 linter_clients: RwLock::new(HashMap::new()),
668 root_uri,
669 lsp_settings: None,
670 }
671 }
672
673 pub fn with_config(root_uri: Option<String>, settings: crate::config::LspSettings) -> Self {
675 Self {
676 clients: RwLock::new(HashMap::new()),
677 linter_clients: RwLock::new(HashMap::new()),
678 root_uri,
679 lsp_settings: Some(settings),
680 }
681 }
682
683 pub async fn get_client(&self, language: &str) -> Result<Arc<LspClient>> {
685 {
686 let clients = self.clients.read().await;
687 if let Some(client) = clients.get(language) {
688 return Ok(Arc::clone(client));
689 }
690 }
691
692 let client = if let Some(settings) = &self.lsp_settings {
693 if let Some(entry) = settings.servers.get(language) {
694 let config = LspConfig::from_server_entry(entry, self.root_uri.clone());
695 LspClient::new(config).await?
696 } else {
697 LspClient::for_language(language, self.root_uri.clone()).await?
698 }
699 } else {
700 LspClient::for_language(language, self.root_uri.clone()).await?
701 };
702 client.initialize().await?;
703
704 let client = Arc::new(client);
705 self.clients
706 .write()
707 .await
708 .insert(language.to_string(), Arc::clone(&client));
709
710 Ok(client)
711 }
712
713 pub async fn get_client_for_file(&self, path: &Path) -> Result<Arc<LspClient>> {
715 let language = detect_language_from_path(path.to_string_lossy().as_ref())
716 .ok_or_else(|| anyhow::anyhow!("Unknown language for file: {}", path.display()))?;
717 self.get_client(language).await
718 }
719
720 #[allow(dead_code)]
722 pub async fn handles_file(&self, path: &Path) -> bool {
723 let clients = self.clients.read().await;
724 clients.values().any(|c| c.handles_file(path))
725 }
726
727 #[allow(dead_code)]
729 pub async fn capabilities_for(&self, language: &str) -> Option<lsp_types::ServerCapabilities> {
730 let clients = self.clients.read().await;
731 if let Some(client) = clients.get(language) {
732 client.capabilities().await
733 } else {
734 None
735 }
736 }
737
738 #[allow(dead_code)]
740 pub async fn close_document(&self, path: &Path) -> Result<()> {
741 if let Ok(client) = self.get_client_for_file(path).await {
742 client.close_document(path).await?;
743 }
744 Ok(())
745 }
746
747 #[allow(dead_code)]
749 pub async fn change_document(&self, path: &Path, content: &str) -> Result<()> {
750 if let Ok(client) = self.get_client_for_file(path).await {
751 client.change_document(path, content).await?;
752 }
753 Ok(())
754 }
755
756 #[allow(dead_code)]
758 pub async fn shutdown_all(&self) {
759 let clients = self.clients.read().await;
760 for (lang, client) in clients.iter() {
761 if let Err(e) = client.shutdown().await {
762 warn!("Failed to shutdown {} language server: {}", lang, e);
763 }
764 }
765 let linters = self.linter_clients.read().await;
766 for (name, client) in linters.iter() {
767 if let Err(e) = client.shutdown().await {
768 warn!("Failed to shutdown {} linter server: {}", name, e);
769 }
770 }
771 }
772
773 pub async fn get_linter_client(&self, name: &str) -> Result<Option<Arc<LspClient>>> {
776 {
778 let linters = self.linter_clients.read().await;
779 if let Some(client) = linters.get(name) {
780 return Ok(Some(Arc::clone(client)));
781 }
782 }
783
784 let lsp_config = if let Some(settings) = &self.lsp_settings {
786 if let Some(entry) = settings.linters.get(name) {
787 if !entry.enabled {
788 return Ok(None);
789 }
790 LspConfig::from_linter_entry(name, entry, self.root_uri.clone())
791 } else if !settings.disable_builtin_linters {
792 if let Some(mut cfg) = get_linter_server_config(name) {
794 cfg.root_uri = self.root_uri.clone();
795 Some(cfg)
796 } else {
797 None
798 }
799 } else {
800 None
801 }
802 } else {
803 if let Some(mut cfg) = get_linter_server_config(name) {
805 cfg.root_uri = self.root_uri.clone();
806 Some(cfg)
807 } else {
808 None
809 }
810 };
811
812 let Some(config) = lsp_config else {
813 return Ok(None);
814 };
815
816 let client = match LspClient::new(config).await {
818 Ok(c) => c,
819 Err(e) => {
820 debug!(linter = name, error = %e, "Linter server not available");
821 return Ok(None);
822 }
823 };
824 if let Err(e) = client.initialize().await {
825 warn!(linter = name, error = %e, "Linter server failed to initialize");
826 return Ok(None);
827 }
828
829 let client = Arc::new(client);
830 self.linter_clients
831 .write()
832 .await
833 .insert(name.to_string(), Arc::clone(&client));
834 info!(linter = name, "Linter server started");
835 Ok(Some(client))
836 }
837
838 pub async fn linter_diagnostics(&self, path: &Path) -> Vec<DiagnosticInfo> {
841 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
842
843 let linter_names: Vec<String> = if let Some(settings) = &self.lsp_settings {
845 settings
846 .linters
847 .iter()
848 .filter(|(_, entry)| {
849 entry.enabled
850 && (entry.file_extensions.iter().any(|e| e == ext)
851 || entry.file_extensions.is_empty())
852 })
853 .map(|(name, _)| name.clone())
854 .collect()
855 } else {
856 let mut names = Vec::new();
858 for candidate in &["eslint", "biome", "ruff", "stylelint"] {
859 if linter_extensions(candidate).contains(&ext) {
860 names.push((*candidate).to_string());
861 }
862 }
863 names
864 };
865
866 let mut all_diagnostics = Vec::new();
867 for name in &linter_names {
868 match self.get_linter_client(name).await {
869 Ok(Some(client)) => match client.diagnostics(path).await {
870 Ok(LspActionResult::Diagnostics { diagnostics }) => {
871 all_diagnostics.extend(diagnostics);
872 }
873 Ok(_) => {}
874 Err(e) => {
875 debug!(linter = %name, error = %e, "Linter diagnostics failed");
876 }
877 },
878 Ok(None) => {}
879 Err(e) => {
880 debug!(linter = %name, error = %e, "Failed to get linter client");
881 }
882 }
883 }
884 all_diagnostics
885 }
886}
887
888impl Default for LspManager {
889 fn default() -> Self {
890 Self::new(None)
891 }
892}