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