1use crate::services::async_bridge::{
15 AsyncBridge, AsyncMessage, LspMessageType, LspProgressValue, LspSemanticTokensResponse,
16 LspServerStatus,
17};
18use crate::services::process_limits::ProcessLimits;
19use lsp_types::{
20 notification::{
21 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument,
22 Initialized, Notification, PublishDiagnostics,
23 },
24 request::{Initialize, Request},
25 ClientCapabilities, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
26 DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult,
27 InitializedParams, PartialResultParams, Position, PublishDiagnosticsParams, Range,
28 SemanticTokenModifier, SemanticTokenType, SemanticTokensClientCapabilities,
29 SemanticTokensClientCapabilitiesRequests, SemanticTokensFullOptions, SemanticTokensParams,
30 SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities,
31 TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
32 TextDocumentPositionParams, TokenFormat, Uri, VersionedTextDocumentIdentifier,
33 WindowClientCapabilities, WorkDoneProgressParams, WorkspaceFolder,
34};
35use serde::{Deserialize, Serialize};
36use serde_json::Value;
37use std::collections::HashMap;
38use std::path::PathBuf;
39use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
40use std::sync::{mpsc as std_mpsc, Arc, Mutex};
41use std::time::{Duration, Instant};
42use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
43use tokio::process::{ChildStdin, ChildStdout};
44use tokio::sync::{mpsc, oneshot};
45
46type PendingRequests = Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>;
47
48const DID_OPEN_GRACE_PERIOD_MS: u64 = 200;
51
52const DEFAULT_REQUEST_TIMEOUT_MS: u64 = 30_000;
58
59const LSP_ERROR_CONTENT_MODIFIED: i64 = -32801;
74const LSP_ERROR_SERVER_CANCELLED: i64 = -32802;
75
76fn is_suppressed_error_code(code: i64) -> bool {
79 code == LSP_ERROR_CONTENT_MODIFIED || code == LSP_ERROR_SERVER_CANCELLED
80}
81
82fn log_response_error(code: i64, message: &str, server_name: &str, language: &str) {
87 if is_suppressed_error_code(code) {
88 tracing::debug!(
89 "LSP response from '{}' ({}): {} (code {}), discarding",
90 server_name,
91 language,
92 message,
93 code
94 );
95 } else {
96 tracing::warn!(
97 "LSP response error from '{}' ({}): {} (code {})",
98 server_name,
99 language,
100 message,
101 code
102 );
103 }
104}
105
106fn should_skip_did_open(
109 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
110 path: &PathBuf,
111 language: &str,
112 uri: &Uri,
113) -> bool {
114 if document_versions.lock().unwrap().contains_key(path) {
115 tracing::debug!(
116 "LSP ({}): skipping didOpen - document already open: {}",
117 language,
118 uri.as_str()
119 );
120 true
121 } else {
122 false
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(untagged)]
129pub enum JsonRpcMessage {
130 Request(JsonRpcRequest),
131 Response(JsonRpcResponse),
132 Notification(JsonRpcNotification),
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct JsonRpcRequest {
138 pub jsonrpc: String,
139 pub id: i64,
140 pub method: String,
141 pub params: Option<Value>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct JsonRpcResponse {
147 pub jsonrpc: String,
148 pub id: i64,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub result: Option<Value>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub error: Option<JsonRpcError>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct JsonRpcNotification {
158 pub jsonrpc: String,
159 pub method: String,
160 pub params: Option<Value>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct JsonRpcError {
166 pub code: i64,
167 pub message: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub data: Option<Value>,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum LspClientState {
178 Initial,
180 Starting,
182 Initializing,
184 Running,
186 Stopping,
188 Stopped,
190 Error,
192}
193
194impl LspClientState {
195 pub fn can_transition_to(&self, next: LspClientState) -> bool {
197 use LspClientState::*;
198 match (self, next) {
199 (Initial, Starting) => true,
201 (Starting, Initializing) | (Starting, Error) => true,
203 (Initializing, Running) | (Initializing, Error) => true,
205 (Running, Stopping) | (Running, Error) => true,
207 (Stopping, Stopped) | (Stopping, Error) => true,
209 (Stopped, Starting) => true,
211 (Error, Stopping) | (Error, Starting) => true,
215 (_, Error) => true,
217 (a, b) if *a == b => true,
219 _ => false,
221 }
222 }
223
224 pub fn transition_to(&mut self, next: LspClientState) -> Result<(), String> {
226 if self.can_transition_to(next) {
227 *self = next;
228 Ok(())
229 } else {
230 Err(format!(
231 "Invalid state transition from {:?} to {:?}",
232 self, next
233 ))
234 }
235 }
236
237 pub fn can_send_requests(&self) -> bool {
239 matches!(self, Self::Running)
240 }
241
242 pub fn can_initialize(&self) -> bool {
244 matches!(self, Self::Initial | Self::Starting | Self::Stopped)
245 }
246
247 pub fn to_server_status(&self) -> LspServerStatus {
249 match self {
250 Self::Initial => LspServerStatus::Starting,
251 Self::Starting => LspServerStatus::Starting,
252 Self::Initializing => LspServerStatus::Initializing,
253 Self::Running => LspServerStatus::Running,
254 Self::Stopping => LspServerStatus::Shutdown,
255 Self::Stopped => LspServerStatus::Shutdown,
256 Self::Error => LspServerStatus::Error,
257 }
258 }
259}
260
261fn create_client_capabilities() -> ClientCapabilities {
263 use lsp_types::{
264 CodeActionClientCapabilities, CodeActionKindLiteralSupport, CodeActionLiteralSupport,
265 CompletionClientCapabilities, DiagnosticClientCapabilities, DiagnosticTag,
266 DiagnosticWorkspaceClientCapabilities, DocumentFormattingClientCapabilities,
267 DocumentHighlightClientCapabilities, DocumentRangeFormattingClientCapabilities,
268 DocumentSymbolClientCapabilities, DynamicRegistrationClientCapabilities,
269 FoldingRangeCapability, FoldingRangeClientCapabilities, FoldingRangeKind,
270 FoldingRangeKindCapability, GeneralClientCapabilities, GotoCapability,
271 HoverClientCapabilities, InlayHintClientCapabilities, InlayHintWorkspaceClientCapabilities,
272 MarkupKind, PublishDiagnosticsClientCapabilities, RenameClientCapabilities,
273 SemanticTokensWorkspaceClientCapabilities, SignatureHelpClientCapabilities, TagSupport,
274 TextDocumentClientCapabilities, TextDocumentSyncClientCapabilities,
275 WorkspaceClientCapabilities, WorkspaceEditClientCapabilities,
276 WorkspaceSymbolClientCapabilities,
277 };
278
279 ClientCapabilities {
280 window: Some(WindowClientCapabilities {
281 work_done_progress: Some(true),
282 ..Default::default()
283 }),
284 workspace: Some(WorkspaceClientCapabilities {
285 apply_edit: Some(true),
286 workspace_edit: Some(WorkspaceEditClientCapabilities {
287 document_changes: Some(true),
288 ..Default::default()
289 }),
290 workspace_folders: Some(true),
291 configuration: Some(true),
299 symbol: Some(WorkspaceSymbolClientCapabilities {
303 dynamic_registration: Some(true),
304 ..Default::default()
305 }),
306 diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
312 refresh_support: Some(true),
313 }),
314 inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
321 refresh_support: Some(true),
322 }),
323 semantic_tokens: Some(SemanticTokensWorkspaceClientCapabilities {
324 refresh_support: Some(true),
325 }),
326 ..Default::default()
327 }),
328 text_document: Some(TextDocumentClientCapabilities {
329 synchronization: Some(TextDocumentSyncClientCapabilities {
330 did_save: Some(true),
331 ..Default::default()
332 }),
333 completion: Some(CompletionClientCapabilities {
339 dynamic_registration: Some(true),
340 ..Default::default()
341 }),
342 hover: Some(HoverClientCapabilities {
343 dynamic_registration: Some(true),
344 content_format: Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]),
345 }),
346 signature_help: Some(SignatureHelpClientCapabilities {
347 dynamic_registration: Some(true),
348 ..Default::default()
349 }),
350 definition: Some(GotoCapability {
351 dynamic_registration: Some(true),
352 link_support: Some(true),
353 }),
354 references: Some(DynamicRegistrationClientCapabilities {
355 dynamic_registration: Some(true),
356 }),
357 document_highlight: Some(DocumentHighlightClientCapabilities {
358 dynamic_registration: Some(true),
359 }),
360 document_symbol: Some(DocumentSymbolClientCapabilities {
361 dynamic_registration: Some(true),
362 ..Default::default()
363 }),
364 formatting: Some(DocumentFormattingClientCapabilities {
365 dynamic_registration: Some(true),
366 }),
367 range_formatting: Some(DocumentRangeFormattingClientCapabilities {
368 dynamic_registration: Some(true),
369 }),
370 code_action: Some(CodeActionClientCapabilities {
371 dynamic_registration: Some(true),
372 code_action_literal_support: Some(CodeActionLiteralSupport {
379 code_action_kind: CodeActionKindLiteralSupport {
380 value_set: vec![
381 String::new(),
382 "quickfix".to_string(),
383 "refactor".to_string(),
384 "refactor.extract".to_string(),
385 "refactor.inline".to_string(),
386 "refactor.rewrite".to_string(),
387 "source".to_string(),
388 "source.organizeImports".to_string(),
389 ],
390 },
391 }),
392 ..Default::default()
393 }),
394 rename: Some(RenameClientCapabilities {
395 dynamic_registration: Some(true),
396 prepare_support: Some(true),
397 honors_change_annotations: Some(true),
398 ..Default::default()
399 }),
400 publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
401 related_information: Some(true),
402 tag_support: Some(TagSupport {
403 value_set: vec![DiagnosticTag::UNNECESSARY, DiagnosticTag::DEPRECATED],
404 }),
405 version_support: Some(true),
406 code_description_support: Some(true),
407 data_support: Some(true),
408 }),
409 inlay_hint: Some(InlayHintClientCapabilities {
410 dynamic_registration: Some(true),
411 ..Default::default()
412 }),
413 diagnostic: Some(DiagnosticClientCapabilities {
414 dynamic_registration: Some(true),
415 ..Default::default()
416 }),
417 folding_range: Some(FoldingRangeClientCapabilities {
418 dynamic_registration: Some(true),
419 line_folding_only: Some(true),
420 folding_range_kind: Some(FoldingRangeKindCapability {
421 value_set: Some(vec![
422 FoldingRangeKind::Comment,
423 FoldingRangeKind::Imports,
424 FoldingRangeKind::Region,
425 ]),
426 }),
427 folding_range: Some(FoldingRangeCapability {
428 collapsed_text: Some(true),
429 }),
430 ..Default::default()
431 }),
432 semantic_tokens: Some(SemanticTokensClientCapabilities {
433 dynamic_registration: Some(true),
434 requests: SemanticTokensClientCapabilitiesRequests {
435 range: Some(true),
436 full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
437 },
438 token_types: vec![
439 SemanticTokenType::NAMESPACE,
440 SemanticTokenType::TYPE,
441 SemanticTokenType::CLASS,
442 SemanticTokenType::ENUM,
443 SemanticTokenType::INTERFACE,
444 SemanticTokenType::STRUCT,
445 SemanticTokenType::TYPE_PARAMETER,
446 SemanticTokenType::PARAMETER,
447 SemanticTokenType::VARIABLE,
448 SemanticTokenType::PROPERTY,
449 SemanticTokenType::ENUM_MEMBER,
450 SemanticTokenType::EVENT,
451 SemanticTokenType::FUNCTION,
452 SemanticTokenType::METHOD,
453 SemanticTokenType::MACRO,
454 SemanticTokenType::KEYWORD,
455 SemanticTokenType::MODIFIER,
456 SemanticTokenType::COMMENT,
457 SemanticTokenType::STRING,
458 SemanticTokenType::NUMBER,
459 SemanticTokenType::REGEXP,
460 SemanticTokenType::OPERATOR,
461 SemanticTokenType::DECORATOR,
462 ],
463 token_modifiers: vec![
464 SemanticTokenModifier::DECLARATION,
465 SemanticTokenModifier::DEFINITION,
466 SemanticTokenModifier::READONLY,
467 SemanticTokenModifier::STATIC,
468 SemanticTokenModifier::DEPRECATED,
469 SemanticTokenModifier::ABSTRACT,
470 SemanticTokenModifier::ASYNC,
471 SemanticTokenModifier::MODIFICATION,
472 SemanticTokenModifier::DOCUMENTATION,
473 SemanticTokenModifier::DEFAULT_LIBRARY,
474 ],
475 formats: vec![TokenFormat::RELATIVE],
476 overlapping_token_support: Some(true),
477 multiline_token_support: Some(true),
478 server_cancel_support: Some(true),
479 augments_syntax_tokens: Some(true),
480 }),
481 ..Default::default()
482 }),
483 general: Some(GeneralClientCapabilities {
484 ..Default::default()
485 }),
486 experimental: Some(serde_json::json!({
488 "serverStatusNotification": true
489 })),
490 ..Default::default()
491 }
492}
493
494use crate::services::lsp::manager::ServerCapabilitySummary;
495
496fn extract_capability_summary(caps: &ServerCapabilities) -> ServerCapabilitySummary {
502 let (sem_legend, sem_full, sem_full_delta, sem_range) = caps
503 .semantic_tokens_provider
504 .as_ref()
505 .map(|provider| {
506 let (legend, full_opt) = match provider {
507 SemanticTokensServerCapabilities::SemanticTokensOptions(o) => {
508 (o.legend.clone(), &o.full)
509 }
510 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(o) => (
511 o.semantic_tokens_options.legend.clone(),
512 &o.semantic_tokens_options.full,
513 ),
514 };
515 let range = match provider {
516 SemanticTokensServerCapabilities::SemanticTokensOptions(o) => {
517 o.range.unwrap_or(false)
518 }
519 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(o) => {
520 o.semantic_tokens_options.range.unwrap_or(false)
521 }
522 };
523 let full = match full_opt {
524 Some(SemanticTokensFullOptions::Bool(v)) => *v,
525 Some(SemanticTokensFullOptions::Delta { .. }) => true,
526 None => false,
527 };
528 let delta = match full_opt {
529 Some(SemanticTokensFullOptions::Delta { delta }) => delta.unwrap_or(false),
530 _ => false,
531 };
532 (Some(legend), full, delta, range)
533 })
534 .unwrap_or((None, false, false, false));
535
536 ServerCapabilitySummary {
537 initialized: false, hover: bool_or_options(&caps.hover_provider, |p| match p {
539 lsp_types::HoverProviderCapability::Simple(v) => *v,
540 lsp_types::HoverProviderCapability::Options(_) => true,
541 }),
542 completion: caps.completion_provider.is_some(),
543 completion_resolve: caps
544 .completion_provider
545 .as_ref()
546 .and_then(|cp| cp.resolve_provider)
547 .unwrap_or(false),
548 completion_trigger_characters: caps
549 .completion_provider
550 .as_ref()
551 .and_then(|cp| cp.trigger_characters.clone())
552 .unwrap_or_default(),
553 definition: bool_or_options(&caps.definition_provider, |p| match p {
554 lsp_types::OneOf::Left(v) => *v,
555 lsp_types::OneOf::Right(_) => true,
556 }),
557 references: bool_or_options(&caps.references_provider, |p| match p {
558 lsp_types::OneOf::Left(v) => *v,
559 lsp_types::OneOf::Right(_) => true,
560 }),
561 document_formatting: bool_or_options(&caps.document_formatting_provider, |p| match p {
562 lsp_types::OneOf::Left(v) => *v,
563 lsp_types::OneOf::Right(_) => true,
564 }),
565 document_range_formatting: bool_or_options(&caps.document_range_formatting_provider, |p| {
566 match p {
567 lsp_types::OneOf::Left(v) => *v,
568 lsp_types::OneOf::Right(_) => true,
569 }
570 }),
571 rename: bool_or_options(&caps.rename_provider, |p| match p {
572 lsp_types::OneOf::Left(v) => *v,
573 lsp_types::OneOf::Right(_) => true,
574 }),
575 signature_help: caps.signature_help_provider.is_some(),
576 inlay_hints: bool_or_options(&caps.inlay_hint_provider, |p| match p {
577 lsp_types::OneOf::Left(v) => *v,
578 lsp_types::OneOf::Right(_) => true,
579 }),
580 folding_ranges: bool_or_options(&caps.folding_range_provider, |p| match p {
581 lsp_types::FoldingRangeProviderCapability::Simple(v) => *v,
582 _ => true,
583 }),
584 semantic_tokens_full: sem_full,
585 semantic_tokens_full_delta: sem_full_delta,
586 semantic_tokens_range: sem_range,
587 semantic_tokens_legend: sem_legend,
588 document_highlight: bool_or_options(&caps.document_highlight_provider, |p| match p {
589 lsp_types::OneOf::Left(v) => *v,
590 lsp_types::OneOf::Right(_) => true,
591 }),
592 code_action: bool_or_options(&caps.code_action_provider, |p| match p {
593 lsp_types::CodeActionProviderCapability::Simple(v) => *v,
594 lsp_types::CodeActionProviderCapability::Options(_) => true,
595 }),
596 code_action_resolve: caps.code_action_provider.as_ref().is_some_and(|p| match p {
597 lsp_types::CodeActionProviderCapability::Options(opts) => {
598 opts.resolve_provider.unwrap_or(false)
599 }
600 _ => false,
601 }),
602 document_symbols: bool_or_options(&caps.document_symbol_provider, |p| match p {
603 lsp_types::OneOf::Left(v) => *v,
604 lsp_types::OneOf::Right(_) => true,
605 }),
606 workspace_symbols: bool_or_options(&caps.workspace_symbol_provider, |p| match p {
607 lsp_types::OneOf::Left(v) => *v,
608 lsp_types::OneOf::Right(_) => true,
609 }),
610 diagnostics: caps.diagnostic_provider.is_some(),
611 }
612}
613
614fn bool_or_options<T>(opt: &Option<T>, check: impl FnOnce(&T) -> bool) -> bool {
616 opt.as_ref().is_some_and(check)
617}
618
619#[derive(Debug)]
621enum LspCommand {
622 Initialize {
624 root_uri: Option<Uri>,
625 initialization_options: Option<Value>,
626 response: oneshot::Sender<Result<InitializeResult, String>>,
627 },
628
629 DidOpen {
631 uri: Uri,
632 text: String,
633 language_id: String,
634 },
635
636 DidChange {
638 uri: Uri,
639 content_changes: Vec<TextDocumentContentChangeEvent>,
640 },
641
642 DidClose { uri: Uri },
644
645 DidSave { uri: Uri, text: Option<String> },
647
648 DidChangeWorkspaceFolders {
650 added: Vec<lsp_types::WorkspaceFolder>,
651 removed: Vec<lsp_types::WorkspaceFolder>,
652 },
653
654 Completion {
656 request_id: u64,
657 uri: Uri,
658 line: u32,
659 character: u32,
660 },
661
662 GotoDefinition {
664 request_id: u64,
665 uri: Uri,
666 line: u32,
667 character: u32,
668 },
669
670 Rename {
672 request_id: u64,
673 uri: Uri,
674 line: u32,
675 character: u32,
676 new_name: String,
677 },
678
679 Hover {
681 request_id: u64,
682 uri: Uri,
683 line: u32,
684 character: u32,
685 },
686
687 References {
689 request_id: u64,
690 uri: Uri,
691 line: u32,
692 character: u32,
693 },
694
695 SignatureHelp {
697 request_id: u64,
698 uri: Uri,
699 line: u32,
700 character: u32,
701 },
702
703 CodeActions {
705 request_id: u64,
706 uri: Uri,
707 start_line: u32,
708 start_char: u32,
709 end_line: u32,
710 end_char: u32,
711 diagnostics: Vec<lsp_types::Diagnostic>,
712 },
713
714 DocumentDiagnostic {
716 request_id: u64,
717 uri: Uri,
718 previous_result_id: Option<String>,
720 },
721
722 InlayHints {
724 request_id: u64,
725 uri: Uri,
726 start_line: u32,
728 start_char: u32,
729 end_line: u32,
730 end_char: u32,
731 },
732
733 FoldingRange { request_id: u64, uri: Uri },
735
736 SemanticTokensFull { request_id: u64, uri: Uri },
738
739 SemanticTokensFullDelta {
741 request_id: u64,
742 uri: Uri,
743 previous_result_id: String,
744 },
745
746 SemanticTokensRange {
748 request_id: u64,
749 uri: Uri,
750 range: lsp_types::Range,
751 },
752
753 ExecuteCommand {
755 command: String,
756 arguments: Option<Vec<Value>>,
757 },
758
759 CodeActionResolve {
761 request_id: u64,
762 action: Box<lsp_types::CodeAction>,
763 },
764
765 CompletionResolve {
767 request_id: u64,
768 item: Box<lsp_types::CompletionItem>,
769 },
770
771 DocumentFormatting {
773 request_id: u64,
774 uri: Uri,
775 tab_size: u32,
776 insert_spaces: bool,
777 },
778
779 DocumentRangeFormatting {
781 request_id: u64,
782 uri: Uri,
783 start_line: u32,
784 start_char: u32,
785 end_line: u32,
786 end_char: u32,
787 tab_size: u32,
788 insert_spaces: bool,
789 },
790
791 PrepareRename {
793 request_id: u64,
794 uri: Uri,
795 line: u32,
796 character: u32,
797 },
798
799 CancelRequest {
801 request_id: u64,
803 },
804
805 PluginRequest {
807 request_id: u64,
808 method: String,
809 params: Option<Value>,
810 },
811
812 Shutdown,
814}
815
816#[derive(Clone)]
823struct LspState {
824 stdin: Arc<tokio::sync::Mutex<ChildStdin>>,
826
827 next_id: Arc<AtomicI64>,
829
830 capabilities: Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
832
833 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
835
836 pending_opens: Arc<std::sync::Mutex<HashMap<PathBuf, Instant>>>,
839
840 initialized: Arc<AtomicBool>,
842
843 async_tx: std_mpsc::Sender<AsyncMessage>,
845
846 language: Arc<String>,
848
849 server_name: Arc<String>,
851
852 active_requests: Arc<std::sync::Mutex<HashMap<u64, i64>>>,
855
856 language_id_overrides: Arc<HashMap<String, String>>,
858}
859
860#[allow(clippy::let_underscore_must_use)]
866impl LspState {
867 async fn replay_pending_commands(&self, commands: Vec<LspCommand>, pending: &PendingRequests) {
869 if commands.is_empty() {
870 return;
871 }
872 tracing::info!(
873 "Replaying {} pending commands after initialization",
874 commands.len()
875 );
876 for cmd in commands {
877 match cmd {
878 LspCommand::DidOpen {
879 uri,
880 text,
881 language_id,
882 } => {
883 tracing::info!("Replaying DidOpen for {}", uri.as_str());
884 let _ = self
885 .handle_did_open_sequential(uri, text, language_id, pending)
886 .await;
887 }
888 LspCommand::DidChange {
889 uri,
890 content_changes,
891 } => {
892 tracing::info!("Replaying DidChange for {}", uri.as_str());
893 let _ = self
894 .handle_did_change_sequential(uri, content_changes, pending)
895 .await;
896 }
897 LspCommand::DidClose { uri } => {
898 tracing::info!("Replaying DidClose for {}", uri.as_str());
899 let _ = self.handle_did_close(uri).await;
900 }
901 LspCommand::DidSave { uri, text } => {
902 tracing::info!("Replaying DidSave for {}", uri.as_str());
903 let _ = self.handle_did_save(uri, text).await;
904 }
905 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
906 tracing::info!(
907 "Replaying DidChangeWorkspaceFolders: +{} -{}",
908 added.len(),
909 removed.len()
910 );
911 let _ = self
912 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
913 lsp_types::DidChangeWorkspaceFoldersParams {
914 event: lsp_types::WorkspaceFoldersChangeEvent { added, removed },
915 },
916 )
917 .await;
918 }
919 LspCommand::SemanticTokensFull { request_id, uri } => {
920 tracing::info!("Replaying semantic tokens request for {}", uri.as_str());
921 let s = self.clone();
922 let p = pending.clone();
923 tokio::spawn(async move {
924 let _ = s.handle_semantic_tokens_full(request_id, uri, &p).await;
925 });
926 }
927 LspCommand::SemanticTokensFullDelta {
928 request_id,
929 uri,
930 previous_result_id,
931 } => {
932 tracing::info!(
933 "Replaying semantic tokens delta request for {}",
934 uri.as_str()
935 );
936 let s = self.clone();
937 let p = pending.clone();
938 tokio::spawn(async move {
939 let _ = s
940 .handle_semantic_tokens_full_delta(
941 request_id,
942 uri,
943 previous_result_id,
944 &p,
945 )
946 .await;
947 });
948 }
949 LspCommand::SemanticTokensRange {
950 request_id,
951 uri,
952 range,
953 } => {
954 tracing::info!(
955 "Replaying semantic tokens range request for {}",
956 uri.as_str()
957 );
958 let s = self.clone();
959 let p = pending.clone();
960 tokio::spawn(async move {
961 let _ = s
962 .handle_semantic_tokens_range(request_id, uri, range, &p)
963 .await;
964 });
965 }
966 LspCommand::FoldingRange { request_id, uri } => {
967 tracing::info!("Replaying folding range request for {}", uri.as_str());
968 let s = self.clone();
969 let p = pending.clone();
970 tokio::spawn(async move {
971 let _ = s.handle_folding_ranges(request_id, uri, &p).await;
972 });
973 }
974 _ => {}
975 }
976 }
977 }
978
979 async fn write_message<T: Serialize>(&self, message: &T) -> Result<(), String> {
981 let json =
982 serde_json::to_string(message).map_err(|e| format!("Serialization error: {}", e))?;
983
984 let content = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
985
986 tracing::trace!("Writing LSP message to stdin ({} bytes)", content.len());
987
988 let mut stdin = self.stdin.lock().await;
989 stdin
990 .write_all(content.as_bytes())
991 .await
992 .map_err(|e| format!("Failed to write to stdin: {}", e))?;
993
994 stdin
995 .flush()
996 .await
997 .map_err(|e| format!("Failed to flush stdin: {}", e))?;
998
999 tracing::trace!("Successfully sent LSP message");
1000
1001 Ok(())
1002 }
1003
1004 async fn send_notification<N>(&self, params: N::Params) -> Result<(), String>
1006 where
1007 N: Notification,
1008 {
1009 let notification = JsonRpcNotification {
1010 jsonrpc: "2.0".to_string(),
1011 method: N::METHOD.to_string(),
1012 params: Some(
1013 serde_json::to_value(params)
1014 .map_err(|e| format!("Failed to serialize params: {}", e))?,
1015 ),
1016 };
1017
1018 self.write_message(¬ification).await
1019 }
1020
1021 async fn send_request_sequential<P: Serialize, R: for<'de> Deserialize<'de>>(
1023 &self,
1024 method: &str,
1025 params: Option<P>,
1026 pending: &PendingRequests,
1027 ) -> Result<R, String> {
1028 self.send_request_with_timeout(
1029 method,
1030 params,
1031 pending,
1032 None,
1033 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
1034 )
1035 .await
1036 }
1037
1038 async fn send_request_sequential_tracked<P: Serialize, R: for<'de> Deserialize<'de>>(
1040 &self,
1041 method: &str,
1042 params: Option<P>,
1043 pending: &PendingRequests,
1044 editor_request_id: Option<u64>,
1045 ) -> Result<R, String> {
1046 self.send_request_with_timeout(
1047 method,
1048 params,
1049 pending,
1050 editor_request_id,
1051 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
1052 )
1053 .await
1054 }
1055
1056 async fn send_request_with_timeout<P: Serialize, R: for<'de> Deserialize<'de>>(
1062 &self,
1063 method: &str,
1064 params: Option<P>,
1065 pending: &PendingRequests,
1066 editor_request_id: Option<u64>,
1067 timeout: Duration,
1068 ) -> Result<R, String> {
1069 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
1070
1071 if let Some(editor_id) = editor_request_id {
1073 self.active_requests.lock().unwrap().insert(editor_id, id);
1074 tracing::trace!("Tracking request: editor_id={}, lsp_id={}", editor_id, id);
1075 }
1076
1077 let params_value = params
1078 .map(|p| serde_json::to_value(p))
1079 .transpose()
1080 .map_err(|e| format!("Failed to serialize params: {}", e))?;
1081 let request = JsonRpcRequest {
1082 jsonrpc: "2.0".to_string(),
1083 id,
1084 method: method.to_string(),
1085 params: params_value,
1086 };
1087
1088 let (tx, rx) = oneshot::channel();
1089 pending.lock().unwrap().insert(id, tx);
1090
1091 if let Err(e) = self.write_message(&request).await {
1092 pending.lock().unwrap().remove(&id);
1093 if let Some(editor_id) = editor_request_id {
1094 self.active_requests.lock().unwrap().remove(&editor_id);
1095 }
1096 return Err(e);
1097 }
1098
1099 tracing::trace!(
1100 "Sent LSP request id={} method={}, waiting up to {:?} for response",
1101 id,
1102 method,
1103 timeout
1104 );
1105
1106 let response_result = match tokio::time::timeout(timeout, rx).await {
1107 Ok(Ok(inner)) => inner,
1108 Ok(Err(_)) => Err("Response channel closed".to_string()),
1109 Err(_) => {
1110 pending.lock().unwrap().remove(&id);
1112 tracing::warn!(
1113 "LSP request '{}' (lsp_id={}) on '{}' ({}) timed out after {:?}; sending $/cancelRequest",
1114 method,
1115 id,
1116 self.server_name.as_str(),
1117 self.language.as_str(),
1118 timeout
1119 );
1120 let _ = self.send_cancel_request(id).await;
1121 Err(format!(
1122 "Request '{}' timed out after {:?}",
1123 method, timeout
1124 ))
1125 }
1126 };
1127
1128 if let Some(editor_id) = editor_request_id {
1129 self.active_requests.lock().unwrap().remove(&editor_id);
1130 tracing::trace!("Completed request: editor_id={}, lsp_id={}", editor_id, id);
1131 }
1132
1133 let result = response_result?;
1134 serde_json::from_value(result).map_err(|e| format!("Failed to deserialize response: {}", e))
1135 }
1136
1137 async fn handle_initialize_sequential(
1139 &self,
1140 root_uri: Option<Uri>,
1141 initialization_options: Option<Value>,
1142 pending: &PendingRequests,
1143 ) -> Result<InitializeResult, String> {
1144 tracing::info!(
1145 "Initializing async LSP server with root_uri: {:?}, initialization_options: {:?}",
1146 root_uri,
1147 initialization_options
1148 );
1149
1150 let workspace_folders = root_uri.as_ref().map(|uri| {
1151 vec![WorkspaceFolder {
1152 uri: uri.clone(),
1153 name: uri
1154 .path()
1155 .as_str()
1156 .split('/')
1157 .next_back()
1158 .unwrap_or("workspace")
1159 .to_string(),
1160 }]
1161 });
1162
1163 #[allow(deprecated)]
1164 let params = InitializeParams {
1165 process_id: Some(std::process::id()),
1166 capabilities: create_client_capabilities(),
1167 workspace_folders,
1168 initialization_options,
1169 root_uri: root_uri.clone(),
1172 ..Default::default()
1173 };
1174
1175 let result: InitializeResult = self
1176 .send_request_sequential(Initialize::METHOD, Some(params), pending)
1177 .await?;
1178
1179 tracing::info!(
1180 "LSP initialize result: position_encoding={:?}",
1181 result.capabilities.position_encoding
1182 );
1183 *self.capabilities.lock().unwrap() = Some(result.capabilities.clone());
1184
1185 self.send_notification::<Initialized>(InitializedParams {})
1187 .await?;
1188
1189 self.initialized.store(true, Ordering::SeqCst);
1190
1191 let capabilities = extract_capability_summary(&result.capabilities);
1192
1193 let _ = self.async_tx.send(AsyncMessage::LspInitialized {
1195 language: (*self.language).clone(),
1196 server_name: (*self.server_name).clone(),
1197 capabilities,
1198 });
1199
1200 let _ = self.async_tx.send(AsyncMessage::LspStatusUpdate {
1202 language: (*self.language).clone(),
1203 server_name: (*self.server_name).clone(),
1204 status: LspServerStatus::Running,
1205 message: None,
1206 });
1207
1208 tracing::info!("Async LSP server initialized successfully");
1209
1210 Ok(result)
1211 }
1212
1213 async fn handle_did_open_sequential(
1215 &self,
1216 uri: Uri,
1217 text: String,
1218 language_id: String,
1219 _pending: &PendingRequests,
1220 ) -> Result<(), String> {
1221 let path = PathBuf::from(uri.path().as_str());
1222
1223 if should_skip_did_open(&self.document_versions, &path, self.language.as_str(), &uri) {
1224 return Ok(());
1225 }
1226
1227 tracing::trace!("LSP: did_open for {}", uri.as_str());
1228
1229 let lsp_language_id = path
1232 .extension()
1233 .and_then(|e| e.to_str())
1234 .and_then(|ext| self.language_id_overrides.get(ext))
1235 .cloned()
1236 .unwrap_or(language_id);
1237
1238 let params = DidOpenTextDocumentParams {
1239 text_document: TextDocumentItem {
1240 uri: uri.clone(),
1241 language_id: lsp_language_id,
1242 version: 0,
1243 text,
1244 },
1245 };
1246
1247 self.document_versions
1248 .lock()
1249 .unwrap()
1250 .insert(path.clone(), 0);
1251
1252 self.pending_opens
1254 .lock()
1255 .unwrap()
1256 .insert(path, Instant::now());
1257
1258 self.send_notification::<DidOpenTextDocument>(params).await
1259 }
1260
1261 async fn handle_did_change_sequential(
1263 &self,
1264 uri: Uri,
1265 content_changes: Vec<TextDocumentContentChangeEvent>,
1266 _pending: &PendingRequests,
1267 ) -> Result<(), String> {
1268 tracing::trace!("LSP: did_change for {}", uri.as_str());
1269
1270 let path = PathBuf::from(uri.path().as_str());
1271
1272 if !self.document_versions.lock().unwrap().contains_key(&path) {
1275 tracing::debug!(
1276 "LSP ({}): skipping didChange - document not yet opened",
1277 self.language
1278 );
1279 return Ok(());
1280 }
1281
1282 let opened_at = self.pending_opens.lock().unwrap().get(&path).copied();
1286 if let Some(opened_at) = opened_at {
1287 let elapsed = opened_at.elapsed();
1288 let grace_period = std::time::Duration::from_millis(DID_OPEN_GRACE_PERIOD_MS);
1289 if elapsed < grace_period {
1290 let wait_time = grace_period - elapsed;
1291 tracing::debug!(
1292 "LSP ({}): waiting {:?} for didOpen grace period before didChange",
1293 self.language,
1294 wait_time
1295 );
1296 tokio::time::sleep(wait_time).await;
1297 }
1298 self.pending_opens.lock().unwrap().remove(&path);
1300 }
1301
1302 let new_version = {
1303 let mut versions = self.document_versions.lock().unwrap();
1304 let version = versions.entry(path).or_insert(0);
1305 *version += 1;
1306 *version
1307 };
1308
1309 let params = DidChangeTextDocumentParams {
1310 text_document: VersionedTextDocumentIdentifier {
1311 uri: uri.clone(),
1312 version: new_version as i32,
1313 },
1314 content_changes,
1315 };
1316
1317 self.send_notification::<DidChangeTextDocument>(params)
1318 .await
1319 }
1320
1321 async fn handle_did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
1323 tracing::trace!("LSP: did_save for {}", uri.as_str());
1324
1325 let params = DidSaveTextDocumentParams {
1326 text_document: TextDocumentIdentifier { uri },
1327 text,
1328 };
1329
1330 self.send_notification::<DidSaveTextDocument>(params).await
1331 }
1332
1333 async fn handle_did_close(&self, uri: Uri) -> Result<(), String> {
1335 let path = PathBuf::from(uri.path().as_str());
1336
1337 if self
1339 .document_versions
1340 .lock()
1341 .unwrap()
1342 .remove(&path)
1343 .is_some()
1344 {
1345 tracing::info!("LSP ({}): didClose for {}", self.language, uri.as_str());
1346 } else {
1347 tracing::debug!(
1348 "LSP ({}): didClose for {} but document was not tracked",
1349 self.language,
1350 uri.as_str()
1351 );
1352 }
1353
1354 self.pending_opens.lock().unwrap().remove(&path);
1356
1357 let params = DidCloseTextDocumentParams {
1358 text_document: TextDocumentIdentifier { uri },
1359 };
1360
1361 self.send_notification::<DidCloseTextDocument>(params).await
1362 }
1363
1364 async fn handle_completion(
1366 &self,
1367 request_id: u64,
1368 uri: Uri,
1369 line: u32,
1370 character: u32,
1371 pending: &PendingRequests,
1372 ) -> Result<(), String> {
1373 use lsp_types::CompletionParams;
1374
1375 tracing::trace!(
1376 "LSP: completion request at {}:{}:{}",
1377 uri.as_str(),
1378 line,
1379 character
1380 );
1381
1382 let params = CompletionParams {
1383 text_document_position: TextDocumentPositionParams {
1384 text_document: TextDocumentIdentifier { uri },
1385 position: Position { line, character },
1386 },
1387 work_done_progress_params: WorkDoneProgressParams::default(),
1388 partial_result_params: PartialResultParams::default(),
1389 context: None,
1390 };
1391
1392 match self
1394 .send_request_sequential_tracked::<_, Value>(
1395 "textDocument/completion",
1396 Some(params),
1397 pending,
1398 Some(request_id),
1399 )
1400 .await
1401 {
1402 Ok(result) => {
1403 let items = if let Ok(list) =
1405 serde_json::from_value::<lsp_types::CompletionList>(result.clone())
1406 {
1407 list.items
1408 } else {
1409 serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result)
1410 .unwrap_or_default()
1411 };
1412
1413 let _ = self
1415 .async_tx
1416 .send(AsyncMessage::LspCompletion { request_id, items });
1417 Ok(())
1418 }
1419 Err(e) => {
1420 tracing::debug!("Completion request failed: {}", e);
1421 let _ = self.async_tx.send(AsyncMessage::LspCompletion {
1423 request_id,
1424 items: vec![],
1425 });
1426 Err(e)
1427 }
1428 }
1429 }
1430
1431 async fn handle_goto_definition(
1433 &self,
1434 request_id: u64,
1435 uri: Uri,
1436 line: u32,
1437 character: u32,
1438 pending: &PendingRequests,
1439 ) -> Result<(), String> {
1440 use lsp_types::GotoDefinitionParams;
1441
1442 tracing::trace!(
1443 "LSP: go-to-definition request at {}:{}:{}",
1444 uri.as_str(),
1445 line,
1446 character
1447 );
1448
1449 let params = GotoDefinitionParams {
1450 text_document_position_params: TextDocumentPositionParams {
1451 text_document: TextDocumentIdentifier { uri },
1452 position: Position { line, character },
1453 },
1454 work_done_progress_params: WorkDoneProgressParams::default(),
1455 partial_result_params: PartialResultParams::default(),
1456 };
1457
1458 match self
1460 .send_request_sequential::<_, Value>("textDocument/definition", Some(params), pending)
1461 .await
1462 {
1463 Ok(result) => {
1464 let locations = if let Ok(loc) =
1466 serde_json::from_value::<lsp_types::Location>(result.clone())
1467 {
1468 vec![loc]
1469 } else if let Ok(locs) =
1470 serde_json::from_value::<Vec<lsp_types::Location>>(result.clone())
1471 {
1472 locs
1473 } else if let Ok(links) =
1474 serde_json::from_value::<Vec<lsp_types::LocationLink>>(result)
1475 {
1476 links
1478 .into_iter()
1479 .map(|link| lsp_types::Location {
1480 uri: link.target_uri,
1481 range: link.target_selection_range,
1482 })
1483 .collect()
1484 } else {
1485 vec![]
1486 };
1487
1488 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1490 request_id,
1491 locations,
1492 });
1493 Ok(())
1494 }
1495 Err(e) => {
1496 tracing::debug!("Go-to-definition request failed: {}", e);
1497 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1499 request_id,
1500 locations: vec![],
1501 });
1502 Err(e)
1503 }
1504 }
1505 }
1506
1507 async fn handle_rename(
1509 &self,
1510 request_id: u64,
1511 uri: Uri,
1512 line: u32,
1513 character: u32,
1514 new_name: String,
1515 pending: &PendingRequests,
1516 ) -> Result<(), String> {
1517 use lsp_types::RenameParams;
1518
1519 tracing::trace!(
1520 "LSP: rename request at {}:{}:{} to '{}'",
1521 uri.as_str(),
1522 line,
1523 character,
1524 new_name
1525 );
1526
1527 let params = RenameParams {
1528 text_document_position: TextDocumentPositionParams {
1529 text_document: TextDocumentIdentifier { uri },
1530 position: Position { line, character },
1531 },
1532 new_name,
1533 work_done_progress_params: WorkDoneProgressParams::default(),
1534 };
1535
1536 match self
1538 .send_request_sequential::<_, Value>("textDocument/rename", Some(params), pending)
1539 .await
1540 {
1541 Ok(result) => {
1542 match serde_json::from_value::<lsp_types::WorkspaceEdit>(result) {
1544 Ok(workspace_edit) => {
1545 let _ = self.async_tx.send(AsyncMessage::LspRename {
1547 request_id,
1548 result: Ok(workspace_edit),
1549 });
1550 Ok(())
1551 }
1552 Err(e) => {
1553 tracing::error!("Failed to parse rename response: {}", e);
1554 let _ = self.async_tx.send(AsyncMessage::LspRename {
1555 request_id,
1556 result: Err(format!("Failed to parse rename response: {}", e)),
1557 });
1558 Err(format!("Failed to parse rename response: {}", e))
1559 }
1560 }
1561 }
1562 Err(e) => {
1563 tracing::debug!("Rename request failed: {}", e);
1564 let _ = self.async_tx.send(AsyncMessage::LspRename {
1566 request_id,
1567 result: Err(e.clone()),
1568 });
1569 Err(e)
1570 }
1571 }
1572 }
1573
1574 async fn handle_hover(
1576 &self,
1577 request_id: u64,
1578 uri: Uri,
1579 line: u32,
1580 character: u32,
1581 pending: &PendingRequests,
1582 ) -> Result<(), String> {
1583 use lsp_types::HoverParams;
1584
1585 tracing::trace!(
1586 "LSP: hover request at {}:{}:{}",
1587 uri.as_str(),
1588 line,
1589 character
1590 );
1591
1592 let params = HoverParams {
1593 text_document_position_params: TextDocumentPositionParams {
1594 text_document: TextDocumentIdentifier { uri },
1595 position: Position { line, character },
1596 },
1597 work_done_progress_params: WorkDoneProgressParams::default(),
1598 };
1599
1600 match self
1602 .send_request_sequential::<_, Value>("textDocument/hover", Some(params), pending)
1603 .await
1604 {
1605 Ok(result) => {
1606 tracing::debug!("Raw LSP hover response: {:?}", result);
1607 let (contents, is_markdown, range) = if result.is_null() {
1609 (String::new(), false, None)
1611 } else {
1612 match serde_json::from_value::<lsp_types::Hover>(result) {
1613 Ok(hover) => {
1614 let (contents, is_markdown) =
1616 Self::extract_hover_contents(&hover.contents);
1617 let range = hover.range.map(|r| {
1619 (
1620 (r.start.line, r.start.character),
1621 (r.end.line, r.end.character),
1622 )
1623 });
1624 (contents, is_markdown, range)
1625 }
1626 Err(e) => {
1627 tracing::error!("Failed to parse hover response: {}", e);
1628 (String::new(), false, None)
1629 }
1630 }
1631 };
1632
1633 let _ = self.async_tx.send(AsyncMessage::LspHover {
1635 request_id,
1636 contents,
1637 is_markdown,
1638 range,
1639 });
1640 Ok(())
1641 }
1642 Err(e) => {
1643 tracing::debug!("Hover request failed: {}", e);
1644 let _ = self.async_tx.send(AsyncMessage::LspHover {
1646 request_id,
1647 contents: String::new(),
1648 is_markdown: false,
1649 range: None,
1650 });
1651 Err(e)
1652 }
1653 }
1654 }
1655
1656 fn extract_hover_contents(contents: &lsp_types::HoverContents) -> (String, bool) {
1659 use lsp_types::{HoverContents, MarkedString, MarkupContent, MarkupKind};
1660
1661 match contents {
1662 HoverContents::Scalar(marked) => match marked {
1663 MarkedString::String(s) => (s.clone(), false),
1664 MarkedString::LanguageString(ls) => {
1665 (format!("```{}\n{}\n```", ls.language, ls.value), true)
1667 }
1668 },
1669 HoverContents::Array(arr) => {
1670 let content = arr
1672 .iter()
1673 .map(|marked| match marked {
1674 MarkedString::String(s) => s.clone(),
1675 MarkedString::LanguageString(ls) => {
1676 format!("```{}\n{}\n```", ls.language, ls.value)
1677 }
1678 })
1679 .collect::<Vec<_>>()
1680 .join("\n\n");
1681 (content, true)
1682 }
1683 HoverContents::Markup(MarkupContent { kind, value }) => {
1684 let is_markdown = matches!(kind, MarkupKind::Markdown);
1686 (value.clone(), is_markdown)
1687 }
1688 }
1689 }
1690
1691 async fn handle_references(
1693 &self,
1694 request_id: u64,
1695 uri: Uri,
1696 line: u32,
1697 character: u32,
1698 pending: &PendingRequests,
1699 ) -> Result<(), String> {
1700 use lsp_types::{ReferenceContext, ReferenceParams};
1701
1702 tracing::trace!(
1703 "LSP: find references request at {}:{}:{}",
1704 uri.as_str(),
1705 line,
1706 character
1707 );
1708
1709 let params = ReferenceParams {
1710 text_document_position: lsp_types::TextDocumentPositionParams {
1711 text_document: TextDocumentIdentifier { uri },
1712 position: Position { line, character },
1713 },
1714 work_done_progress_params: WorkDoneProgressParams::default(),
1715 partial_result_params: PartialResultParams::default(),
1716 context: ReferenceContext {
1717 include_declaration: true,
1718 },
1719 };
1720
1721 match self
1723 .send_request_sequential::<_, Value>("textDocument/references", Some(params), pending)
1724 .await
1725 {
1726 Ok(result) => {
1727 let locations = if result.is_null() {
1729 Vec::new()
1730 } else {
1731 serde_json::from_value::<Vec<lsp_types::Location>>(result).unwrap_or_default()
1732 };
1733
1734 tracing::trace!("LSP: found {} references", locations.len());
1735
1736 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1738 request_id,
1739 locations,
1740 });
1741 Ok(())
1742 }
1743 Err(e) => {
1744 tracing::debug!("Find references request failed: {}", e);
1745 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1747 request_id,
1748 locations: Vec::new(),
1749 });
1750 Err(e)
1751 }
1752 }
1753 }
1754
1755 async fn handle_signature_help(
1757 &self,
1758 request_id: u64,
1759 uri: Uri,
1760 line: u32,
1761 character: u32,
1762 pending: &PendingRequests,
1763 ) -> Result<(), String> {
1764 use lsp_types::SignatureHelpParams;
1765
1766 tracing::trace!(
1767 "LSP: signature help request at {}:{}:{}",
1768 uri.as_str(),
1769 line,
1770 character
1771 );
1772
1773 let params = SignatureHelpParams {
1774 text_document_position_params: TextDocumentPositionParams {
1775 text_document: TextDocumentIdentifier { uri },
1776 position: Position { line, character },
1777 },
1778 work_done_progress_params: WorkDoneProgressParams::default(),
1779 context: None, };
1781
1782 match self
1784 .send_request_sequential::<_, Value>(
1785 "textDocument/signatureHelp",
1786 Some(params),
1787 pending,
1788 )
1789 .await
1790 {
1791 Ok(result) => {
1792 let signature_help = if result.is_null() {
1794 None
1795 } else {
1796 serde_json::from_value::<lsp_types::SignatureHelp>(result).ok()
1797 };
1798
1799 tracing::trace!(
1800 "LSP: signature help received: {} signatures",
1801 signature_help
1802 .as_ref()
1803 .map(|h| h.signatures.len())
1804 .unwrap_or(0)
1805 );
1806
1807 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1809 request_id,
1810 signature_help,
1811 });
1812 Ok(())
1813 }
1814 Err(e) => {
1815 tracing::debug!("Signature help request failed: {}", e);
1816 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1818 request_id,
1819 signature_help: None,
1820 });
1821 Err(e)
1822 }
1823 }
1824 }
1825
1826 #[allow(clippy::too_many_arguments)]
1828 async fn handle_code_actions(
1829 &self,
1830 request_id: u64,
1831 uri: Uri,
1832 start_line: u32,
1833 start_char: u32,
1834 end_line: u32,
1835 end_char: u32,
1836 diagnostics: Vec<lsp_types::Diagnostic>,
1837 pending: &PendingRequests,
1838 ) -> Result<(), String> {
1839 use lsp_types::{CodeActionContext, CodeActionParams};
1840
1841 tracing::trace!(
1842 "LSP: code actions request at {}:{}:{}-{}:{}",
1843 uri.as_str(),
1844 start_line,
1845 start_char,
1846 end_line,
1847 end_char
1848 );
1849
1850 let params = CodeActionParams {
1851 text_document: TextDocumentIdentifier { uri },
1852 range: Range {
1853 start: Position {
1854 line: start_line,
1855 character: start_char,
1856 },
1857 end: Position {
1858 line: end_line,
1859 character: end_char,
1860 },
1861 },
1862 context: CodeActionContext {
1863 diagnostics,
1864 only: None,
1865 trigger_kind: None,
1866 },
1867 work_done_progress_params: WorkDoneProgressParams::default(),
1868 partial_result_params: PartialResultParams::default(),
1869 };
1870
1871 match self
1873 .send_request_sequential::<_, Value>("textDocument/codeAction", Some(params), pending)
1874 .await
1875 {
1876 Ok(result) => {
1877 let actions = if result.is_null() {
1879 Vec::new()
1880 } else {
1881 serde_json::from_value::<Vec<lsp_types::CodeActionOrCommand>>(result)
1882 .unwrap_or_default()
1883 };
1884
1885 tracing::trace!("LSP: received {} code actions", actions.len());
1886
1887 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1889 request_id,
1890 actions,
1891 });
1892 Ok(())
1893 }
1894 Err(e) => {
1895 tracing::debug!("Code actions request failed: {}", e);
1896 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1898 request_id,
1899 actions: Vec::new(),
1900 });
1901 Err(e)
1902 }
1903 }
1904 }
1905
1906 async fn handle_execute_command(
1908 &self,
1909 command: String,
1910 arguments: Option<Vec<Value>>,
1911 pending: &PendingRequests,
1912 ) -> Result<(), String> {
1913 let params = lsp_types::ExecuteCommandParams {
1914 command: command.clone(),
1915 arguments: arguments.unwrap_or_default(),
1916 work_done_progress_params: lsp_types::WorkDoneProgressParams::default(),
1917 };
1918
1919 match self
1920 .send_request_sequential::<_, Value>("workspace/executeCommand", Some(params), pending)
1921 .await
1922 {
1923 Ok(_) => {
1924 tracing::info!("ExecuteCommand '{}' completed", command);
1925 Ok(())
1926 }
1927 Err(e) => {
1928 tracing::debug!("ExecuteCommand '{}' failed: {}", command, e);
1929 Err(e)
1930 }
1931 }
1932 }
1933
1934 async fn handle_code_action_resolve(
1936 &self,
1937 request_id: u64,
1938 action: lsp_types::CodeAction,
1939 pending: &PendingRequests,
1940 ) -> Result<(), String> {
1941 match self
1942 .send_request_sequential::<_, Value>("codeAction/resolve", Some(action), pending)
1943 .await
1944 {
1945 Ok(result) => {
1946 let resolved = serde_json::from_value::<lsp_types::CodeAction>(result)
1947 .map_err(|e| format!("Failed to parse codeAction/resolve response: {}", e));
1948 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
1949 request_id,
1950 action: resolved,
1951 });
1952 Ok(())
1953 }
1954 Err(e) => {
1955 tracing::debug!("codeAction/resolve failed: {}", e);
1956 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
1957 request_id,
1958 action: Err(e.clone()),
1959 });
1960 Err(e)
1961 }
1962 }
1963 }
1964
1965 async fn handle_completion_resolve(
1967 &self,
1968 request_id: u64,
1969 item: lsp_types::CompletionItem,
1970 pending: &PendingRequests,
1971 ) -> Result<(), String> {
1972 match self
1973 .send_request_sequential::<_, Value>("completionItem/resolve", Some(item), pending)
1974 .await
1975 {
1976 Ok(result) => {
1977 let resolved = serde_json::from_value::<lsp_types::CompletionItem>(result)
1978 .map_err(|e| format!("Failed to parse completionItem/resolve response: {}", e));
1979 let _ = self.async_tx.send(AsyncMessage::LspCompletionResolved {
1980 request_id,
1981 item: resolved,
1982 });
1983 Ok(())
1984 }
1985 Err(e) => {
1986 tracing::debug!("completionItem/resolve failed: {}", e);
1987 Err(e)
1988 }
1989 }
1990 }
1991
1992 async fn handle_document_formatting(
1994 &self,
1995 request_id: u64,
1996 uri: Uri,
1997 tab_size: u32,
1998 insert_spaces: bool,
1999 pending: &PendingRequests,
2000 ) -> Result<(), String> {
2001 use lsp_types::{DocumentFormattingParams, FormattingOptions};
2002
2003 let params = DocumentFormattingParams {
2004 text_document: TextDocumentIdentifier { uri: uri.clone() },
2005 options: FormattingOptions {
2006 tab_size,
2007 insert_spaces,
2008 ..Default::default()
2009 },
2010 work_done_progress_params: WorkDoneProgressParams::default(),
2011 };
2012
2013 match self
2014 .send_request_sequential::<_, Value>("textDocument/formatting", Some(params), pending)
2015 .await
2016 {
2017 Ok(result) => {
2018 let edits = if result.is_null() {
2019 Vec::new()
2020 } else {
2021 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
2022 };
2023 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
2024 request_id,
2025 uri: uri.as_str().to_string(),
2026 edits,
2027 });
2028 Ok(())
2029 }
2030 Err(e) => {
2031 tracing::debug!("textDocument/formatting failed: {}", e);
2032 Err(e)
2033 }
2034 }
2035 }
2036
2037 #[allow(clippy::too_many_arguments)]
2039 async fn handle_document_range_formatting(
2040 &self,
2041 request_id: u64,
2042 uri: Uri,
2043 start_line: u32,
2044 start_char: u32,
2045 end_line: u32,
2046 end_char: u32,
2047 tab_size: u32,
2048 insert_spaces: bool,
2049 pending: &PendingRequests,
2050 ) -> Result<(), String> {
2051 use lsp_types::{DocumentRangeFormattingParams, FormattingOptions};
2052
2053 let params = DocumentRangeFormattingParams {
2054 text_document: TextDocumentIdentifier { uri: uri.clone() },
2055 range: Range {
2056 start: Position::new(start_line, start_char),
2057 end: Position::new(end_line, end_char),
2058 },
2059 options: FormattingOptions {
2060 tab_size,
2061 insert_spaces,
2062 ..Default::default()
2063 },
2064 work_done_progress_params: WorkDoneProgressParams::default(),
2065 };
2066
2067 match self
2068 .send_request_sequential::<_, Value>(
2069 "textDocument/rangeFormatting",
2070 Some(params),
2071 pending,
2072 )
2073 .await
2074 {
2075 Ok(result) => {
2076 let edits = if result.is_null() {
2077 Vec::new()
2078 } else {
2079 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
2080 };
2081 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
2082 request_id,
2083 uri: uri.as_str().to_string(),
2084 edits,
2085 });
2086 Ok(())
2087 }
2088 Err(e) => {
2089 tracing::debug!("textDocument/rangeFormatting failed: {}", e);
2090 Err(e)
2091 }
2092 }
2093 }
2094
2095 async fn handle_prepare_rename(
2097 &self,
2098 request_id: u64,
2099 uri: Uri,
2100 line: u32,
2101 character: u32,
2102 pending: &PendingRequests,
2103 ) -> Result<(), String> {
2104 let params = TextDocumentPositionParams {
2105 text_document: TextDocumentIdentifier { uri },
2106 position: Position::new(line, character),
2107 };
2108
2109 match self
2110 .send_request_sequential::<_, Value>(
2111 "textDocument/prepareRename",
2112 Some(params),
2113 pending,
2114 )
2115 .await
2116 {
2117 Ok(result) => {
2118 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2119 request_id,
2120 result: Ok(result),
2121 });
2122 Ok(())
2123 }
2124 Err(e) => {
2125 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2126 request_id,
2127 result: Err(e.clone()),
2128 });
2129 Err(e)
2130 }
2131 }
2132 }
2133
2134 async fn handle_document_diagnostic(
2135 &self,
2136 request_id: u64,
2137 uri: Uri,
2138 previous_result_id: Option<String>,
2139 pending: &PendingRequests,
2140 ) -> Result<(), String> {
2141 use lsp_types::DocumentDiagnosticParams;
2142
2143 let supports_pull = self
2149 .capabilities
2150 .lock()
2151 .unwrap()
2152 .as_ref()
2153 .and_then(|c| c.diagnostic_provider.as_ref())
2154 .is_some();
2155 if !supports_pull {
2156 tracing::trace!(
2157 "LSP: server does not support pull diagnostics, skipping request for {}",
2158 uri.as_str()
2159 );
2160 return Ok(());
2161 }
2162
2163 tracing::trace!(
2164 "LSP: document diagnostic request for {} (previous_result_id: {:?})",
2165 uri.as_str(),
2166 previous_result_id
2167 );
2168
2169 let params = DocumentDiagnosticParams {
2170 text_document: TextDocumentIdentifier { uri: uri.clone() },
2171 identifier: None,
2172 previous_result_id,
2173 work_done_progress_params: WorkDoneProgressParams::default(),
2174 partial_result_params: PartialResultParams::default(),
2175 };
2176
2177 match self
2179 .send_request_sequential::<_, Value>("textDocument/diagnostic", Some(params), pending)
2180 .await
2181 {
2182 Ok(result) => {
2183 let uri_string = uri.as_str().to_string();
2186
2187 if let Ok(full_report) = serde_json::from_value::<
2189 lsp_types::RelatedFullDocumentDiagnosticReport,
2190 >(result.clone())
2191 {
2192 let diagnostics = full_report.full_document_diagnostic_report.items;
2193 let result_id = full_report.full_document_diagnostic_report.result_id;
2194
2195 tracing::trace!(
2196 "LSP: received {} diagnostics for {} (result_id: {:?})",
2197 diagnostics.len(),
2198 uri_string,
2199 result_id
2200 );
2201
2202 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2203 request_id,
2204 uri: uri_string,
2205 result_id,
2206 diagnostics,
2207 unchanged: false,
2208 });
2209 } else if let Ok(unchanged_report) = serde_json::from_value::<
2210 lsp_types::RelatedUnchangedDocumentDiagnosticReport,
2211 >(result.clone())
2212 {
2213 let result_id = unchanged_report
2214 .unchanged_document_diagnostic_report
2215 .result_id;
2216
2217 tracing::trace!(
2218 "LSP: diagnostics unchanged for {} (result_id: {:?})",
2219 uri_string,
2220 result_id
2221 );
2222
2223 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2224 request_id,
2225 uri: uri_string,
2226 result_id: Some(result_id),
2227 diagnostics: Vec::new(),
2228 unchanged: true,
2229 });
2230 } else {
2231 tracing::warn!(
2233 "LSP: could not parse diagnostic report, sending empty: {}",
2234 result
2235 );
2236 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2237 request_id,
2238 uri: uri_string,
2239 result_id: None,
2240 diagnostics: Vec::new(),
2241 unchanged: false,
2242 });
2243 }
2244
2245 Ok(())
2246 }
2247 Err(e) => {
2248 tracing::debug!("Document diagnostic request failed: {}", e);
2249 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2251 request_id,
2252 uri: uri.as_str().to_string(),
2253 result_id: None,
2254 diagnostics: Vec::new(),
2255 unchanged: false,
2256 });
2257 Err(e)
2258 }
2259 }
2260 }
2261
2262 #[allow(clippy::too_many_arguments)]
2264 async fn handle_inlay_hints(
2265 &self,
2266 request_id: u64,
2267 uri: Uri,
2268 start_line: u32,
2269 start_char: u32,
2270 end_line: u32,
2271 end_char: u32,
2272 pending: &PendingRequests,
2273 ) -> Result<(), String> {
2274 use lsp_types::InlayHintParams;
2275
2276 tracing::trace!(
2277 "LSP: inlay hints request for {} ({}:{} - {}:{})",
2278 uri.as_str(),
2279 start_line,
2280 start_char,
2281 end_line,
2282 end_char
2283 );
2284
2285 let params = InlayHintParams {
2286 text_document: TextDocumentIdentifier { uri: uri.clone() },
2287 range: Range {
2288 start: Position {
2289 line: start_line,
2290 character: start_char,
2291 },
2292 end: Position {
2293 line: end_line,
2294 character: end_char,
2295 },
2296 },
2297 work_done_progress_params: WorkDoneProgressParams::default(),
2298 };
2299
2300 match self
2301 .send_request_sequential::<_, Option<Vec<lsp_types::InlayHint>>>(
2302 "textDocument/inlayHint",
2303 Some(params),
2304 pending,
2305 )
2306 .await
2307 {
2308 Ok(hints) => {
2309 let hints = hints.unwrap_or_default();
2310 let uri_string = uri.as_str().to_string();
2311
2312 tracing::trace!(
2313 "LSP: received {} inlay hints for {}",
2314 hints.len(),
2315 uri_string
2316 );
2317
2318 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2319 request_id,
2320 uri: uri_string,
2321 hints,
2322 });
2323
2324 Ok(())
2325 }
2326 Err(e) => {
2327 tracing::debug!("Inlay hints request failed: {}", e);
2328 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2330 request_id,
2331 uri: uri.as_str().to_string(),
2332 hints: Vec::new(),
2333 });
2334 Err(e)
2335 }
2336 }
2337 }
2338
2339 async fn handle_folding_ranges(
2341 &self,
2342 request_id: u64,
2343 uri: Uri,
2344 pending: &PendingRequests,
2345 ) -> Result<(), String> {
2346 use lsp_types::FoldingRangeParams;
2347
2348 tracing::trace!("LSP: folding range request for {}", uri.as_str());
2349
2350 let params = FoldingRangeParams {
2351 text_document: TextDocumentIdentifier { uri: uri.clone() },
2352 work_done_progress_params: WorkDoneProgressParams::default(),
2353 partial_result_params: PartialResultParams::default(),
2354 };
2355
2356 match self
2357 .send_request_sequential::<_, Option<Vec<lsp_types::FoldingRange>>>(
2358 "textDocument/foldingRange",
2359 Some(params),
2360 pending,
2361 )
2362 .await
2363 {
2364 Ok(ranges) => {
2365 let ranges = ranges.unwrap_or_default();
2366 let uri_string = uri.as_str().to_string();
2367
2368 tracing::trace!(
2369 "LSP: received {} folding ranges for {}",
2370 ranges.len(),
2371 uri_string
2372 );
2373
2374 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2375 request_id,
2376 uri: uri_string,
2377 ranges,
2378 });
2379
2380 Ok(())
2381 }
2382 Err(e) => {
2383 tracing::debug!("Folding range request failed: {}", e);
2384 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2385 request_id,
2386 uri: uri.as_str().to_string(),
2387 ranges: Vec::new(),
2388 });
2389 Err(e)
2390 }
2391 }
2392 }
2393
2394 async fn handle_semantic_tokens_full(
2395 &self,
2396 request_id: u64,
2397 uri: Uri,
2398 pending: &PendingRequests,
2399 ) -> Result<(), String> {
2400 use lsp_types::request::SemanticTokensFullRequest;
2401
2402 tracing::trace!("LSP: semanticTokens/full request for {}", uri.as_str());
2403
2404 let params = SemanticTokensParams {
2405 work_done_progress_params: WorkDoneProgressParams::default(),
2406 partial_result_params: PartialResultParams::default(),
2407 text_document: TextDocumentIdentifier { uri: uri.clone() },
2408 };
2409
2410 match self
2411 .send_request_sequential_tracked::<_, Option<SemanticTokensResult>>(
2412 SemanticTokensFullRequest::METHOD,
2413 Some(params),
2414 pending,
2415 Some(request_id),
2416 )
2417 .await
2418 {
2419 Ok(result) => {
2420 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2421 request_id,
2422 uri: uri.as_str().to_string(),
2423 response: LspSemanticTokensResponse::Full(Ok(result)),
2424 });
2425 Ok(())
2426 }
2427 Err(e) => {
2428 tracing::debug!("Semantic tokens request failed: {}", e);
2429 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2430 request_id,
2431 uri: uri.as_str().to_string(),
2432 response: LspSemanticTokensResponse::Full(Err(e.clone())),
2433 });
2434 Err(e)
2435 }
2436 }
2437 }
2438
2439 async fn handle_semantic_tokens_full_delta(
2440 &self,
2441 request_id: u64,
2442 uri: Uri,
2443 previous_result_id: String,
2444 pending: &PendingRequests,
2445 ) -> Result<(), String> {
2446 use lsp_types::{
2447 request::SemanticTokensFullDeltaRequest, SemanticTokensDeltaParams,
2448 SemanticTokensFullDeltaResult,
2449 };
2450
2451 tracing::trace!(
2452 "LSP: semanticTokens/full/delta request for {}",
2453 uri.as_str()
2454 );
2455
2456 let params = SemanticTokensDeltaParams {
2457 work_done_progress_params: WorkDoneProgressParams::default(),
2458 partial_result_params: PartialResultParams::default(),
2459 text_document: TextDocumentIdentifier { uri: uri.clone() },
2460 previous_result_id,
2461 };
2462
2463 match self
2464 .send_request_sequential_tracked::<_, Option<SemanticTokensFullDeltaResult>>(
2465 SemanticTokensFullDeltaRequest::METHOD,
2466 Some(params),
2467 pending,
2468 Some(request_id),
2469 )
2470 .await
2471 {
2472 Ok(result) => {
2473 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2474 request_id,
2475 uri: uri.as_str().to_string(),
2476 response: LspSemanticTokensResponse::FullDelta(Ok(result)),
2477 });
2478 Ok(())
2479 }
2480 Err(e) => {
2481 tracing::debug!("Semantic tokens delta request failed: {}", e);
2482 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2483 request_id,
2484 uri: uri.as_str().to_string(),
2485 response: LspSemanticTokensResponse::FullDelta(Err(e.clone())),
2486 });
2487 Err(e)
2488 }
2489 }
2490 }
2491
2492 async fn handle_semantic_tokens_range(
2493 &self,
2494 request_id: u64,
2495 uri: Uri,
2496 range: lsp_types::Range,
2497 pending: &PendingRequests,
2498 ) -> Result<(), String> {
2499 use lsp_types::{request::SemanticTokensRangeRequest, SemanticTokensRangeParams};
2500
2501 tracing::trace!("LSP: semanticTokens/range request for {}", uri.as_str());
2502
2503 let params = SemanticTokensRangeParams {
2504 work_done_progress_params: WorkDoneProgressParams::default(),
2505 partial_result_params: PartialResultParams::default(),
2506 text_document: TextDocumentIdentifier { uri: uri.clone() },
2507 range,
2508 };
2509
2510 match self
2511 .send_request_sequential_tracked::<_, Option<lsp_types::SemanticTokensRangeResult>>(
2512 SemanticTokensRangeRequest::METHOD,
2513 Some(params),
2514 pending,
2515 Some(request_id),
2516 )
2517 .await
2518 {
2519 Ok(result) => {
2520 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2521 request_id,
2522 uri: uri.as_str().to_string(),
2523 response: LspSemanticTokensResponse::Range(Ok(result)),
2524 });
2525 Ok(())
2526 }
2527 Err(e) => {
2528 tracing::debug!("Semantic tokens range request failed: {}", e);
2529 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2530 request_id,
2531 uri: uri.as_str().to_string(),
2532 response: LspSemanticTokensResponse::Range(Err(e.clone())),
2533 });
2534 Err(e)
2535 }
2536 }
2537 }
2538
2539 async fn handle_plugin_request(
2541 &self,
2542 request_id: u64,
2543 method: String,
2544 params: Option<Value>,
2545 pending: &PendingRequests,
2546 ) {
2547 tracing::trace!(
2548 "Plugin request {} => method={} params={:?}",
2549 request_id,
2550 method,
2551 params
2552 );
2553 let result = self
2554 .send_request_sequential_tracked::<Value, Value>(
2555 &method,
2556 params,
2557 pending,
2558 Some(request_id),
2559 )
2560 .await;
2561
2562 tracing::trace!(
2563 "Plugin request {} completed with result {:?}",
2564 request_id,
2565 &result
2566 );
2567 let _ = self.async_tx.send(AsyncMessage::PluginLspResponse {
2568 language: (*self.language).clone(),
2569 request_id,
2570 result,
2571 });
2572 }
2573
2574 async fn handle_shutdown(&self) -> Result<(), String> {
2576 tracing::info!("Shutting down async LSP server");
2577
2578 let notification = JsonRpcNotification {
2579 jsonrpc: "2.0".to_string(),
2580 method: "shutdown".to_string(),
2581 params: None,
2582 };
2583
2584 self.write_message(¬ification).await?;
2585
2586 let exit = JsonRpcNotification {
2587 jsonrpc: "2.0".to_string(),
2588 method: "exit".to_string(),
2589 params: None,
2590 };
2591
2592 self.write_message(&exit).await
2593 }
2594
2595 async fn send_cancel_request(&self, lsp_id: i64) -> Result<(), String> {
2597 tracing::trace!("Sending $/cancelRequest for LSP id {}", lsp_id);
2598
2599 let notification = JsonRpcNotification {
2600 jsonrpc: "2.0".to_string(),
2601 method: "$/cancelRequest".to_string(),
2602 params: Some(serde_json::json!({ "id": lsp_id })),
2603 };
2604
2605 self.write_message(¬ification).await
2606 }
2607
2608 async fn handle_cancel_request(&self, request_id: u64) -> Result<(), String> {
2610 let lsp_id = self.active_requests.lock().unwrap().remove(&request_id);
2611 if let Some(lsp_id) = lsp_id {
2612 tracing::info!(
2613 "Cancelling request: editor_id={}, lsp_id={}",
2614 request_id,
2615 lsp_id
2616 );
2617 self.send_cancel_request(lsp_id).await
2618 } else {
2619 tracing::trace!(
2620 "Cancel request ignored: no active LSP request for editor_id={}",
2621 request_id
2622 );
2623 Ok(())
2624 }
2625 }
2626}
2627
2628struct LspTask {
2630 _process: crate::services::remote::StdioChild,
2633
2634 stdin: ChildStdin,
2636
2637 stdout: BufReader<ChildStdout>,
2639
2640 next_id: i64,
2642
2643 pending: HashMap<i64, oneshot::Sender<Result<Value, String>>>,
2645
2646 capabilities: Option<ServerCapabilities>,
2648
2649 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2651
2652 pending_opens: HashMap<PathBuf, Instant>,
2655
2656 initialized: bool,
2658
2659 async_tx: std_mpsc::Sender<AsyncMessage>,
2661
2662 language: String,
2664
2665 server_name: String,
2667
2668 server_command: String,
2670
2671 stderr_log_path: std::path::PathBuf,
2673
2674 language_id_overrides: HashMap<String, String>,
2676}
2677
2678impl LspTask {
2679 #[allow(clippy::too_many_arguments)]
2689 async fn spawn(
2690 command: &str,
2691 args: &[String],
2692 env: &std::collections::HashMap<String, String>,
2693 language: String,
2694 server_name: String,
2695 async_tx: std_mpsc::Sender<AsyncMessage>,
2696 process_limits: &ProcessLimits,
2697 stderr_log_path: std::path::PathBuf,
2698 language_id_overrides: HashMap<String, String>,
2699 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2700 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
2701 ) -> Result<Self, String> {
2702 tracing::info!("Spawning async LSP server: {} {:?}", command, args);
2703 tracing::info!("Process limits: {:?}", process_limits);
2704 tracing::info!("LSP stderr will be logged to: {:?}", stderr_log_path);
2705
2706 if !long_running_spawner.command_exists(command).await {
2711 return Err(format!(
2712 "LSP server executable '{}' not found in the active authority's PATH. \
2713 Please install it or check your configuration.",
2714 command
2715 ));
2716 }
2717
2718 let env_pairs: Vec<(String, String)> =
2723 env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2724
2725 let mut stdio_child = long_running_spawner
2726 .spawn_stdio(command, args, env_pairs, None, Some(process_limits))
2727 .await
2728 .map_err(|e| format!("Failed to spawn LSP server '{}': {}", command, e))?;
2729
2730 let stdin = stdio_child
2731 .take_stdin()
2732 .ok_or_else(|| "Failed to get stdin".to_string())?;
2733
2734 let stdout_stream = stdio_child
2735 .take_stdout()
2736 .ok_or_else(|| "Failed to get stdout".to_string())?;
2737 let stdout = BufReader::new(stdout_stream);
2738
2739 if let Some(stderr_stream) = stdio_child.take_stderr() {
2745 let log_path = stderr_log_path.clone();
2746 tokio::spawn(async move {
2747 use tokio::fs::File;
2748 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader as TokioBufReader};
2749 let mut file = match File::create(&log_path).await {
2750 Ok(f) => f,
2751 Err(e) => {
2752 tracing::warn!("Could not create LSP stderr log {:?}: {}", log_path, e);
2753 return;
2754 }
2755 };
2756 let mut reader = TokioBufReader::new(stderr_stream);
2757 let mut buf = String::new();
2758 loop {
2759 buf.clear();
2760 match reader.read_line(&mut buf).await {
2761 Ok(0) => break,
2762 Ok(_) => {
2763 if let Err(e) = file.write_all(buf.as_bytes()).await {
2764 tracing::warn!(
2765 "Write to LSP stderr log {:?} failed: {}",
2766 log_path,
2767 e
2768 );
2769 return;
2770 }
2771 }
2772 Err(e) => {
2773 tracing::debug!("LSP stderr stream closed for {:?}: {}", log_path, e);
2774 return;
2775 }
2776 }
2777 }
2778 });
2779 }
2780
2781 Ok(Self {
2782 _process: stdio_child,
2783 stdin,
2784 stdout,
2785 next_id: 0,
2786 pending: HashMap::new(),
2787 capabilities: None,
2788 document_versions,
2789 pending_opens: HashMap::new(),
2790 initialized: false,
2791 async_tx,
2792 language,
2793 server_name,
2794 server_command: command.to_string(),
2795 stderr_log_path,
2796 language_id_overrides,
2797 })
2798 }
2799
2800 #[allow(clippy::too_many_arguments)]
2802 #[allow(clippy::let_underscore_must_use)] fn spawn_stdout_reader(
2804 mut stdout: BufReader<ChildStdout>,
2805 pending: PendingRequests,
2806 async_tx: std_mpsc::Sender<AsyncMessage>,
2807 language: String,
2808 server_name: String,
2809 server_command: String,
2810 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2811 stderr_log_path: std::path::PathBuf,
2812 shutting_down: Arc<AtomicBool>,
2813 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2814 config_options: Arc<std::sync::Mutex<Option<Value>>>,
2815 capabilities: Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
2816 ) {
2817 tokio::spawn(async move {
2818 tracing::info!("LSP stdout reader task started for {}", language);
2819 loop {
2820 match read_message_from_stdout(&mut stdout).await {
2821 Ok(message) => {
2822 tracing::trace!("Read message from LSP server: {:?}", message);
2823 if let Err(e) = handle_message_dispatch(
2824 message,
2825 &pending,
2826 &async_tx,
2827 &language,
2828 &server_name,
2829 &server_command,
2830 &stdin_writer,
2831 &document_versions,
2832 &config_options,
2833 &capabilities,
2834 )
2835 .await
2836 {
2837 tracing::error!("Error handling LSP message: {}", e);
2838 }
2839 }
2840 Err(e) => {
2841 if shutting_down.load(Ordering::SeqCst) {
2843 tracing::info!(
2844 "LSP stdout reader exiting due to graceful shutdown for {}",
2845 language
2846 );
2847 } else {
2848 tracing::error!("Error reading from LSP server: {}", e);
2849 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2850 language: language.clone(),
2851 server_name: server_name.clone(),
2852 status: LspServerStatus::Error,
2853 message: None,
2854 });
2855 let _ = async_tx.send(AsyncMessage::LspError {
2856 language: language.clone(),
2857 error: format!("Read error: {}", e),
2858 stderr_log_path: Some(stderr_log_path.clone()),
2859 });
2860 }
2861 break;
2862 }
2863 }
2864 }
2865 {
2868 let mut pending_guard = pending.lock().unwrap();
2869 let count = pending_guard.len();
2870 if count > 0 {
2871 tracing::info!(
2872 "LSP stdout reader: draining {} pending requests for {}",
2873 count,
2874 language
2875 );
2876 for (id, tx) in pending_guard.drain() {
2877 tracing::debug!(
2878 "LSP stdout reader: failing pending request id={} for {}",
2879 id,
2880 language
2881 );
2882 let _ = tx.send(Err(
2883 "LSP server connection closed while awaiting response".to_string(),
2884 ));
2885 }
2886 }
2887 }
2888
2889 tracing::info!("LSP stdout reader task exiting for {}", language);
2890 });
2891 }
2892
2893 #[allow(clippy::let_underscore_must_use)]
2897 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
2898 tracing::info!("LspTask::run() started for language: {}", self.language);
2899
2900 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
2902
2903 let state = LspState {
2905 stdin: stdin_writer.clone(),
2906 next_id: Arc::new(AtomicI64::new(self.next_id)),
2907 capabilities: Arc::new(Mutex::new(self.capabilities)),
2908 document_versions: self.document_versions.clone(),
2909 pending_opens: Arc::new(Mutex::new(self.pending_opens)),
2910 initialized: Arc::new(AtomicBool::new(self.initialized)),
2911 async_tx: self.async_tx.clone(),
2912 language: Arc::new(self.language.clone()),
2913 server_name: Arc::new(self.server_name.clone()),
2914 active_requests: Arc::new(Mutex::new(HashMap::new())),
2915 language_id_overrides: Arc::new(self.language_id_overrides.clone()),
2916 };
2917
2918 let pending = Arc::new(Mutex::new(self.pending));
2919 let async_tx = state.async_tx.clone();
2920 let language_clone: String = (*state.language).clone();
2921 let server_name: String = (*state.server_name).clone();
2922
2923 let config_options: Arc<std::sync::Mutex<Option<Value>>> =
2927 Arc::new(std::sync::Mutex::new(None));
2928
2929 let shutting_down = Arc::new(AtomicBool::new(false));
2931
2932 Self::spawn_stdout_reader(
2934 self.stdout,
2935 pending.clone(),
2936 async_tx.clone(),
2937 language_clone.clone(),
2938 self.server_name.clone(),
2939 self.server_command.clone(),
2940 stdin_writer.clone(),
2941 self.stderr_log_path,
2942 shutting_down.clone(),
2943 self.document_versions.clone(),
2944 config_options.clone(),
2945 state.capabilities.clone(),
2946 );
2947
2948 macro_rules! await_draining {
2978 ($fut:expr, $command_rx:expr, $buf:expr) => {{
2979 let fut = $fut;
2980 tokio::pin!(fut);
2981 loop {
2982 tokio::select! {
2983 biased; result = &mut fut => break result,
2985 Some(cmd) = $command_rx.recv() => {
2986 $buf.push_back(cmd);
2987 }
2988 }
2989 }
2990 }};
2991 }
2992
2993 macro_rules! spawn_request {
2995 ($state:expr, $pending:expr, |$s:ident, $p:ident| $body:expr) => {{
2996 let $s = $state.clone();
2997 let $p = $pending.clone();
2998 tokio::spawn(async move {
2999 let _ = $body;
3000 });
3001 }};
3002 }
3003
3004 let mut pending_commands = Vec::new();
3005 let mut draining_buffer: std::collections::VecDeque<LspCommand> =
3006 std::collections::VecDeque::new();
3007 loop {
3008 let cmd = if let Some(cmd) = draining_buffer.pop_front() {
3011 cmd
3012 } else {
3013 match command_rx.recv().await {
3014 Some(cmd) => cmd,
3015 None => {
3016 tracing::info!("Command channel closed");
3017 break;
3018 }
3019 }
3020 };
3021
3022 tracing::trace!("LspTask received command: {:?}", cmd);
3023 let initialized = state.initialized.load(Ordering::SeqCst);
3024 match cmd {
3025 LspCommand::Initialize {
3026 root_uri,
3027 initialization_options,
3028 response,
3029 } => {
3030 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3032 language: language_clone.clone(),
3033 server_name: server_name.clone(),
3034 status: LspServerStatus::Initializing,
3035 message: None,
3036 });
3037 tracing::info!("Processing Initialize command");
3038 *config_options.lock().unwrap() = initialization_options.clone();
3042 let result = await_draining!(
3043 state.handle_initialize_sequential(
3044 root_uri,
3045 initialization_options,
3046 &pending
3047 ),
3048 command_rx,
3049 draining_buffer
3050 );
3051 let success = result.is_ok();
3052 let _ = response.send(result);
3053
3054 if success {
3056 let queued = std::mem::take(&mut pending_commands);
3057 await_draining!(
3058 state.replay_pending_commands(queued, &pending),
3059 command_rx,
3060 draining_buffer
3061 );
3062 }
3063 }
3064 LspCommand::DidOpen {
3065 uri,
3066 text,
3067 language_id,
3068 } => {
3069 if initialized {
3070 tracing::info!("Processing DidOpen for {}", uri.as_str());
3071 let _ = state
3072 .handle_did_open_sequential(uri, text, language_id, &pending)
3073 .await;
3074 } else {
3075 tracing::trace!(
3076 "Queueing DidOpen for {} until initialization completes",
3077 uri.as_str()
3078 );
3079 pending_commands.push(LspCommand::DidOpen {
3080 uri,
3081 text,
3082 language_id,
3083 });
3084 }
3085 }
3086 LspCommand::DidChange {
3087 uri,
3088 content_changes,
3089 } => {
3090 if initialized {
3091 tracing::trace!("Processing DidChange for {}", uri.as_str());
3092 let _ = state
3095 .handle_did_change_sequential(uri, content_changes, &pending)
3096 .await;
3097 } else {
3098 tracing::trace!(
3099 "Queueing DidChange for {} until initialization completes",
3100 uri.as_str()
3101 );
3102 pending_commands.push(LspCommand::DidChange {
3103 uri,
3104 content_changes,
3105 });
3106 }
3107 }
3108 LspCommand::DidClose { uri } => {
3109 if initialized {
3110 tracing::info!("Processing DidClose for {}", uri.as_str());
3111 let _ = state.handle_did_close(uri).await;
3112 } else {
3113 tracing::trace!(
3114 "Queueing DidClose for {} until initialization completes",
3115 uri.as_str()
3116 );
3117 pending_commands.push(LspCommand::DidClose { uri });
3118 }
3119 }
3120 LspCommand::DidSave { uri, text } => {
3121 if initialized {
3122 tracing::info!("Processing DidSave for {}", uri.as_str());
3123 let _ = state.handle_did_save(uri, text).await;
3124 } else {
3125 tracing::trace!(
3126 "Queueing DidSave for {} until initialization completes",
3127 uri.as_str()
3128 );
3129 pending_commands.push(LspCommand::DidSave { uri, text });
3130 }
3131 }
3132 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
3133 if initialized {
3134 tracing::info!(
3135 "Processing DidChangeWorkspaceFolders: +{} -{}",
3136 added.len(),
3137 removed.len()
3138 );
3139 let _ = state
3140 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
3141 lsp_types::DidChangeWorkspaceFoldersParams {
3142 event: lsp_types::WorkspaceFoldersChangeEvent {
3143 added,
3144 removed,
3145 },
3146 },
3147 )
3148 .await;
3149 } else {
3150 tracing::trace!(
3151 "Queueing DidChangeWorkspaceFolders until initialization completes"
3152 );
3153 pending_commands
3154 .push(LspCommand::DidChangeWorkspaceFolders { added, removed });
3155 }
3156 }
3157 LspCommand::Completion {
3158 request_id,
3159 uri,
3160 line,
3161 character,
3162 } => {
3163 if initialized {
3164 tracing::info!("Processing Completion request for {}", uri.as_str());
3165 spawn_request!(state, pending, |s, p| s
3166 .handle_completion(request_id, uri, line, character, &p)
3167 .await);
3168 } else {
3169 tracing::trace!("LSP not initialized, sending empty completion");
3170 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
3171 request_id,
3172 items: vec![],
3173 });
3174 }
3175 }
3176 LspCommand::GotoDefinition {
3177 request_id,
3178 uri,
3179 line,
3180 character,
3181 } => {
3182 if initialized {
3183 tracing::info!("Processing GotoDefinition request for {}", uri.as_str());
3184 spawn_request!(state, pending, |s, p| s
3185 .handle_goto_definition(request_id, uri, line, character, &p)
3186 .await);
3187 } else {
3188 tracing::trace!("LSP not initialized, sending empty locations");
3189 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
3190 request_id,
3191 locations: vec![],
3192 });
3193 }
3194 }
3195 LspCommand::Rename {
3196 request_id,
3197 uri,
3198 line,
3199 character,
3200 new_name,
3201 } => {
3202 if initialized {
3203 tracing::info!("Processing Rename request for {}", uri.as_str());
3204 spawn_request!(state, pending, |s, p| s
3205 .handle_rename(request_id, uri, line, character, new_name, &p)
3206 .await);
3207 } else {
3208 tracing::trace!("LSP not initialized, cannot rename");
3209 let _ = state.async_tx.send(AsyncMessage::LspRename {
3210 request_id,
3211 result: Err("LSP not initialized".to_string()),
3212 });
3213 }
3214 }
3215 LspCommand::Hover {
3216 request_id,
3217 uri,
3218 line,
3219 character,
3220 } => {
3221 if initialized {
3222 tracing::info!("Processing Hover request for {}", uri.as_str());
3223 spawn_request!(state, pending, |s, p| s
3224 .handle_hover(request_id, uri, line, character, &p)
3225 .await);
3226 } else {
3227 tracing::trace!("LSP not initialized, cannot get hover");
3228 let _ = state.async_tx.send(AsyncMessage::LspHover {
3229 request_id,
3230 contents: String::new(),
3231 is_markdown: false,
3232 range: None,
3233 });
3234 }
3235 }
3236 LspCommand::References {
3237 request_id,
3238 uri,
3239 line,
3240 character,
3241 } => {
3242 if initialized {
3243 tracing::info!("Processing References request for {}", uri.as_str());
3244 spawn_request!(state, pending, |s, p| s
3245 .handle_references(request_id, uri, line, character, &p)
3246 .await);
3247 } else {
3248 tracing::trace!("LSP not initialized, cannot get references");
3249 let _ = state.async_tx.send(AsyncMessage::LspReferences {
3250 request_id,
3251 locations: Vec::new(),
3252 });
3253 }
3254 }
3255 LspCommand::SignatureHelp {
3256 request_id,
3257 uri,
3258 line,
3259 character,
3260 } => {
3261 if initialized {
3262 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
3263 spawn_request!(state, pending, |s, p| s
3264 .handle_signature_help(request_id, uri, line, character, &p)
3265 .await);
3266 } else {
3267 tracing::trace!("LSP not initialized, cannot get signature help");
3268 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
3269 request_id,
3270 signature_help: None,
3271 });
3272 }
3273 }
3274 LspCommand::CodeActions {
3275 request_id,
3276 uri,
3277 start_line,
3278 start_char,
3279 end_line,
3280 end_char,
3281 diagnostics,
3282 } => {
3283 if initialized {
3284 tracing::info!("Processing CodeActions request for {}", uri.as_str());
3285 spawn_request!(state, pending, |s, p| s
3286 .handle_code_actions(
3287 request_id,
3288 uri,
3289 start_line,
3290 start_char,
3291 end_line,
3292 end_char,
3293 diagnostics,
3294 &p,
3295 )
3296 .await);
3297 } else {
3298 tracing::trace!("LSP not initialized, cannot get code actions");
3299 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
3300 request_id,
3301 actions: Vec::new(),
3302 });
3303 }
3304 }
3305 LspCommand::DocumentDiagnostic {
3306 request_id,
3307 uri,
3308 previous_result_id,
3309 } => {
3310 if initialized {
3311 tracing::info!(
3312 "Processing DocumentDiagnostic request for {}",
3313 uri.as_str()
3314 );
3315 spawn_request!(state, pending, |s, p| s
3316 .handle_document_diagnostic(request_id, uri, previous_result_id, &p)
3317 .await);
3318 } else {
3319 tracing::trace!("LSP not initialized, cannot get document diagnostics");
3320 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
3321 request_id,
3322 uri: uri.as_str().to_string(),
3323 result_id: None,
3324 diagnostics: Vec::new(),
3325 unchanged: false,
3326 });
3327 }
3328 }
3329 LspCommand::InlayHints {
3330 request_id,
3331 uri,
3332 start_line,
3333 start_char,
3334 end_line,
3335 end_char,
3336 } => {
3337 if initialized {
3338 tracing::info!("Processing InlayHints request for {}", uri.as_str());
3339 spawn_request!(state, pending, |s, p| s
3340 .handle_inlay_hints(
3341 request_id, uri, start_line, start_char, end_line, end_char, &p,
3342 )
3343 .await);
3344 } else {
3345 tracing::trace!("LSP not initialized, cannot get inlay hints");
3346 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
3347 request_id,
3348 uri: uri.as_str().to_string(),
3349 hints: Vec::new(),
3350 });
3351 }
3352 }
3353 LspCommand::FoldingRange { request_id, uri } => {
3354 if initialized {
3355 tracing::info!("Processing FoldingRange request for {}", uri.as_str());
3356 spawn_request!(state, pending, |s, p| s
3357 .handle_folding_ranges(request_id, uri, &p)
3358 .await);
3359 } else {
3360 tracing::trace!("LSP not initialized, cannot get folding ranges");
3361 let _ = state.async_tx.send(AsyncMessage::LspFoldingRanges {
3362 request_id,
3363 uri: uri.as_str().to_string(),
3364 ranges: Vec::new(),
3365 });
3366 }
3367 }
3368 LspCommand::SemanticTokensFull { request_id, uri } => {
3369 if initialized {
3370 tracing::info!("Processing SemanticTokens request for {}", uri.as_str());
3371 spawn_request!(state, pending, |s, p| s
3372 .handle_semantic_tokens_full(request_id, uri, &p)
3373 .await);
3374 } else {
3375 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3376 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3377 request_id,
3378 uri: uri.as_str().to_string(),
3379 response: LspSemanticTokensResponse::Full(Err(
3380 "LSP not initialized".to_string()
3381 )),
3382 });
3383 }
3384 }
3385 LspCommand::SemanticTokensFullDelta {
3386 request_id,
3387 uri,
3388 previous_result_id,
3389 } => {
3390 if initialized {
3391 tracing::info!(
3392 "Processing SemanticTokens delta request for {}",
3393 uri.as_str()
3394 );
3395 spawn_request!(state, pending, |s, p| s
3396 .handle_semantic_tokens_full_delta(
3397 request_id,
3398 uri,
3399 previous_result_id,
3400 &p,
3401 )
3402 .await);
3403 } else {
3404 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3405 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3406 request_id,
3407 uri: uri.as_str().to_string(),
3408 response: LspSemanticTokensResponse::FullDelta(Err(
3409 "LSP not initialized".to_string(),
3410 )),
3411 });
3412 }
3413 }
3414 LspCommand::SemanticTokensRange {
3415 request_id,
3416 uri,
3417 range,
3418 } => {
3419 if initialized {
3420 tracing::info!(
3421 "Processing SemanticTokens range request for {}",
3422 uri.as_str()
3423 );
3424 spawn_request!(state, pending, |s, p| s
3425 .handle_semantic_tokens_range(request_id, uri, range, &p)
3426 .await);
3427 } else {
3428 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3429 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3430 request_id,
3431 uri: uri.as_str().to_string(),
3432 response: LspSemanticTokensResponse::Range(Err(
3433 "LSP not initialized".to_string()
3434 )),
3435 });
3436 }
3437 }
3438 LspCommand::ExecuteCommand { command, arguments } => {
3439 if initialized {
3440 tracing::info!("Processing ExecuteCommand: {}", command);
3441 spawn_request!(state, pending, |s, p| s
3442 .handle_execute_command(command, arguments, &p)
3443 .await);
3444 } else {
3445 tracing::trace!("LSP not initialized, cannot execute command");
3446 }
3447 }
3448 LspCommand::CodeActionResolve { request_id, action } => {
3449 if initialized {
3450 tracing::info!("Processing CodeActionResolve (request_id={})", request_id);
3451 spawn_request!(state, pending, |s, p| s
3452 .handle_code_action_resolve(request_id, *action, &p)
3453 .await);
3454 } else {
3455 tracing::trace!("LSP not initialized, cannot resolve code action");
3456 let _ = state.async_tx.send(AsyncMessage::LspCodeActionResolved {
3457 request_id,
3458 action: Err("LSP not initialized".to_string()),
3459 });
3460 }
3461 }
3462 LspCommand::CompletionResolve { request_id, item } => {
3463 if initialized {
3464 spawn_request!(state, pending, |s, p| s
3465 .handle_completion_resolve(request_id, *item, &p)
3466 .await);
3467 }
3468 }
3469 LspCommand::DocumentFormatting {
3470 request_id,
3471 uri,
3472 tab_size,
3473 insert_spaces,
3474 } => {
3475 if initialized {
3476 tracing::info!("Processing DocumentFormatting for {}", uri.as_str());
3477 spawn_request!(state, pending, |s, p| s
3478 .handle_document_formatting(
3479 request_id,
3480 uri,
3481 tab_size,
3482 insert_spaces,
3483 &p,
3484 )
3485 .await);
3486 }
3487 }
3488 LspCommand::DocumentRangeFormatting {
3489 request_id,
3490 uri,
3491 start_line,
3492 start_char,
3493 end_line,
3494 end_char,
3495 tab_size,
3496 insert_spaces,
3497 } => {
3498 if initialized {
3499 spawn_request!(state, pending, |s, p| s
3500 .handle_document_range_formatting(
3501 request_id,
3502 uri,
3503 start_line,
3504 start_char,
3505 end_line,
3506 end_char,
3507 tab_size,
3508 insert_spaces,
3509 &p,
3510 )
3511 .await);
3512 }
3513 }
3514 LspCommand::PrepareRename {
3515 request_id,
3516 uri,
3517 line,
3518 character,
3519 } => {
3520 if initialized {
3521 spawn_request!(state, pending, |s, p| s
3522 .handle_prepare_rename(request_id, uri, line, character, &p)
3523 .await);
3524 }
3525 }
3526 LspCommand::CancelRequest { request_id } => {
3527 tracing::info!("Processing CancelRequest for editor_id={}", request_id);
3528 let _ = state.handle_cancel_request(request_id).await;
3530 }
3531 LspCommand::PluginRequest {
3532 request_id,
3533 method,
3534 params,
3535 } => {
3536 if initialized {
3537 tracing::trace!("Processing plugin request {} ({})", request_id, method);
3538 spawn_request!(state, pending, |s, p| s
3539 .handle_plugin_request(request_id, method, params, &p)
3540 .await);
3541 } else {
3542 tracing::trace!(
3543 "Plugin LSP request {} received before initialization",
3544 request_id
3545 );
3546 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
3547 language: language_clone.clone(),
3548 request_id,
3549 result: Err("LSP not initialized".to_string()),
3550 });
3551 }
3552 }
3553 LspCommand::Shutdown => {
3554 tracing::info!("Processing Shutdown command");
3555 shutting_down.store(true, Ordering::SeqCst);
3557 let _ = state.handle_shutdown().await;
3558 break;
3559 }
3560 }
3561 }
3562
3563 tracing::info!("LSP task exiting for language: {}", self.language);
3564 }
3565}
3566
3567async fn read_message_from_stdout(
3569 stdout: &mut BufReader<ChildStdout>,
3570) -> Result<JsonRpcMessage, String> {
3571 let mut content_length: Option<usize> = None;
3573
3574 loop {
3575 let mut line = String::new();
3576 let bytes_read = stdout
3577 .read_line(&mut line)
3578 .await
3579 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
3580
3581 if bytes_read == 0 {
3583 return Err("LSP server closed stdout (EOF)".to_string());
3584 }
3585
3586 if line == "\r\n" {
3587 break;
3588 }
3589
3590 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
3591 content_length = Some(
3592 len_str
3593 .trim()
3594 .parse()
3595 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
3596 );
3597 }
3598 }
3599
3600 let content_length =
3601 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
3602
3603 let mut content = vec![0u8; content_length];
3605 stdout
3606 .read_exact(&mut content)
3607 .await
3608 .map_err(|e| format!("Failed to read content: {}", e))?;
3609
3610 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
3611
3612 tracing::trace!("Received LSP message: {}", json);
3613
3614 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
3615}
3616
3617fn registrations_from_params(params: Option<&Value>) -> Vec<(String, Option<Value>)> {
3621 params
3622 .and_then(|p| serde_json::from_value::<lsp_types::RegistrationParams>(p.clone()).ok())
3623 .map(|rp| {
3624 rp.registrations
3625 .into_iter()
3626 .map(|r| (r.method, r.register_options))
3627 .collect()
3628 })
3629 .unwrap_or_default()
3630}
3631
3632fn unregistrations_from_params(params: Option<&Value>) -> Vec<String> {
3635 params
3636 .and_then(|p| serde_json::from_value::<lsp_types::UnregistrationParams>(p.clone()).ok())
3637 .map(|up| up.unregisterations.into_iter().map(|u| u.method).collect())
3638 .unwrap_or_default()
3639}
3640
3641fn sync_raw_capabilities(
3653 capabilities: &Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
3654 registrations: &[(String, Option<Value>)],
3655 register: bool,
3656) {
3657 use lsp_types::{DiagnosticOptions, DiagnosticServerCapabilities};
3658
3659 if !registrations
3660 .iter()
3661 .any(|(method, _)| method == "textDocument/diagnostic")
3662 {
3663 return;
3664 }
3665
3666 let mut guard = capabilities.lock().unwrap();
3667 let caps = guard.get_or_insert_with(ServerCapabilities::default);
3668 for (method, options) in registrations {
3669 if method == "textDocument/diagnostic" {
3670 caps.diagnostic_provider = register.then(|| {
3671 let opts = options
3672 .as_ref()
3673 .and_then(|o| serde_json::from_value::<DiagnosticOptions>(o.clone()).ok())
3674 .unwrap_or_default();
3675 DiagnosticServerCapabilities::Options(opts)
3676 });
3677 }
3678 }
3679}
3680
3681fn resolve_workspace_configuration(
3691 items: &[Value],
3692 init_options: Option<&Value>,
3693 server_command: &str,
3694) -> Vec<Value> {
3695 if items.is_empty() {
3696 return vec![resolve_configuration_section(
3697 None,
3698 init_options,
3699 server_command,
3700 )];
3701 }
3702 items
3703 .iter()
3704 .map(|item| {
3705 let section = item
3706 .get("section")
3707 .and_then(Value::as_str)
3708 .filter(|s| !s.is_empty());
3709 resolve_configuration_section(section, init_options, server_command)
3710 })
3711 .collect()
3712}
3713
3714fn resolve_configuration_section(
3718 section: Option<&str>,
3719 init_options: Option<&Value>,
3720 server_command: &str,
3721) -> Value {
3722 if let Some(options) = init_options {
3723 match section {
3724 Some(section) => {
3725 let mut current = options;
3726 let mut resolved = true;
3727 for part in section.split('.') {
3728 match current.get(part) {
3729 Some(next) => current = next,
3730 None => {
3731 resolved = false;
3732 break;
3733 }
3734 }
3735 }
3736 if resolved {
3737 return current.clone();
3738 }
3739 }
3740 None => return options.clone(),
3742 }
3743 }
3744 default_configuration_section(server_command)
3745}
3746
3747fn default_configuration_section(server_command: &str) -> Value {
3752 if server_command_is_rust_analyzer(server_command) {
3753 serde_json::json!({
3754 "inlayHints": {
3755 "typeHints": { "enable": true },
3756 "parameterHints": { "enable": true },
3757 "chainingHints": { "enable": true },
3758 "closureReturnTypeHints": { "enable": "always" }
3759 }
3760 })
3761 } else {
3762 Value::Null
3763 }
3764}
3765
3766fn server_command_is_rust_analyzer(server_command: &str) -> bool {
3767 std::path::Path::new(server_command)
3768 .file_name()
3769 .and_then(|name| name.to_str())
3770 .unwrap_or(server_command)
3771 .contains("rust-analyzer")
3772}
3773
3774fn null_response(id: i64) -> JsonRpcResponse {
3776 JsonRpcResponse {
3777 jsonrpc: "2.0".to_string(),
3778 id,
3779 result: Some(Value::Null),
3780 error: None,
3781 }
3782}
3783
3784fn parse_window_message(
3787 params: Option<Value>,
3788 default_type: i64,
3789) -> Option<(LspMessageType, String)> {
3790 let msg = serde_json::from_value::<serde_json::Map<String, Value>>(params?).ok()?;
3791 let type_num = msg
3792 .get("type")
3793 .and_then(|v| v.as_i64())
3794 .unwrap_or(default_type);
3795 let message = msg
3796 .get("message")
3797 .and_then(|v| v.as_str())
3798 .unwrap_or("(no message)")
3799 .to_string();
3800 let message_type = match type_num {
3801 1 => LspMessageType::Error,
3802 2 => LspMessageType::Warning,
3803 3 => LspMessageType::Info,
3804 _ => LspMessageType::Log,
3805 };
3806 Some((message_type, message))
3807}
3808
3809fn log_lsp_message(message_type: LspMessageType, language: &str, message: &str) {
3812 match message_type {
3813 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3814 LspMessageType::Warning => tracing::warn!("LSP ({}): {}", language, message),
3815 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3816 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3817 }
3818}
3819
3820fn parse_progress_notification(
3823 params: Option<Value>,
3824 language: &str,
3825) -> Option<(String, LspProgressValue)> {
3826 let progress = serde_json::from_value::<serde_json::Map<String, Value>>(params?).ok()?;
3827 let token = progress
3828 .get("token")
3829 .and_then(|v| {
3830 v.as_str()
3831 .map(|s| s.to_string())
3832 .or_else(|| v.as_i64().map(|n| n.to_string()))
3833 })
3834 .unwrap_or_else(|| "unknown".to_string());
3835 let value_obj = progress.get("value").and_then(|v| v.as_object())?;
3836 let kind = value_obj.get("kind").and_then(|v| v.as_str());
3837 let value = match kind {
3838 Some("begin") => {
3839 let title = value_obj
3840 .get("title")
3841 .and_then(|v| v.as_str())
3842 .unwrap_or("Working...")
3843 .to_string();
3844 let message = value_obj
3845 .get("message")
3846 .and_then(|v| v.as_str())
3847 .map(|s| s.to_string());
3848 let percentage = value_obj
3849 .get("percentage")
3850 .and_then(|v| v.as_u64())
3851 .map(|p| p as u32);
3852 tracing::info!(
3853 "LSP ({}) progress begin: {} {:?} {:?}",
3854 language,
3855 title,
3856 message,
3857 percentage
3858 );
3859 LspProgressValue::Begin {
3860 title,
3861 message,
3862 percentage,
3863 }
3864 }
3865 Some("report") => {
3866 let message = value_obj
3867 .get("message")
3868 .and_then(|v| v.as_str())
3869 .map(|s| s.to_string());
3870 let percentage = value_obj
3871 .get("percentage")
3872 .and_then(|v| v.as_u64())
3873 .map(|p| p as u32);
3874 tracing::trace!(
3875 "LSP ({}) progress report: {:?} {:?}",
3876 language,
3877 message,
3878 percentage
3879 );
3880 LspProgressValue::Report {
3881 message,
3882 percentage,
3883 }
3884 }
3885 Some("end") => {
3886 let message = value_obj
3887 .get("message")
3888 .and_then(|v| v.as_str())
3889 .map(|s| s.to_string());
3890 tracing::info!("LSP ({}) progress end: {:?}", language, message);
3891 LspProgressValue::End { message }
3892 }
3893 _ => return None,
3894 };
3895 Some((token, value))
3896}
3897
3898#[allow(clippy::too_many_arguments)]
3900#[allow(clippy::let_underscore_must_use)] async fn handle_message_dispatch(
3902 message: JsonRpcMessage,
3903 pending: &PendingRequests,
3904 async_tx: &std_mpsc::Sender<AsyncMessage>,
3905 language: &str,
3906 server_name: &str,
3907 server_command: &str,
3908 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
3909 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3910 config_options: &Arc<std::sync::Mutex<Option<Value>>>,
3911 capabilities: &Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
3912) -> Result<(), String> {
3913 match message {
3914 JsonRpcMessage::Response(response) => {
3915 tracing::trace!("Received LSP response for request id={}", response.id);
3916 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
3917 let result = if let Some(error) = response.error {
3918 log_response_error(error.code, &error.message, server_name, language);
3919 Err(format!(
3920 "LSP error from '{}' ({}): {} (code {})",
3921 server_name, language, error.message, error.code
3922 ))
3923 } else {
3924 tracing::trace!(
3925 "LSP response success from '{}' ({}) for request id={}",
3926 server_name,
3927 language,
3928 response.id
3929 );
3930 Ok(response.result.unwrap_or(serde_json::Value::Null))
3932 };
3933 let _ = tx.send(result);
3934 } else {
3935 tracing::warn!(
3936 "Received LSP response from '{}' ({}) for unknown request id={}",
3937 server_name,
3938 language,
3939 response.id
3940 );
3941 }
3942 }
3943 JsonRpcMessage::Notification(notification) => {
3944 tracing::trace!("Received LSP notification: {}", notification.method);
3945 handle_notification_dispatch(
3946 notification,
3947 async_tx,
3948 language,
3949 server_name,
3950 document_versions,
3951 )
3952 .await?;
3953 }
3954 JsonRpcMessage::Request(request) => {
3955 tracing::trace!("Received request from server: {}", request.method);
3957 let response = match request.method.as_str() {
3958 "window/workDoneProgress/create" => {
3959 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
3961 null_response(request.id)
3962 }
3963 "workspace/configuration" => {
3964 tracing::trace!(
3971 "Responding to workspace/configuration for {}",
3972 server_command
3973 );
3974
3975 let empty = Vec::new();
3976 let items = request
3977 .params
3978 .as_ref()
3979 .and_then(|p| p.get("items"))
3980 .and_then(|items| items.as_array())
3981 .unwrap_or(&empty);
3982
3983 let stored = config_options.lock().unwrap().clone();
3984 let configs =
3985 resolve_workspace_configuration(items, stored.as_ref(), server_command);
3986
3987 JsonRpcResponse {
3988 jsonrpc: "2.0".to_string(),
3989 id: request.id,
3990 result: Some(Value::Array(configs)),
3991 error: None,
3992 }
3993 }
3994 "client/registerCapability" => {
3995 let registrations = registrations_from_params(request.params.as_ref());
4002 tracing::debug!(
4003 "client/registerCapability (id={}) registering {} method(s): {:?}",
4004 request.id,
4005 registrations.len(),
4006 registrations.iter().map(|(m, _)| m).collect::<Vec<_>>()
4007 );
4008 if !registrations.is_empty() {
4009 sync_raw_capabilities(capabilities, ®istrations, true);
4012 let _ = async_tx.send(AsyncMessage::LspDynamicCapabilities {
4013 language: language.to_string(),
4014 server_name: server_name.to_string(),
4015 register: true,
4016 registrations,
4017 });
4018 }
4019 null_response(request.id)
4020 }
4021 "client/unregisterCapability" => {
4022 let methods = unregistrations_from_params(request.params.as_ref());
4026 tracing::debug!(
4027 "client/unregisterCapability (id={}) unregistering {} method(s): {:?}",
4028 request.id,
4029 methods.len(),
4030 methods
4031 );
4032 if !methods.is_empty() {
4033 let registrations: Vec<(String, Option<Value>)> =
4034 methods.into_iter().map(|m| (m, None)).collect();
4035 sync_raw_capabilities(capabilities, ®istrations, false);
4036 let _ = async_tx.send(AsyncMessage::LspDynamicCapabilities {
4037 language: language.to_string(),
4038 server_name: server_name.to_string(),
4039 register: false,
4040 registrations,
4041 });
4042 }
4043 null_response(request.id)
4044 }
4045 "workspace/diagnostic/refresh" => {
4046 tracing::info!(
4049 "LSP ({}) requested diagnostic refresh (workspace/diagnostic/refresh)",
4050 language
4051 );
4052 let _ = async_tx.send(AsyncMessage::LspDiagnosticRefresh {
4053 language: language.to_string(),
4054 });
4055 null_response(request.id)
4056 }
4057 "workspace/inlayHint/refresh" => {
4058 tracing::info!(
4063 "LSP ({}) requested inlay-hint refresh (workspace/inlayHint/refresh)",
4064 language
4065 );
4066 let _ = async_tx.send(AsyncMessage::LspInlayHintRefresh {
4067 language: language.to_string(),
4068 });
4069 null_response(request.id)
4070 }
4071 "workspace/semanticTokens/refresh" => {
4072 tracing::info!(
4074 "LSP ({}) requested semantic-tokens refresh (workspace/semanticTokens/refresh)",
4075 language
4076 );
4077 let _ = async_tx.send(AsyncMessage::LspSemanticTokensRefresh {
4078 language: language.to_string(),
4079 });
4080 null_response(request.id)
4081 }
4082 "workspace/applyEdit" => {
4083 tracing::info!("LSP ({}) received workspace/applyEdit request", language);
4085 let applied = if let Some(params) = &request.params {
4086 match serde_json::from_value::<lsp_types::ApplyWorkspaceEditParams>(
4087 params.clone(),
4088 ) {
4089 Ok(apply_params) => {
4090 let label = apply_params.label.clone();
4091 let _ = async_tx.send(AsyncMessage::LspApplyEdit {
4092 edit: apply_params.edit,
4093 label,
4094 });
4095 true
4096 }
4097 Err(e) => {
4098 tracing::error!(
4099 "Failed to parse workspace/applyEdit params: {}",
4100 e
4101 );
4102 false
4103 }
4104 }
4105 } else {
4106 false
4107 };
4108 JsonRpcResponse {
4109 jsonrpc: "2.0".to_string(),
4110 id: request.id,
4111 result: Some(serde_json::json!({ "applied": applied })),
4112 error: None,
4113 }
4114 }
4115 _ => {
4116 tracing::debug!("Server request for plugins: {}", request.method);
4118 let _ = async_tx.send(AsyncMessage::LspServerRequest {
4119 language: language.to_string(),
4120 server_command: server_command.to_string(),
4121 method: request.method.clone(),
4122 params: request.params.clone(),
4123 });
4124 null_response(request.id)
4125 }
4126 };
4127
4128 let json = serde_json::to_string(&response)
4130 .map_err(|e| format!("Failed to serialize response: {}", e))?;
4131 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
4132
4133 let mut stdin = stdin_writer.lock().await;
4134 use tokio::io::AsyncWriteExt;
4135 if let Err(e) = stdin.write_all(message.as_bytes()).await {
4136 tracing::error!("Failed to write server response: {}", e);
4137 }
4138 if let Err(e) = stdin.flush().await {
4139 tracing::error!("Failed to flush server response: {}", e);
4140 }
4141 tracing::trace!("Sent response to server request id={}", response.id);
4142 }
4143 }
4144 Ok(())
4145}
4146
4147#[allow(clippy::let_underscore_must_use)] async fn handle_notification_dispatch(
4150 notification: JsonRpcNotification,
4151 async_tx: &std_mpsc::Sender<AsyncMessage>,
4152 language: &str,
4153 server_name: &str,
4154 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
4155) -> Result<(), String> {
4156 match notification.method.as_str() {
4157 PublishDiagnostics::METHOD => {
4158 if let Some(params) = notification.params {
4159 let params: PublishDiagnosticsParams = serde_json::from_value(params)
4160 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
4161
4162 if let Some(diag_version) = params.version {
4166 let path = PathBuf::from(params.uri.path().as_str());
4167 let current_version = document_versions.lock().unwrap().get(&path).copied();
4168 if let Some(current) = current_version {
4169 if (diag_version as i64) < current {
4170 tracing::debug!(
4171 "LSP ({}): dropping stale diagnostics for {} (diag version {} < current {})",
4172 language,
4173 params.uri.as_str(),
4174 diag_version,
4175 current
4176 );
4177 return Ok(());
4178 }
4179 }
4180 }
4181
4182 tracing::trace!(
4183 "Received {} diagnostics for {}",
4184 params.diagnostics.len(),
4185 params.uri.as_str()
4186 );
4187
4188 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
4190 uri: params.uri.to_string(),
4191 diagnostics: params.diagnostics,
4192 server_name: server_name.to_string(),
4193 });
4194 }
4195 }
4196 "window/showMessage" => {
4197 if let Some((message_type, message)) = parse_window_message(notification.params, 3) {
4198 log_lsp_message(message_type, language, &message);
4199 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
4200 language: language.to_string(),
4201 message_type,
4202 message,
4203 });
4204 }
4205 }
4206 "window/logMessage" => {
4207 if let Some((message_type, message)) = parse_window_message(notification.params, 4) {
4208 log_lsp_message(message_type, language, &message);
4209 let _ = async_tx.send(AsyncMessage::LspLogMessage {
4210 language: language.to_string(),
4211 message_type,
4212 message,
4213 });
4214 }
4215 }
4216 "$/progress" => {
4217 if let Some((token, value)) = parse_progress_notification(notification.params, language)
4218 {
4219 let _ = async_tx.send(AsyncMessage::LspProgress {
4220 language: language.to_string(),
4221 token,
4222 value,
4223 });
4224 }
4225 }
4226 "experimental/serverStatus" => {
4227 if let Some(params) = notification.params {
4230 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
4231 {
4232 let quiescent = status
4233 .get("quiescent")
4234 .and_then(|v| v.as_bool())
4235 .unwrap_or(false);
4236
4237 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
4238
4239 if quiescent {
4240 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
4242 language: language.to_string(),
4243 });
4244 }
4245 }
4246 }
4247 }
4248 _ => {
4249 tracing::debug!("Unhandled notification: {}", notification.method);
4250 }
4251 }
4252
4253 Ok(())
4254}
4255
4256static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
4258
4259pub struct LspHandle {
4261 id: u64,
4263
4264 scope: crate::services::lsp::manager::LanguageScope,
4266
4267 command_tx: mpsc::Sender<LspCommand>,
4269
4270 state: Arc<Mutex<LspClientState>>,
4272
4273 runtime: tokio::runtime::Handle,
4275
4276 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
4279}
4280
4281#[allow(clippy::let_underscore_must_use)]
4285impl LspHandle {
4286 #[allow(clippy::too_many_arguments)]
4295 pub fn spawn(
4296 runtime: &tokio::runtime::Handle,
4297 command: &str,
4298 args: &[String],
4299 env: std::collections::HashMap<String, String>,
4300 scope: crate::services::lsp::manager::LanguageScope,
4301 server_name: String,
4302 async_bridge: &AsyncBridge,
4303 process_limits: ProcessLimits,
4304 language_id_overrides: std::collections::HashMap<String, String>,
4305 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
4306 ) -> Result<Self, String> {
4307 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
4309 let language_label = scope.label().to_string();
4310 let language_clone = language_label.clone();
4311 let server_name_clone = server_name.clone();
4312 let command = command.to_string();
4313 let args = args.to_vec();
4314 let state = Arc::new(Mutex::new(LspClientState::Starting));
4315
4316 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language_label);
4318
4319 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4321 language: language_label.clone(),
4322 server_name: server_name_clone.clone(),
4323 status: LspServerStatus::Starting,
4324 message: None,
4325 });
4326
4327 let document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>> =
4331 Arc::new(std::sync::Mutex::new(HashMap::new()));
4332 let document_versions_for_task = document_versions.clone();
4333
4334 let state_clone = state.clone();
4335 let stderr_log_path_clone = stderr_log_path.clone();
4336 runtime.spawn(async move {
4337 match LspTask::spawn(
4338 &command,
4339 &args,
4340 &env,
4341 language_clone.clone(),
4342 server_name_clone.clone(),
4343 async_tx.clone(),
4344 &process_limits,
4345 stderr_log_path_clone.clone(),
4346 language_id_overrides,
4347 document_versions_for_task,
4348 long_running_spawner,
4349 )
4350 .await
4351 {
4352 Ok(task) => {
4353 task.run(command_rx).await;
4354 }
4355 Err(e) => {
4356 tracing::error!("Failed to spawn LSP task: {}", e);
4357
4358 let stub = format!(
4373 "[fresh] LSP server '{}' for {} failed to spawn:\n {}\n\n\
4374 Configured command: {} {}\n",
4375 server_name_clone,
4376 language_clone,
4377 e,
4378 command,
4379 args.join(" "),
4380 );
4381 if let Err(write_err) = std::fs::write(&stderr_log_path_clone, stub.as_bytes())
4382 {
4383 tracing::warn!(
4384 "Failed to write LSP failure-stub log for {}: {}",
4385 language_clone,
4386 write_err,
4387 );
4388 }
4389
4390 if let Ok(mut s) = state_clone.lock() {
4392 let _ = s.transition_to(LspClientState::Error);
4393 }
4394
4395 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4396 language: language_clone.clone(),
4397 server_name: server_name_clone.clone(),
4398 status: LspServerStatus::Error,
4399 message: None,
4400 });
4401 let _ = async_tx.send(AsyncMessage::LspError {
4402 language: language_clone,
4403 error: e,
4404 stderr_log_path: Some(stderr_log_path_clone),
4405 });
4406 }
4407 }
4408 });
4409
4410 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4411
4412 Ok(Self {
4413 id,
4414 scope,
4415 command_tx,
4416 state,
4417 runtime: runtime.clone(),
4418 document_versions,
4419 })
4420 }
4421
4422 pub fn id(&self) -> u64 {
4424 self.id
4425 }
4426
4427 pub fn scope(&self) -> &crate::services::lsp::manager::LanguageScope {
4429 &self.scope
4430 }
4431
4432 pub fn document_version(&self, path: &std::path::Path) -> Option<i64> {
4435 self.document_versions
4436 .lock()
4437 .ok()
4438 .and_then(|versions| versions.get(path).copied())
4439 }
4440
4441 pub fn initialize(
4450 &self,
4451 root_uri: Option<Uri>,
4452 initialization_options: Option<Value>,
4453 ) -> Result<(), String> {
4454 {
4456 let mut state = self.state.lock().unwrap();
4457 if !state.can_initialize() {
4458 return Err(format!(
4459 "Cannot initialize: client is in state {:?}",
4460 *state
4461 ));
4462 }
4463 state.transition_to(LspClientState::Initializing)?;
4465 }
4466
4467 let state = self.state.clone();
4468
4469 let (tx, rx) = oneshot::channel();
4471
4472 self.command_tx
4473 .try_send(LspCommand::Initialize {
4474 root_uri,
4475 initialization_options,
4476 response: tx,
4477 })
4478 .map_err(|_| "Failed to send initialize command".to_string())?;
4479
4480 let runtime = self.runtime.clone();
4482 runtime.spawn(async move {
4483 match tokio::time::timeout(std::time::Duration::from_secs(60), rx).await {
4484 Ok(Ok(Ok(_))) => {
4485 if let Ok(mut s) = state.lock() {
4487 let _ = s.transition_to(LspClientState::Running);
4488 }
4489 tracing::info!("LSP initialization completed successfully");
4490 }
4491 Ok(Ok(Err(e))) => {
4492 tracing::error!("LSP initialization failed: {}", e);
4493 if let Ok(mut s) = state.lock() {
4494 let _ = s.transition_to(LspClientState::Error);
4495 }
4496 }
4497 Ok(Err(_)) => {
4498 tracing::error!("LSP initialization response channel closed");
4499 if let Ok(mut s) = state.lock() {
4500 let _ = s.transition_to(LspClientState::Error);
4501 }
4502 }
4503 Err(_) => {
4504 tracing::error!("LSP initialization timed out after 60 seconds");
4505 if let Ok(mut s) = state.lock() {
4506 let _ = s.transition_to(LspClientState::Error);
4507 }
4508 }
4509 }
4510 });
4511
4512 Ok(())
4513 }
4514
4515 pub fn is_initialized(&self) -> bool {
4517 self.state.lock().unwrap().can_send_requests()
4518 }
4519
4520 pub fn state(&self) -> LspClientState {
4522 *self.state.lock().unwrap()
4523 }
4524
4525 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
4531 if !self.scope.accepts(&language_id) {
4533 tracing::warn!(
4534 "did_open: document language '{}' not accepted by LSP handle (serves {:?}) for {}",
4535 language_id,
4536 self.scope,
4537 uri.as_str()
4538 );
4539 return Err(format!(
4540 "Language mismatch: document is '{}' but LSP serves {:?}",
4541 language_id, self.scope
4542 ));
4543 }
4544
4545 self.command_tx
4547 .try_send(LspCommand::DidOpen {
4548 uri,
4549 text,
4550 language_id,
4551 })
4552 .map_err(|_| "Failed to send did_open command".to_string())
4553 }
4554
4555 pub fn did_change(
4557 &self,
4558 uri: Uri,
4559 content_changes: Vec<TextDocumentContentChangeEvent>,
4560 ) -> Result<(), String> {
4561 self.command_tx
4563 .try_send(LspCommand::DidChange {
4564 uri,
4565 content_changes,
4566 })
4567 .map_err(|_| "Failed to send did_change command".to_string())
4568 }
4569
4570 pub fn did_close(&self, uri: Uri) -> Result<(), String> {
4572 self.command_tx
4573 .try_send(LspCommand::DidClose { uri })
4574 .map_err(|_| "Failed to send did_close command".to_string())
4575 }
4576
4577 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
4579 self.command_tx
4580 .try_send(LspCommand::DidSave { uri, text })
4581 .map_err(|_| "Failed to send did_save command".to_string())
4582 }
4583
4584 pub fn add_workspace_folder(&self, uri: lsp_types::Uri, name: String) -> Result<(), String> {
4586 self.command_tx
4587 .try_send(LspCommand::DidChangeWorkspaceFolders {
4588 added: vec![lsp_types::WorkspaceFolder { uri, name }],
4589 removed: vec![],
4590 })
4591 .map_err(|_| "Failed to send workspace folder change".to_string())
4592 }
4593
4594 pub fn completion(
4596 &self,
4597 request_id: u64,
4598 uri: Uri,
4599 line: u32,
4600 character: u32,
4601 ) -> Result<(), String> {
4602 self.command_tx
4603 .try_send(LspCommand::Completion {
4604 request_id,
4605 uri,
4606 line,
4607 character,
4608 })
4609 .map_err(|_| "Failed to send completion command".to_string())
4610 }
4611
4612 pub fn goto_definition(
4614 &self,
4615 request_id: u64,
4616 uri: Uri,
4617 line: u32,
4618 character: u32,
4619 ) -> Result<(), String> {
4620 self.command_tx
4621 .try_send(LspCommand::GotoDefinition {
4622 request_id,
4623 uri,
4624 line,
4625 character,
4626 })
4627 .map_err(|_| "Failed to send goto_definition command".to_string())
4628 }
4629
4630 pub fn rename(
4632 &self,
4633 request_id: u64,
4634 uri: Uri,
4635 line: u32,
4636 character: u32,
4637 new_name: String,
4638 ) -> Result<(), String> {
4639 self.command_tx
4640 .try_send(LspCommand::Rename {
4641 request_id,
4642 uri,
4643 line,
4644 character,
4645 new_name,
4646 })
4647 .map_err(|_| "Failed to send rename command".to_string())
4648 }
4649
4650 pub fn hover(
4652 &self,
4653 request_id: u64,
4654 uri: Uri,
4655 line: u32,
4656 character: u32,
4657 ) -> Result<(), String> {
4658 self.command_tx
4659 .try_send(LspCommand::Hover {
4660 request_id,
4661 uri,
4662 line,
4663 character,
4664 })
4665 .map_err(|_| "Failed to send hover command".to_string())
4666 }
4667
4668 pub fn references(
4670 &self,
4671 request_id: u64,
4672 uri: Uri,
4673 line: u32,
4674 character: u32,
4675 ) -> Result<(), String> {
4676 self.command_tx
4677 .try_send(LspCommand::References {
4678 request_id,
4679 uri,
4680 line,
4681 character,
4682 })
4683 .map_err(|_| "Failed to send references command".to_string())
4684 }
4685
4686 pub fn signature_help(
4688 &self,
4689 request_id: u64,
4690 uri: Uri,
4691 line: u32,
4692 character: u32,
4693 ) -> Result<(), String> {
4694 self.command_tx
4695 .try_send(LspCommand::SignatureHelp {
4696 request_id,
4697 uri,
4698 line,
4699 character,
4700 })
4701 .map_err(|_| "Failed to send signature_help command".to_string())
4702 }
4703
4704 #[allow(clippy::too_many_arguments)]
4706 pub fn code_actions(
4707 &self,
4708 request_id: u64,
4709 uri: Uri,
4710 start_line: u32,
4711 start_char: u32,
4712 end_line: u32,
4713 end_char: u32,
4714 diagnostics: Vec<lsp_types::Diagnostic>,
4715 ) -> Result<(), String> {
4716 self.command_tx
4717 .try_send(LspCommand::CodeActions {
4718 request_id,
4719 uri,
4720 start_line,
4721 start_char,
4722 end_line,
4723 end_char,
4724 diagnostics,
4725 })
4726 .map_err(|_| "Failed to send code_actions command".to_string())
4727 }
4728
4729 pub fn execute_command(
4734 &self,
4735 command: String,
4736 arguments: Option<Vec<Value>>,
4737 ) -> Result<(), String> {
4738 self.command_tx
4739 .try_send(LspCommand::ExecuteCommand { command, arguments })
4740 .map_err(|_| "Failed to send execute_command command".to_string())
4741 }
4742
4743 pub fn code_action_resolve(
4748 &self,
4749 request_id: u64,
4750 action: lsp_types::CodeAction,
4751 ) -> Result<(), String> {
4752 self.command_tx
4753 .try_send(LspCommand::CodeActionResolve {
4754 request_id,
4755 action: Box::new(action),
4756 })
4757 .map_err(|_| "Failed to send code_action_resolve command".to_string())
4758 }
4759
4760 pub fn completion_resolve(
4762 &self,
4763 request_id: u64,
4764 item: lsp_types::CompletionItem,
4765 ) -> Result<(), String> {
4766 self.command_tx
4767 .try_send(LspCommand::CompletionResolve {
4768 request_id,
4769 item: Box::new(item),
4770 })
4771 .map_err(|_| "Failed to send completion_resolve command".to_string())
4772 }
4773
4774 pub fn document_formatting(
4776 &self,
4777 request_id: u64,
4778 uri: Uri,
4779 tab_size: u32,
4780 insert_spaces: bool,
4781 ) -> Result<(), String> {
4782 self.command_tx
4783 .try_send(LspCommand::DocumentFormatting {
4784 request_id,
4785 uri,
4786 tab_size,
4787 insert_spaces,
4788 })
4789 .map_err(|_| "Failed to send document_formatting command".to_string())
4790 }
4791
4792 #[allow(clippy::too_many_arguments)]
4794 pub fn document_range_formatting(
4795 &self,
4796 request_id: u64,
4797 uri: Uri,
4798 start_line: u32,
4799 start_char: u32,
4800 end_line: u32,
4801 end_char: u32,
4802 tab_size: u32,
4803 insert_spaces: bool,
4804 ) -> Result<(), String> {
4805 self.command_tx
4806 .try_send(LspCommand::DocumentRangeFormatting {
4807 request_id,
4808 uri,
4809 start_line,
4810 start_char,
4811 end_line,
4812 end_char,
4813 tab_size,
4814 insert_spaces,
4815 })
4816 .map_err(|_| "Failed to send document_range_formatting command".to_string())
4817 }
4818
4819 pub fn prepare_rename(
4821 &self,
4822 request_id: u64,
4823 uri: Uri,
4824 line: u32,
4825 character: u32,
4826 ) -> Result<(), String> {
4827 self.command_tx
4828 .try_send(LspCommand::PrepareRename {
4829 request_id,
4830 uri,
4831 line,
4832 character,
4833 })
4834 .map_err(|_| "Failed to send prepare_rename command".to_string())
4835 }
4836
4837 pub fn document_diagnostic(
4842 &self,
4843 request_id: u64,
4844 uri: Uri,
4845 previous_result_id: Option<String>,
4846 ) -> Result<(), String> {
4847 self.command_tx
4848 .try_send(LspCommand::DocumentDiagnostic {
4849 request_id,
4850 uri,
4851 previous_result_id,
4852 })
4853 .map_err(|_| "Failed to send document_diagnostic command".to_string())
4854 }
4855
4856 pub fn inlay_hints(
4860 &self,
4861 request_id: u64,
4862 uri: Uri,
4863 start_line: u32,
4864 start_char: u32,
4865 end_line: u32,
4866 end_char: u32,
4867 ) -> Result<(), String> {
4868 self.command_tx
4869 .try_send(LspCommand::InlayHints {
4870 request_id,
4871 uri,
4872 start_line,
4873 start_char,
4874 end_line,
4875 end_char,
4876 })
4877 .map_err(|_| "Failed to send inlay_hints command".to_string())
4878 }
4879
4880 pub fn folding_ranges(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4882 self.command_tx
4883 .try_send(LspCommand::FoldingRange { request_id, uri })
4884 .map_err(|_| "Failed to send folding_range command".to_string())
4885 }
4886
4887 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4889 self.command_tx
4890 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
4891 .map_err(|_| "Failed to send semantic_tokens command".to_string())
4892 }
4893
4894 pub fn semantic_tokens_full_delta(
4896 &self,
4897 request_id: u64,
4898 uri: Uri,
4899 previous_result_id: String,
4900 ) -> Result<(), String> {
4901 self.command_tx
4902 .try_send(LspCommand::SemanticTokensFullDelta {
4903 request_id,
4904 uri,
4905 previous_result_id,
4906 })
4907 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
4908 }
4909
4910 pub fn semantic_tokens_range(
4912 &self,
4913 request_id: u64,
4914 uri: Uri,
4915 range: lsp_types::Range,
4916 ) -> Result<(), String> {
4917 self.command_tx
4918 .try_send(LspCommand::SemanticTokensRange {
4919 request_id,
4920 uri,
4921 range,
4922 })
4923 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
4924 }
4925
4926 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
4931 self.command_tx
4932 .try_send(LspCommand::CancelRequest { request_id })
4933 .map_err(|_| "Failed to send cancel_request command".to_string())
4934 }
4935
4936 pub fn send_plugin_request(
4938 &self,
4939 request_id: u64,
4940 method: String,
4941 params: Option<Value>,
4942 ) -> Result<(), String> {
4943 tracing::trace!(
4944 "LspHandle sending plugin request {}: method={}",
4945 request_id,
4946 method
4947 );
4948 match self.command_tx.try_send(LspCommand::PluginRequest {
4949 request_id,
4950 method,
4951 params,
4952 }) {
4953 Ok(()) => {
4954 tracing::trace!(
4955 "LspHandle enqueued plugin request {} successfully",
4956 request_id
4957 );
4958 Ok(())
4959 }
4960 Err(e) => {
4961 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
4962 Err("Failed to send plugin LSP request".to_string())
4963 }
4964 }
4965 }
4966
4967 pub fn shutdown(&self) -> Result<(), String> {
4969 {
4971 let mut state = self.state.lock().unwrap();
4972 if let Err(e) = state.transition_to(LspClientState::Stopping) {
4973 tracing::warn!("State transition warning during shutdown: {}", e);
4974 }
4976 }
4977
4978 self.command_tx
4979 .try_send(LspCommand::Shutdown)
4980 .map_err(|_| "Failed to send shutdown command".to_string())?;
4981
4982 {
4985 let mut state = self.state.lock().unwrap();
4986 let _ = state.transition_to(LspClientState::Stopped);
4987 }
4988
4989 Ok(())
4990 }
4991}
4992
4993#[allow(clippy::let_underscore_must_use)] impl Drop for LspHandle {
4995 fn drop(&mut self) {
4996 let _ = self.command_tx.try_send(LspCommand::Shutdown);
5002
5003 if let Ok(mut state) = self.state.lock() {
5005 let _ = state.transition_to(LspClientState::Stopped);
5006 }
5007 }
5008}
5009
5010#[cfg(test)]
5011mod tests {
5012 use super::*;
5013 use crate::services::lsp::manager::LanguageScope;
5014 use crate::services::remote::LocalLongRunningSpawner;
5015
5016 fn config_item(section: &str) -> Value {
5018 serde_json::json!({ "section": section })
5019 }
5020
5021 #[test]
5022 fn workspace_configuration_resolves_section_from_init_options() {
5023 let opts = serde_json::json!({
5026 "harper-ls": { "linters": { "SpellCheck": false } }
5027 });
5028 let configs =
5029 resolve_workspace_configuration(&[config_item("harper-ls")], Some(&opts), "harper-ls");
5030 assert_eq!(
5031 configs,
5032 vec![serde_json::json!({ "linters": { "SpellCheck": false } })]
5033 );
5034 }
5035
5036 #[test]
5037 fn workspace_configuration_resolves_dotted_section() {
5038 let opts = serde_json::json!({ "a": { "b": { "c": 1 } } });
5039 let configs =
5040 resolve_workspace_configuration(&[config_item("a.b")], Some(&opts), "some-ls");
5041 assert_eq!(configs, vec![serde_json::json!({ "c": 1 })]);
5042 }
5043
5044 #[test]
5045 fn workspace_configuration_unknown_section_is_null_for_non_rust() {
5046 let opts = serde_json::json!({ "harper-ls": { "linters": {} } });
5049 let configs =
5050 resolve_workspace_configuration(&[config_item("marksman")], Some(&opts), "marksman");
5051 assert_eq!(configs, vec![Value::Null]);
5052 }
5053
5054 #[test]
5055 fn workspace_configuration_rust_analyzer_default_enables_inlay_hints() {
5056 for command in [
5058 "rust-analyzer",
5059 "/usr/local/bin/rust-analyzer",
5060 "custom-rust-analyzer",
5061 ] {
5062 let configs =
5063 resolve_workspace_configuration(&[config_item("rust-analyzer")], None, command);
5064 assert_eq!(configs.len(), 1);
5065 assert_eq!(
5066 configs[0]["inlayHints"]["typeHints"]["enable"], true,
5067 "{command}"
5068 );
5069 }
5070 }
5071
5072 #[test]
5073 fn workspace_configuration_non_rust_without_options_is_null() {
5074 let configs =
5075 resolve_workspace_configuration(&[config_item("harper-ls")], None, "harper-ls");
5076 assert_eq!(configs, vec![Value::Null]);
5077 }
5078
5079 #[test]
5080 fn workspace_configuration_one_response_per_item() {
5081 let opts = serde_json::json!({ "a": 1, "b": 2 });
5082 let configs = resolve_workspace_configuration(
5083 &[config_item("a"), config_item("b"), config_item("missing")],
5084 Some(&opts),
5085 "some-ls",
5086 );
5087 assert_eq!(
5088 configs,
5089 vec![serde_json::json!(1), serde_json::json!(2), Value::Null]
5090 );
5091 }
5092
5093 #[test]
5094 fn workspace_configuration_no_items_returns_whole_object() {
5095 let opts = serde_json::json!({ "linters": { "SpellCheck": false } });
5097 let configs = resolve_workspace_configuration(&[], Some(&opts), "harper-ls");
5098 assert_eq!(configs, vec![opts]);
5099 }
5100
5101 fn local_spawner() -> Arc<dyn crate::services::remote::LongRunningSpawner> {
5104 Arc::new(LocalLongRunningSpawner::new(
5105 Arc::new(crate::services::env_provider::EnvProvider::inactive()),
5106 Arc::new(crate::services::workspace_trust::WorkspaceTrust::permissive()),
5107 ))
5108 }
5109
5110 #[test]
5111 fn test_json_rpc_request_serialization() {
5112 let request = JsonRpcRequest {
5113 jsonrpc: "2.0".to_string(),
5114 id: 1,
5115 method: "initialize".to_string(),
5116 params: Some(serde_json::json!({"rootUri": "file:///test"})),
5117 };
5118
5119 let json = serde_json::to_string(&request).unwrap();
5120 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5121 assert!(json.contains("\"id\":1"));
5122 assert!(json.contains("\"method\":\"initialize\""));
5123 assert!(json.contains("\"rootUri\":\"file:///test\""));
5124 }
5125
5126 #[test]
5127 fn test_json_rpc_response_serialization() {
5128 let response = JsonRpcResponse {
5129 jsonrpc: "2.0".to_string(),
5130 id: 1,
5131 result: Some(serde_json::json!({"success": true})),
5132 error: None,
5133 };
5134
5135 let json = serde_json::to_string(&response).unwrap();
5136 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5137 assert!(json.contains("\"id\":1"));
5138 assert!(json.contains("\"success\":true"));
5139 assert!(!json.contains("\"error\""));
5140 }
5141
5142 #[test]
5150 fn code_action_capability_advertises_literal_support() {
5151 let caps = create_client_capabilities();
5152 let code_action = caps
5153 .text_document
5154 .as_ref()
5155 .and_then(|td| td.code_action.as_ref())
5156 .expect("code_action capability must be set");
5157
5158 let literal = code_action
5159 .code_action_literal_support
5160 .as_ref()
5161 .expect("codeActionLiteralSupport must be advertised");
5162
5163 let kinds = &literal.code_action_kind.value_set;
5164 for required in [
5165 "",
5166 "quickfix",
5167 "refactor",
5168 "refactor.extract",
5169 "refactor.inline",
5170 "refactor.rewrite",
5171 "source",
5172 "source.organizeImports",
5173 ] {
5174 assert!(
5175 kinds.iter().any(|k| k == required),
5176 "expected codeActionKind value_set to include {required:?}, got {kinds:?}",
5177 );
5178 }
5179 }
5180
5181 #[test]
5182 fn advertises_dynamic_registration_on_honored_capabilities() {
5183 let caps = create_client_capabilities();
5188 let td = caps
5189 .text_document
5190 .as_ref()
5191 .expect("text_document capabilities must be set");
5192
5193 assert_eq!(
5194 td.inlay_hint.as_ref().and_then(|c| c.dynamic_registration),
5195 Some(true),
5196 "inlay_hint must advertise dynamicRegistration"
5197 );
5198 assert_eq!(
5199 td.completion.as_ref().and_then(|c| c.dynamic_registration),
5200 Some(true),
5201 "completion must advertise dynamicRegistration"
5202 );
5203 assert_eq!(
5204 td.formatting.as_ref().and_then(|c| c.dynamic_registration),
5205 Some(true),
5206 "formatting must advertise dynamicRegistration"
5207 );
5208 assert_eq!(
5209 td.document_symbol
5210 .as_ref()
5211 .and_then(|c| c.dynamic_registration),
5212 Some(true),
5213 "document_symbol must advertise dynamicRegistration"
5214 );
5215 assert_eq!(
5216 caps.workspace
5217 .as_ref()
5218 .and_then(|w| w.symbol.as_ref())
5219 .and_then(|s| s.dynamic_registration),
5220 Some(true),
5221 "workspace.symbol must advertise dynamicRegistration"
5222 );
5223 }
5224
5225 #[test]
5226 fn advertises_inlay_hint_and_semantic_tokens_refresh_support() {
5227 let caps = create_client_capabilities();
5231 let workspace = caps.workspace.as_ref().expect("workspace caps must be set");
5232
5233 assert_eq!(
5234 workspace
5235 .inlay_hint
5236 .as_ref()
5237 .and_then(|c| c.refresh_support),
5238 Some(true),
5239 "workspace.inlayHint.refreshSupport must be advertised"
5240 );
5241 assert_eq!(
5242 workspace
5243 .semantic_tokens
5244 .as_ref()
5245 .and_then(|c| c.refresh_support),
5246 Some(true),
5247 "workspace.semanticTokens.refreshSupport must be advertised"
5248 );
5249 }
5250
5251 #[test]
5252 fn sync_raw_capabilities_mirrors_dynamic_diagnostic_provider() {
5253 let caps: Arc<std::sync::Mutex<Option<ServerCapabilities>>> =
5258 Arc::new(std::sync::Mutex::new(Some(ServerCapabilities::default())));
5259 assert!(caps
5260 .lock()
5261 .unwrap()
5262 .as_ref()
5263 .unwrap()
5264 .diagnostic_provider
5265 .is_none());
5266
5267 sync_raw_capabilities(
5268 &caps,
5269 &[("textDocument/diagnostic".to_string(), None)],
5270 true,
5271 );
5272 assert!(
5273 caps.lock()
5274 .unwrap()
5275 .as_ref()
5276 .unwrap()
5277 .diagnostic_provider
5278 .is_some(),
5279 "dynamic diagnostic registration must set diagnostic_provider so pulls aren't skipped"
5280 );
5281
5282 sync_raw_capabilities(
5283 &caps,
5284 &[("textDocument/diagnostic".to_string(), None)],
5285 false,
5286 );
5287 assert!(
5288 caps.lock()
5289 .unwrap()
5290 .as_ref()
5291 .unwrap()
5292 .diagnostic_provider
5293 .is_none(),
5294 "unregister must clear diagnostic_provider"
5295 );
5296 }
5297
5298 #[test]
5299 fn sync_raw_capabilities_ignores_non_diagnostic_methods() {
5300 let caps: Arc<std::sync::Mutex<Option<ServerCapabilities>>> =
5303 Arc::new(std::sync::Mutex::new(None));
5304 sync_raw_capabilities(&caps, &[("textDocument/hover".to_string(), None)], true);
5305 assert!(
5306 caps.lock().unwrap().is_none(),
5307 "a non-diagnostic registration must not materialize the raw snapshot"
5308 );
5309 }
5310
5311 #[test]
5312 fn parses_register_and_unregister_capability_params() {
5313 let register = serde_json::json!({
5314 "registrations": [
5315 { "id": "1", "method": "textDocument/inlayHint" },
5316 {
5317 "id": "2",
5318 "method": "textDocument/completion",
5319 "registerOptions": { "triggerCharacters": ["."] }
5320 }
5321 ]
5322 });
5323 let parsed = registrations_from_params(Some(®ister));
5324 assert_eq!(parsed.len(), 2);
5325 assert_eq!(parsed[0].0, "textDocument/inlayHint");
5326 assert!(parsed[0].1.is_none());
5327 assert_eq!(parsed[1].0, "textDocument/completion");
5328 assert!(parsed[1].1.is_some());
5329
5330 let unregister = serde_json::json!({
5331 "unregisterations": [
5332 { "id": "1", "method": "textDocument/inlayHint" }
5333 ]
5334 });
5335 let methods = unregistrations_from_params(Some(&unregister));
5336 assert_eq!(methods, vec!["textDocument/inlayHint".to_string()]);
5337
5338 assert!(registrations_from_params(Some(&serde_json::json!({ "bogus": 1 }))).is_empty());
5340 assert!(unregistrations_from_params(None).is_empty());
5341 }
5342
5343 #[test]
5344 fn test_json_rpc_error_response() {
5345 let response = JsonRpcResponse {
5346 jsonrpc: "2.0".to_string(),
5347 id: 1,
5348 result: None,
5349 error: Some(JsonRpcError {
5350 code: -32600,
5351 message: "Invalid request".to_string(),
5352 data: None,
5353 }),
5354 };
5355
5356 let json = serde_json::to_string(&response).unwrap();
5357 assert!(json.contains("\"error\""));
5358 assert!(json.contains("\"code\":-32600"));
5359 assert!(json.contains("\"message\":\"Invalid request\""));
5360 }
5361
5362 #[test]
5363 fn test_suppressed_error_codes() {
5364 assert!(is_suppressed_error_code(LSP_ERROR_CONTENT_MODIFIED));
5366 assert!(is_suppressed_error_code(LSP_ERROR_SERVER_CANCELLED));
5367
5368 assert!(!is_suppressed_error_code(-32600)); assert!(!is_suppressed_error_code(-32601)); assert!(!is_suppressed_error_code(-32602)); assert!(!is_suppressed_error_code(-32603)); assert!(!is_suppressed_error_code(-32700)); assert!(!is_suppressed_error_code(0));
5378 }
5379
5380 fn capture_warn_logs(body: impl FnOnce()) -> (bool, String) {
5384 use std::time::Duration;
5385 use tempfile::NamedTempFile;
5386 use tracing_subscriber::prelude::*;
5387
5388 let log_file = NamedTempFile::new().unwrap();
5389 let log_path = log_file.into_temp_path();
5390 let (layer, handle) =
5391 crate::services::warning_log::create_with_path(log_path.to_path_buf()).unwrap();
5392 let subscriber = tracing_subscriber::registry().with(layer);
5393
5394 tracing::subscriber::with_default(subscriber, body);
5395
5396 let emitted = handle
5397 .receiver
5398 .recv_timeout(Duration::from_millis(100))
5399 .is_ok();
5400 let contents = std::fs::read_to_string(&log_path).unwrap_or_default();
5401 (emitted, contents)
5402 }
5403
5404 #[test]
5405 fn test_content_modified_and_server_cancelled_are_not_logged_as_warn() {
5406 for code in [LSP_ERROR_CONTENT_MODIFIED, LSP_ERROR_SERVER_CANCELLED] {
5407 let (emitted, contents) = capture_warn_logs(|| {
5408 log_response_error(code, "expected during editing", "rust-analyzer", "rust");
5409 });
5410 assert!(
5411 !emitted,
5412 "code {} must not notify the WARN channel; got log:\n{}",
5413 code, contents
5414 );
5415 }
5416 }
5417
5418 #[test]
5419 fn test_method_not_found_still_surfaces_as_warn() {
5420 let (emitted, contents) = capture_warn_logs(|| {
5424 log_response_error(
5425 -32601,
5426 "Unhandled method textDocument/inlayHint",
5427 "vscode-json-language-server",
5428 "json",
5429 );
5430 });
5431 assert!(
5432 emitted,
5433 "MethodNotFound should notify the WARN channel so the mismatch is visible"
5434 );
5435 assert!(
5436 contents.contains("code -32601"),
5437 "WARN log should record the error code; got:\n{}",
5438 contents
5439 );
5440 }
5441
5442 #[test]
5443 fn test_non_suppressed_errors_still_warn() {
5444 let (emitted, contents) = capture_warn_logs(|| {
5447 log_response_error(-32603, "internal error", "rust-analyzer", "rust");
5448 });
5449 assert!(
5450 emitted,
5451 "non-suppressed error codes should notify the WARN channel"
5452 );
5453 assert!(
5454 contents.contains("code -32603"),
5455 "WARN log should record the error code; got:\n{}",
5456 contents
5457 );
5458 assert!(
5459 contents.contains("rust-analyzer"),
5460 "WARN log should record the server name; got:\n{}",
5461 contents
5462 );
5463 }
5464
5465 #[test]
5466 fn test_json_rpc_notification_serialization() {
5467 let notification = JsonRpcNotification {
5468 jsonrpc: "2.0".to_string(),
5469 method: "textDocument/didOpen".to_string(),
5470 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
5471 };
5472
5473 let json = serde_json::to_string(¬ification).unwrap();
5474 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5475 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
5476 assert!(json.contains("\"uri\":\"file:///test.rs\""));
5477 assert!(!json.contains("\"id\"")); }
5479
5480 #[test]
5481 fn test_json_rpc_message_deserialization_request() {
5482 let json =
5483 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
5484 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5485
5486 match message {
5487 JsonRpcMessage::Request(request) => {
5488 assert_eq!(request.jsonrpc, "2.0");
5489 assert_eq!(request.id, 1);
5490 assert_eq!(request.method, "initialize");
5491 assert!(request.params.is_some());
5492 }
5493 _ => panic!("Expected Request"),
5494 }
5495 }
5496
5497 #[test]
5498 fn test_json_rpc_message_deserialization_response() {
5499 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
5500 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5501
5502 match message {
5503 JsonRpcMessage::Response(response) => {
5504 assert_eq!(response.jsonrpc, "2.0");
5505 assert_eq!(response.id, 1);
5506 assert!(response.result.is_some());
5507 assert!(response.error.is_none());
5508 }
5509 _ => panic!("Expected Response"),
5510 }
5511 }
5512
5513 #[test]
5514 fn test_json_rpc_message_deserialization_notification() {
5515 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
5516 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5517
5518 match message {
5519 JsonRpcMessage::Notification(notification) => {
5520 assert_eq!(notification.jsonrpc, "2.0");
5521 assert_eq!(notification.method, "textDocument/didOpen");
5522 assert!(notification.params.is_some());
5523 }
5524 _ => panic!("Expected Notification"),
5525 }
5526 }
5527
5528 #[test]
5529 fn test_json_rpc_error_deserialization() {
5530 let json =
5531 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
5532 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5533
5534 match message {
5535 JsonRpcMessage::Response(response) => {
5536 assert_eq!(response.jsonrpc, "2.0");
5537 assert_eq!(response.id, 1);
5538 assert!(response.result.is_none());
5539 assert!(response.error.is_some());
5540 let error = response.error.unwrap();
5541 assert_eq!(error.code, -32600);
5542 assert_eq!(error.message, "Invalid request");
5543 }
5544 _ => panic!("Expected Response with error"),
5545 }
5546 }
5547
5548 #[tokio::test]
5549 async fn test_lsp_handle_spawn_and_drop() {
5550 let runtime = tokio::runtime::Handle::current();
5553 let async_bridge = AsyncBridge::new();
5554
5555 let result = LspHandle::spawn(
5558 &runtime,
5559 "cat",
5560 &[],
5561 Default::default(),
5562 LanguageScope::single("test"),
5563 "test-server".to_string(),
5564 &async_bridge,
5565 ProcessLimits::unlimited(),
5566 Default::default(),
5567 local_spawner(),
5568 );
5569
5570 assert!(result.is_ok());
5572
5573 let handle = result.unwrap();
5574
5575 drop(handle);
5577
5578 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
5580 }
5581
5582 #[tokio::test]
5583 async fn test_lsp_handle_did_open_queues_before_initialization() {
5584 let runtime = tokio::runtime::Handle::current();
5585 let async_bridge = AsyncBridge::new();
5586
5587 let handle = LspHandle::spawn(
5588 &runtime,
5589 "cat",
5590 &[],
5591 Default::default(),
5592 LanguageScope::single("test"),
5593 "test-server".to_string(),
5594 &async_bridge,
5595 ProcessLimits::unlimited(),
5596 Default::default(),
5597 local_spawner(),
5598 )
5599 .unwrap();
5600
5601 let result = handle.did_open(
5603 "file:///test.txt".parse().unwrap(),
5604 "fn main() {}".to_string(),
5605 "test".to_string(),
5606 );
5607
5608 assert!(result.is_ok());
5610 }
5611
5612 #[tokio::test]
5613 async fn test_lsp_handle_did_change_queues_before_initialization() {
5614 let runtime = tokio::runtime::Handle::current();
5615 let async_bridge = AsyncBridge::new();
5616
5617 let handle = LspHandle::spawn(
5618 &runtime,
5619 "cat",
5620 &[],
5621 Default::default(),
5622 LanguageScope::single("test"),
5623 "test-server".to_string(),
5624 &async_bridge,
5625 ProcessLimits::unlimited(),
5626 Default::default(),
5627 local_spawner(),
5628 )
5629 .unwrap();
5630
5631 let result = handle.did_change(
5633 "file:///test.rs".parse().unwrap(),
5634 vec![TextDocumentContentChangeEvent {
5635 range: Some(lsp_types::Range::new(
5636 lsp_types::Position::new(0, 0),
5637 lsp_types::Position::new(0, 0),
5638 )),
5639 range_length: None,
5640 text: "fn main() {}".to_string(),
5641 }],
5642 );
5643
5644 assert!(result.is_ok());
5646 }
5647
5648 #[tokio::test]
5649 async fn test_lsp_handle_incremental_change_with_range() {
5650 let runtime = tokio::runtime::Handle::current();
5651 let async_bridge = AsyncBridge::new();
5652
5653 let handle = LspHandle::spawn(
5654 &runtime,
5655 "cat",
5656 &[],
5657 Default::default(),
5658 LanguageScope::single("test"),
5659 "test-server".to_string(),
5660 &async_bridge,
5661 ProcessLimits::unlimited(),
5662 Default::default(),
5663 local_spawner(),
5664 )
5665 .unwrap();
5666
5667 let result = handle.did_change(
5669 "file:///test.rs".parse().unwrap(),
5670 vec![TextDocumentContentChangeEvent {
5671 range: Some(lsp_types::Range::new(
5672 lsp_types::Position::new(0, 3),
5673 lsp_types::Position::new(0, 7),
5674 )),
5675 range_length: None,
5676 text: String::new(), }],
5678 );
5679
5680 assert!(result.is_ok());
5682 }
5683
5684 #[tokio::test]
5685 async fn test_lsp_handle_spawn_invalid_command() {
5686 let runtime = tokio::runtime::Handle::current();
5687 let async_bridge = AsyncBridge::new();
5688
5689 let result = LspHandle::spawn(
5691 &runtime,
5692 "this-command-does-not-exist-12345",
5693 &[],
5694 Default::default(),
5695 LanguageScope::single("test"),
5696 "test-server".to_string(),
5697 &async_bridge,
5698 ProcessLimits::unlimited(),
5699 Default::default(),
5700 local_spawner(),
5701 );
5702
5703 assert!(result.is_ok());
5706
5707 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
5709
5710 let messages = async_bridge.try_recv_all();
5712 assert!(!messages.is_empty());
5713
5714 let has_error = messages
5715 .iter()
5716 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
5717 assert!(has_error, "Expected LspError message");
5718 }
5719
5720 #[test]
5721 fn test_lsp_handle_shutdown_from_sync_context() {
5722 std::thread::spawn(|| {
5725 let rt = tokio::runtime::Runtime::new().unwrap();
5727 let async_bridge = AsyncBridge::new();
5728
5729 let handle = rt.block_on(async {
5730 let runtime = tokio::runtime::Handle::current();
5731 LspHandle::spawn(
5732 &runtime,
5733 "cat",
5734 &[],
5735 Default::default(),
5736 LanguageScope::single("test"),
5737 "test-server".to_string(),
5738 &async_bridge,
5739 ProcessLimits::unlimited(),
5740 Default::default(),
5741 local_spawner(),
5742 )
5743 .unwrap()
5744 });
5745
5746 assert!(handle.shutdown().is_ok());
5748
5749 std::thread::sleep(std::time::Duration::from_millis(50));
5751 })
5752 .join()
5753 .unwrap();
5754 }
5755
5756 #[test]
5757 fn test_lsp_command_debug_format() {
5758 let cmd = LspCommand::Shutdown;
5760 let debug_str = format!("{:?}", cmd);
5761 assert!(debug_str.contains("Shutdown"));
5762 }
5763
5764 #[test]
5765 fn test_lsp_client_state_can_initialize_from_starting() {
5766 let state = LspClientState::Starting;
5772
5773 assert!(
5775 state.can_initialize(),
5776 "Starting state must allow initialization to avoid race condition"
5777 );
5778
5779 let mut state = LspClientState::Starting;
5781
5782 assert!(state.can_transition_to(LspClientState::Initializing));
5784 assert!(state.transition_to(LspClientState::Initializing).is_ok());
5785
5786 assert!(state.can_transition_to(LspClientState::Running));
5788 assert!(state.transition_to(LspClientState::Running).is_ok());
5789 }
5790
5791 #[tokio::test]
5792 async fn test_lsp_handle_initialize_from_starting_state() {
5793 let runtime = tokio::runtime::Handle::current();
5801 let async_bridge = AsyncBridge::new();
5802
5803 let handle = LspHandle::spawn(
5805 &runtime,
5806 "cat", &[],
5808 Default::default(),
5809 LanguageScope::single("test"),
5810 "test-server".to_string(),
5811 &async_bridge,
5812 ProcessLimits::unlimited(),
5813 Default::default(),
5814 local_spawner(),
5815 )
5816 .unwrap();
5817
5818 let result = handle.initialize(None, None);
5821
5822 assert!(
5823 result.is_ok(),
5824 "initialize() must succeed from Starting state. Got error: {:?}",
5825 result.err()
5826 );
5827 }
5828
5829 #[tokio::test]
5830 async fn test_lsp_state_machine_race_condition_fix() {
5831 let runtime = tokio::runtime::Handle::current();
5838 let async_bridge = AsyncBridge::new();
5839
5840 let fake_lsp_script = r#"
5842 read -r line # Read Content-Length header
5843 read -r empty # Read empty line
5844 read -r json # Read JSON body
5845
5846 # Send a valid initialize response
5847 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
5848 echo "Content-Length: ${#response}"
5849 echo ""
5850 echo -n "$response"
5851
5852 # Keep running to avoid EOF
5853 sleep 10
5854 "#;
5855
5856 let handle = LspHandle::spawn(
5858 &runtime,
5859 "bash",
5860 &["-c".to_string(), fake_lsp_script.to_string()],
5861 Default::default(),
5862 LanguageScope::single("fake"),
5863 "test-server".to_string(),
5864 &async_bridge,
5865 ProcessLimits::unlimited(),
5866 Default::default(),
5867 local_spawner(),
5868 )
5869 .unwrap();
5870
5871 let init_result = handle.initialize(None, None);
5873 assert!(
5874 init_result.is_ok(),
5875 "initialize() failed from Starting state: {:?}",
5876 init_result.err()
5877 );
5878
5879 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
5881
5882 let messages = async_bridge.try_recv_all();
5884 let has_status_update = messages
5885 .iter()
5886 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
5887
5888 assert!(
5889 has_status_update,
5890 "Expected status update messages from LSP initialization"
5891 );
5892
5893 #[allow(clippy::let_underscore_must_use)]
5895 let _ = handle.shutdown();
5896 }
5897
5898 #[test]
5899 fn test_lsp_client_state_can_shutdown_from_error() {
5900 let mut state = LspClientState::Error;
5907
5908 assert!(
5909 state.can_transition_to(LspClientState::Stopping),
5910 "Error state must allow transition to Stopping for graceful shutdown"
5911 );
5912 assert!(state.transition_to(LspClientState::Stopping).is_ok());
5913 assert!(state.transition_to(LspClientState::Stopped).is_ok());
5916 }
5917
5918 #[tokio::test]
5919 async fn test_lsp_handle_shutdown_after_spawn_failure_advances_state() {
5920 let runtime = tokio::runtime::Handle::current();
5926 let async_bridge = AsyncBridge::new();
5927
5928 let handle = LspHandle::spawn(
5929 &runtime,
5930 "fresh-nonexistent-lsp-binary-7c93af",
5931 &[],
5932 Default::default(),
5933 LanguageScope::single("test"),
5934 "test-server".to_string(),
5935 &async_bridge,
5936 ProcessLimits::unlimited(),
5937 Default::default(),
5938 local_spawner(),
5939 )
5940 .unwrap();
5941
5942 for _ in 0..200 {
5945 if handle.state() == LspClientState::Error {
5946 break;
5947 }
5948 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
5949 }
5950 assert_eq!(
5951 handle.state(),
5952 LspClientState::Error,
5953 "spawn task should have transitioned to Error after failed spawn"
5954 );
5955
5956 #[allow(clippy::let_underscore_must_use)]
5961 let _ = handle.shutdown();
5962 let final_state = handle.state();
5963 assert!(
5964 matches!(
5965 final_state,
5966 LspClientState::Stopping | LspClientState::Stopped
5967 ),
5968 "shutdown from Error must advance state, got {:?}",
5969 final_state
5970 );
5971 }
5972}