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, SemanticTokensLegend, 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
359fn extract_semantic_token_capability(
360 capabilities: &ServerCapabilities,
361) -> (Option<SemanticTokensLegend>, bool, bool, bool) {
362 capabilities
363 .semantic_tokens_provider
364 .as_ref()
365 .map(|provider| match provider {
366 SemanticTokensServerCapabilities::SemanticTokensOptions(options) => (
367 Some(options.legend.clone()),
368 semantic_tokens_full_supported(&options.full),
369 semantic_tokens_full_delta_supported(&options.full),
370 options.range.unwrap_or(false),
371 ),
372 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(options) => {
373 let legend = options.semantic_tokens_options.legend.clone();
374 let full = semantic_tokens_full_supported(&options.semantic_tokens_options.full);
375 let delta =
376 semantic_tokens_full_delta_supported(&options.semantic_tokens_options.full);
377 let range = options.semantic_tokens_options.range.unwrap_or(false);
378 (Some(legend), full, delta, range)
379 }
380 })
381 .unwrap_or((None, false, false, false))
382}
383
384fn semantic_tokens_full_supported(full: &Option<SemanticTokensFullOptions>) -> bool {
385 match full {
386 Some(SemanticTokensFullOptions::Bool(v)) => *v,
387 Some(SemanticTokensFullOptions::Delta { .. }) => true,
388 None => false,
389 }
390}
391
392fn semantic_tokens_full_delta_supported(full: &Option<SemanticTokensFullOptions>) -> bool {
393 match full {
394 Some(SemanticTokensFullOptions::Delta { delta }) => delta.unwrap_or(false),
395 _ => false,
396 }
397}
398
399fn folding_ranges_supported(capabilities: &ServerCapabilities) -> bool {
400 match capabilities.folding_range_provider.as_ref() {
401 Some(lsp_types::FoldingRangeProviderCapability::Simple(v)) => *v,
402 Some(_) => true,
403 None => false,
404 }
405}
406
407#[derive(Debug)]
409enum LspCommand {
410 Initialize {
412 root_uri: Option<Uri>,
413 initialization_options: Option<Value>,
414 response: oneshot::Sender<Result<InitializeResult, String>>,
415 },
416
417 DidOpen {
419 uri: Uri,
420 text: String,
421 language_id: String,
422 },
423
424 DidChange {
426 uri: Uri,
427 content_changes: Vec<TextDocumentContentChangeEvent>,
428 },
429
430 DidClose { uri: Uri },
432
433 DidSave { uri: Uri, text: Option<String> },
435
436 DidChangeWorkspaceFolders {
438 added: Vec<lsp_types::WorkspaceFolder>,
439 removed: Vec<lsp_types::WorkspaceFolder>,
440 },
441
442 Completion {
444 request_id: u64,
445 uri: Uri,
446 line: u32,
447 character: u32,
448 },
449
450 GotoDefinition {
452 request_id: u64,
453 uri: Uri,
454 line: u32,
455 character: u32,
456 },
457
458 Rename {
460 request_id: u64,
461 uri: Uri,
462 line: u32,
463 character: u32,
464 new_name: String,
465 },
466
467 Hover {
469 request_id: u64,
470 uri: Uri,
471 line: u32,
472 character: u32,
473 },
474
475 References {
477 request_id: u64,
478 uri: Uri,
479 line: u32,
480 character: u32,
481 },
482
483 SignatureHelp {
485 request_id: u64,
486 uri: Uri,
487 line: u32,
488 character: u32,
489 },
490
491 CodeActions {
493 request_id: u64,
494 uri: Uri,
495 start_line: u32,
496 start_char: u32,
497 end_line: u32,
498 end_char: u32,
499 diagnostics: Vec<lsp_types::Diagnostic>,
500 },
501
502 DocumentDiagnostic {
504 request_id: u64,
505 uri: Uri,
506 previous_result_id: Option<String>,
508 },
509
510 InlayHints {
512 request_id: u64,
513 uri: Uri,
514 start_line: u32,
516 start_char: u32,
517 end_line: u32,
518 end_char: u32,
519 },
520
521 FoldingRange { request_id: u64, uri: Uri },
523
524 SemanticTokensFull { request_id: u64, uri: Uri },
526
527 SemanticTokensFullDelta {
529 request_id: u64,
530 uri: Uri,
531 previous_result_id: String,
532 },
533
534 SemanticTokensRange {
536 request_id: u64,
537 uri: Uri,
538 range: lsp_types::Range,
539 },
540
541 CancelRequest {
543 request_id: u64,
545 },
546
547 PluginRequest {
549 request_id: u64,
550 method: String,
551 params: Option<Value>,
552 },
553
554 Shutdown,
556}
557
558struct LspState {
560 stdin: Arc<tokio::sync::Mutex<ChildStdin>>,
562
563 next_id: i64,
565
566 capabilities: Option<ServerCapabilities>,
568
569 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
571
572 pending_opens: HashMap<PathBuf, Instant>,
575
576 initialized: bool,
578
579 async_tx: std_mpsc::Sender<AsyncMessage>,
581
582 language: String,
584
585 active_requests: HashMap<u64, i64>,
588
589 language_id_overrides: HashMap<String, String>,
591}
592
593#[allow(clippy::let_underscore_must_use)]
599impl LspState {
600 #[allow(clippy::type_complexity)]
602 async fn replay_pending_commands(
603 &mut self,
604 commands: Vec<LspCommand>,
605 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
606 ) {
607 if commands.is_empty() {
608 return;
609 }
610 tracing::info!(
611 "Replaying {} pending commands after initialization",
612 commands.len()
613 );
614 for cmd in commands {
615 match cmd {
616 LspCommand::DidOpen {
617 uri,
618 text,
619 language_id,
620 } => {
621 tracing::info!("Replaying DidOpen for {}", uri.as_str());
622 let _ = self
623 .handle_did_open_sequential(uri, text, language_id, pending)
624 .await;
625 }
626 LspCommand::DidChange {
627 uri,
628 content_changes,
629 } => {
630 tracing::info!("Replaying DidChange for {}", uri.as_str());
631 let _ = self
632 .handle_did_change_sequential(uri, content_changes, pending)
633 .await;
634 }
635 LspCommand::DidClose { uri } => {
636 tracing::info!("Replaying DidClose for {}", uri.as_str());
637 let _ = self.handle_did_close(uri).await;
638 }
639 LspCommand::DidSave { uri, text } => {
640 tracing::info!("Replaying DidSave for {}", uri.as_str());
641 let _ = self.handle_did_save(uri, text).await;
642 }
643 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
644 tracing::info!(
645 "Replaying DidChangeWorkspaceFolders: +{} -{}",
646 added.len(),
647 removed.len()
648 );
649 let _ = self
650 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
651 lsp_types::DidChangeWorkspaceFoldersParams {
652 event: lsp_types::WorkspaceFoldersChangeEvent { added, removed },
653 },
654 )
655 .await;
656 }
657 LspCommand::SemanticTokensFull { request_id, uri } => {
658 tracing::info!("Replaying semantic tokens request for {}", uri.as_str());
659 let _ = self
660 .handle_semantic_tokens_full(request_id, uri, pending)
661 .await;
662 }
663 LspCommand::SemanticTokensFullDelta {
664 request_id,
665 uri,
666 previous_result_id,
667 } => {
668 tracing::info!(
669 "Replaying semantic tokens delta request for {}",
670 uri.as_str()
671 );
672 let _ = self
673 .handle_semantic_tokens_full_delta(
674 request_id,
675 uri,
676 previous_result_id,
677 pending,
678 )
679 .await;
680 }
681 LspCommand::SemanticTokensRange {
682 request_id,
683 uri,
684 range,
685 } => {
686 tracing::info!(
687 "Replaying semantic tokens range request for {}",
688 uri.as_str()
689 );
690 let _ = self
691 .handle_semantic_tokens_range(request_id, uri, range, pending)
692 .await;
693 }
694 LspCommand::FoldingRange { request_id, uri } => {
695 tracing::info!("Replaying folding range request for {}", uri.as_str());
696 let _ = self.handle_folding_ranges(request_id, uri, pending).await;
697 }
698 _ => {}
699 }
700 }
701 }
702
703 async fn write_message<T: Serialize>(&mut self, message: &T) -> Result<(), String> {
705 let json =
706 serde_json::to_string(message).map_err(|e| format!("Serialization error: {}", e))?;
707
708 let content = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
709
710 tracing::trace!("Writing LSP message to stdin ({} bytes)", content.len());
711
712 let mut stdin = self.stdin.lock().await;
713 stdin
714 .write_all(content.as_bytes())
715 .await
716 .map_err(|e| format!("Failed to write to stdin: {}", e))?;
717
718 stdin
719 .flush()
720 .await
721 .map_err(|e| format!("Failed to flush stdin: {}", e))?;
722
723 tracing::trace!("Successfully sent LSP message");
724
725 Ok(())
726 }
727
728 async fn send_notification<N>(&mut self, params: N::Params) -> Result<(), String>
730 where
731 N: Notification,
732 {
733 let notification = JsonRpcNotification {
734 jsonrpc: "2.0".to_string(),
735 method: N::METHOD.to_string(),
736 params: Some(
737 serde_json::to_value(params)
738 .map_err(|e| format!("Failed to serialize params: {}", e))?,
739 ),
740 };
741
742 self.write_message(¬ification).await
743 }
744
745 #[allow(clippy::type_complexity)]
747 async fn send_request_sequential<P: Serialize, R: for<'de> Deserialize<'de>>(
748 &mut self,
749 method: &str,
750 params: Option<P>,
751 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
752 ) -> Result<R, String> {
753 self.send_request_sequential_tracked(method, params, pending, None)
754 .await
755 }
756
757 #[allow(clippy::type_complexity)]
759 async fn send_request_sequential_tracked<P: Serialize, R: for<'de> Deserialize<'de>>(
760 &mut self,
761 method: &str,
762 params: Option<P>,
763 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
764 editor_request_id: Option<u64>,
765 ) -> Result<R, String> {
766 let id = self.next_id;
767 self.next_id += 1;
768
769 if let Some(editor_id) = editor_request_id {
771 self.active_requests.insert(editor_id, id);
772 tracing::trace!("Tracking request: editor_id={}, lsp_id={}", editor_id, id);
773 }
774
775 let params_value = params
776 .map(|p| serde_json::to_value(p))
777 .transpose()
778 .map_err(|e| format!("Failed to serialize params: {}", e))?;
779 let request = JsonRpcRequest {
780 jsonrpc: "2.0".to_string(),
781 id,
782 method: method.to_string(),
783 params: params_value,
784 };
785
786 let (tx, rx) = oneshot::channel();
787 pending.lock().unwrap().insert(id, tx);
788
789 self.write_message(&request).await?;
790
791 tracing::trace!("Sent LSP request id={}, waiting for response...", id);
792
793 let result = rx
795 .await
796 .map_err(|_| "Response channel closed".to_string())??;
797
798 tracing::trace!("Received LSP response for request id={}", id);
799
800 if let Some(editor_id) = editor_request_id {
802 self.active_requests.remove(&editor_id);
803 tracing::trace!("Completed request: editor_id={}, lsp_id={}", editor_id, id);
804 }
805
806 serde_json::from_value(result).map_err(|e| format!("Failed to deserialize response: {}", e))
807 }
808
809 #[allow(clippy::type_complexity)]
811 async fn handle_initialize_sequential(
812 &mut self,
813 root_uri: Option<Uri>,
814 initialization_options: Option<Value>,
815 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
816 ) -> Result<InitializeResult, String> {
817 tracing::info!(
818 "Initializing async LSP server with root_uri: {:?}, initialization_options: {:?}",
819 root_uri,
820 initialization_options
821 );
822
823 let workspace_folders = root_uri.as_ref().map(|uri| {
824 vec![WorkspaceFolder {
825 uri: uri.clone(),
826 name: uri
827 .path()
828 .as_str()
829 .split('/')
830 .next_back()
831 .unwrap_or("workspace")
832 .to_string(),
833 }]
834 });
835
836 #[allow(deprecated)]
837 let params = InitializeParams {
838 process_id: Some(std::process::id()),
839 capabilities: create_client_capabilities(),
840 workspace_folders,
841 initialization_options,
842 root_uri: root_uri.clone(),
845 ..Default::default()
846 };
847
848 let result: InitializeResult = self
849 .send_request_sequential(Initialize::METHOD, Some(params), pending)
850 .await?;
851
852 self.capabilities = Some(result.capabilities.clone());
853
854 self.send_notification::<Initialized>(InitializedParams {})
856 .await?;
857
858 self.initialized = true;
859
860 let completion_trigger_characters = result
862 .capabilities
863 .completion_provider
864 .as_ref()
865 .and_then(|cp| cp.trigger_characters.clone())
866 .unwrap_or_default();
867
868 let (
869 semantic_tokens_legend,
870 semantic_tokens_full,
871 semantic_tokens_full_delta,
872 semantic_tokens_range,
873 ) = extract_semantic_token_capability(&result.capabilities);
874 let folding_ranges_supported = folding_ranges_supported(&result.capabilities);
875
876 let _ = self.async_tx.send(AsyncMessage::LspInitialized {
878 language: self.language.clone(),
879 completion_trigger_characters,
880 semantic_tokens_legend,
881 semantic_tokens_full,
882 semantic_tokens_full_delta,
883 semantic_tokens_range,
884 folding_ranges_supported,
885 });
886
887 let _ = self.async_tx.send(AsyncMessage::LspStatusUpdate {
889 language: self.language.clone(),
890 status: LspServerStatus::Running,
891 message: None,
892 });
893
894 tracing::info!("Async LSP server initialized successfully");
895
896 Ok(result)
897 }
898
899 #[allow(clippy::type_complexity)]
901 async fn handle_did_open_sequential(
902 &mut self,
903 uri: Uri,
904 text: String,
905 language_id: String,
906 _pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
907 ) -> Result<(), String> {
908 let path = PathBuf::from(uri.path().as_str());
909
910 if should_skip_did_open(&self.document_versions, &path, &self.language, &uri) {
911 return Ok(());
912 }
913
914 tracing::trace!("LSP: did_open for {}", uri.as_str());
915
916 let lsp_language_id = path
919 .extension()
920 .and_then(|e| e.to_str())
921 .and_then(|ext| self.language_id_overrides.get(ext))
922 .cloned()
923 .unwrap_or(language_id);
924
925 let params = DidOpenTextDocumentParams {
926 text_document: TextDocumentItem {
927 uri: uri.clone(),
928 language_id: lsp_language_id,
929 version: 0,
930 text,
931 },
932 };
933
934 self.document_versions
935 .lock()
936 .unwrap()
937 .insert(path.clone(), 0);
938
939 self.pending_opens.insert(path, Instant::now());
941
942 self.send_notification::<DidOpenTextDocument>(params).await
943 }
944
945 #[allow(clippy::type_complexity)]
947 async fn handle_did_change_sequential(
948 &mut self,
949 uri: Uri,
950 content_changes: Vec<TextDocumentContentChangeEvent>,
951 _pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
952 ) -> Result<(), String> {
953 tracing::trace!("LSP: did_change for {}", uri.as_str());
954
955 let path = PathBuf::from(uri.path().as_str());
956
957 if !self.document_versions.lock().unwrap().contains_key(&path) {
960 tracing::debug!(
961 "LSP ({}): skipping didChange - document not yet opened",
962 self.language
963 );
964 return Ok(());
965 }
966
967 if let Some(opened_at) = self.pending_opens.get(&path) {
971 let elapsed = opened_at.elapsed();
972 let grace_period = std::time::Duration::from_millis(DID_OPEN_GRACE_PERIOD_MS);
973 if elapsed < grace_period {
974 let wait_time = grace_period - elapsed;
975 tracing::debug!(
976 "LSP ({}): waiting {:?} for didOpen grace period before didChange",
977 self.language,
978 wait_time
979 );
980 tokio::time::sleep(wait_time).await;
981 }
982 self.pending_opens.remove(&path);
984 }
985
986 let new_version = {
987 let mut versions = self.document_versions.lock().unwrap();
988 let version = versions.entry(path).or_insert(0);
989 *version += 1;
990 *version
991 };
992
993 let params = DidChangeTextDocumentParams {
994 text_document: VersionedTextDocumentIdentifier {
995 uri: uri.clone(),
996 version: new_version as i32,
997 },
998 content_changes,
999 };
1000
1001 self.send_notification::<DidChangeTextDocument>(params)
1002 .await
1003 }
1004
1005 async fn handle_did_save(&mut self, uri: Uri, text: Option<String>) -> Result<(), String> {
1007 tracing::trace!("LSP: did_save for {}", uri.as_str());
1008
1009 let params = DidSaveTextDocumentParams {
1010 text_document: TextDocumentIdentifier { uri },
1011 text,
1012 };
1013
1014 self.send_notification::<DidSaveTextDocument>(params).await
1015 }
1016
1017 async fn handle_did_close(&mut self, uri: Uri) -> Result<(), String> {
1019 let path = PathBuf::from(uri.path().as_str());
1020
1021 if self
1023 .document_versions
1024 .lock()
1025 .unwrap()
1026 .remove(&path)
1027 .is_some()
1028 {
1029 tracing::info!("LSP ({}): didClose for {}", self.language, uri.as_str());
1030 } else {
1031 tracing::debug!(
1032 "LSP ({}): didClose for {} but document was not tracked",
1033 self.language,
1034 uri.as_str()
1035 );
1036 }
1037
1038 self.pending_opens.remove(&path);
1040
1041 let params = DidCloseTextDocumentParams {
1042 text_document: TextDocumentIdentifier { uri },
1043 };
1044
1045 self.send_notification::<DidCloseTextDocument>(params).await
1046 }
1047
1048 #[allow(clippy::type_complexity)]
1050 async fn handle_completion(
1051 &mut self,
1052 request_id: u64,
1053 uri: Uri,
1054 line: u32,
1055 character: u32,
1056 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1057 ) -> Result<(), String> {
1058 use lsp_types::{
1059 CompletionParams, PartialResultParams, Position, TextDocumentIdentifier,
1060 TextDocumentPositionParams, WorkDoneProgressParams,
1061 };
1062
1063 tracing::trace!(
1064 "LSP: completion request at {}:{}:{}",
1065 uri.as_str(),
1066 line,
1067 character
1068 );
1069
1070 let params = CompletionParams {
1071 text_document_position: TextDocumentPositionParams {
1072 text_document: TextDocumentIdentifier { uri },
1073 position: Position { line, character },
1074 },
1075 work_done_progress_params: WorkDoneProgressParams::default(),
1076 partial_result_params: PartialResultParams::default(),
1077 context: None,
1078 };
1079
1080 match self
1082 .send_request_sequential_tracked::<_, Value>(
1083 "textDocument/completion",
1084 Some(params),
1085 pending,
1086 Some(request_id),
1087 )
1088 .await
1089 {
1090 Ok(result) => {
1091 let items = if let Ok(list) =
1093 serde_json::from_value::<lsp_types::CompletionList>(result.clone())
1094 {
1095 list.items
1096 } else {
1097 serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result)
1098 .unwrap_or_default()
1099 };
1100
1101 let _ = self
1103 .async_tx
1104 .send(AsyncMessage::LspCompletion { request_id, items });
1105 Ok(())
1106 }
1107 Err(e) => {
1108 tracing::debug!("Completion request failed: {}", e);
1109 let _ = self.async_tx.send(AsyncMessage::LspCompletion {
1111 request_id,
1112 items: vec![],
1113 });
1114 Err(e)
1115 }
1116 }
1117 }
1118
1119 #[allow(clippy::type_complexity)]
1121 async fn handle_goto_definition(
1122 &mut self,
1123 request_id: u64,
1124 uri: Uri,
1125 line: u32,
1126 character: u32,
1127 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1128 ) -> Result<(), String> {
1129 use lsp_types::{
1130 GotoDefinitionParams, PartialResultParams, Position, TextDocumentIdentifier,
1131 TextDocumentPositionParams, WorkDoneProgressParams,
1132 };
1133
1134 tracing::trace!(
1135 "LSP: go-to-definition request at {}:{}:{}",
1136 uri.as_str(),
1137 line,
1138 character
1139 );
1140
1141 let params = GotoDefinitionParams {
1142 text_document_position_params: TextDocumentPositionParams {
1143 text_document: TextDocumentIdentifier { uri },
1144 position: Position { line, character },
1145 },
1146 work_done_progress_params: WorkDoneProgressParams::default(),
1147 partial_result_params: PartialResultParams::default(),
1148 };
1149
1150 match self
1152 .send_request_sequential::<_, Value>("textDocument/definition", Some(params), pending)
1153 .await
1154 {
1155 Ok(result) => {
1156 let locations = if let Ok(loc) =
1158 serde_json::from_value::<lsp_types::Location>(result.clone())
1159 {
1160 vec![loc]
1161 } else if let Ok(locs) =
1162 serde_json::from_value::<Vec<lsp_types::Location>>(result.clone())
1163 {
1164 locs
1165 } else if let Ok(links) =
1166 serde_json::from_value::<Vec<lsp_types::LocationLink>>(result)
1167 {
1168 links
1170 .into_iter()
1171 .map(|link| lsp_types::Location {
1172 uri: link.target_uri,
1173 range: link.target_selection_range,
1174 })
1175 .collect()
1176 } else {
1177 vec![]
1178 };
1179
1180 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1182 request_id,
1183 locations,
1184 });
1185 Ok(())
1186 }
1187 Err(e) => {
1188 tracing::debug!("Go-to-definition request failed: {}", e);
1189 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1191 request_id,
1192 locations: vec![],
1193 });
1194 Err(e)
1195 }
1196 }
1197 }
1198
1199 #[allow(clippy::type_complexity)]
1201 async fn handle_rename(
1202 &mut self,
1203 request_id: u64,
1204 uri: Uri,
1205 line: u32,
1206 character: u32,
1207 new_name: String,
1208 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1209 ) -> Result<(), String> {
1210 use lsp_types::{
1211 Position, RenameParams, TextDocumentIdentifier, TextDocumentPositionParams,
1212 WorkDoneProgressParams,
1213 };
1214
1215 tracing::trace!(
1216 "LSP: rename request at {}:{}:{} to '{}'",
1217 uri.as_str(),
1218 line,
1219 character,
1220 new_name
1221 );
1222
1223 let params = RenameParams {
1224 text_document_position: TextDocumentPositionParams {
1225 text_document: TextDocumentIdentifier { uri },
1226 position: Position { line, character },
1227 },
1228 new_name,
1229 work_done_progress_params: WorkDoneProgressParams::default(),
1230 };
1231
1232 match self
1234 .send_request_sequential::<_, Value>("textDocument/rename", Some(params), pending)
1235 .await
1236 {
1237 Ok(result) => {
1238 match serde_json::from_value::<lsp_types::WorkspaceEdit>(result) {
1240 Ok(workspace_edit) => {
1241 let _ = self.async_tx.send(AsyncMessage::LspRename {
1243 request_id,
1244 result: Ok(workspace_edit),
1245 });
1246 Ok(())
1247 }
1248 Err(e) => {
1249 tracing::error!("Failed to parse rename response: {}", e);
1250 let _ = self.async_tx.send(AsyncMessage::LspRename {
1251 request_id,
1252 result: Err(format!("Failed to parse rename response: {}", e)),
1253 });
1254 Err(format!("Failed to parse rename response: {}", e))
1255 }
1256 }
1257 }
1258 Err(e) => {
1259 tracing::debug!("Rename request failed: {}", e);
1260 let _ = self.async_tx.send(AsyncMessage::LspRename {
1262 request_id,
1263 result: Err(e.clone()),
1264 });
1265 Err(e)
1266 }
1267 }
1268 }
1269
1270 #[allow(clippy::type_complexity)]
1272 async fn handle_hover(
1273 &mut self,
1274 request_id: u64,
1275 uri: Uri,
1276 line: u32,
1277 character: u32,
1278 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1279 ) -> Result<(), String> {
1280 use lsp_types::{
1281 HoverParams, Position, TextDocumentIdentifier, TextDocumentPositionParams,
1282 WorkDoneProgressParams,
1283 };
1284
1285 tracing::trace!(
1286 "LSP: hover request at {}:{}:{}",
1287 uri.as_str(),
1288 line,
1289 character
1290 );
1291
1292 let params = HoverParams {
1293 text_document_position_params: TextDocumentPositionParams {
1294 text_document: TextDocumentIdentifier { uri },
1295 position: Position { line, character },
1296 },
1297 work_done_progress_params: WorkDoneProgressParams::default(),
1298 };
1299
1300 match self
1302 .send_request_sequential::<_, Value>("textDocument/hover", Some(params), pending)
1303 .await
1304 {
1305 Ok(result) => {
1306 tracing::debug!("Raw LSP hover response: {:?}", result);
1307 let (contents, is_markdown, range) = if result.is_null() {
1309 (String::new(), false, None)
1311 } else {
1312 match serde_json::from_value::<lsp_types::Hover>(result) {
1313 Ok(hover) => {
1314 let (contents, is_markdown) =
1316 Self::extract_hover_contents(&hover.contents);
1317 let range = hover.range.map(|r| {
1319 (
1320 (r.start.line, r.start.character),
1321 (r.end.line, r.end.character),
1322 )
1323 });
1324 (contents, is_markdown, range)
1325 }
1326 Err(e) => {
1327 tracing::error!("Failed to parse hover response: {}", e);
1328 (String::new(), false, None)
1329 }
1330 }
1331 };
1332
1333 let _ = self.async_tx.send(AsyncMessage::LspHover {
1335 request_id,
1336 contents,
1337 is_markdown,
1338 range,
1339 });
1340 Ok(())
1341 }
1342 Err(e) => {
1343 tracing::debug!("Hover request failed: {}", e);
1344 let _ = self.async_tx.send(AsyncMessage::LspHover {
1346 request_id,
1347 contents: String::new(),
1348 is_markdown: false,
1349 range: None,
1350 });
1351 Err(e)
1352 }
1353 }
1354 }
1355
1356 fn extract_hover_contents(contents: &lsp_types::HoverContents) -> (String, bool) {
1359 use lsp_types::{HoverContents, MarkedString, MarkupContent, MarkupKind};
1360
1361 match contents {
1362 HoverContents::Scalar(marked) => match marked {
1363 MarkedString::String(s) => (s.clone(), false),
1364 MarkedString::LanguageString(ls) => {
1365 (format!("```{}\n{}\n```", ls.language, ls.value), true)
1367 }
1368 },
1369 HoverContents::Array(arr) => {
1370 let content = arr
1372 .iter()
1373 .map(|marked| match marked {
1374 MarkedString::String(s) => s.clone(),
1375 MarkedString::LanguageString(ls) => {
1376 format!("```{}\n{}\n```", ls.language, ls.value)
1377 }
1378 })
1379 .collect::<Vec<_>>()
1380 .join("\n\n");
1381 (content, true)
1382 }
1383 HoverContents::Markup(MarkupContent { kind, value }) => {
1384 let is_markdown = matches!(kind, MarkupKind::Markdown);
1386 (value.clone(), is_markdown)
1387 }
1388 }
1389 }
1390
1391 #[allow(clippy::type_complexity)]
1393 async fn handle_references(
1394 &mut self,
1395 request_id: u64,
1396 uri: Uri,
1397 line: u32,
1398 character: u32,
1399 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1400 ) -> Result<(), String> {
1401 use lsp_types::{
1402 PartialResultParams, Position, ReferenceContext, ReferenceParams,
1403 TextDocumentIdentifier, WorkDoneProgressParams,
1404 };
1405
1406 tracing::trace!(
1407 "LSP: find references request at {}:{}:{}",
1408 uri.as_str(),
1409 line,
1410 character
1411 );
1412
1413 let params = ReferenceParams {
1414 text_document_position: lsp_types::TextDocumentPositionParams {
1415 text_document: TextDocumentIdentifier { uri },
1416 position: Position { line, character },
1417 },
1418 work_done_progress_params: WorkDoneProgressParams::default(),
1419 partial_result_params: PartialResultParams::default(),
1420 context: ReferenceContext {
1421 include_declaration: true,
1422 },
1423 };
1424
1425 match self
1427 .send_request_sequential::<_, Value>("textDocument/references", Some(params), pending)
1428 .await
1429 {
1430 Ok(result) => {
1431 let locations = if result.is_null() {
1433 Vec::new()
1434 } else {
1435 serde_json::from_value::<Vec<lsp_types::Location>>(result).unwrap_or_default()
1436 };
1437
1438 tracing::trace!("LSP: found {} references", locations.len());
1439
1440 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1442 request_id,
1443 locations,
1444 });
1445 Ok(())
1446 }
1447 Err(e) => {
1448 tracing::debug!("Find references request failed: {}", e);
1449 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1451 request_id,
1452 locations: Vec::new(),
1453 });
1454 Err(e)
1455 }
1456 }
1457 }
1458
1459 #[allow(clippy::type_complexity)]
1461 async fn handle_signature_help(
1462 &mut self,
1463 request_id: u64,
1464 uri: Uri,
1465 line: u32,
1466 character: u32,
1467 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1468 ) -> Result<(), String> {
1469 use lsp_types::{
1470 Position, SignatureHelpParams, TextDocumentIdentifier, TextDocumentPositionParams,
1471 WorkDoneProgressParams,
1472 };
1473
1474 tracing::trace!(
1475 "LSP: signature help request at {}:{}:{}",
1476 uri.as_str(),
1477 line,
1478 character
1479 );
1480
1481 let params = SignatureHelpParams {
1482 text_document_position_params: TextDocumentPositionParams {
1483 text_document: TextDocumentIdentifier { uri },
1484 position: Position { line, character },
1485 },
1486 work_done_progress_params: WorkDoneProgressParams::default(),
1487 context: None, };
1489
1490 match self
1492 .send_request_sequential::<_, Value>(
1493 "textDocument/signatureHelp",
1494 Some(params),
1495 pending,
1496 )
1497 .await
1498 {
1499 Ok(result) => {
1500 let signature_help = if result.is_null() {
1502 None
1503 } else {
1504 serde_json::from_value::<lsp_types::SignatureHelp>(result).ok()
1505 };
1506
1507 tracing::trace!(
1508 "LSP: signature help received: {} signatures",
1509 signature_help
1510 .as_ref()
1511 .map(|h| h.signatures.len())
1512 .unwrap_or(0)
1513 );
1514
1515 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1517 request_id,
1518 signature_help,
1519 });
1520 Ok(())
1521 }
1522 Err(e) => {
1523 tracing::debug!("Signature help request failed: {}", e);
1524 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1526 request_id,
1527 signature_help: None,
1528 });
1529 Err(e)
1530 }
1531 }
1532 }
1533
1534 #[allow(clippy::type_complexity)]
1536 #[allow(clippy::too_many_arguments)]
1537 async fn handle_code_actions(
1538 &mut self,
1539 request_id: u64,
1540 uri: Uri,
1541 start_line: u32,
1542 start_char: u32,
1543 end_line: u32,
1544 end_char: u32,
1545 diagnostics: Vec<lsp_types::Diagnostic>,
1546 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1547 ) -> Result<(), String> {
1548 use lsp_types::{
1549 CodeActionContext, CodeActionParams, PartialResultParams, Position, Range,
1550 TextDocumentIdentifier, WorkDoneProgressParams,
1551 };
1552
1553 tracing::trace!(
1554 "LSP: code actions request at {}:{}:{}-{}:{}",
1555 uri.as_str(),
1556 start_line,
1557 start_char,
1558 end_line,
1559 end_char
1560 );
1561
1562 let params = CodeActionParams {
1563 text_document: TextDocumentIdentifier { uri },
1564 range: Range {
1565 start: Position {
1566 line: start_line,
1567 character: start_char,
1568 },
1569 end: Position {
1570 line: end_line,
1571 character: end_char,
1572 },
1573 },
1574 context: CodeActionContext {
1575 diagnostics,
1576 only: None,
1577 trigger_kind: None,
1578 },
1579 work_done_progress_params: WorkDoneProgressParams::default(),
1580 partial_result_params: PartialResultParams::default(),
1581 };
1582
1583 match self
1585 .send_request_sequential::<_, Value>("textDocument/codeAction", Some(params), pending)
1586 .await
1587 {
1588 Ok(result) => {
1589 let actions = if result.is_null() {
1591 Vec::new()
1592 } else {
1593 serde_json::from_value::<Vec<lsp_types::CodeActionOrCommand>>(result)
1594 .unwrap_or_default()
1595 };
1596
1597 tracing::trace!("LSP: received {} code actions", actions.len());
1598
1599 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1601 request_id,
1602 actions,
1603 });
1604 Ok(())
1605 }
1606 Err(e) => {
1607 tracing::debug!("Code actions request failed: {}", e);
1608 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1610 request_id,
1611 actions: Vec::new(),
1612 });
1613 Err(e)
1614 }
1615 }
1616 }
1617
1618 #[allow(clippy::type_complexity)]
1620 async fn handle_document_diagnostic(
1621 &mut self,
1622 request_id: u64,
1623 uri: Uri,
1624 previous_result_id: Option<String>,
1625 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1626 ) -> Result<(), String> {
1627 use lsp_types::{
1628 DocumentDiagnosticParams, PartialResultParams, TextDocumentIdentifier,
1629 WorkDoneProgressParams,
1630 };
1631
1632 if self
1634 .capabilities
1635 .as_ref()
1636 .and_then(|c| c.diagnostic_provider.as_ref())
1637 .is_none()
1638 {
1639 tracing::trace!(
1640 "LSP: server does not support pull diagnostics, skipping request for {}",
1641 uri.as_str()
1642 );
1643 return Ok(());
1644 }
1645
1646 tracing::trace!(
1647 "LSP: document diagnostic request for {} (previous_result_id: {:?})",
1648 uri.as_str(),
1649 previous_result_id
1650 );
1651
1652 let params = DocumentDiagnosticParams {
1653 text_document: TextDocumentIdentifier { uri: uri.clone() },
1654 identifier: None,
1655 previous_result_id,
1656 work_done_progress_params: WorkDoneProgressParams::default(),
1657 partial_result_params: PartialResultParams::default(),
1658 };
1659
1660 match self
1662 .send_request_sequential::<_, Value>("textDocument/diagnostic", Some(params), pending)
1663 .await
1664 {
1665 Ok(result) => {
1666 let uri_string = uri.as_str().to_string();
1669
1670 if let Ok(full_report) = serde_json::from_value::<
1672 lsp_types::RelatedFullDocumentDiagnosticReport,
1673 >(result.clone())
1674 {
1675 let diagnostics = full_report.full_document_diagnostic_report.items;
1676 let result_id = full_report.full_document_diagnostic_report.result_id;
1677
1678 tracing::trace!(
1679 "LSP: received {} diagnostics for {} (result_id: {:?})",
1680 diagnostics.len(),
1681 uri_string,
1682 result_id
1683 );
1684
1685 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1686 request_id,
1687 uri: uri_string,
1688 result_id,
1689 diagnostics,
1690 unchanged: false,
1691 });
1692 } else if let Ok(unchanged_report) = serde_json::from_value::<
1693 lsp_types::RelatedUnchangedDocumentDiagnosticReport,
1694 >(result.clone())
1695 {
1696 let result_id = unchanged_report
1697 .unchanged_document_diagnostic_report
1698 .result_id;
1699
1700 tracing::trace!(
1701 "LSP: diagnostics unchanged for {} (result_id: {:?})",
1702 uri_string,
1703 result_id
1704 );
1705
1706 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1707 request_id,
1708 uri: uri_string,
1709 result_id: Some(result_id),
1710 diagnostics: Vec::new(),
1711 unchanged: true,
1712 });
1713 } else {
1714 tracing::warn!(
1716 "LSP: could not parse diagnostic report, sending empty: {}",
1717 result
1718 );
1719 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1720 request_id,
1721 uri: uri_string,
1722 result_id: None,
1723 diagnostics: Vec::new(),
1724 unchanged: false,
1725 });
1726 }
1727
1728 Ok(())
1729 }
1730 Err(e) => {
1731 tracing::debug!("Document diagnostic request failed: {}", e);
1732 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1734 request_id,
1735 uri: uri.as_str().to_string(),
1736 result_id: None,
1737 diagnostics: Vec::new(),
1738 unchanged: false,
1739 });
1740 Err(e)
1741 }
1742 }
1743 }
1744
1745 #[allow(clippy::type_complexity)]
1747 #[allow(clippy::too_many_arguments)]
1748 async fn handle_inlay_hints(
1749 &mut self,
1750 request_id: u64,
1751 uri: Uri,
1752 start_line: u32,
1753 start_char: u32,
1754 end_line: u32,
1755 end_char: u32,
1756 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1757 ) -> Result<(), String> {
1758 use lsp_types::{
1759 InlayHintParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
1760 };
1761
1762 tracing::trace!(
1763 "LSP: inlay hints request for {} ({}:{} - {}:{})",
1764 uri.as_str(),
1765 start_line,
1766 start_char,
1767 end_line,
1768 end_char
1769 );
1770
1771 let params = InlayHintParams {
1772 text_document: TextDocumentIdentifier { uri: uri.clone() },
1773 range: Range {
1774 start: Position {
1775 line: start_line,
1776 character: start_char,
1777 },
1778 end: Position {
1779 line: end_line,
1780 character: end_char,
1781 },
1782 },
1783 work_done_progress_params: WorkDoneProgressParams::default(),
1784 };
1785
1786 match self
1787 .send_request_sequential::<_, Option<Vec<lsp_types::InlayHint>>>(
1788 "textDocument/inlayHint",
1789 Some(params),
1790 pending,
1791 )
1792 .await
1793 {
1794 Ok(hints) => {
1795 let hints = hints.unwrap_or_default();
1796 let uri_string = uri.as_str().to_string();
1797
1798 tracing::trace!(
1799 "LSP: received {} inlay hints for {}",
1800 hints.len(),
1801 uri_string
1802 );
1803
1804 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
1805 request_id,
1806 uri: uri_string,
1807 hints,
1808 });
1809
1810 Ok(())
1811 }
1812 Err(e) => {
1813 tracing::debug!("Inlay hints request failed: {}", e);
1814 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
1816 request_id,
1817 uri: uri.as_str().to_string(),
1818 hints: Vec::new(),
1819 });
1820 Err(e)
1821 }
1822 }
1823 }
1824
1825 #[allow(clippy::type_complexity)]
1827 async fn handle_folding_ranges(
1828 &mut self,
1829 request_id: u64,
1830 uri: Uri,
1831 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1832 ) -> Result<(), String> {
1833 use lsp_types::{
1834 FoldingRangeParams, PartialResultParams, TextDocumentIdentifier, WorkDoneProgressParams,
1835 };
1836
1837 tracing::trace!("LSP: folding range request for {}", uri.as_str());
1838
1839 let params = FoldingRangeParams {
1840 text_document: TextDocumentIdentifier { uri: uri.clone() },
1841 work_done_progress_params: WorkDoneProgressParams::default(),
1842 partial_result_params: PartialResultParams::default(),
1843 };
1844
1845 match self
1846 .send_request_sequential::<_, Option<Vec<lsp_types::FoldingRange>>>(
1847 "textDocument/foldingRange",
1848 Some(params),
1849 pending,
1850 )
1851 .await
1852 {
1853 Ok(ranges) => {
1854 let ranges = ranges.unwrap_or_default();
1855 let uri_string = uri.as_str().to_string();
1856
1857 tracing::trace!(
1858 "LSP: received {} folding ranges for {}",
1859 ranges.len(),
1860 uri_string
1861 );
1862
1863 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
1864 request_id,
1865 uri: uri_string,
1866 ranges,
1867 });
1868
1869 Ok(())
1870 }
1871 Err(e) => {
1872 tracing::debug!("Folding range request failed: {}", e);
1873 let _ = self.async_tx.send(AsyncMessage::LspFoldingRanges {
1874 request_id,
1875 uri: uri.as_str().to_string(),
1876 ranges: Vec::new(),
1877 });
1878 Err(e)
1879 }
1880 }
1881 }
1882
1883 #[allow(clippy::type_complexity)]
1884 async fn handle_semantic_tokens_full(
1885 &mut self,
1886 request_id: u64,
1887 uri: Uri,
1888 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1889 ) -> Result<(), String> {
1890 use lsp_types::{
1891 request::SemanticTokensFullRequest, PartialResultParams, TextDocumentIdentifier,
1892 WorkDoneProgressParams,
1893 };
1894
1895 tracing::trace!("LSP: semanticTokens/full request for {}", uri.as_str());
1896
1897 let params = SemanticTokensParams {
1898 work_done_progress_params: WorkDoneProgressParams::default(),
1899 partial_result_params: PartialResultParams::default(),
1900 text_document: TextDocumentIdentifier { uri: uri.clone() },
1901 };
1902
1903 match self
1904 .send_request_sequential_tracked::<_, Option<SemanticTokensResult>>(
1905 SemanticTokensFullRequest::METHOD,
1906 Some(params),
1907 pending,
1908 Some(request_id),
1909 )
1910 .await
1911 {
1912 Ok(result) => {
1913 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1914 request_id,
1915 uri: uri.as_str().to_string(),
1916 response: LspSemanticTokensResponse::Full(Ok(result)),
1917 });
1918 Ok(())
1919 }
1920 Err(e) => {
1921 tracing::debug!("Semantic tokens request failed: {}", e);
1922 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1923 request_id,
1924 uri: uri.as_str().to_string(),
1925 response: LspSemanticTokensResponse::Full(Err(e.clone())),
1926 });
1927 Err(e)
1928 }
1929 }
1930 }
1931
1932 #[allow(clippy::type_complexity)]
1933 async fn handle_semantic_tokens_full_delta(
1934 &mut self,
1935 request_id: u64,
1936 uri: Uri,
1937 previous_result_id: String,
1938 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1939 ) -> Result<(), String> {
1940 use lsp_types::{
1941 request::SemanticTokensFullDeltaRequest, PartialResultParams,
1942 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, TextDocumentIdentifier,
1943 WorkDoneProgressParams,
1944 };
1945
1946 tracing::trace!(
1947 "LSP: semanticTokens/full/delta request for {}",
1948 uri.as_str()
1949 );
1950
1951 let params = SemanticTokensDeltaParams {
1952 work_done_progress_params: WorkDoneProgressParams::default(),
1953 partial_result_params: PartialResultParams::default(),
1954 text_document: TextDocumentIdentifier { uri: uri.clone() },
1955 previous_result_id,
1956 };
1957
1958 match self
1959 .send_request_sequential_tracked::<_, Option<SemanticTokensFullDeltaResult>>(
1960 SemanticTokensFullDeltaRequest::METHOD,
1961 Some(params),
1962 pending,
1963 Some(request_id),
1964 )
1965 .await
1966 {
1967 Ok(result) => {
1968 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1969 request_id,
1970 uri: uri.as_str().to_string(),
1971 response: LspSemanticTokensResponse::FullDelta(Ok(result)),
1972 });
1973 Ok(())
1974 }
1975 Err(e) => {
1976 tracing::debug!("Semantic tokens delta request failed: {}", e);
1977 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1978 request_id,
1979 uri: uri.as_str().to_string(),
1980 response: LspSemanticTokensResponse::FullDelta(Err(e.clone())),
1981 });
1982 Err(e)
1983 }
1984 }
1985 }
1986
1987 #[allow(clippy::type_complexity)]
1988 async fn handle_semantic_tokens_range(
1989 &mut self,
1990 request_id: u64,
1991 uri: Uri,
1992 range: lsp_types::Range,
1993 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1994 ) -> Result<(), String> {
1995 use lsp_types::{
1996 request::SemanticTokensRangeRequest, PartialResultParams, SemanticTokensRangeParams,
1997 TextDocumentIdentifier, WorkDoneProgressParams,
1998 };
1999
2000 tracing::trace!("LSP: semanticTokens/range request for {}", uri.as_str());
2001
2002 let params = SemanticTokensRangeParams {
2003 work_done_progress_params: WorkDoneProgressParams::default(),
2004 partial_result_params: PartialResultParams::default(),
2005 text_document: TextDocumentIdentifier { uri: uri.clone() },
2006 range,
2007 };
2008
2009 match self
2010 .send_request_sequential_tracked::<_, Option<lsp_types::SemanticTokensRangeResult>>(
2011 SemanticTokensRangeRequest::METHOD,
2012 Some(params),
2013 pending,
2014 Some(request_id),
2015 )
2016 .await
2017 {
2018 Ok(result) => {
2019 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2020 request_id,
2021 uri: uri.as_str().to_string(),
2022 response: LspSemanticTokensResponse::Range(Ok(result)),
2023 });
2024 Ok(())
2025 }
2026 Err(e) => {
2027 tracing::debug!("Semantic tokens range request failed: {}", e);
2028 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
2029 request_id,
2030 uri: uri.as_str().to_string(),
2031 response: LspSemanticTokensResponse::Range(Err(e.clone())),
2032 });
2033 Err(e)
2034 }
2035 }
2036 }
2037
2038 #[allow(clippy::type_complexity)]
2040 async fn handle_plugin_request(
2041 &mut self,
2042 request_id: u64,
2043 method: String,
2044 params: Option<Value>,
2045 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2046 ) {
2047 tracing::trace!(
2048 "Plugin request {} => method={} params={:?}",
2049 request_id,
2050 method,
2051 params
2052 );
2053 let result = self
2054 .send_request_sequential_tracked::<Value, Value>(
2055 &method,
2056 params,
2057 pending,
2058 Some(request_id),
2059 )
2060 .await;
2061
2062 tracing::trace!(
2063 "Plugin request {} completed with result {:?}",
2064 request_id,
2065 &result
2066 );
2067 let _ = self.async_tx.send(AsyncMessage::PluginLspResponse {
2068 language: self.language.clone(),
2069 request_id,
2070 result,
2071 });
2072 }
2073
2074 async fn handle_shutdown(&mut self) -> Result<(), String> {
2076 tracing::info!("Shutting down async LSP server");
2077
2078 let notification = JsonRpcNotification {
2079 jsonrpc: "2.0".to_string(),
2080 method: "shutdown".to_string(),
2081 params: None,
2082 };
2083
2084 self.write_message(¬ification).await?;
2085
2086 let exit = JsonRpcNotification {
2087 jsonrpc: "2.0".to_string(),
2088 method: "exit".to_string(),
2089 params: None,
2090 };
2091
2092 self.write_message(&exit).await
2093 }
2094
2095 async fn send_cancel_request(&mut self, lsp_id: i64) -> Result<(), String> {
2097 tracing::trace!("Sending $/cancelRequest for LSP id {}", lsp_id);
2098
2099 let notification = JsonRpcNotification {
2100 jsonrpc: "2.0".to_string(),
2101 method: "$/cancelRequest".to_string(),
2102 params: Some(serde_json::json!({ "id": lsp_id })),
2103 };
2104
2105 self.write_message(¬ification).await
2106 }
2107
2108 async fn handle_cancel_request(&mut self, request_id: u64) -> Result<(), String> {
2110 if let Some(lsp_id) = self.active_requests.remove(&request_id) {
2111 tracing::info!(
2112 "Cancelling request: editor_id={}, lsp_id={}",
2113 request_id,
2114 lsp_id
2115 );
2116 self.send_cancel_request(lsp_id).await
2117 } else {
2118 tracing::trace!(
2119 "Cancel request ignored: no active LSP request for editor_id={}",
2120 request_id
2121 );
2122 Ok(())
2123 }
2124 }
2125}
2126
2127struct LspTask {
2129 _process: Child,
2131
2132 stdin: ChildStdin,
2134
2135 stdout: BufReader<ChildStdout>,
2137
2138 next_id: i64,
2140
2141 pending: HashMap<i64, oneshot::Sender<Result<Value, String>>>,
2143
2144 capabilities: Option<ServerCapabilities>,
2146
2147 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2149
2150 pending_opens: HashMap<PathBuf, Instant>,
2153
2154 initialized: bool,
2156
2157 async_tx: std_mpsc::Sender<AsyncMessage>,
2159
2160 language: String,
2162
2163 server_command: String,
2165
2166 stderr_log_path: std::path::PathBuf,
2168
2169 language_id_overrides: HashMap<String, String>,
2171}
2172
2173impl LspTask {
2174 async fn spawn(
2176 command: &str,
2177 args: &[String],
2178 env: &std::collections::HashMap<String, String>,
2179 language: String,
2180 async_tx: std_mpsc::Sender<AsyncMessage>,
2181 process_limits: &ProcessLimits,
2182 stderr_log_path: std::path::PathBuf,
2183 language_id_overrides: HashMap<String, String>,
2184 ) -> Result<Self, String> {
2185 tracing::info!("Spawning async LSP server: {} {:?}", command, args);
2186 tracing::info!("Process limits: {:?}", process_limits);
2187 tracing::info!("LSP stderr will be logged to: {:?}", stderr_log_path);
2188
2189 if !Self::command_exists(command) {
2192 return Err(format!(
2193 "LSP server executable '{}' not found. Please install it or check your PATH.",
2194 command
2195 ));
2196 }
2197
2198 let stderr_file = std::fs::File::create(&stderr_log_path).map_err(|e| {
2200 format!(
2201 "Failed to create LSP stderr log file {:?}: {}",
2202 stderr_log_path, e
2203 )
2204 })?;
2205
2206 let mut cmd = Command::new(command);
2207 cmd.args(args)
2208 .envs(env)
2209 .stdin(std::process::Stdio::piped())
2210 .stdout(std::process::Stdio::piped())
2211 .stderr(std::process::Stdio::from(stderr_file))
2212 .kill_on_drop(true);
2213
2214 process_limits
2216 .apply_to_command(&mut cmd)
2217 .map_err(|e| format!("Failed to apply process limits: {}", e))?;
2218
2219 let mut process = cmd.spawn().map_err(|e| {
2220 format!(
2221 "Failed to spawn LSP server '{}': {}",
2222 command,
2223 match e.kind() {
2224 std::io::ErrorKind::NotFound => "executable not found in PATH".to_string(),
2225 std::io::ErrorKind::PermissionDenied =>
2226 "permission denied (check file permissions)".to_string(),
2227 _ => e.to_string(),
2228 }
2229 )
2230 })?;
2231
2232 let stdin = process
2233 .stdin
2234 .take()
2235 .ok_or_else(|| "Failed to get stdin".to_string())?;
2236
2237 let stdout = BufReader::new(
2238 process
2239 .stdout
2240 .take()
2241 .ok_or_else(|| "Failed to get stdout".to_string())?,
2242 );
2243
2244 Ok(Self {
2245 _process: process,
2246 stdin,
2247 stdout,
2248 next_id: 0,
2249 pending: HashMap::new(),
2250 capabilities: None,
2251 document_versions: Arc::new(std::sync::Mutex::new(HashMap::new())),
2252 pending_opens: HashMap::new(),
2253 initialized: false,
2254 async_tx,
2255 language,
2256 server_command: command.to_string(),
2257 stderr_log_path,
2258 language_id_overrides,
2259 })
2260 }
2261
2262 fn command_exists(command: &str) -> bool {
2264 use std::path::Path;
2265
2266 if command.contains('/') || command.contains('\\') {
2268 let path = Path::new(command);
2269 return path.exists() && path.is_file();
2270 }
2271
2272 if let Ok(path_var) = std::env::var("PATH") {
2274 #[cfg(unix)]
2275 let separator = ':';
2276 #[cfg(windows)]
2277 let separator = ';';
2278
2279 for dir in path_var.split(separator) {
2280 let full_path = Path::new(dir).join(command);
2281 if full_path.exists() && full_path.is_file() {
2282 return true;
2283 }
2284 #[cfg(windows)]
2286 {
2287 let with_exe = Path::new(dir).join(format!("{}.exe", command));
2288 if with_exe.exists() && with_exe.is_file() {
2289 return true;
2290 }
2291 }
2292 }
2293 }
2294
2295 false
2296 }
2297
2298 #[allow(clippy::type_complexity)]
2300 #[allow(clippy::too_many_arguments)]
2301 #[allow(clippy::let_underscore_must_use)] fn spawn_stdout_reader(
2303 mut stdout: BufReader<ChildStdout>,
2304 pending: Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2305 async_tx: std_mpsc::Sender<AsyncMessage>,
2306 language: String,
2307 server_command: String,
2308 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2309 stderr_log_path: std::path::PathBuf,
2310 shutting_down: Arc<AtomicBool>,
2311 document_versions: Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2312 ) {
2313 tokio::spawn(async move {
2314 tracing::info!("LSP stdout reader task started for {}", language);
2315 loop {
2316 match read_message_from_stdout(&mut stdout).await {
2317 Ok(message) => {
2318 tracing::trace!("Read message from LSP server: {:?}", message);
2319 if let Err(e) = handle_message_dispatch(
2320 message,
2321 &pending,
2322 &async_tx,
2323 &language,
2324 &server_command,
2325 &stdin_writer,
2326 &document_versions,
2327 )
2328 .await
2329 {
2330 tracing::error!("Error handling LSP message: {}", e);
2331 }
2332 }
2333 Err(e) => {
2334 if shutting_down.load(Ordering::SeqCst) {
2336 tracing::info!(
2337 "LSP stdout reader exiting due to graceful shutdown for {}",
2338 language
2339 );
2340 } else {
2341 tracing::error!("Error reading from LSP server: {}", e);
2342 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2343 language: language.clone(),
2344 status: LspServerStatus::Error,
2345 message: None,
2346 });
2347 let _ = async_tx.send(AsyncMessage::LspError {
2348 language: language.clone(),
2349 error: format!("Read error: {}", e),
2350 stderr_log_path: Some(stderr_log_path.clone()),
2351 });
2352 }
2353 break;
2354 }
2355 }
2356 }
2357 tracing::info!("LSP stdout reader task exiting for {}", language);
2358 });
2359 }
2360
2361 #[allow(clippy::let_underscore_must_use)]
2365 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
2366 tracing::info!("LspTask::run() started for language: {}", self.language);
2367
2368 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
2370
2371 let mut state = LspState {
2373 stdin: stdin_writer.clone(),
2374 next_id: self.next_id,
2375 capabilities: self.capabilities,
2376 document_versions: self.document_versions.clone(),
2377 pending_opens: self.pending_opens,
2378 initialized: self.initialized,
2379 async_tx: self.async_tx.clone(),
2380 language: self.language.clone(),
2381 active_requests: HashMap::new(),
2382 language_id_overrides: self.language_id_overrides.clone(),
2383 };
2384
2385 let pending = Arc::new(Mutex::new(self.pending));
2386 let async_tx = state.async_tx.clone();
2387 let language_clone = state.language.clone();
2388
2389 let shutting_down = Arc::new(AtomicBool::new(false));
2391
2392 Self::spawn_stdout_reader(
2394 self.stdout,
2395 pending.clone(),
2396 async_tx.clone(),
2397 language_clone.clone(),
2398 self.server_command.clone(),
2399 stdin_writer.clone(),
2400 self.stderr_log_path,
2401 shutting_down.clone(),
2402 self.document_versions.clone(),
2403 );
2404
2405 let mut pending_commands = Vec::new();
2410 loop {
2411 tokio::select! {
2412 Some(cmd) = command_rx.recv() => {
2414 tracing::trace!("LspTask received command: {:?}", cmd);
2415 match cmd {
2416 LspCommand::Initialize { root_uri, initialization_options, response } => {
2417 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2419 language: language_clone.clone(),
2420 status: LspServerStatus::Initializing,
2421 message: None,
2422 });
2423 tracing::info!("Processing Initialize command");
2424 let result =
2425 state.handle_initialize_sequential(root_uri, initialization_options, &pending).await;
2426 let success = result.is_ok();
2427 let _ = response.send(result);
2428
2429 if success {
2431 let queued = std::mem::take(&mut pending_commands);
2432 state.replay_pending_commands(queued, &pending).await;
2433 }
2434 }
2435 LspCommand::DidOpen {
2436 uri,
2437 text,
2438 language_id,
2439 } => {
2440 if state.initialized {
2441 tracing::info!("Processing DidOpen for {}", uri.as_str());
2442 let _ = state
2443 .handle_did_open_sequential(uri, text, language_id, &pending)
2444 .await;
2445 } else {
2446 tracing::trace!(
2447 "Queueing DidOpen for {} until initialization completes",
2448 uri.as_str()
2449 );
2450 pending_commands.push(LspCommand::DidOpen {
2451 uri,
2452 text,
2453 language_id,
2454 });
2455 }
2456 }
2457 LspCommand::DidChange {
2458 uri,
2459 content_changes,
2460 } => {
2461 if state.initialized {
2462 tracing::trace!("Processing DidChange for {}", uri.as_str());
2463 let _ = state
2464 .handle_did_change_sequential(uri, content_changes, &pending)
2465 .await;
2466 } else {
2467 tracing::trace!(
2468 "Queueing DidChange for {} until initialization completes",
2469 uri.as_str()
2470 );
2471 pending_commands.push(LspCommand::DidChange {
2472 uri,
2473 content_changes,
2474 });
2475 }
2476 }
2477 LspCommand::DidClose { uri } => {
2478 if state.initialized {
2479 tracing::info!("Processing DidClose for {}", uri.as_str());
2480 let _ = state.handle_did_close(uri).await;
2481 } else {
2482 tracing::trace!(
2483 "Queueing DidClose for {} until initialization completes",
2484 uri.as_str()
2485 );
2486 pending_commands.push(LspCommand::DidClose { uri });
2487 }
2488 }
2489 LspCommand::DidSave { uri, text } => {
2490 if state.initialized {
2491 tracing::info!("Processing DidSave for {}", uri.as_str());
2492 let _ = state.handle_did_save(uri, text).await;
2493 } else {
2494 tracing::trace!(
2495 "Queueing DidSave for {} until initialization completes",
2496 uri.as_str()
2497 );
2498 pending_commands.push(LspCommand::DidSave { uri, text });
2499 }
2500 }
2501 LspCommand::DidChangeWorkspaceFolders { added, removed } => {
2502 if state.initialized {
2503 tracing::info!(
2504 "Processing DidChangeWorkspaceFolders: +{} -{}",
2505 added.len(),
2506 removed.len()
2507 );
2508 let _ = state
2509 .send_notification::<lsp_types::notification::DidChangeWorkspaceFolders>(
2510 lsp_types::DidChangeWorkspaceFoldersParams {
2511 event: lsp_types::WorkspaceFoldersChangeEvent {
2512 added,
2513 removed,
2514 },
2515 },
2516 )
2517 .await;
2518 } else {
2519 tracing::trace!(
2520 "Queueing DidChangeWorkspaceFolders until initialization completes"
2521 );
2522 pending_commands.push(LspCommand::DidChangeWorkspaceFolders { added, removed });
2523 }
2524 }
2525 LspCommand::Completion {
2526 request_id,
2527 uri,
2528 line,
2529 character,
2530 } => {
2531 if state.initialized {
2532 tracing::info!(
2533 "Processing Completion request for {}",
2534 uri.as_str()
2535 );
2536 let _ = state
2537 .handle_completion(request_id, uri, line, character, &pending)
2538 .await;
2539 } else {
2540 tracing::trace!("LSP not initialized, sending empty completion");
2541 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
2542 request_id,
2543 items: vec![],
2544 });
2545 }
2546 }
2547 LspCommand::GotoDefinition {
2548 request_id,
2549 uri,
2550 line,
2551 character,
2552 } => {
2553 if state.initialized {
2554 tracing::info!(
2555 "Processing GotoDefinition request for {}",
2556 uri.as_str()
2557 );
2558 let _ = state
2559 .handle_goto_definition(
2560 request_id, uri, line, character, &pending,
2561 )
2562 .await;
2563 } else {
2564 tracing::trace!("LSP not initialized, sending empty locations");
2565 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
2566 request_id,
2567 locations: vec![],
2568 });
2569 }
2570 }
2571 LspCommand::Rename {
2572 request_id,
2573 uri,
2574 line,
2575 character,
2576 new_name,
2577 } => {
2578 if state.initialized {
2579 tracing::info!("Processing Rename request for {}", uri.as_str());
2580 let _ = state
2581 .handle_rename(
2582 request_id, uri, line, character, new_name, &pending,
2583 )
2584 .await;
2585 } else {
2586 tracing::trace!("LSP not initialized, cannot rename");
2587 let _ = state.async_tx.send(AsyncMessage::LspRename {
2588 request_id,
2589 result: Err("LSP not initialized".to_string()),
2590 });
2591 }
2592 }
2593 LspCommand::Hover {
2594 request_id,
2595 uri,
2596 line,
2597 character,
2598 } => {
2599 if state.initialized {
2600 tracing::info!("Processing Hover request for {}", uri.as_str());
2601 let _ = state
2602 .handle_hover(request_id, uri, line, character, &pending)
2603 .await;
2604 } else {
2605 tracing::trace!("LSP not initialized, cannot get hover");
2606 let _ = state.async_tx.send(AsyncMessage::LspHover {
2607 request_id,
2608 contents: String::new(),
2609 is_markdown: false,
2610 range: None,
2611 });
2612 }
2613 }
2614 LspCommand::References {
2615 request_id,
2616 uri,
2617 line,
2618 character,
2619 } => {
2620 if state.initialized {
2621 tracing::info!("Processing References request for {}", uri.as_str());
2622 let _ = state
2623 .handle_references(request_id, uri, line, character, &pending)
2624 .await;
2625 } else {
2626 tracing::trace!("LSP not initialized, cannot get references");
2627 let _ = state.async_tx.send(AsyncMessage::LspReferences {
2628 request_id,
2629 locations: Vec::new(),
2630 });
2631 }
2632 }
2633 LspCommand::SignatureHelp {
2634 request_id,
2635 uri,
2636 line,
2637 character,
2638 } => {
2639 if state.initialized {
2640 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
2641 let _ = state
2642 .handle_signature_help(request_id, uri, line, character, &pending)
2643 .await;
2644 } else {
2645 tracing::trace!("LSP not initialized, cannot get signature help");
2646 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
2647 request_id,
2648 signature_help: None,
2649 });
2650 }
2651 }
2652 LspCommand::CodeActions {
2653 request_id,
2654 uri,
2655 start_line,
2656 start_char,
2657 end_line,
2658 end_char,
2659 diagnostics,
2660 } => {
2661 if state.initialized {
2662 tracing::info!("Processing CodeActions request for {}", uri.as_str());
2663 let _ = state
2664 .handle_code_actions(
2665 request_id,
2666 uri,
2667 start_line,
2668 start_char,
2669 end_line,
2670 end_char,
2671 diagnostics,
2672 &pending,
2673 )
2674 .await;
2675 } else {
2676 tracing::trace!("LSP not initialized, cannot get code actions");
2677 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
2678 request_id,
2679 actions: Vec::new(),
2680 });
2681 }
2682 }
2683 LspCommand::DocumentDiagnostic {
2684 request_id,
2685 uri,
2686 previous_result_id,
2687 } => {
2688 if state.initialized {
2689 tracing::info!(
2690 "Processing DocumentDiagnostic request for {}",
2691 uri.as_str()
2692 );
2693 let _ = state
2694 .handle_document_diagnostic(
2695 request_id,
2696 uri,
2697 previous_result_id,
2698 &pending,
2699 )
2700 .await;
2701 } else {
2702 tracing::trace!(
2703 "LSP not initialized, cannot get document diagnostics"
2704 );
2705 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2706 request_id,
2707 uri: uri.as_str().to_string(),
2708 result_id: None,
2709 diagnostics: Vec::new(),
2710 unchanged: false,
2711 });
2712 }
2713 }
2714 LspCommand::InlayHints {
2715 request_id,
2716 uri,
2717 start_line,
2718 start_char,
2719 end_line,
2720 end_char,
2721 } => {
2722 if state.initialized {
2723 tracing::info!(
2724 "Processing InlayHints request for {}",
2725 uri.as_str()
2726 );
2727 let _ = state
2728 .handle_inlay_hints(
2729 request_id,
2730 uri,
2731 start_line,
2732 start_char,
2733 end_line,
2734 end_char,
2735 &pending,
2736 )
2737 .await;
2738 } else {
2739 tracing::trace!(
2740 "LSP not initialized, cannot get inlay hints"
2741 );
2742 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
2743 request_id,
2744 uri: uri.as_str().to_string(),
2745 hints: Vec::new(),
2746 });
2747 }
2748 }
2749 LspCommand::FoldingRange { request_id, uri } => {
2750 if state.initialized {
2751 tracing::info!(
2752 "Processing FoldingRange request for {}",
2753 uri.as_str()
2754 );
2755 let _ = state
2756 .handle_folding_ranges(request_id, uri, &pending)
2757 .await;
2758 } else {
2759 tracing::trace!(
2760 "LSP not initialized, cannot get folding ranges"
2761 );
2762 let _ = state.async_tx.send(AsyncMessage::LspFoldingRanges {
2763 request_id,
2764 uri: uri.as_str().to_string(),
2765 ranges: Vec::new(),
2766 });
2767 }
2768 }
2769 LspCommand::SemanticTokensFull { request_id, uri } => {
2770 if state.initialized {
2771 tracing::info!(
2772 "Processing SemanticTokens request for {}",
2773 uri.as_str()
2774 );
2775 let _ = state
2776 .handle_semantic_tokens_full(request_id, uri, &pending)
2777 .await;
2778 } else {
2779 tracing::trace!(
2780 "LSP not initialized, cannot get semantic tokens"
2781 );
2782 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2783 request_id,
2784 uri: uri.as_str().to_string(),
2785 response: LspSemanticTokensResponse::Full(Err(
2786 "LSP not initialized".to_string(),
2787 )),
2788 });
2789 }
2790 }
2791 LspCommand::SemanticTokensFullDelta {
2792 request_id,
2793 uri,
2794 previous_result_id,
2795 } => {
2796 if state.initialized {
2797 tracing::info!(
2798 "Processing SemanticTokens delta request for {}",
2799 uri.as_str()
2800 );
2801 let _ = state
2802 .handle_semantic_tokens_full_delta(
2803 request_id,
2804 uri,
2805 previous_result_id,
2806 &pending,
2807 )
2808 .await;
2809 } else {
2810 tracing::trace!(
2811 "LSP not initialized, cannot get semantic tokens"
2812 );
2813 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2814 request_id,
2815 uri: uri.as_str().to_string(),
2816 response: LspSemanticTokensResponse::FullDelta(Err(
2817 "LSP not initialized".to_string(),
2818 )),
2819 });
2820 }
2821 }
2822 LspCommand::SemanticTokensRange {
2823 request_id,
2824 uri,
2825 range,
2826 } => {
2827 if state.initialized {
2828 tracing::info!(
2829 "Processing SemanticTokens range request for {}",
2830 uri.as_str()
2831 );
2832 let _ = state
2833 .handle_semantic_tokens_range(request_id, uri, range, &pending)
2834 .await;
2835 } else {
2836 tracing::trace!(
2837 "LSP not initialized, cannot get semantic tokens"
2838 );
2839 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2840 request_id,
2841 uri: uri.as_str().to_string(),
2842 response: LspSemanticTokensResponse::Range(Err(
2843 "LSP not initialized".to_string(),
2844 )),
2845 });
2846 }
2847 }
2848 LspCommand::CancelRequest { request_id } => {
2849 tracing::info!(
2850 "Processing CancelRequest for editor_id={}",
2851 request_id
2852 );
2853 let _ = state.handle_cancel_request(request_id).await;
2854 }
2855 LspCommand::PluginRequest {
2856 request_id,
2857 method,
2858 params,
2859 } => {
2860 if state.initialized {
2861 tracing::trace!(
2862 "Processing plugin request {} ({})",
2863 request_id,
2864 method
2865 );
2866 let _ = state
2867 .handle_plugin_request(
2868 request_id,
2869 method,
2870 params,
2871 &pending,
2872 )
2873 .await;
2874 } else {
2875 tracing::trace!(
2876 "Plugin LSP request {} received before initialization",
2877 request_id
2878 );
2879 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
2880 language: language_clone.clone(),
2881 request_id,
2882 result: Err("LSP not initialized".to_string()),
2883 });
2884 }
2885 }
2886 LspCommand::Shutdown => {
2887 tracing::info!("Processing Shutdown command");
2888 shutting_down.store(true, Ordering::SeqCst);
2890 let _ = state.handle_shutdown().await;
2891 break;
2892 }
2893 }
2894 }
2895 else => {
2897 tracing::info!("Command channel closed");
2898 break;
2899 }
2900 }
2901 }
2902
2903 tracing::info!("LSP task exiting for language: {}", self.language);
2904 }
2905}
2906
2907async fn read_message_from_stdout(
2909 stdout: &mut BufReader<ChildStdout>,
2910) -> Result<JsonRpcMessage, String> {
2911 let mut content_length: Option<usize> = None;
2913
2914 loop {
2915 let mut line = String::new();
2916 let bytes_read = stdout
2917 .read_line(&mut line)
2918 .await
2919 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
2920
2921 if bytes_read == 0 {
2923 return Err("LSP server closed stdout (EOF)".to_string());
2924 }
2925
2926 if line == "\r\n" {
2927 break;
2928 }
2929
2930 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
2931 content_length = Some(
2932 len_str
2933 .trim()
2934 .parse()
2935 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
2936 );
2937 }
2938 }
2939
2940 let content_length =
2941 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
2942
2943 let mut content = vec![0u8; content_length];
2945 stdout
2946 .read_exact(&mut content)
2947 .await
2948 .map_err(|e| format!("Failed to read content: {}", e))?;
2949
2950 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
2951
2952 tracing::trace!("Received LSP message: {}", json);
2953
2954 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
2955}
2956
2957#[allow(clippy::type_complexity)]
2959#[allow(clippy::let_underscore_must_use)] async fn handle_message_dispatch(
2961 message: JsonRpcMessage,
2962 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2963 async_tx: &std_mpsc::Sender<AsyncMessage>,
2964 language: &str,
2965 server_command: &str,
2966 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
2967 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
2968) -> Result<(), String> {
2969 match message {
2970 JsonRpcMessage::Response(response) => {
2971 tracing::trace!("Received LSP response for request id={}", response.id);
2972 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
2973 let result = if let Some(error) = response.error {
2974 if error.code == LSP_ERROR_CONTENT_MODIFIED
2977 || error.code == LSP_ERROR_SERVER_CANCELLED
2978 {
2979 tracing::debug!(
2980 "LSP response: {} (code {}), discarding",
2981 error.message,
2982 error.code
2983 );
2984 } else {
2985 tracing::warn!(
2986 "LSP response error: {} (code {})",
2987 error.message,
2988 error.code
2989 );
2990 }
2991 Err(format!(
2992 "LSP error: {} (code {})",
2993 error.message, error.code
2994 ))
2995 } else {
2996 tracing::trace!("LSP response success for request id={}", response.id);
2997 Ok(response.result.unwrap_or(serde_json::Value::Null))
2999 };
3000 let _ = tx.send(result);
3001 } else {
3002 tracing::warn!(
3003 "Received LSP response for unknown request id={}",
3004 response.id
3005 );
3006 }
3007 }
3008 JsonRpcMessage::Notification(notification) => {
3009 tracing::trace!("Received LSP notification: {}", notification.method);
3010 handle_notification_dispatch(notification, async_tx, language, document_versions)
3011 .await?;
3012 }
3013 JsonRpcMessage::Request(request) => {
3014 tracing::trace!("Received request from server: {}", request.method);
3016 let response = match request.method.as_str() {
3017 "window/workDoneProgress/create" => {
3018 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
3020 JsonRpcResponse {
3021 jsonrpc: "2.0".to_string(),
3022 id: request.id,
3023 result: Some(Value::Null),
3024 error: None,
3025 }
3026 }
3027 "workspace/configuration" => {
3028 tracing::trace!(
3032 "Responding to workspace/configuration with inlay hints enabled"
3033 );
3034
3035 let num_items = request
3037 .params
3038 .as_ref()
3039 .and_then(|p| p.get("items"))
3040 .and_then(|items| items.as_array())
3041 .map(|arr| arr.len())
3042 .unwrap_or(1);
3043
3044 let ra_config = serde_json::json!({
3046 "inlayHints": {
3047 "typeHints": {
3048 "enable": true
3049 },
3050 "parameterHints": {
3051 "enable": true
3052 },
3053 "chainingHints": {
3054 "enable": true
3055 },
3056 "closureReturnTypeHints": {
3057 "enable": "always"
3058 }
3059 }
3060 });
3061
3062 let configs: Vec<Value> = (0..num_items).map(|_| ra_config.clone()).collect();
3064
3065 JsonRpcResponse {
3066 jsonrpc: "2.0".to_string(),
3067 id: request.id,
3068 result: Some(Value::Array(configs)),
3069 error: None,
3070 }
3071 }
3072 "client/registerCapability" => {
3073 tracing::trace!(
3075 "Acknowledging client/registerCapability (id={})",
3076 request.id
3077 );
3078 JsonRpcResponse {
3079 jsonrpc: "2.0".to_string(),
3080 id: request.id,
3081 result: Some(Value::Null),
3082 error: None,
3083 }
3084 }
3085 "workspace/diagnostic/refresh" => {
3086 tracing::info!(
3089 "LSP ({}) requested diagnostic refresh (workspace/diagnostic/refresh)",
3090 language
3091 );
3092 let _ = async_tx.send(AsyncMessage::LspDiagnosticRefresh {
3093 language: language.to_string(),
3094 });
3095 JsonRpcResponse {
3096 jsonrpc: "2.0".to_string(),
3097 id: request.id,
3098 result: Some(Value::Null),
3099 error: None,
3100 }
3101 }
3102 _ => {
3103 tracing::debug!("Server request for plugins: {}", request.method);
3105 let _ = async_tx.send(AsyncMessage::LspServerRequest {
3106 language: language.to_string(),
3107 server_command: server_command.to_string(),
3108 method: request.method.clone(),
3109 params: request.params.clone(),
3110 });
3111 JsonRpcResponse {
3112 jsonrpc: "2.0".to_string(),
3113 id: request.id,
3114 result: Some(Value::Null),
3115 error: None,
3116 }
3117 }
3118 };
3119
3120 let json = serde_json::to_string(&response)
3122 .map_err(|e| format!("Failed to serialize response: {}", e))?;
3123 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
3124
3125 let mut stdin = stdin_writer.lock().await;
3126 use tokio::io::AsyncWriteExt;
3127 if let Err(e) = stdin.write_all(message.as_bytes()).await {
3128 tracing::error!("Failed to write server response: {}", e);
3129 }
3130 if let Err(e) = stdin.flush().await {
3131 tracing::error!("Failed to flush server response: {}", e);
3132 }
3133 tracing::trace!("Sent response to server request id={}", response.id);
3134 }
3135 }
3136 Ok(())
3137}
3138
3139#[allow(clippy::let_underscore_must_use)] async fn handle_notification_dispatch(
3142 notification: JsonRpcNotification,
3143 async_tx: &std_mpsc::Sender<AsyncMessage>,
3144 language: &str,
3145 document_versions: &Arc<std::sync::Mutex<HashMap<PathBuf, i64>>>,
3146) -> Result<(), String> {
3147 match notification.method.as_str() {
3148 PublishDiagnostics::METHOD => {
3149 if let Some(params) = notification.params {
3150 let params: PublishDiagnosticsParams = serde_json::from_value(params)
3151 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
3152
3153 if let Some(diag_version) = params.version {
3157 let path = PathBuf::from(params.uri.path().as_str());
3158 let current_version = document_versions.lock().unwrap().get(&path).copied();
3159 if let Some(current) = current_version {
3160 if (diag_version as i64) < current {
3161 tracing::debug!(
3162 "LSP ({}): dropping stale diagnostics for {} (diag version {} < current {})",
3163 language,
3164 params.uri.as_str(),
3165 diag_version,
3166 current
3167 );
3168 return Ok(());
3169 }
3170 }
3171 }
3172
3173 tracing::trace!(
3174 "Received {} diagnostics for {}",
3175 params.diagnostics.len(),
3176 params.uri.as_str()
3177 );
3178
3179 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
3181 uri: params.uri.to_string(),
3182 diagnostics: params.diagnostics,
3183 });
3184 }
3185 }
3186 "window/showMessage" => {
3187 if let Some(params) = notification.params {
3188 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3189 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(3);
3190 let message = msg
3191 .get("message")
3192 .and_then(|v| v.as_str())
3193 .unwrap_or("(no message)")
3194 .to_string();
3195
3196 let message_type = match message_type_num {
3197 1 => LspMessageType::Error,
3198 2 => LspMessageType::Warning,
3199 3 => LspMessageType::Info,
3200 _ => LspMessageType::Log,
3201 };
3202
3203 match message_type {
3205 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3206 LspMessageType::Warning => {
3207 tracing::warn!("LSP ({}): {}", language, message)
3208 }
3209 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3210 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3211 }
3212
3213 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
3215 language: language.to_string(),
3216 message_type,
3217 message,
3218 });
3219 }
3220 }
3221 }
3222 "window/logMessage" => {
3223 if let Some(params) = notification.params {
3224 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
3225 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(4);
3226 let message = msg
3227 .get("message")
3228 .and_then(|v| v.as_str())
3229 .unwrap_or("(no message)")
3230 .to_string();
3231
3232 let message_type = match message_type_num {
3233 1 => LspMessageType::Error,
3234 2 => LspMessageType::Warning,
3235 3 => LspMessageType::Info,
3236 _ => LspMessageType::Log,
3237 };
3238
3239 match message_type {
3241 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
3242 LspMessageType::Warning => {
3243 tracing::warn!("LSP ({}): {}", language, message)
3244 }
3245 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
3246 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
3247 }
3248
3249 let _ = async_tx.send(AsyncMessage::LspLogMessage {
3251 language: language.to_string(),
3252 message_type,
3253 message,
3254 });
3255 }
3256 }
3257 }
3258 "$/progress" => {
3259 if let Some(params) = notification.params {
3260 if let Ok(progress) =
3261 serde_json::from_value::<serde_json::Map<String, Value>>(params)
3262 {
3263 let token = progress
3264 .get("token")
3265 .and_then(|v| {
3266 v.as_str()
3267 .map(|s| s.to_string())
3268 .or_else(|| v.as_i64().map(|n| n.to_string()))
3269 })
3270 .unwrap_or_else(|| "unknown".to_string());
3271
3272 if let Some(value_obj) = progress.get("value").and_then(|v| v.as_object()) {
3273 let kind = value_obj.get("kind").and_then(|v| v.as_str());
3274
3275 let value = match kind {
3276 Some("begin") => {
3277 let title = value_obj
3278 .get("title")
3279 .and_then(|v| v.as_str())
3280 .unwrap_or("Working...")
3281 .to_string();
3282 let message = value_obj
3283 .get("message")
3284 .and_then(|v| v.as_str())
3285 .map(|s| s.to_string());
3286 let percentage = value_obj
3287 .get("percentage")
3288 .and_then(|v| v.as_u64())
3289 .map(|p| p as u32);
3290
3291 tracing::info!(
3292 "LSP ({}) progress begin: {} {:?} {:?}",
3293 language,
3294 title,
3295 message,
3296 percentage
3297 );
3298
3299 Some(LspProgressValue::Begin {
3300 title,
3301 message,
3302 percentage,
3303 })
3304 }
3305 Some("report") => {
3306 let message = value_obj
3307 .get("message")
3308 .and_then(|v| v.as_str())
3309 .map(|s| s.to_string());
3310 let percentage = value_obj
3311 .get("percentage")
3312 .and_then(|v| v.as_u64())
3313 .map(|p| p as u32);
3314
3315 tracing::trace!(
3316 "LSP ({}) progress report: {:?} {:?}",
3317 language,
3318 message,
3319 percentage
3320 );
3321
3322 Some(LspProgressValue::Report {
3323 message,
3324 percentage,
3325 })
3326 }
3327 Some("end") => {
3328 let message = value_obj
3329 .get("message")
3330 .and_then(|v| v.as_str())
3331 .map(|s| s.to_string());
3332
3333 tracing::info!("LSP ({}) progress end: {:?}", language, message);
3334
3335 Some(LspProgressValue::End { message })
3336 }
3337 _ => None,
3338 };
3339
3340 if let Some(value) = value {
3341 let _ = async_tx.send(AsyncMessage::LspProgress {
3342 language: language.to_string(),
3343 token,
3344 value,
3345 });
3346 }
3347 }
3348 }
3349 }
3350 }
3351 "experimental/serverStatus" => {
3352 if let Some(params) = notification.params {
3355 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
3356 {
3357 let quiescent = status
3358 .get("quiescent")
3359 .and_then(|v| v.as_bool())
3360 .unwrap_or(false);
3361
3362 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
3363
3364 if quiescent {
3365 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
3367 language: language.to_string(),
3368 });
3369 }
3370 }
3371 }
3372 }
3373 _ => {
3374 tracing::debug!("Unhandled notification: {}", notification.method);
3375 }
3376 }
3377
3378 Ok(())
3379}
3380
3381static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
3383
3384pub struct LspHandle {
3386 id: u64,
3388
3389 language: String,
3391
3392 command_tx: mpsc::Sender<LspCommand>,
3394
3395 state: Arc<Mutex<LspClientState>>,
3397
3398 runtime: tokio::runtime::Handle,
3400}
3401
3402#[allow(clippy::let_underscore_must_use)]
3406impl LspHandle {
3407 pub fn spawn(
3409 runtime: &tokio::runtime::Handle,
3410 command: &str,
3411 args: &[String],
3412 env: std::collections::HashMap<String, String>,
3413 language: String,
3414 async_bridge: &AsyncBridge,
3415 process_limits: ProcessLimits,
3416 language_id_overrides: std::collections::HashMap<String, String>,
3417 ) -> Result<Self, String> {
3418 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
3420 let language_clone = language.clone();
3421 let command = command.to_string();
3422 let args = args.to_vec();
3423 let state = Arc::new(Mutex::new(LspClientState::Starting));
3424
3425 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language);
3427
3428 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3430 language: language.clone(),
3431 status: LspServerStatus::Starting,
3432 message: None,
3433 });
3434
3435 let state_clone = state.clone();
3436 let stderr_log_path_clone = stderr_log_path.clone();
3437 runtime.spawn(async move {
3438 match LspTask::spawn(
3439 &command,
3440 &args,
3441 &env,
3442 language_clone.clone(),
3443 async_tx.clone(),
3444 &process_limits,
3445 stderr_log_path_clone.clone(),
3446 language_id_overrides,
3447 )
3448 .await
3449 {
3450 Ok(task) => {
3451 task.run(command_rx).await;
3452 }
3453 Err(e) => {
3454 tracing::error!("Failed to spawn LSP task: {}", e);
3455
3456 if let Ok(mut s) = state_clone.lock() {
3458 let _ = s.transition_to(LspClientState::Error);
3459 }
3460
3461 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3462 language: language_clone.clone(),
3463 status: LspServerStatus::Error,
3464 message: None,
3465 });
3466 let _ = async_tx.send(AsyncMessage::LspError {
3467 language: language_clone,
3468 error: e,
3469 stderr_log_path: Some(stderr_log_path_clone),
3470 });
3471 }
3472 }
3473 });
3474
3475 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3476
3477 Ok(Self {
3478 id,
3479 language,
3480 command_tx,
3481 state,
3482 runtime: runtime.clone(),
3483 })
3484 }
3485
3486 pub fn id(&self) -> u64 {
3488 self.id
3489 }
3490
3491 pub fn language(&self) -> &str {
3493 &self.language
3494 }
3495
3496 pub fn initialize(
3505 &self,
3506 root_uri: Option<Uri>,
3507 initialization_options: Option<Value>,
3508 ) -> Result<(), String> {
3509 {
3511 let mut state = self.state.lock().unwrap();
3512 if !state.can_initialize() {
3513 return Err(format!(
3514 "Cannot initialize: client is in state {:?}",
3515 *state
3516 ));
3517 }
3518 state.transition_to(LspClientState::Initializing)?;
3520 }
3521
3522 let state = self.state.clone();
3523
3524 let (tx, rx) = oneshot::channel();
3526
3527 self.command_tx
3528 .try_send(LspCommand::Initialize {
3529 root_uri,
3530 initialization_options,
3531 response: tx,
3532 })
3533 .map_err(|_| "Failed to send initialize command".to_string())?;
3534
3535 let runtime = self.runtime.clone();
3537 runtime.spawn(async move {
3538 match tokio::time::timeout(std::time::Duration::from_secs(10), rx).await {
3539 Ok(Ok(Ok(_))) => {
3540 if let Ok(mut s) = state.lock() {
3542 let _ = s.transition_to(LspClientState::Running);
3543 }
3544 tracing::info!("LSP initialization completed successfully");
3545 }
3546 Ok(Ok(Err(e))) => {
3547 tracing::error!("LSP initialization failed: {}", e);
3548 if let Ok(mut s) = state.lock() {
3549 let _ = s.transition_to(LspClientState::Error);
3550 }
3551 }
3552 Ok(Err(_)) => {
3553 tracing::error!("LSP initialization response channel closed");
3554 if let Ok(mut s) = state.lock() {
3555 let _ = s.transition_to(LspClientState::Error);
3556 }
3557 }
3558 Err(_) => {
3559 tracing::error!("LSP initialization timed out after 10 seconds");
3560 if let Ok(mut s) = state.lock() {
3561 let _ = s.transition_to(LspClientState::Error);
3562 }
3563 }
3564 }
3565 });
3566
3567 Ok(())
3568 }
3569
3570 pub fn is_initialized(&self) -> bool {
3572 self.state.lock().unwrap().can_send_requests()
3573 }
3574
3575 pub fn state(&self) -> LspClientState {
3577 *self.state.lock().unwrap()
3578 }
3579
3580 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
3586 if language_id != self.language {
3588 tracing::warn!(
3589 "did_open: document language '{}' does not match LSP handle language '{}' for {}",
3590 language_id,
3591 self.language,
3592 uri.as_str()
3593 );
3594 return Err(format!(
3596 "Language mismatch: document is '{}' but LSP handles '{}'",
3597 language_id, self.language
3598 ));
3599 }
3600
3601 self.command_tx
3603 .try_send(LspCommand::DidOpen {
3604 uri,
3605 text,
3606 language_id,
3607 })
3608 .map_err(|_| "Failed to send did_open command".to_string())
3609 }
3610
3611 pub fn did_change(
3613 &self,
3614 uri: Uri,
3615 content_changes: Vec<TextDocumentContentChangeEvent>,
3616 ) -> Result<(), String> {
3617 self.command_tx
3619 .try_send(LspCommand::DidChange {
3620 uri,
3621 content_changes,
3622 })
3623 .map_err(|_| "Failed to send did_change command".to_string())
3624 }
3625
3626 pub fn did_close(&self, uri: Uri) -> Result<(), String> {
3628 self.command_tx
3629 .try_send(LspCommand::DidClose { uri })
3630 .map_err(|_| "Failed to send did_close command".to_string())
3631 }
3632
3633 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
3635 self.command_tx
3636 .try_send(LspCommand::DidSave { uri, text })
3637 .map_err(|_| "Failed to send did_save command".to_string())
3638 }
3639
3640 pub fn add_workspace_folder(&self, uri: lsp_types::Uri, name: String) -> Result<(), String> {
3642 self.command_tx
3643 .try_send(LspCommand::DidChangeWorkspaceFolders {
3644 added: vec![lsp_types::WorkspaceFolder { uri, name }],
3645 removed: vec![],
3646 })
3647 .map_err(|_| "Failed to send workspace folder change".to_string())
3648 }
3649
3650 pub fn completion(
3652 &self,
3653 request_id: u64,
3654 uri: Uri,
3655 line: u32,
3656 character: u32,
3657 ) -> Result<(), String> {
3658 self.command_tx
3659 .try_send(LspCommand::Completion {
3660 request_id,
3661 uri,
3662 line,
3663 character,
3664 })
3665 .map_err(|_| "Failed to send completion command".to_string())
3666 }
3667
3668 pub fn goto_definition(
3670 &self,
3671 request_id: u64,
3672 uri: Uri,
3673 line: u32,
3674 character: u32,
3675 ) -> Result<(), String> {
3676 self.command_tx
3677 .try_send(LspCommand::GotoDefinition {
3678 request_id,
3679 uri,
3680 line,
3681 character,
3682 })
3683 .map_err(|_| "Failed to send goto_definition command".to_string())
3684 }
3685
3686 pub fn rename(
3688 &self,
3689 request_id: u64,
3690 uri: Uri,
3691 line: u32,
3692 character: u32,
3693 new_name: String,
3694 ) -> Result<(), String> {
3695 self.command_tx
3696 .try_send(LspCommand::Rename {
3697 request_id,
3698 uri,
3699 line,
3700 character,
3701 new_name,
3702 })
3703 .map_err(|_| "Failed to send rename command".to_string())
3704 }
3705
3706 pub fn hover(
3708 &self,
3709 request_id: u64,
3710 uri: Uri,
3711 line: u32,
3712 character: u32,
3713 ) -> Result<(), String> {
3714 self.command_tx
3715 .try_send(LspCommand::Hover {
3716 request_id,
3717 uri,
3718 line,
3719 character,
3720 })
3721 .map_err(|_| "Failed to send hover command".to_string())
3722 }
3723
3724 pub fn references(
3726 &self,
3727 request_id: u64,
3728 uri: Uri,
3729 line: u32,
3730 character: u32,
3731 ) -> Result<(), String> {
3732 self.command_tx
3733 .try_send(LspCommand::References {
3734 request_id,
3735 uri,
3736 line,
3737 character,
3738 })
3739 .map_err(|_| "Failed to send references command".to_string())
3740 }
3741
3742 pub fn signature_help(
3744 &self,
3745 request_id: u64,
3746 uri: Uri,
3747 line: u32,
3748 character: u32,
3749 ) -> Result<(), String> {
3750 self.command_tx
3751 .try_send(LspCommand::SignatureHelp {
3752 request_id,
3753 uri,
3754 line,
3755 character,
3756 })
3757 .map_err(|_| "Failed to send signature_help command".to_string())
3758 }
3759
3760 #[allow(clippy::too_many_arguments)]
3762 pub fn code_actions(
3763 &self,
3764 request_id: u64,
3765 uri: Uri,
3766 start_line: u32,
3767 start_char: u32,
3768 end_line: u32,
3769 end_char: u32,
3770 diagnostics: Vec<lsp_types::Diagnostic>,
3771 ) -> Result<(), String> {
3772 self.command_tx
3773 .try_send(LspCommand::CodeActions {
3774 request_id,
3775 uri,
3776 start_line,
3777 start_char,
3778 end_line,
3779 end_char,
3780 diagnostics,
3781 })
3782 .map_err(|_| "Failed to send code_actions command".to_string())
3783 }
3784
3785 pub fn document_diagnostic(
3790 &self,
3791 request_id: u64,
3792 uri: Uri,
3793 previous_result_id: Option<String>,
3794 ) -> Result<(), String> {
3795 self.command_tx
3796 .try_send(LspCommand::DocumentDiagnostic {
3797 request_id,
3798 uri,
3799 previous_result_id,
3800 })
3801 .map_err(|_| "Failed to send document_diagnostic command".to_string())
3802 }
3803
3804 pub fn inlay_hints(
3808 &self,
3809 request_id: u64,
3810 uri: Uri,
3811 start_line: u32,
3812 start_char: u32,
3813 end_line: u32,
3814 end_char: u32,
3815 ) -> Result<(), String> {
3816 self.command_tx
3817 .try_send(LspCommand::InlayHints {
3818 request_id,
3819 uri,
3820 start_line,
3821 start_char,
3822 end_line,
3823 end_char,
3824 })
3825 .map_err(|_| "Failed to send inlay_hints command".to_string())
3826 }
3827
3828 pub fn folding_ranges(&self, request_id: u64, uri: Uri) -> Result<(), String> {
3830 self.command_tx
3831 .try_send(LspCommand::FoldingRange { request_id, uri })
3832 .map_err(|_| "Failed to send folding_range command".to_string())
3833 }
3834
3835 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
3837 self.command_tx
3838 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
3839 .map_err(|_| "Failed to send semantic_tokens command".to_string())
3840 }
3841
3842 pub fn semantic_tokens_full_delta(
3844 &self,
3845 request_id: u64,
3846 uri: Uri,
3847 previous_result_id: String,
3848 ) -> Result<(), String> {
3849 self.command_tx
3850 .try_send(LspCommand::SemanticTokensFullDelta {
3851 request_id,
3852 uri,
3853 previous_result_id,
3854 })
3855 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
3856 }
3857
3858 pub fn semantic_tokens_range(
3860 &self,
3861 request_id: u64,
3862 uri: Uri,
3863 range: lsp_types::Range,
3864 ) -> Result<(), String> {
3865 self.command_tx
3866 .try_send(LspCommand::SemanticTokensRange {
3867 request_id,
3868 uri,
3869 range,
3870 })
3871 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
3872 }
3873
3874 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
3879 self.command_tx
3880 .try_send(LspCommand::CancelRequest { request_id })
3881 .map_err(|_| "Failed to send cancel_request command".to_string())
3882 }
3883
3884 pub fn send_plugin_request(
3886 &self,
3887 request_id: u64,
3888 method: String,
3889 params: Option<Value>,
3890 ) -> Result<(), String> {
3891 tracing::trace!(
3892 "LspHandle sending plugin request {}: method={}",
3893 request_id,
3894 method
3895 );
3896 match self.command_tx.try_send(LspCommand::PluginRequest {
3897 request_id,
3898 method,
3899 params,
3900 }) {
3901 Ok(()) => {
3902 tracing::trace!(
3903 "LspHandle enqueued plugin request {} successfully",
3904 request_id
3905 );
3906 Ok(())
3907 }
3908 Err(e) => {
3909 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
3910 Err("Failed to send plugin LSP request".to_string())
3911 }
3912 }
3913 }
3914
3915 pub fn shutdown(&self) -> Result<(), String> {
3917 {
3919 let mut state = self.state.lock().unwrap();
3920 if let Err(e) = state.transition_to(LspClientState::Stopping) {
3921 tracing::warn!("State transition warning during shutdown: {}", e);
3922 }
3924 }
3925
3926 self.command_tx
3927 .try_send(LspCommand::Shutdown)
3928 .map_err(|_| "Failed to send shutdown command".to_string())?;
3929
3930 {
3933 let mut state = self.state.lock().unwrap();
3934 let _ = state.transition_to(LspClientState::Stopped);
3935 }
3936
3937 Ok(())
3938 }
3939}
3940
3941#[allow(clippy::let_underscore_must_use)] impl Drop for LspHandle {
3943 fn drop(&mut self) {
3944 let _ = self.command_tx.try_send(LspCommand::Shutdown);
3950
3951 if let Ok(mut state) = self.state.lock() {
3953 let _ = state.transition_to(LspClientState::Stopped);
3954 }
3955 }
3956}
3957
3958#[cfg(test)]
3959mod tests {
3960 use super::*;
3961
3962 #[test]
3963 fn test_json_rpc_request_serialization() {
3964 let request = JsonRpcRequest {
3965 jsonrpc: "2.0".to_string(),
3966 id: 1,
3967 method: "initialize".to_string(),
3968 params: Some(serde_json::json!({"rootUri": "file:///test"})),
3969 };
3970
3971 let json = serde_json::to_string(&request).unwrap();
3972 assert!(json.contains("\"jsonrpc\":\"2.0\""));
3973 assert!(json.contains("\"id\":1"));
3974 assert!(json.contains("\"method\":\"initialize\""));
3975 assert!(json.contains("\"rootUri\":\"file:///test\""));
3976 }
3977
3978 #[test]
3979 fn test_json_rpc_response_serialization() {
3980 let response = JsonRpcResponse {
3981 jsonrpc: "2.0".to_string(),
3982 id: 1,
3983 result: Some(serde_json::json!({"success": true})),
3984 error: None,
3985 };
3986
3987 let json = serde_json::to_string(&response).unwrap();
3988 assert!(json.contains("\"jsonrpc\":\"2.0\""));
3989 assert!(json.contains("\"id\":1"));
3990 assert!(json.contains("\"success\":true"));
3991 assert!(!json.contains("\"error\""));
3992 }
3993
3994 #[test]
3995 fn test_json_rpc_error_response() {
3996 let response = JsonRpcResponse {
3997 jsonrpc: "2.0".to_string(),
3998 id: 1,
3999 result: None,
4000 error: Some(JsonRpcError {
4001 code: -32600,
4002 message: "Invalid request".to_string(),
4003 data: None,
4004 }),
4005 };
4006
4007 let json = serde_json::to_string(&response).unwrap();
4008 assert!(json.contains("\"error\""));
4009 assert!(json.contains("\"code\":-32600"));
4010 assert!(json.contains("\"message\":\"Invalid request\""));
4011 }
4012
4013 #[test]
4014 fn test_json_rpc_notification_serialization() {
4015 let notification = JsonRpcNotification {
4016 jsonrpc: "2.0".to_string(),
4017 method: "textDocument/didOpen".to_string(),
4018 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
4019 };
4020
4021 let json = serde_json::to_string(¬ification).unwrap();
4022 assert!(json.contains("\"jsonrpc\":\"2.0\""));
4023 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
4024 assert!(json.contains("\"uri\":\"file:///test.rs\""));
4025 assert!(!json.contains("\"id\"")); }
4027
4028 #[test]
4029 fn test_json_rpc_message_deserialization_request() {
4030 let json =
4031 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
4032 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4033
4034 match message {
4035 JsonRpcMessage::Request(request) => {
4036 assert_eq!(request.jsonrpc, "2.0");
4037 assert_eq!(request.id, 1);
4038 assert_eq!(request.method, "initialize");
4039 assert!(request.params.is_some());
4040 }
4041 _ => panic!("Expected Request"),
4042 }
4043 }
4044
4045 #[test]
4046 fn test_json_rpc_message_deserialization_response() {
4047 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
4048 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4049
4050 match message {
4051 JsonRpcMessage::Response(response) => {
4052 assert_eq!(response.jsonrpc, "2.0");
4053 assert_eq!(response.id, 1);
4054 assert!(response.result.is_some());
4055 assert!(response.error.is_none());
4056 }
4057 _ => panic!("Expected Response"),
4058 }
4059 }
4060
4061 #[test]
4062 fn test_json_rpc_message_deserialization_notification() {
4063 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
4064 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4065
4066 match message {
4067 JsonRpcMessage::Notification(notification) => {
4068 assert_eq!(notification.jsonrpc, "2.0");
4069 assert_eq!(notification.method, "textDocument/didOpen");
4070 assert!(notification.params.is_some());
4071 }
4072 _ => panic!("Expected Notification"),
4073 }
4074 }
4075
4076 #[test]
4077 fn test_json_rpc_error_deserialization() {
4078 let json =
4079 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
4080 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
4081
4082 match message {
4083 JsonRpcMessage::Response(response) => {
4084 assert_eq!(response.jsonrpc, "2.0");
4085 assert_eq!(response.id, 1);
4086 assert!(response.result.is_none());
4087 assert!(response.error.is_some());
4088 let error = response.error.unwrap();
4089 assert_eq!(error.code, -32600);
4090 assert_eq!(error.message, "Invalid request");
4091 }
4092 _ => panic!("Expected Response with error"),
4093 }
4094 }
4095
4096 #[tokio::test]
4097 async fn test_lsp_handle_spawn_and_drop() {
4098 let runtime = tokio::runtime::Handle::current();
4101 let async_bridge = AsyncBridge::new();
4102
4103 let result = LspHandle::spawn(
4106 &runtime,
4107 "cat",
4108 &[],
4109 Default::default(),
4110 "test".to_string(),
4111 &async_bridge,
4112 ProcessLimits::unlimited(),
4113 Default::default(),
4114 );
4115
4116 assert!(result.is_ok());
4118
4119 let handle = result.unwrap();
4120
4121 drop(handle);
4123
4124 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
4126 }
4127
4128 #[tokio::test]
4129 async fn test_lsp_handle_did_open_queues_before_initialization() {
4130 let runtime = tokio::runtime::Handle::current();
4131 let async_bridge = AsyncBridge::new();
4132
4133 let handle = LspHandle::spawn(
4134 &runtime,
4135 "cat",
4136 &[],
4137 Default::default(),
4138 "test".to_string(),
4139 &async_bridge,
4140 ProcessLimits::unlimited(),
4141 Default::default(),
4142 )
4143 .unwrap();
4144
4145 let result = handle.did_open(
4147 "file:///test.txt".parse().unwrap(),
4148 "fn main() {}".to_string(),
4149 "test".to_string(),
4150 );
4151
4152 assert!(result.is_ok());
4154 }
4155
4156 #[tokio::test]
4157 async fn test_lsp_handle_did_change_queues_before_initialization() {
4158 let runtime = tokio::runtime::Handle::current();
4159 let async_bridge = AsyncBridge::new();
4160
4161 let handle = LspHandle::spawn(
4162 &runtime,
4163 "cat",
4164 &[],
4165 Default::default(),
4166 "test".to_string(),
4167 &async_bridge,
4168 ProcessLimits::unlimited(),
4169 Default::default(),
4170 )
4171 .unwrap();
4172
4173 let result = handle.did_change(
4175 "file:///test.rs".parse().unwrap(),
4176 vec![TextDocumentContentChangeEvent {
4177 range: Some(lsp_types::Range::new(
4178 lsp_types::Position::new(0, 0),
4179 lsp_types::Position::new(0, 0),
4180 )),
4181 range_length: None,
4182 text: "fn main() {}".to_string(),
4183 }],
4184 );
4185
4186 assert!(result.is_ok());
4188 }
4189
4190 #[tokio::test]
4191 async fn test_lsp_handle_incremental_change_with_range() {
4192 let runtime = tokio::runtime::Handle::current();
4193 let async_bridge = AsyncBridge::new();
4194
4195 let handle = LspHandle::spawn(
4196 &runtime,
4197 "cat",
4198 &[],
4199 Default::default(),
4200 "test".to_string(),
4201 &async_bridge,
4202 ProcessLimits::unlimited(),
4203 Default::default(),
4204 )
4205 .unwrap();
4206
4207 let result = handle.did_change(
4209 "file:///test.rs".parse().unwrap(),
4210 vec![TextDocumentContentChangeEvent {
4211 range: Some(lsp_types::Range::new(
4212 lsp_types::Position::new(0, 3),
4213 lsp_types::Position::new(0, 7),
4214 )),
4215 range_length: None,
4216 text: String::new(), }],
4218 );
4219
4220 assert!(result.is_ok());
4222 }
4223
4224 #[tokio::test]
4225 async fn test_lsp_handle_spawn_invalid_command() {
4226 let runtime = tokio::runtime::Handle::current();
4227 let async_bridge = AsyncBridge::new();
4228
4229 let result = LspHandle::spawn(
4231 &runtime,
4232 "this-command-does-not-exist-12345",
4233 &[],
4234 Default::default(),
4235 "test".to_string(),
4236 &async_bridge,
4237 ProcessLimits::unlimited(),
4238 Default::default(),
4239 );
4240
4241 assert!(result.is_ok());
4244
4245 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
4247
4248 let messages = async_bridge.try_recv_all();
4250 assert!(!messages.is_empty());
4251
4252 let has_error = messages
4253 .iter()
4254 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
4255 assert!(has_error, "Expected LspError message");
4256 }
4257
4258 #[test]
4259 fn test_lsp_handle_shutdown_from_sync_context() {
4260 std::thread::spawn(|| {
4263 let rt = tokio::runtime::Runtime::new().unwrap();
4265 let async_bridge = AsyncBridge::new();
4266
4267 let handle = rt.block_on(async {
4268 let runtime = tokio::runtime::Handle::current();
4269 LspHandle::spawn(
4270 &runtime,
4271 "cat",
4272 &[],
4273 Default::default(),
4274 "test".to_string(),
4275 &async_bridge,
4276 ProcessLimits::unlimited(),
4277 Default::default(),
4278 )
4279 .unwrap()
4280 });
4281
4282 assert!(handle.shutdown().is_ok());
4284
4285 std::thread::sleep(std::time::Duration::from_millis(50));
4287 })
4288 .join()
4289 .unwrap();
4290 }
4291
4292 #[test]
4293 fn test_lsp_command_debug_format() {
4294 let cmd = LspCommand::Shutdown;
4296 let debug_str = format!("{:?}", cmd);
4297 assert!(debug_str.contains("Shutdown"));
4298 }
4299
4300 #[test]
4301 fn test_lsp_client_state_can_initialize_from_starting() {
4302 let state = LspClientState::Starting;
4308
4309 assert!(
4311 state.can_initialize(),
4312 "Starting state must allow initialization to avoid race condition"
4313 );
4314
4315 let mut state = LspClientState::Starting;
4317
4318 assert!(state.can_transition_to(LspClientState::Initializing));
4320 assert!(state.transition_to(LspClientState::Initializing).is_ok());
4321
4322 assert!(state.can_transition_to(LspClientState::Running));
4324 assert!(state.transition_to(LspClientState::Running).is_ok());
4325 }
4326
4327 #[tokio::test]
4328 async fn test_lsp_handle_initialize_from_starting_state() {
4329 let runtime = tokio::runtime::Handle::current();
4337 let async_bridge = AsyncBridge::new();
4338
4339 let handle = LspHandle::spawn(
4341 &runtime,
4342 "cat", &[],
4344 Default::default(),
4345 "test".to_string(),
4346 &async_bridge,
4347 ProcessLimits::unlimited(),
4348 Default::default(),
4349 )
4350 .unwrap();
4351
4352 let result = handle.initialize(None, None);
4355
4356 assert!(
4357 result.is_ok(),
4358 "initialize() must succeed from Starting state. Got error: {:?}",
4359 result.err()
4360 );
4361 }
4362
4363 #[tokio::test]
4364 async fn test_lsp_state_machine_race_condition_fix() {
4365 let runtime = tokio::runtime::Handle::current();
4372 let async_bridge = AsyncBridge::new();
4373
4374 let fake_lsp_script = r#"
4376 read -r line # Read Content-Length header
4377 read -r empty # Read empty line
4378 read -r json # Read JSON body
4379
4380 # Send a valid initialize response
4381 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
4382 echo "Content-Length: ${#response}"
4383 echo ""
4384 echo -n "$response"
4385
4386 # Keep running to avoid EOF
4387 sleep 10
4388 "#;
4389
4390 let handle = LspHandle::spawn(
4392 &runtime,
4393 "bash",
4394 &["-c".to_string(), fake_lsp_script.to_string()],
4395 Default::default(),
4396 "fake".to_string(),
4397 &async_bridge,
4398 ProcessLimits::unlimited(),
4399 Default::default(),
4400 )
4401 .unwrap();
4402
4403 let init_result = handle.initialize(None, None);
4405 assert!(
4406 init_result.is_ok(),
4407 "initialize() failed from Starting state: {:?}",
4408 init_result.err()
4409 );
4410
4411 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
4413
4414 let messages = async_bridge.try_recv_all();
4416 let has_status_update = messages
4417 .iter()
4418 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
4419
4420 assert!(
4421 has_status_update,
4422 "Expected status update messages from LSP initialization"
4423 );
4424
4425 #[allow(clippy::let_underscore_must_use)]
4427 let _ = handle.shutdown();
4428 }
4429}