1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::str::FromStr;
4
5use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender};
6use lsp_types::notification::{
7 DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument,
8};
9use lsp_types::{
10 DidChangeTextDocumentParams, DidChangeWatchedFilesParams, DidCloseTextDocumentParams,
11 DidOpenTextDocumentParams, FileChangeType, FileEvent, TextDocumentContentChangeEvent,
12 TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier,
13};
14
15use crate::config::Config;
16use crate::lsp::child_registry::LspChildRegistry;
17use crate::lsp::client::{LspClient, LspEvent, ServerState};
18use crate::lsp::diagnostics::{from_lsp_diagnostics, DiagnosticsStore, StoredDiagnostic};
19use crate::lsp::document::DocumentStore;
20use crate::lsp::registry::{resolve_lsp_binary, servers_for_file, ServerDef, ServerKind};
21use crate::lsp::roots::{find_workspace_root, ServerKey};
22use crate::lsp::LspError;
23use crate::slog_error;
24
25#[derive(Debug, Clone)]
30pub enum ServerAttemptResult {
31 Ok { server_key: ServerKey },
33 NoRootMarker { looked_for: Vec<String> },
36 BinaryNotInstalled { binary: String },
39 SpawnFailed { binary: String, reason: String },
41}
42
43#[derive(Debug, Clone)]
45pub struct ServerAttempt {
46 pub server_id: String,
48 pub server_name: String,
50 pub result: ServerAttemptResult,
51}
52
53#[derive(Debug, Clone, Default)]
59pub struct EnsureServerOutcomes {
60 pub successful: Vec<ServerKey>,
62 pub attempts: Vec<ServerAttempt>,
65}
66
67impl EnsureServerOutcomes {
68 pub fn no_server_registered(&self) -> bool {
70 self.attempts.is_empty()
71 }
72}
73
74#[derive(Debug, Clone, Default)]
84pub struct PostEditWaitOutcome {
85 pub diagnostics: Vec<StoredDiagnostic>,
89 pub pending_servers: Vec<ServerKey>,
93 pub exited_servers: Vec<ServerKey>,
97}
98
99impl PostEditWaitOutcome {
100 pub fn complete(&self) -> bool {
103 self.pending_servers.is_empty() && self.exited_servers.is_empty()
104 }
105}
106
107#[derive(Debug, Clone)]
109pub enum PullFileOutcome {
110 Full { diagnostic_count: usize },
112 Unchanged,
114 PartialNotSupported,
117 PullNotSupported,
120 RequestFailed { reason: String },
122}
123
124#[derive(Debug, Clone)]
126pub struct PullFileResult {
127 pub server_key: ServerKey,
128 pub outcome: PullFileOutcome,
129}
130
131#[derive(Debug, Clone)]
133pub struct PullWorkspaceResult {
134 pub server_key: ServerKey,
135 pub files_reported: Vec<PathBuf>,
139 pub complete: bool,
141 pub cancelled: bool,
143 pub supports_workspace: bool,
147}
148
149pub struct LspManager {
150 clients: HashMap<ServerKey, LspClient>,
152 documents: HashMap<ServerKey, DocumentStore>,
154 diagnostics: DiagnosticsStore,
156 event_tx: Sender<LspEvent>,
158 event_rx: Receiver<LspEvent>,
159 binary_overrides: HashMap<ServerKind, PathBuf>,
161 extra_env: HashMap<String, String>,
165 failed_spawns: HashMap<ServerKey, ServerAttemptResult>,
180 child_registry: LspChildRegistry,
184}
185
186impl LspManager {
187 pub fn new() -> Self {
188 let (event_tx, event_rx) = unbounded();
189 Self {
190 clients: HashMap::new(),
191 documents: HashMap::new(),
192 diagnostics: DiagnosticsStore::new(),
193 event_tx,
194 event_rx,
195 binary_overrides: HashMap::new(),
196 extra_env: HashMap::new(),
197 failed_spawns: HashMap::new(),
198 child_registry: LspChildRegistry::new(),
199 }
200 }
201
202 pub fn set_child_registry(&mut self, registry: LspChildRegistry) {
204 self.child_registry = registry;
205 }
206
207 pub fn set_extra_env(&mut self, key: &str, value: &str) {
211 self.extra_env.insert(key.to_string(), value.to_string());
212 }
213
214 pub fn server_count(&self) -> usize {
216 self.clients.len()
217 }
218
219 pub fn override_binary(&mut self, kind: ServerKind, binary_path: PathBuf) {
221 self.binary_overrides.insert(kind, binary_path);
222 }
223
224 pub fn ensure_server_for_file(&mut self, file_path: &Path, config: &Config) -> Vec<ServerKey> {
231 self.ensure_server_for_file_detailed(file_path, config)
232 .successful
233 }
234
235 pub fn ensure_server_for_file_detailed(
243 &mut self,
244 file_path: &Path,
245 config: &Config,
246 ) -> EnsureServerOutcomes {
247 let defs = servers_for_file(file_path, config);
248 let mut outcomes = EnsureServerOutcomes::default();
249
250 for def in defs {
251 let server_id = def.kind.id_str().to_string();
252 let server_name = def.name.to_string();
253
254 let Some(root) = find_workspace_root(file_path, &def.root_markers) else {
255 outcomes.attempts.push(ServerAttempt {
256 server_id,
257 server_name,
258 result: ServerAttemptResult::NoRootMarker {
259 looked_for: def.root_markers.iter().map(|s| s.to_string()).collect(),
260 },
261 });
262 continue;
263 };
264
265 let key = ServerKey {
266 kind: def.kind.clone(),
267 root,
268 };
269
270 if !self.clients.contains_key(&key) {
271 if let Some(cached) = self.failed_spawns.get(&key) {
278 outcomes.attempts.push(ServerAttempt {
279 server_id,
280 server_name,
281 result: cached.clone(),
282 });
283 continue;
284 }
285
286 match self.spawn_server(&def, &key.root, config) {
287 Ok(client) => {
288 self.clients.insert(key.clone(), client);
289 self.documents.entry(key.clone()).or_default();
290 }
291 Err(err) => {
292 slog_error!("failed to spawn {}: {}", def.name, err);
293 let result = classify_spawn_error(&def.binary, &err);
294 self.failed_spawns.insert(key.clone(), result.clone());
298 outcomes.attempts.push(ServerAttempt {
299 server_id,
300 server_name,
301 result,
302 });
303 continue;
304 }
305 }
306 }
307
308 outcomes.attempts.push(ServerAttempt {
309 server_id,
310 server_name,
311 result: ServerAttemptResult::Ok {
312 server_key: key.clone(),
313 },
314 });
315 outcomes.successful.push(key);
316 }
317
318 outcomes
319 }
320
321 pub fn ensure_server_for_file_default(&mut self, file_path: &Path) -> Vec<ServerKey> {
324 self.ensure_server_for_file(file_path, &Config::default())
325 }
326 pub fn ensure_file_open(
330 &mut self,
331 file_path: &Path,
332 config: &Config,
333 ) -> Result<Vec<ServerKey>, LspError> {
334 let canonical_path = canonicalize_for_lsp(file_path)?;
335 let server_keys = self.ensure_server_for_file(&canonical_path, config);
336 if server_keys.is_empty() {
337 return Ok(server_keys);
338 }
339
340 let uri = uri_for_path(&canonical_path)?;
341 let language_id = language_id_for_extension(
342 canonical_path
343 .extension()
344 .and_then(|ext| ext.to_str())
345 .unwrap_or_default(),
346 )
347 .to_string();
348
349 for key in &server_keys {
350 let already_open = self
351 .documents
352 .get(key)
353 .is_some_and(|store| store.is_open(&canonical_path));
354
355 if !already_open {
356 let content = std::fs::read_to_string(&canonical_path).map_err(LspError::Io)?;
357 if let Some(client) = self.clients.get_mut(key) {
358 client.send_notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
359 text_document: TextDocumentItem::new(
360 uri.clone(),
361 language_id.clone(),
362 0,
363 content,
364 ),
365 })?;
366 }
367 self.documents
368 .entry(key.clone())
369 .or_default()
370 .open(canonical_path.clone());
371 continue;
372 }
373
374 let drifted = self
384 .documents
385 .get(key)
386 .is_some_and(|store| store.is_stale_on_disk(&canonical_path));
387 if drifted {
388 let content = std::fs::read_to_string(&canonical_path).map_err(LspError::Io)?;
389 let next_version = self
390 .documents
391 .get(key)
392 .and_then(|store| store.version(&canonical_path))
393 .map(|v| v + 1)
394 .unwrap_or(1);
395 if let Some(client) = self.clients.get_mut(key) {
396 client.send_notification::<DidChangeTextDocument>(
397 DidChangeTextDocumentParams {
398 text_document: VersionedTextDocumentIdentifier::new(
399 uri.clone(),
400 next_version,
401 ),
402 content_changes: vec![TextDocumentContentChangeEvent {
403 range: None,
404 range_length: None,
405 text: content,
406 }],
407 },
408 )?;
409 }
410 if let Some(store) = self.documents.get_mut(key) {
411 store.bump_version(&canonical_path);
412 }
413 }
414 }
415
416 Ok(server_keys)
417 }
418
419 pub fn ensure_file_open_default(
420 &mut self,
421 file_path: &Path,
422 ) -> Result<Vec<ServerKey>, LspError> {
423 self.ensure_file_open(file_path, &Config::default())
424 }
425
426 pub fn notify_file_changed(
432 &mut self,
433 file_path: &Path,
434 content: &str,
435 config: &Config,
436 ) -> Result<(), LspError> {
437 self.notify_file_changed_versioned(file_path, content, config)
438 .map(|_| ())
439 }
440
441 pub fn notify_file_changed_versioned(
452 &mut self,
453 file_path: &Path,
454 content: &str,
455 config: &Config,
456 ) -> Result<Vec<(ServerKey, i32)>, LspError> {
457 let canonical_path = canonicalize_for_lsp(file_path)?;
458 let server_keys = self.ensure_server_for_file(&canonical_path, config);
459 if server_keys.is_empty() {
460 return Ok(Vec::new());
461 }
462
463 let uri = uri_for_path(&canonical_path)?;
464 let language_id = language_id_for_extension(
465 canonical_path
466 .extension()
467 .and_then(|ext| ext.to_str())
468 .unwrap_or_default(),
469 )
470 .to_string();
471
472 let mut versions: Vec<(ServerKey, i32)> = Vec::with_capacity(server_keys.len());
473
474 for key in server_keys {
475 let current_version = self
476 .documents
477 .get(&key)
478 .and_then(|store| store.version(&canonical_path));
479
480 if let Some(version) = current_version {
481 let next_version = version + 1;
482 if let Some(client) = self.clients.get_mut(&key) {
483 client.send_notification::<DidChangeTextDocument>(
484 DidChangeTextDocumentParams {
485 text_document: VersionedTextDocumentIdentifier::new(
486 uri.clone(),
487 next_version,
488 ),
489 content_changes: vec![TextDocumentContentChangeEvent {
490 range: None,
491 range_length: None,
492 text: content.to_string(),
493 }],
494 },
495 )?;
496 }
497 if let Some(store) = self.documents.get_mut(&key) {
498 store.bump_version(&canonical_path);
499 }
500 versions.push((key, next_version));
501 continue;
502 }
503
504 if let Some(client) = self.clients.get_mut(&key) {
505 client.send_notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
506 text_document: TextDocumentItem::new(
507 uri.clone(),
508 language_id.clone(),
509 0,
510 content.to_string(),
511 ),
512 })?;
513 }
514 self.documents
515 .entry(key.clone())
516 .or_default()
517 .open(canonical_path.clone());
518 versions.push((key, 0));
521 }
522
523 Ok(versions)
524 }
525
526 pub fn notify_file_changed_default(
527 &mut self,
528 file_path: &Path,
529 content: &str,
530 ) -> Result<(), LspError> {
531 self.notify_file_changed(file_path, content, &Config::default())
532 }
533
534 pub fn notify_files_watched_changed(
540 &mut self,
541 paths: &[(PathBuf, FileChangeType)],
542 _config: &Config,
543 ) -> Result<(), LspError> {
544 if paths.is_empty() {
545 return Ok(());
546 }
547
548 let mut canonical_events = Vec::with_capacity(paths.len());
549 for (path, typ) in paths {
550 let canonical_path = resolve_for_lsp_uri(path);
551 canonical_events.push((canonical_path, *typ));
552 }
553
554 let keys: Vec<ServerKey> = self.clients.keys().cloned().collect();
555 for key in keys {
556 let mut changes = Vec::new();
557 for (path, typ) in &canonical_events {
558 if !path.starts_with(&key.root) {
559 continue;
560 }
561 changes.push(FileEvent::new(uri_for_path(path)?, *typ));
562 }
563
564 if changes.is_empty() {
565 continue;
566 }
567
568 if let Some(client) = self.clients.get_mut(&key) {
569 if !client.supports_watched_files() {
574 log::debug!(
575 "skipping didChangeWatchedFiles for {:?} (capability not declared)",
576 key
577 );
578 continue;
579 }
580 client.send_notification::<DidChangeWatchedFiles>(DidChangeWatchedFilesParams {
581 changes,
582 })?;
583 }
584 }
585
586 Ok(())
587 }
588
589 pub fn notify_file_closed(&mut self, file_path: &Path) -> Result<(), LspError> {
591 let canonical_path = canonicalize_for_lsp(file_path)?;
592 let uri = uri_for_path(&canonical_path)?;
593 let keys: Vec<ServerKey> = self.documents.keys().cloned().collect();
594
595 for key in keys {
596 let was_open = self
597 .documents
598 .get(&key)
599 .map(|store| store.is_open(&canonical_path))
600 .unwrap_or(false);
601 if !was_open {
602 continue;
603 }
604
605 if let Some(client) = self.clients.get_mut(&key) {
606 client.send_notification::<DidCloseTextDocument>(DidCloseTextDocumentParams {
607 text_document: TextDocumentIdentifier::new(uri.clone()),
608 })?;
609 }
610
611 if let Some(store) = self.documents.get_mut(&key) {
612 store.close(&canonical_path);
613 }
614 }
615
616 Ok(())
617 }
618
619 pub fn client_for_file(&self, file_path: &Path, config: &Config) -> Option<&LspClient> {
621 let key = self.server_key_for_file(file_path, config)?;
622 self.clients.get(&key)
623 }
624
625 pub fn client_for_file_default(&self, file_path: &Path) -> Option<&LspClient> {
626 self.client_for_file(file_path, &Config::default())
627 }
628
629 pub fn client_for_file_mut(
631 &mut self,
632 file_path: &Path,
633 config: &Config,
634 ) -> Option<&mut LspClient> {
635 let key = self.server_key_for_file(file_path, config)?;
636 self.clients.get_mut(&key)
637 }
638
639 pub fn client_for_file_mut_default(&mut self, file_path: &Path) -> Option<&mut LspClient> {
640 self.client_for_file_mut(file_path, &Config::default())
641 }
642
643 pub fn active_client_count(&self) -> usize {
645 self.clients.len()
646 }
647
648 pub fn drain_events(&mut self) -> Vec<LspEvent> {
650 let mut events = Vec::new();
651 while let Ok(event) = self.event_rx.try_recv() {
652 self.handle_event(&event);
653 events.push(event);
654 }
655 events
656 }
657
658 pub fn wait_for_diagnostics(
660 &mut self,
661 file_path: &Path,
662 config: &Config,
663 timeout: std::time::Duration,
664 ) -> Vec<StoredDiagnostic> {
665 let deadline = std::time::Instant::now() + timeout;
666 self.wait_for_file_diagnostics(file_path, config, deadline)
667 }
668
669 pub fn wait_for_diagnostics_default(
670 &mut self,
671 file_path: &Path,
672 timeout: std::time::Duration,
673 ) -> Vec<StoredDiagnostic> {
674 self.wait_for_diagnostics(file_path, &Config::default(), timeout)
675 }
676
677 #[doc(hidden)]
682 pub fn diagnostics_store_for_test(&self) -> &DiagnosticsStore {
683 &self.diagnostics
684 }
685
686 pub fn snapshot_diagnostic_epochs(&self, file_path: &Path) -> HashMap<ServerKey, u64> {
691 let lookup_path = normalize_lookup_path(file_path);
692 self.diagnostics
693 .entries_for_file(&lookup_path)
694 .into_iter()
695 .map(|(key, entry)| (key.clone(), entry.epoch))
696 .collect()
697 }
698
699 pub fn wait_for_post_edit_diagnostics(
722 &mut self,
723 file_path: &Path,
724 _config: &Config,
728 expected_versions: &[(ServerKey, i32)],
729 pre_snapshot: &HashMap<ServerKey, u64>,
730 timeout: std::time::Duration,
731 ) -> PostEditWaitOutcome {
732 let lookup_path = normalize_lookup_path(file_path);
733 let deadline = std::time::Instant::now() + timeout;
734
735 let _ = self.drain_events_for_file(&lookup_path);
740
741 let mut fresh: HashMap<ServerKey, Vec<StoredDiagnostic>> = HashMap::new();
742 let mut exited: Vec<ServerKey> = Vec::new();
743
744 loop {
745 for (key, target_version) in expected_versions {
752 if fresh.contains_key(key) || exited.contains(key) {
753 continue;
754 }
755 if !self.clients.contains_key(key) {
756 exited.push(key.clone());
757 continue;
758 }
759 if let Some(entry) = self
760 .diagnostics
761 .entries_for_file(&lookup_path)
762 .into_iter()
763 .find_map(|(k, e)| if k == key { Some(e) } else { None })
764 {
765 let is_fresh = match entry.version {
766 Some(v) => v == *target_version,
767 None => {
768 let pre = pre_snapshot.get(key).copied().unwrap_or(0);
769 entry.epoch > pre
770 }
771 };
772 if is_fresh {
773 fresh.insert(key.clone(), entry.diagnostics.clone());
774 }
775 }
776 }
777
778 if fresh.len() + exited.len() == expected_versions.len() {
780 break;
781 }
782
783 let now = std::time::Instant::now();
784 if now >= deadline {
785 break;
786 }
787
788 let timeout = deadline.saturating_duration_since(now);
789 match self.event_rx.recv_timeout(timeout) {
790 Ok(event) => {
791 self.handle_event(&event);
792 }
793 Err(RecvTimeoutError::Timeout) | Err(RecvTimeoutError::Disconnected) => break,
794 }
795 }
796
797 let pending: Vec<ServerKey> = expected_versions
799 .iter()
800 .filter(|(k, _)| !fresh.contains_key(k) && !exited.contains(k))
801 .map(|(k, _)| k.clone())
802 .collect();
803
804 let mut diagnostics: Vec<StoredDiagnostic> = fresh
807 .into_iter()
808 .flat_map(|(_, diags)| diags.into_iter())
809 .collect();
810 diagnostics.sort_by(|a, b| {
811 a.file
812 .cmp(&b.file)
813 .then(a.line.cmp(&b.line))
814 .then(a.column.cmp(&b.column))
815 .then(a.message.cmp(&b.message))
816 });
817
818 PostEditWaitOutcome {
819 diagnostics,
820 pending_servers: pending,
821 exited_servers: exited,
822 }
823 }
824
825 pub fn wait_for_file_diagnostics(
831 &mut self,
832 file_path: &Path,
833 config: &Config,
834 deadline: std::time::Instant,
835 ) -> Vec<StoredDiagnostic> {
836 let lookup_path = normalize_lookup_path(file_path);
837
838 if self.server_key_for_file(&lookup_path, config).is_none() {
839 return Vec::new();
840 }
841
842 loop {
843 if self.drain_events_for_file(&lookup_path) {
844 break;
845 }
846
847 let now = std::time::Instant::now();
848 if now >= deadline {
849 break;
850 }
851
852 let timeout = deadline.saturating_duration_since(now);
853 match self.event_rx.recv_timeout(timeout) {
854 Ok(event) => {
855 if matches!(
856 self.handle_event(&event),
857 Some(ref published_file) if published_file.as_path() == lookup_path.as_path()
858 ) {
859 break;
860 }
861 }
862 Err(RecvTimeoutError::Timeout) | Err(RecvTimeoutError::Disconnected) => break,
863 }
864 }
865
866 self.get_diagnostics_for_file(&lookup_path)
867 .into_iter()
868 .cloned()
869 .collect()
870 }
871
872 pub const PULL_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
878
879 pub fn pull_file_timeout() -> std::time::Duration {
881 Self::PULL_FILE_TIMEOUT
882 }
883
884 const PULL_WORKSPACE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
888
889 pub fn pull_file_diagnostics(
900 &mut self,
901 file_path: &Path,
902 config: &Config,
903 ) -> Result<Vec<PullFileResult>, LspError> {
904 let canonical_path = canonicalize_for_lsp(file_path)?;
905 self.ensure_file_open(&canonical_path, config)?;
908
909 let server_keys = self.ensure_server_for_file(&canonical_path, config);
910 if server_keys.is_empty() {
911 return Ok(Vec::new());
912 }
913
914 let uri = uri_for_path(&canonical_path)?;
915 let mut results = Vec::with_capacity(server_keys.len());
916
917 for key in server_keys {
918 let supports_pull = self
919 .clients
920 .get(&key)
921 .and_then(|c| c.diagnostic_capabilities())
922 .is_some_and(|caps| caps.pull_diagnostics);
923
924 if !supports_pull {
925 results.push(PullFileResult {
926 server_key: key.clone(),
927 outcome: PullFileOutcome::PullNotSupported,
928 });
929 continue;
930 }
931
932 let previous_result_id = self
934 .diagnostics
935 .entries_for_file(&canonical_path)
936 .into_iter()
937 .find(|(k, _)| **k == key)
938 .and_then(|(_, entry)| entry.result_id.clone());
939
940 let identifier = self
941 .clients
942 .get(&key)
943 .and_then(|c| c.diagnostic_capabilities())
944 .and_then(|caps| caps.identifier.clone());
945
946 let params = lsp_types::DocumentDiagnosticParams {
947 text_document: lsp_types::TextDocumentIdentifier { uri: uri.clone() },
948 identifier,
949 previous_result_id,
950 work_done_progress_params: Default::default(),
951 partial_result_params: Default::default(),
952 };
953
954 let outcome = match self.send_pull_request(&key, params) {
955 Ok(report) => self.ingest_document_report(&key, &canonical_path, report),
956 Err(err) => PullFileOutcome::RequestFailed {
957 reason: err.to_string(),
958 },
959 };
960
961 results.push(PullFileResult {
962 server_key: key,
963 outcome,
964 });
965 }
966
967 Ok(results)
968 }
969
970 pub fn pull_workspace_diagnostics(
975 &mut self,
976 server_key: &ServerKey,
977 timeout: Option<std::time::Duration>,
978 ) -> Result<PullWorkspaceResult, LspError> {
979 let _timeout = timeout.unwrap_or(Self::PULL_WORKSPACE_TIMEOUT);
980
981 let supports_workspace = self
982 .clients
983 .get(server_key)
984 .and_then(|c| c.diagnostic_capabilities())
985 .is_some_and(|caps| caps.workspace_diagnostics);
986
987 if !supports_workspace {
988 return Ok(PullWorkspaceResult {
989 server_key: server_key.clone(),
990 files_reported: Vec::new(),
991 complete: false,
992 cancelled: false,
993 supports_workspace: false,
994 });
995 }
996
997 let identifier = self
998 .clients
999 .get(server_key)
1000 .and_then(|c| c.diagnostic_capabilities())
1001 .and_then(|caps| caps.identifier.clone());
1002
1003 let params = lsp_types::WorkspaceDiagnosticParams {
1004 identifier,
1005 previous_result_ids: Vec::new(),
1006 work_done_progress_params: Default::default(),
1007 partial_result_params: Default::default(),
1008 };
1009
1010 let result = match self
1018 .clients
1019 .get_mut(server_key)
1020 .ok_or_else(|| LspError::ServerNotReady("server not found".into()))?
1021 .send_request::<lsp_types::request::WorkspaceDiagnosticRequest>(params)
1022 {
1023 Ok(result) => result,
1024 Err(LspError::Timeout(_)) => {
1025 return Ok(PullWorkspaceResult {
1026 server_key: server_key.clone(),
1027 files_reported: Vec::new(),
1028 complete: false,
1029 cancelled: true,
1030 supports_workspace: true,
1031 });
1032 }
1033 Err(err) => return Err(err),
1034 };
1035
1036 let items = match result {
1041 lsp_types::WorkspaceDiagnosticReportResult::Report(report) => report.items,
1042 lsp_types::WorkspaceDiagnosticReportResult::Partial(_) => Vec::new(),
1043 };
1044
1045 let mut files_reported = Vec::with_capacity(items.len());
1047 for item in items {
1048 match item {
1049 lsp_types::WorkspaceDocumentDiagnosticReport::Full(full) => {
1050 if let Some(file) = uri_to_path(&full.uri) {
1051 let stored = from_lsp_diagnostics(
1052 file.clone(),
1053 full.full_document_diagnostic_report.items.clone(),
1054 );
1055 self.diagnostics.publish_with_result_id(
1056 server_key.clone(),
1057 file.clone(),
1058 stored,
1059 full.full_document_diagnostic_report.result_id.clone(),
1060 );
1061 files_reported.push(file);
1062 }
1063 }
1064 lsp_types::WorkspaceDocumentDiagnosticReport::Unchanged(_unchanged) => {
1065 }
1068 }
1069 }
1070
1071 Ok(PullWorkspaceResult {
1072 server_key: server_key.clone(),
1073 files_reported,
1074 complete: true,
1075 cancelled: false,
1076 supports_workspace: true,
1077 })
1078 }
1079
1080 fn send_pull_request(
1082 &mut self,
1083 key: &ServerKey,
1084 params: lsp_types::DocumentDiagnosticParams,
1085 ) -> Result<lsp_types::DocumentDiagnosticReportResult, LspError> {
1086 let client = self
1087 .clients
1088 .get_mut(key)
1089 .ok_or_else(|| LspError::ServerNotReady("server not found".into()))?;
1090 client.send_request::<lsp_types::request::DocumentDiagnosticRequest>(params)
1091 }
1092
1093 fn ingest_document_report(
1096 &mut self,
1097 key: &ServerKey,
1098 canonical_path: &Path,
1099 result: lsp_types::DocumentDiagnosticReportResult,
1100 ) -> PullFileOutcome {
1101 let report = match result {
1102 lsp_types::DocumentDiagnosticReportResult::Report(report) => report,
1103 lsp_types::DocumentDiagnosticReportResult::Partial(_) => {
1104 return PullFileOutcome::PartialNotSupported;
1108 }
1109 };
1110
1111 match report {
1112 lsp_types::DocumentDiagnosticReport::Full(full) => {
1113 let result_id = full.full_document_diagnostic_report.result_id.clone();
1114 let stored = from_lsp_diagnostics(
1115 canonical_path.to_path_buf(),
1116 full.full_document_diagnostic_report.items.clone(),
1117 );
1118 let count = stored.len();
1119 self.diagnostics.publish_with_result_id(
1120 key.clone(),
1121 canonical_path.to_path_buf(),
1122 stored,
1123 result_id,
1124 );
1125 PullFileOutcome::Full {
1126 diagnostic_count: count,
1127 }
1128 }
1129 lsp_types::DocumentDiagnosticReport::Unchanged(_unchanged) => {
1130 PullFileOutcome::Unchanged
1133 }
1134 }
1135 }
1136
1137 pub fn shutdown_all(&mut self) {
1139 for (key, mut client) in self.clients.drain() {
1140 if let Err(err) = client.shutdown() {
1141 slog_error!("error shutting down {:?}: {}", key, err);
1142 }
1143 }
1144 self.documents.clear();
1145 self.diagnostics = DiagnosticsStore::new();
1146 }
1147
1148 pub fn has_active_servers(&self) -> bool {
1150 self.clients
1151 .values()
1152 .any(|client| client.state() == ServerState::Ready)
1153 }
1154
1155 pub fn active_server_keys(&self) -> Vec<ServerKey> {
1158 self.clients.keys().cloned().collect()
1159 }
1160
1161 pub fn get_diagnostics_for_file(&self, file: &Path) -> Vec<&StoredDiagnostic> {
1162 let normalized = normalize_lookup_path(file);
1163 self.diagnostics.for_file(&normalized)
1164 }
1165
1166 pub fn get_diagnostics_for_directory(&self, dir: &Path) -> Vec<&StoredDiagnostic> {
1167 let normalized = normalize_lookup_path(dir);
1168 self.diagnostics.for_directory(&normalized)
1169 }
1170
1171 pub fn get_all_diagnostics(&self) -> Vec<&StoredDiagnostic> {
1172 self.diagnostics.all()
1173 }
1174
1175 fn drain_events_for_file(&mut self, file_path: &Path) -> bool {
1176 let mut saw_file_diagnostics = false;
1177 while let Ok(event) = self.event_rx.try_recv() {
1178 if matches!(
1179 self.handle_event(&event),
1180 Some(ref published_file) if published_file.as_path() == file_path
1181 ) {
1182 saw_file_diagnostics = true;
1183 }
1184 }
1185 saw_file_diagnostics
1186 }
1187
1188 fn handle_event(&mut self, event: &LspEvent) -> Option<PathBuf> {
1189 match event {
1190 LspEvent::Notification {
1191 server_kind,
1192 root,
1193 method,
1194 params: Some(params),
1195 } if method == "textDocument/publishDiagnostics" => {
1196 self.handle_publish_diagnostics(server_kind.clone(), root.clone(), params)
1197 }
1198 LspEvent::ServerExited { server_kind, root } => {
1199 let key = ServerKey {
1200 kind: server_kind.clone(),
1201 root: root.clone(),
1202 };
1203 self.clients.remove(&key);
1204 self.documents.remove(&key);
1205 self.diagnostics.clear_server(server_kind.clone());
1206 None
1207 }
1208 _ => None,
1209 }
1210 }
1211
1212 fn handle_publish_diagnostics(
1213 &mut self,
1214 server: ServerKind,
1215 root: PathBuf,
1216 params: &serde_json::Value,
1217 ) -> Option<PathBuf> {
1218 if let Ok(publish_params) =
1219 serde_json::from_value::<lsp_types::PublishDiagnosticsParams>(params.clone())
1220 {
1221 let file = uri_to_path(&publish_params.uri)?;
1222 let stored = from_lsp_diagnostics(file.clone(), publish_params.diagnostics);
1223 let key = ServerKey { kind: server, root };
1229 self.diagnostics
1230 .publish_full(key, file.clone(), stored, None, publish_params.version);
1231 return Some(file);
1232 }
1233 None
1234 }
1235
1236 fn spawn_server(
1237 &self,
1238 def: &ServerDef,
1239 root: &Path,
1240 config: &Config,
1241 ) -> Result<LspClient, LspError> {
1242 let binary = self.resolve_binary(def, config)?;
1243
1244 let mut merged_env = def.env.clone();
1248 for (key, value) in &self.extra_env {
1249 merged_env.insert(key.clone(), value.clone());
1250 }
1251
1252 let mut client = LspClient::spawn(
1253 def.kind.clone(),
1254 root.to_path_buf(),
1255 &binary,
1256 &def.args,
1257 &merged_env,
1258 self.event_tx.clone(),
1259 self.child_registry.clone(),
1260 )?;
1261 client.initialize(root, def.initialization_options.clone())?;
1262 Ok(client)
1263 }
1264
1265 fn resolve_binary(&self, def: &ServerDef, config: &Config) -> Result<PathBuf, LspError> {
1266 if let Some(path) = self.binary_overrides.get(&def.kind) {
1267 if path.exists() {
1268 return Ok(path.clone());
1269 }
1270 return Err(LspError::NotFound(format!(
1271 "override binary for {:?} not found: {}",
1272 def.kind,
1273 path.display()
1274 )));
1275 }
1276
1277 if let Some(path) = env_binary_override(&def.kind) {
1278 if path.exists() {
1279 return Ok(path);
1280 }
1281 return Err(LspError::NotFound(format!(
1282 "environment override binary for {:?} not found: {}",
1283 def.kind,
1284 path.display()
1285 )));
1286 }
1287
1288 resolve_lsp_binary(
1293 &def.binary,
1294 config.project_root.as_deref(),
1295 &config.lsp_paths_extra,
1296 )
1297 .ok_or_else(|| {
1298 LspError::NotFound(format!(
1299 "language server binary '{}' not found in node_modules/.bin, lsp_paths_extra, or PATH",
1300 def.binary
1301 ))
1302 })
1303 }
1304
1305 fn server_key_for_file(&self, file_path: &Path, config: &Config) -> Option<ServerKey> {
1306 for def in servers_for_file(file_path, config) {
1307 let root = find_workspace_root(file_path, &def.root_markers)?;
1308 let key = ServerKey {
1309 kind: def.kind.clone(),
1310 root,
1311 };
1312 if self.clients.contains_key(&key) {
1313 return Some(key);
1314 }
1315 }
1316 None
1317 }
1318}
1319
1320impl Default for LspManager {
1321 fn default() -> Self {
1322 Self::new()
1323 }
1324}
1325
1326fn canonicalize_for_lsp(file_path: &Path) -> Result<PathBuf, LspError> {
1327 std::fs::canonicalize(file_path).map_err(LspError::from)
1328}
1329
1330fn resolve_for_lsp_uri(file_path: &Path) -> PathBuf {
1331 if let Ok(path) = std::fs::canonicalize(file_path) {
1332 return path;
1333 }
1334
1335 let mut existing = file_path.to_path_buf();
1336 let mut missing = Vec::new();
1337 while !existing.exists() {
1338 let Some(name) = existing.file_name() else {
1339 break;
1340 };
1341 missing.push(name.to_owned());
1342 let Some(parent) = existing.parent() else {
1343 break;
1344 };
1345 existing = parent.to_path_buf();
1346 }
1347
1348 let mut resolved = std::fs::canonicalize(&existing).unwrap_or(existing);
1349 for segment in missing.into_iter().rev() {
1350 resolved.push(segment);
1351 }
1352 resolved
1353}
1354
1355fn uri_for_path(path: &Path) -> Result<lsp_types::Uri, LspError> {
1356 let url = url::Url::from_file_path(path).map_err(|_| {
1357 LspError::NotFound(format!(
1358 "failed to convert '{}' to file URI",
1359 path.display()
1360 ))
1361 })?;
1362 lsp_types::Uri::from_str(url.as_str()).map_err(|_| {
1363 LspError::NotFound(format!("failed to parse file URI for '{}'", path.display()))
1364 })
1365}
1366
1367fn language_id_for_extension(ext: &str) -> &'static str {
1368 match ext {
1369 "ts" => "typescript",
1370 "tsx" => "typescriptreact",
1371 "js" | "mjs" | "cjs" => "javascript",
1372 "jsx" => "javascriptreact",
1373 "py" | "pyi" => "python",
1374 "rs" => "rust",
1375 "go" => "go",
1376 "html" | "htm" => "html",
1377 _ => "plaintext",
1378 }
1379}
1380
1381fn normalize_lookup_path(path: &Path) -> PathBuf {
1382 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
1383}
1384
1385fn uri_to_path(uri: &lsp_types::Uri) -> Option<PathBuf> {
1386 let url = url::Url::parse(uri.as_str()).ok()?;
1387 url.to_file_path()
1388 .ok()
1389 .map(|path| normalize_lookup_path(&path))
1390}
1391
1392fn classify_spawn_error(binary: &str, err: &LspError) -> ServerAttemptResult {
1399 match err {
1400 LspError::NotFound(_) => ServerAttemptResult::BinaryNotInstalled {
1405 binary: binary.to_string(),
1406 },
1407 other => ServerAttemptResult::SpawnFailed {
1408 binary: binary.to_string(),
1409 reason: other.to_string(),
1410 },
1411 }
1412}
1413
1414fn env_binary_override(kind: &ServerKind) -> Option<PathBuf> {
1415 let id = kind.id_str();
1416 let suffix: String = id
1417 .chars()
1418 .map(|ch| {
1419 if ch.is_ascii_alphanumeric() {
1420 ch.to_ascii_uppercase()
1421 } else {
1422 '_'
1423 }
1424 })
1425 .collect();
1426 let key = format!("AFT_LSP_{suffix}_BINARY");
1427 std::env::var_os(key).map(PathBuf::from)
1428}