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