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