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 fn command_exists(command: &str) -> bool {
2639 use std::path::Path;
2640
2641 if command.contains('/') || command.contains('\\') {
2643 let path = Path::new(command);
2644 return path.exists() && path.is_file();
2645 }
2646
2647 if let Ok(path_var) = std::env::var("PATH") {
2649 #[cfg(unix)]
2650 let separator = ':';
2651 #[cfg(windows)]
2652 let separator = ';';
2653
2654 for dir in path_var.split(separator) {
2655 let full_path = Path::new(dir).join(command);
2656 if full_path.exists() && full_path.is_file() {
2657 return true;
2658 }
2659 #[cfg(windows)]
2661 {
2662 let with_exe = Path::new(dir).join(format!("{}.exe", command));
2663 if with_exe.exists() && with_exe.is_file() {
2664 return true;
2665 }
2666 }
2667 }
2668 }
2669
2670 false
2671 }
2672
2673 #[allow(clippy::type_complexity)]
2675 #[allow(clippy::too_many_arguments)]
2676 #[allow(clippy::let_underscore_must_use)] fn spawn_stdout_reader(
2678 mut stdout: BufReader<ChildStdout>,
2679 pending: Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2680 async_tx: std_mpsc::Sender<AsyncMessage>,
2681 language: String,
2682 server_name: String,
2683 server_command: String,
2684 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2685 stderr_log_path: std::path::PathBuf,
2686 shutting_down: Arc<AtomicBool>,
2687 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2688 ) {
2689 tokio::spawn(async move {
2690 tracing::info!("LSP stdout reader task started for {}", language);
2691 loop {
2692 match read_message_from_stdout(&mut stdout).await {
2693 Ok(message) => {
2694 tracing::trace!("Read message from LSP server: {:?}", message);
2695 if let Err(e) = handle_message_dispatch(
2696 message,
2697 &pending,
2698 &async_tx,
2699 &language,
2700 &server_name,
2701 &server_command,
2702 &stdin_writer,
2703 &document_versions,
2704 )
2705 .await
2706 {
2707 tracing::error!("Error handling LSP message: {}", e);
2708 }
2709 }
2710 Err(e) => {
2711 if shutting_down.load(Ordering::SeqCst) {
2713 tracing::info!(
2714 "LSP stdout reader exiting due to graceful shutdown for {}",
2715 language
2716 );
2717 } else {
2718 tracing::error!("Error reading from LSP server: {}", e);
2719 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2720 language: language.clone(),
2721 server_name: server_name.clone(),
2722 status: LspServerStatus::Error,
2723 message: None,
2724 });
2725 let _ = async_tx.send(AsyncMessage::LspError {
2726 language: language.clone(),
2727 error: format!("Read error: {}", e),
2728 stderr_log_path: Some(stderr_log_path.clone()),
2729 });
2730 }
2731 break;
2732 }
2733 }
2734 }
2735 {
2738 let mut pending_guard = pending.lock().unwrap();
2739 let count = pending_guard.len();
2740 if count > 0 {
2741 tracing::info!(
2742 "LSP stdout reader: draining {} pending requests for {}",
2743 count,
2744 language
2745 );
2746 for (id, tx) in pending_guard.drain() {
2747 tracing::debug!(
2748 "LSP stdout reader: failing pending request id={} for {}",
2749 id,
2750 language
2751 );
2752 let _ = tx.send(Err(
2753 "LSP server connection closed while awaiting response".to_string(),
2754 ));
2755 }
2756 }
2757 }
2758
2759 tracing::info!("LSP stdout reader task exiting for {}", language);
2760 });
2761 }
2762
2763 #[allow(clippy::let_underscore_must_use)]
2767 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
2768 tracing::info!("LspTask::run() started for language: {}", self.language);
2769
2770 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
2772
2773 let mut state = LspState {
2775 stdin: stdin_writer.clone(),
2776 next_id: self.next_id,
2777 capabilities: self.capabilities,
2778 document_versions: self.document_versions.clone(),
2779 pending_opens: self.pending_opens,
2780 initialized: self.initialized,
2781 async_tx: self.async_tx.clone(),
2782 language: self.language.clone(),
2783 server_name: self.server_name.clone(),
2784 active_requests: HashMap::new(),
2785 language_id_overrides: self.language_id_overrides.clone(),
2786 };
2787
2788 let pending = Arc::new(Mutex::new(self.pending));
2789 let async_tx = state.async_tx.clone();
2790 let language_clone = state.language.clone();
2791 let server_name = state.server_name.clone();
2792
2793 let shutting_down = Arc::new(AtomicBool::new(false));
2795
2796 Self::spawn_stdout_reader(
2798 self.stdout,
2799 pending.clone(),
2800 async_tx.clone(),
2801 language_clone.clone(),
2802 self.server_name.clone(),
2803 self.server_command.clone(),
2804 stdin_writer.clone(),
2805 self.stderr_log_path,
2806 shutting_down.clone(),
2807 self.document_versions.clone(),
2808 );
2809
2810 macro_rules! await_draining {
2830 ($fut:expr, $command_rx:expr, $buf:expr) => {{
2831 let fut = $fut;
2832 tokio::pin!(fut);
2833 loop {
2834 tokio::select! {
2835 biased; result = &mut fut => break result,
2837 Some(cmd) = $command_rx.recv() => {
2838 $buf.push_back(cmd);
2839 }
2840 }
2841 }
2842 }};
2843 }
2844
2845 let mut pending_commands = Vec::new();
2846 let mut draining_buffer: std::collections::VecDeque<LspCommand> =
2847 std::collections::VecDeque::new();
2848 loop {
2849 let cmd = if let Some(cmd) = draining_buffer.pop_front() {
2852 cmd
2853 } else {
2854 match command_rx.recv().await {
2855 Some(cmd) => cmd,
2856 None => {
2857 tracing::info!("Command channel closed");
2858 break;
2859 }
2860 }
2861 };
2862
2863 tracing::trace!("LspTask received command: {:?}", cmd);
2864 match cmd {
2865 LspCommand::Initialize {
2866 root_uri,
2867 initialization_options,
2868 response,
2869 } => {
2870 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2872 language: language_clone.clone(),
2873 server_name: server_name.clone(),
2874 status: LspServerStatus::Initializing,
2875 message: None,
2876 });
2877 tracing::info!("Processing Initialize command");
2878 let result = await_draining!(
2879 state.handle_initialize_sequential(
2880 root_uri,
2881 initialization_options,
2882 &pending
2883 ),
2884 command_rx,
2885 draining_buffer
2886 );
2887 let success = result.is_ok();
2888 let _ = response.send(result);
2889
2890 if success {
2892 let queued = std::mem::take(&mut pending_commands);
2893 await_draining!(
2894 state.replay_pending_commands(queued, &pending),
2895 command_rx,
2896 draining_buffer
2897 );
2898 }
2899 }
2900 LspCommand::DidOpen {
2901 uri,
2902 text,
2903 language_id,
2904 } => {
2905 if state.initialized {
2906 tracing::info!("Processing DidOpen for {}", uri.as_str());
2907 let _ = state
2908 .handle_did_open_sequential(uri, text, language_id, &pending)
2909 .await;
2910 } else {
2911 tracing::trace!(
2912 "Queueing DidOpen for {} until initialization completes",
2913 uri.as_str()
2914 );
2915 pending_commands.push(LspCommand::DidOpen {
2916 uri,
2917 text,
2918 language_id,
2919 });
2920 }
2921 }
2922 LspCommand::DidChange {
2923 uri,
2924 content_changes,
2925 } => {
2926 if state.initialized {
2927 tracing::trace!("Processing DidChange for {}", uri.as_str());
2928 let _ = state
2929 .handle_did_change_sequential(uri, content_changes, &pending)
2930 .await;
2931 } else {
2932 tracing::trace!(
2933 "Queueing DidChange for {} until initialization completes",
2934 uri.as_str()
2935 );
2936 pending_commands.push(LspCommand::DidChange {
2937 uri,
2938 content_changes,
2939 });
2940 }
2941 }
2942 LspCommand::DidClose { uri } => {
2943 if state.initialized {
2944 tracing::info!("Processing DidClose for {}", uri.as_str());
2945 let _ = state.handle_did_close(uri).await;
2946 } else {
2947 tracing::trace!(
2948 "Queueing DidClose for {} until initialization completes",
2949 uri.as_str()
2950 );
2951 pending_commands.push(LspCommand::DidClose { uri });
2952 }
2953 }
2954 LspCommand::DidSave { uri, text } => {
2955 if state.initialized {
2956 tracing::info!("Processing DidSave for {}", uri.as_str());
2957 let _ = state.handle_did_save(uri, text).await;
2958 } else {
2959 tracing::trace!(
2960 "Queueing DidSave for {} until initialization completes",
2961 uri.as_str()
2962 );
2963 pending_commands.push(LspCommand::DidSave { uri, text });
2964 }
2965 }
2966 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
2967 if state.initialized {
2968 tracing::info!(
2969 "Processing DidChangeWorkspaceFolders: +{} -{}",
2970 added.len(),
2971 removed.len()
2972 );
2973 let _ = state
2974 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
2975 lsp_types::DidChangeWorkspaceFoldersParams {
2976 event: lsp_types::WorkspaceFoldersChangeEvent {
2977 added,
2978 removed,
2979 },
2980 },
2981 )
2982 .await;
2983 } else {
2984 tracing::trace!(
2985 "Queueing DidChangeWorkspaceFolders until initialization completes"
2986 );
2987 pending_commands
2988 .push(LspCommand::DidChangeWorkspaceFolders { added, removed });
2989 }
2990 }
2991 LspCommand::Completion {
2992 request_id,
2993 uri,
2994 line,
2995 character,
2996 } => {
2997 if state.initialized {
2998 tracing::info!("Processing Completion request for {}", uri.as_str());
2999 let _ = await_draining!(
3000 state.handle_completion(request_id, uri, line, character, &pending),
3001 command_rx,
3002 draining_buffer
3003 );
3004 } else {
3005 tracing::trace!("LSP not initialized, sending empty completion");
3006 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
3007 request_id,
3008 items: vec![],
3009 });
3010 }
3011 }
3012 LspCommand::GotoDefinition {
3013 request_id,
3014 uri,
3015 line,
3016 character,
3017 } => {
3018 if state.initialized {
3019 tracing::info!("Processing GotoDefinition request for {}", uri.as_str());
3020 let _ = await_draining!(
3021 state.handle_goto_definition(
3022 request_id, uri, line, character, &pending,
3023 ),
3024 command_rx,
3025 draining_buffer
3026 );
3027 } else {
3028 tracing::trace!("LSP not initialized, sending empty locations");
3029 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
3030 request_id,
3031 locations: vec![],
3032 });
3033 }
3034 }
3035 LspCommand::Rename {
3036 request_id,
3037 uri,
3038 line,
3039 character,
3040 new_name,
3041 } => {
3042 if state.initialized {
3043 tracing::info!("Processing Rename request for {}", uri.as_str());
3044 let _ = await_draining!(
3045 state.handle_rename(
3046 request_id, uri, line, character, new_name, &pending,
3047 ),
3048 command_rx,
3049 draining_buffer
3050 );
3051 } else {
3052 tracing::trace!("LSP not initialized, cannot rename");
3053 let _ = state.async_tx.send(AsyncMessage::LspRename {
3054 request_id,
3055 result: Err("LSP not initialized".to_string()),
3056 });
3057 }
3058 }
3059 LspCommand::Hover {
3060 request_id,
3061 uri,
3062 line,
3063 character,
3064 } => {
3065 if state.initialized {
3066 tracing::info!("Processing Hover request for {}", uri.as_str());
3067 let _ = await_draining!(
3068 state.handle_hover(request_id, uri, line, character, &pending),
3069 command_rx,
3070 draining_buffer
3071 );
3072 } else {
3073 tracing::trace!("LSP not initialized, cannot get hover");
3074 let _ = state.async_tx.send(AsyncMessage::LspHover {
3075 request_id,
3076 contents: String::new(),
3077 is_markdown: false,
3078 range: None,
3079 });
3080 }
3081 }
3082 LspCommand::References {
3083 request_id,
3084 uri,
3085 line,
3086 character,
3087 } => {
3088 if state.initialized {
3089 tracing::info!("Processing References request for {}", uri.as_str());
3090 let _ = await_draining!(
3091 state.handle_references(request_id, uri, line, character, &pending),
3092 command_rx,
3093 draining_buffer
3094 );
3095 } else {
3096 tracing::trace!("LSP not initialized, cannot get references");
3097 let _ = state.async_tx.send(AsyncMessage::LspReferences {
3098 request_id,
3099 locations: Vec::new(),
3100 });
3101 }
3102 }
3103 LspCommand::SignatureHelp {
3104 request_id,
3105 uri,
3106 line,
3107 character,
3108 } => {
3109 if state.initialized {
3110 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
3111 let _ = await_draining!(
3112 state.handle_signature_help(request_id, uri, line, character, &pending),
3113 command_rx,
3114 draining_buffer
3115 );
3116 } else {
3117 tracing::trace!("LSP not initialized, cannot get signature help");
3118 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
3119 request_id,
3120 signature_help: None,
3121 });
3122 }
3123 }
3124 LspCommand::CodeActions {
3125 request_id,
3126 uri,
3127 start_line,
3128 start_char,
3129 end_line,
3130 end_char,
3131 diagnostics,
3132 } => {
3133 if state.initialized {
3134 tracing::info!("Processing CodeActions request for {}", uri.as_str());
3135 let _ = await_draining!(
3136 state.handle_code_actions(
3137 request_id,
3138 uri,
3139 start_line,
3140 start_char,
3141 end_line,
3142 end_char,
3143 diagnostics,
3144 &pending,
3145 ),
3146 command_rx,
3147 draining_buffer
3148 );
3149 } else {
3150 tracing::trace!("LSP not initialized, cannot get code actions");
3151 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
3152 request_id,
3153 actions: Vec::new(),
3154 });
3155 }
3156 }
3157 LspCommand::DocumentDiagnostic {
3158 request_id,
3159 uri,
3160 previous_result_id,
3161 } => {
3162 if state.initialized {
3163 tracing::info!(
3164 "Processing DocumentDiagnostic request for {}",
3165 uri.as_str()
3166 );
3167 let _ = await_draining!(
3168 state.handle_document_diagnostic(
3169 request_id,
3170 uri,
3171 previous_result_id,
3172 &pending,
3173 ),
3174 command_rx,
3175 draining_buffer
3176 );
3177 } else {
3178 tracing::trace!("LSP not initialized, cannot get document diagnostics");
3179 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
3180 request_id,
3181 uri: uri.as_str().to_string(),
3182 result_id: None,
3183 diagnostics: Vec::new(),
3184 unchanged: false,
3185 });
3186 }
3187 }
3188 LspCommand::InlayHints {
3189 request_id,
3190 uri,
3191 start_line,
3192 start_char,
3193 end_line,
3194 end_char,
3195 } => {
3196 if state.initialized {
3197 tracing::info!("Processing InlayHints request for {}", uri.as_str());
3198 let _ = await_draining!(
3199 state.handle_inlay_hints(
3200 request_id, uri, start_line, start_char, end_line, end_char,
3201 &pending,
3202 ),
3203 command_rx,
3204 draining_buffer
3205 );
3206 } else {
3207 tracing::trace!("LSP not initialized, cannot get inlay hints");
3208 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
3209 request_id,
3210 uri: uri.as_str().to_string(),
3211 hints: Vec::new(),
3212 });
3213 }
3214 }
3215 LspCommand::FoldingRange { request_id, uri } => {
3216 if state.initialized {
3217 tracing::info!("Processing FoldingRange request for {}", uri.as_str());
3218 let _ = await_draining!(
3219 state.handle_folding_ranges(request_id, uri, &pending),
3220 command_rx,
3221 draining_buffer
3222 );
3223 } else {
3224 tracing::trace!("LSP not initialized, cannot get folding ranges");
3225 let _ = state.async_tx.send(AsyncMessage::LspFoldingRanges {
3226 request_id,
3227 uri: uri.as_str().to_string(),
3228 ranges: Vec::new(),
3229 });
3230 }
3231 }
3232 LspCommand::SemanticTokensFull { request_id, uri } => {
3233 if state.initialized {
3234 tracing::info!("Processing SemanticTokens request for {}", uri.as_str());
3235 let _ = await_draining!(
3236 state.handle_semantic_tokens_full(request_id, uri, &pending),
3237 command_rx,
3238 draining_buffer
3239 );
3240 } else {
3241 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3242 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3243 request_id,
3244 uri: uri.as_str().to_string(),
3245 response: LspSemanticTokensResponse::Full(Err(
3246 "LSP not initialized".to_string()
3247 )),
3248 });
3249 }
3250 }
3251 LspCommand::SemanticTokensFullDelta {
3252 request_id,
3253 uri,
3254 previous_result_id,
3255 } => {
3256 if state.initialized {
3257 tracing::info!(
3258 "Processing SemanticTokens delta request for {}",
3259 uri.as_str()
3260 );
3261 let _ = await_draining!(
3262 state.handle_semantic_tokens_full_delta(
3263 request_id,
3264 uri,
3265 previous_result_id,
3266 &pending,
3267 ),
3268 command_rx,
3269 draining_buffer
3270 );
3271 } else {
3272 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3273 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3274 request_id,
3275 uri: uri.as_str().to_string(),
3276 response: LspSemanticTokensResponse::FullDelta(Err(
3277 "LSP not initialized".to_string(),
3278 )),
3279 });
3280 }
3281 }
3282 LspCommand::SemanticTokensRange {
3283 request_id,
3284 uri,
3285 range,
3286 } => {
3287 if state.initialized {
3288 tracing::info!(
3289 "Processing SemanticTokens range request for {}",
3290 uri.as_str()
3291 );
3292 let _ = await_draining!(
3293 state.handle_semantic_tokens_range(request_id, uri, range, &pending),
3294 command_rx,
3295 draining_buffer
3296 );
3297 } else {
3298 tracing::trace!("LSP not initialized, cannot get semantic tokens");
3299 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
3300 request_id,
3301 uri: uri.as_str().to_string(),
3302 response: LspSemanticTokensResponse::Range(Err(
3303 "LSP not initialized".to_string()
3304 )),
3305 });
3306 }
3307 }
3308 LspCommand::ExecuteCommand { command, arguments } => {
3309 if state.initialized {
3310 tracing::info!("Processing ExecuteCommand: {}", command);
3311 let _ = await_draining!(
3312 state.handle_execute_command(command, arguments, &pending),
3313 command_rx,
3314 draining_buffer
3315 );
3316 } else {
3317 tracing::trace!("LSP not initialized, cannot execute command");
3318 }
3319 }
3320 LspCommand::CodeActionResolve { request_id, action } => {
3321 if state.initialized {
3322 tracing::info!("Processing CodeActionResolve (request_id={})", request_id);
3323 let _ = await_draining!(
3324 state.handle_code_action_resolve(request_id, *action, &pending),
3325 command_rx,
3326 draining_buffer
3327 );
3328 } else {
3329 tracing::trace!("LSP not initialized, cannot resolve code action");
3330 let _ = state.async_tx.send(AsyncMessage::LspCodeActionResolved {
3331 request_id,
3332 action: Err("LSP not initialized".to_string()),
3333 });
3334 }
3335 }
3336 LspCommand::CompletionResolve { request_id, item } => {
3337 if state.initialized {
3338 let _ = await_draining!(
3339 state.handle_completion_resolve(request_id, *item, &pending),
3340 command_rx,
3341 draining_buffer
3342 );
3343 }
3344 }
3345 LspCommand::DocumentFormatting {
3346 request_id,
3347 uri,
3348 tab_size,
3349 insert_spaces,
3350 } => {
3351 if state.initialized {
3352 tracing::info!("Processing DocumentFormatting for {}", uri.as_str());
3353 let _ = await_draining!(
3354 state.handle_document_formatting(
3355 request_id,
3356 uri,
3357 tab_size,
3358 insert_spaces,
3359 &pending,
3360 ),
3361 command_rx,
3362 draining_buffer
3363 );
3364 }
3365 }
3366 LspCommand::DocumentRangeFormatting {
3367 request_id,
3368 uri,
3369 start_line,
3370 start_char,
3371 end_line,
3372 end_char,
3373 tab_size,
3374 insert_spaces,
3375 } => {
3376 if state.initialized {
3377 let _ = await_draining!(
3378 state.handle_document_range_formatting(
3379 request_id,
3380 uri,
3381 start_line,
3382 start_char,
3383 end_line,
3384 end_char,
3385 tab_size,
3386 insert_spaces,
3387 &pending,
3388 ),
3389 command_rx,
3390 draining_buffer
3391 );
3392 }
3393 }
3394 LspCommand::PrepareRename {
3395 request_id,
3396 uri,
3397 line,
3398 character,
3399 } => {
3400 if state.initialized {
3401 let _ = await_draining!(
3402 state
3403 .handle_prepare_rename(request_id, uri, line, character, &pending,),
3404 command_rx,
3405 draining_buffer
3406 );
3407 }
3408 }
3409 LspCommand::CancelRequest { request_id } => {
3410 tracing::info!("Processing CancelRequest for editor_id={}", request_id);
3411 let _ = state.handle_cancel_request(request_id).await;
3412 }
3413 LspCommand::PluginRequest {
3414 request_id,
3415 method,
3416 params,
3417 } => {
3418 if state.initialized {
3419 tracing::trace!("Processing plugin request {} ({})", request_id, method);
3420 await_draining!(
3421 state.handle_plugin_request(request_id, method, params, &pending,),
3422 command_rx,
3423 draining_buffer
3424 );
3425 } else {
3426 tracing::trace!(
3427 "Plugin LSP request {} received before initialization",
3428 request_id
3429 );
3430 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
3431 language: language_clone.clone(),
3432 request_id,
3433 result: Err("LSP not initialized".to_string()),
3434 });
3435 }
3436 }
3437 LspCommand::Shutdown => {
3438 tracing::info!("Processing Shutdown command");
3439 shutting_down.store(true, Ordering::SeqCst);
3441 let _ = state.handle_shutdown().await;
3442 break;
3443 }
3444 }
3445 }
3446
3447 tracing::info!("LSP task exiting for language: {}", self.language);
3448 }
3449}
3450
3451async fn read_message_from_stdout(
3453 stdout: &mut BufReader<ChildStdout>,
3454) -> Result<JsonRpcMessage, String> {
3455 let mut content_length: Option<usize> = None;
3457
3458 loop {
3459 let mut line = String::new();
3460 let bytes_read = stdout
3461 .read_line(&mut line)
3462 .await
3463 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
3464
3465 if bytes_read == 0 {
3467 return Err("LSP server closed stdout (EOF)".to_string());
3468 }
3469
3470 if line == "\r\n" {
3471 break;
3472 }
3473
3474 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
3475 content_length = Some(
3476 len_str
3477 .trim()
3478 .parse()
3479 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
3480 );
3481 }
3482 }
3483
3484 let content_length =
3485 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
3486
3487 let mut content = vec![0u8; content_length];
3489 stdout
3490 .read_exact(&mut content)
3491 .await
3492 .map_err(|e| format!("Failed to read content: {}", e))?;
3493
3494 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
3495
3496 tracing::trace!("Received LSP message: {}", json);
3497
3498 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
3499}
3500
3501#[allow(clippy::too_many_arguments)]
3503#[allow(clippy::type_complexity)]
3504#[allow(clippy::let_underscore_must_use)] async fn handle_message_dispatch(
3506 message: JsonRpcMessage,
3507 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
3508 async_tx: &std_mpsc::Sender<AsyncMessage>,
3509 language: &str,
3510 server_name: &str,
3511 server_command: &str,
3512 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
3513 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3514) -> Result<(), String> {
3515 match message {
3516 JsonRpcMessage::Response(response) => {
3517 tracing::trace!("Received LSP response for request id={}", response.id);
3518 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
3519 let result = if let Some(error) = response.error {
3520 if error.code == LSP_ERROR_CONTENT_MODIFIED
3523 || error.code == LSP_ERROR_SERVER_CANCELLED
3524 {
3525 tracing::debug!(
3526 "LSP response: {} (code {}), discarding",
3527 error.message,
3528 error.code
3529 );
3530 } else {
3531 tracing::warn!(
3532 "LSP response error: {} (code {})",
3533 error.message,
3534 error.code
3535 );
3536 }
3537 Err(format!(
3538 "LSP error: {} (code {})",
3539 error.message, error.code
3540 ))
3541 } else {
3542 tracing::trace!("LSP response success for request id={}", response.id);
3543 Ok(response.result.unwrap_or(serde_json::Value::Null))
3545 };
3546 let _ = tx.send(result);
3547 } else {
3548 tracing::warn!(
3549 "Received LSP response for unknown request id={}",
3550 response.id
3551 );
3552 }
3553 }
3554 JsonRpcMessage::Notification(notification) => {
3555 tracing::trace!("Received LSP notification: {}", notification.method);
3556 handle_notification_dispatch(
3557 notification,
3558 async_tx,
3559 language,
3560 server_name,
3561 document_versions,
3562 )
3563 .await?;
3564 }
3565 JsonRpcMessage::Request(request) => {
3566 tracing::trace!("Received request from server: {}", request.method);
3568 let response = match request.method.as_str() {
3569 "window/workDoneProgress/create" => {
3570 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
3572 JsonRpcResponse {
3573 jsonrpc: "2.0".to_string(),
3574 id: request.id,
3575 result: Some(Value::Null),
3576 error: None,
3577 }
3578 }
3579 "workspace/configuration" => {
3580 tracing::trace!(
3584 "Responding to workspace/configuration with inlay hints enabled"
3585 );
3586
3587 let num_items = request
3589 .params
3590 .as_ref()
3591 .and_then(|p| p.get("items"))
3592 .and_then(|items| items.as_array())
3593 .map(|arr| arr.len())
3594 .unwrap_or(1);
3595
3596 let ra_config = serde_json::json!({
3598 "inlayHints": {
3599 "typeHints": {
3600 "enable": true
3601 },
3602 "parameterHints": {
3603 "enable": true
3604 },
3605 "chainingHints": {
3606 "enable": true
3607 },
3608 "closureReturnTypeHints": {
3609 "enable": "always"
3610 }
3611 }
3612 });
3613
3614 let configs: Vec<Value> = (0..num_items).map(|_| ra_config.clone()).collect();
3616
3617 JsonRpcResponse {
3618 jsonrpc: "2.0".to_string(),
3619 id: request.id,
3620 result: Some(Value::Array(configs)),
3621 error: None,
3622 }
3623 }
3624 "client/registerCapability" => {
3625 tracing::trace!(
3627 "Acknowledging client/registerCapability (id={})",
3628 request.id
3629 );
3630 JsonRpcResponse {
3631 jsonrpc: "2.0".to_string(),
3632 id: request.id,
3633 result: Some(Value::Null),
3634 error: None,
3635 }
3636 }
3637 "workspace/diagnostic/refresh" => {
3638 tracing::info!(
3641 "LSP ({}) requested diagnostic refresh (workspace/diagnostic/refresh)",
3642 language
3643 );
3644 let _ = async_tx.send(AsyncMessage::LspDiagnosticRefresh {
3645 language: language.to_string(),
3646 });
3647 JsonRpcResponse {
3648 jsonrpc: "2.0".to_string(),
3649 id: request.id,
3650 result: Some(Value::Null),
3651 error: None,
3652 }
3653 }
3654 "workspace/applyEdit" => {
3655 tracing::info!("LSP ({}) received workspace/applyEdit request", language);
3657 let applied = if let Some(params) = &request.params {
3658 match serde_json::from_value::<lsp_types::ApplyWorkspaceEditParams>(
3659 params.clone(),
3660 ) {
3661 Ok(apply_params) => {
3662 let label = apply_params.label.clone();
3663 let _ = async_tx.send(AsyncMessage::LspApplyEdit {
3664 edit: apply_params.edit,
3665 label,
3666 });
3667 true
3668 }
3669 Err(e) => {
3670 tracing::error!(
3671 "Failed to parse workspace/applyEdit params: {}",
3672 e
3673 );
3674 false
3675 }
3676 }
3677 } else {
3678 false
3679 };
3680 JsonRpcResponse {
3681 jsonrpc: "2.0".to_string(),
3682 id: request.id,
3683 result: Some(serde_json::json!({ "applied": applied })),
3684 error: None,
3685 }
3686 }
3687 _ => {
3688 tracing::debug!("Server request for plugins: {}", request.method);
3690 let _ = async_tx.send(AsyncMessage::LspServerRequest {
3691 language: language.to_string(),
3692 server_command: server_command.to_string(),
3693 method: request.method.clone(),
3694 params: request.params.clone(),
3695 });
3696 JsonRpcResponse {
3697 jsonrpc: "2.0".to_string(),
3698 id: request.id,
3699 result: Some(Value::Null),
3700 error: None,
3701 }
3702 }
3703 };
3704
3705 let json = serde_json::to_string(&response)
3707 .map_err(|e| format!("Failed to serialize response: {}", e))?;
3708 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
3709
3710 let mut stdin = stdin_writer.lock().await;
3711 use tokio::io::AsyncWriteExt;
3712 if let Err(e) = stdin.write_all(message.as_bytes()).await {
3713 tracing::error!("Failed to write server response: {}", e);
3714 }
3715 if let Err(e) = stdin.flush().await {
3716 tracing::error!("Failed to flush server response: {}", e);
3717 }
3718 tracing::trace!("Sent response to server request id={}", response.id);
3719 }
3720 }
3721 Ok(())
3722}
3723
3724#[allow(clippy::let_underscore_must_use)] async fn handle_notification_dispatch(
3727 notification: JsonRpcNotification,
3728 async_tx: &std_mpsc::Sender<AsyncMessage>,
3729 language: &str,
3730 server_name: &str,
3731 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3732) -> Result<(), String> {
3733 match notification.method.as_str() {
3734 PublishDiagnostics::METHOD => {
3735 if let Some(params) = notification.params {
3736 let params: PublishDiagnosticsParams = serde_json::from_value(params)
3737 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
3738
3739 if let Some(diag_version) = params.version {
3743 let path = PathBuf::from(params.uri.path().as_str());
3744 let current_version = document_versions.lock().unwrap().get(&path).copied();
3745 if let Some(current) = current_version {
3746 if (diag_version as i64) < current {
3747 tracing::debug!(
3748 "LSP ({}): dropping stale diagnostics for {} (diag version {} < current {})",
3749 language,
3750 params.uri.as_str(),
3751 diag_version,
3752 current
3753 );
3754 return Ok(());
3755 }
3756 }
3757 }
3758
3759 tracing::trace!(
3760 "Received {} diagnostics for {}",
3761 params.diagnostics.len(),
3762 params.uri.as_str()
3763 );
3764
3765 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
3767 uri: params.uri.to_string(),
3768 diagnostics: params.diagnostics,
3769 server_name: server_name.to_string(),
3770 });
3771 }
3772 }
3773 "window/showMessage" => {
3774 if let Some(params) = notification.params {
3775 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3776 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(3);
3777 let message = msg
3778 .get("message")
3779 .and_then(|v| v.as_str())
3780 .unwrap_or("(no message)")
3781 .to_string();
3782
3783 let message_type = match message_type_num {
3784 1 => LspMessageType::Error,
3785 2 => LspMessageType::Warning,
3786 3 => LspMessageType::Info,
3787 _ => LspMessageType::Log,
3788 };
3789
3790 match message_type {
3792 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3793 LspMessageType::Warning => {
3794 tracing::warn!("LSP ({}): {}", language, message)
3795 }
3796 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3797 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3798 }
3799
3800 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
3802 language: language.to_string(),
3803 message_type,
3804 message,
3805 });
3806 }
3807 }
3808 }
3809 "window/logMessage" => {
3810 if let Some(params) = notification.params {
3811 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3812 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(4);
3813 let message = msg
3814 .get("message")
3815 .and_then(|v| v.as_str())
3816 .unwrap_or("(no message)")
3817 .to_string();
3818
3819 let message_type = match message_type_num {
3820 1 => LspMessageType::Error,
3821 2 => LspMessageType::Warning,
3822 3 => LspMessageType::Info,
3823 _ => LspMessageType::Log,
3824 };
3825
3826 match message_type {
3828 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3829 LspMessageType::Warning => {
3830 tracing::warn!("LSP ({}): {}", language, message)
3831 }
3832 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3833 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3834 }
3835
3836 let _ = async_tx.send(AsyncMessage::LspLogMessage {
3838 language: language.to_string(),
3839 message_type,
3840 message,
3841 });
3842 }
3843 }
3844 }
3845 "$/progress" => {
3846 if let Some(params) = notification.params {
3847 if let Ok(progress) =
3848 serde_json::from_value::<serde_json::Map<String, Value>>(params)
3849 {
3850 let token = progress
3851 .get("token")
3852 .and_then(|v| {
3853 v.as_str()
3854 .map(|s| s.to_string())
3855 .or_else(|| v.as_i64().map(|n| n.to_string()))
3856 })
3857 .unwrap_or_else(|| "unknown".to_string());
3858
3859 if let Some(value_obj) = progress.get("value").and_then(|v| v.as_object()) {
3860 let kind = value_obj.get("kind").and_then(|v| v.as_str());
3861
3862 let value = match kind {
3863 Some("begin") => {
3864 let title = value_obj
3865 .get("title")
3866 .and_then(|v| v.as_str())
3867 .unwrap_or("Working...")
3868 .to_string();
3869 let message = value_obj
3870 .get("message")
3871 .and_then(|v| v.as_str())
3872 .map(|s| s.to_string());
3873 let percentage = value_obj
3874 .get("percentage")
3875 .and_then(|v| v.as_u64())
3876 .map(|p| p as u32);
3877
3878 tracing::info!(
3879 "LSP ({}) progress begin: {} {:?} {:?}",
3880 language,
3881 title,
3882 message,
3883 percentage
3884 );
3885
3886 Some(LspProgressValue::Begin {
3887 title,
3888 message,
3889 percentage,
3890 })
3891 }
3892 Some("report") => {
3893 let message = value_obj
3894 .get("message")
3895 .and_then(|v| v.as_str())
3896 .map(|s| s.to_string());
3897 let percentage = value_obj
3898 .get("percentage")
3899 .and_then(|v| v.as_u64())
3900 .map(|p| p as u32);
3901
3902 tracing::trace!(
3903 "LSP ({}) progress report: {:?} {:?}",
3904 language,
3905 message,
3906 percentage
3907 );
3908
3909 Some(LspProgressValue::Report {
3910 message,
3911 percentage,
3912 })
3913 }
3914 Some("end") => {
3915 let message = value_obj
3916 .get("message")
3917 .and_then(|v| v.as_str())
3918 .map(|s| s.to_string());
3919
3920 tracing::info!("LSP ({}) progress end: {:?}", language, message);
3921
3922 Some(LspProgressValue::End { message })
3923 }
3924 _ => None,
3925 };
3926
3927 if let Some(value) = value {
3928 let _ = async_tx.send(AsyncMessage::LspProgress {
3929 language: language.to_string(),
3930 token,
3931 value,
3932 });
3933 }
3934 }
3935 }
3936 }
3937 }
3938 "experimental/serverStatus" => {
3939 if let Some(params) = notification.params {
3942 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
3943 {
3944 let quiescent = status
3945 .get("quiescent")
3946 .and_then(|v| v.as_bool())
3947 .unwrap_or(false);
3948
3949 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
3950
3951 if quiescent {
3952 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
3954 language: language.to_string(),
3955 });
3956 }
3957 }
3958 }
3959 }
3960 _ => {
3961 tracing::debug!("Unhandled notification: {}", notification.method);
3962 }
3963 }
3964
3965 Ok(())
3966}
3967
3968static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
3970
3971pub struct LspHandle {
3973 id: u64,
3975
3976 scope: crate::services::lsp::manager::LanguageScope,
3978
3979 command_tx: mpsc::Sender<LspCommand>,
3981
3982 state: Arc<Mutex<LspClientState>>,
3984
3985 runtime: tokio::runtime::Handle,
3987
3988 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3991}
3992
3993#[allow(clippy::let_underscore_must_use)]
3997impl LspHandle {
3998 #[allow(clippy::too_many_arguments)]
4000 pub fn spawn(
4001 runtime: &tokio::runtime::Handle,
4002 command: &str,
4003 args: &[String],
4004 env: std::collections::HashMap<String, String>,
4005 scope: crate::services::lsp::manager::LanguageScope,
4006 server_name: String,
4007 async_bridge: &AsyncBridge,
4008 process_limits: ProcessLimits,
4009 language_id_overrides: std::collections::HashMap<String, String>,
4010 ) -> Result<Self, String> {
4011 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
4013 let language_label = scope.label().to_string();
4014 let language_clone = language_label.clone();
4015 let server_name_clone = server_name.clone();
4016 let command = command.to_string();
4017 let args = args.to_vec();
4018 let state = Arc::new(Mutex::new(LspClientState::Starting));
4019
4020 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language_label);
4022
4023 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4025 language: language_label.clone(),
4026 server_name: server_name_clone.clone(),
4027 status: LspServerStatus::Starting,
4028 message: None,
4029 });
4030
4031 let document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>> =
4035 Arc::new(std::sync::Mutex::new(HashMap::new()));
4036 let document_versions_for_task = document_versions.clone();
4037
4038 let state_clone = state.clone();
4039 let stderr_log_path_clone = stderr_log_path.clone();
4040 runtime.spawn(async move {
4041 match LspTask::spawn(
4042 &command,
4043 &args,
4044 &env,
4045 language_clone.clone(),
4046 server_name_clone.clone(),
4047 async_tx.clone(),
4048 &process_limits,
4049 stderr_log_path_clone.clone(),
4050 language_id_overrides,
4051 document_versions_for_task,
4052 )
4053 .await
4054 {
4055 Ok(task) => {
4056 task.run(command_rx).await;
4057 }
4058 Err(e) => {
4059 tracing::error!("Failed to spawn LSP task: {}", e);
4060
4061 if let Ok(mut s) = state_clone.lock() {
4063 let _ = s.transition_to(LspClientState::Error);
4064 }
4065
4066 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
4067 language: language_clone.clone(),
4068 server_name: server_name_clone.clone(),
4069 status: LspServerStatus::Error,
4070 message: None,
4071 });
4072 let _ = async_tx.send(AsyncMessage::LspError {
4073 language: language_clone,
4074 error: e,
4075 stderr_log_path: Some(stderr_log_path_clone),
4076 });
4077 }
4078 }
4079 });
4080
4081 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4082
4083 Ok(Self {
4084 id,
4085 scope,
4086 command_tx,
4087 state,
4088 runtime: runtime.clone(),
4089 document_versions,
4090 })
4091 }
4092
4093 pub fn id(&self) -> u64 {
4095 self.id
4096 }
4097
4098 pub fn scope(&self) -> &crate::services::lsp::manager::LanguageScope {
4100 &self.scope
4101 }
4102
4103 pub fn document_version(&self, path: &std::path::Path) -> Option<i64> {
4106 self.document_versions
4107 .lock()
4108 .ok()
4109 .and_then(|versions| versions.get(path).copied())
4110 }
4111
4112 pub fn initialize(
4121 &self,
4122 root_uri: Option<Uri>,
4123 initialization_options: Option<Value>,
4124 ) -> Result<(), String> {
4125 {
4127 let mut state = self.state.lock().unwrap();
4128 if !state.can_initialize() {
4129 return Err(format!(
4130 "Cannot initialize: client is in state {:?}",
4131 *state
4132 ));
4133 }
4134 state.transition_to(LspClientState::Initializing)?;
4136 }
4137
4138 let state = self.state.clone();
4139
4140 let (tx, rx) = oneshot::channel();
4142
4143 self.command_tx
4144 .try_send(LspCommand::Initialize {
4145 root_uri,
4146 initialization_options,
4147 response: tx,
4148 })
4149 .map_err(|_| "Failed to send initialize command".to_string())?;
4150
4151 let runtime = self.runtime.clone();
4153 runtime.spawn(async move {
4154 match tokio::time::timeout(std::time::Duration::from_secs(60), rx).await {
4155 Ok(Ok(Ok(_))) => {
4156 if let Ok(mut s) = state.lock() {
4158 let _ = s.transition_to(LspClientState::Running);
4159 }
4160 tracing::info!("LSP initialization completed successfully");
4161 }
4162 Ok(Ok(Err(e))) => {
4163 tracing::error!("LSP initialization failed: {}", e);
4164 if let Ok(mut s) = state.lock() {
4165 let _ = s.transition_to(LspClientState::Error);
4166 }
4167 }
4168 Ok(Err(_)) => {
4169 tracing::error!("LSP initialization response channel closed");
4170 if let Ok(mut s) = state.lock() {
4171 let _ = s.transition_to(LspClientState::Error);
4172 }
4173 }
4174 Err(_) => {
4175 tracing::error!("LSP initialization timed out after 60 seconds");
4176 if let Ok(mut s) = state.lock() {
4177 let _ = s.transition_to(LspClientState::Error);
4178 }
4179 }
4180 }
4181 });
4182
4183 Ok(())
4184 }
4185
4186 pub fn is_initialized(&self) -> bool {
4188 self.state.lock().unwrap().can_send_requests()
4189 }
4190
4191 pub fn state(&self) -> LspClientState {
4193 *self.state.lock().unwrap()
4194 }
4195
4196 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
4202 if !self.scope.accepts(&language_id) {
4204 tracing::warn!(
4205 "did_open: document language '{}' not accepted by LSP handle (serves {:?}) for {}",
4206 language_id,
4207 self.scope,
4208 uri.as_str()
4209 );
4210 return Err(format!(
4211 "Language mismatch: document is '{}' but LSP serves {:?}",
4212 language_id, self.scope
4213 ));
4214 }
4215
4216 self.command_tx
4218 .try_send(LspCommand::DidOpen {
4219 uri,
4220 text,
4221 language_id,
4222 })
4223 .map_err(|_| "Failed to send did_open command".to_string())
4224 }
4225
4226 pub fn did_change(
4228 &self,
4229 uri: Uri,
4230 content_changes: Vec<TextDocumentContentChangeEvent>,
4231 ) -> Result<(), String> {
4232 self.command_tx
4234 .try_send(LspCommand::DidChange {
4235 uri,
4236 content_changes,
4237 })
4238 .map_err(|_| "Failed to send did_change command".to_string())
4239 }
4240
4241 pub fn did_close(&self, uri: Uri) -> Result<(), String> {
4243 self.command_tx
4244 .try_send(LspCommand::DidClose { uri })
4245 .map_err(|_| "Failed to send did_close command".to_string())
4246 }
4247
4248 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
4250 self.command_tx
4251 .try_send(LspCommand::DidSave { uri, text })
4252 .map_err(|_| "Failed to send did_save command".to_string())
4253 }
4254
4255 pub fn add_workspace_folder(&self, uri: lsp_types::Uri, name: String) -> Result<(), String> {
4257 self.command_tx
4258 .try_send(LspCommand::DidChangeWorkspaceFolders {
4259 added: vec![lsp_types::WorkspaceFolder { uri, name }],
4260 removed: vec![],
4261 })
4262 .map_err(|_| "Failed to send workspace folder change".to_string())
4263 }
4264
4265 pub fn completion(
4267 &self,
4268 request_id: u64,
4269 uri: Uri,
4270 line: u32,
4271 character: u32,
4272 ) -> Result<(), String> {
4273 self.command_tx
4274 .try_send(LspCommand::Completion {
4275 request_id,
4276 uri,
4277 line,
4278 character,
4279 })
4280 .map_err(|_| "Failed to send completion command".to_string())
4281 }
4282
4283 pub fn goto_definition(
4285 &self,
4286 request_id: u64,
4287 uri: Uri,
4288 line: u32,
4289 character: u32,
4290 ) -> Result<(), String> {
4291 self.command_tx
4292 .try_send(LspCommand::GotoDefinition {
4293 request_id,
4294 uri,
4295 line,
4296 character,
4297 })
4298 .map_err(|_| "Failed to send goto_definition command".to_string())
4299 }
4300
4301 pub fn rename(
4303 &self,
4304 request_id: u64,
4305 uri: Uri,
4306 line: u32,
4307 character: u32,
4308 new_name: String,
4309 ) -> Result<(), String> {
4310 self.command_tx
4311 .try_send(LspCommand::Rename {
4312 request_id,
4313 uri,
4314 line,
4315 character,
4316 new_name,
4317 })
4318 .map_err(|_| "Failed to send rename command".to_string())
4319 }
4320
4321 pub fn hover(
4323 &self,
4324 request_id: u64,
4325 uri: Uri,
4326 line: u32,
4327 character: u32,
4328 ) -> Result<(), String> {
4329 self.command_tx
4330 .try_send(LspCommand::Hover {
4331 request_id,
4332 uri,
4333 line,
4334 character,
4335 })
4336 .map_err(|_| "Failed to send hover command".to_string())
4337 }
4338
4339 pub fn references(
4341 &self,
4342 request_id: u64,
4343 uri: Uri,
4344 line: u32,
4345 character: u32,
4346 ) -> Result<(), String> {
4347 self.command_tx
4348 .try_send(LspCommand::References {
4349 request_id,
4350 uri,
4351 line,
4352 character,
4353 })
4354 .map_err(|_| "Failed to send references command".to_string())
4355 }
4356
4357 pub fn signature_help(
4359 &self,
4360 request_id: u64,
4361 uri: Uri,
4362 line: u32,
4363 character: u32,
4364 ) -> Result<(), String> {
4365 self.command_tx
4366 .try_send(LspCommand::SignatureHelp {
4367 request_id,
4368 uri,
4369 line,
4370 character,
4371 })
4372 .map_err(|_| "Failed to send signature_help command".to_string())
4373 }
4374
4375 #[allow(clippy::too_many_arguments)]
4377 pub fn code_actions(
4378 &self,
4379 request_id: u64,
4380 uri: Uri,
4381 start_line: u32,
4382 start_char: u32,
4383 end_line: u32,
4384 end_char: u32,
4385 diagnostics: Vec<lsp_types::Diagnostic>,
4386 ) -> Result<(), String> {
4387 self.command_tx
4388 .try_send(LspCommand::CodeActions {
4389 request_id,
4390 uri,
4391 start_line,
4392 start_char,
4393 end_line,
4394 end_char,
4395 diagnostics,
4396 })
4397 .map_err(|_| "Failed to send code_actions command".to_string())
4398 }
4399
4400 pub fn execute_command(
4405 &self,
4406 command: String,
4407 arguments: Option<Vec<Value>>,
4408 ) -> Result<(), String> {
4409 self.command_tx
4410 .try_send(LspCommand::ExecuteCommand { command, arguments })
4411 .map_err(|_| "Failed to send execute_command command".to_string())
4412 }
4413
4414 pub fn code_action_resolve(
4419 &self,
4420 request_id: u64,
4421 action: lsp_types::CodeAction,
4422 ) -> Result<(), String> {
4423 self.command_tx
4424 .try_send(LspCommand::CodeActionResolve {
4425 request_id,
4426 action: Box::new(action),
4427 })
4428 .map_err(|_| "Failed to send code_action_resolve command".to_string())
4429 }
4430
4431 pub fn completion_resolve(
4433 &self,
4434 request_id: u64,
4435 item: lsp_types::CompletionItem,
4436 ) -> Result<(), String> {
4437 self.command_tx
4438 .try_send(LspCommand::CompletionResolve {
4439 request_id,
4440 item: Box::new(item),
4441 })
4442 .map_err(|_| "Failed to send completion_resolve command".to_string())
4443 }
4444
4445 pub fn document_formatting(
4447 &self,
4448 request_id: u64,
4449 uri: Uri,
4450 tab_size: u32,
4451 insert_spaces: bool,
4452 ) -> Result<(), String> {
4453 self.command_tx
4454 .try_send(LspCommand::DocumentFormatting {
4455 request_id,
4456 uri,
4457 tab_size,
4458 insert_spaces,
4459 })
4460 .map_err(|_| "Failed to send document_formatting command".to_string())
4461 }
4462
4463 #[allow(clippy::too_many_arguments)]
4465 pub fn document_range_formatting(
4466 &self,
4467 request_id: u64,
4468 uri: Uri,
4469 start_line: u32,
4470 start_char: u32,
4471 end_line: u32,
4472 end_char: u32,
4473 tab_size: u32,
4474 insert_spaces: bool,
4475 ) -> Result<(), String> {
4476 self.command_tx
4477 .try_send(LspCommand::DocumentRangeFormatting {
4478 request_id,
4479 uri,
4480 start_line,
4481 start_char,
4482 end_line,
4483 end_char,
4484 tab_size,
4485 insert_spaces,
4486 })
4487 .map_err(|_| "Failed to send document_range_formatting command".to_string())
4488 }
4489
4490 pub fn prepare_rename(
4492 &self,
4493 request_id: u64,
4494 uri: Uri,
4495 line: u32,
4496 character: u32,
4497 ) -> Result<(), String> {
4498 self.command_tx
4499 .try_send(LspCommand::PrepareRename {
4500 request_id,
4501 uri,
4502 line,
4503 character,
4504 })
4505 .map_err(|_| "Failed to send prepare_rename command".to_string())
4506 }
4507
4508 pub fn document_diagnostic(
4513 &self,
4514 request_id: u64,
4515 uri: Uri,
4516 previous_result_id: Option<String>,
4517 ) -> Result<(), String> {
4518 self.command_tx
4519 .try_send(LspCommand::DocumentDiagnostic {
4520 request_id,
4521 uri,
4522 previous_result_id,
4523 })
4524 .map_err(|_| "Failed to send document_diagnostic command".to_string())
4525 }
4526
4527 pub fn inlay_hints(
4531 &self,
4532 request_id: u64,
4533 uri: Uri,
4534 start_line: u32,
4535 start_char: u32,
4536 end_line: u32,
4537 end_char: u32,
4538 ) -> Result<(), String> {
4539 self.command_tx
4540 .try_send(LspCommand::InlayHints {
4541 request_id,
4542 uri,
4543 start_line,
4544 start_char,
4545 end_line,
4546 end_char,
4547 })
4548 .map_err(|_| "Failed to send inlay_hints command".to_string())
4549 }
4550
4551 pub fn folding_ranges(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4553 self.command_tx
4554 .try_send(LspCommand::FoldingRange { request_id, uri })
4555 .map_err(|_| "Failed to send folding_range command".to_string())
4556 }
4557
4558 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
4560 self.command_tx
4561 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
4562 .map_err(|_| "Failed to send semantic_tokens command".to_string())
4563 }
4564
4565 pub fn semantic_tokens_full_delta(
4567 &self,
4568 request_id: u64,
4569 uri: Uri,
4570 previous_result_id: String,
4571 ) -> Result<(), String> {
4572 self.command_tx
4573 .try_send(LspCommand::SemanticTokensFullDelta {
4574 request_id,
4575 uri,
4576 previous_result_id,
4577 })
4578 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
4579 }
4580
4581 pub fn semantic_tokens_range(
4583 &self,
4584 request_id: u64,
4585 uri: Uri,
4586 range: lsp_types::Range,
4587 ) -> Result<(), String> {
4588 self.command_tx
4589 .try_send(LspCommand::SemanticTokensRange {
4590 request_id,
4591 uri,
4592 range,
4593 })
4594 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
4595 }
4596
4597 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
4602 self.command_tx
4603 .try_send(LspCommand::CancelRequest { request_id })
4604 .map_err(|_| "Failed to send cancel_request command".to_string())
4605 }
4606
4607 pub fn send_plugin_request(
4609 &self,
4610 request_id: u64,
4611 method: String,
4612 params: Option<Value>,
4613 ) -> Result<(), String> {
4614 tracing::trace!(
4615 "LspHandle sending plugin request {}: method={}",
4616 request_id,
4617 method
4618 );
4619 match self.command_tx.try_send(LspCommand::PluginRequest {
4620 request_id,
4621 method,
4622 params,
4623 }) {
4624 Ok(()) => {
4625 tracing::trace!(
4626 "LspHandle enqueued plugin request {} successfully",
4627 request_id
4628 );
4629 Ok(())
4630 }
4631 Err(e) => {
4632 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
4633 Err("Failed to send plugin LSP request".to_string())
4634 }
4635 }
4636 }
4637
4638 pub fn shutdown(&self) -> Result<(), String> {
4640 {
4642 let mut state = self.state.lock().unwrap();
4643 if let Err(e) = state.transition_to(LspClientState::Stopping) {
4644 tracing::warn!("State transition warning during shutdown: {}", e);
4645 }
4647 }
4648
4649 self.command_tx
4650 .try_send(LspCommand::Shutdown)
4651 .map_err(|_| "Failed to send shutdown command".to_string())?;
4652
4653 {
4656 let mut state = self.state.lock().unwrap();
4657 let _ = state.transition_to(LspClientState::Stopped);
4658 }
4659
4660 Ok(())
4661 }
4662}
4663
4664#[allow(clippy::let_underscore_must_use)] impl Drop for LspHandle {
4666 fn drop(&mut self) {
4667 let _ = self.command_tx.try_send(LspCommand::Shutdown);
4673
4674 if let Ok(mut state) = self.state.lock() {
4676 let _ = state.transition_to(LspClientState::Stopped);
4677 }
4678 }
4679}
4680
4681#[cfg(test)]
4682mod tests {
4683 use super::*;
4684 use crate::services::lsp::manager::LanguageScope;
4685
4686 #[test]
4687 fn test_json_rpc_request_serialization() {
4688 let request = JsonRpcRequest {
4689 jsonrpc: "2.0".to_string(),
4690 id: 1,
4691 method: "initialize".to_string(),
4692 params: Some(serde_json::json!({"rootUri": "file:///test"})),
4693 };
4694
4695 let json = serde_json::to_string(&request).unwrap();
4696 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4697 assert!(json.contains("\"id\":1"));
4698 assert!(json.contains("\"method\":\"initialize\""));
4699 assert!(json.contains("\"rootUri\":\"file:///test\""));
4700 }
4701
4702 #[test]
4703 fn test_json_rpc_response_serialization() {
4704 let response = JsonRpcResponse {
4705 jsonrpc: "2.0".to_string(),
4706 id: 1,
4707 result: Some(serde_json::json!({"success": true})),
4708 error: None,
4709 };
4710
4711 let json = serde_json::to_string(&response).unwrap();
4712 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4713 assert!(json.contains("\"id\":1"));
4714 assert!(json.contains("\"success\":true"));
4715 assert!(!json.contains("\"error\""));
4716 }
4717
4718 #[test]
4719 fn test_json_rpc_error_response() {
4720 let response = JsonRpcResponse {
4721 jsonrpc: "2.0".to_string(),
4722 id: 1,
4723 result: None,
4724 error: Some(JsonRpcError {
4725 code: -32600,
4726 message: "Invalid request".to_string(),
4727 data: None,
4728 }),
4729 };
4730
4731 let json = serde_json::to_string(&response).unwrap();
4732 assert!(json.contains("\"error\""));
4733 assert!(json.contains("\"code\":-32600"));
4734 assert!(json.contains("\"message\":\"Invalid request\""));
4735 }
4736
4737 #[test]
4738 fn test_json_rpc_notification_serialization() {
4739 let notification = JsonRpcNotification {
4740 jsonrpc: "2.0".to_string(),
4741 method: "textDocument/didOpen".to_string(),
4742 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
4743 };
4744
4745 let json = serde_json::to_string(¬ification).unwrap();
4746 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4747 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
4748 assert!(json.contains("\"uri\":\"file:///test.rs\""));
4749 assert!(!json.contains("\"id\"")); }
4751
4752 #[test]
4753 fn test_json_rpc_message_deserialization_request() {
4754 let json =
4755 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
4756 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4757
4758 match message {
4759 JsonRpcMessage::Request(request) => {
4760 assert_eq!(request.jsonrpc, "2.0");
4761 assert_eq!(request.id, 1);
4762 assert_eq!(request.method, "initialize");
4763 assert!(request.params.is_some());
4764 }
4765 _ => panic!("Expected Request"),
4766 }
4767 }
4768
4769 #[test]
4770 fn test_json_rpc_message_deserialization_response() {
4771 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
4772 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4773
4774 match message {
4775 JsonRpcMessage::Response(response) => {
4776 assert_eq!(response.jsonrpc, "2.0");
4777 assert_eq!(response.id, 1);
4778 assert!(response.result.is_some());
4779 assert!(response.error.is_none());
4780 }
4781 _ => panic!("Expected Response"),
4782 }
4783 }
4784
4785 #[test]
4786 fn test_json_rpc_message_deserialization_notification() {
4787 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
4788 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4789
4790 match message {
4791 JsonRpcMessage::Notification(notification) => {
4792 assert_eq!(notification.jsonrpc, "2.0");
4793 assert_eq!(notification.method, "textDocument/didOpen");
4794 assert!(notification.params.is_some());
4795 }
4796 _ => panic!("Expected Notification"),
4797 }
4798 }
4799
4800 #[test]
4801 fn test_json_rpc_error_deserialization() {
4802 let json =
4803 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
4804 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4805
4806 match message {
4807 JsonRpcMessage::Response(response) => {
4808 assert_eq!(response.jsonrpc, "2.0");
4809 assert_eq!(response.id, 1);
4810 assert!(response.result.is_none());
4811 assert!(response.error.is_some());
4812 let error = response.error.unwrap();
4813 assert_eq!(error.code, -32600);
4814 assert_eq!(error.message, "Invalid request");
4815 }
4816 _ => panic!("Expected Response with error"),
4817 }
4818 }
4819
4820 #[tokio::test]
4821 async fn test_lsp_handle_spawn_and_drop() {
4822 let runtime = tokio::runtime::Handle::current();
4825 let async_bridge = AsyncBridge::new();
4826
4827 let result = LspHandle::spawn(
4830 &runtime,
4831 "cat",
4832 &[],
4833 Default::default(),
4834 LanguageScope::single("test"),
4835 "test-server".to_string(),
4836 &async_bridge,
4837 ProcessLimits::unlimited(),
4838 Default::default(),
4839 );
4840
4841 assert!(result.is_ok());
4843
4844 let handle = result.unwrap();
4845
4846 drop(handle);
4848
4849 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
4851 }
4852
4853 #[tokio::test]
4854 async fn test_lsp_handle_did_open_queues_before_initialization() {
4855 let runtime = tokio::runtime::Handle::current();
4856 let async_bridge = AsyncBridge::new();
4857
4858 let handle = LspHandle::spawn(
4859 &runtime,
4860 "cat",
4861 &[],
4862 Default::default(),
4863 LanguageScope::single("test"),
4864 "test-server".to_string(),
4865 &async_bridge,
4866 ProcessLimits::unlimited(),
4867 Default::default(),
4868 )
4869 .unwrap();
4870
4871 let result = handle.did_open(
4873 "file:///test.txt".parse().unwrap(),
4874 "fn main() {}".to_string(),
4875 "test".to_string(),
4876 );
4877
4878 assert!(result.is_ok());
4880 }
4881
4882 #[tokio::test]
4883 async fn test_lsp_handle_did_change_queues_before_initialization() {
4884 let runtime = tokio::runtime::Handle::current();
4885 let async_bridge = AsyncBridge::new();
4886
4887 let handle = LspHandle::spawn(
4888 &runtime,
4889 "cat",
4890 &[],
4891 Default::default(),
4892 LanguageScope::single("test"),
4893 "test-server".to_string(),
4894 &async_bridge,
4895 ProcessLimits::unlimited(),
4896 Default::default(),
4897 )
4898 .unwrap();
4899
4900 let result = handle.did_change(
4902 "file:///test.rs".parse().unwrap(),
4903 vec![TextDocumentContentChangeEvent {
4904 range: Some(lsp_types::Range::new(
4905 lsp_types::Position::new(0, 0),
4906 lsp_types::Position::new(0, 0),
4907 )),
4908 range_length: None,
4909 text: "fn main() {}".to_string(),
4910 }],
4911 );
4912
4913 assert!(result.is_ok());
4915 }
4916
4917 #[tokio::test]
4918 async fn test_lsp_handle_incremental_change_with_range() {
4919 let runtime = tokio::runtime::Handle::current();
4920 let async_bridge = AsyncBridge::new();
4921
4922 let handle = LspHandle::spawn(
4923 &runtime,
4924 "cat",
4925 &[],
4926 Default::default(),
4927 LanguageScope::single("test"),
4928 "test-server".to_string(),
4929 &async_bridge,
4930 ProcessLimits::unlimited(),
4931 Default::default(),
4932 )
4933 .unwrap();
4934
4935 let result = handle.did_change(
4937 "file:///test.rs".parse().unwrap(),
4938 vec![TextDocumentContentChangeEvent {
4939 range: Some(lsp_types::Range::new(
4940 lsp_types::Position::new(0, 3),
4941 lsp_types::Position::new(0, 7),
4942 )),
4943 range_length: None,
4944 text: String::new(), }],
4946 );
4947
4948 assert!(result.is_ok());
4950 }
4951
4952 #[tokio::test]
4953 async fn test_lsp_handle_spawn_invalid_command() {
4954 let runtime = tokio::runtime::Handle::current();
4955 let async_bridge = AsyncBridge::new();
4956
4957 let result = LspHandle::spawn(
4959 &runtime,
4960 "this-command-does-not-exist-12345",
4961 &[],
4962 Default::default(),
4963 LanguageScope::single("test"),
4964 "test-server".to_string(),
4965 &async_bridge,
4966 ProcessLimits::unlimited(),
4967 Default::default(),
4968 );
4969
4970 assert!(result.is_ok());
4973
4974 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
4976
4977 let messages = async_bridge.try_recv_all();
4979 assert!(!messages.is_empty());
4980
4981 let has_error = messages
4982 .iter()
4983 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
4984 assert!(has_error, "Expected LspError message");
4985 }
4986
4987 #[test]
4988 fn test_lsp_handle_shutdown_from_sync_context() {
4989 std::thread::spawn(|| {
4992 let rt = tokio::runtime::Runtime::new().unwrap();
4994 let async_bridge = AsyncBridge::new();
4995
4996 let handle = rt.block_on(async {
4997 let runtime = tokio::runtime::Handle::current();
4998 LspHandle::spawn(
4999 &runtime,
5000 "cat",
5001 &[],
5002 Default::default(),
5003 LanguageScope::single("test"),
5004 "test-server".to_string(),
5005 &async_bridge,
5006 ProcessLimits::unlimited(),
5007 Default::default(),
5008 )
5009 .unwrap()
5010 });
5011
5012 assert!(handle.shutdown().is_ok());
5014
5015 std::thread::sleep(std::time::Duration::from_millis(50));
5017 })
5018 .join()
5019 .unwrap();
5020 }
5021
5022 #[test]
5023 fn test_lsp_command_debug_format() {
5024 let cmd = LspCommand::Shutdown;
5026 let debug_str = format!("{:?}", cmd);
5027 assert!(debug_str.contains("Shutdown"));
5028 }
5029
5030 #[test]
5031 fn test_lsp_client_state_can_initialize_from_starting() {
5032 let state = LspClientState::Starting;
5038
5039 assert!(
5041 state.can_initialize(),
5042 "Starting state must allow initialization to avoid race condition"
5043 );
5044
5045 let mut state = LspClientState::Starting;
5047
5048 assert!(state.can_transition_to(LspClientState::Initializing));
5050 assert!(state.transition_to(LspClientState::Initializing).is_ok());
5051
5052 assert!(state.can_transition_to(LspClientState::Running));
5054 assert!(state.transition_to(LspClientState::Running).is_ok());
5055 }
5056
5057 #[tokio::test]
5058 async fn test_lsp_handle_initialize_from_starting_state() {
5059 let runtime = tokio::runtime::Handle::current();
5067 let async_bridge = AsyncBridge::new();
5068
5069 let handle = LspHandle::spawn(
5071 &runtime,
5072 "cat", &[],
5074 Default::default(),
5075 LanguageScope::single("test"),
5076 "test-server".to_string(),
5077 &async_bridge,
5078 ProcessLimits::unlimited(),
5079 Default::default(),
5080 )
5081 .unwrap();
5082
5083 let result = handle.initialize(None, None);
5086
5087 assert!(
5088 result.is_ok(),
5089 "initialize() must succeed from Starting state. Got error: {:?}",
5090 result.err()
5091 );
5092 }
5093
5094 #[tokio::test]
5095 async fn test_lsp_state_machine_race_condition_fix() {
5096 let runtime = tokio::runtime::Handle::current();
5103 let async_bridge = AsyncBridge::new();
5104
5105 let fake_lsp_script = r#"
5107 read -r line # Read Content-Length header
5108 read -r empty # Read empty line
5109 read -r json # Read JSON body
5110
5111 # Send a valid initialize response
5112 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
5113 echo "Content-Length: ${#response}"
5114 echo ""
5115 echo -n "$response"
5116
5117 # Keep running to avoid EOF
5118 sleep 10
5119 "#;
5120
5121 let handle = LspHandle::spawn(
5123 &runtime,
5124 "bash",
5125 &["-c".to_string(), fake_lsp_script.to_string()],
5126 Default::default(),
5127 LanguageScope::single("fake"),
5128 "test-server".to_string(),
5129 &async_bridge,
5130 ProcessLimits::unlimited(),
5131 Default::default(),
5132 )
5133 .unwrap();
5134
5135 let init_result = handle.initialize(None, None);
5137 assert!(
5138 init_result.is_ok(),
5139 "initialize() failed from Starting state: {:?}",
5140 init_result.err()
5141 );
5142
5143 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
5145
5146 let messages = async_bridge.try_recv_all();
5148 let has_status_update = messages
5149 .iter()
5150 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
5151
5152 assert!(
5153 has_status_update,
5154 "Expected status update messages from LSP initialization"
5155 );
5156
5157 #[allow(clippy::let_underscore_must_use)]
5159 let _ = handle.shutdown();
5160 }
5161}