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