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