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 implementation: bool_or_options(&caps.implementation_provider, |p| match p {
653 lsp_types::ImplementationProviderCapability::Simple(v) => *v,
654 lsp_types::ImplementationProviderCapability::Options(_) => true,
655 }),
656 references: bool_or_options(&caps.references_provider, |p| match p {
657 lsp_types::OneOf::Left(v) => *v,
658 lsp_types::OneOf::Right(_) => true,
659 }),
660 document_formatting: bool_or_options(&caps.document_formatting_provider, |p| match p {
661 lsp_types::OneOf::Left(v) => *v,
662 lsp_types::OneOf::Right(_) => true,
663 }),
664 document_range_formatting: bool_or_options(&caps.document_range_formatting_provider, |p| {
665 match p {
666 lsp_types::OneOf::Left(v) => *v,
667 lsp_types::OneOf::Right(_) => true,
668 }
669 }),
670 rename: bool_or_options(&caps.rename_provider, |p| match p {
671 lsp_types::OneOf::Left(v) => *v,
672 lsp_types::OneOf::Right(_) => true,
673 }),
674 signature_help: caps.signature_help_provider.is_some(),
675 inlay_hints: bool_or_options(&caps.inlay_hint_provider, |p| match p {
676 lsp_types::OneOf::Left(v) => *v,
677 lsp_types::OneOf::Right(_) => true,
678 }),
679 folding_ranges: bool_or_options(&caps.folding_range_provider, |p| match p {
680 lsp_types::FoldingRangeProviderCapability::Simple(v) => *v,
681 _ => true,
682 }),
683 semantic_tokens_full: sem_full,
684 semantic_tokens_full_delta: sem_full_delta,
685 semantic_tokens_range: sem_range,
686 semantic_tokens_legend: sem_legend,
687 document_highlight: bool_or_options(&caps.document_highlight_provider, |p| match p {
688 lsp_types::OneOf::Left(v) => *v,
689 lsp_types::OneOf::Right(_) => true,
690 }),
691 code_action: bool_or_options(&caps.code_action_provider, |p| match p {
692 lsp_types::CodeActionProviderCapability::Simple(v) => *v,
693 lsp_types::CodeActionProviderCapability::Options(_) => true,
694 }),
695 code_action_resolve: caps.code_action_provider.as_ref().is_some_and(|p| match p {
696 lsp_types::CodeActionProviderCapability::Options(opts) => {
697 opts.resolve_provider.unwrap_or(false)
698 }
699 _ => false,
700 }),
701 document_symbols: bool_or_options(&caps.document_symbol_provider, |p| match p {
702 lsp_types::OneOf::Left(v) => *v,
703 lsp_types::OneOf::Right(_) => true,
704 }),
705 workspace_symbols: bool_or_options(&caps.workspace_symbol_provider, |p| match p {
706 lsp_types::OneOf::Left(v) => *v,
707 lsp_types::OneOf::Right(_) => true,
708 }),
709 diagnostics: caps.diagnostic_provider.is_some(),
710 }
711}
712
713fn bool_or_options<T>(opt: &Option<T>, check: impl FnOnce(&T) -> bool) -> bool {
715 opt.as_ref().is_some_and(check)
716}
717
718#[derive(Debug)]
720enum LspCommand {
721 Initialize {
723 root_uri: Option<Uri>,
724 initialization_options: Option<Value>,
725 response: oneshot::Sender<Result<InitializeResult, String>>,
726 },
727
728 DidOpen {
730 uri: Uri,
731 text: String,
732 language_id: String,
733 },
734
735 DidChange {
737 uri: Uri,
738 content_changes: Vec<TextDocumentContentChangeEvent>,
739 },
740
741 DidClose { uri: Uri },
743
744 DidSave { uri: Uri, text: Option<String> },
746
747 DidChangeWorkspaceFolders {
749 added: Vec<lsp_types::WorkspaceFolder>,
750 removed: Vec<lsp_types::WorkspaceFolder>,
751 },
752
753 Completion {
755 request_id: u64,
756 uri: Uri,
757 line: u32,
758 character: u32,
759 },
760
761 GotoDefinition {
763 request_id: u64,
764 uri: Uri,
765 line: u32,
766 character: u32,
767 },
768
769 Implementation {
771 request_id: u64,
772 uri: Uri,
773 line: u32,
774 character: u32,
775 },
776
777 Rename {
779 request_id: u64,
780 uri: Uri,
781 line: u32,
782 character: u32,
783 new_name: String,
784 },
785
786 Hover {
788 request_id: u64,
789 uri: Uri,
790 line: u32,
791 character: u32,
792 },
793
794 References {
796 request_id: u64,
797 uri: Uri,
798 line: u32,
799 character: u32,
800 },
801
802 SignatureHelp {
804 request_id: u64,
805 uri: Uri,
806 line: u32,
807 character: u32,
808 },
809
810 CodeActions {
812 request_id: u64,
813 uri: Uri,
814 start_line: u32,
815 start_char: u32,
816 end_line: u32,
817 end_char: u32,
818 diagnostics: Vec<lsp_types::Diagnostic>,
819 },
820
821 DocumentDiagnostic {
823 request_id: u64,
824 uri: Uri,
825 previous_result_id: Option<String>,
827 },
828
829 InlayHints {
831 request_id: u64,
832 uri: Uri,
833 start_line: u32,
835 start_char: u32,
836 end_line: u32,
837 end_char: u32,
838 },
839
840 FoldingRange { request_id: u64, uri: Uri },
842
843 SemanticTokensFull { request_id: u64, uri: Uri },
845
846 SemanticTokensFullDelta {
848 request_id: u64,
849 uri: Uri,
850 previous_result_id: String,
851 },
852
853 SemanticTokensRange {
855 request_id: u64,
856 uri: Uri,
857 range: lsp_types::Range,
858 },
859
860 ExecuteCommand {
862 command: String,
863 arguments: Option<Vec<Value>>,
864 },
865
866 CodeActionResolve {
868 request_id: u64,
869 action: Box<lsp_types::CodeAction>,
870 },
871
872 CompletionResolve {
874 request_id: u64,
875 item: Box<lsp_types::CompletionItem>,
876 },
877
878 DocumentFormatting {
880 request_id: u64,
881 uri: Uri,
882 tab_size: u32,
883 insert_spaces: bool,
884 },
885
886 DocumentRangeFormatting {
888 request_id: u64,
889 uri: Uri,
890 start_line: u32,
891 start_char: u32,
892 end_line: u32,
893 end_char: u32,
894 tab_size: u32,
895 insert_spaces: bool,
896 },
897
898 PrepareRename {
900 request_id: u64,
901 uri: Uri,
902 line: u32,
903 character: u32,
904 },
905
906 CancelRequest {
908 request_id: u64,
910 },
911
912 PluginRequest {
914 request_id: u64,
915 method: String,
916 params: Option<Value>,
917 },
918
919 Shutdown,
921}
922
923#[derive(Clone)]
930struct LspState {
931 stdin: Arc<tokio::sync::Mutex<ChildStdin>>,
933
934 next_id: Arc<AtomicI64>,
936
937 capabilities: Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
939
940 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
942
943 pending_opens: Arc<std::sync::Mutex<HashMap<PathBuf, Instant>>>,
946
947 initialized: Arc<AtomicBool>,
949
950 async_tx: std_mpsc::Sender<AsyncMessage>,
952
953 language: Arc<String>,
955
956 server_name: Arc<String>,
958
959 active_requests: Arc<std::sync::Mutex<HashMap<u64, i64>>>,
962
963 language_id_overrides: Arc<HashMap<String, String>>,
965}
966
967#[allow(clippy::let_underscore_must_use)]
973impl LspState {
974 async fn replay_pending_commands(&self, commands: Vec<LspCommand>, pending: &PendingRequests) {
976 if commands.is_empty() {
977 return;
978 }
979 tracing::info!(
980 "Replaying {} pending commands after initialization",
981 commands.len()
982 );
983 for cmd in commands {
984 match cmd {
985 LspCommand::DidOpen {
986 uri,
987 text,
988 language_id,
989 } => {
990 tracing::info!("Replaying DidOpen for {}", uri.as_str());
991 let _ = self
992 .handle_did_open_sequential(uri, text, language_id, pending)
993 .await;
994 }
995 LspCommand::DidChange {
996 uri,
997 content_changes,
998 } => {
999 tracing::info!("Replaying DidChange for {}", uri.as_str());
1000 let _ = self
1001 .handle_did_change_sequential(uri, content_changes, pending)
1002 .await;
1003 }
1004 LspCommand::DidClose { uri } => {
1005 tracing::info!("Replaying DidClose for {}", uri.as_str());
1006 let _ = self.handle_did_close(uri).await;
1007 }
1008 LspCommand::DidSave { uri, text } => {
1009 tracing::info!("Replaying DidSave for {}", uri.as_str());
1010 let _ = self.handle_did_save(uri, text).await;
1011 }
1012 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
1013 tracing::info!(
1014 "Replaying DidChangeWorkspaceFolders: +{} -{}",
1015 added.len(),
1016 removed.len()
1017 );
1018 let _ = self
1019 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
1020 lsp_types::DidChangeWorkspaceFoldersParams {
1021 event: lsp_types::WorkspaceFoldersChangeEvent { added, removed },
1022 },
1023 )
1024 .await;
1025 }
1026 LspCommand::SemanticTokensFull { request_id, uri } => {
1027 tracing::info!("Replaying semantic tokens request for {}", uri.as_str());
1028 let s = self.clone();
1029 let p = pending.clone();
1030 tokio::spawn(async move {
1031 let _ = s.handle_semantic_tokens_full(request_id, uri, &p).await;
1032 });
1033 }
1034 LspCommand::SemanticTokensFullDelta {
1035 request_id,
1036 uri,
1037 previous_result_id,
1038 } => {
1039 tracing::info!(
1040 "Replaying semantic tokens delta request for {}",
1041 uri.as_str()
1042 );
1043 let s = self.clone();
1044 let p = pending.clone();
1045 tokio::spawn(async move {
1046 let _ = s
1047 .handle_semantic_tokens_full_delta(
1048 request_id,
1049 uri,
1050 previous_result_id,
1051 &p,
1052 )
1053 .await;
1054 });
1055 }
1056 LspCommand::SemanticTokensRange {
1057 request_id,
1058 uri,
1059 range,
1060 } => {
1061 tracing::info!(
1062 "Replaying semantic tokens range request for {}",
1063 uri.as_str()
1064 );
1065 let s = self.clone();
1066 let p = pending.clone();
1067 tokio::spawn(async move {
1068 let _ = s
1069 .handle_semantic_tokens_range(request_id, uri, range, &p)
1070 .await;
1071 });
1072 }
1073 LspCommand::FoldingRange { request_id, uri } => {
1074 tracing::info!("Replaying folding range request for {}", uri.as_str());
1075 let s = self.clone();
1076 let p = pending.clone();
1077 tokio::spawn(async move {
1078 let _ = s.handle_folding_ranges(request_id, uri, &p).await;
1079 });
1080 }
1081 _ => {}
1082 }
1083 }
1084 }
1085
1086 async fn write_message<T: Serialize>(&self, message: &T) -> Result<(), String> {
1088 let json =
1089 serde_json::to_string(message).map_err(|e| format!("Serialization error: {}", e))?;
1090
1091 let content = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
1092
1093 tracing::trace!("Writing LSP message to stdin ({} bytes)", content.len());
1094
1095 let mut stdin = self.stdin.lock().await;
1096 stdin
1097 .write_all(content.as_bytes())
1098 .await
1099 .map_err(|e| format!("Failed to write to stdin: {}", e))?;
1100
1101 stdin
1102 .flush()
1103 .await
1104 .map_err(|e| format!("Failed to flush stdin: {}", e))?;
1105
1106 tracing::trace!("Successfully sent LSP message");
1107
1108 Ok(())
1109 }
1110
1111 async fn send_notification<N>(&self, params: N::Params) -> Result<(), String>
1113 where
1114 N: Notification,
1115 {
1116 let notification = JsonRpcNotification {
1117 jsonrpc: "2.0".to_string(),
1118 method: N::METHOD.to_string(),
1119 params: Some(
1120 serde_json::to_value(params)
1121 .map_err(|e| format!("Failed to serialize params: {}", e))?,
1122 ),
1123 };
1124
1125 self.write_message(¬ification).await
1126 }
1127
1128 async fn send_request_sequential<P: Serialize, R: for<'de> Deserialize<'de>>(
1130 &self,
1131 method: &str,
1132 params: Option<P>,
1133 pending: &PendingRequests,
1134 ) -> Result<R, String> {
1135 self.send_request_with_timeout(
1136 method,
1137 params,
1138 pending,
1139 None,
1140 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
1141 )
1142 .await
1143 }
1144
1145 async fn send_request_sequential_tracked<P: Serialize, R: for<'de> Deserialize<'de>>(
1147 &self,
1148 method: &str,
1149 params: Option<P>,
1150 pending: &PendingRequests,
1151 editor_request_id: Option<u64>,
1152 ) -> Result<R, String> {
1153 self.send_request_with_timeout(
1154 method,
1155 params,
1156 pending,
1157 editor_request_id,
1158 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
1159 )
1160 .await
1161 }
1162
1163 async fn send_request_with_timeout<P: Serialize, R: for<'de> Deserialize<'de>>(
1169 &self,
1170 method: &str,
1171 params: Option<P>,
1172 pending: &PendingRequests,
1173 editor_request_id: Option<u64>,
1174 timeout: Duration,
1175 ) -> Result<R, String> {
1176 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
1177
1178 if let Some(editor_id) = editor_request_id {
1180 self.active_requests.lock().unwrap().insert(editor_id, id);
1181 tracing::trace!("Tracking request: editor_id={}, lsp_id={}", editor_id, id);
1182 }
1183
1184 let params_value = params
1185 .map(|p| serde_json::to_value(p))
1186 .transpose()
1187 .map_err(|e| format!("Failed to serialize params: {}", e))?;
1188 let request = JsonRpcRequest {
1189 jsonrpc: "2.0".to_string(),
1190 id: JsonRpcId::Number(id),
1191 method: method.to_string(),
1192 params: params_value,
1193 };
1194
1195 let (tx, rx) = oneshot::channel();
1196 pending.lock().unwrap().insert(id, (method.to_string(), tx));
1197
1198 if let Err(e) = self.write_message(&request).await {
1199 pending.lock().unwrap().remove(&id);
1200 if let Some(editor_id) = editor_request_id {
1201 self.active_requests.lock().unwrap().remove(&editor_id);
1202 }
1203 return Err(e);
1204 }
1205
1206 tracing::trace!(
1207 "Sent LSP request id={} method={}, waiting up to {:?} for response",
1208 id,
1209 method,
1210 timeout
1211 );
1212
1213 let response_result = match tokio::time::timeout(timeout, rx).await {
1214 Ok(Ok(inner)) => inner,
1215 Ok(Err(_)) => Err("Response channel closed".to_string()),
1216 Err(_) => {
1217 pending.lock().unwrap().remove(&id);
1219 tracing::warn!(
1220 "LSP request '{}' (lsp_id={}) on '{}' ({}) timed out after {:?}; sending $/cancelRequest",
1221 method,
1222 id,
1223 self.server_name.as_str(),
1224 self.language.as_str(),
1225 timeout
1226 );
1227 let _ = self.send_cancel_request(id).await;
1228 Err(format!(
1229 "Request '{}' timed out after {:?}",
1230 method, timeout
1231 ))
1232 }
1233 };
1234
1235 if let Some(editor_id) = editor_request_id {
1236 self.active_requests.lock().unwrap().remove(&editor_id);
1237 tracing::trace!("Completed request: editor_id={}, lsp_id={}", editor_id, id);
1238 }
1239
1240 let result = response_result?;
1241 serde_json::from_value(result).map_err(|e| format!("Failed to deserialize response: {}", e))
1242 }
1243
1244 async fn handle_initialize_sequential(
1246 &self,
1247 root_uri: Option<Uri>,
1248 initialization_options: Option<Value>,
1249 pending: &PendingRequests,
1250 ) -> Result<InitializeResult, String> {
1251 tracing::info!(
1252 "Initializing async LSP server with root_uri: {:?}, initialization_options: {:?}",
1253 root_uri,
1254 initialization_options
1255 );
1256
1257 let workspace_folders = root_uri.as_ref().map(|uri| {
1258 vec![WorkspaceFolder {
1259 uri: uri.clone(),
1260 name: uri
1261 .path()
1262 .as_str()
1263 .split('/')
1264 .next_back()
1265 .unwrap_or("workspace")
1266 .to_string(),
1267 }]
1268 });
1269
1270 #[allow(deprecated)]
1271 let params = InitializeParams {
1272 process_id: Some(std::process::id()),
1273 capabilities: create_client_capabilities(),
1274 workspace_folders,
1275 initialization_options,
1276 root_uri: root_uri.clone(),
1279 ..Default::default()
1280 };
1281
1282 let result: InitializeResult = self
1283 .send_request_sequential(Initialize::METHOD, Some(params), pending)
1284 .await?;
1285
1286 tracing::info!(
1287 "LSP initialize result: position_encoding={:?}",
1288 result.capabilities.position_encoding
1289 );
1290 *self.capabilities.lock().unwrap() = Some(result.capabilities.clone());
1291
1292 self.send_notification::<Initialized>(InitializedParams {})
1294 .await?;
1295
1296 self.initialized.store(true, Ordering::SeqCst);
1297
1298 let capabilities = extract_capability_summary(&result.capabilities);
1299
1300 let _ = self.async_tx.send(AsyncMessage::LspInitialized {
1302 language: (*self.language).clone(),
1303 server_name: (*self.server_name).clone(),
1304 capabilities,
1305 });
1306
1307 let _ = self.async_tx.send(AsyncMessage::LspStatusUpdate {
1309 language: (*self.language).clone(),
1310 server_name: (*self.server_name).clone(),
1311 status: LspServerStatus::Running,
1312 message: None,
1313 });
1314
1315 tracing::info!("Async LSP server initialized successfully");
1316
1317 Ok(result)
1318 }
1319
1320 async fn handle_did_open_sequential(
1322 &self,
1323 uri: Uri,
1324 text: String,
1325 language_id: String,
1326 _pending: &PendingRequests,
1327 ) -> Result<(), String> {
1328 let path = PathBuf::from(uri.path().as_str());
1329
1330 if should_skip_did_open(&self.document_versions, &path, self.language.as_str(), &uri) {
1331 return Ok(());
1332 }
1333
1334 tracing::trace!("LSP: did_open for {}", uri.as_str());
1335
1336 let lsp_language_id = path
1339 .extension()
1340 .and_then(|e| e.to_str())
1341 .and_then(|ext| self.language_id_overrides.get(ext))
1342 .cloned()
1343 .unwrap_or(language_id);
1344
1345 let params = DidOpenTextDocumentParams {
1346 text_document: TextDocumentItem {
1347 uri: uri.clone(),
1348 language_id: lsp_language_id,
1349 version: 0,
1350 text,
1351 },
1352 };
1353
1354 self.document_versions
1355 .lock()
1356 .unwrap()
1357 .insert(path.clone(), 0);
1358
1359 self.pending_opens
1361 .lock()
1362 .unwrap()
1363 .insert(path, Instant::now());
1364
1365 self.send_notification::<DidOpenTextDocument>(params).await
1366 }
1367
1368 async fn handle_did_change_sequential(
1370 &self,
1371 uri: Uri,
1372 content_changes: Vec<TextDocumentContentChangeEvent>,
1373 _pending: &PendingRequests,
1374 ) -> Result<(), String> {
1375 tracing::trace!("LSP: did_change for {}", uri.as_str());
1376
1377 let path = PathBuf::from(uri.path().as_str());
1378
1379 if !self.document_versions.lock().unwrap().contains_key(&path) {
1382 tracing::debug!(
1383 "LSP ({}): skipping didChange - document not yet opened",
1384 self.language
1385 );
1386 return Ok(());
1387 }
1388
1389 let opened_at = self.pending_opens.lock().unwrap().get(&path).copied();
1393 if let Some(opened_at) = opened_at {
1394 let elapsed = opened_at.elapsed();
1395 let grace_period = std::time::Duration::from_millis(DID_OPEN_GRACE_PERIOD_MS);
1396 if elapsed < grace_period {
1397 let wait_time = grace_period - elapsed;
1398 tracing::debug!(
1399 "LSP ({}): waiting {:?} for didOpen grace period before didChange",
1400 self.language,
1401 wait_time
1402 );
1403 tokio::time::sleep(wait_time).await;
1404 }
1405 self.pending_opens.lock().unwrap().remove(&path);
1407 }
1408
1409 let new_version = {
1410 let mut versions = self.document_versions.lock().unwrap();
1411 let version = versions.entry(path).or_insert(0);
1412 *version += 1;
1413 *version
1414 };
1415
1416 let params = DidChangeTextDocumentParams {
1417 text_document: VersionedTextDocumentIdentifier {
1418 uri: uri.clone(),
1419 version: new_version as i32,
1420 },
1421 content_changes,
1422 };
1423
1424 self.send_notification::<DidChangeTextDocument>(params)
1425 .await
1426 }
1427
1428 async fn handle_did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
1430 tracing::trace!("LSP: did_save for {}", uri.as_str());
1431
1432 let params = DidSaveTextDocumentParams {
1433 text_document: TextDocumentIdentifier { uri },
1434 text,
1435 };
1436
1437 self.send_notification::<DidSaveTextDocument>(params).await
1438 }
1439
1440 async fn handle_did_close(&self, uri: Uri) -> Result<(), String> {
1442 let path = PathBuf::from(uri.path().as_str());
1443
1444 if self
1446 .document_versions
1447 .lock()
1448 .unwrap()
1449 .remove(&path)
1450 .is_some()
1451 {
1452 tracing::info!("LSP ({}): didClose for {}", self.language, uri.as_str());
1453 } else {
1454 tracing::debug!(
1455 "LSP ({}): didClose for {} but document was not tracked",
1456 self.language,
1457 uri.as_str()
1458 );
1459 }
1460
1461 self.pending_opens.lock().unwrap().remove(&path);
1463
1464 let params = DidCloseTextDocumentParams {
1465 text_document: TextDocumentIdentifier { uri },
1466 };
1467
1468 self.send_notification::<DidCloseTextDocument>(params).await
1469 }
1470
1471 async fn handle_completion(
1473 &self,
1474 request_id: u64,
1475 uri: Uri,
1476 line: u32,
1477 character: u32,
1478 pending: &PendingRequests,
1479 ) -> Result<(), String> {
1480 use lsp_types::CompletionParams;
1481
1482 tracing::trace!(
1483 "LSP: completion request at {}:{}:{}",
1484 uri.as_str(),
1485 line,
1486 character
1487 );
1488
1489 let params = CompletionParams {
1490 text_document_position: Self::text_document_position(uri, line, character),
1491 work_done_progress_params: WorkDoneProgressParams::default(),
1492 partial_result_params: PartialResultParams::default(),
1493 context: None,
1494 };
1495
1496 match self
1498 .send_request_sequential_tracked::<_, Value>(
1499 "textDocument/completion",
1500 Some(params),
1501 pending,
1502 Some(request_id),
1503 )
1504 .await
1505 {
1506 Ok(result) => {
1507 let items = if let Ok(list) =
1509 serde_json::from_value::<lsp_types::CompletionList>(result.clone())
1510 {
1511 list.items
1512 } else {
1513 serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result)
1514 .unwrap_or_default()
1515 };
1516
1517 let _ = self
1519 .async_tx
1520 .send(AsyncMessage::LspCompletion { request_id, items });
1521 Ok(())
1522 }
1523 Err(e) => {
1524 tracing::debug!("Completion request failed: {}", e);
1525 let _ = self.async_tx.send(AsyncMessage::LspCompletion {
1527 request_id,
1528 items: vec![],
1529 });
1530 Err(e)
1531 }
1532 }
1533 }
1534
1535 fn text_document_position(uri: Uri, line: u32, character: u32) -> TextDocumentPositionParams {
1541 TextDocumentPositionParams {
1542 text_document: TextDocumentIdentifier { uri },
1543 position: Position { line, character },
1544 }
1545 }
1546
1547 fn locations_from_response(result: Value) -> Vec<lsp_types::Location> {
1554 if let Ok(loc) = serde_json::from_value::<lsp_types::Location>(result.clone()) {
1555 vec![loc]
1556 } else if let Ok(locs) = serde_json::from_value::<Vec<lsp_types::Location>>(result.clone())
1557 {
1558 locs
1559 } else if let Ok(links) = serde_json::from_value::<Vec<lsp_types::LocationLink>>(result) {
1560 links
1561 .into_iter()
1562 .map(|link| lsp_types::Location {
1563 uri: link.target_uri,
1564 range: link.target_selection_range,
1565 })
1566 .collect()
1567 } else {
1568 vec![]
1569 }
1570 }
1571
1572 async fn handle_goto_definition(
1574 &self,
1575 request_id: u64,
1576 uri: Uri,
1577 line: u32,
1578 character: u32,
1579 pending: &PendingRequests,
1580 ) -> Result<(), String> {
1581 use lsp_types::GotoDefinitionParams;
1582
1583 tracing::trace!(
1584 "LSP: go-to-definition request at {}:{}:{}",
1585 uri.as_str(),
1586 line,
1587 character
1588 );
1589
1590 let params = GotoDefinitionParams {
1591 text_document_position_params: Self::text_document_position(uri, line, character),
1592 work_done_progress_params: WorkDoneProgressParams::default(),
1593 partial_result_params: PartialResultParams::default(),
1594 };
1595
1596 match self
1598 .send_request_sequential::<_, Value>("textDocument/definition", Some(params), pending)
1599 .await
1600 {
1601 Ok(result) => {
1602 let locations = Self::locations_from_response(result);
1604
1605 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1607 request_id,
1608 locations,
1609 });
1610 Ok(())
1611 }
1612 Err(e) => {
1613 tracing::debug!("Go-to-definition request failed: {}", e);
1614 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1616 request_id,
1617 locations: vec![],
1618 });
1619 Err(e)
1620 }
1621 }
1622 }
1623
1624 async fn handle_implementation(
1626 &self,
1627 request_id: u64,
1628 uri: Uri,
1629 line: u32,
1630 character: u32,
1631 pending: &PendingRequests,
1632 ) -> Result<(), String> {
1633 use lsp_types::request::GotoImplementationParams;
1634
1635 tracing::trace!(
1636 "LSP: go-to-implementation request at {}:{}:{}",
1637 uri.as_str(),
1638 line,
1639 character
1640 );
1641
1642 let params = GotoImplementationParams {
1643 text_document_position_params: Self::text_document_position(uri, line, character),
1644 work_done_progress_params: WorkDoneProgressParams::default(),
1645 partial_result_params: PartialResultParams::default(),
1646 };
1647
1648 match self
1650 .send_request_sequential::<_, Value>(
1651 "textDocument/implementation",
1652 Some(params),
1653 pending,
1654 )
1655 .await
1656 {
1657 Ok(result) => {
1658 let locations = Self::locations_from_response(result);
1660
1661 let _ = self.async_tx.send(AsyncMessage::LspImplementation {
1663 request_id,
1664 locations,
1665 });
1666 Ok(())
1667 }
1668 Err(e) => {
1669 tracing::debug!("Go-to-implementation request failed: {}", e);
1670 let _ = self.async_tx.send(AsyncMessage::LspImplementation {
1672 request_id,
1673 locations: vec![],
1674 });
1675 Err(e)
1676 }
1677 }
1678 }
1679
1680 async fn handle_rename(
1682 &self,
1683 request_id: u64,
1684 uri: Uri,
1685 line: u32,
1686 character: u32,
1687 new_name: String,
1688 pending: &PendingRequests,
1689 ) -> Result<(), String> {
1690 use lsp_types::RenameParams;
1691
1692 tracing::trace!(
1693 "LSP: rename request at {}:{}:{} to '{}'",
1694 uri.as_str(),
1695 line,
1696 character,
1697 new_name
1698 );
1699
1700 let params = RenameParams {
1701 text_document_position: Self::text_document_position(uri, line, character),
1702 new_name,
1703 work_done_progress_params: WorkDoneProgressParams::default(),
1704 };
1705
1706 match self
1708 .send_request_sequential::<_, Value>("textDocument/rename", Some(params), pending)
1709 .await
1710 {
1711 Ok(result) => {
1712 match serde_json::from_value::<lsp_types::WorkspaceEdit>(result) {
1714 Ok(workspace_edit) => {
1715 let _ = self.async_tx.send(AsyncMessage::LspRename {
1717 request_id,
1718 result: Ok(workspace_edit),
1719 });
1720 Ok(())
1721 }
1722 Err(e) => {
1723 tracing::error!("Failed to parse rename response: {}", e);
1724 let _ = self.async_tx.send(AsyncMessage::LspRename {
1725 request_id,
1726 result: Err(format!("Failed to parse rename response: {}", e)),
1727 });
1728 Err(format!("Failed to parse rename response: {}", e))
1729 }
1730 }
1731 }
1732 Err(e) => {
1733 tracing::debug!("Rename request failed: {}", e);
1734 let _ = self.async_tx.send(AsyncMessage::LspRename {
1736 request_id,
1737 result: Err(e.clone()),
1738 });
1739 Err(e)
1740 }
1741 }
1742 }
1743
1744 async fn handle_hover(
1746 &self,
1747 request_id: u64,
1748 uri: Uri,
1749 line: u32,
1750 character: u32,
1751 pending: &PendingRequests,
1752 ) -> Result<(), String> {
1753 use lsp_types::HoverParams;
1754
1755 tracing::trace!(
1756 "LSP: hover request at {}:{}:{}",
1757 uri.as_str(),
1758 line,
1759 character
1760 );
1761
1762 let params = HoverParams {
1763 text_document_position_params: Self::text_document_position(uri, line, character),
1764 work_done_progress_params: WorkDoneProgressParams::default(),
1765 };
1766
1767 match self
1769 .send_request_sequential::<_, Value>("textDocument/hover", Some(params), pending)
1770 .await
1771 {
1772 Ok(result) => {
1773 tracing::debug!("Raw LSP hover response: {:?}", result);
1774 let (contents, is_markdown, range) = if result.is_null() {
1776 (String::new(), false, None)
1778 } else {
1779 match serde_json::from_value::<lsp_types::Hover>(result) {
1780 Ok(hover) => {
1781 let (contents, is_markdown) =
1783 Self::extract_hover_contents(&hover.contents);
1784 let range = hover.range.map(|r| {
1786 (
1787 (r.start.line, r.start.character),
1788 (r.end.line, r.end.character),
1789 )
1790 });
1791 (contents, is_markdown, range)
1792 }
1793 Err(e) => {
1794 tracing::error!("Failed to parse hover response: {}", e);
1795 (String::new(), false, None)
1796 }
1797 }
1798 };
1799
1800 let _ = self.async_tx.send(AsyncMessage::LspHover {
1802 request_id,
1803 contents,
1804 is_markdown,
1805 range,
1806 });
1807 Ok(())
1808 }
1809 Err(e) => {
1810 tracing::debug!("Hover request failed: {}", e);
1811 let _ = self.async_tx.send(AsyncMessage::LspHover {
1813 request_id,
1814 contents: String::new(),
1815 is_markdown: false,
1816 range: None,
1817 });
1818 Err(e)
1819 }
1820 }
1821 }
1822
1823 fn extract_hover_contents(contents: &lsp_types::HoverContents) -> (String, bool) {
1826 use lsp_types::{HoverContents, MarkedString, MarkupContent, MarkupKind};
1827
1828 match contents {
1829 HoverContents::Scalar(marked) => match marked {
1830 MarkedString::String(s) => (s.clone(), false),
1831 MarkedString::LanguageString(ls) => {
1832 (format!("```{}\n{}\n```", ls.language, ls.value), true)
1834 }
1835 },
1836 HoverContents::Array(arr) => {
1837 let content = arr
1839 .iter()
1840 .map(|marked| match marked {
1841 MarkedString::String(s) => s.clone(),
1842 MarkedString::LanguageString(ls) => {
1843 format!("```{}\n{}\n```", ls.language, ls.value)
1844 }
1845 })
1846 .collect::<Vec<_>>()
1847 .join("\n\n");
1848 (content, true)
1849 }
1850 HoverContents::Markup(MarkupContent { kind, value }) => {
1851 let is_markdown = matches!(kind, MarkupKind::Markdown);
1853 (value.clone(), is_markdown)
1854 }
1855 }
1856 }
1857
1858 async fn handle_references(
1860 &self,
1861 request_id: u64,
1862 uri: Uri,
1863 line: u32,
1864 character: u32,
1865 pending: &PendingRequests,
1866 ) -> Result<(), String> {
1867 use lsp_types::{ReferenceContext, ReferenceParams};
1868
1869 tracing::trace!(
1870 "LSP: find references request at {}:{}:{}",
1871 uri.as_str(),
1872 line,
1873 character
1874 );
1875
1876 let params = ReferenceParams {
1877 text_document_position: Self::text_document_position(uri, line, character),
1878 work_done_progress_params: WorkDoneProgressParams::default(),
1879 partial_result_params: PartialResultParams::default(),
1880 context: ReferenceContext {
1881 include_declaration: true,
1882 },
1883 };
1884
1885 match self
1887 .send_request_sequential::<_, Value>("textDocument/references", Some(params), pending)
1888 .await
1889 {
1890 Ok(result) => {
1891 let locations = if result.is_null() {
1893 Vec::new()
1894 } else {
1895 serde_json::from_value::<Vec<lsp_types::Location>>(result).unwrap_or_default()
1896 };
1897
1898 tracing::trace!("LSP: found {} references", locations.len());
1899
1900 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1902 request_id,
1903 locations,
1904 });
1905 Ok(())
1906 }
1907 Err(e) => {
1908 tracing::debug!("Find references request failed: {}", e);
1909 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1911 request_id,
1912 locations: Vec::new(),
1913 });
1914 Err(e)
1915 }
1916 }
1917 }
1918
1919 async fn handle_signature_help(
1921 &self,
1922 request_id: u64,
1923 uri: Uri,
1924 line: u32,
1925 character: u32,
1926 pending: &PendingRequests,
1927 ) -> Result<(), String> {
1928 use lsp_types::SignatureHelpParams;
1929
1930 tracing::trace!(
1931 "LSP: signature help request at {}:{}:{}",
1932 uri.as_str(),
1933 line,
1934 character
1935 );
1936
1937 let params = SignatureHelpParams {
1938 text_document_position_params: Self::text_document_position(uri, line, character),
1939 work_done_progress_params: WorkDoneProgressParams::default(),
1940 context: None, };
1942
1943 match self
1945 .send_request_sequential::<_, Value>(
1946 "textDocument/signatureHelp",
1947 Some(params),
1948 pending,
1949 )
1950 .await
1951 {
1952 Ok(result) => {
1953 let signature_help = if result.is_null() {
1955 None
1956 } else {
1957 serde_json::from_value::<lsp_types::SignatureHelp>(result).ok()
1958 };
1959
1960 tracing::trace!(
1961 "LSP: signature help received: {} signatures",
1962 signature_help
1963 .as_ref()
1964 .map(|h| h.signatures.len())
1965 .unwrap_or(0)
1966 );
1967
1968 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1970 request_id,
1971 signature_help,
1972 });
1973 Ok(())
1974 }
1975 Err(e) => {
1976 tracing::debug!("Signature help request failed: {}", e);
1977 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1979 request_id,
1980 signature_help: None,
1981 });
1982 Err(e)
1983 }
1984 }
1985 }
1986
1987 #[allow(clippy::too_many_arguments)]
1989 async fn handle_code_actions(
1990 &self,
1991 request_id: u64,
1992 uri: Uri,
1993 start_line: u32,
1994 start_char: u32,
1995 end_line: u32,
1996 end_char: u32,
1997 diagnostics: Vec<lsp_types::Diagnostic>,
1998 pending: &PendingRequests,
1999 ) -> Result<(), String> {
2000 use lsp_types::{CodeActionContext, CodeActionParams};
2001
2002 tracing::trace!(
2003 "LSP: code actions request at {}:{}:{}-{}:{}",
2004 uri.as_str(),
2005 start_line,
2006 start_char,
2007 end_line,
2008 end_char
2009 );
2010
2011 let params = CodeActionParams {
2012 text_document: TextDocumentIdentifier { uri },
2013 range: Range {
2014 start: Position {
2015 line: start_line,
2016 character: start_char,
2017 },
2018 end: Position {
2019 line: end_line,
2020 character: end_char,
2021 },
2022 },
2023 context: CodeActionContext {
2024 diagnostics,
2025 only: None,
2026 trigger_kind: None,
2027 },
2028 work_done_progress_params: WorkDoneProgressParams::default(),
2029 partial_result_params: PartialResultParams::default(),
2030 };
2031
2032 match self
2034 .send_request_sequential::<_, Value>("textDocument/codeAction", Some(params), pending)
2035 .await
2036 {
2037 Ok(result) => {
2038 let actions = if result.is_null() {
2040 Vec::new()
2041 } else {
2042 serde_json::from_value::<Vec<lsp_types::CodeActionOrCommand>>(result)
2043 .unwrap_or_default()
2044 };
2045
2046 tracing::trace!("LSP: received {} code actions", actions.len());
2047
2048 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
2050 request_id,
2051 actions,
2052 });
2053 Ok(())
2054 }
2055 Err(e) => {
2056 tracing::debug!("Code actions request failed: {}", e);
2057 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
2059 request_id,
2060 actions: Vec::new(),
2061 });
2062 Err(e)
2063 }
2064 }
2065 }
2066
2067 async fn handle_execute_command(
2069 &self,
2070 command: String,
2071 arguments: Option<Vec<Value>>,
2072 pending: &PendingRequests,
2073 ) -> Result<(), String> {
2074 let params = lsp_types::ExecuteCommandParams {
2075 command: command.clone(),
2076 arguments: arguments.unwrap_or_default(),
2077 work_done_progress_params: lsp_types::WorkDoneProgressParams::default(),
2078 };
2079
2080 match self
2081 .send_request_sequential::<_, Value>("workspace/executeCommand", Some(params), pending)
2082 .await
2083 {
2084 Ok(_) => {
2085 tracing::info!("ExecuteCommand '{}' completed", command);
2086 Ok(())
2087 }
2088 Err(e) => {
2089 tracing::debug!("ExecuteCommand '{}' failed: {}", command, e);
2090 Err(e)
2091 }
2092 }
2093 }
2094
2095 async fn handle_code_action_resolve(
2097 &self,
2098 request_id: u64,
2099 action: lsp_types::CodeAction,
2100 pending: &PendingRequests,
2101 ) -> Result<(), String> {
2102 match self
2103 .send_request_sequential::<_, Value>("codeAction/resolve", Some(action), pending)
2104 .await
2105 {
2106 Ok(result) => {
2107 let resolved = serde_json::from_value::<lsp_types::CodeAction>(result)
2108 .map_err(|e| format!("Failed to parse codeAction/resolve response: {}", e));
2109 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
2110 request_id,
2111 action: resolved,
2112 });
2113 Ok(())
2114 }
2115 Err(e) => {
2116 tracing::debug!("codeAction/resolve failed: {}", e);
2117 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
2118 request_id,
2119 action: Err(e.clone()),
2120 });
2121 Err(e)
2122 }
2123 }
2124 }
2125
2126 async fn handle_completion_resolve(
2128 &self,
2129 request_id: u64,
2130 item: lsp_types::CompletionItem,
2131 pending: &PendingRequests,
2132 ) -> Result<(), String> {
2133 match self
2134 .send_request_sequential::<_, Value>("completionItem/resolve", Some(item), pending)
2135 .await
2136 {
2137 Ok(result) => {
2138 let resolved = serde_json::from_value::<lsp_types::CompletionItem>(result)
2139 .map_err(|e| format!("Failed to parse completionItem/resolve response: {}", e));
2140 let _ = self.async_tx.send(AsyncMessage::LspCompletionResolved {
2141 request_id,
2142 item: resolved,
2143 });
2144 Ok(())
2145 }
2146 Err(e) => {
2147 tracing::debug!("completionItem/resolve failed: {}", e);
2148 Err(e)
2149 }
2150 }
2151 }
2152
2153 async fn handle_document_formatting(
2155 &self,
2156 request_id: u64,
2157 uri: Uri,
2158 tab_size: u32,
2159 insert_spaces: bool,
2160 pending: &PendingRequests,
2161 ) -> Result<(), String> {
2162 use lsp_types::{DocumentFormattingParams, FormattingOptions};
2163
2164 let params = DocumentFormattingParams {
2165 text_document: TextDocumentIdentifier { uri: uri.clone() },
2166 options: FormattingOptions {
2167 tab_size,
2168 insert_spaces,
2169 ..Default::default()
2170 },
2171 work_done_progress_params: WorkDoneProgressParams::default(),
2172 };
2173
2174 match self
2175 .send_request_sequential::<_, Value>("textDocument/formatting", Some(params), pending)
2176 .await
2177 {
2178 Ok(result) => {
2179 let edits = if result.is_null() {
2180 Vec::new()
2181 } else {
2182 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
2183 };
2184 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
2185 request_id,
2186 uri: uri.as_str().to_string(),
2187 edits,
2188 });
2189 Ok(())
2190 }
2191 Err(e) => {
2192 tracing::debug!("textDocument/formatting failed: {}", e);
2193 Err(e)
2194 }
2195 }
2196 }
2197
2198 #[allow(clippy::too_many_arguments)]
2200 async fn handle_document_range_formatting(
2201 &self,
2202 request_id: u64,
2203 uri: Uri,
2204 start_line: u32,
2205 start_char: u32,
2206 end_line: u32,
2207 end_char: u32,
2208 tab_size: u32,
2209 insert_spaces: bool,
2210 pending: &PendingRequests,
2211 ) -> Result<(), String> {
2212 use lsp_types::{DocumentRangeFormattingParams, FormattingOptions};
2213
2214 let params = DocumentRangeFormattingParams {
2215 text_document: TextDocumentIdentifier { uri: uri.clone() },
2216 range: Range {
2217 start: Position::new(start_line, start_char),
2218 end: Position::new(end_line, end_char),
2219 },
2220 options: FormattingOptions {
2221 tab_size,
2222 insert_spaces,
2223 ..Default::default()
2224 },
2225 work_done_progress_params: WorkDoneProgressParams::default(),
2226 };
2227
2228 match self
2229 .send_request_sequential::<_, Value>(
2230 "textDocument/rangeFormatting",
2231 Some(params),
2232 pending,
2233 )
2234 .await
2235 {
2236 Ok(result) => {
2237 let edits = if result.is_null() {
2238 Vec::new()
2239 } else {
2240 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
2241 };
2242 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
2243 request_id,
2244 uri: uri.as_str().to_string(),
2245 edits,
2246 });
2247 Ok(())
2248 }
2249 Err(e) => {
2250 tracing::debug!("textDocument/rangeFormatting failed: {}", e);
2251 Err(e)
2252 }
2253 }
2254 }
2255
2256 async fn handle_prepare_rename(
2258 &self,
2259 request_id: u64,
2260 uri: Uri,
2261 line: u32,
2262 character: u32,
2263 pending: &PendingRequests,
2264 ) -> Result<(), String> {
2265 let params = TextDocumentPositionParams {
2266 text_document: TextDocumentIdentifier { uri },
2267 position: Position::new(line, character),
2268 };
2269
2270 match self
2271 .send_request_sequential::<_, Value>(
2272 "textDocument/prepareRename",
2273 Some(params),
2274 pending,
2275 )
2276 .await
2277 {
2278 Ok(result) => {
2279 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2280 request_id,
2281 result: Ok(result),
2282 });
2283 Ok(())
2284 }
2285 Err(e) => {
2286 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2287 request_id,
2288 result: Err(e.clone()),
2289 });
2290 Err(e)
2291 }
2292 }
2293 }
2294
2295 async fn handle_document_diagnostic(
2296 &self,
2297 request_id: u64,
2298 uri: Uri,
2299 previous_result_id: Option<String>,
2300 pending: &PendingRequests,
2301 ) -> Result<(), String> {
2302 use lsp_types::DocumentDiagnosticParams;
2303
2304 let supports_pull = self
2310 .capabilities
2311 .lock()
2312 .unwrap()
2313 .as_ref()
2314 .and_then(|c| c.diagnostic_provider.as_ref())
2315 .is_some();
2316 if !supports_pull {
2317 tracing::trace!(
2318 "LSP: server does not support pull diagnostics, skipping request for {}",
2319 uri.as_str()
2320 );
2321 return Ok(());
2322 }
2323
2324 tracing::trace!(
2325 "LSP: document diagnostic request for {} (previous_result_id: {:?})",
2326 uri.as_str(),
2327 previous_result_id
2328 );
2329
2330 let params = DocumentDiagnosticParams {
2331 text_document: TextDocumentIdentifier { uri: uri.clone() },
2332 identifier: None,
2333 previous_result_id,
2334 work_done_progress_params: WorkDoneProgressParams::default(),
2335 partial_result_params: PartialResultParams::default(),
2336 };
2337
2338 match self
2340 .send_request_sequential::<_, Value>("textDocument/diagnostic", Some(params), pending)
2341 .await
2342 {
2343 Ok(result) => {
2344 let uri_string = uri.as_str().to_string();
2347
2348 if let Ok(full_report) = serde_json::from_value::<
2350 lsp_types::RelatedFullDocumentDiagnosticReport,
2351 >(result.clone())
2352 {
2353 let diagnostics = full_report.full_document_diagnostic_report.items;
2354 let result_id = full_report.full_document_diagnostic_report.result_id;
2355
2356 tracing::trace!(
2357 "LSP: received {} diagnostics for {} (result_id: {:?})",
2358 diagnostics.len(),
2359 uri_string,
2360 result_id
2361 );
2362
2363 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2364 request_id,
2365 uri: uri_string,
2366 result_id,
2367 diagnostics,
2368 unchanged: false,
2369 });
2370 } else if let Ok(unchanged_report) = serde_json::from_value::<
2371 lsp_types::RelatedUnchangedDocumentDiagnosticReport,
2372 >(result.clone())
2373 {
2374 let result_id = unchanged_report
2375 .unchanged_document_diagnostic_report
2376 .result_id;
2377
2378 tracing::trace!(
2379 "LSP: diagnostics unchanged for {} (result_id: {:?})",
2380 uri_string,
2381 result_id
2382 );
2383
2384 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2385 request_id,
2386 uri: uri_string,
2387 result_id: Some(result_id),
2388 diagnostics: Vec::new(),
2389 unchanged: true,
2390 });
2391 } else {
2392 tracing::warn!(
2394 "LSP: could not parse diagnostic report, sending empty: {}",
2395 result
2396 );
2397 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2398 request_id,
2399 uri: uri_string,
2400 result_id: None,
2401 diagnostics: Vec::new(),
2402 unchanged: false,
2403 });
2404 }
2405
2406 Ok(())
2407 }
2408 Err(e) => {
2409 tracing::debug!("Document diagnostic request failed: {}", e);
2410 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2412 request_id,
2413 uri: uri.as_str().to_string(),
2414 result_id: None,
2415 diagnostics: Vec::new(),
2416 unchanged: false,
2417 });
2418 Err(e)
2419 }
2420 }
2421 }
2422
2423 #[allow(clippy::too_many_arguments)]
2425 async fn handle_inlay_hints(
2426 &self,
2427 request_id: u64,
2428 uri: Uri,
2429 start_line: u32,
2430 start_char: u32,
2431 end_line: u32,
2432 end_char: u32,
2433 pending: &PendingRequests,
2434 ) -> Result<(), String> {
2435 use lsp_types::InlayHintParams;
2436
2437 tracing::trace!(
2438 "LSP: inlay hints request for {} ({}:{} - {}:{})",
2439 uri.as_str(),
2440 start_line,
2441 start_char,
2442 end_line,
2443 end_char
2444 );
2445
2446 let params = InlayHintParams {
2447 text_document: TextDocumentIdentifier { uri: uri.clone() },
2448 range: Range {
2449 start: Position {
2450 line: start_line,
2451 character: start_char,
2452 },
2453 end: Position {
2454 line: end_line,
2455 character: end_char,
2456 },
2457 },
2458 work_done_progress_params: WorkDoneProgressParams::default(),
2459 };
2460
2461 match self
2462 .send_request_sequential::<_, Option<Vec<lsp_types::InlayHint>>>(
2463 "textDocument/inlayHint",
2464 Some(params),
2465 pending,
2466 )
2467 .await
2468 {
2469 Ok(hints) => {
2470 let hints = hints.unwrap_or_default();
2471 let uri_string = uri.as_str().to_string();
2472
2473 tracing::trace!(
2474 "LSP: received {} inlay hints for {}",
2475 hints.len(),
2476 uri_string
2477 );
2478
2479 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2480 request_id,
2481 uri: uri_string,
2482 hints,
2483 });
2484
2485 Ok(())
2486 }
2487 Err(e) => {
2488 tracing::debug!("Inlay hints request failed: {}", e);
2489 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2491 request_id,
2492 uri: uri.as_str().to_string(),
2493 hints: Vec::new(),
2494 });
2495 Err(e)
2496 }
2497 }
2498 }
2499
2500 async fn handle_folding_ranges(
2502 &self,
2503 request_id: u64,
2504 uri: Uri,
2505 pending: &PendingRequests,
2506 ) -> Result<(), String> {
2507 use lsp_types::FoldingRangeParams;
2508
2509 tracing::trace!("LSP: folding range request for {}", uri.as_str());
2510
2511 let params = FoldingRangeParams {
2512 text_document: TextDocumentIdentifier { uri: uri.clone() },
2513 work_done_progress_params: WorkDoneProgressParams::default(),
2514 partial_result_params: PartialResultParams::default(),
2515 };
2516
2517 match self
2518 .send_request_sequential::<_, Option<Vec<lsp_types::FoldingRange>>>(
2519 "textDocument/foldingRange",
2520 Some(params),
2521 pending,
2522 )
2523 .await
2524 {
2525 Ok(ranges) => {
2526 let ranges = ranges.unwrap_or_default();
2527 let uri_string = uri.as_str().to_string();
2528
2529 tracing::trace!(
2530 "LSP: received {} folding ranges for {}",
2531 ranges.len(),
2532 uri_string
2533 );
2534
2535 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2536 request_id,
2537 uri: uri_string,
2538 ranges,
2539 });
2540
2541 Ok(())
2542 }
2543 Err(e) => {
2544 tracing::debug!("Folding range request failed: {}", e);
2545 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2546 request_id,
2547 uri: uri.as_str().to_string(),
2548 ranges: Vec::new(),
2549 });
2550 Err(e)
2551 }
2552 }
2553 }
2554
2555 async fn handle_semantic_tokens_full(
2556 &self,
2557 request_id: u64,
2558 uri: Uri,
2559 pending: &PendingRequests,
2560 ) -> Result<(), String> {
2561 use lsp_types::request::SemanticTokensFullRequest;
2562
2563 tracing::trace!("LSP: semanticTokens/full request for {}", uri.as_str());
2564
2565 let params = SemanticTokensParams {
2566 work_done_progress_params: WorkDoneProgressParams::default(),
2567 partial_result_params: PartialResultParams::default(),
2568 text_document: TextDocumentIdentifier { uri: uri.clone() },
2569 };
2570
2571 match self
2572 .send_request_sequential_tracked::<_, Option<SemanticTokensResult>>(
2573 SemanticTokensFullRequest::METHOD,
2574 Some(params),
2575 pending,
2576 Some(request_id),
2577 )
2578 .await
2579 {
2580 Ok(result) => {
2581 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2582 request_id,
2583 uri: uri.as_str().to_string(),
2584 response: LspSemanticTokensResponse::Full(Ok(result)),
2585 });
2586 Ok(())
2587 }
2588 Err(e) => {
2589 tracing::debug!("Semantic tokens request failed: {}", e);
2590 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2591 request_id,
2592 uri: uri.as_str().to_string(),
2593 response: LspSemanticTokensResponse::Full(Err(e.clone())),
2594 });
2595 Err(e)
2596 }
2597 }
2598 }
2599
2600 async fn handle_semantic_tokens_full_delta(
2601 &self,
2602 request_id: u64,
2603 uri: Uri,
2604 previous_result_id: String,
2605 pending: &PendingRequests,
2606 ) -> Result<(), String> {
2607 use lsp_types::{
2608 request::SemanticTokensFullDeltaRequest, SemanticTokensDeltaParams,
2609 SemanticTokensFullDeltaResult,
2610 };
2611
2612 tracing::trace!(
2613 "LSP: semanticTokens/full/delta request for {}",
2614 uri.as_str()
2615 );
2616
2617 let params = SemanticTokensDeltaParams {
2618 work_done_progress_params: WorkDoneProgressParams::default(),
2619 partial_result_params: PartialResultParams::default(),
2620 text_document: TextDocumentIdentifier { uri: uri.clone() },
2621 previous_result_id,
2622 };
2623
2624 match self
2625 .send_request_sequential_tracked::<_, Option<SemanticTokensFullDeltaResult>>(
2626 SemanticTokensFullDeltaRequest::METHOD,
2627 Some(params),
2628 pending,
2629 Some(request_id),
2630 )
2631 .await
2632 {
2633 Ok(result) => {
2634 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2635 request_id,
2636 uri: uri.as_str().to_string(),
2637 response: LspSemanticTokensResponse::FullDelta(Ok(result)),
2638 });
2639 Ok(())
2640 }
2641 Err(e) => {
2642 tracing::debug!("Semantic tokens delta request failed: {}", e);
2643 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2644 request_id,
2645 uri: uri.as_str().to_string(),
2646 response: LspSemanticTokensResponse::FullDelta(Err(e.clone())),
2647 });
2648 Err(e)
2649 }
2650 }
2651 }
2652
2653 async fn handle_semantic_tokens_range(
2654 &self,
2655 request_id: u64,
2656 uri: Uri,
2657 range: lsp_types::Range,
2658 pending: &PendingRequests,
2659 ) -> Result<(), String> {
2660 use lsp_types::{request::SemanticTokensRangeRequest, SemanticTokensRangeParams};
2661
2662 tracing::trace!("LSP: semanticTokens/range request for {}", uri.as_str());
2663
2664 let params = SemanticTokensRangeParams {
2665 work_done_progress_params: WorkDoneProgressParams::default(),
2666 partial_result_params: PartialResultParams::default(),
2667 text_document: TextDocumentIdentifier { uri: uri.clone() },
2668 range,
2669 };
2670
2671 match self
2672 .send_request_sequential_tracked::<_, Option<lsp_types::SemanticTokensRangeResult>>(
2673 SemanticTokensRangeRequest::METHOD,
2674 Some(params),
2675 pending,
2676 Some(request_id),
2677 )
2678 .await
2679 {
2680 Ok(result) => {
2681 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2682 request_id,
2683 uri: uri.as_str().to_string(),
2684 response: LspSemanticTokensResponse::Range(Ok(result)),
2685 });
2686 Ok(())
2687 }
2688 Err(e) => {
2689 tracing::debug!("Semantic tokens range request failed: {}", e);
2690 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2691 request_id,
2692 uri: uri.as_str().to_string(),
2693 response: LspSemanticTokensResponse::Range(Err(e.clone())),
2694 });
2695 Err(e)
2696 }
2697 }
2698 }
2699
2700 async fn handle_plugin_request(
2702 &self,
2703 request_id: u64,
2704 method: String,
2705 params: Option<Value>,
2706 pending: &PendingRequests,
2707 ) {
2708 tracing::trace!(
2709 "Plugin request {} => method={} params={:?}",
2710 request_id,
2711 method,
2712 params
2713 );
2714 let result = self
2715 .send_request_sequential_tracked::<Value, Value>(
2716 &method,
2717 params,
2718 pending,
2719 Some(request_id),
2720 )
2721 .await;
2722
2723 tracing::trace!(
2724 "Plugin request {} completed with result {:?}",
2725 request_id,
2726 &result
2727 );
2728 let _ = self.async_tx.send(AsyncMessage::PluginLspResponse {
2729 language: (*self.language).clone(),
2730 request_id,
2731 result,
2732 });
2733 }
2734
2735 async fn handle_shutdown(&self) -> Result<(), String> {
2737 tracing::info!("Shutting down async LSP server");
2738
2739 let notification = JsonRpcNotification {
2740 jsonrpc: "2.0".to_string(),
2741 method: "shutdown".to_string(),
2742 params: None,
2743 };
2744
2745 self.write_message(¬ification).await?;
2746
2747 let exit = JsonRpcNotification {
2748 jsonrpc: "2.0".to_string(),
2749 method: "exit".to_string(),
2750 params: None,
2751 };
2752
2753 self.write_message(&exit).await
2754 }
2755
2756 async fn send_cancel_request(&self, lsp_id: i64) -> Result<(), String> {
2758 tracing::trace!("Sending $/cancelRequest for LSP id {}", lsp_id);
2759
2760 let notification = JsonRpcNotification {
2761 jsonrpc: "2.0".to_string(),
2762 method: "$/cancelRequest".to_string(),
2763 params: Some(serde_json::json!({ "id": lsp_id })),
2764 };
2765
2766 self.write_message(¬ification).await
2767 }
2768
2769 async fn handle_cancel_request(&self, request_id: u64) -> Result<(), String> {
2771 let lsp_id = self.active_requests.lock().unwrap().remove(&request_id);
2772 if let Some(lsp_id) = lsp_id {
2773 tracing::info!(
2774 "Cancelling request: editor_id={}, lsp_id={}",
2775 request_id,
2776 lsp_id
2777 );
2778 self.send_cancel_request(lsp_id).await
2779 } else {
2780 tracing::trace!(
2781 "Cancel request ignored: no active LSP request for editor_id={}",
2782 request_id
2783 );
2784 Ok(())
2785 }
2786 }
2787}
2788
2789struct LspTask {
2791 _process: crate::services::remote::StdioChild,
2794
2795 stdin: ChildStdin,
2797
2798 stdout: BufReader<ChildStdout>,
2800
2801 next_id: i64,
2803
2804 pending: HashMap<i64, (String, oneshot::Sender<Result<Value, String>>)>,
2807
2808 capabilities: Option<ServerCapabilities>,
2810
2811 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2813
2814 pending_opens: HashMap<PathBuf, Instant>,
2817
2818 initialized: bool,
2820
2821 async_tx: std_mpsc::Sender<AsyncMessage>,
2823
2824 language: String,
2826
2827 server_name: String,
2829
2830 server_command: String,
2832
2833 stderr_log_path: std::path::PathBuf,
2835
2836 language_id_overrides: HashMap<String, String>,
2838}
2839
2840impl LspTask {
2841 #[allow(clippy::too_many_arguments)]
2851 async fn spawn(
2852 command: &str,
2853 args: &[String],
2854 env: &std::collections::HashMap<String, String>,
2855 language: String,
2856 server_name: String,
2857 async_tx: std_mpsc::Sender<AsyncMessage>,
2858 process_limits: &ProcessLimits,
2859 stderr_log_path: std::path::PathBuf,
2860 language_id_overrides: HashMap<String, String>,
2861 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2862 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
2863 ) -> Result<Self, String> {
2864 tracing::info!("Spawning async LSP server: {} {:?}", command, args);
2865 tracing::info!("Process limits: {:?}", process_limits);
2866 tracing::info!("LSP stderr will be logged to: {:?}", stderr_log_path);
2867
2868 if !long_running_spawner.command_exists(command).await {
2873 return Err(format!(
2874 "LSP server executable '{}' not found in the active authority's PATH. \
2875 Please install it or check your configuration.",
2876 command
2877 ));
2878 }
2879
2880 let env_pairs: Vec<(String, String)> =
2885 env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2886
2887 let mut stdio_child = long_running_spawner
2888 .spawn_stdio(command, args, env_pairs, None, Some(process_limits))
2889 .await
2890 .map_err(|e| format!("Failed to spawn LSP server '{}': {}", command, e))?;
2891
2892 let stdin = stdio_child
2893 .take_stdin()
2894 .ok_or_else(|| "Failed to get stdin".to_string())?;
2895
2896 let stdout_stream = stdio_child
2897 .take_stdout()
2898 .ok_or_else(|| "Failed to get stdout".to_string())?;
2899 let stdout = BufReader::new(stdout_stream);
2900
2901 if let Some(stderr_stream) = stdio_child.take_stderr() {
2907 let log_path = stderr_log_path.clone();
2908 tokio::spawn(async move {
2909 use tokio::fs::File;
2910 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader as TokioBufReader};
2911 let mut file = match File::create(&log_path).await {
2912 Ok(f) => f,
2913 Err(e) => {
2914 tracing::warn!("Could not create LSP stderr log {:?}: {}", log_path, e);
2915 return;
2916 }
2917 };
2918 let mut reader = TokioBufReader::new(stderr_stream);
2919 let mut buf = String::new();
2920 loop {
2921 buf.clear();
2922 match reader.read_line(&mut buf).await {
2923 Ok(0) => break,
2924 Ok(_) => {
2925 if let Err(e) = file.write_all(buf.as_bytes()).await {
2926 tracing::warn!(
2927 "Write to LSP stderr log {:?} failed: {}",
2928 log_path,
2929 e
2930 );
2931 return;
2932 }
2933 }
2934 Err(e) => {
2935 tracing::debug!("LSP stderr stream closed for {:?}: {}", log_path, e);
2936 return;
2937 }
2938 }
2939 }
2940 });
2941 }
2942
2943 Ok(Self {
2944 _process: stdio_child,
2945 stdin,
2946 stdout,
2947 next_id: 0,
2948 pending: HashMap::new(),
2949 capabilities: None,
2950 document_versions,
2951 pending_opens: HashMap::new(),
2952 initialized: false,
2953 async_tx,
2954 language,
2955 server_name,
2956 server_command: command.to_string(),
2957 stderr_log_path,
2958 language_id_overrides,
2959 })
2960 }
2961
2962 #[allow(clippy::too_many_arguments)]
2964 #[allow(clippy::let_underscore_must_use)] fn spawn_stdout_reader(
2966 mut stdout: BufReader<ChildStdout>,
2967 pending: PendingRequests,
2968 async_tx: std_mpsc::Sender<AsyncMessage>,
2969 language: String,
2970 server_name: String,
2971 server_command: String,
2972 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2973 stderr_log_path: std::path::PathBuf,
2974 shutting_down: Arc<AtomicBool>,
2975 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2976 config_options: Arc<std::sync::Mutex<Option<Value>>>,
2977 capabilities: Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
2978 ) {
2979 tokio::spawn(async move {
2980 tracing::info!("LSP stdout reader task started for {}", language);
2981 loop {
2982 match read_message_from_stdout(&mut stdout).await {
2983 Ok(message) => {
2984 tracing::trace!("Read message from LSP server: {:?}", message);
2985 if let Err(e) = handle_message_dispatch(
2986 message,
2987 &pending,
2988 &async_tx,
2989 &language,
2990 &server_name,
2991 &server_command,
2992 &stdin_writer,
2993 &document_versions,
2994 &config_options,
2995 &capabilities,
2996 )
2997 .await
2998 {
2999 tracing::error!("Error handling LSP message: {}", e);
3000 }
3001 }
3002 Err(e) => {
3003 if shutting_down.load(Ordering::SeqCst) {
3005 tracing::info!(
3006 "LSP stdout reader exiting due to graceful shutdown for {}",
3007 language
3008 );
3009 } else {
3010 tracing::error!("Error reading from LSP server: {}", e);
3011 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3012 language: language.clone(),
3013 server_name: server_name.clone(),
3014 status: LspServerStatus::Error,
3015 message: None,
3016 });
3017 let _ = async_tx.send(AsyncMessage::LspError {
3018 language: language.clone(),
3019 error: format!("Read error: {}", e),
3020 stderr_log_path: Some(stderr_log_path.clone()),
3021 });
3022 }
3023 break;
3024 }
3025 }
3026 }
3027 {
3030 let mut pending_guard = pending.lock().unwrap();
3031 let count = pending_guard.len();
3032 if count > 0 {
3033 tracing::info!(
3034 "LSP stdout reader: draining {} pending requests for {}",
3035 count,
3036 language
3037 );
3038 for (id, (_method, tx)) in pending_guard.drain() {
3039 tracing::debug!(
3040 "LSP stdout reader: failing pending request id={} for {}",
3041 id,
3042 language
3043 );
3044 let _ = tx.send(Err(
3045 "LSP server connection closed while awaiting response".to_string(),
3046 ));
3047 }
3048 }
3049 }
3050
3051 tracing::info!("LSP stdout reader task exiting for {}", language);
3052 });
3053 }
3054
3055 #[allow(clippy::let_underscore_must_use)]
3059 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
3060 tracing::info!("LspTask::run() started for language: {}", self.language);
3061
3062 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
3064
3065 let state = LspState {
3067 stdin: stdin_writer.clone(),
3068 next_id: Arc::new(AtomicI64::new(self.next_id)),
3069 capabilities: Arc::new(Mutex::new(self.capabilities)),
3070 document_versions: self.document_versions.clone(),
3071 pending_opens: Arc::new(Mutex::new(self.pending_opens)),
3072 initialized: Arc::new(AtomicBool::new(self.initialized)),
3073 async_tx: self.async_tx.clone(),
3074 language: Arc::new(self.language.clone()),
3075 server_name: Arc::new(self.server_name.clone()),
3076 active_requests: Arc::new(Mutex::new(HashMap::new())),
3077 language_id_overrides: Arc::new(self.language_id_overrides.clone()),
3078 };
3079
3080 let pending = Arc::new(Mutex::new(self.pending));
3081 let async_tx = state.async_tx.clone();
3082 let language_clone: String = (*state.language).clone();
3083 let server_name: String = (*state.server_name).clone();
3084
3085 let config_options: Arc<std::sync::Mutex<Option<Value>>> =
3089 Arc::new(std::sync::Mutex::new(None));
3090
3091 let shutting_down = Arc::new(AtomicBool::new(false));
3093
3094 Self::spawn_stdout_reader(
3096 self.stdout,
3097 pending.clone(),
3098 async_tx.clone(),
3099 language_clone.clone(),
3100 self.server_name.clone(),
3101 self.server_command.clone(),
3102 stdin_writer.clone(),
3103 self.stderr_log_path,
3104 shutting_down.clone(),
3105 self.document_versions.clone(),
3106 config_options.clone(),
3107 state.capabilities.clone(),
3108 );
3109
3110 macro_rules! await_draining {
3140 ($fut:expr, $command_rx:expr, $buf:expr) => {{
3141 let fut = $fut;
3142 tokio::pin!(fut);
3143 loop {
3144 tokio::select! {
3145 biased; result = &mut fut => break result,
3147 Some(cmd) = $command_rx.recv() => {
3148 $buf.push_back(cmd);
3149 }
3150 }
3151 }
3152 }};
3153 }
3154
3155 macro_rules! spawn_request {
3157 ($state:expr, $pending:expr, |$s:ident, $p:ident| $body:expr) => {{
3158 let $s = $state.clone();
3159 let $p = $pending.clone();
3160 tokio::spawn(async move {
3161 let _ = $body;
3162 });
3163 }};
3164 }
3165
3166 let mut pending_commands = Vec::new();
3167 let mut draining_buffer: std::collections::VecDeque<LspCommand> =
3168 std::collections::VecDeque::new();
3169 loop {
3170 let cmd = if let Some(cmd) = draining_buffer.pop_front() {
3173 cmd
3174 } else {
3175 match command_rx.recv().await {
3176 Some(cmd) => cmd,
3177 None => {
3178 tracing::info!("Command channel closed");
3179 break;
3180 }
3181 }
3182 };
3183
3184 tracing::trace!("LspTask received command: {:?}", cmd);
3185 let initialized = state.initialized.load(Ordering::SeqCst);
3186 match cmd {
3187 LspCommand::Initialize {
3188 root_uri,
3189 initialization_options,
3190 response,
3191 } => {
3192 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3194 language: language_clone.clone(),
3195 server_name: server_name.clone(),
3196 status: LspServerStatus::Initializing,
3197 message: None,
3198 });
3199 tracing::info!("Processing Initialize command");
3200 *config_options.lock().unwrap() = initialization_options.clone();
3204 let result = await_draining!(
3205 state.handle_initialize_sequential(
3206 root_uri,
3207 initialization_options,
3208 &pending
3209 ),
3210 command_rx,
3211 draining_buffer
3212 );
3213 let success = result.is_ok();
3214 let _ = response.send(result);
3215
3216 if success {
3218 let queued = std::mem::take(&mut pending_commands);
3219 await_draining!(
3220 state.replay_pending_commands(queued, &pending),
3221 command_rx,
3222 draining_buffer
3223 );
3224 }
3225 }
3226 LspCommand::DidOpen {
3227 uri,
3228 text,
3229 language_id,
3230 } => {
3231 if initialized {
3232 tracing::info!("Processing DidOpen for {}", uri.as_str());
3233 let _ = state
3234 .handle_did_open_sequential(uri, text, language_id, &pending)
3235 .await;
3236 } else {
3237 tracing::trace!(
3238 "Queueing DidOpen for {} until initialization completes",
3239 uri.as_str()
3240 );
3241 pending_commands.push(LspCommand::DidOpen {
3242 uri,
3243 text,
3244 language_id,
3245 });
3246 }
3247 }
3248 LspCommand::DidChange {
3249 uri,
3250 content_changes,
3251 } => {
3252 if initialized {
3253 tracing::trace!("Processing DidChange for {}", uri.as_str());
3254 let _ = state
3257 .handle_did_change_sequential(uri, content_changes, &pending)
3258 .await;
3259 } else {
3260 tracing::trace!(
3261 "Queueing DidChange for {} until initialization completes",
3262 uri.as_str()
3263 );
3264 pending_commands.push(LspCommand::DidChange {
3265 uri,
3266 content_changes,
3267 });
3268 }
3269 }
3270 LspCommand::DidClose { uri } => {
3271 if initialized {
3272 tracing::info!("Processing DidClose for {}", uri.as_str());
3273 let _ = state.handle_did_close(uri).await;
3274 } else {
3275 tracing::trace!(
3276 "Queueing DidClose for {} until initialization completes",
3277 uri.as_str()
3278 );
3279 pending_commands.push(LspCommand::DidClose { uri });
3280 }
3281 }
3282 LspCommand::DidSave { uri, text } => {
3283 if initialized {
3284 tracing::info!("Processing DidSave for {}", uri.as_str());
3285 let _ = state.handle_did_save(uri, text).await;
3286 } else {
3287 tracing::trace!(
3288 "Queueing DidSave for {} until initialization completes",
3289 uri.as_str()
3290 );
3291 pending_commands.push(LspCommand::DidSave { uri, text });
3292 }
3293 }
3294 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
3295 if initialized {
3296 tracing::info!(
3297 "Processing DidChangeWorkspaceFolders: +{} -{}",
3298 added.len(),
3299 removed.len()
3300 );
3301 let _ = state
3302 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
3303 lsp_types::DidChangeWorkspaceFoldersParams {
3304 event: lsp_types::WorkspaceFoldersChangeEvent {
3305 added,
3306 removed,
3307 },
3308 },
3309 )
3310 .await;
3311 } else {
3312 tracing::trace!(
3313 "Queueing DidChangeWorkspaceFolders until initialization completes"
3314 );
3315 pending_commands
3316 .push(LspCommand::DidChangeWorkspaceFolders { added, removed });
3317 }
3318 }
3319 LspCommand::Completion {
3320 request_id,
3321 uri,
3322 line,
3323 character,
3324 } => {
3325 if initialized {
3326 tracing::info!("Processing Completion request for {}", uri.as_str());
3327 spawn_request!(state, pending, |s, p| s
3328 .handle_completion(request_id, uri, line, character, &p)
3329 .await);
3330 } else {
3331 tracing::trace!("LSP not initialized, sending empty completion");
3332 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
3333 request_id,
3334 items: vec![],
3335 });
3336 }
3337 }
3338 LspCommand::GotoDefinition {
3339 request_id,
3340 uri,
3341 line,
3342 character,
3343 } => {
3344 if initialized {
3345 tracing::info!("Processing GotoDefinition request for {}", uri.as_str());
3346 spawn_request!(state, pending, |s, p| s
3347 .handle_goto_definition(request_id, uri, line, character, &p)
3348 .await);
3349 } else {
3350 tracing::trace!("LSP not initialized, sending empty locations");
3351 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
3352 request_id,
3353 locations: vec![],
3354 });
3355 }
3356 }
3357 LspCommand::Implementation {
3358 request_id,
3359 uri,
3360 line,
3361 character,
3362 } => {
3363 if initialized {
3364 tracing::info!("Processing Implementation request for {}", uri.as_str());
3365 spawn_request!(state, pending, |s, p| s
3366 .handle_implementation(request_id, uri, line, character, &p)
3367 .await);
3368 } else {
3369 tracing::trace!("LSP not initialized, sending empty locations");
3370 let _ = state.async_tx.send(AsyncMessage::LspImplementation {
3371 request_id,
3372 locations: vec![],
3373 });
3374 }
3375 }
3376 LspCommand::Rename {
3377 request_id,
3378 uri,
3379 line,
3380 character,
3381 new_name,
3382 } => {
3383 if initialized {
3384 tracing::info!("Processing Rename request for {}", uri.as_str());
3385 spawn_request!(state, pending, |s, p| s
3386 .handle_rename(request_id, uri, line, character, new_name, &p)
3387 .await);
3388 } else {
3389 tracing::trace!("LSP not initialized, cannot rename");
3390 let _ = state.async_tx.send(AsyncMessage::LspRename {
3391 request_id,
3392 result: Err("LSP not initialized".to_string()),
3393 });
3394 }
3395 }
3396 LspCommand::Hover {
3397 request_id,
3398 uri,
3399 line,
3400 character,
3401 } => {
3402 if initialized {
3403 tracing::info!("Processing Hover request for {}", uri.as_str());
3404 spawn_request!(state, pending, |s, p| s
3405 .handle_hover(request_id, uri, line, character, &p)
3406 .await);
3407 } else {
3408 tracing::trace!("LSP not initialized, cannot get hover");
3409 let _ = state.async_tx.send(AsyncMessage::LspHover {
3410 request_id,
3411 contents: String::new(),
3412 is_markdown: false,
3413 range: None,
3414 });
3415 }
3416 }
3417 LspCommand::References {
3418 request_id,
3419 uri,
3420 line,
3421 character,
3422 } => {
3423 if initialized {
3424 tracing::info!("Processing References request for {}", uri.as_str());
3425 spawn_request!(state, pending, |s, p| s
3426 .handle_references(request_id, uri, line, character, &p)
3427 .await);
3428 } else {
3429 tracing::trace!("LSP not initialized, cannot get references");
3430 let _ = state.async_tx.send(AsyncMessage::LspReferences {
3431 request_id,
3432 locations: Vec::new(),
3433 });
3434 }
3435 }
3436 LspCommand::SignatureHelp {
3437 request_id,
3438 uri,
3439 line,
3440 character,
3441 } => {
3442 if initialized {
3443 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
3444 spawn_request!(state, pending, |s, p| s
3445 .handle_signature_help(request_id, uri, line, character, &p)
3446 .await);
3447 } else {
3448 tracing::trace!("LSP not initialized, cannot get signature help");
3449 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
3450 request_id,
3451 signature_help: None,
3452 });
3453 }
3454 }
3455 LspCommand::CodeActions {
3456 request_id,
3457 uri,
3458 start_line,
3459 start_char,
3460 end_line,
3461 end_char,
3462 diagnostics,
3463 } => {
3464 if initialized {
3465 tracing::info!("Processing CodeActions request for {}", uri.as_str());
3466 spawn_request!(state, pending, |s, p| s
3467 .handle_code_actions(
3468 request_id,
3469 uri,
3470 start_line,
3471 start_char,
3472 end_line,
3473 end_char,
3474 diagnostics,
3475 &p,
3476 )
3477 .await);
3478 } else {
3479 tracing::trace!("LSP not initialized, cannot get code actions");
3480 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
3481 request_id,
3482 actions: Vec::new(),
3483 });
3484 }
3485 }
3486 LspCommand::DocumentDiagnostic {
3487 request_id,
3488 uri,
3489 previous_result_id,
3490 } => {
3491 if initialized {
3492 tracing::info!(
3493 "Processing DocumentDiagnostic request for {}",
3494 uri.as_str()
3495 );
3496 spawn_request!(state, pending, |s, p| s
3497 .handle_document_diagnostic(request_id, uri, previous_result_id, &p)
3498 .await);
3499 } else {
3500 tracing::trace!("LSP not initialized, cannot get document diagnostics");
3501 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
3502 request_id,
3503 uri: uri.as_str().to_string(),
3504 result_id: None,
3505 diagnostics: Vec::new(),
3506 unchanged: false,
3507 });
3508 }
3509 }
3510 LspCommand::InlayHints {
3511 request_id,
3512 uri,
3513 start_line,
3514 start_char,
3515 end_line,
3516 end_char,
3517 } => {
3518 if initialized {
3519 tracing::info!("Processing InlayHints request for {}", uri.as_str());
3520 spawn_request!(state, pending, |s, p| s
3521 .handle_inlay_hints(
3522 request_id, uri, start_line, start_char, end_line, end_char, &p,
3523 )
3524 .await);
3525 } else {
3526 tracing::trace!("LSP not initialized, cannot get inlay hints");
3527 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
3528 request_id,
3529 uri: uri.as_str().to_string(),
3530 hints: Vec::new(),
3531 });
3532 }
3533 }
3534 LspCommand::FoldingRange { request_id, uri } => {
3535 if initialized {
3536 tracing::info!("Processing FoldingRange request for {}", uri.as_str());
3537 spawn_request!(state, pending, |s, p| s
3538 .handle_folding_ranges(request_id, uri, &p)
3539 .await);
3540 } else {
3541 tracing::trace!("LSP not initialized, cannot get folding ranges");
3542 let _ = state.async_tx.send(AsyncMessage::LspFoldingRanges {
3543 request_id,
3544 uri: uri.as_str().to_string(),
3545 ranges: Vec::new(),
3546 });
3547 }
3548 }
3549 LspCommand::SemanticTokensFull { request_id, uri } => {
3550 if initialized {
3551 tracing::info!("Processing SemanticTokens request for {}", uri.as_str());
3552 spawn_request!(state, pending, |s, p| s
3553 .handle_semantic_tokens_full(request_id, uri, &p)
3554 .await);
3555 } else {
3556 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3557 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3558 request_id,
3559 uri: uri.as_str().to_string(),
3560 response: LspSemanticTokensResponse::Full(Err(
3561 "LSP not initialized".to_string()
3562 )),
3563 });
3564 }
3565 }
3566 LspCommand::SemanticTokensFullDelta {
3567 request_id,
3568 uri,
3569 previous_result_id,
3570 } => {
3571 if initialized {
3572 tracing::info!(
3573 "Processing SemanticTokens delta request for {}",
3574 uri.as_str()
3575 );
3576 spawn_request!(state, pending, |s, p| s
3577 .handle_semantic_tokens_full_delta(
3578 request_id,
3579 uri,
3580 previous_result_id,
3581 &p,
3582 )
3583 .await);
3584 } else {
3585 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3586 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3587 request_id,
3588 uri: uri.as_str().to_string(),
3589 response: LspSemanticTokensResponse::FullDelta(Err(
3590 "LSP not initialized".to_string(),
3591 )),
3592 });
3593 }
3594 }
3595 LspCommand::SemanticTokensRange {
3596 request_id,
3597 uri,
3598 range,
3599 } => {
3600 if initialized {
3601 tracing::info!(
3602 "Processing SemanticTokens range request for {}",
3603 uri.as_str()
3604 );
3605 spawn_request!(state, pending, |s, p| s
3606 .handle_semantic_tokens_range(request_id, uri, range, &p)
3607 .await);
3608 } else {
3609 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3610 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3611 request_id,
3612 uri: uri.as_str().to_string(),
3613 response: LspSemanticTokensResponse::Range(Err(
3614 "LSP not initialized".to_string()
3615 )),
3616 });
3617 }
3618 }
3619 LspCommand::ExecuteCommand { command, arguments } => {
3620 if initialized {
3621 tracing::info!("Processing ExecuteCommand: {}", command);
3622 spawn_request!(state, pending, |s, p| s
3623 .handle_execute_command(command, arguments, &p)
3624 .await);
3625 } else {
3626 tracing::trace!("LSP not initialized, cannot execute command");
3627 }
3628 }
3629 LspCommand::CodeActionResolve { request_id, action } => {
3630 if initialized {
3631 tracing::info!("Processing CodeActionResolve (request_id={})", request_id);
3632 spawn_request!(state, pending, |s, p| s
3633 .handle_code_action_resolve(request_id, *action, &p)
3634 .await);
3635 } else {
3636 tracing::trace!("LSP not initialized, cannot resolve code action");
3637 let _ = state.async_tx.send(AsyncMessage::LspCodeActionResolved {
3638 request_id,
3639 action: Err("LSP not initialized".to_string()),
3640 });
3641 }
3642 }
3643 LspCommand::CompletionResolve { request_id, item } => {
3644 if initialized {
3645 spawn_request!(state, pending, |s, p| s
3646 .handle_completion_resolve(request_id, *item, &p)
3647 .await);
3648 }
3649 }
3650 LspCommand::DocumentFormatting {
3651 request_id,
3652 uri,
3653 tab_size,
3654 insert_spaces,
3655 } => {
3656 if initialized {
3657 tracing::info!("Processing DocumentFormatting for {}", uri.as_str());
3658 spawn_request!(state, pending, |s, p| s
3659 .handle_document_formatting(
3660 request_id,
3661 uri,
3662 tab_size,
3663 insert_spaces,
3664 &p,
3665 )
3666 .await);
3667 }
3668 }
3669 LspCommand::DocumentRangeFormatting {
3670 request_id,
3671 uri,
3672 start_line,
3673 start_char,
3674 end_line,
3675 end_char,
3676 tab_size,
3677 insert_spaces,
3678 } => {
3679 if initialized {
3680 spawn_request!(state, pending, |s, p| s
3681 .handle_document_range_formatting(
3682 request_id,
3683 uri,
3684 start_line,
3685 start_char,
3686 end_line,
3687 end_char,
3688 tab_size,
3689 insert_spaces,
3690 &p,
3691 )
3692 .await);
3693 }
3694 }
3695 LspCommand::PrepareRename {
3696 request_id,
3697 uri,
3698 line,
3699 character,
3700 } => {
3701 if initialized {
3702 spawn_request!(state, pending, |s, p| s
3703 .handle_prepare_rename(request_id, uri, line, character, &p)
3704 .await);
3705 }
3706 }
3707 LspCommand::CancelRequest { request_id } => {
3708 tracing::info!("Processing CancelRequest for editor_id={}", request_id);
3709 let _ = state.handle_cancel_request(request_id).await;
3711 }
3712 LspCommand::PluginRequest {
3713 request_id,
3714 method,
3715 params,
3716 } => {
3717 if initialized {
3718 tracing::trace!("Processing plugin request {} ({})", request_id, method);
3719 spawn_request!(state, pending, |s, p| s
3720 .handle_plugin_request(request_id, method, params, &p)
3721 .await);
3722 } else {
3723 tracing::trace!(
3724 "Plugin LSP request {} received before initialization",
3725 request_id
3726 );
3727 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
3728 language: language_clone.clone(),
3729 request_id,
3730 result: Err("LSP not initialized".to_string()),
3731 });
3732 }
3733 }
3734 LspCommand::Shutdown => {
3735 tracing::info!("Processing Shutdown command");
3736 shutting_down.store(true, Ordering::SeqCst);
3738 let _ = state.handle_shutdown().await;
3739 break;
3740 }
3741 }
3742 }
3743
3744 tracing::info!("LSP task exiting for language: {}", self.language);
3745 }
3746}
3747
3748async fn read_message_from_stdout(
3750 stdout: &mut BufReader<ChildStdout>,
3751) -> Result<JsonRpcMessage, String> {
3752 let mut content_length: Option<usize> = None;
3754
3755 loop {
3756 let mut line = String::new();
3757 let bytes_read = stdout
3758 .read_line(&mut line)
3759 .await
3760 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
3761
3762 if bytes_read == 0 {
3764 return Err("LSP server closed stdout (EOF)".to_string());
3765 }
3766
3767 if line == "\r\n" {
3768 break;
3769 }
3770
3771 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
3772 content_length = Some(
3773 len_str
3774 .trim()
3775 .parse()
3776 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
3777 );
3778 }
3779 }
3780
3781 let content_length =
3782 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
3783
3784 let mut content = vec![0u8; content_length];
3786 stdout
3787 .read_exact(&mut content)
3788 .await
3789 .map_err(|e| format!("Failed to read content: {}", e))?;
3790
3791 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
3792
3793 tracing::trace!("Received LSP message: {}", json);
3794
3795 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
3796}
3797
3798fn registrations_from_params(params: Option<&Value>) -> Vec<(String, Option<Value>)> {
3802 params
3803 .and_then(|p| serde_json::from_value::<lsp_types::RegistrationParams>(p.clone()).ok())
3804 .map(|rp| {
3805 rp.registrations
3806 .into_iter()
3807 .map(|r| (r.method, r.register_options))
3808 .collect()
3809 })
3810 .unwrap_or_default()
3811}
3812
3813fn unregistrations_from_params(params: Option<&Value>) -> Vec<String> {
3816 params
3817 .and_then(|p| serde_json::from_value::<lsp_types::UnregistrationParams>(p.clone()).ok())
3818 .map(|up| up.unregisterations.into_iter().map(|u| u.method).collect())
3819 .unwrap_or_default()
3820}
3821
3822fn sync_raw_capabilities(
3834 capabilities: &Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
3835 registrations: &[(String, Option<Value>)],
3836 register: bool,
3837) {
3838 use lsp_types::{DiagnosticOptions, DiagnosticServerCapabilities};
3839
3840 if !registrations
3841 .iter()
3842 .any(|(method, _)| method == "textDocument/diagnostic")
3843 {
3844 return;
3845 }
3846
3847 let mut guard = capabilities.lock().unwrap();
3848 let caps = guard.get_or_insert_with(ServerCapabilities::default);
3849 for (method, options) in registrations {
3850 if method == "textDocument/diagnostic" {
3851 caps.diagnostic_provider = register.then(|| {
3852 let opts = options
3853 .as_ref()
3854 .and_then(|o| serde_json::from_value::<DiagnosticOptions>(o.clone()).ok())
3855 .unwrap_or_default();
3856 DiagnosticServerCapabilities::Options(opts)
3857 });
3858 }
3859 }
3860}
3861
3862fn resolve_workspace_configuration(
3872 items: &[Value],
3873 init_options: Option<&Value>,
3874 server_command: &str,
3875) -> Vec<Value> {
3876 if items.is_empty() {
3877 return vec![resolve_configuration_section(
3878 None,
3879 init_options,
3880 server_command,
3881 )];
3882 }
3883 items
3884 .iter()
3885 .map(|item| {
3886 let section = item
3887 .get("section")
3888 .and_then(Value::as_str)
3889 .filter(|s| !s.is_empty());
3890 resolve_configuration_section(section, init_options, server_command)
3891 })
3892 .collect()
3893}
3894
3895fn resolve_configuration_section(
3899 section: Option<&str>,
3900 init_options: Option<&Value>,
3901 server_command: &str,
3902) -> Value {
3903 if let Some(options) = init_options {
3904 match section {
3905 Some(section) => {
3906 let mut current = options;
3907 let mut resolved = true;
3908 for part in section.split('.') {
3909 match current.get(part) {
3910 Some(next) => current = next,
3911 None => {
3912 resolved = false;
3913 break;
3914 }
3915 }
3916 }
3917 if resolved {
3918 return current.clone();
3919 }
3920 }
3921 None => return options.clone(),
3923 }
3924 }
3925 default_configuration_section(server_command)
3926}
3927
3928fn default_configuration_section(server_command: &str) -> Value {
3933 if server_command_is_rust_analyzer(server_command) {
3934 serde_json::json!({
3935 "inlayHints": {
3936 "typeHints": { "enable": true },
3937 "parameterHints": { "enable": true },
3938 "chainingHints": { "enable": true },
3939 "closureReturnTypeHints": { "enable": "always" }
3940 }
3941 })
3942 } else {
3943 Value::Null
3944 }
3945}
3946
3947fn server_command_is_rust_analyzer(server_command: &str) -> bool {
3948 std::path::Path::new(server_command)
3949 .file_name()
3950 .and_then(|name| name.to_str())
3951 .unwrap_or(server_command)
3952 .contains("rust-analyzer")
3953}
3954
3955fn null_response(id: JsonRpcId) -> JsonRpcResponse {
3957 JsonRpcResponse {
3958 jsonrpc: "2.0".to_string(),
3959 id,
3960 result: Some(Value::Null),
3961 error: None,
3962 }
3963}
3964
3965fn parse_window_message(
3968 params: Option<Value>,
3969 default_type: i64,
3970) -> Option<(LspMessageType, String)> {
3971 let msg = serde_json::from_value::<serde_json::Map<String, Value>>(params?).ok()?;
3972 let type_num = msg
3973 .get("type")
3974 .and_then(|v| v.as_i64())
3975 .unwrap_or(default_type);
3976 let message = msg
3977 .get("message")
3978 .and_then(|v| v.as_str())
3979 .unwrap_or("(no message)")
3980 .to_string();
3981 let message_type = match type_num {
3982 1 => LspMessageType::Error,
3983 2 => LspMessageType::Warning,
3984 3 => LspMessageType::Info,
3985 _ => LspMessageType::Log,
3986 };
3987 Some((message_type, message))
3988}
3989
3990fn log_lsp_message(message_type: LspMessageType, language: &str, message: &str) {
3993 match message_type {
3994 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3995 LspMessageType::Warning => tracing::warn!("LSP ({}): {}", language, message),
3996 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3997 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3998 }
3999}
4000
4001fn parse_progress_notification(
4004 params: Option<Value>,
4005 language: &str,
4006) -> Option<(String, LspProgressValue)> {
4007 let progress = serde_json::from_value::<serde_json::Map<String, Value>>(params?).ok()?;
4008 let token = progress
4009 .get("token")
4010 .and_then(|v| {
4011 v.as_str()
4012 .map(|s| s.to_string())
4013 .or_else(|| v.as_i64().map(|n| n.to_string()))
4014 })
4015 .unwrap_or_else(|| "unknown".to_string());
4016 let value_obj = progress.get("value").and_then(|v| v.as_object())?;
4017 let kind = value_obj.get("kind").and_then(|v| v.as_str());
4018 let value = match kind {
4019 Some("begin") => {
4020 let title = value_obj
4021 .get("title")
4022 .and_then(|v| v.as_str())
4023 .unwrap_or("Working...")
4024 .to_string();
4025 let message = value_obj
4026 .get("message")
4027 .and_then(|v| v.as_str())
4028 .map(|s| s.to_string());
4029 let percentage = value_obj
4030 .get("percentage")
4031 .and_then(|v| v.as_u64())
4032 .map(|p| p as u32);
4033 tracing::info!(
4034 "LSP ({}) progress begin: {} {:?} {:?}",
4035 language,
4036 title,
4037 message,
4038 percentage
4039 );
4040 LspProgressValue::Begin {
4041 title,
4042 message,
4043 percentage,
4044 }
4045 }
4046 Some("report") => {
4047 let message = value_obj
4048 .get("message")
4049 .and_then(|v| v.as_str())
4050 .map(|s| s.to_string());
4051 let percentage = value_obj
4052 .get("percentage")
4053 .and_then(|v| v.as_u64())
4054 .map(|p| p as u32);
4055 tracing::trace!(
4056 "LSP ({}) progress report: {:?} {:?}",
4057 language,
4058 message,
4059 percentage
4060 );
4061 LspProgressValue::Report {
4062 message,
4063 percentage,
4064 }
4065 }
4066 Some("end") => {
4067 let message = value_obj
4068 .get("message")
4069 .and_then(|v| v.as_str())
4070 .map(|s| s.to_string());
4071 tracing::info!("LSP ({}) progress end: {:?}", language, message);
4072 LspProgressValue::End { message }
4073 }
4074 _ => return None,
4075 };
4076 Some((token, value))
4077}
4078
4079#[allow(clippy::too_many_arguments)]
4081#[allow(clippy::let_underscore_must_use)] async fn handle_message_dispatch(
4083 message: JsonRpcMessage,
4084 pending: &PendingRequests,
4085 async_tx: &std_mpsc::Sender<AsyncMessage>,
4086 language: &str,
4087 server_name: &str,
4088 server_command: &str,
4089 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
4090 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
4091 config_options: &Arc<std::sync::Mutex<Option<Value>>>,
4092 capabilities: &Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
4093) -> Result<(), String> {
4094 match message {
4095 JsonRpcMessage::Response(response) => {
4096 tracing::trace!("Received LSP response for request id={}", response.id);
4097 let pending_id = response.id.as_i64();
4102 if let Some((method, tx)) =
4103 pending_id.and_then(|id| pending.lock().unwrap().remove(&id))
4104 {
4105 let result = if let Some(error) = response.error {
4106 log_response_error(error.code, &error.message, server_name, language, &method);
4107 Err(format!(
4108 "LSP error from '{}' ({}): {} (code {})",
4109 server_name, language, error.message, error.code
4110 ))
4111 } else {
4112 tracing::trace!(
4113 "LSP response success from '{}' ({}) for request id={}",
4114 server_name,
4115 language,
4116 response.id
4117 );
4118 Ok(response.result.unwrap_or(serde_json::Value::Null))
4120 };
4121 let _ = tx.send(result);
4122 } else {
4123 tracing::warn!(
4124 "Received LSP response from '{}' ({}) for unknown request id={}",
4125 server_name,
4126 language,
4127 response.id
4128 );
4129 }
4130 }
4131 JsonRpcMessage::Notification(notification) => {
4132 tracing::trace!("Received LSP notification: {}", notification.method);
4133 handle_notification_dispatch(
4134 notification,
4135 async_tx,
4136 language,
4137 server_name,
4138 document_versions,
4139 )
4140 .await?;
4141 }
4142 JsonRpcMessage::Request(request) => {
4143 tracing::trace!("Received request from server: {}", request.method);
4145 let response = match request.method.as_str() {
4146 "window/workDoneProgress/create" => {
4147 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
4149 null_response(request.id)
4150 }
4151 "workspace/configuration" => {
4152 tracing::trace!(
4159 "Responding to workspace/configuration for {}",
4160 server_command
4161 );
4162
4163 let empty = Vec::new();
4164 let items = request
4165 .params
4166 .as_ref()
4167 .and_then(|p| p.get("items"))
4168 .and_then(|items| items.as_array())
4169 .unwrap_or(&empty);
4170
4171 let stored = config_options.lock().unwrap().clone();
4172 let configs =
4173 resolve_workspace_configuration(items, stored.as_ref(), server_command);
4174
4175 JsonRpcResponse {
4176 jsonrpc: "2.0".to_string(),
4177 id: request.id,
4178 result: Some(Value::Array(configs)),
4179 error: None,
4180 }
4181 }
4182 "client/registerCapability" => {
4183 let registrations = registrations_from_params(request.params.as_ref());
4190 tracing::debug!(
4191 "client/registerCapability (id={}) registering {} method(s): {:?}",
4192 request.id,
4193 registrations.len(),
4194 registrations.iter().map(|(m, _)| m).collect::<Vec<_>>()
4195 );
4196 if !registrations.is_empty() {
4197 sync_raw_capabilities(capabilities, ®istrations, true);
4200 let _ = async_tx.send(AsyncMessage::LspDynamicCapabilities {
4201 language: language.to_string(),
4202 server_name: server_name.to_string(),
4203 register: true,
4204 registrations,
4205 });
4206 }
4207 null_response(request.id)
4208 }
4209 "client/unregisterCapability" => {
4210 let methods = unregistrations_from_params(request.params.as_ref());
4214 tracing::debug!(
4215 "client/unregisterCapability (id={}) unregistering {} method(s): {:?}",
4216 request.id,
4217 methods.len(),
4218 methods
4219 );
4220 if !methods.is_empty() {
4221 let registrations: Vec<(String, Option<Value>)> =
4222 methods.into_iter().map(|m| (m, None)).collect();
4223 sync_raw_capabilities(capabilities, ®istrations, false);
4224 let _ = async_tx.send(AsyncMessage::LspDynamicCapabilities {
4225 language: language.to_string(),
4226 server_name: server_name.to_string(),
4227 register: false,
4228 registrations,
4229 });
4230 }
4231 null_response(request.id)
4232 }
4233 "workspace/diagnostic/refresh" => {
4234 tracing::info!(
4237 "LSP ({}) requested diagnostic refresh (workspace/diagnostic/refresh)",
4238 language
4239 );
4240 let _ = async_tx.send(AsyncMessage::LspDiagnosticRefresh {
4241 language: language.to_string(),
4242 });
4243 null_response(request.id)
4244 }
4245 "workspace/inlayHint/refresh" => {
4246 tracing::info!(
4251 "LSP ({}) requested inlay-hint refresh (workspace/inlayHint/refresh)",
4252 language
4253 );
4254 let _ = async_tx.send(AsyncMessage::LspInlayHintRefresh {
4255 language: language.to_string(),
4256 });
4257 null_response(request.id)
4258 }
4259 "workspace/semanticTokens/refresh" => {
4260 tracing::info!(
4262 "LSP ({}) requested semantic-tokens refresh (workspace/semanticTokens/refresh)",
4263 language
4264 );
4265 let _ = async_tx.send(AsyncMessage::LspSemanticTokensRefresh {
4266 language: language.to_string(),
4267 });
4268 null_response(request.id)
4269 }
4270 "workspace/applyEdit" => {
4271 tracing::info!("LSP ({}) received workspace/applyEdit request", language);
4273 let applied = if let Some(params) = &request.params {
4274 match serde_json::from_value::<lsp_types::ApplyWorkspaceEditParams>(
4275 params.clone(),
4276 ) {
4277 Ok(apply_params) => {
4278 let label = apply_params.label.clone();
4279 let _ = async_tx.send(AsyncMessage::LspApplyEdit {
4280 edit: apply_params.edit,
4281 label,
4282 });
4283 true
4284 }
4285 Err(e) => {
4286 tracing::error!(
4287 "Failed to parse workspace/applyEdit params: {}",
4288 e
4289 );
4290 false
4291 }
4292 }
4293 } else {
4294 false
4295 };
4296 JsonRpcResponse {
4297 jsonrpc: "2.0".to_string(),
4298 id: request.id,
4299 result: Some(serde_json::json!({ "applied": applied })),
4300 error: None,
4301 }
4302 }
4303 _ => {
4304 tracing::debug!("Server request for plugins: {}", request.method);
4306 let _ = async_tx.send(AsyncMessage::LspServerRequest {
4307 language: language.to_string(),
4308 server_command: server_command.to_string(),
4309 method: request.method.clone(),
4310 params: request.params.clone(),
4311 });
4312 null_response(request.id)
4313 }
4314 };
4315
4316 let json = serde_json::to_string(&response)
4318 .map_err(|e| format!("Failed to serialize response: {}", e))?;
4319 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
4320
4321 let mut stdin = stdin_writer.lock().await;
4322 use tokio::io::AsyncWriteExt;
4323 if let Err(e) = stdin.write_all(message.as_bytes()).await {
4324 tracing::error!("Failed to write server response: {}", e);
4325 }
4326 if let Err(e) = stdin.flush().await {
4327 tracing::error!("Failed to flush server response: {}", e);
4328 }
4329 tracing::trace!("Sent response to server request id={}", response.id);
4330 }
4331 }
4332 Ok(())
4333}
4334
4335#[allow(clippy::let_underscore_must_use)] async fn handle_notification_dispatch(
4338 notification: JsonRpcNotification,
4339 async_tx: &std_mpsc::Sender<AsyncMessage>,
4340 language: &str,
4341 server_name: &str,
4342 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
4343) -> Result<(), String> {
4344 match notification.method.as_str() {
4345 PublishDiagnostics::METHOD => {
4346 if let Some(params) = notification.params {
4347 let params: PublishDiagnosticsParams = serde_json::from_value(params)
4348 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
4349
4350 if let Some(diag_version) = params.version {
4354 let path = PathBuf::from(params.uri.path().as_str());
4355 let current_version = document_versions.lock().unwrap().get(&path).copied();
4356 if let Some(current) = current_version {
4357 if (diag_version as i64) < current {
4358 tracing::debug!(
4359 "LSP ({}): dropping stale diagnostics for {} (diag version {} < current {})",
4360 language,
4361 params.uri.as_str(),
4362 diag_version,
4363 current
4364 );
4365 return Ok(());
4366 }
4367 }
4368 }
4369
4370 tracing::trace!(
4371 "Received {} diagnostics for {}",
4372 params.diagnostics.len(),
4373 params.uri.as_str()
4374 );
4375
4376 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
4378 uri: params.uri.to_string(),
4379 diagnostics: params.diagnostics,
4380 server_name: server_name.to_string(),
4381 });
4382 }
4383 }
4384 "window/showMessage" => {
4385 if let Some((message_type, message)) = parse_window_message(notification.params, 3) {
4386 log_lsp_message(message_type, language, &message);
4387 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
4388 language: language.to_string(),
4389 message_type,
4390 message,
4391 });
4392 }
4393 }
4394 "window/logMessage" => {
4395 if let Some((message_type, message)) = parse_window_message(notification.params, 4) {
4396 log_lsp_message(message_type, language, &message);
4397 let _ = async_tx.send(AsyncMessage::LspLogMessage {
4398 language: language.to_string(),
4399 message_type,
4400 message,
4401 });
4402 }
4403 }
4404 "$/progress" => {
4405 if let Some((token, value)) = parse_progress_notification(notification.params, language)
4406 {
4407 let _ = async_tx.send(AsyncMessage::LspProgress {
4408 language: language.to_string(),
4409 token,
4410 value,
4411 });
4412 }
4413 }
4414 "experimental/serverStatus" => {
4415 if let Some(params) = notification.params {
4418 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
4419 {
4420 let quiescent = status
4421 .get("quiescent")
4422 .and_then(|v| v.as_bool())
4423 .unwrap_or(false);
4424
4425 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
4426
4427 if quiescent {
4428 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
4430 language: language.to_string(),
4431 });
4432 }
4433 }
4434 }
4435 }
4436 _ => {
4437 tracing::debug!("Unhandled notification: {}", notification.method);
4438 }
4439 }
4440
4441 Ok(())
4442}
4443
4444static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
4446
4447pub struct LspHandle {
4449 id: u64,
4451
4452 scope: crate::services::lsp::manager::LanguageScope,
4454
4455 command_tx: mpsc::Sender<LspCommand>,
4457
4458 state: Arc<Mutex<LspClientState>>,
4460
4461 runtime: tokio::runtime::Handle,
4463
4464 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
4467}
4468
4469#[allow(clippy::let_underscore_must_use)]
4473impl LspHandle {
4474 #[allow(clippy::too_many_arguments)]
4483 pub fn spawn(
4484 runtime: &tokio::runtime::Handle,
4485 command: &str,
4486 args: &[String],
4487 env: std::collections::HashMap<String, String>,
4488 scope: crate::services::lsp::manager::LanguageScope,
4489 server_name: String,
4490 async_bridge: &AsyncBridge,
4491 process_limits: ProcessLimits,
4492 language_id_overrides: std::collections::HashMap<String, String>,
4493 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
4494 ) -> Result<Self, String> {
4495 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
4497 let language_label = scope.label().to_string();
4498 let language_clone = language_label.clone();
4499 let server_name_clone = server_name.clone();
4500 let command = command.to_string();
4501 let args = args.to_vec();
4502 let state = Arc::new(Mutex::new(LspClientState::Starting));
4503
4504 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language_label);
4506
4507 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4509 language: language_label.clone(),
4510 server_name: server_name_clone.clone(),
4511 status: LspServerStatus::Starting,
4512 message: None,
4513 });
4514
4515 let document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>> =
4519 Arc::new(std::sync::Mutex::new(HashMap::new()));
4520 let document_versions_for_task = document_versions.clone();
4521
4522 let state_clone = state.clone();
4523 let stderr_log_path_clone = stderr_log_path.clone();
4524 runtime.spawn(async move {
4525 match LspTask::spawn(
4526 &command,
4527 &args,
4528 &env,
4529 language_clone.clone(),
4530 server_name_clone.clone(),
4531 async_tx.clone(),
4532 &process_limits,
4533 stderr_log_path_clone.clone(),
4534 language_id_overrides,
4535 document_versions_for_task,
4536 long_running_spawner,
4537 )
4538 .await
4539 {
4540 Ok(task) => {
4541 task.run(command_rx).await;
4542 }
4543 Err(e) => {
4544 tracing::error!("Failed to spawn LSP task: {}", e);
4545
4546 let stub = format!(
4561 "[fresh] LSP server '{}' for {} failed to spawn:\n {}\n\n\
4562 Configured command: {} {}\n",
4563 server_name_clone,
4564 language_clone,
4565 e,
4566 command,
4567 args.join(" "),
4568 );
4569 if let Err(write_err) = std::fs::write(&stderr_log_path_clone, stub.as_bytes())
4570 {
4571 tracing::warn!(
4572 "Failed to write LSP failure-stub log for {}: {}",
4573 language_clone,
4574 write_err,
4575 );
4576 }
4577
4578 if let Ok(mut s) = state_clone.lock() {
4580 let _ = s.transition_to(LspClientState::Error);
4581 }
4582
4583 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4584 language: language_clone.clone(),
4585 server_name: server_name_clone.clone(),
4586 status: LspServerStatus::Error,
4587 message: None,
4588 });
4589 let _ = async_tx.send(AsyncMessage::LspError {
4590 language: language_clone,
4591 error: e,
4592 stderr_log_path: Some(stderr_log_path_clone),
4593 });
4594 }
4595 }
4596 });
4597
4598 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4599
4600 Ok(Self {
4601 id,
4602 scope,
4603 command_tx,
4604 state,
4605 runtime: runtime.clone(),
4606 document_versions,
4607 })
4608 }
4609
4610 pub fn id(&self) -> u64 {
4612 self.id
4613 }
4614
4615 pub fn scope(&self) -> &crate::services::lsp::manager::LanguageScope {
4617 &self.scope
4618 }
4619
4620 pub fn document_version(&self, path: &std::path::Path) -> Option<i64> {
4623 self.document_versions
4624 .lock()
4625 .ok()
4626 .and_then(|versions| versions.get(path).copied())
4627 }
4628
4629 pub fn initialize(
4638 &self,
4639 root_uri: Option<Uri>,
4640 initialization_options: Option<Value>,
4641 ) -> Result<(), String> {
4642 {
4644 let mut state = self.state.lock().unwrap();
4645 if !state.can_initialize() {
4646 return Err(format!(
4647 "Cannot initialize: client is in state {:?}",
4648 *state
4649 ));
4650 }
4651 state.transition_to(LspClientState::Initializing)?;
4653 }
4654
4655 let state = self.state.clone();
4656
4657 let (tx, rx) = oneshot::channel();
4659
4660 self.command_tx
4661 .try_send(LspCommand::Initialize {
4662 root_uri,
4663 initialization_options,
4664 response: tx,
4665 })
4666 .map_err(|_| "Failed to send initialize command".to_string())?;
4667
4668 let runtime = self.runtime.clone();
4670 runtime.spawn(async move {
4671 match tokio::time::timeout(std::time::Duration::from_secs(60), rx).await {
4672 Ok(Ok(Ok(_))) => {
4673 if let Ok(mut s) = state.lock() {
4675 let _ = s.transition_to(LspClientState::Running);
4676 }
4677 tracing::info!("LSP initialization completed successfully");
4678 }
4679 Ok(Ok(Err(e))) => {
4680 tracing::error!("LSP initialization failed: {}", e);
4681 if let Ok(mut s) = state.lock() {
4682 let _ = s.transition_to(LspClientState::Error);
4683 }
4684 }
4685 Ok(Err(_)) => {
4686 tracing::error!("LSP initialization response channel closed");
4687 if let Ok(mut s) = state.lock() {
4688 let _ = s.transition_to(LspClientState::Error);
4689 }
4690 }
4691 Err(_) => {
4692 tracing::error!("LSP initialization timed out after 60 seconds");
4693 if let Ok(mut s) = state.lock() {
4694 let _ = s.transition_to(LspClientState::Error);
4695 }
4696 }
4697 }
4698 });
4699
4700 Ok(())
4701 }
4702
4703 pub fn is_initialized(&self) -> bool {
4705 self.state.lock().unwrap().can_send_requests()
4706 }
4707
4708 pub fn state(&self) -> LspClientState {
4710 *self.state.lock().unwrap()
4711 }
4712
4713 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
4719 if !self.scope.accepts(&language_id) {
4721 tracing::warn!(
4722 "did_open: document language '{}' not accepted by LSP handle (serves {:?}) for {}",
4723 language_id,
4724 self.scope,
4725 uri.as_str()
4726 );
4727 return Err(format!(
4728 "Language mismatch: document is '{}' but LSP serves {:?}",
4729 language_id, self.scope
4730 ));
4731 }
4732
4733 self.command_tx
4735 .try_send(LspCommand::DidOpen {
4736 uri,
4737 text,
4738 language_id,
4739 })
4740 .map_err(|_| "Failed to send did_open command".to_string())
4741 }
4742
4743 pub fn did_change(
4745 &self,
4746 uri: Uri,
4747 content_changes: Vec<TextDocumentContentChangeEvent>,
4748 ) -> Result<(), String> {
4749 self.command_tx
4751 .try_send(LspCommand::DidChange {
4752 uri,
4753 content_changes,
4754 })
4755 .map_err(|_| "Failed to send did_change command".to_string())
4756 }
4757
4758 pub fn did_close(&self, uri: Uri) -> Result<(), String> {
4760 self.command_tx
4761 .try_send(LspCommand::DidClose { uri })
4762 .map_err(|_| "Failed to send did_close command".to_string())
4763 }
4764
4765 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
4767 self.command_tx
4768 .try_send(LspCommand::DidSave { uri, text })
4769 .map_err(|_| "Failed to send did_save command".to_string())
4770 }
4771
4772 pub fn add_workspace_folder(&self, uri: lsp_types::Uri, name: String) -> Result<(), String> {
4774 self.command_tx
4775 .try_send(LspCommand::DidChangeWorkspaceFolders {
4776 added: vec![lsp_types::WorkspaceFolder { uri, name }],
4777 removed: vec![],
4778 })
4779 .map_err(|_| "Failed to send workspace folder change".to_string())
4780 }
4781
4782 pub fn completion(
4784 &self,
4785 request_id: u64,
4786 uri: Uri,
4787 line: u32,
4788 character: u32,
4789 ) -> Result<(), String> {
4790 self.command_tx
4791 .try_send(LspCommand::Completion {
4792 request_id,
4793 uri,
4794 line,
4795 character,
4796 })
4797 .map_err(|_| "Failed to send completion command".to_string())
4798 }
4799
4800 pub fn goto_definition(
4802 &self,
4803 request_id: u64,
4804 uri: Uri,
4805 line: u32,
4806 character: u32,
4807 ) -> Result<(), String> {
4808 self.command_tx
4809 .try_send(LspCommand::GotoDefinition {
4810 request_id,
4811 uri,
4812 line,
4813 character,
4814 })
4815 .map_err(|_| "Failed to send goto_definition command".to_string())
4816 }
4817
4818 pub fn implementation(
4820 &self,
4821 request_id: u64,
4822 uri: Uri,
4823 line: u32,
4824 character: u32,
4825 ) -> Result<(), String> {
4826 self.command_tx
4827 .try_send(LspCommand::Implementation {
4828 request_id,
4829 uri,
4830 line,
4831 character,
4832 })
4833 .map_err(|_| "Failed to send implementation command".to_string())
4834 }
4835
4836 pub fn rename(
4838 &self,
4839 request_id: u64,
4840 uri: Uri,
4841 line: u32,
4842 character: u32,
4843 new_name: String,
4844 ) -> Result<(), String> {
4845 self.command_tx
4846 .try_send(LspCommand::Rename {
4847 request_id,
4848 uri,
4849 line,
4850 character,
4851 new_name,
4852 })
4853 .map_err(|_| "Failed to send rename command".to_string())
4854 }
4855
4856 pub fn hover(
4858 &self,
4859 request_id: u64,
4860 uri: Uri,
4861 line: u32,
4862 character: u32,
4863 ) -> Result<(), String> {
4864 self.command_tx
4865 .try_send(LspCommand::Hover {
4866 request_id,
4867 uri,
4868 line,
4869 character,
4870 })
4871 .map_err(|_| "Failed to send hover command".to_string())
4872 }
4873
4874 pub fn references(
4876 &self,
4877 request_id: u64,
4878 uri: Uri,
4879 line: u32,
4880 character: u32,
4881 ) -> Result<(), String> {
4882 self.command_tx
4883 .try_send(LspCommand::References {
4884 request_id,
4885 uri,
4886 line,
4887 character,
4888 })
4889 .map_err(|_| "Failed to send references command".to_string())
4890 }
4891
4892 pub fn signature_help(
4894 &self,
4895 request_id: u64,
4896 uri: Uri,
4897 line: u32,
4898 character: u32,
4899 ) -> Result<(), String> {
4900 self.command_tx
4901 .try_send(LspCommand::SignatureHelp {
4902 request_id,
4903 uri,
4904 line,
4905 character,
4906 })
4907 .map_err(|_| "Failed to send signature_help command".to_string())
4908 }
4909
4910 #[allow(clippy::too_many_arguments)]
4912 pub fn code_actions(
4913 &self,
4914 request_id: u64,
4915 uri: Uri,
4916 start_line: u32,
4917 start_char: u32,
4918 end_line: u32,
4919 end_char: u32,
4920 diagnostics: Vec<lsp_types::Diagnostic>,
4921 ) -> Result<(), String> {
4922 self.command_tx
4923 .try_send(LspCommand::CodeActions {
4924 request_id,
4925 uri,
4926 start_line,
4927 start_char,
4928 end_line,
4929 end_char,
4930 diagnostics,
4931 })
4932 .map_err(|_| "Failed to send code_actions command".to_string())
4933 }
4934
4935 pub fn execute_command(
4940 &self,
4941 command: String,
4942 arguments: Option<Vec<Value>>,
4943 ) -> Result<(), String> {
4944 self.command_tx
4945 .try_send(LspCommand::ExecuteCommand { command, arguments })
4946 .map_err(|_| "Failed to send execute_command command".to_string())
4947 }
4948
4949 pub fn code_action_resolve(
4954 &self,
4955 request_id: u64,
4956 action: lsp_types::CodeAction,
4957 ) -> Result<(), String> {
4958 self.command_tx
4959 .try_send(LspCommand::CodeActionResolve {
4960 request_id,
4961 action: Box::new(action),
4962 })
4963 .map_err(|_| "Failed to send code_action_resolve command".to_string())
4964 }
4965
4966 pub fn completion_resolve(
4968 &self,
4969 request_id: u64,
4970 item: lsp_types::CompletionItem,
4971 ) -> Result<(), String> {
4972 self.command_tx
4973 .try_send(LspCommand::CompletionResolve {
4974 request_id,
4975 item: Box::new(item),
4976 })
4977 .map_err(|_| "Failed to send completion_resolve command".to_string())
4978 }
4979
4980 pub fn document_formatting(
4982 &self,
4983 request_id: u64,
4984 uri: Uri,
4985 tab_size: u32,
4986 insert_spaces: bool,
4987 ) -> Result<(), String> {
4988 self.command_tx
4989 .try_send(LspCommand::DocumentFormatting {
4990 request_id,
4991 uri,
4992 tab_size,
4993 insert_spaces,
4994 })
4995 .map_err(|_| "Failed to send document_formatting command".to_string())
4996 }
4997
4998 #[allow(clippy::too_many_arguments)]
5000 pub fn document_range_formatting(
5001 &self,
5002 request_id: u64,
5003 uri: Uri,
5004 start_line: u32,
5005 start_char: u32,
5006 end_line: u32,
5007 end_char: u32,
5008 tab_size: u32,
5009 insert_spaces: bool,
5010 ) -> Result<(), String> {
5011 self.command_tx
5012 .try_send(LspCommand::DocumentRangeFormatting {
5013 request_id,
5014 uri,
5015 start_line,
5016 start_char,
5017 end_line,
5018 end_char,
5019 tab_size,
5020 insert_spaces,
5021 })
5022 .map_err(|_| "Failed to send document_range_formatting command".to_string())
5023 }
5024
5025 pub fn prepare_rename(
5027 &self,
5028 request_id: u64,
5029 uri: Uri,
5030 line: u32,
5031 character: u32,
5032 ) -> Result<(), String> {
5033 self.command_tx
5034 .try_send(LspCommand::PrepareRename {
5035 request_id,
5036 uri,
5037 line,
5038 character,
5039 })
5040 .map_err(|_| "Failed to send prepare_rename command".to_string())
5041 }
5042
5043 pub fn document_diagnostic(
5048 &self,
5049 request_id: u64,
5050 uri: Uri,
5051 previous_result_id: Option<String>,
5052 ) -> Result<(), String> {
5053 self.command_tx
5054 .try_send(LspCommand::DocumentDiagnostic {
5055 request_id,
5056 uri,
5057 previous_result_id,
5058 })
5059 .map_err(|_| "Failed to send document_diagnostic command".to_string())
5060 }
5061
5062 pub fn inlay_hints(
5066 &self,
5067 request_id: u64,
5068 uri: Uri,
5069 start_line: u32,
5070 start_char: u32,
5071 end_line: u32,
5072 end_char: u32,
5073 ) -> Result<(), String> {
5074 self.command_tx
5075 .try_send(LspCommand::InlayHints {
5076 request_id,
5077 uri,
5078 start_line,
5079 start_char,
5080 end_line,
5081 end_char,
5082 })
5083 .map_err(|_| "Failed to send inlay_hints command".to_string())
5084 }
5085
5086 pub fn folding_ranges(&self, request_id: u64, uri: Uri) -> Result<(), String> {
5088 self.command_tx
5089 .try_send(LspCommand::FoldingRange { request_id, uri })
5090 .map_err(|_| "Failed to send folding_range command".to_string())
5091 }
5092
5093 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
5095 self.command_tx
5096 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
5097 .map_err(|_| "Failed to send semantic_tokens command".to_string())
5098 }
5099
5100 pub fn semantic_tokens_full_delta(
5102 &self,
5103 request_id: u64,
5104 uri: Uri,
5105 previous_result_id: String,
5106 ) -> Result<(), String> {
5107 self.command_tx
5108 .try_send(LspCommand::SemanticTokensFullDelta {
5109 request_id,
5110 uri,
5111 previous_result_id,
5112 })
5113 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
5114 }
5115
5116 pub fn semantic_tokens_range(
5118 &self,
5119 request_id: u64,
5120 uri: Uri,
5121 range: lsp_types::Range,
5122 ) -> Result<(), String> {
5123 self.command_tx
5124 .try_send(LspCommand::SemanticTokensRange {
5125 request_id,
5126 uri,
5127 range,
5128 })
5129 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
5130 }
5131
5132 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
5137 self.command_tx
5138 .try_send(LspCommand::CancelRequest { request_id })
5139 .map_err(|_| "Failed to send cancel_request command".to_string())
5140 }
5141
5142 pub fn send_plugin_request(
5144 &self,
5145 request_id: u64,
5146 method: String,
5147 params: Option<Value>,
5148 ) -> Result<(), String> {
5149 tracing::trace!(
5150 "LspHandle sending plugin request {}: method={}",
5151 request_id,
5152 method
5153 );
5154 match self.command_tx.try_send(LspCommand::PluginRequest {
5155 request_id,
5156 method,
5157 params,
5158 }) {
5159 Ok(()) => {
5160 tracing::trace!(
5161 "LspHandle enqueued plugin request {} successfully",
5162 request_id
5163 );
5164 Ok(())
5165 }
5166 Err(e) => {
5167 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
5168 Err("Failed to send plugin LSP request".to_string())
5169 }
5170 }
5171 }
5172
5173 pub fn shutdown(&self) -> Result<(), String> {
5175 {
5177 let mut state = self.state.lock().unwrap();
5178 if let Err(e) = state.transition_to(LspClientState::Stopping) {
5179 tracing::warn!("State transition warning during shutdown: {}", e);
5180 }
5182 }
5183
5184 self.command_tx
5185 .try_send(LspCommand::Shutdown)
5186 .map_err(|_| "Failed to send shutdown command".to_string())?;
5187
5188 {
5191 let mut state = self.state.lock().unwrap();
5192 let _ = state.transition_to(LspClientState::Stopped);
5193 }
5194
5195 Ok(())
5196 }
5197}
5198
5199#[allow(clippy::let_underscore_must_use)] impl Drop for LspHandle {
5201 fn drop(&mut self) {
5202 let _ = self.command_tx.try_send(LspCommand::Shutdown);
5208
5209 if let Ok(mut state) = self.state.lock() {
5211 let _ = state.transition_to(LspClientState::Stopped);
5212 }
5213 }
5214}
5215
5216#[cfg(test)]
5217mod tests {
5218 use super::*;
5219 use crate::services::lsp::manager::LanguageScope;
5220 use crate::services::remote::LocalLongRunningSpawner;
5221
5222 fn config_item(section: &str) -> Value {
5224 serde_json::json!({ "section": section })
5225 }
5226
5227 #[test]
5228 fn workspace_configuration_resolves_section_from_init_options() {
5229 let opts = serde_json::json!({
5232 "harper-ls": { "linters": { "SpellCheck": false } }
5233 });
5234 let configs =
5235 resolve_workspace_configuration(&[config_item("harper-ls")], Some(&opts), "harper-ls");
5236 assert_eq!(
5237 configs,
5238 vec![serde_json::json!({ "linters": { "SpellCheck": false } })]
5239 );
5240 }
5241
5242 #[test]
5243 fn workspace_configuration_resolves_dotted_section() {
5244 let opts = serde_json::json!({ "a": { "b": { "c": 1 } } });
5245 let configs =
5246 resolve_workspace_configuration(&[config_item("a.b")], Some(&opts), "some-ls");
5247 assert_eq!(configs, vec![serde_json::json!({ "c": 1 })]);
5248 }
5249
5250 #[test]
5251 fn workspace_configuration_unknown_section_is_null_for_non_rust() {
5252 let opts = serde_json::json!({ "harper-ls": { "linters": {} } });
5255 let configs =
5256 resolve_workspace_configuration(&[config_item("marksman")], Some(&opts), "marksman");
5257 assert_eq!(configs, vec![Value::Null]);
5258 }
5259
5260 #[test]
5261 fn workspace_configuration_rust_analyzer_default_enables_inlay_hints() {
5262 for command in [
5264 "rust-analyzer",
5265 "/usr/local/bin/rust-analyzer",
5266 "custom-rust-analyzer",
5267 ] {
5268 let configs =
5269 resolve_workspace_configuration(&[config_item("rust-analyzer")], None, command);
5270 assert_eq!(configs.len(), 1);
5271 assert_eq!(
5272 configs[0]["inlayHints"]["typeHints"]["enable"], true,
5273 "{command}"
5274 );
5275 }
5276 }
5277
5278 #[test]
5279 fn workspace_configuration_non_rust_without_options_is_null() {
5280 let configs =
5281 resolve_workspace_configuration(&[config_item("harper-ls")], None, "harper-ls");
5282 assert_eq!(configs, vec![Value::Null]);
5283 }
5284
5285 #[test]
5286 fn workspace_configuration_one_response_per_item() {
5287 let opts = serde_json::json!({ "a": 1, "b": 2 });
5288 let configs = resolve_workspace_configuration(
5289 &[config_item("a"), config_item("b"), config_item("missing")],
5290 Some(&opts),
5291 "some-ls",
5292 );
5293 assert_eq!(
5294 configs,
5295 vec![serde_json::json!(1), serde_json::json!(2), Value::Null]
5296 );
5297 }
5298
5299 #[test]
5300 fn workspace_configuration_no_items_returns_whole_object() {
5301 let opts = serde_json::json!({ "linters": { "SpellCheck": false } });
5303 let configs = resolve_workspace_configuration(&[], Some(&opts), "harper-ls");
5304 assert_eq!(configs, vec![opts]);
5305 }
5306
5307 fn local_spawner() -> Arc<dyn crate::services::remote::LongRunningSpawner> {
5310 Arc::new(LocalLongRunningSpawner::new(
5311 Arc::new(crate::services::env_provider::EnvProvider::inactive()),
5312 Arc::new(crate::services::workspace_trust::WorkspaceTrust::permissive()),
5313 ))
5314 }
5315
5316 #[test]
5317 fn test_json_rpc_request_serialization() {
5318 let request = JsonRpcRequest {
5319 jsonrpc: "2.0".to_string(),
5320 id: JsonRpcId::Number(1),
5321 method: "initialize".to_string(),
5322 params: Some(serde_json::json!({"rootUri": "file:///test"})),
5323 };
5324
5325 let json = serde_json::to_string(&request).unwrap();
5326 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5327 assert!(json.contains("\"id\":1"));
5328 assert!(json.contains("\"method\":\"initialize\""));
5329 assert!(json.contains("\"rootUri\":\"file:///test\""));
5330 }
5331
5332 #[test]
5333 fn test_json_rpc_response_serialization() {
5334 let response = JsonRpcResponse {
5335 jsonrpc: "2.0".to_string(),
5336 id: JsonRpcId::Number(1),
5337 result: Some(serde_json::json!({"success": true})),
5338 error: None,
5339 };
5340
5341 let json = serde_json::to_string(&response).unwrap();
5342 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5343 assert!(json.contains("\"id\":1"));
5344 assert!(json.contains("\"success\":true"));
5345 assert!(!json.contains("\"error\""));
5346 }
5347
5348 #[test]
5356 fn code_action_capability_advertises_literal_support() {
5357 let caps = create_client_capabilities();
5358 let code_action = caps
5359 .text_document
5360 .as_ref()
5361 .and_then(|td| td.code_action.as_ref())
5362 .expect("code_action capability must be set");
5363
5364 let literal = code_action
5365 .code_action_literal_support
5366 .as_ref()
5367 .expect("codeActionLiteralSupport must be advertised");
5368
5369 let kinds = &literal.code_action_kind.value_set;
5370 for required in [
5371 "",
5372 "quickfix",
5373 "refactor",
5374 "refactor.extract",
5375 "refactor.inline",
5376 "refactor.rewrite",
5377 "source",
5378 "source.organizeImports",
5379 ] {
5380 assert!(
5381 kinds.iter().any(|k| k == required),
5382 "expected codeActionKind value_set to include {required:?}, got {kinds:?}",
5383 );
5384 }
5385 }
5386
5387 #[test]
5388 fn advertises_dynamic_registration_on_honored_capabilities() {
5389 let caps = create_client_capabilities();
5394 let td = caps
5395 .text_document
5396 .as_ref()
5397 .expect("text_document capabilities must be set");
5398
5399 assert_eq!(
5400 td.inlay_hint.as_ref().and_then(|c| c.dynamic_registration),
5401 Some(true),
5402 "inlay_hint must advertise dynamicRegistration"
5403 );
5404 assert_eq!(
5405 td.completion.as_ref().and_then(|c| c.dynamic_registration),
5406 Some(true),
5407 "completion must advertise dynamicRegistration"
5408 );
5409 assert_eq!(
5410 td.formatting.as_ref().and_then(|c| c.dynamic_registration),
5411 Some(true),
5412 "formatting must advertise dynamicRegistration"
5413 );
5414 assert_eq!(
5415 td.document_symbol
5416 .as_ref()
5417 .and_then(|c| c.dynamic_registration),
5418 Some(true),
5419 "document_symbol must advertise dynamicRegistration"
5420 );
5421 assert_eq!(
5422 caps.workspace
5423 .as_ref()
5424 .and_then(|w| w.symbol.as_ref())
5425 .and_then(|s| s.dynamic_registration),
5426 Some(true),
5427 "workspace.symbol must advertise dynamicRegistration"
5428 );
5429 }
5430
5431 #[test]
5432 fn advertises_inlay_hint_and_semantic_tokens_refresh_support() {
5433 let caps = create_client_capabilities();
5437 let workspace = caps.workspace.as_ref().expect("workspace caps must be set");
5438
5439 assert_eq!(
5440 workspace
5441 .inlay_hint
5442 .as_ref()
5443 .and_then(|c| c.refresh_support),
5444 Some(true),
5445 "workspace.inlayHint.refreshSupport must be advertised"
5446 );
5447 assert_eq!(
5448 workspace
5449 .semantic_tokens
5450 .as_ref()
5451 .and_then(|c| c.refresh_support),
5452 Some(true),
5453 "workspace.semanticTokens.refreshSupport must be advertised"
5454 );
5455 }
5456
5457 #[test]
5458 fn sync_raw_capabilities_mirrors_dynamic_diagnostic_provider() {
5459 let caps: Arc<std::sync::Mutex<Option<ServerCapabilities>>> =
5464 Arc::new(std::sync::Mutex::new(Some(ServerCapabilities::default())));
5465 assert!(caps
5466 .lock()
5467 .unwrap()
5468 .as_ref()
5469 .unwrap()
5470 .diagnostic_provider
5471 .is_none());
5472
5473 sync_raw_capabilities(
5474 &caps,
5475 &[("textDocument/diagnostic".to_string(), None)],
5476 true,
5477 );
5478 assert!(
5479 caps.lock()
5480 .unwrap()
5481 .as_ref()
5482 .unwrap()
5483 .diagnostic_provider
5484 .is_some(),
5485 "dynamic diagnostic registration must set diagnostic_provider so pulls aren't skipped"
5486 );
5487
5488 sync_raw_capabilities(
5489 &caps,
5490 &[("textDocument/diagnostic".to_string(), None)],
5491 false,
5492 );
5493 assert!(
5494 caps.lock()
5495 .unwrap()
5496 .as_ref()
5497 .unwrap()
5498 .diagnostic_provider
5499 .is_none(),
5500 "unregister must clear diagnostic_provider"
5501 );
5502 }
5503
5504 #[test]
5505 fn sync_raw_capabilities_ignores_non_diagnostic_methods() {
5506 let caps: Arc<std::sync::Mutex<Option<ServerCapabilities>>> =
5509 Arc::new(std::sync::Mutex::new(None));
5510 sync_raw_capabilities(&caps, &[("textDocument/hover".to_string(), None)], true);
5511 assert!(
5512 caps.lock().unwrap().is_none(),
5513 "a non-diagnostic registration must not materialize the raw snapshot"
5514 );
5515 }
5516
5517 #[test]
5518 fn parses_register_and_unregister_capability_params() {
5519 let register = serde_json::json!({
5520 "registrations": [
5521 { "id": "1", "method": "textDocument/inlayHint" },
5522 {
5523 "id": "2",
5524 "method": "textDocument/completion",
5525 "registerOptions": { "triggerCharacters": ["."] }
5526 }
5527 ]
5528 });
5529 let parsed = registrations_from_params(Some(®ister));
5530 assert_eq!(parsed.len(), 2);
5531 assert_eq!(parsed[0].0, "textDocument/inlayHint");
5532 assert!(parsed[0].1.is_none());
5533 assert_eq!(parsed[1].0, "textDocument/completion");
5534 assert!(parsed[1].1.is_some());
5535
5536 let unregister = serde_json::json!({
5537 "unregisterations": [
5538 { "id": "1", "method": "textDocument/inlayHint" }
5539 ]
5540 });
5541 let methods = unregistrations_from_params(Some(&unregister));
5542 assert_eq!(methods, vec!["textDocument/inlayHint".to_string()]);
5543
5544 assert!(registrations_from_params(Some(&serde_json::json!({ "bogus": 1 }))).is_empty());
5546 assert!(unregistrations_from_params(None).is_empty());
5547 }
5548
5549 #[test]
5550 fn test_json_rpc_error_response() {
5551 let response = JsonRpcResponse {
5552 jsonrpc: "2.0".to_string(),
5553 id: JsonRpcId::Number(1),
5554 result: None,
5555 error: Some(JsonRpcError {
5556 code: -32600,
5557 message: "Invalid request".to_string(),
5558 data: None,
5559 }),
5560 };
5561
5562 let json = serde_json::to_string(&response).unwrap();
5563 assert!(json.contains("\"error\""));
5564 assert!(json.contains("\"code\":-32600"));
5565 assert!(json.contains("\"message\":\"Invalid request\""));
5566 }
5567
5568 #[test]
5569 fn test_suppressed_error_codes() {
5570 assert!(is_suppressed_error_code(LSP_ERROR_CONTENT_MODIFIED));
5572 assert!(is_suppressed_error_code(LSP_ERROR_SERVER_CANCELLED));
5573
5574 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));
5584
5585 assert!(!is_suppressed_error_code(LSP_ERROR_REQUEST_FAILED));
5588 }
5589
5590 #[test]
5591 fn test_request_failed_suppressed_only_for_informational_methods() {
5592 assert!(is_suppressed_response_error(
5597 LSP_ERROR_REQUEST_FAILED,
5598 "textDocument/hover"
5599 ));
5600 assert!(is_suppressed_response_error(
5601 LSP_ERROR_REQUEST_FAILED,
5602 "textDocument/completion"
5603 ));
5604
5605 assert!(!is_suppressed_response_error(
5608 LSP_ERROR_REQUEST_FAILED,
5609 "textDocument/formatting"
5610 ));
5611 assert!(!is_suppressed_response_error(
5612 LSP_ERROR_REQUEST_FAILED,
5613 "textDocument/rename"
5614 ));
5615
5616 assert!(!is_suppressed_response_error(-32603, "textDocument/hover"));
5619 }
5620
5621 #[test]
5622 fn test_request_failed_on_hover_is_not_logged_as_warn() {
5623 let (emitted, contents) = capture_warn_logs(|| {
5626 log_response_error(
5627 LSP_ERROR_REQUEST_FAILED,
5628 "No information available",
5629 "asm-lsp",
5630 "asm",
5631 "textDocument/hover",
5632 );
5633 });
5634 assert!(
5635 !emitted,
5636 "hover RequestFailed must not notify the WARN channel; got log:\n{}",
5637 contents
5638 );
5639 }
5640
5641 #[test]
5642 fn test_request_failed_on_formatting_still_warns() {
5643 let (emitted, _contents) = capture_warn_logs(|| {
5644 log_response_error(
5645 LSP_ERROR_REQUEST_FAILED,
5646 "formatting failed",
5647 "some-server",
5648 "rust",
5649 "textDocument/formatting",
5650 );
5651 });
5652 assert!(
5653 emitted,
5654 "RequestFailed on an actionable method should still WARN"
5655 );
5656 }
5657
5658 fn capture_warn_logs(body: impl FnOnce()) -> (bool, String) {
5662 use std::time::Duration;
5663 use tempfile::NamedTempFile;
5664 use tracing_subscriber::prelude::*;
5665
5666 let log_file = NamedTempFile::new().unwrap();
5667 let log_path = log_file.into_temp_path();
5668 let (layer, handle) =
5669 crate::services::warning_log::create_with_path(log_path.to_path_buf()).unwrap();
5670 let subscriber = tracing_subscriber::registry().with(layer);
5671
5672 tracing::subscriber::with_default(subscriber, body);
5673
5674 let emitted = handle
5675 .receiver
5676 .recv_timeout(Duration::from_millis(100))
5677 .is_ok();
5678 let contents = std::fs::read_to_string(&log_path).unwrap_or_default();
5679 (emitted, contents)
5680 }
5681
5682 #[test]
5683 fn test_content_modified_and_server_cancelled_are_not_logged_as_warn() {
5684 for code in [LSP_ERROR_CONTENT_MODIFIED, LSP_ERROR_SERVER_CANCELLED] {
5685 let (emitted, contents) = capture_warn_logs(|| {
5686 log_response_error(
5687 code,
5688 "expected during editing",
5689 "rust-analyzer",
5690 "rust",
5691 "textDocument/completion",
5692 );
5693 });
5694 assert!(
5695 !emitted,
5696 "code {} must not notify the WARN channel; got log:\n{}",
5697 code, contents
5698 );
5699 }
5700 }
5701
5702 #[test]
5703 fn test_method_not_found_still_surfaces_as_warn() {
5704 let (emitted, contents) = capture_warn_logs(|| {
5708 log_response_error(
5709 -32601,
5710 "Unhandled method textDocument/inlayHint",
5711 "vscode-json-language-server",
5712 "json",
5713 "textDocument/inlayHint",
5714 );
5715 });
5716 assert!(
5717 emitted,
5718 "MethodNotFound should notify the WARN channel so the mismatch is visible"
5719 );
5720 assert!(
5721 contents.contains("code -32601"),
5722 "WARN log should record the error code; got:\n{}",
5723 contents
5724 );
5725 }
5726
5727 #[test]
5728 fn test_non_suppressed_errors_still_warn() {
5729 let (emitted, contents) = capture_warn_logs(|| {
5732 log_response_error(
5733 -32603,
5734 "internal error",
5735 "rust-analyzer",
5736 "rust",
5737 "textDocument/hover",
5738 );
5739 });
5740 assert!(
5741 emitted,
5742 "non-suppressed error codes should notify the WARN channel"
5743 );
5744 assert!(
5745 contents.contains("code -32603"),
5746 "WARN log should record the error code; got:\n{}",
5747 contents
5748 );
5749 assert!(
5750 contents.contains("rust-analyzer"),
5751 "WARN log should record the server name; got:\n{}",
5752 contents
5753 );
5754 }
5755
5756 #[test]
5757 fn test_json_rpc_notification_serialization() {
5758 let notification = JsonRpcNotification {
5759 jsonrpc: "2.0".to_string(),
5760 method: "textDocument/didOpen".to_string(),
5761 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
5762 };
5763
5764 let json = serde_json::to_string(¬ification).unwrap();
5765 assert!(json.contains("\"jsonrpc\":\"2.0\""));
5766 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
5767 assert!(json.contains("\"uri\":\"file:///test.rs\""));
5768 assert!(!json.contains("\"id\"")); }
5770
5771 #[test]
5772 fn test_json_rpc_message_deserialization_request() {
5773 let json =
5774 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
5775 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5776
5777 match message {
5778 JsonRpcMessage::Request(request) => {
5779 assert_eq!(request.jsonrpc, "2.0");
5780 assert_eq!(request.id, JsonRpcId::Number(1));
5781 assert_eq!(request.method, "initialize");
5782 assert!(request.params.is_some());
5783 }
5784 _ => panic!("Expected Request"),
5785 }
5786 }
5787
5788 #[test]
5799 fn string_id_request_deserializes_as_request_not_notification() {
5800 let json = r#"{"jsonrpc":"2.0","id":"dyn-reg-1","method":"client/registerCapability","params":{"registrations":[{"id":"completion-reg","method":"textDocument/completion"}]}}"#;
5801 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5802
5803 match message {
5804 JsonRpcMessage::Request(request) => {
5805 assert_eq!(request.id, JsonRpcId::Str("dyn-reg-1".to_string()));
5806 assert_eq!(request.method, "client/registerCapability");
5807 }
5808 other => panic!("expected Request for a string-id message, got {other:?}"),
5809 }
5810 }
5811
5812 #[test]
5815 fn null_response_preserves_string_id() {
5816 let json = serde_json::to_string(&null_response(JsonRpcId::Str("dyn-reg-1".to_string())))
5817 .expect("serialize null response");
5818 assert!(
5819 json.contains(r#""id":"dyn-reg-1""#),
5820 "string id must be echoed verbatim, got: {json}"
5821 );
5822 }
5823
5824 #[test]
5825 fn test_json_rpc_message_deserialization_response() {
5826 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
5827 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5828
5829 match message {
5830 JsonRpcMessage::Response(response) => {
5831 assert_eq!(response.jsonrpc, "2.0");
5832 assert_eq!(response.id, JsonRpcId::Number(1));
5833 assert!(response.result.is_some());
5834 assert!(response.error.is_none());
5835 }
5836 _ => panic!("Expected Response"),
5837 }
5838 }
5839
5840 #[test]
5841 fn test_json_rpc_message_deserialization_notification() {
5842 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
5843 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5844
5845 match message {
5846 JsonRpcMessage::Notification(notification) => {
5847 assert_eq!(notification.jsonrpc, "2.0");
5848 assert_eq!(notification.method, "textDocument/didOpen");
5849 assert!(notification.params.is_some());
5850 }
5851 _ => panic!("Expected Notification"),
5852 }
5853 }
5854
5855 #[test]
5856 fn test_json_rpc_error_deserialization() {
5857 let json =
5858 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
5859 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
5860
5861 match message {
5862 JsonRpcMessage::Response(response) => {
5863 assert_eq!(response.jsonrpc, "2.0");
5864 assert_eq!(response.id, JsonRpcId::Number(1));
5865 assert!(response.result.is_none());
5866 assert!(response.error.is_some());
5867 let error = response.error.unwrap();
5868 assert_eq!(error.code, -32600);
5869 assert_eq!(error.message, "Invalid request");
5870 }
5871 _ => panic!("Expected Response with error"),
5872 }
5873 }
5874
5875 #[tokio::test]
5876 async fn test_lsp_handle_spawn_and_drop() {
5877 let runtime = tokio::runtime::Handle::current();
5880 let async_bridge = AsyncBridge::new();
5881
5882 let result = LspHandle::spawn(
5885 &runtime,
5886 "cat",
5887 &[],
5888 Default::default(),
5889 LanguageScope::single("test"),
5890 "test-server".to_string(),
5891 &async_bridge,
5892 ProcessLimits::unlimited(),
5893 Default::default(),
5894 local_spawner(),
5895 );
5896
5897 assert!(result.is_ok());
5899
5900 let handle = result.unwrap();
5901
5902 drop(handle);
5904
5905 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
5907 }
5908
5909 #[tokio::test]
5910 async fn test_lsp_handle_did_open_queues_before_initialization() {
5911 let runtime = tokio::runtime::Handle::current();
5912 let async_bridge = AsyncBridge::new();
5913
5914 let handle = LspHandle::spawn(
5915 &runtime,
5916 "cat",
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 .unwrap();
5927
5928 let result = handle.did_open(
5930 "file:///test.txt".parse().unwrap(),
5931 "fn main() {}".to_string(),
5932 "test".to_string(),
5933 );
5934
5935 assert!(result.is_ok());
5937 }
5938
5939 #[tokio::test]
5940 async fn test_lsp_handle_did_change_queues_before_initialization() {
5941 let runtime = tokio::runtime::Handle::current();
5942 let async_bridge = AsyncBridge::new();
5943
5944 let handle = LspHandle::spawn(
5945 &runtime,
5946 "cat",
5947 &[],
5948 Default::default(),
5949 LanguageScope::single("test"),
5950 "test-server".to_string(),
5951 &async_bridge,
5952 ProcessLimits::unlimited(),
5953 Default::default(),
5954 local_spawner(),
5955 )
5956 .unwrap();
5957
5958 let result = handle.did_change(
5960 "file:///test.rs".parse().unwrap(),
5961 vec![TextDocumentContentChangeEvent {
5962 range: Some(lsp_types::Range::new(
5963 lsp_types::Position::new(0, 0),
5964 lsp_types::Position::new(0, 0),
5965 )),
5966 range_length: None,
5967 text: "fn main() {}".to_string(),
5968 }],
5969 );
5970
5971 assert!(result.is_ok());
5973 }
5974
5975 #[tokio::test]
5976 async fn test_lsp_handle_incremental_change_with_range() {
5977 let runtime = tokio::runtime::Handle::current();
5978 let async_bridge = AsyncBridge::new();
5979
5980 let handle = LspHandle::spawn(
5981 &runtime,
5982 "cat",
5983 &[],
5984 Default::default(),
5985 LanguageScope::single("test"),
5986 "test-server".to_string(),
5987 &async_bridge,
5988 ProcessLimits::unlimited(),
5989 Default::default(),
5990 local_spawner(),
5991 )
5992 .unwrap();
5993
5994 let result = handle.did_change(
5996 "file:///test.rs".parse().unwrap(),
5997 vec![TextDocumentContentChangeEvent {
5998 range: Some(lsp_types::Range::new(
5999 lsp_types::Position::new(0, 3),
6000 lsp_types::Position::new(0, 7),
6001 )),
6002 range_length: None,
6003 text: String::new(), }],
6005 );
6006
6007 assert!(result.is_ok());
6009 }
6010
6011 #[tokio::test]
6012 async fn test_lsp_handle_spawn_invalid_command() {
6013 let runtime = tokio::runtime::Handle::current();
6014 let async_bridge = AsyncBridge::new();
6015
6016 let result = LspHandle::spawn(
6018 &runtime,
6019 "this-command-does-not-exist-12345",
6020 &[],
6021 Default::default(),
6022 LanguageScope::single("test"),
6023 "test-server".to_string(),
6024 &async_bridge,
6025 ProcessLimits::unlimited(),
6026 Default::default(),
6027 local_spawner(),
6028 );
6029
6030 assert!(result.is_ok());
6033
6034 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
6036
6037 let messages = async_bridge.try_recv_all();
6039 assert!(!messages.is_empty());
6040
6041 let has_error = messages
6042 .iter()
6043 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
6044 assert!(has_error, "Expected LspError message");
6045 }
6046
6047 #[test]
6048 fn test_lsp_handle_shutdown_from_sync_context() {
6049 std::thread::spawn(|| {
6052 let rt = tokio::runtime::Runtime::new().unwrap();
6054 let async_bridge = AsyncBridge::new();
6055
6056 let handle = rt.block_on(async {
6057 let runtime = tokio::runtime::Handle::current();
6058 LspHandle::spawn(
6059 &runtime,
6060 "cat",
6061 &[],
6062 Default::default(),
6063 LanguageScope::single("test"),
6064 "test-server".to_string(),
6065 &async_bridge,
6066 ProcessLimits::unlimited(),
6067 Default::default(),
6068 local_spawner(),
6069 )
6070 .unwrap()
6071 });
6072
6073 assert!(handle.shutdown().is_ok());
6075
6076 std::thread::sleep(std::time::Duration::from_millis(50));
6078 })
6079 .join()
6080 .unwrap();
6081 }
6082
6083 #[test]
6084 fn test_lsp_command_debug_format() {
6085 let cmd = LspCommand::Shutdown;
6087 let debug_str = format!("{:?}", cmd);
6088 assert!(debug_str.contains("Shutdown"));
6089 }
6090
6091 #[test]
6092 fn test_lsp_client_state_can_initialize_from_starting() {
6093 let state = LspClientState::Starting;
6099
6100 assert!(
6102 state.can_initialize(),
6103 "Starting state must allow initialization to avoid race condition"
6104 );
6105
6106 let mut state = LspClientState::Starting;
6108
6109 assert!(state.can_transition_to(LspClientState::Initializing));
6111 assert!(state.transition_to(LspClientState::Initializing).is_ok());
6112
6113 assert!(state.can_transition_to(LspClientState::Running));
6115 assert!(state.transition_to(LspClientState::Running).is_ok());
6116 }
6117
6118 #[tokio::test]
6119 async fn test_lsp_handle_initialize_from_starting_state() {
6120 let runtime = tokio::runtime::Handle::current();
6128 let async_bridge = AsyncBridge::new();
6129
6130 let handle = LspHandle::spawn(
6132 &runtime,
6133 "cat", &[],
6135 Default::default(),
6136 LanguageScope::single("test"),
6137 "test-server".to_string(),
6138 &async_bridge,
6139 ProcessLimits::unlimited(),
6140 Default::default(),
6141 local_spawner(),
6142 )
6143 .unwrap();
6144
6145 let result = handle.initialize(None, None);
6148
6149 assert!(
6150 result.is_ok(),
6151 "initialize() must succeed from Starting state. Got error: {:?}",
6152 result.err()
6153 );
6154 }
6155
6156 #[tokio::test]
6157 async fn test_lsp_state_machine_race_condition_fix() {
6158 let runtime = tokio::runtime::Handle::current();
6165 let async_bridge = AsyncBridge::new();
6166
6167 let fake_lsp_script = r#"
6169 read -r line # Read Content-Length header
6170 read -r empty # Read empty line
6171 read -r json # Read JSON body
6172
6173 # Send a valid initialize response
6174 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
6175 echo "Content-Length: ${#response}"
6176 echo ""
6177 echo -n "$response"
6178
6179 # Keep running to avoid EOF
6180 sleep 10
6181 "#;
6182
6183 let handle = LspHandle::spawn(
6185 &runtime,
6186 "bash",
6187 &["-c".to_string(), fake_lsp_script.to_string()],
6188 Default::default(),
6189 LanguageScope::single("fake"),
6190 "test-server".to_string(),
6191 &async_bridge,
6192 ProcessLimits::unlimited(),
6193 Default::default(),
6194 local_spawner(),
6195 )
6196 .unwrap();
6197
6198 let init_result = handle.initialize(None, None);
6200 assert!(
6201 init_result.is_ok(),
6202 "initialize() failed from Starting state: {:?}",
6203 init_result.err()
6204 );
6205
6206 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
6208
6209 let messages = async_bridge.try_recv_all();
6211 let has_status_update = messages
6212 .iter()
6213 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
6214
6215 assert!(
6216 has_status_update,
6217 "Expected status update messages from LSP initialization"
6218 );
6219
6220 #[allow(clippy::let_underscore_must_use)]
6222 let _ = handle.shutdown();
6223 }
6224
6225 #[test]
6226 fn test_lsp_client_state_can_shutdown_from_error() {
6227 let mut state = LspClientState::Error;
6234
6235 assert!(
6236 state.can_transition_to(LspClientState::Stopping),
6237 "Error state must allow transition to Stopping for graceful shutdown"
6238 );
6239 assert!(state.transition_to(LspClientState::Stopping).is_ok());
6240 assert!(state.transition_to(LspClientState::Stopped).is_ok());
6243 }
6244
6245 #[tokio::test]
6246 async fn test_lsp_handle_shutdown_after_spawn_failure_advances_state() {
6247 let runtime = tokio::runtime::Handle::current();
6253 let async_bridge = AsyncBridge::new();
6254
6255 let handle = LspHandle::spawn(
6256 &runtime,
6257 "fresh-nonexistent-lsp-binary-7c93af",
6258 &[],
6259 Default::default(),
6260 LanguageScope::single("test"),
6261 "test-server".to_string(),
6262 &async_bridge,
6263 ProcessLimits::unlimited(),
6264 Default::default(),
6265 local_spawner(),
6266 )
6267 .unwrap();
6268
6269 for _ in 0..200 {
6272 if handle.state() == LspClientState::Error {
6273 break;
6274 }
6275 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
6276 }
6277 assert_eq!(
6278 handle.state(),
6279 LspClientState::Error,
6280 "spawn task should have transitioned to Error after failed spawn"
6281 );
6282
6283 #[allow(clippy::let_underscore_must_use)]
6288 let _ = handle.shutdown();
6289 let final_state = handle.state();
6290 assert!(
6291 matches!(
6292 final_state,
6293 LspClientState::Stopping | LspClientState::Stopped
6294 ),
6295 "shutdown from Error must advance state, got {:?}",
6296 final_state
6297 );
6298 }
6299}