1use crate::services::async_bridge::{
15 AsyncBridge, AsyncMessage, LspMessageType, LspProgressValue, LspSemanticTokensResponse,
16 LspServerStatus,
17};
18use crate::services::process_limits::ProcessLimits;
19use lsp_types::{
20 notification::{
21 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument,
22 Initialized, Notification, PublishDiagnostics,
23 },
24 request::{Initialize, Request},
25 ClientCapabilities, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
26 DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult,
27 InitializedParams, PartialResultParams, Position, PublishDiagnosticsParams, Range,
28 SemanticTokenModifier, SemanticTokenType, SemanticTokensClientCapabilities,
29 SemanticTokensClientCapabilitiesRequests, SemanticTokensFullOptions, SemanticTokensParams,
30 SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities,
31 TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
32 TextDocumentPositionParams, TokenFormat, Uri, VersionedTextDocumentIdentifier,
33 WindowClientCapabilities, WorkDoneProgressParams, WorkspaceFolder,
34};
35use serde::{Deserialize, Serialize};
36use serde_json::Value;
37use std::collections::HashMap;
38use std::path::PathBuf;
39use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
40use std::sync::{mpsc as std_mpsc, Arc, Mutex};
41use std::time::{Duration, Instant};
42use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
43use tokio::process::{ChildStdin, ChildStdout};
44use tokio::sync::{mpsc, oneshot};
45
46type PendingRequests = Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>;
47
48const DID_OPEN_GRACE_PERIOD_MS: u64 = 200;
51
52const DEFAULT_REQUEST_TIMEOUT_MS: u64 = 30_000;
58
59const LSP_ERROR_CONTENT_MODIFIED: i64 = -32801;
74const LSP_ERROR_SERVER_CANCELLED: i64 = -32802;
75
76fn is_suppressed_error_code(code: i64) -> bool {
79 code == LSP_ERROR_CONTENT_MODIFIED || code == LSP_ERROR_SERVER_CANCELLED
80}
81
82fn log_response_error(code: i64, message: &str, server_name: &str, language: &str) {
87 if is_suppressed_error_code(code) {
88 tracing::debug!(
89 "LSP response from '{}' ({}): {} (code {}), discarding",
90 server_name,
91 language,
92 message,
93 code
94 );
95 } else {
96 tracing::warn!(
97 "LSP response error from '{}' ({}): {} (code {})",
98 server_name,
99 language,
100 message,
101 code
102 );
103 }
104}
105
106fn should_skip_did_open(
109 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
110 path: &PathBuf,
111 language: &str,
112 uri: &Uri,
113) -> bool {
114 if document_versions.lock().unwrap().contains_key(path) {
115 tracing::debug!(
116 "LSP ({}): skipping didOpen - document already open: {}",
117 language,
118 uri.as_str()
119 );
120 true
121 } else {
122 false
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(untagged)]
129pub enum JsonRpcMessage {
130 Request(JsonRpcRequest),
131 Response(JsonRpcResponse),
132 Notification(JsonRpcNotification),
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct JsonRpcRequest {
138 pub jsonrpc: String,
139 pub id: i64,
140 pub method: String,
141 pub params: Option<Value>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct JsonRpcResponse {
147 pub jsonrpc: String,
148 pub id: i64,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub result: Option<Value>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub error: Option<JsonRpcError>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct JsonRpcNotification {
158 pub jsonrpc: String,
159 pub method: String,
160 pub params: Option<Value>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct JsonRpcError {
166 pub code: i64,
167 pub message: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub data: Option<Value>,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum LspClientState {
178 Initial,
180 Starting,
182 Initializing,
184 Running,
186 Stopping,
188 Stopped,
190 Error,
192}
193
194impl LspClientState {
195 pub fn can_transition_to(&self, next: LspClientState) -> bool {
197 use LspClientState::*;
198 match (self, next) {
199 (Initial, Starting) => true,
201 (Starting, Initializing) | (Starting, Error) => true,
203 (Initializing, Running) | (Initializing, Error) => true,
205 (Running, Stopping) | (Running, Error) => true,
207 (Stopping, Stopped) | (Stopping, Error) => true,
209 (Stopped, Starting) => true,
211 (Error, Stopping) | (Error, Starting) => true,
215 (_, Error) => true,
217 (a, b) if *a == b => true,
219 _ => false,
221 }
222 }
223
224 pub fn transition_to(&mut self, next: LspClientState) -> Result<(), String> {
226 if self.can_transition_to(next) {
227 *self = next;
228 Ok(())
229 } else {
230 Err(format!(
231 "Invalid state transition from {:?} to {:?}",
232 self, next
233 ))
234 }
235 }
236
237 pub fn can_send_requests(&self) -> bool {
239 matches!(self, Self::Running)
240 }
241
242 pub fn can_initialize(&self) -> bool {
244 matches!(self, Self::Initial | Self::Starting | Self::Stopped)
245 }
246
247 pub fn to_server_status(&self) -> LspServerStatus {
249 match self {
250 Self::Initial => LspServerStatus::Starting,
251 Self::Starting => LspServerStatus::Starting,
252 Self::Initializing => LspServerStatus::Initializing,
253 Self::Running => LspServerStatus::Running,
254 Self::Stopping => LspServerStatus::Shutdown,
255 Self::Stopped => LspServerStatus::Shutdown,
256 Self::Error => LspServerStatus::Error,
257 }
258 }
259}
260
261fn create_client_capabilities() -> ClientCapabilities {
263 use lsp_types::{
264 CodeActionClientCapabilities, CompletionClientCapabilities, DiagnosticClientCapabilities,
265 DiagnosticTag, DynamicRegistrationClientCapabilities, FoldingRangeCapability,
266 FoldingRangeClientCapabilities, FoldingRangeKind, FoldingRangeKindCapability,
267 GeneralClientCapabilities, GotoCapability, HoverClientCapabilities,
268 InlayHintClientCapabilities, MarkupKind, PublishDiagnosticsClientCapabilities,
269 RenameClientCapabilities, SignatureHelpClientCapabilities, TagSupport,
270 TextDocumentClientCapabilities, TextDocumentSyncClientCapabilities,
271 WorkspaceClientCapabilities, WorkspaceEditClientCapabilities,
272 };
273
274 ClientCapabilities {
275 window: Some(WindowClientCapabilities {
276 work_done_progress: Some(true),
277 ..Default::default()
278 }),
279 workspace: Some(WorkspaceClientCapabilities {
280 apply_edit: Some(true),
281 workspace_edit: Some(WorkspaceEditClientCapabilities {
282 document_changes: Some(true),
283 ..Default::default()
284 }),
285 workspace_folders: Some(true),
286 ..Default::default()
287 }),
288 text_document: Some(TextDocumentClientCapabilities {
289 synchronization: Some(TextDocumentSyncClientCapabilities {
290 did_save: Some(true),
291 ..Default::default()
292 }),
293 completion: Some(CompletionClientCapabilities {
294 ..Default::default()
295 }),
296 hover: Some(HoverClientCapabilities {
297 content_format: Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]),
298 ..Default::default()
299 }),
300 signature_help: Some(SignatureHelpClientCapabilities {
301 ..Default::default()
302 }),
303 definition: Some(GotoCapability {
304 link_support: Some(true),
305 ..Default::default()
306 }),
307 references: Some(DynamicRegistrationClientCapabilities::default()),
308 code_action: Some(CodeActionClientCapabilities {
309 ..Default::default()
310 }),
311 rename: Some(RenameClientCapabilities {
312 dynamic_registration: Some(true),
313 prepare_support: Some(true),
314 honors_change_annotations: Some(true),
315 ..Default::default()
316 }),
317 publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
318 related_information: Some(true),
319 tag_support: Some(TagSupport {
320 value_set: vec![DiagnosticTag::UNNECESSARY, DiagnosticTag::DEPRECATED],
321 }),
322 version_support: Some(true),
323 code_description_support: Some(true),
324 data_support: Some(true),
325 }),
326 inlay_hint: Some(InlayHintClientCapabilities {
327 ..Default::default()
328 }),
329 diagnostic: Some(DiagnosticClientCapabilities {
330 ..Default::default()
331 }),
332 folding_range: Some(FoldingRangeClientCapabilities {
333 dynamic_registration: Some(true),
334 line_folding_only: Some(true),
335 folding_range_kind: Some(FoldingRangeKindCapability {
336 value_set: Some(vec![
337 FoldingRangeKind::Comment,
338 FoldingRangeKind::Imports,
339 FoldingRangeKind::Region,
340 ]),
341 }),
342 folding_range: Some(FoldingRangeCapability {
343 collapsed_text: Some(true),
344 }),
345 ..Default::default()
346 }),
347 semantic_tokens: Some(SemanticTokensClientCapabilities {
348 dynamic_registration: Some(true),
349 requests: SemanticTokensClientCapabilitiesRequests {
350 range: Some(true),
351 full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
352 },
353 token_types: vec![
354 SemanticTokenType::NAMESPACE,
355 SemanticTokenType::TYPE,
356 SemanticTokenType::CLASS,
357 SemanticTokenType::ENUM,
358 SemanticTokenType::INTERFACE,
359 SemanticTokenType::STRUCT,
360 SemanticTokenType::TYPE_PARAMETER,
361 SemanticTokenType::PARAMETER,
362 SemanticTokenType::VARIABLE,
363 SemanticTokenType::PROPERTY,
364 SemanticTokenType::ENUM_MEMBER,
365 SemanticTokenType::EVENT,
366 SemanticTokenType::FUNCTION,
367 SemanticTokenType::METHOD,
368 SemanticTokenType::MACRO,
369 SemanticTokenType::KEYWORD,
370 SemanticTokenType::MODIFIER,
371 SemanticTokenType::COMMENT,
372 SemanticTokenType::STRING,
373 SemanticTokenType::NUMBER,
374 SemanticTokenType::REGEXP,
375 SemanticTokenType::OPERATOR,
376 SemanticTokenType::DECORATOR,
377 ],
378 token_modifiers: vec![
379 SemanticTokenModifier::DECLARATION,
380 SemanticTokenModifier::DEFINITION,
381 SemanticTokenModifier::READONLY,
382 SemanticTokenModifier::STATIC,
383 SemanticTokenModifier::DEPRECATED,
384 SemanticTokenModifier::ABSTRACT,
385 SemanticTokenModifier::ASYNC,
386 SemanticTokenModifier::MODIFICATION,
387 SemanticTokenModifier::DOCUMENTATION,
388 SemanticTokenModifier::DEFAULT_LIBRARY,
389 ],
390 formats: vec![TokenFormat::RELATIVE],
391 overlapping_token_support: Some(true),
392 multiline_token_support: Some(true),
393 server_cancel_support: Some(true),
394 augments_syntax_tokens: Some(true),
395 }),
396 ..Default::default()
397 }),
398 general: Some(GeneralClientCapabilities {
399 ..Default::default()
400 }),
401 experimental: Some(serde_json::json!({
403 "serverStatusNotification": true
404 })),
405 ..Default::default()
406 }
407}
408
409use crate::services::lsp::manager::ServerCapabilitySummary;
410
411fn extract_capability_summary(caps: &ServerCapabilities) -> ServerCapabilitySummary {
417 let (sem_legend, sem_full, sem_full_delta, sem_range) = caps
418 .semantic_tokens_provider
419 .as_ref()
420 .map(|provider| {
421 let (legend, full_opt) = match provider {
422 SemanticTokensServerCapabilities::SemanticTokensOptions(o) => {
423 (o.legend.clone(), &o.full)
424 }
425 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(o) => (
426 o.semantic_tokens_options.legend.clone(),
427 &o.semantic_tokens_options.full,
428 ),
429 };
430 let range = match provider {
431 SemanticTokensServerCapabilities::SemanticTokensOptions(o) => {
432 o.range.unwrap_or(false)
433 }
434 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(o) => {
435 o.semantic_tokens_options.range.unwrap_or(false)
436 }
437 };
438 let full = match full_opt {
439 Some(SemanticTokensFullOptions::Bool(v)) => *v,
440 Some(SemanticTokensFullOptions::Delta { .. }) => true,
441 None => false,
442 };
443 let delta = match full_opt {
444 Some(SemanticTokensFullOptions::Delta { delta }) => delta.unwrap_or(false),
445 _ => false,
446 };
447 (Some(legend), full, delta, range)
448 })
449 .unwrap_or((None, false, false, false));
450
451 ServerCapabilitySummary {
452 initialized: false, hover: bool_or_options(&caps.hover_provider, |p| match p {
454 lsp_types::HoverProviderCapability::Simple(v) => *v,
455 lsp_types::HoverProviderCapability::Options(_) => true,
456 }),
457 completion: caps.completion_provider.is_some(),
458 completion_resolve: caps
459 .completion_provider
460 .as_ref()
461 .and_then(|cp| cp.resolve_provider)
462 .unwrap_or(false),
463 completion_trigger_characters: caps
464 .completion_provider
465 .as_ref()
466 .and_then(|cp| cp.trigger_characters.clone())
467 .unwrap_or_default(),
468 definition: bool_or_options(&caps.definition_provider, |p| match p {
469 lsp_types::OneOf::Left(v) => *v,
470 lsp_types::OneOf::Right(_) => true,
471 }),
472 references: bool_or_options(&caps.references_provider, |p| match p {
473 lsp_types::OneOf::Left(v) => *v,
474 lsp_types::OneOf::Right(_) => true,
475 }),
476 document_formatting: bool_or_options(&caps.document_formatting_provider, |p| match p {
477 lsp_types::OneOf::Left(v) => *v,
478 lsp_types::OneOf::Right(_) => true,
479 }),
480 document_range_formatting: bool_or_options(&caps.document_range_formatting_provider, |p| {
481 match p {
482 lsp_types::OneOf::Left(v) => *v,
483 lsp_types::OneOf::Right(_) => true,
484 }
485 }),
486 rename: bool_or_options(&caps.rename_provider, |p| match p {
487 lsp_types::OneOf::Left(v) => *v,
488 lsp_types::OneOf::Right(_) => true,
489 }),
490 signature_help: caps.signature_help_provider.is_some(),
491 inlay_hints: bool_or_options(&caps.inlay_hint_provider, |p| match p {
492 lsp_types::OneOf::Left(v) => *v,
493 lsp_types::OneOf::Right(_) => true,
494 }),
495 folding_ranges: bool_or_options(&caps.folding_range_provider, |p| match p {
496 lsp_types::FoldingRangeProviderCapability::Simple(v) => *v,
497 _ => true,
498 }),
499 semantic_tokens_full: sem_full,
500 semantic_tokens_full_delta: sem_full_delta,
501 semantic_tokens_range: sem_range,
502 semantic_tokens_legend: sem_legend,
503 document_highlight: bool_or_options(&caps.document_highlight_provider, |p| match p {
504 lsp_types::OneOf::Left(v) => *v,
505 lsp_types::OneOf::Right(_) => true,
506 }),
507 code_action: bool_or_options(&caps.code_action_provider, |p| match p {
508 lsp_types::CodeActionProviderCapability::Simple(v) => *v,
509 lsp_types::CodeActionProviderCapability::Options(_) => true,
510 }),
511 code_action_resolve: caps.code_action_provider.as_ref().is_some_and(|p| match p {
512 lsp_types::CodeActionProviderCapability::Options(opts) => {
513 opts.resolve_provider.unwrap_or(false)
514 }
515 _ => false,
516 }),
517 document_symbols: bool_or_options(&caps.document_symbol_provider, |p| match p {
518 lsp_types::OneOf::Left(v) => *v,
519 lsp_types::OneOf::Right(_) => true,
520 }),
521 workspace_symbols: bool_or_options(&caps.workspace_symbol_provider, |p| match p {
522 lsp_types::OneOf::Left(v) => *v,
523 lsp_types::OneOf::Right(_) => true,
524 }),
525 diagnostics: caps.diagnostic_provider.is_some(),
526 }
527}
528
529fn bool_or_options<T>(opt: &Option<T>, check: impl FnOnce(&T) -> bool) -> bool {
531 opt.as_ref().is_some_and(check)
532}
533
534#[derive(Debug)]
536enum LspCommand {
537 Initialize {
539 root_uri: Option<Uri>,
540 initialization_options: Option<Value>,
541 response: oneshot::Sender<Result<InitializeResult, String>>,
542 },
543
544 DidOpen {
546 uri: Uri,
547 text: String,
548 language_id: String,
549 },
550
551 DidChange {
553 uri: Uri,
554 content_changes: Vec<TextDocumentContentChangeEvent>,
555 },
556
557 DidClose { uri: Uri },
559
560 DidSave { uri: Uri, text: Option<String> },
562
563 DidChangeWorkspaceFolders {
565 added: Vec<lsp_types::WorkspaceFolder>,
566 removed: Vec<lsp_types::WorkspaceFolder>,
567 },
568
569 Completion {
571 request_id: u64,
572 uri: Uri,
573 line: u32,
574 character: u32,
575 },
576
577 GotoDefinition {
579 request_id: u64,
580 uri: Uri,
581 line: u32,
582 character: u32,
583 },
584
585 Rename {
587 request_id: u64,
588 uri: Uri,
589 line: u32,
590 character: u32,
591 new_name: String,
592 },
593
594 Hover {
596 request_id: u64,
597 uri: Uri,
598 line: u32,
599 character: u32,
600 },
601
602 References {
604 request_id: u64,
605 uri: Uri,
606 line: u32,
607 character: u32,
608 },
609
610 SignatureHelp {
612 request_id: u64,
613 uri: Uri,
614 line: u32,
615 character: u32,
616 },
617
618 CodeActions {
620 request_id: u64,
621 uri: Uri,
622 start_line: u32,
623 start_char: u32,
624 end_line: u32,
625 end_char: u32,
626 diagnostics: Vec<lsp_types::Diagnostic>,
627 },
628
629 DocumentDiagnostic {
631 request_id: u64,
632 uri: Uri,
633 previous_result_id: Option<String>,
635 },
636
637 InlayHints {
639 request_id: u64,
640 uri: Uri,
641 start_line: u32,
643 start_char: u32,
644 end_line: u32,
645 end_char: u32,
646 },
647
648 FoldingRange { request_id: u64, uri: Uri },
650
651 SemanticTokensFull { request_id: u64, uri: Uri },
653
654 SemanticTokensFullDelta {
656 request_id: u64,
657 uri: Uri,
658 previous_result_id: String,
659 },
660
661 SemanticTokensRange {
663 request_id: u64,
664 uri: Uri,
665 range: lsp_types::Range,
666 },
667
668 ExecuteCommand {
670 command: String,
671 arguments: Option<Vec<Value>>,
672 },
673
674 CodeActionResolve {
676 request_id: u64,
677 action: Box<lsp_types::CodeAction>,
678 },
679
680 CompletionResolve {
682 request_id: u64,
683 item: Box<lsp_types::CompletionItem>,
684 },
685
686 DocumentFormatting {
688 request_id: u64,
689 uri: Uri,
690 tab_size: u32,
691 insert_spaces: bool,
692 },
693
694 DocumentRangeFormatting {
696 request_id: u64,
697 uri: Uri,
698 start_line: u32,
699 start_char: u32,
700 end_line: u32,
701 end_char: u32,
702 tab_size: u32,
703 insert_spaces: bool,
704 },
705
706 PrepareRename {
708 request_id: u64,
709 uri: Uri,
710 line: u32,
711 character: u32,
712 },
713
714 CancelRequest {
716 request_id: u64,
718 },
719
720 PluginRequest {
722 request_id: u64,
723 method: String,
724 params: Option<Value>,
725 },
726
727 Shutdown,
729}
730
731#[derive(Clone)]
738struct LspState {
739 stdin: Arc<tokio::sync::Mutex<ChildStdin>>,
741
742 next_id: Arc<AtomicI64>,
744
745 capabilities: Arc<std::sync::Mutex<Option<ServerCapabilities>>>,
747
748 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
750
751 pending_opens: Arc<std::sync::Mutex<HashMap<PathBuf, Instant>>>,
754
755 initialized: Arc<AtomicBool>,
757
758 async_tx: std_mpsc::Sender<AsyncMessage>,
760
761 language: Arc<String>,
763
764 server_name: Arc<String>,
766
767 active_requests: Arc<std::sync::Mutex<HashMap<u64, i64>>>,
770
771 language_id_overrides: Arc<HashMap<String, String>>,
773}
774
775#[allow(clippy::let_underscore_must_use)]
781impl LspState {
782 async fn replay_pending_commands(&self, commands: Vec<LspCommand>, pending: &PendingRequests) {
784 if commands.is_empty() {
785 return;
786 }
787 tracing::info!(
788 "Replaying {} pending commands after initialization",
789 commands.len()
790 );
791 for cmd in commands {
792 match cmd {
793 LspCommand::DidOpen {
794 uri,
795 text,
796 language_id,
797 } => {
798 tracing::info!("Replaying DidOpen for {}", uri.as_str());
799 let _ = self
800 .handle_did_open_sequential(uri, text, language_id, pending)
801 .await;
802 }
803 LspCommand::DidChange {
804 uri,
805 content_changes,
806 } => {
807 tracing::info!("Replaying DidChange for {}", uri.as_str());
808 let _ = self
809 .handle_did_change_sequential(uri, content_changes, pending)
810 .await;
811 }
812 LspCommand::DidClose { uri } => {
813 tracing::info!("Replaying DidClose for {}", uri.as_str());
814 let _ = self.handle_did_close(uri).await;
815 }
816 LspCommand::DidSave { uri, text } => {
817 tracing::info!("Replaying DidSave for {}", uri.as_str());
818 let _ = self.handle_did_save(uri, text).await;
819 }
820 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
821 tracing::info!(
822 "Replaying DidChangeWorkspaceFolders: +{} -{}",
823 added.len(),
824 removed.len()
825 );
826 let _ = self
827 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
828 lsp_types::DidChangeWorkspaceFoldersParams {
829 event: lsp_types::WorkspaceFoldersChangeEvent { added, removed },
830 },
831 )
832 .await;
833 }
834 LspCommand::SemanticTokensFull { request_id, uri } => {
835 tracing::info!("Replaying semantic tokens request for {}", uri.as_str());
836 let s = self.clone();
837 let p = pending.clone();
838 tokio::spawn(async move {
839 let _ = s.handle_semantic_tokens_full(request_id, uri, &p).await;
840 });
841 }
842 LspCommand::SemanticTokensFullDelta {
843 request_id,
844 uri,
845 previous_result_id,
846 } => {
847 tracing::info!(
848 "Replaying semantic tokens delta request for {}",
849 uri.as_str()
850 );
851 let s = self.clone();
852 let p = pending.clone();
853 tokio::spawn(async move {
854 let _ = s
855 .handle_semantic_tokens_full_delta(
856 request_id,
857 uri,
858 previous_result_id,
859 &p,
860 )
861 .await;
862 });
863 }
864 LspCommand::SemanticTokensRange {
865 request_id,
866 uri,
867 range,
868 } => {
869 tracing::info!(
870 "Replaying semantic tokens range request for {}",
871 uri.as_str()
872 );
873 let s = self.clone();
874 let p = pending.clone();
875 tokio::spawn(async move {
876 let _ = s
877 .handle_semantic_tokens_range(request_id, uri, range, &p)
878 .await;
879 });
880 }
881 LspCommand::FoldingRange { request_id, uri } => {
882 tracing::info!("Replaying folding range request for {}", uri.as_str());
883 let s = self.clone();
884 let p = pending.clone();
885 tokio::spawn(async move {
886 let _ = s.handle_folding_ranges(request_id, uri, &p).await;
887 });
888 }
889 _ => {}
890 }
891 }
892 }
893
894 async fn write_message<T: Serialize>(&self, message: &T) -> Result<(), String> {
896 let json =
897 serde_json::to_string(message).map_err(|e| format!("Serialization error: {}", e))?;
898
899 let content = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
900
901 tracing::trace!("Writing LSP message to stdin ({} bytes)", content.len());
902
903 let mut stdin = self.stdin.lock().await;
904 stdin
905 .write_all(content.as_bytes())
906 .await
907 .map_err(|e| format!("Failed to write to stdin: {}", e))?;
908
909 stdin
910 .flush()
911 .await
912 .map_err(|e| format!("Failed to flush stdin: {}", e))?;
913
914 tracing::trace!("Successfully sent LSP message");
915
916 Ok(())
917 }
918
919 async fn send_notification<N>(&self, params: N::Params) -> Result<(), String>
921 where
922 N: Notification,
923 {
924 let notification = JsonRpcNotification {
925 jsonrpc: "2.0".to_string(),
926 method: N::METHOD.to_string(),
927 params: Some(
928 serde_json::to_value(params)
929 .map_err(|e| format!("Failed to serialize params: {}", e))?,
930 ),
931 };
932
933 self.write_message(¬ification).await
934 }
935
936 async fn send_request_sequential<P: Serialize, R: for<'de> Deserialize<'de>>(
938 &self,
939 method: &str,
940 params: Option<P>,
941 pending: &PendingRequests,
942 ) -> Result<R, String> {
943 self.send_request_with_timeout(
944 method,
945 params,
946 pending,
947 None,
948 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
949 )
950 .await
951 }
952
953 async fn send_request_sequential_tracked<P: Serialize, R: for<'de> Deserialize<'de>>(
955 &self,
956 method: &str,
957 params: Option<P>,
958 pending: &PendingRequests,
959 editor_request_id: Option<u64>,
960 ) -> Result<R, String> {
961 self.send_request_with_timeout(
962 method,
963 params,
964 pending,
965 editor_request_id,
966 Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MS),
967 )
968 .await
969 }
970
971 async fn send_request_with_timeout<P: Serialize, R: for<'de> Deserialize<'de>>(
977 &self,
978 method: &str,
979 params: Option<P>,
980 pending: &PendingRequests,
981 editor_request_id: Option<u64>,
982 timeout: Duration,
983 ) -> Result<R, String> {
984 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
985
986 if let Some(editor_id) = editor_request_id {
988 self.active_requests.lock().unwrap().insert(editor_id, id);
989 tracing::trace!("Tracking request: editor_id={}, lsp_id={}", editor_id, id);
990 }
991
992 let params_value = params
993 .map(|p| serde_json::to_value(p))
994 .transpose()
995 .map_err(|e| format!("Failed to serialize params: {}", e))?;
996 let request = JsonRpcRequest {
997 jsonrpc: "2.0".to_string(),
998 id,
999 method: method.to_string(),
1000 params: params_value,
1001 };
1002
1003 let (tx, rx) = oneshot::channel();
1004 pending.lock().unwrap().insert(id, tx);
1005
1006 if let Err(e) = self.write_message(&request).await {
1007 pending.lock().unwrap().remove(&id);
1008 if let Some(editor_id) = editor_request_id {
1009 self.active_requests.lock().unwrap().remove(&editor_id);
1010 }
1011 return Err(e);
1012 }
1013
1014 tracing::trace!(
1015 "Sent LSP request id={} method={}, waiting up to {:?} for response",
1016 id,
1017 method,
1018 timeout
1019 );
1020
1021 let response_result = match tokio::time::timeout(timeout, rx).await {
1022 Ok(Ok(inner)) => inner,
1023 Ok(Err(_)) => Err("Response channel closed".to_string()),
1024 Err(_) => {
1025 pending.lock().unwrap().remove(&id);
1027 tracing::warn!(
1028 "LSP request '{}' (lsp_id={}) on '{}' ({}) timed out after {:?}; sending $/cancelRequest",
1029 method,
1030 id,
1031 self.server_name.as_str(),
1032 self.language.as_str(),
1033 timeout
1034 );
1035 let _ = self.send_cancel_request(id).await;
1036 Err(format!(
1037 "Request '{}' timed out after {:?}",
1038 method, timeout
1039 ))
1040 }
1041 };
1042
1043 if let Some(editor_id) = editor_request_id {
1044 self.active_requests.lock().unwrap().remove(&editor_id);
1045 tracing::trace!("Completed request: editor_id={}, lsp_id={}", editor_id, id);
1046 }
1047
1048 let result = response_result?;
1049 serde_json::from_value(result).map_err(|e| format!("Failed to deserialize response: {}", e))
1050 }
1051
1052 async fn handle_initialize_sequential(
1054 &self,
1055 root_uri: Option<Uri>,
1056 initialization_options: Option<Value>,
1057 pending: &PendingRequests,
1058 ) -> Result<InitializeResult, String> {
1059 tracing::info!(
1060 "Initializing async LSP server with root_uri: {:?}, initialization_options: {:?}",
1061 root_uri,
1062 initialization_options
1063 );
1064
1065 let workspace_folders = root_uri.as_ref().map(|uri| {
1066 vec![WorkspaceFolder {
1067 uri: uri.clone(),
1068 name: uri
1069 .path()
1070 .as_str()
1071 .split('/')
1072 .next_back()
1073 .unwrap_or("workspace")
1074 .to_string(),
1075 }]
1076 });
1077
1078 #[allow(deprecated)]
1079 let params = InitializeParams {
1080 process_id: Some(std::process::id()),
1081 capabilities: create_client_capabilities(),
1082 workspace_folders,
1083 initialization_options,
1084 root_uri: root_uri.clone(),
1087 ..Default::default()
1088 };
1089
1090 let result: InitializeResult = self
1091 .send_request_sequential(Initialize::METHOD, Some(params), pending)
1092 .await?;
1093
1094 tracing::info!(
1095 "LSP initialize result: position_encoding={:?}",
1096 result.capabilities.position_encoding
1097 );
1098 *self.capabilities.lock().unwrap() = Some(result.capabilities.clone());
1099
1100 self.send_notification::<Initialized>(InitializedParams {})
1102 .await?;
1103
1104 self.initialized.store(true, Ordering::SeqCst);
1105
1106 let capabilities = extract_capability_summary(&result.capabilities);
1107
1108 let _ = self.async_tx.send(AsyncMessage::LspInitialized {
1110 language: (*self.language).clone(),
1111 server_name: (*self.server_name).clone(),
1112 capabilities,
1113 });
1114
1115 let _ = self.async_tx.send(AsyncMessage::LspStatusUpdate {
1117 language: (*self.language).clone(),
1118 server_name: (*self.server_name).clone(),
1119 status: LspServerStatus::Running,
1120 message: None,
1121 });
1122
1123 tracing::info!("Async LSP server initialized successfully");
1124
1125 Ok(result)
1126 }
1127
1128 async fn handle_did_open_sequential(
1130 &self,
1131 uri: Uri,
1132 text: String,
1133 language_id: String,
1134 _pending: &PendingRequests,
1135 ) -> Result<(), String> {
1136 let path = PathBuf::from(uri.path().as_str());
1137
1138 if should_skip_did_open(&self.document_versions, &path, self.language.as_str(), &uri) {
1139 return Ok(());
1140 }
1141
1142 tracing::trace!("LSP: did_open for {}", uri.as_str());
1143
1144 let lsp_language_id = path
1147 .extension()
1148 .and_then(|e| e.to_str())
1149 .and_then(|ext| self.language_id_overrides.get(ext))
1150 .cloned()
1151 .unwrap_or(language_id);
1152
1153 let params = DidOpenTextDocumentParams {
1154 text_document: TextDocumentItem {
1155 uri: uri.clone(),
1156 language_id: lsp_language_id,
1157 version: 0,
1158 text,
1159 },
1160 };
1161
1162 self.document_versions
1163 .lock()
1164 .unwrap()
1165 .insert(path.clone(), 0);
1166
1167 self.pending_opens
1169 .lock()
1170 .unwrap()
1171 .insert(path, Instant::now());
1172
1173 self.send_notification::<DidOpenTextDocument>(params).await
1174 }
1175
1176 async fn handle_did_change_sequential(
1178 &self,
1179 uri: Uri,
1180 content_changes: Vec<TextDocumentContentChangeEvent>,
1181 _pending: &PendingRequests,
1182 ) -> Result<(), String> {
1183 tracing::trace!("LSP: did_change for {}", uri.as_str());
1184
1185 let path = PathBuf::from(uri.path().as_str());
1186
1187 if !self.document_versions.lock().unwrap().contains_key(&path) {
1190 tracing::debug!(
1191 "LSP ({}): skipping didChange - document not yet opened",
1192 self.language
1193 );
1194 return Ok(());
1195 }
1196
1197 let opened_at = self.pending_opens.lock().unwrap().get(&path).copied();
1201 if let Some(opened_at) = opened_at {
1202 let elapsed = opened_at.elapsed();
1203 let grace_period = std::time::Duration::from_millis(DID_OPEN_GRACE_PERIOD_MS);
1204 if elapsed < grace_period {
1205 let wait_time = grace_period - elapsed;
1206 tracing::debug!(
1207 "LSP ({}): waiting {:?} for didOpen grace period before didChange",
1208 self.language,
1209 wait_time
1210 );
1211 tokio::time::sleep(wait_time).await;
1212 }
1213 self.pending_opens.lock().unwrap().remove(&path);
1215 }
1216
1217 let new_version = {
1218 let mut versions = self.document_versions.lock().unwrap();
1219 let version = versions.entry(path).or_insert(0);
1220 *version += 1;
1221 *version
1222 };
1223
1224 let params = DidChangeTextDocumentParams {
1225 text_document: VersionedTextDocumentIdentifier {
1226 uri: uri.clone(),
1227 version: new_version as i32,
1228 },
1229 content_changes,
1230 };
1231
1232 self.send_notification::<DidChangeTextDocument>(params)
1233 .await
1234 }
1235
1236 async fn handle_did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
1238 tracing::trace!("LSP: did_save for {}", uri.as_str());
1239
1240 let params = DidSaveTextDocumentParams {
1241 text_document: TextDocumentIdentifier { uri },
1242 text,
1243 };
1244
1245 self.send_notification::<DidSaveTextDocument>(params).await
1246 }
1247
1248 async fn handle_did_close(&self, uri: Uri) -> Result<(), String> {
1250 let path = PathBuf::from(uri.path().as_str());
1251
1252 if self
1254 .document_versions
1255 .lock()
1256 .unwrap()
1257 .remove(&path)
1258 .is_some()
1259 {
1260 tracing::info!("LSP ({}): didClose for {}", self.language, uri.as_str());
1261 } else {
1262 tracing::debug!(
1263 "LSP ({}): didClose for {} but document was not tracked",
1264 self.language,
1265 uri.as_str()
1266 );
1267 }
1268
1269 self.pending_opens.lock().unwrap().remove(&path);
1271
1272 let params = DidCloseTextDocumentParams {
1273 text_document: TextDocumentIdentifier { uri },
1274 };
1275
1276 self.send_notification::<DidCloseTextDocument>(params).await
1277 }
1278
1279 async fn handle_completion(
1281 &self,
1282 request_id: u64,
1283 uri: Uri,
1284 line: u32,
1285 character: u32,
1286 pending: &PendingRequests,
1287 ) -> Result<(), String> {
1288 use lsp_types::CompletionParams;
1289
1290 tracing::trace!(
1291 "LSP: completion request at {}:{}:{}",
1292 uri.as_str(),
1293 line,
1294 character
1295 );
1296
1297 let params = CompletionParams {
1298 text_document_position: TextDocumentPositionParams {
1299 text_document: TextDocumentIdentifier { uri },
1300 position: Position { line, character },
1301 },
1302 work_done_progress_params: WorkDoneProgressParams::default(),
1303 partial_result_params: PartialResultParams::default(),
1304 context: None,
1305 };
1306
1307 match self
1309 .send_request_sequential_tracked::<_, Value>(
1310 "textDocument/completion",
1311 Some(params),
1312 pending,
1313 Some(request_id),
1314 )
1315 .await
1316 {
1317 Ok(result) => {
1318 let items = if let Ok(list) =
1320 serde_json::from_value::<lsp_types::CompletionList>(result.clone())
1321 {
1322 list.items
1323 } else {
1324 serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result)
1325 .unwrap_or_default()
1326 };
1327
1328 let _ = self
1330 .async_tx
1331 .send(AsyncMessage::LspCompletion { request_id, items });
1332 Ok(())
1333 }
1334 Err(e) => {
1335 tracing::debug!("Completion request failed: {}", e);
1336 let _ = self.async_tx.send(AsyncMessage::LspCompletion {
1338 request_id,
1339 items: vec![],
1340 });
1341 Err(e)
1342 }
1343 }
1344 }
1345
1346 async fn handle_goto_definition(
1348 &self,
1349 request_id: u64,
1350 uri: Uri,
1351 line: u32,
1352 character: u32,
1353 pending: &PendingRequests,
1354 ) -> Result<(), String> {
1355 use lsp_types::GotoDefinitionParams;
1356
1357 tracing::trace!(
1358 "LSP: go-to-definition request at {}:{}:{}",
1359 uri.as_str(),
1360 line,
1361 character
1362 );
1363
1364 let params = GotoDefinitionParams {
1365 text_document_position_params: TextDocumentPositionParams {
1366 text_document: TextDocumentIdentifier { uri },
1367 position: Position { line, character },
1368 },
1369 work_done_progress_params: WorkDoneProgressParams::default(),
1370 partial_result_params: PartialResultParams::default(),
1371 };
1372
1373 match self
1375 .send_request_sequential::<_, Value>("textDocument/definition", Some(params), pending)
1376 .await
1377 {
1378 Ok(result) => {
1379 let locations = if let Ok(loc) =
1381 serde_json::from_value::<lsp_types::Location>(result.clone())
1382 {
1383 vec![loc]
1384 } else if let Ok(locs) =
1385 serde_json::from_value::<Vec<lsp_types::Location>>(result.clone())
1386 {
1387 locs
1388 } else if let Ok(links) =
1389 serde_json::from_value::<Vec<lsp_types::LocationLink>>(result)
1390 {
1391 links
1393 .into_iter()
1394 .map(|link| lsp_types::Location {
1395 uri: link.target_uri,
1396 range: link.target_selection_range,
1397 })
1398 .collect()
1399 } else {
1400 vec![]
1401 };
1402
1403 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1405 request_id,
1406 locations,
1407 });
1408 Ok(())
1409 }
1410 Err(e) => {
1411 tracing::debug!("Go-to-definition request failed: {}", e);
1412 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1414 request_id,
1415 locations: vec![],
1416 });
1417 Err(e)
1418 }
1419 }
1420 }
1421
1422 async fn handle_rename(
1424 &self,
1425 request_id: u64,
1426 uri: Uri,
1427 line: u32,
1428 character: u32,
1429 new_name: String,
1430 pending: &PendingRequests,
1431 ) -> Result<(), String> {
1432 use lsp_types::RenameParams;
1433
1434 tracing::trace!(
1435 "LSP: rename request at {}:{}:{} to '{}'",
1436 uri.as_str(),
1437 line,
1438 character,
1439 new_name
1440 );
1441
1442 let params = RenameParams {
1443 text_document_position: TextDocumentPositionParams {
1444 text_document: TextDocumentIdentifier { uri },
1445 position: Position { line, character },
1446 },
1447 new_name,
1448 work_done_progress_params: WorkDoneProgressParams::default(),
1449 };
1450
1451 match self
1453 .send_request_sequential::<_, Value>("textDocument/rename", Some(params), pending)
1454 .await
1455 {
1456 Ok(result) => {
1457 match serde_json::from_value::<lsp_types::WorkspaceEdit>(result) {
1459 Ok(workspace_edit) => {
1460 let _ = self.async_tx.send(AsyncMessage::LspRename {
1462 request_id,
1463 result: Ok(workspace_edit),
1464 });
1465 Ok(())
1466 }
1467 Err(e) => {
1468 tracing::error!("Failed to parse rename response: {}", e);
1469 let _ = self.async_tx.send(AsyncMessage::LspRename {
1470 request_id,
1471 result: Err(format!("Failed to parse rename response: {}", e)),
1472 });
1473 Err(format!("Failed to parse rename response: {}", e))
1474 }
1475 }
1476 }
1477 Err(e) => {
1478 tracing::debug!("Rename request failed: {}", e);
1479 let _ = self.async_tx.send(AsyncMessage::LspRename {
1481 request_id,
1482 result: Err(e.clone()),
1483 });
1484 Err(e)
1485 }
1486 }
1487 }
1488
1489 async fn handle_hover(
1491 &self,
1492 request_id: u64,
1493 uri: Uri,
1494 line: u32,
1495 character: u32,
1496 pending: &PendingRequests,
1497 ) -> Result<(), String> {
1498 use lsp_types::HoverParams;
1499
1500 tracing::trace!(
1501 "LSP: hover request at {}:{}:{}",
1502 uri.as_str(),
1503 line,
1504 character
1505 );
1506
1507 let params = HoverParams {
1508 text_document_position_params: TextDocumentPositionParams {
1509 text_document: TextDocumentIdentifier { uri },
1510 position: Position { line, character },
1511 },
1512 work_done_progress_params: WorkDoneProgressParams::default(),
1513 };
1514
1515 match self
1517 .send_request_sequential::<_, Value>("textDocument/hover", Some(params), pending)
1518 .await
1519 {
1520 Ok(result) => {
1521 tracing::debug!("Raw LSP hover response: {:?}", result);
1522 let (contents, is_markdown, range) = if result.is_null() {
1524 (String::new(), false, None)
1526 } else {
1527 match serde_json::from_value::<lsp_types::Hover>(result) {
1528 Ok(hover) => {
1529 let (contents, is_markdown) =
1531 Self::extract_hover_contents(&hover.contents);
1532 let range = hover.range.map(|r| {
1534 (
1535 (r.start.line, r.start.character),
1536 (r.end.line, r.end.character),
1537 )
1538 });
1539 (contents, is_markdown, range)
1540 }
1541 Err(e) => {
1542 tracing::error!("Failed to parse hover response: {}", e);
1543 (String::new(), false, None)
1544 }
1545 }
1546 };
1547
1548 let _ = self.async_tx.send(AsyncMessage::LspHover {
1550 request_id,
1551 contents,
1552 is_markdown,
1553 range,
1554 });
1555 Ok(())
1556 }
1557 Err(e) => {
1558 tracing::debug!("Hover request failed: {}", e);
1559 let _ = self.async_tx.send(AsyncMessage::LspHover {
1561 request_id,
1562 contents: String::new(),
1563 is_markdown: false,
1564 range: None,
1565 });
1566 Err(e)
1567 }
1568 }
1569 }
1570
1571 fn extract_hover_contents(contents: &lsp_types::HoverContents) -> (String, bool) {
1574 use lsp_types::{HoverContents, MarkedString, MarkupContent, MarkupKind};
1575
1576 match contents {
1577 HoverContents::Scalar(marked) => match marked {
1578 MarkedString::String(s) => (s.clone(), false),
1579 MarkedString::LanguageString(ls) => {
1580 (format!("```{}\n{}\n```", ls.language, ls.value), true)
1582 }
1583 },
1584 HoverContents::Array(arr) => {
1585 let content = arr
1587 .iter()
1588 .map(|marked| match marked {
1589 MarkedString::String(s) => s.clone(),
1590 MarkedString::LanguageString(ls) => {
1591 format!("```{}\n{}\n```", ls.language, ls.value)
1592 }
1593 })
1594 .collect::<Vec<_>>()
1595 .join("\n\n");
1596 (content, true)
1597 }
1598 HoverContents::Markup(MarkupContent { kind, value }) => {
1599 let is_markdown = matches!(kind, MarkupKind::Markdown);
1601 (value.clone(), is_markdown)
1602 }
1603 }
1604 }
1605
1606 async fn handle_references(
1608 &self,
1609 request_id: u64,
1610 uri: Uri,
1611 line: u32,
1612 character: u32,
1613 pending: &PendingRequests,
1614 ) -> Result<(), String> {
1615 use lsp_types::{ReferenceContext, ReferenceParams};
1616
1617 tracing::trace!(
1618 "LSP: find references request at {}:{}:{}",
1619 uri.as_str(),
1620 line,
1621 character
1622 );
1623
1624 let params = ReferenceParams {
1625 text_document_position: lsp_types::TextDocumentPositionParams {
1626 text_document: TextDocumentIdentifier { uri },
1627 position: Position { line, character },
1628 },
1629 work_done_progress_params: WorkDoneProgressParams::default(),
1630 partial_result_params: PartialResultParams::default(),
1631 context: ReferenceContext {
1632 include_declaration: true,
1633 },
1634 };
1635
1636 match self
1638 .send_request_sequential::<_, Value>("textDocument/references", Some(params), pending)
1639 .await
1640 {
1641 Ok(result) => {
1642 let locations = if result.is_null() {
1644 Vec::new()
1645 } else {
1646 serde_json::from_value::<Vec<lsp_types::Location>>(result).unwrap_or_default()
1647 };
1648
1649 tracing::trace!("LSP: found {} references", locations.len());
1650
1651 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1653 request_id,
1654 locations,
1655 });
1656 Ok(())
1657 }
1658 Err(e) => {
1659 tracing::debug!("Find references request failed: {}", e);
1660 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1662 request_id,
1663 locations: Vec::new(),
1664 });
1665 Err(e)
1666 }
1667 }
1668 }
1669
1670 async fn handle_signature_help(
1672 &self,
1673 request_id: u64,
1674 uri: Uri,
1675 line: u32,
1676 character: u32,
1677 pending: &PendingRequests,
1678 ) -> Result<(), String> {
1679 use lsp_types::SignatureHelpParams;
1680
1681 tracing::trace!(
1682 "LSP: signature help request at {}:{}:{}",
1683 uri.as_str(),
1684 line,
1685 character
1686 );
1687
1688 let params = SignatureHelpParams {
1689 text_document_position_params: TextDocumentPositionParams {
1690 text_document: TextDocumentIdentifier { uri },
1691 position: Position { line, character },
1692 },
1693 work_done_progress_params: WorkDoneProgressParams::default(),
1694 context: None, };
1696
1697 match self
1699 .send_request_sequential::<_, Value>(
1700 "textDocument/signatureHelp",
1701 Some(params),
1702 pending,
1703 )
1704 .await
1705 {
1706 Ok(result) => {
1707 let signature_help = if result.is_null() {
1709 None
1710 } else {
1711 serde_json::from_value::<lsp_types::SignatureHelp>(result).ok()
1712 };
1713
1714 tracing::trace!(
1715 "LSP: signature help received: {} signatures",
1716 signature_help
1717 .as_ref()
1718 .map(|h| h.signatures.len())
1719 .unwrap_or(0)
1720 );
1721
1722 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1724 request_id,
1725 signature_help,
1726 });
1727 Ok(())
1728 }
1729 Err(e) => {
1730 tracing::debug!("Signature help request failed: {}", e);
1731 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1733 request_id,
1734 signature_help: None,
1735 });
1736 Err(e)
1737 }
1738 }
1739 }
1740
1741 #[allow(clippy::too_many_arguments)]
1743 async fn handle_code_actions(
1744 &self,
1745 request_id: u64,
1746 uri: Uri,
1747 start_line: u32,
1748 start_char: u32,
1749 end_line: u32,
1750 end_char: u32,
1751 diagnostics: Vec<lsp_types::Diagnostic>,
1752 pending: &PendingRequests,
1753 ) -> Result<(), String> {
1754 use lsp_types::{CodeActionContext, CodeActionParams};
1755
1756 tracing::trace!(
1757 "LSP: code actions request at {}:{}:{}-{}:{}",
1758 uri.as_str(),
1759 start_line,
1760 start_char,
1761 end_line,
1762 end_char
1763 );
1764
1765 let params = CodeActionParams {
1766 text_document: TextDocumentIdentifier { uri },
1767 range: Range {
1768 start: Position {
1769 line: start_line,
1770 character: start_char,
1771 },
1772 end: Position {
1773 line: end_line,
1774 character: end_char,
1775 },
1776 },
1777 context: CodeActionContext {
1778 diagnostics,
1779 only: None,
1780 trigger_kind: None,
1781 },
1782 work_done_progress_params: WorkDoneProgressParams::default(),
1783 partial_result_params: PartialResultParams::default(),
1784 };
1785
1786 match self
1788 .send_request_sequential::<_, Value>("textDocument/codeAction", Some(params), pending)
1789 .await
1790 {
1791 Ok(result) => {
1792 let actions = if result.is_null() {
1794 Vec::new()
1795 } else {
1796 serde_json::from_value::<Vec<lsp_types::CodeActionOrCommand>>(result)
1797 .unwrap_or_default()
1798 };
1799
1800 tracing::trace!("LSP: received {} code actions", actions.len());
1801
1802 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1804 request_id,
1805 actions,
1806 });
1807 Ok(())
1808 }
1809 Err(e) => {
1810 tracing::debug!("Code actions request failed: {}", e);
1811 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1813 request_id,
1814 actions: Vec::new(),
1815 });
1816 Err(e)
1817 }
1818 }
1819 }
1820
1821 async fn handle_execute_command(
1823 &self,
1824 command: String,
1825 arguments: Option<Vec<Value>>,
1826 pending: &PendingRequests,
1827 ) -> Result<(), String> {
1828 let params = lsp_types::ExecuteCommandParams {
1829 command: command.clone(),
1830 arguments: arguments.unwrap_or_default(),
1831 work_done_progress_params: lsp_types::WorkDoneProgressParams::default(),
1832 };
1833
1834 match self
1835 .send_request_sequential::<_, Value>("workspace/executeCommand", Some(params), pending)
1836 .await
1837 {
1838 Ok(_) => {
1839 tracing::info!("ExecuteCommand '{}' completed", command);
1840 Ok(())
1841 }
1842 Err(e) => {
1843 tracing::debug!("ExecuteCommand '{}' failed: {}", command, e);
1844 Err(e)
1845 }
1846 }
1847 }
1848
1849 async fn handle_code_action_resolve(
1851 &self,
1852 request_id: u64,
1853 action: lsp_types::CodeAction,
1854 pending: &PendingRequests,
1855 ) -> Result<(), String> {
1856 match self
1857 .send_request_sequential::<_, Value>("codeAction/resolve", Some(action), pending)
1858 .await
1859 {
1860 Ok(result) => {
1861 let resolved = serde_json::from_value::<lsp_types::CodeAction>(result)
1862 .map_err(|e| format!("Failed to parse codeAction/resolve response: {}", e));
1863 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
1864 request_id,
1865 action: resolved,
1866 });
1867 Ok(())
1868 }
1869 Err(e) => {
1870 tracing::debug!("codeAction/resolve failed: {}", e);
1871 let _ = self.async_tx.send(AsyncMessage::LspCodeActionResolved {
1872 request_id,
1873 action: Err(e.clone()),
1874 });
1875 Err(e)
1876 }
1877 }
1878 }
1879
1880 async fn handle_completion_resolve(
1882 &self,
1883 request_id: u64,
1884 item: lsp_types::CompletionItem,
1885 pending: &PendingRequests,
1886 ) -> Result<(), String> {
1887 match self
1888 .send_request_sequential::<_, Value>("completionItem/resolve", Some(item), pending)
1889 .await
1890 {
1891 Ok(result) => {
1892 let resolved = serde_json::from_value::<lsp_types::CompletionItem>(result)
1893 .map_err(|e| format!("Failed to parse completionItem/resolve response: {}", e));
1894 let _ = self.async_tx.send(AsyncMessage::LspCompletionResolved {
1895 request_id,
1896 item: resolved,
1897 });
1898 Ok(())
1899 }
1900 Err(e) => {
1901 tracing::debug!("completionItem/resolve failed: {}", e);
1902 Err(e)
1903 }
1904 }
1905 }
1906
1907 async fn handle_document_formatting(
1909 &self,
1910 request_id: u64,
1911 uri: Uri,
1912 tab_size: u32,
1913 insert_spaces: bool,
1914 pending: &PendingRequests,
1915 ) -> Result<(), String> {
1916 use lsp_types::{DocumentFormattingParams, FormattingOptions};
1917
1918 let params = DocumentFormattingParams {
1919 text_document: TextDocumentIdentifier { uri: uri.clone() },
1920 options: FormattingOptions {
1921 tab_size,
1922 insert_spaces,
1923 ..Default::default()
1924 },
1925 work_done_progress_params: WorkDoneProgressParams::default(),
1926 };
1927
1928 match self
1929 .send_request_sequential::<_, Value>("textDocument/formatting", Some(params), pending)
1930 .await
1931 {
1932 Ok(result) => {
1933 let edits = if result.is_null() {
1934 Vec::new()
1935 } else {
1936 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
1937 };
1938 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
1939 request_id,
1940 uri: uri.as_str().to_string(),
1941 edits,
1942 });
1943 Ok(())
1944 }
1945 Err(e) => {
1946 tracing::debug!("textDocument/formatting failed: {}", e);
1947 Err(e)
1948 }
1949 }
1950 }
1951
1952 #[allow(clippy::too_many_arguments)]
1954 async fn handle_document_range_formatting(
1955 &self,
1956 request_id: u64,
1957 uri: Uri,
1958 start_line: u32,
1959 start_char: u32,
1960 end_line: u32,
1961 end_char: u32,
1962 tab_size: u32,
1963 insert_spaces: bool,
1964 pending: &PendingRequests,
1965 ) -> Result<(), String> {
1966 use lsp_types::{DocumentRangeFormattingParams, FormattingOptions};
1967
1968 let params = DocumentRangeFormattingParams {
1969 text_document: TextDocumentIdentifier { uri: uri.clone() },
1970 range: Range {
1971 start: Position::new(start_line, start_char),
1972 end: Position::new(end_line, end_char),
1973 },
1974 options: FormattingOptions {
1975 tab_size,
1976 insert_spaces,
1977 ..Default::default()
1978 },
1979 work_done_progress_params: WorkDoneProgressParams::default(),
1980 };
1981
1982 match self
1983 .send_request_sequential::<_, Value>(
1984 "textDocument/rangeFormatting",
1985 Some(params),
1986 pending,
1987 )
1988 .await
1989 {
1990 Ok(result) => {
1991 let edits = if result.is_null() {
1992 Vec::new()
1993 } else {
1994 serde_json::from_value::<Vec<lsp_types::TextEdit>>(result).unwrap_or_default()
1995 };
1996 let _ = self.async_tx.send(AsyncMessage::LspFormatting {
1997 request_id,
1998 uri: uri.as_str().to_string(),
1999 edits,
2000 });
2001 Ok(())
2002 }
2003 Err(e) => {
2004 tracing::debug!("textDocument/rangeFormatting failed: {}", e);
2005 Err(e)
2006 }
2007 }
2008 }
2009
2010 async fn handle_prepare_rename(
2012 &self,
2013 request_id: u64,
2014 uri: Uri,
2015 line: u32,
2016 character: u32,
2017 pending: &PendingRequests,
2018 ) -> Result<(), String> {
2019 let params = TextDocumentPositionParams {
2020 text_document: TextDocumentIdentifier { uri },
2021 position: Position::new(line, character),
2022 };
2023
2024 match self
2025 .send_request_sequential::<_, Value>(
2026 "textDocument/prepareRename",
2027 Some(params),
2028 pending,
2029 )
2030 .await
2031 {
2032 Ok(result) => {
2033 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2034 request_id,
2035 result: Ok(result),
2036 });
2037 Ok(())
2038 }
2039 Err(e) => {
2040 let _ = self.async_tx.send(AsyncMessage::LspPrepareRename {
2041 request_id,
2042 result: Err(e.clone()),
2043 });
2044 Err(e)
2045 }
2046 }
2047 }
2048
2049 async fn handle_document_diagnostic(
2050 &self,
2051 request_id: u64,
2052 uri: Uri,
2053 previous_result_id: Option<String>,
2054 pending: &PendingRequests,
2055 ) -> Result<(), String> {
2056 use lsp_types::DocumentDiagnosticParams;
2057
2058 let supports_pull = self
2060 .capabilities
2061 .lock()
2062 .unwrap()
2063 .as_ref()
2064 .and_then(|c| c.diagnostic_provider.as_ref())
2065 .is_some();
2066 if !supports_pull {
2067 tracing::trace!(
2068 "LSP: server does not support pull diagnostics, skipping request for {}",
2069 uri.as_str()
2070 );
2071 return Ok(());
2072 }
2073
2074 tracing::trace!(
2075 "LSP: document diagnostic request for {} (previous_result_id: {:?})",
2076 uri.as_str(),
2077 previous_result_id
2078 );
2079
2080 let params = DocumentDiagnosticParams {
2081 text_document: TextDocumentIdentifier { uri: uri.clone() },
2082 identifier: None,
2083 previous_result_id,
2084 work_done_progress_params: WorkDoneProgressParams::default(),
2085 partial_result_params: PartialResultParams::default(),
2086 };
2087
2088 match self
2090 .send_request_sequential::<_, Value>("textDocument/diagnostic", Some(params), pending)
2091 .await
2092 {
2093 Ok(result) => {
2094 let uri_string = uri.as_str().to_string();
2097
2098 if let Ok(full_report) = serde_json::from_value::<
2100 lsp_types::RelatedFullDocumentDiagnosticReport,
2101 >(result.clone())
2102 {
2103 let diagnostics = full_report.full_document_diagnostic_report.items;
2104 let result_id = full_report.full_document_diagnostic_report.result_id;
2105
2106 tracing::trace!(
2107 "LSP: received {} diagnostics for {} (result_id: {:?})",
2108 diagnostics.len(),
2109 uri_string,
2110 result_id
2111 );
2112
2113 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2114 request_id,
2115 uri: uri_string,
2116 result_id,
2117 diagnostics,
2118 unchanged: false,
2119 });
2120 } else if let Ok(unchanged_report) = serde_json::from_value::<
2121 lsp_types::RelatedUnchangedDocumentDiagnosticReport,
2122 >(result.clone())
2123 {
2124 let result_id = unchanged_report
2125 .unchanged_document_diagnostic_report
2126 .result_id;
2127
2128 tracing::trace!(
2129 "LSP: diagnostics unchanged for {} (result_id: {:?})",
2130 uri_string,
2131 result_id
2132 );
2133
2134 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2135 request_id,
2136 uri: uri_string,
2137 result_id: Some(result_id),
2138 diagnostics: Vec::new(),
2139 unchanged: true,
2140 });
2141 } else {
2142 tracing::warn!(
2144 "LSP: could not parse diagnostic report, sending empty: {}",
2145 result
2146 );
2147 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2148 request_id,
2149 uri: uri_string,
2150 result_id: None,
2151 diagnostics: Vec::new(),
2152 unchanged: false,
2153 });
2154 }
2155
2156 Ok(())
2157 }
2158 Err(e) => {
2159 tracing::debug!("Document diagnostic request failed: {}", e);
2160 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2162 request_id,
2163 uri: uri.as_str().to_string(),
2164 result_id: None,
2165 diagnostics: Vec::new(),
2166 unchanged: false,
2167 });
2168 Err(e)
2169 }
2170 }
2171 }
2172
2173 #[allow(clippy::too_many_arguments)]
2175 async fn handle_inlay_hints(
2176 &self,
2177 request_id: u64,
2178 uri: Uri,
2179 start_line: u32,
2180 start_char: u32,
2181 end_line: u32,
2182 end_char: u32,
2183 pending: &PendingRequests,
2184 ) -> Result<(), String> {
2185 use lsp_types::InlayHintParams;
2186
2187 tracing::trace!(
2188 "LSP: inlay hints request for {} ({}:{} - {}:{})",
2189 uri.as_str(),
2190 start_line,
2191 start_char,
2192 end_line,
2193 end_char
2194 );
2195
2196 let params = InlayHintParams {
2197 text_document: TextDocumentIdentifier { uri: uri.clone() },
2198 range: Range {
2199 start: Position {
2200 line: start_line,
2201 character: start_char,
2202 },
2203 end: Position {
2204 line: end_line,
2205 character: end_char,
2206 },
2207 },
2208 work_done_progress_params: WorkDoneProgressParams::default(),
2209 };
2210
2211 match self
2212 .send_request_sequential::<_, Option<Vec<lsp_types::InlayHint>>>(
2213 "textDocument/inlayHint",
2214 Some(params),
2215 pending,
2216 )
2217 .await
2218 {
2219 Ok(hints) => {
2220 let hints = hints.unwrap_or_default();
2221 let uri_string = uri.as_str().to_string();
2222
2223 tracing::trace!(
2224 "LSP: received {} inlay hints for {}",
2225 hints.len(),
2226 uri_string
2227 );
2228
2229 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2230 request_id,
2231 uri: uri_string,
2232 hints,
2233 });
2234
2235 Ok(())
2236 }
2237 Err(e) => {
2238 tracing::debug!("Inlay hints request failed: {}", e);
2239 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
2241 request_id,
2242 uri: uri.as_str().to_string(),
2243 hints: Vec::new(),
2244 });
2245 Err(e)
2246 }
2247 }
2248 }
2249
2250 async fn handle_folding_ranges(
2252 &self,
2253 request_id: u64,
2254 uri: Uri,
2255 pending: &PendingRequests,
2256 ) -> Result<(), String> {
2257 use lsp_types::FoldingRangeParams;
2258
2259 tracing::trace!("LSP: folding range request for {}", uri.as_str());
2260
2261 let params = FoldingRangeParams {
2262 text_document: TextDocumentIdentifier { uri: uri.clone() },
2263 work_done_progress_params: WorkDoneProgressParams::default(),
2264 partial_result_params: PartialResultParams::default(),
2265 };
2266
2267 match self
2268 .send_request_sequential::<_, Option<Vec<lsp_types::FoldingRange>>>(
2269 "textDocument/foldingRange",
2270 Some(params),
2271 pending,
2272 )
2273 .await
2274 {
2275 Ok(ranges) => {
2276 let ranges = ranges.unwrap_or_default();
2277 let uri_string = uri.as_str().to_string();
2278
2279 tracing::trace!(
2280 "LSP: received {} folding ranges for {}",
2281 ranges.len(),
2282 uri_string
2283 );
2284
2285 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2286 request_id,
2287 uri: uri_string,
2288 ranges,
2289 });
2290
2291 Ok(())
2292 }
2293 Err(e) => {
2294 tracing::debug!("Folding range request failed: {}", e);
2295 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
2296 request_id,
2297 uri: uri.as_str().to_string(),
2298 ranges: Vec::new(),
2299 });
2300 Err(e)
2301 }
2302 }
2303 }
2304
2305 async fn handle_semantic_tokens_full(
2306 &self,
2307 request_id: u64,
2308 uri: Uri,
2309 pending: &PendingRequests,
2310 ) -> Result<(), String> {
2311 use lsp_types::request::SemanticTokensFullRequest;
2312
2313 tracing::trace!("LSP: semanticTokens/full request for {}", uri.as_str());
2314
2315 let params = SemanticTokensParams {
2316 work_done_progress_params: WorkDoneProgressParams::default(),
2317 partial_result_params: PartialResultParams::default(),
2318 text_document: TextDocumentIdentifier { uri: uri.clone() },
2319 };
2320
2321 match self
2322 .send_request_sequential_tracked::<_, Option<SemanticTokensResult>>(
2323 SemanticTokensFullRequest::METHOD,
2324 Some(params),
2325 pending,
2326 Some(request_id),
2327 )
2328 .await
2329 {
2330 Ok(result) => {
2331 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2332 request_id,
2333 uri: uri.as_str().to_string(),
2334 response: LspSemanticTokensResponse::Full(Ok(result)),
2335 });
2336 Ok(())
2337 }
2338 Err(e) => {
2339 tracing::debug!("Semantic tokens request failed: {}", e);
2340 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2341 request_id,
2342 uri: uri.as_str().to_string(),
2343 response: LspSemanticTokensResponse::Full(Err(e.clone())),
2344 });
2345 Err(e)
2346 }
2347 }
2348 }
2349
2350 async fn handle_semantic_tokens_full_delta(
2351 &self,
2352 request_id: u64,
2353 uri: Uri,
2354 previous_result_id: String,
2355 pending: &PendingRequests,
2356 ) -> Result<(), String> {
2357 use lsp_types::{
2358 request::SemanticTokensFullDeltaRequest, SemanticTokensDeltaParams,
2359 SemanticTokensFullDeltaResult,
2360 };
2361
2362 tracing::trace!(
2363 "LSP: semanticTokens/full/delta request for {}",
2364 uri.as_str()
2365 );
2366
2367 let params = SemanticTokensDeltaParams {
2368 work_done_progress_params: WorkDoneProgressParams::default(),
2369 partial_result_params: PartialResultParams::default(),
2370 text_document: TextDocumentIdentifier { uri: uri.clone() },
2371 previous_result_id,
2372 };
2373
2374 match self
2375 .send_request_sequential_tracked::<_, Option<SemanticTokensFullDeltaResult>>(
2376 SemanticTokensFullDeltaRequest::METHOD,
2377 Some(params),
2378 pending,
2379 Some(request_id),
2380 )
2381 .await
2382 {
2383 Ok(result) => {
2384 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2385 request_id,
2386 uri: uri.as_str().to_string(),
2387 response: LspSemanticTokensResponse::FullDelta(Ok(result)),
2388 });
2389 Ok(())
2390 }
2391 Err(e) => {
2392 tracing::debug!("Semantic tokens delta request failed: {}", e);
2393 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2394 request_id,
2395 uri: uri.as_str().to_string(),
2396 response: LspSemanticTokensResponse::FullDelta(Err(e.clone())),
2397 });
2398 Err(e)
2399 }
2400 }
2401 }
2402
2403 async fn handle_semantic_tokens_range(
2404 &self,
2405 request_id: u64,
2406 uri: Uri,
2407 range: lsp_types::Range,
2408 pending: &PendingRequests,
2409 ) -> Result<(), String> {
2410 use lsp_types::{request::SemanticTokensRangeRequest, SemanticTokensRangeParams};
2411
2412 tracing::trace!("LSP: semanticTokens/range request for {}", uri.as_str());
2413
2414 let params = SemanticTokensRangeParams {
2415 work_done_progress_params: WorkDoneProgressParams::default(),
2416 partial_result_params: PartialResultParams::default(),
2417 text_document: TextDocumentIdentifier { uri: uri.clone() },
2418 range,
2419 };
2420
2421 match self
2422 .send_request_sequential_tracked::<_, Option<lsp_types::SemanticTokensRangeResult>>(
2423 SemanticTokensRangeRequest::METHOD,
2424 Some(params),
2425 pending,
2426 Some(request_id),
2427 )
2428 .await
2429 {
2430 Ok(result) => {
2431 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2432 request_id,
2433 uri: uri.as_str().to_string(),
2434 response: LspSemanticTokensResponse::Range(Ok(result)),
2435 });
2436 Ok(())
2437 }
2438 Err(e) => {
2439 tracing::debug!("Semantic tokens range request failed: {}", e);
2440 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2441 request_id,
2442 uri: uri.as_str().to_string(),
2443 response: LspSemanticTokensResponse::Range(Err(e.clone())),
2444 });
2445 Err(e)
2446 }
2447 }
2448 }
2449
2450 async fn handle_plugin_request(
2452 &self,
2453 request_id: u64,
2454 method: String,
2455 params: Option<Value>,
2456 pending: &PendingRequests,
2457 ) {
2458 tracing::trace!(
2459 "Plugin request {} => method={} params={:?}",
2460 request_id,
2461 method,
2462 params
2463 );
2464 let result = self
2465 .send_request_sequential_tracked::<Value, Value>(
2466 &method,
2467 params,
2468 pending,
2469 Some(request_id),
2470 )
2471 .await;
2472
2473 tracing::trace!(
2474 "Plugin request {} completed with result {:?}",
2475 request_id,
2476 &result
2477 );
2478 let _ = self.async_tx.send(AsyncMessage::PluginLspResponse {
2479 language: (*self.language).clone(),
2480 request_id,
2481 result,
2482 });
2483 }
2484
2485 async fn handle_shutdown(&self) -> Result<(), String> {
2487 tracing::info!("Shutting down async LSP server");
2488
2489 let notification = JsonRpcNotification {
2490 jsonrpc: "2.0".to_string(),
2491 method: "shutdown".to_string(),
2492 params: None,
2493 };
2494
2495 self.write_message(¬ification).await?;
2496
2497 let exit = JsonRpcNotification {
2498 jsonrpc: "2.0".to_string(),
2499 method: "exit".to_string(),
2500 params: None,
2501 };
2502
2503 self.write_message(&exit).await
2504 }
2505
2506 async fn send_cancel_request(&self, lsp_id: i64) -> Result<(), String> {
2508 tracing::trace!("Sending $/cancelRequest for LSP id {}", lsp_id);
2509
2510 let notification = JsonRpcNotification {
2511 jsonrpc: "2.0".to_string(),
2512 method: "$/cancelRequest".to_string(),
2513 params: Some(serde_json::json!({ "id": lsp_id })),
2514 };
2515
2516 self.write_message(¬ification).await
2517 }
2518
2519 async fn handle_cancel_request(&self, request_id: u64) -> Result<(), String> {
2521 let lsp_id = self.active_requests.lock().unwrap().remove(&request_id);
2522 if let Some(lsp_id) = lsp_id {
2523 tracing::info!(
2524 "Cancelling request: editor_id={}, lsp_id={}",
2525 request_id,
2526 lsp_id
2527 );
2528 self.send_cancel_request(lsp_id).await
2529 } else {
2530 tracing::trace!(
2531 "Cancel request ignored: no active LSP request for editor_id={}",
2532 request_id
2533 );
2534 Ok(())
2535 }
2536 }
2537}
2538
2539struct LspTask {
2541 _process: crate::services::remote::StdioChild,
2544
2545 stdin: ChildStdin,
2547
2548 stdout: BufReader<ChildStdout>,
2550
2551 next_id: i64,
2553
2554 pending: HashMap<i64, oneshot::Sender<Result<Value, String>>>,
2556
2557 capabilities: Option<ServerCapabilities>,
2559
2560 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2562
2563 pending_opens: HashMap<PathBuf, Instant>,
2566
2567 initialized: bool,
2569
2570 async_tx: std_mpsc::Sender<AsyncMessage>,
2572
2573 language: String,
2575
2576 server_name: String,
2578
2579 server_command: String,
2581
2582 stderr_log_path: std::path::PathBuf,
2584
2585 language_id_overrides: HashMap<String, String>,
2587}
2588
2589impl LspTask {
2590 #[allow(clippy::too_many_arguments)]
2600 async fn spawn(
2601 command: &str,
2602 args: &[String],
2603 env: &std::collections::HashMap<String, String>,
2604 language: String,
2605 server_name: String,
2606 async_tx: std_mpsc::Sender<AsyncMessage>,
2607 process_limits: &ProcessLimits,
2608 stderr_log_path: std::path::PathBuf,
2609 language_id_overrides: HashMap<String, String>,
2610 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2611 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
2612 ) -> Result<Self, String> {
2613 tracing::info!("Spawning async LSP server: {} {:?}", command, args);
2614 tracing::info!("Process limits: {:?}", process_limits);
2615 tracing::info!("LSP stderr will be logged to: {:?}", stderr_log_path);
2616
2617 if !long_running_spawner.command_exists(command).await {
2622 return Err(format!(
2623 "LSP server executable '{}' not found in the active authority's PATH. \
2624 Please install it or check your configuration.",
2625 command
2626 ));
2627 }
2628
2629 let env_pairs: Vec<(String, String)> =
2634 env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2635
2636 let mut stdio_child = long_running_spawner
2637 .spawn_stdio(command, args, env_pairs, None, Some(process_limits))
2638 .await
2639 .map_err(|e| format!("Failed to spawn LSP server '{}': {}", command, e))?;
2640
2641 let stdin = stdio_child
2642 .take_stdin()
2643 .ok_or_else(|| "Failed to get stdin".to_string())?;
2644
2645 let stdout_stream = stdio_child
2646 .take_stdout()
2647 .ok_or_else(|| "Failed to get stdout".to_string())?;
2648 let stdout = BufReader::new(stdout_stream);
2649
2650 if let Some(stderr_stream) = stdio_child.take_stderr() {
2656 let log_path = stderr_log_path.clone();
2657 tokio::spawn(async move {
2658 use tokio::fs::File;
2659 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader as TokioBufReader};
2660 let mut file = match File::create(&log_path).await {
2661 Ok(f) => f,
2662 Err(e) => {
2663 tracing::warn!("Could not create LSP stderr log {:?}: {}", log_path, e);
2664 return;
2665 }
2666 };
2667 let mut reader = TokioBufReader::new(stderr_stream);
2668 let mut buf = String::new();
2669 loop {
2670 buf.clear();
2671 match reader.read_line(&mut buf).await {
2672 Ok(0) => break,
2673 Ok(_) => {
2674 if let Err(e) = file.write_all(buf.as_bytes()).await {
2675 tracing::warn!(
2676 "Write to LSP stderr log {:?} failed: {}",
2677 log_path,
2678 e
2679 );
2680 return;
2681 }
2682 }
2683 Err(e) => {
2684 tracing::debug!("LSP stderr stream closed for {:?}: {}", log_path, e);
2685 return;
2686 }
2687 }
2688 }
2689 });
2690 }
2691
2692 Ok(Self {
2693 _process: stdio_child,
2694 stdin,
2695 stdout,
2696 next_id: 0,
2697 pending: HashMap::new(),
2698 capabilities: None,
2699 document_versions,
2700 pending_opens: HashMap::new(),
2701 initialized: false,
2702 async_tx,
2703 language,
2704 server_name,
2705 server_command: command.to_string(),
2706 stderr_log_path,
2707 language_id_overrides,
2708 })
2709 }
2710
2711 #[allow(clippy::too_many_arguments)]
2713 #[allow(clippy::let_underscore_must_use)] fn spawn_stdout_reader(
2715 mut stdout: BufReader<ChildStdout>,
2716 pending: PendingRequests,
2717 async_tx: std_mpsc::Sender<AsyncMessage>,
2718 language: String,
2719 server_name: String,
2720 server_command: String,
2721 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2722 stderr_log_path: std::path::PathBuf,
2723 shutting_down: Arc<AtomicBool>,
2724 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2725 ) {
2726 tokio::spawn(async move {
2727 tracing::info!("LSP stdout reader task started for {}", language);
2728 loop {
2729 match read_message_from_stdout(&mut stdout).await {
2730 Ok(message) => {
2731 tracing::trace!("Read message from LSP server: {:?}", message);
2732 if let Err(e) = handle_message_dispatch(
2733 message,
2734 &pending,
2735 &async_tx,
2736 &language,
2737 &server_name,
2738 &server_command,
2739 &stdin_writer,
2740 &document_versions,
2741 )
2742 .await
2743 {
2744 tracing::error!("Error handling LSP message: {}", e);
2745 }
2746 }
2747 Err(e) => {
2748 if shutting_down.load(Ordering::SeqCst) {
2750 tracing::info!(
2751 "LSP stdout reader exiting due to graceful shutdown for {}",
2752 language
2753 );
2754 } else {
2755 tracing::error!("Error reading from LSP server: {}", e);
2756 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2757 language: language.clone(),
2758 server_name: server_name.clone(),
2759 status: LspServerStatus::Error,
2760 message: None,
2761 });
2762 let _ = async_tx.send(AsyncMessage::LspError {
2763 language: language.clone(),
2764 error: format!("Read error: {}", e),
2765 stderr_log_path: Some(stderr_log_path.clone()),
2766 });
2767 }
2768 break;
2769 }
2770 }
2771 }
2772 {
2775 let mut pending_guard = pending.lock().unwrap();
2776 let count = pending_guard.len();
2777 if count > 0 {
2778 tracing::info!(
2779 "LSP stdout reader: draining {} pending requests for {}",
2780 count,
2781 language
2782 );
2783 for (id, tx) in pending_guard.drain() {
2784 tracing::debug!(
2785 "LSP stdout reader: failing pending request id={} for {}",
2786 id,
2787 language
2788 );
2789 let _ = tx.send(Err(
2790 "LSP server connection closed while awaiting response".to_string(),
2791 ));
2792 }
2793 }
2794 }
2795
2796 tracing::info!("LSP stdout reader task exiting for {}", language);
2797 });
2798 }
2799
2800 #[allow(clippy::let_underscore_must_use)]
2804 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
2805 tracing::info!("LspTask::run() started for language: {}", self.language);
2806
2807 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
2809
2810 let state = LspState {
2812 stdin: stdin_writer.clone(),
2813 next_id: Arc::new(AtomicI64::new(self.next_id)),
2814 capabilities: Arc::new(Mutex::new(self.capabilities)),
2815 document_versions: self.document_versions.clone(),
2816 pending_opens: Arc::new(Mutex::new(self.pending_opens)),
2817 initialized: Arc::new(AtomicBool::new(self.initialized)),
2818 async_tx: self.async_tx.clone(),
2819 language: Arc::new(self.language.clone()),
2820 server_name: Arc::new(self.server_name.clone()),
2821 active_requests: Arc::new(Mutex::new(HashMap::new())),
2822 language_id_overrides: Arc::new(self.language_id_overrides.clone()),
2823 };
2824
2825 let pending = Arc::new(Mutex::new(self.pending));
2826 let async_tx = state.async_tx.clone();
2827 let language_clone: String = (*state.language).clone();
2828 let server_name: String = (*state.server_name).clone();
2829
2830 let shutting_down = Arc::new(AtomicBool::new(false));
2832
2833 Self::spawn_stdout_reader(
2835 self.stdout,
2836 pending.clone(),
2837 async_tx.clone(),
2838 language_clone.clone(),
2839 self.server_name.clone(),
2840 self.server_command.clone(),
2841 stdin_writer.clone(),
2842 self.stderr_log_path,
2843 shutting_down.clone(),
2844 self.document_versions.clone(),
2845 );
2846
2847 macro_rules! await_draining {
2877 ($fut:expr, $command_rx:expr, $buf:expr) => {{
2878 let fut = $fut;
2879 tokio::pin!(fut);
2880 loop {
2881 tokio::select! {
2882 biased; result = &mut fut => break result,
2884 Some(cmd) = $command_rx.recv() => {
2885 $buf.push_back(cmd);
2886 }
2887 }
2888 }
2889 }};
2890 }
2891
2892 macro_rules! spawn_request {
2894 ($state:expr, $pending:expr, |$s:ident, $p:ident| $body:expr) => {{
2895 let $s = $state.clone();
2896 let $p = $pending.clone();
2897 tokio::spawn(async move {
2898 let _ = $body;
2899 });
2900 }};
2901 }
2902
2903 let mut pending_commands = Vec::new();
2904 let mut draining_buffer: std::collections::VecDeque<LspCommand> =
2905 std::collections::VecDeque::new();
2906 loop {
2907 let cmd = if let Some(cmd) = draining_buffer.pop_front() {
2910 cmd
2911 } else {
2912 match command_rx.recv().await {
2913 Some(cmd) => cmd,
2914 None => {
2915 tracing::info!("Command channel closed");
2916 break;
2917 }
2918 }
2919 };
2920
2921 tracing::trace!("LspTask received command: {:?}", cmd);
2922 let initialized = state.initialized.load(Ordering::SeqCst);
2923 match cmd {
2924 LspCommand::Initialize {
2925 root_uri,
2926 initialization_options,
2927 response,
2928 } => {
2929 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2931 language: language_clone.clone(),
2932 server_name: server_name.clone(),
2933 status: LspServerStatus::Initializing,
2934 message: None,
2935 });
2936 tracing::info!("Processing Initialize command");
2937 let result = await_draining!(
2938 state.handle_initialize_sequential(
2939 root_uri,
2940 initialization_options,
2941 &pending
2942 ),
2943 command_rx,
2944 draining_buffer
2945 );
2946 let success = result.is_ok();
2947 let _ = response.send(result);
2948
2949 if success {
2951 let queued = std::mem::take(&mut pending_commands);
2952 await_draining!(
2953 state.replay_pending_commands(queued, &pending),
2954 command_rx,
2955 draining_buffer
2956 );
2957 }
2958 }
2959 LspCommand::DidOpen {
2960 uri,
2961 text,
2962 language_id,
2963 } => {
2964 if initialized {
2965 tracing::info!("Processing DidOpen for {}", uri.as_str());
2966 let _ = state
2967 .handle_did_open_sequential(uri, text, language_id, &pending)
2968 .await;
2969 } else {
2970 tracing::trace!(
2971 "Queueing DidOpen for {} until initialization completes",
2972 uri.as_str()
2973 );
2974 pending_commands.push(LspCommand::DidOpen {
2975 uri,
2976 text,
2977 language_id,
2978 });
2979 }
2980 }
2981 LspCommand::DidChange {
2982 uri,
2983 content_changes,
2984 } => {
2985 if initialized {
2986 tracing::trace!("Processing DidChange for {}", uri.as_str());
2987 let _ = state
2990 .handle_did_change_sequential(uri, content_changes, &pending)
2991 .await;
2992 } else {
2993 tracing::trace!(
2994 "Queueing DidChange for {} until initialization completes",
2995 uri.as_str()
2996 );
2997 pending_commands.push(LspCommand::DidChange {
2998 uri,
2999 content_changes,
3000 });
3001 }
3002 }
3003 LspCommand::DidClose { uri } => {
3004 if initialized {
3005 tracing::info!("Processing DidClose for {}", uri.as_str());
3006 let _ = state.handle_did_close(uri).await;
3007 } else {
3008 tracing::trace!(
3009 "Queueing DidClose for {} until initialization completes",
3010 uri.as_str()
3011 );
3012 pending_commands.push(LspCommand::DidClose { uri });
3013 }
3014 }
3015 LspCommand::DidSave { uri, text } => {
3016 if initialized {
3017 tracing::info!("Processing DidSave for {}", uri.as_str());
3018 let _ = state.handle_did_save(uri, text).await;
3019 } else {
3020 tracing::trace!(
3021 "Queueing DidSave for {} until initialization completes",
3022 uri.as_str()
3023 );
3024 pending_commands.push(LspCommand::DidSave { uri, text });
3025 }
3026 }
3027 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
3028 if initialized {
3029 tracing::info!(
3030 "Processing DidChangeWorkspaceFolders: +{} -{}",
3031 added.len(),
3032 removed.len()
3033 );
3034 let _ = state
3035 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
3036 lsp_types::DidChangeWorkspaceFoldersParams {
3037 event: lsp_types::WorkspaceFoldersChangeEvent {
3038 added,
3039 removed,
3040 },
3041 },
3042 )
3043 .await;
3044 } else {
3045 tracing::trace!(
3046 "Queueing DidChangeWorkspaceFolders until initialization completes"
3047 );
3048 pending_commands
3049 .push(LspCommand::DidChangeWorkspaceFolders { added, removed });
3050 }
3051 }
3052 LspCommand::Completion {
3053 request_id,
3054 uri,
3055 line,
3056 character,
3057 } => {
3058 if initialized {
3059 tracing::info!("Processing Completion request for {}", uri.as_str());
3060 spawn_request!(state, pending, |s, p| s
3061 .handle_completion(request_id, uri, line, character, &p)
3062 .await);
3063 } else {
3064 tracing::trace!("LSP not initialized, sending empty completion");
3065 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
3066 request_id,
3067 items: vec![],
3068 });
3069 }
3070 }
3071 LspCommand::GotoDefinition {
3072 request_id,
3073 uri,
3074 line,
3075 character,
3076 } => {
3077 if initialized {
3078 tracing::info!("Processing GotoDefinition request for {}", uri.as_str());
3079 spawn_request!(state, pending, |s, p| s
3080 .handle_goto_definition(request_id, uri, line, character, &p)
3081 .await);
3082 } else {
3083 tracing::trace!("LSP not initialized, sending empty locations");
3084 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
3085 request_id,
3086 locations: vec![],
3087 });
3088 }
3089 }
3090 LspCommand::Rename {
3091 request_id,
3092 uri,
3093 line,
3094 character,
3095 new_name,
3096 } => {
3097 if initialized {
3098 tracing::info!("Processing Rename request for {}", uri.as_str());
3099 spawn_request!(state, pending, |s, p| s
3100 .handle_rename(request_id, uri, line, character, new_name, &p)
3101 .await);
3102 } else {
3103 tracing::trace!("LSP not initialized, cannot rename");
3104 let _ = state.async_tx.send(AsyncMessage::LspRename {
3105 request_id,
3106 result: Err("LSP not initialized".to_string()),
3107 });
3108 }
3109 }
3110 LspCommand::Hover {
3111 request_id,
3112 uri,
3113 line,
3114 character,
3115 } => {
3116 if initialized {
3117 tracing::info!("Processing Hover request for {}", uri.as_str());
3118 spawn_request!(state, pending, |s, p| s
3119 .handle_hover(request_id, uri, line, character, &p)
3120 .await);
3121 } else {
3122 tracing::trace!("LSP not initialized, cannot get hover");
3123 let _ = state.async_tx.send(AsyncMessage::LspHover {
3124 request_id,
3125 contents: String::new(),
3126 is_markdown: false,
3127 range: None,
3128 });
3129 }
3130 }
3131 LspCommand::References {
3132 request_id,
3133 uri,
3134 line,
3135 character,
3136 } => {
3137 if initialized {
3138 tracing::info!("Processing References request for {}", uri.as_str());
3139 spawn_request!(state, pending, |s, p| s
3140 .handle_references(request_id, uri, line, character, &p)
3141 .await);
3142 } else {
3143 tracing::trace!("LSP not initialized, cannot get references");
3144 let _ = state.async_tx.send(AsyncMessage::LspReferences {
3145 request_id,
3146 locations: Vec::new(),
3147 });
3148 }
3149 }
3150 LspCommand::SignatureHelp {
3151 request_id,
3152 uri,
3153 line,
3154 character,
3155 } => {
3156 if initialized {
3157 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
3158 spawn_request!(state, pending, |s, p| s
3159 .handle_signature_help(request_id, uri, line, character, &p)
3160 .await);
3161 } else {
3162 tracing::trace!("LSP not initialized, cannot get signature help");
3163 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
3164 request_id,
3165 signature_help: None,
3166 });
3167 }
3168 }
3169 LspCommand::CodeActions {
3170 request_id,
3171 uri,
3172 start_line,
3173 start_char,
3174 end_line,
3175 end_char,
3176 diagnostics,
3177 } => {
3178 if initialized {
3179 tracing::info!("Processing CodeActions request for {}", uri.as_str());
3180 spawn_request!(state, pending, |s, p| s
3181 .handle_code_actions(
3182 request_id,
3183 uri,
3184 start_line,
3185 start_char,
3186 end_line,
3187 end_char,
3188 diagnostics,
3189 &p,
3190 )
3191 .await);
3192 } else {
3193 tracing::trace!("LSP not initialized, cannot get code actions");
3194 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
3195 request_id,
3196 actions: Vec::new(),
3197 });
3198 }
3199 }
3200 LspCommand::DocumentDiagnostic {
3201 request_id,
3202 uri,
3203 previous_result_id,
3204 } => {
3205 if initialized {
3206 tracing::info!(
3207 "Processing DocumentDiagnostic request for {}",
3208 uri.as_str()
3209 );
3210 spawn_request!(state, pending, |s, p| s
3211 .handle_document_diagnostic(request_id, uri, previous_result_id, &p)
3212 .await);
3213 } else {
3214 tracing::trace!("LSP not initialized, cannot get document diagnostics");
3215 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
3216 request_id,
3217 uri: uri.as_str().to_string(),
3218 result_id: None,
3219 diagnostics: Vec::new(),
3220 unchanged: false,
3221 });
3222 }
3223 }
3224 LspCommand::InlayHints {
3225 request_id,
3226 uri,
3227 start_line,
3228 start_char,
3229 end_line,
3230 end_char,
3231 } => {
3232 if initialized {
3233 tracing::info!("Processing InlayHints request for {}", uri.as_str());
3234 spawn_request!(state, pending, |s, p| s
3235 .handle_inlay_hints(
3236 request_id, uri, start_line, start_char, end_line, end_char, &p,
3237 )
3238 .await);
3239 } else {
3240 tracing::trace!("LSP not initialized, cannot get inlay hints");
3241 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
3242 request_id,
3243 uri: uri.as_str().to_string(),
3244 hints: Vec::new(),
3245 });
3246 }
3247 }
3248 LspCommand::FoldingRange { request_id, uri } => {
3249 if initialized {
3250 tracing::info!("Processing FoldingRange request for {}", uri.as_str());
3251 spawn_request!(state, pending, |s, p| s
3252 .handle_folding_ranges(request_id, uri, &p)
3253 .await);
3254 } else {
3255 tracing::trace!("LSP not initialized, cannot get folding ranges");
3256 let _ = state.async_tx.send(AsyncMessage::LspFoldingRanges {
3257 request_id,
3258 uri: uri.as_str().to_string(),
3259 ranges: Vec::new(),
3260 });
3261 }
3262 }
3263 LspCommand::SemanticTokensFull { request_id, uri } => {
3264 if initialized {
3265 tracing::info!("Processing SemanticTokens request for {}", uri.as_str());
3266 spawn_request!(state, pending, |s, p| s
3267 .handle_semantic_tokens_full(request_id, uri, &p)
3268 .await);
3269 } else {
3270 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3271 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3272 request_id,
3273 uri: uri.as_str().to_string(),
3274 response: LspSemanticTokensResponse::Full(Err(
3275 "LSP not initialized".to_string()
3276 )),
3277 });
3278 }
3279 }
3280 LspCommand::SemanticTokensFullDelta {
3281 request_id,
3282 uri,
3283 previous_result_id,
3284 } => {
3285 if initialized {
3286 tracing::info!(
3287 "Processing SemanticTokens delta request for {}",
3288 uri.as_str()
3289 );
3290 spawn_request!(state, pending, |s, p| s
3291 .handle_semantic_tokens_full_delta(
3292 request_id,
3293 uri,
3294 previous_result_id,
3295 &p,
3296 )
3297 .await);
3298 } else {
3299 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3300 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3301 request_id,
3302 uri: uri.as_str().to_string(),
3303 response: LspSemanticTokensResponse::FullDelta(Err(
3304 "LSP not initialized".to_string(),
3305 )),
3306 });
3307 }
3308 }
3309 LspCommand::SemanticTokensRange {
3310 request_id,
3311 uri,
3312 range,
3313 } => {
3314 if initialized {
3315 tracing::info!(
3316 "Processing SemanticTokens range request for {}",
3317 uri.as_str()
3318 );
3319 spawn_request!(state, pending, |s, p| s
3320 .handle_semantic_tokens_range(request_id, uri, range, &p)
3321 .await);
3322 } else {
3323 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3324 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3325 request_id,
3326 uri: uri.as_str().to_string(),
3327 response: LspSemanticTokensResponse::Range(Err(
3328 "LSP not initialized".to_string()
3329 )),
3330 });
3331 }
3332 }
3333 LspCommand::ExecuteCommand { command, arguments } => {
3334 if initialized {
3335 tracing::info!("Processing ExecuteCommand: {}", command);
3336 spawn_request!(state, pending, |s, p| s
3337 .handle_execute_command(command, arguments, &p)
3338 .await);
3339 } else {
3340 tracing::trace!("LSP not initialized, cannot execute command");
3341 }
3342 }
3343 LspCommand::CodeActionResolve { request_id, action } => {
3344 if initialized {
3345 tracing::info!("Processing CodeActionResolve (request_id={})", request_id);
3346 spawn_request!(state, pending, |s, p| s
3347 .handle_code_action_resolve(request_id, *action, &p)
3348 .await);
3349 } else {
3350 tracing::trace!("LSP not initialized, cannot resolve code action");
3351 let _ = state.async_tx.send(AsyncMessage::LspCodeActionResolved {
3352 request_id,
3353 action: Err("LSP not initialized".to_string()),
3354 });
3355 }
3356 }
3357 LspCommand::CompletionResolve { request_id, item } => {
3358 if initialized {
3359 spawn_request!(state, pending, |s, p| s
3360 .handle_completion_resolve(request_id, *item, &p)
3361 .await);
3362 }
3363 }
3364 LspCommand::DocumentFormatting {
3365 request_id,
3366 uri,
3367 tab_size,
3368 insert_spaces,
3369 } => {
3370 if initialized {
3371 tracing::info!("Processing DocumentFormatting for {}", uri.as_str());
3372 spawn_request!(state, pending, |s, p| s
3373 .handle_document_formatting(
3374 request_id,
3375 uri,
3376 tab_size,
3377 insert_spaces,
3378 &p,
3379 )
3380 .await);
3381 }
3382 }
3383 LspCommand::DocumentRangeFormatting {
3384 request_id,
3385 uri,
3386 start_line,
3387 start_char,
3388 end_line,
3389 end_char,
3390 tab_size,
3391 insert_spaces,
3392 } => {
3393 if initialized {
3394 spawn_request!(state, pending, |s, p| s
3395 .handle_document_range_formatting(
3396 request_id,
3397 uri,
3398 start_line,
3399 start_char,
3400 end_line,
3401 end_char,
3402 tab_size,
3403 insert_spaces,
3404 &p,
3405 )
3406 .await);
3407 }
3408 }
3409 LspCommand::PrepareRename {
3410 request_id,
3411 uri,
3412 line,
3413 character,
3414 } => {
3415 if initialized {
3416 spawn_request!(state, pending, |s, p| s
3417 .handle_prepare_rename(request_id, uri, line, character, &p)
3418 .await);
3419 }
3420 }
3421 LspCommand::CancelRequest { request_id } => {
3422 tracing::info!("Processing CancelRequest for editor_id={}", request_id);
3423 let _ = state.handle_cancel_request(request_id).await;
3425 }
3426 LspCommand::PluginRequest {
3427 request_id,
3428 method,
3429 params,
3430 } => {
3431 if initialized {
3432 tracing::trace!("Processing plugin request {} ({})", request_id, method);
3433 spawn_request!(state, pending, |s, p| s
3434 .handle_plugin_request(request_id, method, params, &p)
3435 .await);
3436 } else {
3437 tracing::trace!(
3438 "Plugin LSP request {} received before initialization",
3439 request_id
3440 );
3441 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
3442 language: language_clone.clone(),
3443 request_id,
3444 result: Err("LSP not initialized".to_string()),
3445 });
3446 }
3447 }
3448 LspCommand::Shutdown => {
3449 tracing::info!("Processing Shutdown command");
3450 shutting_down.store(true, Ordering::SeqCst);
3452 let _ = state.handle_shutdown().await;
3453 break;
3454 }
3455 }
3456 }
3457
3458 tracing::info!("LSP task exiting for language: {}", self.language);
3459 }
3460}
3461
3462async fn read_message_from_stdout(
3464 stdout: &mut BufReader<ChildStdout>,
3465) -> Result<JsonRpcMessage, String> {
3466 let mut content_length: Option<usize> = None;
3468
3469 loop {
3470 let mut line = String::new();
3471 let bytes_read = stdout
3472 .read_line(&mut line)
3473 .await
3474 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
3475
3476 if bytes_read == 0 {
3478 return Err("LSP server closed stdout (EOF)".to_string());
3479 }
3480
3481 if line == "\r\n" {
3482 break;
3483 }
3484
3485 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
3486 content_length = Some(
3487 len_str
3488 .trim()
3489 .parse()
3490 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
3491 );
3492 }
3493 }
3494
3495 let content_length =
3496 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
3497
3498 let mut content = vec![0u8; content_length];
3500 stdout
3501 .read_exact(&mut content)
3502 .await
3503 .map_err(|e| format!("Failed to read content: {}", e))?;
3504
3505 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
3506
3507 tracing::trace!("Received LSP message: {}", json);
3508
3509 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
3510}
3511
3512#[allow(clippy::too_many_arguments)]
3514#[allow(clippy::let_underscore_must_use)] async fn handle_message_dispatch(
3516 message: JsonRpcMessage,
3517 pending: &PendingRequests,
3518 async_tx: &std_mpsc::Sender<AsyncMessage>,
3519 language: &str,
3520 server_name: &str,
3521 server_command: &str,
3522 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
3523 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3524) -> Result<(), String> {
3525 match message {
3526 JsonRpcMessage::Response(response) => {
3527 tracing::trace!("Received LSP response for request id={}", response.id);
3528 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
3529 let result = if let Some(error) = response.error {
3530 log_response_error(error.code, &error.message, server_name, language);
3531 Err(format!(
3532 "LSP error from '{}' ({}): {} (code {})",
3533 server_name, language, error.message, error.code
3534 ))
3535 } else {
3536 tracing::trace!(
3537 "LSP response success from '{}' ({}) for request id={}",
3538 server_name,
3539 language,
3540 response.id
3541 );
3542 Ok(response.result.unwrap_or(serde_json::Value::Null))
3544 };
3545 let _ = tx.send(result);
3546 } else {
3547 tracing::warn!(
3548 "Received LSP response from '{}' ({}) for unknown request id={}",
3549 server_name,
3550 language,
3551 response.id
3552 );
3553 }
3554 }
3555 JsonRpcMessage::Notification(notification) => {
3556 tracing::trace!("Received LSP notification: {}", notification.method);
3557 handle_notification_dispatch(
3558 notification,
3559 async_tx,
3560 language,
3561 server_name,
3562 document_versions,
3563 )
3564 .await?;
3565 }
3566 JsonRpcMessage::Request(request) => {
3567 tracing::trace!("Received request from server: {}", request.method);
3569 let response = match request.method.as_str() {
3570 "window/workDoneProgress/create" => {
3571 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
3573 JsonRpcResponse {
3574 jsonrpc: "2.0".to_string(),
3575 id: request.id,
3576 result: Some(Value::Null),
3577 error: None,
3578 }
3579 }
3580 "workspace/configuration" => {
3581 tracing::trace!(
3585 "Responding to workspace/configuration with inlay hints enabled"
3586 );
3587
3588 let num_items = request
3590 .params
3591 .as_ref()
3592 .and_then(|p| p.get("items"))
3593 .and_then(|items| items.as_array())
3594 .map(|arr| arr.len())
3595 .unwrap_or(1);
3596
3597 let ra_config = serde_json::json!({
3599 "inlayHints": {
3600 "typeHints": {
3601 "enable": true
3602 },
3603 "parameterHints": {
3604 "enable": true
3605 },
3606 "chainingHints": {
3607 "enable": true
3608 },
3609 "closureReturnTypeHints": {
3610 "enable": "always"
3611 }
3612 }
3613 });
3614
3615 let configs: Vec<Value> = (0..num_items).map(|_| ra_config.clone()).collect();
3617
3618 JsonRpcResponse {
3619 jsonrpc: "2.0".to_string(),
3620 id: request.id,
3621 result: Some(Value::Array(configs)),
3622 error: None,
3623 }
3624 }
3625 "client/registerCapability" => {
3626 tracing::trace!(
3628 "Acknowledging client/registerCapability (id={})",
3629 request.id
3630 );
3631 JsonRpcResponse {
3632 jsonrpc: "2.0".to_string(),
3633 id: request.id,
3634 result: Some(Value::Null),
3635 error: None,
3636 }
3637 }
3638 "workspace/diagnostic/refresh" => {
3639 tracing::info!(
3642 "LSP ({}) requested diagnostic refresh (workspace/diagnostic/refresh)",
3643 language
3644 );
3645 let _ = async_tx.send(AsyncMessage::LspDiagnosticRefresh {
3646 language: language.to_string(),
3647 });
3648 JsonRpcResponse {
3649 jsonrpc: "2.0".to_string(),
3650 id: request.id,
3651 result: Some(Value::Null),
3652 error: None,
3653 }
3654 }
3655 "workspace/applyEdit" => {
3656 tracing::info!("LSP ({}) received workspace/applyEdit request", language);
3658 let applied = if let Some(params) = &request.params {
3659 match serde_json::from_value::<lsp_types::ApplyWorkspaceEditParams>(
3660 params.clone(),
3661 ) {
3662 Ok(apply_params) => {
3663 let label = apply_params.label.clone();
3664 let _ = async_tx.send(AsyncMessage::LspApplyEdit {
3665 edit: apply_params.edit,
3666 label,
3667 });
3668 true
3669 }
3670 Err(e) => {
3671 tracing::error!(
3672 "Failed to parse workspace/applyEdit params: {}",
3673 e
3674 );
3675 false
3676 }
3677 }
3678 } else {
3679 false
3680 };
3681 JsonRpcResponse {
3682 jsonrpc: "2.0".to_string(),
3683 id: request.id,
3684 result: Some(serde_json::json!({ "applied": applied })),
3685 error: None,
3686 }
3687 }
3688 _ => {
3689 tracing::debug!("Server request for plugins: {}", request.method);
3691 let _ = async_tx.send(AsyncMessage::LspServerRequest {
3692 language: language.to_string(),
3693 server_command: server_command.to_string(),
3694 method: request.method.clone(),
3695 params: request.params.clone(),
3696 });
3697 JsonRpcResponse {
3698 jsonrpc: "2.0".to_string(),
3699 id: request.id,
3700 result: Some(Value::Null),
3701 error: None,
3702 }
3703 }
3704 };
3705
3706 let json = serde_json::to_string(&response)
3708 .map_err(|e| format!("Failed to serialize response: {}", e))?;
3709 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
3710
3711 let mut stdin = stdin_writer.lock().await;
3712 use tokio::io::AsyncWriteExt;
3713 if let Err(e) = stdin.write_all(message.as_bytes()).await {
3714 tracing::error!("Failed to write server response: {}", e);
3715 }
3716 if let Err(e) = stdin.flush().await {
3717 tracing::error!("Failed to flush server response: {}", e);
3718 }
3719 tracing::trace!("Sent response to server request id={}", response.id);
3720 }
3721 }
3722 Ok(())
3723}
3724
3725#[allow(clippy::let_underscore_must_use)] async fn handle_notification_dispatch(
3728 notification: JsonRpcNotification,
3729 async_tx: &std_mpsc::Sender<AsyncMessage>,
3730 language: &str,
3731 server_name: &str,
3732 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3733) -> Result<(), String> {
3734 match notification.method.as_str() {
3735 PublishDiagnostics::METHOD => {
3736 if let Some(params) = notification.params {
3737 let params: PublishDiagnosticsParams = serde_json::from_value(params)
3738 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
3739
3740 if let Some(diag_version) = params.version {
3744 let path = PathBuf::from(params.uri.path().as_str());
3745 let current_version = document_versions.lock().unwrap().get(&path).copied();
3746 if let Some(current) = current_version {
3747 if (diag_version as i64) < current {
3748 tracing::debug!(
3749 "LSP ({}): dropping stale diagnostics for {} (diag version {} < current {})",
3750 language,
3751 params.uri.as_str(),
3752 diag_version,
3753 current
3754 );
3755 return Ok(());
3756 }
3757 }
3758 }
3759
3760 tracing::trace!(
3761 "Received {} diagnostics for {}",
3762 params.diagnostics.len(),
3763 params.uri.as_str()
3764 );
3765
3766 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
3768 uri: params.uri.to_string(),
3769 diagnostics: params.diagnostics,
3770 server_name: server_name.to_string(),
3771 });
3772 }
3773 }
3774 "window/showMessage" => {
3775 if let Some(params) = notification.params {
3776 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3777 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(3);
3778 let message = msg
3779 .get("message")
3780 .and_then(|v| v.as_str())
3781 .unwrap_or("(no message)")
3782 .to_string();
3783
3784 let message_type = match message_type_num {
3785 1 => LspMessageType::Error,
3786 2 => LspMessageType::Warning,
3787 3 => LspMessageType::Info,
3788 _ => LspMessageType::Log,
3789 };
3790
3791 match message_type {
3793 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3794 LspMessageType::Warning => {
3795 tracing::warn!("LSP ({}): {}", language, message)
3796 }
3797 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3798 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3799 }
3800
3801 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
3803 language: language.to_string(),
3804 message_type,
3805 message,
3806 });
3807 }
3808 }
3809 }
3810 "window/logMessage" => {
3811 if let Some(params) = notification.params {
3812 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3813 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(4);
3814 let message = msg
3815 .get("message")
3816 .and_then(|v| v.as_str())
3817 .unwrap_or("(no message)")
3818 .to_string();
3819
3820 let message_type = match message_type_num {
3821 1 => LspMessageType::Error,
3822 2 => LspMessageType::Warning,
3823 3 => LspMessageType::Info,
3824 _ => LspMessageType::Log,
3825 };
3826
3827 match message_type {
3829 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3830 LspMessageType::Warning => {
3831 tracing::warn!("LSP ({}): {}", language, message)
3832 }
3833 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3834 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3835 }
3836
3837 let _ = async_tx.send(AsyncMessage::LspLogMessage {
3839 language: language.to_string(),
3840 message_type,
3841 message,
3842 });
3843 }
3844 }
3845 }
3846 "$/progress" => {
3847 if let Some(params) = notification.params {
3848 if let Ok(progress) =
3849 serde_json::from_value::<serde_json::Map<String, Value>>(params)
3850 {
3851 let token = progress
3852 .get("token")
3853 .and_then(|v| {
3854 v.as_str()
3855 .map(|s| s.to_string())
3856 .or_else(|| v.as_i64().map(|n| n.to_string()))
3857 })
3858 .unwrap_or_else(|| "unknown".to_string());
3859
3860 if let Some(value_obj) = progress.get("value").and_then(|v| v.as_object()) {
3861 let kind = value_obj.get("kind").and_then(|v| v.as_str());
3862
3863 let value = match kind {
3864 Some("begin") => {
3865 let title = value_obj
3866 .get("title")
3867 .and_then(|v| v.as_str())
3868 .unwrap_or("Working...")
3869 .to_string();
3870 let message = value_obj
3871 .get("message")
3872 .and_then(|v| v.as_str())
3873 .map(|s| s.to_string());
3874 let percentage = value_obj
3875 .get("percentage")
3876 .and_then(|v| v.as_u64())
3877 .map(|p| p as u32);
3878
3879 tracing::info!(
3880 "LSP ({}) progress begin: {} {:?} {:?}",
3881 language,
3882 title,
3883 message,
3884 percentage
3885 );
3886
3887 Some(LspProgressValue::Begin {
3888 title,
3889 message,
3890 percentage,
3891 })
3892 }
3893 Some("report") => {
3894 let message = value_obj
3895 .get("message")
3896 .and_then(|v| v.as_str())
3897 .map(|s| s.to_string());
3898 let percentage = value_obj
3899 .get("percentage")
3900 .and_then(|v| v.as_u64())
3901 .map(|p| p as u32);
3902
3903 tracing::trace!(
3904 "LSP ({}) progress report: {:?} {:?}",
3905 language,
3906 message,
3907 percentage
3908 );
3909
3910 Some(LspProgressValue::Report {
3911 message,
3912 percentage,
3913 })
3914 }
3915 Some("end") => {
3916 let message = value_obj
3917 .get("message")
3918 .and_then(|v| v.as_str())
3919 .map(|s| s.to_string());
3920
3921 tracing::info!("LSP ({}) progress end: {:?}", language, message);
3922
3923 Some(LspProgressValue::End { message })
3924 }
3925 _ => None,
3926 };
3927
3928 if let Some(value) = value {
3929 let _ = async_tx.send(AsyncMessage::LspProgress {
3930 language: language.to_string(),
3931 token,
3932 value,
3933 });
3934 }
3935 }
3936 }
3937 }
3938 }
3939 "experimental/serverStatus" => {
3940 if let Some(params) = notification.params {
3943 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
3944 {
3945 let quiescent = status
3946 .get("quiescent")
3947 .and_then(|v| v.as_bool())
3948 .unwrap_or(false);
3949
3950 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
3951
3952 if quiescent {
3953 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
3955 language: language.to_string(),
3956 });
3957 }
3958 }
3959 }
3960 }
3961 _ => {
3962 tracing::debug!("Unhandled notification: {}", notification.method);
3963 }
3964 }
3965
3966 Ok(())
3967}
3968
3969static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
3971
3972pub struct LspHandle {
3974 id: u64,
3976
3977 scope: crate::services::lsp::manager::LanguageScope,
3979
3980 command_tx: mpsc::Sender<LspCommand>,
3982
3983 state: Arc<Mutex<LspClientState>>,
3985
3986 runtime: tokio::runtime::Handle,
3988
3989 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3992}
3993
3994#[allow(clippy::let_underscore_must_use)]
3998impl LspHandle {
3999 #[allow(clippy::too_many_arguments)]
4008 pub fn spawn(
4009 runtime: &tokio::runtime::Handle,
4010 command: &str,
4011 args: &[String],
4012 env: std::collections::HashMap<String, String>,
4013 scope: crate::services::lsp::manager::LanguageScope,
4014 server_name: String,
4015 async_bridge: &AsyncBridge,
4016 process_limits: ProcessLimits,
4017 language_id_overrides: std::collections::HashMap<String, String>,
4018 long_running_spawner: Arc<dyn crate::services::remote::LongRunningSpawner>,
4019 ) -> Result<Self, String> {
4020 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
4022 let language_label = scope.label().to_string();
4023 let language_clone = language_label.clone();
4024 let server_name_clone = server_name.clone();
4025 let command = command.to_string();
4026 let args = args.to_vec();
4027 let state = Arc::new(Mutex::new(LspClientState::Starting));
4028
4029 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language_label);
4031
4032 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4034 language: language_label.clone(),
4035 server_name: server_name_clone.clone(),
4036 status: LspServerStatus::Starting,
4037 message: None,
4038 });
4039
4040 let document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>> =
4044 Arc::new(std::sync::Mutex::new(HashMap::new()));
4045 let document_versions_for_task = document_versions.clone();
4046
4047 let state_clone = state.clone();
4048 let stderr_log_path_clone = stderr_log_path.clone();
4049 runtime.spawn(async move {
4050 match LspTask::spawn(
4051 &command,
4052 &args,
4053 &env,
4054 language_clone.clone(),
4055 server_name_clone.clone(),
4056 async_tx.clone(),
4057 &process_limits,
4058 stderr_log_path_clone.clone(),
4059 language_id_overrides,
4060 document_versions_for_task,
4061 long_running_spawner,
4062 )
4063 .await
4064 {
4065 Ok(task) => {
4066 task.run(command_rx).await;
4067 }
4068 Err(e) => {
4069 tracing::error!("Failed to spawn LSP task: {}", e);
4070
4071 let stub = format!(
4086 "[fresh] LSP server '{}' for {} failed to spawn:\n {}\n\n\
4087 Configured command: {} {}\n",
4088 server_name_clone,
4089 language_clone,
4090 e,
4091 command,
4092 args.join(" "),
4093 );
4094 if let Err(write_err) = std::fs::write(&stderr_log_path_clone, stub.as_bytes())
4095 {
4096 tracing::warn!(
4097 "Failed to write LSP failure-stub log for {}: {}",
4098 language_clone,
4099 write_err,
4100 );
4101 }
4102
4103 if let Ok(mut s) = state_clone.lock() {
4105 let _ = s.transition_to(LspClientState::Error);
4106 }
4107
4108 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4109 language: language_clone.clone(),
4110 server_name: server_name_clone.clone(),
4111 status: LspServerStatus::Error,
4112 message: None,
4113 });
4114 let _ = async_tx.send(AsyncMessage::LspError {
4115 language: language_clone,
4116 error: e,
4117 stderr_log_path: Some(stderr_log_path_clone),
4118 });
4119 }
4120 }
4121 });
4122
4123 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4124
4125 Ok(Self {
4126 id,
4127 scope,
4128 command_tx,
4129 state,
4130 runtime: runtime.clone(),
4131 document_versions,
4132 })
4133 }
4134
4135 pub fn id(&self) -> u64 {
4137 self.id
4138 }
4139
4140 pub fn scope(&self) -> &crate::services::lsp::manager::LanguageScope {
4142 &self.scope
4143 }
4144
4145 pub fn document_version(&self, path: &std::path::Path) -> Option<i64> {
4148 self.document_versions
4149 .lock()
4150 .ok()
4151 .and_then(|versions| versions.get(path).copied())
4152 }
4153
4154 pub fn initialize(
4163 &self,
4164 root_uri: Option<Uri>,
4165 initialization_options: Option<Value>,
4166 ) -> Result<(), String> {
4167 {
4169 let mut state = self.state.lock().unwrap();
4170 if !state.can_initialize() {
4171 return Err(format!(
4172 "Cannot initialize: client is in state {:?}",
4173 *state
4174 ));
4175 }
4176 state.transition_to(LspClientState::Initializing)?;
4178 }
4179
4180 let state = self.state.clone();
4181
4182 let (tx, rx) = oneshot::channel();
4184
4185 self.command_tx
4186 .try_send(LspCommand::Initialize {
4187 root_uri,
4188 initialization_options,
4189 response: tx,
4190 })
4191 .map_err(|_| "Failed to send initialize command".to_string())?;
4192
4193 let runtime = self.runtime.clone();
4195 runtime.spawn(async move {
4196 match tokio::time::timeout(std::time::Duration::from_secs(60), rx).await {
4197 Ok(Ok(Ok(_))) => {
4198 if let Ok(mut s) = state.lock() {
4200 let _ = s.transition_to(LspClientState::Running);
4201 }
4202 tracing::info!("LSP initialization completed successfully");
4203 }
4204 Ok(Ok(Err(e))) => {
4205 tracing::error!("LSP initialization failed: {}", e);
4206 if let Ok(mut s) = state.lock() {
4207 let _ = s.transition_to(LspClientState::Error);
4208 }
4209 }
4210 Ok(Err(_)) => {
4211 tracing::error!("LSP initialization response channel closed");
4212 if let Ok(mut s) = state.lock() {
4213 let _ = s.transition_to(LspClientState::Error);
4214 }
4215 }
4216 Err(_) => {
4217 tracing::error!("LSP initialization timed out after 60 seconds");
4218 if let Ok(mut s) = state.lock() {
4219 let _ = s.transition_to(LspClientState::Error);
4220 }
4221 }
4222 }
4223 });
4224
4225 Ok(())
4226 }
4227
4228 pub fn is_initialized(&self) -> bool {
4230 self.state.lock().unwrap().can_send_requests()
4231 }
4232
4233 pub fn state(&self) -> LspClientState {
4235 *self.state.lock().unwrap()
4236 }
4237
4238 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
4244 if !self.scope.accepts(&language_id) {
4246 tracing::warn!(
4247 "did_open: document language '{}' not accepted by LSP handle (serves {:?}) for {}",
4248 language_id,
4249 self.scope,
4250 uri.as_str()
4251 );
4252 return Err(format!(
4253 "Language mismatch: document is '{}' but LSP serves {:?}",
4254 language_id, self.scope
4255 ));
4256 }
4257
4258 self.command_tx
4260 .try_send(LspCommand::DidOpen {
4261 uri,
4262 text,
4263 language_id,
4264 })
4265 .map_err(|_| "Failed to send did_open command".to_string())
4266 }
4267
4268 pub fn did_change(
4270 &self,
4271 uri: Uri,
4272 content_changes: Vec<TextDocumentContentChangeEvent>,
4273 ) -> Result<(), String> {
4274 self.command_tx
4276 .try_send(LspCommand::DidChange {
4277 uri,
4278 content_changes,
4279 })
4280 .map_err(|_| "Failed to send did_change command".to_string())
4281 }
4282
4283 pub fn did_close(&self, uri: Uri) -> Result<(), String> {
4285 self.command_tx
4286 .try_send(LspCommand::DidClose { uri })
4287 .map_err(|_| "Failed to send did_close command".to_string())
4288 }
4289
4290 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
4292 self.command_tx
4293 .try_send(LspCommand::DidSave { uri, text })
4294 .map_err(|_| "Failed to send did_save command".to_string())
4295 }
4296
4297 pub fn add_workspace_folder(&self, uri: lsp_types::Uri, name: String) -> Result<(), String> {
4299 self.command_tx
4300 .try_send(LspCommand::DidChangeWorkspaceFolders {
4301 added: vec![lsp_types::WorkspaceFolder { uri, name }],
4302 removed: vec![],
4303 })
4304 .map_err(|_| "Failed to send workspace folder change".to_string())
4305 }
4306
4307 pub fn completion(
4309 &self,
4310 request_id: u64,
4311 uri: Uri,
4312 line: u32,
4313 character: u32,
4314 ) -> Result<(), String> {
4315 self.command_tx
4316 .try_send(LspCommand::Completion {
4317 request_id,
4318 uri,
4319 line,
4320 character,
4321 })
4322 .map_err(|_| "Failed to send completion command".to_string())
4323 }
4324
4325 pub fn goto_definition(
4327 &self,
4328 request_id: u64,
4329 uri: Uri,
4330 line: u32,
4331 character: u32,
4332 ) -> Result<(), String> {
4333 self.command_tx
4334 .try_send(LspCommand::GotoDefinition {
4335 request_id,
4336 uri,
4337 line,
4338 character,
4339 })
4340 .map_err(|_| "Failed to send goto_definition command".to_string())
4341 }
4342
4343 pub fn rename(
4345 &self,
4346 request_id: u64,
4347 uri: Uri,
4348 line: u32,
4349 character: u32,
4350 new_name: String,
4351 ) -> Result<(), String> {
4352 self.command_tx
4353 .try_send(LspCommand::Rename {
4354 request_id,
4355 uri,
4356 line,
4357 character,
4358 new_name,
4359 })
4360 .map_err(|_| "Failed to send rename command".to_string())
4361 }
4362
4363 pub fn hover(
4365 &self,
4366 request_id: u64,
4367 uri: Uri,
4368 line: u32,
4369 character: u32,
4370 ) -> Result<(), String> {
4371 self.command_tx
4372 .try_send(LspCommand::Hover {
4373 request_id,
4374 uri,
4375 line,
4376 character,
4377 })
4378 .map_err(|_| "Failed to send hover command".to_string())
4379 }
4380
4381 pub fn references(
4383 &self,
4384 request_id: u64,
4385 uri: Uri,
4386 line: u32,
4387 character: u32,
4388 ) -> Result<(), String> {
4389 self.command_tx
4390 .try_send(LspCommand::References {
4391 request_id,
4392 uri,
4393 line,
4394 character,
4395 })
4396 .map_err(|_| "Failed to send references command".to_string())
4397 }
4398
4399 pub fn signature_help(
4401 &self,
4402 request_id: u64,
4403 uri: Uri,
4404 line: u32,
4405 character: u32,
4406 ) -> Result<(), String> {
4407 self.command_tx
4408 .try_send(LspCommand::SignatureHelp {
4409 request_id,
4410 uri,
4411 line,
4412 character,
4413 })
4414 .map_err(|_| "Failed to send signature_help command".to_string())
4415 }
4416
4417 #[allow(clippy::too_many_arguments)]
4419 pub fn code_actions(
4420 &self,
4421 request_id: u64,
4422 uri: Uri,
4423 start_line: u32,
4424 start_char: u32,
4425 end_line: u32,
4426 end_char: u32,
4427 diagnostics: Vec<lsp_types::Diagnostic>,
4428 ) -> Result<(), String> {
4429 self.command_tx
4430 .try_send(LspCommand::CodeActions {
4431 request_id,
4432 uri,
4433 start_line,
4434 start_char,
4435 end_line,
4436 end_char,
4437 diagnostics,
4438 })
4439 .map_err(|_| "Failed to send code_actions command".to_string())
4440 }
4441
4442 pub fn execute_command(
4447 &self,
4448 command: String,
4449 arguments: Option<Vec<Value>>,
4450 ) -> Result<(), String> {
4451 self.command_tx
4452 .try_send(LspCommand::ExecuteCommand { command, arguments })
4453 .map_err(|_| "Failed to send execute_command command".to_string())
4454 }
4455
4456 pub fn code_action_resolve(
4461 &self,
4462 request_id: u64,
4463 action: lsp_types::CodeAction,
4464 ) -> Result<(), String> {
4465 self.command_tx
4466 .try_send(LspCommand::CodeActionResolve {
4467 request_id,
4468 action: Box::new(action),
4469 })
4470 .map_err(|_| "Failed to send code_action_resolve command".to_string())
4471 }
4472
4473 pub fn completion_resolve(
4475 &self,
4476 request_id: u64,
4477 item: lsp_types::CompletionItem,
4478 ) -> Result<(), String> {
4479 self.command_tx
4480 .try_send(LspCommand::CompletionResolve {
4481 request_id,
4482 item: Box::new(item),
4483 })
4484 .map_err(|_| "Failed to send completion_resolve command".to_string())
4485 }
4486
4487 pub fn document_formatting(
4489 &self,
4490 request_id: u64,
4491 uri: Uri,
4492 tab_size: u32,
4493 insert_spaces: bool,
4494 ) -> Result<(), String> {
4495 self.command_tx
4496 .try_send(LspCommand::DocumentFormatting {
4497 request_id,
4498 uri,
4499 tab_size,
4500 insert_spaces,
4501 })
4502 .map_err(|_| "Failed to send document_formatting command".to_string())
4503 }
4504
4505 #[allow(clippy::too_many_arguments)]
4507 pub fn document_range_formatting(
4508 &self,
4509 request_id: u64,
4510 uri: Uri,
4511 start_line: u32,
4512 start_char: u32,
4513 end_line: u32,
4514 end_char: u32,
4515 tab_size: u32,
4516 insert_spaces: bool,
4517 ) -> Result<(), String> {
4518 self.command_tx
4519 .try_send(LspCommand::DocumentRangeFormatting {
4520 request_id,
4521 uri,
4522 start_line,
4523 start_char,
4524 end_line,
4525 end_char,
4526 tab_size,
4527 insert_spaces,
4528 })
4529 .map_err(|_| "Failed to send document_range_formatting command".to_string())
4530 }
4531
4532 pub fn prepare_rename(
4534 &self,
4535 request_id: u64,
4536 uri: Uri,
4537 line: u32,
4538 character: u32,
4539 ) -> Result<(), String> {
4540 self.command_tx
4541 .try_send(LspCommand::PrepareRename {
4542 request_id,
4543 uri,
4544 line,
4545 character,
4546 })
4547 .map_err(|_| "Failed to send prepare_rename command".to_string())
4548 }
4549
4550 pub fn document_diagnostic(
4555 &self,
4556 request_id: u64,
4557 uri: Uri,
4558 previous_result_id: Option<String>,
4559 ) -> Result<(), String> {
4560 self.command_tx
4561 .try_send(LspCommand::DocumentDiagnostic {
4562 request_id,
4563 uri,
4564 previous_result_id,
4565 })
4566 .map_err(|_| "Failed to send document_diagnostic command".to_string())
4567 }
4568
4569 pub fn inlay_hints(
4573 &self,
4574 request_id: u64,
4575 uri: Uri,
4576 start_line: u32,
4577 start_char: u32,
4578 end_line: u32,
4579 end_char: u32,
4580 ) -> Result<(), String> {
4581 self.command_tx
4582 .try_send(LspCommand::InlayHints {
4583 request_id,
4584 uri,
4585 start_line,
4586 start_char,
4587 end_line,
4588 end_char,
4589 })
4590 .map_err(|_| "Failed to send inlay_hints command".to_string())
4591 }
4592
4593 pub fn folding_ranges(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4595 self.command_tx
4596 .try_send(LspCommand::FoldingRange { request_id, uri })
4597 .map_err(|_| "Failed to send folding_range command".to_string())
4598 }
4599
4600 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4602 self.command_tx
4603 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
4604 .map_err(|_| "Failed to send semantic_tokens command".to_string())
4605 }
4606
4607 pub fn semantic_tokens_full_delta(
4609 &self,
4610 request_id: u64,
4611 uri: Uri,
4612 previous_result_id: String,
4613 ) -> Result<(), String> {
4614 self.command_tx
4615 .try_send(LspCommand::SemanticTokensFullDelta {
4616 request_id,
4617 uri,
4618 previous_result_id,
4619 })
4620 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
4621 }
4622
4623 pub fn semantic_tokens_range(
4625 &self,
4626 request_id: u64,
4627 uri: Uri,
4628 range: lsp_types::Range,
4629 ) -> Result<(), String> {
4630 self.command_tx
4631 .try_send(LspCommand::SemanticTokensRange {
4632 request_id,
4633 uri,
4634 range,
4635 })
4636 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
4637 }
4638
4639 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
4644 self.command_tx
4645 .try_send(LspCommand::CancelRequest { request_id })
4646 .map_err(|_| "Failed to send cancel_request command".to_string())
4647 }
4648
4649 pub fn send_plugin_request(
4651 &self,
4652 request_id: u64,
4653 method: String,
4654 params: Option<Value>,
4655 ) -> Result<(), String> {
4656 tracing::trace!(
4657 "LspHandle sending plugin request {}: method={}",
4658 request_id,
4659 method
4660 );
4661 match self.command_tx.try_send(LspCommand::PluginRequest {
4662 request_id,
4663 method,
4664 params,
4665 }) {
4666 Ok(()) => {
4667 tracing::trace!(
4668 "LspHandle enqueued plugin request {} successfully",
4669 request_id
4670 );
4671 Ok(())
4672 }
4673 Err(e) => {
4674 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
4675 Err("Failed to send plugin LSP request".to_string())
4676 }
4677 }
4678 }
4679
4680 pub fn shutdown(&self) -> Result<(), String> {
4682 {
4684 let mut state = self.state.lock().unwrap();
4685 if let Err(e) = state.transition_to(LspClientState::Stopping) {
4686 tracing::warn!("State transition warning during shutdown: {}", e);
4687 }
4689 }
4690
4691 self.command_tx
4692 .try_send(LspCommand::Shutdown)
4693 .map_err(|_| "Failed to send shutdown command".to_string())?;
4694
4695 {
4698 let mut state = self.state.lock().unwrap();
4699 let _ = state.transition_to(LspClientState::Stopped);
4700 }
4701
4702 Ok(())
4703 }
4704}
4705
4706#[allow(clippy::let_underscore_must_use)] impl Drop for LspHandle {
4708 fn drop(&mut self) {
4709 let _ = self.command_tx.try_send(LspCommand::Shutdown);
4715
4716 if let Ok(mut state) = self.state.lock() {
4718 let _ = state.transition_to(LspClientState::Stopped);
4719 }
4720 }
4721}
4722
4723#[cfg(test)]
4724mod tests {
4725 use super::*;
4726 use crate::services::lsp::manager::LanguageScope;
4727 use crate::services::remote::LocalLongRunningSpawner;
4728
4729 fn local_spawner() -> Arc<dyn crate::services::remote::LongRunningSpawner> {
4732 Arc::new(LocalLongRunningSpawner)
4733 }
4734
4735 #[test]
4736 fn test_json_rpc_request_serialization() {
4737 let request = JsonRpcRequest {
4738 jsonrpc: "2.0".to_string(),
4739 id: 1,
4740 method: "initialize".to_string(),
4741 params: Some(serde_json::json!({"rootUri": "file:///test"})),
4742 };
4743
4744 let json = serde_json::to_string(&request).unwrap();
4745 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4746 assert!(json.contains("\"id\":1"));
4747 assert!(json.contains("\"method\":\"initialize\""));
4748 assert!(json.contains("\"rootUri\":\"file:///test\""));
4749 }
4750
4751 #[test]
4752 fn test_json_rpc_response_serialization() {
4753 let response = JsonRpcResponse {
4754 jsonrpc: "2.0".to_string(),
4755 id: 1,
4756 result: Some(serde_json::json!({"success": true})),
4757 error: None,
4758 };
4759
4760 let json = serde_json::to_string(&response).unwrap();
4761 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4762 assert!(json.contains("\"id\":1"));
4763 assert!(json.contains("\"success\":true"));
4764 assert!(!json.contains("\"error\""));
4765 }
4766
4767 #[test]
4768 fn test_json_rpc_error_response() {
4769 let response = JsonRpcResponse {
4770 jsonrpc: "2.0".to_string(),
4771 id: 1,
4772 result: None,
4773 error: Some(JsonRpcError {
4774 code: -32600,
4775 message: "Invalid request".to_string(),
4776 data: None,
4777 }),
4778 };
4779
4780 let json = serde_json::to_string(&response).unwrap();
4781 assert!(json.contains("\"error\""));
4782 assert!(json.contains("\"code\":-32600"));
4783 assert!(json.contains("\"message\":\"Invalid request\""));
4784 }
4785
4786 #[test]
4787 fn test_suppressed_error_codes() {
4788 assert!(is_suppressed_error_code(LSP_ERROR_CONTENT_MODIFIED));
4790 assert!(is_suppressed_error_code(LSP_ERROR_SERVER_CANCELLED));
4791
4792 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));
4802 }
4803
4804 fn capture_warn_logs(body: impl FnOnce()) -> (bool, String) {
4808 use std::time::Duration;
4809 use tempfile::NamedTempFile;
4810 use tracing_subscriber::prelude::*;
4811
4812 let log_file = NamedTempFile::new().unwrap();
4813 let log_path = log_file.into_temp_path();
4814 let (layer, handle) =
4815 crate::services::warning_log::create_with_path(log_path.to_path_buf()).unwrap();
4816 let subscriber = tracing_subscriber::registry().with(layer);
4817
4818 tracing::subscriber::with_default(subscriber, body);
4819
4820 let emitted = handle
4821 .receiver
4822 .recv_timeout(Duration::from_millis(100))
4823 .is_ok();
4824 let contents = std::fs::read_to_string(&log_path).unwrap_or_default();
4825 (emitted, contents)
4826 }
4827
4828 #[test]
4829 fn test_content_modified_and_server_cancelled_are_not_logged_as_warn() {
4830 for code in [LSP_ERROR_CONTENT_MODIFIED, LSP_ERROR_SERVER_CANCELLED] {
4831 let (emitted, contents) = capture_warn_logs(|| {
4832 log_response_error(code, "expected during editing", "rust-analyzer", "rust");
4833 });
4834 assert!(
4835 !emitted,
4836 "code {} must not notify the WARN channel; got log:\n{}",
4837 code, contents
4838 );
4839 }
4840 }
4841
4842 #[test]
4843 fn test_method_not_found_still_surfaces_as_warn() {
4844 let (emitted, contents) = capture_warn_logs(|| {
4848 log_response_error(
4849 -32601,
4850 "Unhandled method textDocument/inlayHint",
4851 "vscode-json-language-server",
4852 "json",
4853 );
4854 });
4855 assert!(
4856 emitted,
4857 "MethodNotFound should notify the WARN channel so the mismatch is visible"
4858 );
4859 assert!(
4860 contents.contains("code -32601"),
4861 "WARN log should record the error code; got:\n{}",
4862 contents
4863 );
4864 }
4865
4866 #[test]
4867 fn test_non_suppressed_errors_still_warn() {
4868 let (emitted, contents) = capture_warn_logs(|| {
4871 log_response_error(-32603, "internal error", "rust-analyzer", "rust");
4872 });
4873 assert!(
4874 emitted,
4875 "non-suppressed error codes should notify the WARN channel"
4876 );
4877 assert!(
4878 contents.contains("code -32603"),
4879 "WARN log should record the error code; got:\n{}",
4880 contents
4881 );
4882 assert!(
4883 contents.contains("rust-analyzer"),
4884 "WARN log should record the server name; got:\n{}",
4885 contents
4886 );
4887 }
4888
4889 #[test]
4890 fn test_json_rpc_notification_serialization() {
4891 let notification = JsonRpcNotification {
4892 jsonrpc: "2.0".to_string(),
4893 method: "textDocument/didOpen".to_string(),
4894 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
4895 };
4896
4897 let json = serde_json::to_string(¬ification).unwrap();
4898 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4899 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
4900 assert!(json.contains("\"uri\":\"file:///test.rs\""));
4901 assert!(!json.contains("\"id\"")); }
4903
4904 #[test]
4905 fn test_json_rpc_message_deserialization_request() {
4906 let json =
4907 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
4908 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4909
4910 match message {
4911 JsonRpcMessage::Request(request) => {
4912 assert_eq!(request.jsonrpc, "2.0");
4913 assert_eq!(request.id, 1);
4914 assert_eq!(request.method, "initialize");
4915 assert!(request.params.is_some());
4916 }
4917 _ => panic!("Expected Request"),
4918 }
4919 }
4920
4921 #[test]
4922 fn test_json_rpc_message_deserialization_response() {
4923 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
4924 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4925
4926 match message {
4927 JsonRpcMessage::Response(response) => {
4928 assert_eq!(response.jsonrpc, "2.0");
4929 assert_eq!(response.id, 1);
4930 assert!(response.result.is_some());
4931 assert!(response.error.is_none());
4932 }
4933 _ => panic!("Expected Response"),
4934 }
4935 }
4936
4937 #[test]
4938 fn test_json_rpc_message_deserialization_notification() {
4939 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
4940 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4941
4942 match message {
4943 JsonRpcMessage::Notification(notification) => {
4944 assert_eq!(notification.jsonrpc, "2.0");
4945 assert_eq!(notification.method, "textDocument/didOpen");
4946 assert!(notification.params.is_some());
4947 }
4948 _ => panic!("Expected Notification"),
4949 }
4950 }
4951
4952 #[test]
4953 fn test_json_rpc_error_deserialization() {
4954 let json =
4955 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
4956 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4957
4958 match message {
4959 JsonRpcMessage::Response(response) => {
4960 assert_eq!(response.jsonrpc, "2.0");
4961 assert_eq!(response.id, 1);
4962 assert!(response.result.is_none());
4963 assert!(response.error.is_some());
4964 let error = response.error.unwrap();
4965 assert_eq!(error.code, -32600);
4966 assert_eq!(error.message, "Invalid request");
4967 }
4968 _ => panic!("Expected Response with error"),
4969 }
4970 }
4971
4972 #[tokio::test]
4973 async fn test_lsp_handle_spawn_and_drop() {
4974 let runtime = tokio::runtime::Handle::current();
4977 let async_bridge = AsyncBridge::new();
4978
4979 let result = LspHandle::spawn(
4982 &runtime,
4983 "cat",
4984 &[],
4985 Default::default(),
4986 LanguageScope::single("test"),
4987 "test-server".to_string(),
4988 &async_bridge,
4989 ProcessLimits::unlimited(),
4990 Default::default(),
4991 local_spawner(),
4992 );
4993
4994 assert!(result.is_ok());
4996
4997 let handle = result.unwrap();
4998
4999 drop(handle);
5001
5002 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
5004 }
5005
5006 #[tokio::test]
5007 async fn test_lsp_handle_did_open_queues_before_initialization() {
5008 let runtime = tokio::runtime::Handle::current();
5009 let async_bridge = AsyncBridge::new();
5010
5011 let handle = LspHandle::spawn(
5012 &runtime,
5013 "cat",
5014 &[],
5015 Default::default(),
5016 LanguageScope::single("test"),
5017 "test-server".to_string(),
5018 &async_bridge,
5019 ProcessLimits::unlimited(),
5020 Default::default(),
5021 local_spawner(),
5022 )
5023 .unwrap();
5024
5025 let result = handle.did_open(
5027 "file:///test.txt".parse().unwrap(),
5028 "fn main() {}".to_string(),
5029 "test".to_string(),
5030 );
5031
5032 assert!(result.is_ok());
5034 }
5035
5036 #[tokio::test]
5037 async fn test_lsp_handle_did_change_queues_before_initialization() {
5038 let runtime = tokio::runtime::Handle::current();
5039 let async_bridge = AsyncBridge::new();
5040
5041 let handle = LspHandle::spawn(
5042 &runtime,
5043 "cat",
5044 &[],
5045 Default::default(),
5046 LanguageScope::single("test"),
5047 "test-server".to_string(),
5048 &async_bridge,
5049 ProcessLimits::unlimited(),
5050 Default::default(),
5051 local_spawner(),
5052 )
5053 .unwrap();
5054
5055 let result = handle.did_change(
5057 "file:///test.rs".parse().unwrap(),
5058 vec![TextDocumentContentChangeEvent {
5059 range: Some(lsp_types::Range::new(
5060 lsp_types::Position::new(0, 0),
5061 lsp_types::Position::new(0, 0),
5062 )),
5063 range_length: None,
5064 text: "fn main() {}".to_string(),
5065 }],
5066 );
5067
5068 assert!(result.is_ok());
5070 }
5071
5072 #[tokio::test]
5073 async fn test_lsp_handle_incremental_change_with_range() {
5074 let runtime = tokio::runtime::Handle::current();
5075 let async_bridge = AsyncBridge::new();
5076
5077 let handle = LspHandle::spawn(
5078 &runtime,
5079 "cat",
5080 &[],
5081 Default::default(),
5082 LanguageScope::single("test"),
5083 "test-server".to_string(),
5084 &async_bridge,
5085 ProcessLimits::unlimited(),
5086 Default::default(),
5087 local_spawner(),
5088 )
5089 .unwrap();
5090
5091 let result = handle.did_change(
5093 "file:///test.rs".parse().unwrap(),
5094 vec![TextDocumentContentChangeEvent {
5095 range: Some(lsp_types::Range::new(
5096 lsp_types::Position::new(0, 3),
5097 lsp_types::Position::new(0, 7),
5098 )),
5099 range_length: None,
5100 text: String::new(), }],
5102 );
5103
5104 assert!(result.is_ok());
5106 }
5107
5108 #[tokio::test]
5109 async fn test_lsp_handle_spawn_invalid_command() {
5110 let runtime = tokio::runtime::Handle::current();
5111 let async_bridge = AsyncBridge::new();
5112
5113 let result = LspHandle::spawn(
5115 &runtime,
5116 "this-command-does-not-exist-12345",
5117 &[],
5118 Default::default(),
5119 LanguageScope::single("test"),
5120 "test-server".to_string(),
5121 &async_bridge,
5122 ProcessLimits::unlimited(),
5123 Default::default(),
5124 local_spawner(),
5125 );
5126
5127 assert!(result.is_ok());
5130
5131 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
5133
5134 let messages = async_bridge.try_recv_all();
5136 assert!(!messages.is_empty());
5137
5138 let has_error = messages
5139 .iter()
5140 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
5141 assert!(has_error, "Expected LspError message");
5142 }
5143
5144 #[test]
5145 fn test_lsp_handle_shutdown_from_sync_context() {
5146 std::thread::spawn(|| {
5149 let rt = tokio::runtime::Runtime::new().unwrap();
5151 let async_bridge = AsyncBridge::new();
5152
5153 let handle = rt.block_on(async {
5154 let runtime = tokio::runtime::Handle::current();
5155 LspHandle::spawn(
5156 &runtime,
5157 "cat",
5158 &[],
5159 Default::default(),
5160 LanguageScope::single("test"),
5161 "test-server".to_string(),
5162 &async_bridge,
5163 ProcessLimits::unlimited(),
5164 Default::default(),
5165 local_spawner(),
5166 )
5167 .unwrap()
5168 });
5169
5170 assert!(handle.shutdown().is_ok());
5172
5173 std::thread::sleep(std::time::Duration::from_millis(50));
5175 })
5176 .join()
5177 .unwrap();
5178 }
5179
5180 #[test]
5181 fn test_lsp_command_debug_format() {
5182 let cmd = LspCommand::Shutdown;
5184 let debug_str = format!("{:?}", cmd);
5185 assert!(debug_str.contains("Shutdown"));
5186 }
5187
5188 #[test]
5189 fn test_lsp_client_state_can_initialize_from_starting() {
5190 let state = LspClientState::Starting;
5196
5197 assert!(
5199 state.can_initialize(),
5200 "Starting state must allow initialization to avoid race condition"
5201 );
5202
5203 let mut state = LspClientState::Starting;
5205
5206 assert!(state.can_transition_to(LspClientState::Initializing));
5208 assert!(state.transition_to(LspClientState::Initializing).is_ok());
5209
5210 assert!(state.can_transition_to(LspClientState::Running));
5212 assert!(state.transition_to(LspClientState::Running).is_ok());
5213 }
5214
5215 #[tokio::test]
5216 async fn test_lsp_handle_initialize_from_starting_state() {
5217 let runtime = tokio::runtime::Handle::current();
5225 let async_bridge = AsyncBridge::new();
5226
5227 let handle = LspHandle::spawn(
5229 &runtime,
5230 "cat", &[],
5232 Default::default(),
5233 LanguageScope::single("test"),
5234 "test-server".to_string(),
5235 &async_bridge,
5236 ProcessLimits::unlimited(),
5237 Default::default(),
5238 local_spawner(),
5239 )
5240 .unwrap();
5241
5242 let result = handle.initialize(None, None);
5245
5246 assert!(
5247 result.is_ok(),
5248 "initialize() must succeed from Starting state. Got error: {:?}",
5249 result.err()
5250 );
5251 }
5252
5253 #[tokio::test]
5254 async fn test_lsp_state_machine_race_condition_fix() {
5255 let runtime = tokio::runtime::Handle::current();
5262 let async_bridge = AsyncBridge::new();
5263
5264 let fake_lsp_script = r#"
5266 read -r line # Read Content-Length header
5267 read -r empty # Read empty line
5268 read -r json # Read JSON body
5269
5270 # Send a valid initialize response
5271 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
5272 echo "Content-Length: ${#response}"
5273 echo ""
5274 echo -n "$response"
5275
5276 # Keep running to avoid EOF
5277 sleep 10
5278 "#;
5279
5280 let handle = LspHandle::spawn(
5282 &runtime,
5283 "bash",
5284 &["-c".to_string(), fake_lsp_script.to_string()],
5285 Default::default(),
5286 LanguageScope::single("fake"),
5287 "test-server".to_string(),
5288 &async_bridge,
5289 ProcessLimits::unlimited(),
5290 Default::default(),
5291 local_spawner(),
5292 )
5293 .unwrap();
5294
5295 let init_result = handle.initialize(None, None);
5297 assert!(
5298 init_result.is_ok(),
5299 "initialize() failed from Starting state: {:?}",
5300 init_result.err()
5301 );
5302
5303 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
5305
5306 let messages = async_bridge.try_recv_all();
5308 let has_status_update = messages
5309 .iter()
5310 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
5311
5312 assert!(
5313 has_status_update,
5314 "Expected status update messages from LSP initialization"
5315 );
5316
5317 #[allow(clippy::let_underscore_must_use)]
5319 let _ = handle.shutdown();
5320 }
5321
5322 #[test]
5323 fn test_lsp_client_state_can_shutdown_from_error() {
5324 let mut state = LspClientState::Error;
5331
5332 assert!(
5333 state.can_transition_to(LspClientState::Stopping),
5334 "Error state must allow transition to Stopping for graceful shutdown"
5335 );
5336 assert!(state.transition_to(LspClientState::Stopping).is_ok());
5337 assert!(state.transition_to(LspClientState::Stopped).is_ok());
5340 }
5341
5342 #[tokio::test]
5343 async fn test_lsp_handle_shutdown_after_spawn_failure_advances_state() {
5344 let runtime = tokio::runtime::Handle::current();
5350 let async_bridge = AsyncBridge::new();
5351
5352 let handle = LspHandle::spawn(
5353 &runtime,
5354 "fresh-nonexistent-lsp-binary-7c93af",
5355 &[],
5356 Default::default(),
5357 LanguageScope::single("test"),
5358 "test-server".to_string(),
5359 &async_bridge,
5360 ProcessLimits::unlimited(),
5361 Default::default(),
5362 local_spawner(),
5363 )
5364 .unwrap();
5365
5366 for _ in 0..200 {
5369 if handle.state() == LspClientState::Error {
5370 break;
5371 }
5372 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
5373 }
5374 assert_eq!(
5375 handle.state(),
5376 LspClientState::Error,
5377 "spawn task should have transitioned to Error after failed spawn"
5378 );
5379
5380 #[allow(clippy::let_underscore_must_use)]
5385 let _ = handle.shutdown();
5386 let final_state = handle.state();
5387 assert!(
5388 matches!(
5389 final_state,
5390 LspClientState::Stopping | LspClientState::Stopped
5391 ),
5392 "shutdown from Error must advance state, got {:?}",
5393 final_state
5394 );
5395 }
5396}