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