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, DidOpenTextDocument, DidSaveTextDocument, Initialized, Notification,
22 PublishDiagnostics,
23 },
24 request::{Initialize, Request},
25 ClientCapabilities, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
26 DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams,
27 PublishDiagnosticsParams, SemanticTokenModifier, SemanticTokenType,
28 SemanticTokensClientCapabilities, SemanticTokensClientCapabilitiesRequests,
29 SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensParams, SemanticTokensResult,
30 SemanticTokensServerCapabilities, ServerCapabilities, TextDocumentContentChangeEvent,
31 TextDocumentIdentifier, TextDocumentItem, TokenFormat, Uri, VersionedTextDocumentIdentifier,
32 WindowClientCapabilities, WorkspaceFolder,
33};
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36use std::collections::HashMap;
37use std::path::PathBuf;
38use std::sync::atomic::{AtomicBool, Ordering};
39use std::sync::{mpsc as std_mpsc, Arc, Mutex};
40use std::time::Instant;
41use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
42use tokio::process::{Child, ChildStdin, ChildStdout, Command};
43use tokio::sync::{mpsc, oneshot};
44
45const DID_OPEN_GRACE_PERIOD_MS: u64 = 200;
48
49fn should_skip_did_open(
52 document_versions: &HashMap<PathBuf, i64>,
53 path: &PathBuf,
54 language: &str,
55 uri: &Uri,
56) -> bool {
57 if document_versions.contains_key(path) {
58 tracing::debug!(
59 "LSP ({}): skipping didOpen - document already open: {}",
60 language,
61 uri.as_str()
62 );
63 true
64 } else {
65 false
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(untagged)]
72pub enum JsonRpcMessage {
73 Request(JsonRpcRequest),
74 Response(JsonRpcResponse),
75 Notification(JsonRpcNotification),
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct JsonRpcRequest {
81 pub jsonrpc: String,
82 pub id: i64,
83 pub method: String,
84 pub params: Option<Value>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct JsonRpcResponse {
90 pub jsonrpc: String,
91 pub id: i64,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub result: Option<Value>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub error: Option<JsonRpcError>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct JsonRpcNotification {
101 pub jsonrpc: String,
102 pub method: String,
103 pub params: Option<Value>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct JsonRpcError {
109 pub code: i64,
110 pub message: String,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub data: Option<Value>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum LspClientState {
121 Initial,
123 Starting,
125 Initializing,
127 Running,
129 Stopping,
131 Stopped,
133 Error,
135}
136
137impl LspClientState {
138 pub fn can_transition_to(&self, next: LspClientState) -> bool {
140 use LspClientState::*;
141 match (self, next) {
142 (Initial, Starting) => true,
144 (Starting, Initializing) | (Starting, Error) => true,
146 (Initializing, Running) | (Initializing, Error) => true,
148 (Running, Stopping) | (Running, Error) => true,
150 (Stopping, Stopped) | (Stopping, Error) => true,
152 (Stopped, Starting) | (Error, Starting) => true,
154 (_, Error) => true,
156 (a, b) if *a == b => true,
158 _ => false,
160 }
161 }
162
163 pub fn transition_to(&mut self, next: LspClientState) -> Result<(), String> {
165 if self.can_transition_to(next) {
166 *self = next;
167 Ok(())
168 } else {
169 Err(format!(
170 "Invalid state transition from {:?} to {:?}",
171 self, next
172 ))
173 }
174 }
175
176 pub fn can_send_requests(&self) -> bool {
178 matches!(self, Self::Running)
179 }
180
181 pub fn can_initialize(&self) -> bool {
183 matches!(self, Self::Initial | Self::Starting | Self::Stopped)
184 }
185
186 pub fn to_server_status(&self) -> LspServerStatus {
188 match self {
189 Self::Initial => LspServerStatus::Starting,
190 Self::Starting => LspServerStatus::Starting,
191 Self::Initializing => LspServerStatus::Initializing,
192 Self::Running => LspServerStatus::Running,
193 Self::Stopping => LspServerStatus::Shutdown,
194 Self::Stopped => LspServerStatus::Shutdown,
195 Self::Error => LspServerStatus::Error,
196 }
197 }
198}
199
200fn create_client_capabilities() -> ClientCapabilities {
202 use lsp_types::{
203 GeneralClientCapabilities, RenameClientCapabilities, TextDocumentClientCapabilities,
204 WorkspaceClientCapabilities, WorkspaceEditClientCapabilities,
205 };
206
207 ClientCapabilities {
208 window: Some(WindowClientCapabilities {
209 work_done_progress: Some(true),
210 ..Default::default()
211 }),
212 workspace: Some(WorkspaceClientCapabilities {
213 apply_edit: Some(true),
214 workspace_edit: Some(WorkspaceEditClientCapabilities {
215 document_changes: Some(true),
216 ..Default::default()
217 }),
218 ..Default::default()
219 }),
220 text_document: Some(TextDocumentClientCapabilities {
221 rename: Some(RenameClientCapabilities {
222 dynamic_registration: Some(true),
223 prepare_support: Some(true),
224 honors_change_annotations: Some(true),
225 ..Default::default()
226 }),
227 semantic_tokens: Some(SemanticTokensClientCapabilities {
228 dynamic_registration: Some(true),
229 requests: SemanticTokensClientCapabilitiesRequests {
230 range: Some(true),
231 full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
232 },
233 token_types: vec![
234 SemanticTokenType::NAMESPACE,
235 SemanticTokenType::TYPE,
236 SemanticTokenType::CLASS,
237 SemanticTokenType::ENUM,
238 SemanticTokenType::INTERFACE,
239 SemanticTokenType::STRUCT,
240 SemanticTokenType::TYPE_PARAMETER,
241 SemanticTokenType::PARAMETER,
242 SemanticTokenType::VARIABLE,
243 SemanticTokenType::PROPERTY,
244 SemanticTokenType::ENUM_MEMBER,
245 SemanticTokenType::EVENT,
246 SemanticTokenType::FUNCTION,
247 SemanticTokenType::METHOD,
248 SemanticTokenType::MACRO,
249 SemanticTokenType::KEYWORD,
250 SemanticTokenType::MODIFIER,
251 SemanticTokenType::COMMENT,
252 SemanticTokenType::STRING,
253 SemanticTokenType::NUMBER,
254 SemanticTokenType::REGEXP,
255 SemanticTokenType::OPERATOR,
256 SemanticTokenType::DECORATOR,
257 ],
258 token_modifiers: vec![
259 SemanticTokenModifier::DECLARATION,
260 SemanticTokenModifier::DEFINITION,
261 SemanticTokenModifier::READONLY,
262 SemanticTokenModifier::STATIC,
263 SemanticTokenModifier::DEPRECATED,
264 SemanticTokenModifier::ABSTRACT,
265 SemanticTokenModifier::ASYNC,
266 SemanticTokenModifier::MODIFICATION,
267 SemanticTokenModifier::DOCUMENTATION,
268 SemanticTokenModifier::DEFAULT_LIBRARY,
269 ],
270 formats: vec![TokenFormat::RELATIVE],
271 overlapping_token_support: Some(true),
272 multiline_token_support: Some(true),
273 server_cancel_support: Some(true),
274 augments_syntax_tokens: Some(true),
275 }),
276 ..Default::default()
277 }),
278 general: Some(GeneralClientCapabilities {
279 ..Default::default()
280 }),
281 experimental: Some(serde_json::json!({
283 "serverStatusNotification": true
284 })),
285 ..Default::default()
286 }
287}
288
289fn extract_semantic_token_capability(
290 capabilities: &ServerCapabilities,
291) -> (Option<SemanticTokensLegend>, bool, bool, bool) {
292 capabilities
293 .semantic_tokens_provider
294 .as_ref()
295 .map(|provider| match provider {
296 SemanticTokensServerCapabilities::SemanticTokensOptions(options) => (
297 Some(options.legend.clone()),
298 semantic_tokens_full_supported(&options.full),
299 semantic_tokens_full_delta_supported(&options.full),
300 options.range.unwrap_or(false),
301 ),
302 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(options) => {
303 let legend = options.semantic_tokens_options.legend.clone();
304 let full = semantic_tokens_full_supported(&options.semantic_tokens_options.full);
305 let delta =
306 semantic_tokens_full_delta_supported(&options.semantic_tokens_options.full);
307 let range = options.semantic_tokens_options.range.unwrap_or(false);
308 (Some(legend), full, delta, range)
309 }
310 })
311 .unwrap_or((None, false, false, false))
312}
313
314fn semantic_tokens_full_supported(full: &Option<SemanticTokensFullOptions>) -> bool {
315 match full {
316 Some(SemanticTokensFullOptions::Bool(v)) => *v,
317 Some(SemanticTokensFullOptions::Delta { .. }) => true,
318 None => false,
319 }
320}
321
322fn semantic_tokens_full_delta_supported(full: &Option<SemanticTokensFullOptions>) -> bool {
323 match full {
324 Some(SemanticTokensFullOptions::Delta { delta }) => delta.unwrap_or(false),
325 _ => false,
326 }
327}
328
329#[derive(Debug)]
331enum LspCommand {
332 Initialize {
334 root_uri: Option<Uri>,
335 initialization_options: Option<Value>,
336 response: oneshot::Sender<Result<InitializeResult, String>>,
337 },
338
339 DidOpen {
341 uri: Uri,
342 text: String,
343 language_id: String,
344 },
345
346 DidChange {
348 uri: Uri,
349 content_changes: Vec<TextDocumentContentChangeEvent>,
350 },
351
352 DidSave { uri: Uri, text: Option<String> },
354
355 Completion {
357 request_id: u64,
358 uri: Uri,
359 line: u32,
360 character: u32,
361 },
362
363 GotoDefinition {
365 request_id: u64,
366 uri: Uri,
367 line: u32,
368 character: u32,
369 },
370
371 Rename {
373 request_id: u64,
374 uri: Uri,
375 line: u32,
376 character: u32,
377 new_name: String,
378 },
379
380 Hover {
382 request_id: u64,
383 uri: Uri,
384 line: u32,
385 character: u32,
386 },
387
388 References {
390 request_id: u64,
391 uri: Uri,
392 line: u32,
393 character: u32,
394 },
395
396 SignatureHelp {
398 request_id: u64,
399 uri: Uri,
400 line: u32,
401 character: u32,
402 },
403
404 CodeActions {
406 request_id: u64,
407 uri: Uri,
408 start_line: u32,
409 start_char: u32,
410 end_line: u32,
411 end_char: u32,
412 diagnostics: Vec<lsp_types::Diagnostic>,
413 },
414
415 DocumentDiagnostic {
417 request_id: u64,
418 uri: Uri,
419 previous_result_id: Option<String>,
421 },
422
423 InlayHints {
425 request_id: u64,
426 uri: Uri,
427 start_line: u32,
429 start_char: u32,
430 end_line: u32,
431 end_char: u32,
432 },
433
434 SemanticTokensFull { request_id: u64, uri: Uri },
436
437 SemanticTokensFullDelta {
439 request_id: u64,
440 uri: Uri,
441 previous_result_id: String,
442 },
443
444 SemanticTokensRange {
446 request_id: u64,
447 uri: Uri,
448 range: lsp_types::Range,
449 },
450
451 CancelRequest {
453 request_id: u64,
455 },
456
457 PluginRequest {
459 request_id: u64,
460 method: String,
461 params: Option<Value>,
462 },
463
464 Shutdown,
466}
467
468struct LspState {
470 stdin: Arc<tokio::sync::Mutex<ChildStdin>>,
472
473 next_id: i64,
475
476 capabilities: Option<ServerCapabilities>,
478
479 document_versions: HashMap<PathBuf, i64>,
481
482 pending_opens: HashMap<PathBuf, Instant>,
485
486 initialized: bool,
488
489 async_tx: std_mpsc::Sender<AsyncMessage>,
491
492 language: String,
494
495 active_requests: HashMap<u64, i64>,
498}
499
500impl LspState {
501 #[allow(clippy::type_complexity)]
503 async fn replay_pending_commands(
504 &mut self,
505 commands: Vec<LspCommand>,
506 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
507 ) {
508 if commands.is_empty() {
509 return;
510 }
511 tracing::info!(
512 "Replaying {} pending commands after initialization",
513 commands.len()
514 );
515 for cmd in commands {
516 match cmd {
517 LspCommand::DidOpen {
518 uri,
519 text,
520 language_id,
521 } => {
522 tracing::info!("Replaying DidOpen for {}", uri.as_str());
523 let _ = self
524 .handle_did_open_sequential(uri, text, language_id, pending)
525 .await;
526 }
527 LspCommand::DidChange {
528 uri,
529 content_changes,
530 } => {
531 tracing::info!("Replaying DidChange for {}", uri.as_str());
532 let _ = self
533 .handle_did_change_sequential(uri, content_changes, pending)
534 .await;
535 }
536 LspCommand::DidSave { uri, text } => {
537 tracing::info!("Replaying DidSave for {}", uri.as_str());
538 let _ = self.handle_did_save(uri, text).await;
539 }
540 LspCommand::SemanticTokensFull { request_id, uri } => {
541 tracing::info!("Replaying semantic tokens request for {}", uri.as_str());
542 let _ = self
543 .handle_semantic_tokens_full(request_id, uri, pending)
544 .await;
545 }
546 LspCommand::SemanticTokensFullDelta {
547 request_id,
548 uri,
549 previous_result_id,
550 } => {
551 tracing::info!(
552 "Replaying semantic tokens delta request for {}",
553 uri.as_str()
554 );
555 let _ = self
556 .handle_semantic_tokens_full_delta(
557 request_id,
558 uri,
559 previous_result_id,
560 pending,
561 )
562 .await;
563 }
564 LspCommand::SemanticTokensRange {
565 request_id,
566 uri,
567 range,
568 } => {
569 tracing::info!(
570 "Replaying semantic tokens range request for {}",
571 uri.as_str()
572 );
573 let _ = self
574 .handle_semantic_tokens_range(request_id, uri, range, pending)
575 .await;
576 }
577 _ => {}
578 }
579 }
580 }
581
582 async fn write_message<T: Serialize>(&mut self, message: &T) -> Result<(), String> {
584 let json =
585 serde_json::to_string(message).map_err(|e| format!("Serialization error: {}", e))?;
586
587 let content = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
588
589 tracing::trace!("Writing LSP message to stdin ({} bytes)", content.len());
590
591 let mut stdin = self.stdin.lock().await;
592 stdin
593 .write_all(content.as_bytes())
594 .await
595 .map_err(|e| format!("Failed to write to stdin: {}", e))?;
596
597 stdin
598 .flush()
599 .await
600 .map_err(|e| format!("Failed to flush stdin: {}", e))?;
601
602 tracing::trace!("Successfully sent LSP message");
603
604 Ok(())
605 }
606
607 async fn send_notification<N>(&mut self, params: N::Params) -> Result<(), String>
609 where
610 N: Notification,
611 {
612 let notification = JsonRpcNotification {
613 jsonrpc: "2.0".to_string(),
614 method: N::METHOD.to_string(),
615 params: Some(
616 serde_json::to_value(params)
617 .map_err(|e| format!("Failed to serialize params: {}", e))?,
618 ),
619 };
620
621 self.write_message(¬ification).await
622 }
623
624 #[allow(clippy::type_complexity)]
626 async fn send_request_sequential<P: Serialize, R: for<'de> Deserialize<'de>>(
627 &mut self,
628 method: &str,
629 params: Option<P>,
630 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
631 ) -> Result<R, String> {
632 self.send_request_sequential_tracked(method, params, pending, None)
633 .await
634 }
635
636 #[allow(clippy::type_complexity)]
638 async fn send_request_sequential_tracked<P: Serialize, R: for<'de> Deserialize<'de>>(
639 &mut self,
640 method: &str,
641 params: Option<P>,
642 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
643 editor_request_id: Option<u64>,
644 ) -> Result<R, String> {
645 let id = self.next_id;
646 self.next_id += 1;
647
648 if let Some(editor_id) = editor_request_id {
650 self.active_requests.insert(editor_id, id);
651 tracing::trace!("Tracking request: editor_id={}, lsp_id={}", editor_id, id);
652 }
653
654 let params_value = params
655 .map(|p| serde_json::to_value(p))
656 .transpose()
657 .map_err(|e| format!("Failed to serialize params: {}", e))?;
658 let request = JsonRpcRequest {
659 jsonrpc: "2.0".to_string(),
660 id,
661 method: method.to_string(),
662 params: params_value,
663 };
664
665 let (tx, rx) = oneshot::channel();
666 pending.lock().unwrap().insert(id, tx);
667
668 self.write_message(&request).await?;
669
670 tracing::trace!("Sent LSP request id={}, waiting for response...", id);
671
672 let result = rx
674 .await
675 .map_err(|_| "Response channel closed".to_string())??;
676
677 tracing::trace!("Received LSP response for request id={}", id);
678
679 if let Some(editor_id) = editor_request_id {
681 self.active_requests.remove(&editor_id);
682 tracing::trace!("Completed request: editor_id={}, lsp_id={}", editor_id, id);
683 }
684
685 serde_json::from_value(result).map_err(|e| format!("Failed to deserialize response: {}", e))
686 }
687
688 #[allow(clippy::type_complexity)]
690 async fn handle_initialize_sequential(
691 &mut self,
692 root_uri: Option<Uri>,
693 initialization_options: Option<Value>,
694 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
695 ) -> Result<InitializeResult, String> {
696 tracing::info!(
697 "Initializing async LSP server with root_uri: {:?}, initialization_options: {:?}",
698 root_uri,
699 initialization_options
700 );
701
702 let workspace_folders = root_uri.as_ref().map(|uri| {
703 vec![WorkspaceFolder {
704 uri: uri.clone(),
705 name: uri
706 .path()
707 .as_str()
708 .split('/')
709 .next_back()
710 .unwrap_or("workspace")
711 .to_string(),
712 }]
713 });
714
715 #[allow(deprecated)]
716 let params = InitializeParams {
717 process_id: Some(std::process::id()),
718 capabilities: create_client_capabilities(),
719 workspace_folders,
720 initialization_options,
721 root_uri: root_uri.clone(),
724 ..Default::default()
725 };
726
727 let result: InitializeResult = self
728 .send_request_sequential(Initialize::METHOD, Some(params), pending)
729 .await?;
730
731 self.capabilities = Some(result.capabilities.clone());
732
733 self.send_notification::<Initialized>(InitializedParams {})
735 .await?;
736
737 self.initialized = true;
738
739 let completion_trigger_characters = result
741 .capabilities
742 .completion_provider
743 .as_ref()
744 .and_then(|cp| cp.trigger_characters.clone())
745 .unwrap_or_default();
746
747 let (
748 semantic_tokens_legend,
749 semantic_tokens_full,
750 semantic_tokens_full_delta,
751 semantic_tokens_range,
752 ) = extract_semantic_token_capability(&result.capabilities);
753
754 let _ = self.async_tx.send(AsyncMessage::LspInitialized {
756 language: self.language.clone(),
757 completion_trigger_characters,
758 semantic_tokens_legend,
759 semantic_tokens_full,
760 semantic_tokens_full_delta,
761 semantic_tokens_range,
762 });
763
764 let _ = self.async_tx.send(AsyncMessage::LspStatusUpdate {
766 language: self.language.clone(),
767 status: LspServerStatus::Running,
768 message: None,
769 });
770
771 tracing::info!("Async LSP server initialized successfully");
772
773 Ok(result)
774 }
775
776 #[allow(clippy::type_complexity)]
778 async fn handle_did_open_sequential(
779 &mut self,
780 uri: Uri,
781 text: String,
782 language_id: String,
783 _pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
784 ) -> Result<(), String> {
785 let path = PathBuf::from(uri.path().as_str());
786
787 if should_skip_did_open(&self.document_versions, &path, &self.language, &uri) {
788 return Ok(());
789 }
790
791 tracing::trace!("LSP: did_open for {}", uri.as_str());
792
793 let params = DidOpenTextDocumentParams {
794 text_document: TextDocumentItem {
795 uri: uri.clone(),
796 language_id,
797 version: 0,
798 text,
799 },
800 };
801
802 self.document_versions.insert(path.clone(), 0);
803
804 self.pending_opens.insert(path, Instant::now());
806
807 self.send_notification::<DidOpenTextDocument>(params).await
808 }
809
810 #[allow(clippy::type_complexity)]
812 async fn handle_did_change_sequential(
813 &mut self,
814 uri: Uri,
815 content_changes: Vec<TextDocumentContentChangeEvent>,
816 _pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
817 ) -> Result<(), String> {
818 tracing::trace!("LSP: did_change for {}", uri.as_str());
819
820 let path = PathBuf::from(uri.path().as_str());
821
822 if !self.document_versions.contains_key(&path) {
825 tracing::debug!(
826 "LSP ({}): skipping didChange - document not yet opened",
827 self.language
828 );
829 return Ok(());
830 }
831
832 if let Some(opened_at) = self.pending_opens.get(&path) {
836 let elapsed = opened_at.elapsed();
837 let grace_period = std::time::Duration::from_millis(DID_OPEN_GRACE_PERIOD_MS);
838 if elapsed < grace_period {
839 let wait_time = grace_period - elapsed;
840 tracing::debug!(
841 "LSP ({}): waiting {:?} for didOpen grace period before didChange",
842 self.language,
843 wait_time
844 );
845 tokio::time::sleep(wait_time).await;
846 }
847 self.pending_opens.remove(&path);
849 }
850
851 let version = self.document_versions.entry(path).or_insert(0);
852 *version += 1;
853
854 let params = DidChangeTextDocumentParams {
855 text_document: VersionedTextDocumentIdentifier {
856 uri: uri.clone(),
857 version: *version as i32,
858 },
859 content_changes,
860 };
861
862 self.send_notification::<DidChangeTextDocument>(params)
863 .await
864 }
865
866 async fn handle_did_save(&mut self, uri: Uri, text: Option<String>) -> Result<(), String> {
868 tracing::trace!("LSP: did_save for {}", uri.as_str());
869
870 let params = DidSaveTextDocumentParams {
871 text_document: TextDocumentIdentifier { uri },
872 text,
873 };
874
875 self.send_notification::<DidSaveTextDocument>(params).await
876 }
877
878 #[allow(clippy::type_complexity)]
880 async fn handle_completion(
881 &mut self,
882 request_id: u64,
883 uri: Uri,
884 line: u32,
885 character: u32,
886 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
887 ) -> Result<(), String> {
888 use lsp_types::{
889 CompletionParams, PartialResultParams, Position, TextDocumentIdentifier,
890 TextDocumentPositionParams, WorkDoneProgressParams,
891 };
892
893 tracing::trace!(
894 "LSP: completion request at {}:{}:{}",
895 uri.as_str(),
896 line,
897 character
898 );
899
900 let params = CompletionParams {
901 text_document_position: TextDocumentPositionParams {
902 text_document: TextDocumentIdentifier { uri },
903 position: Position { line, character },
904 },
905 work_done_progress_params: WorkDoneProgressParams::default(),
906 partial_result_params: PartialResultParams::default(),
907 context: None,
908 };
909
910 match self
912 .send_request_sequential_tracked::<_, Value>(
913 "textDocument/completion",
914 Some(params),
915 pending,
916 Some(request_id),
917 )
918 .await
919 {
920 Ok(result) => {
921 let items = if let Ok(list) =
923 serde_json::from_value::<lsp_types::CompletionList>(result.clone())
924 {
925 list.items
926 } else {
927 serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result)
928 .unwrap_or_default()
929 };
930
931 let _ = self
933 .async_tx
934 .send(AsyncMessage::LspCompletion { request_id, items });
935 Ok(())
936 }
937 Err(e) => {
938 tracing::error!("Completion request failed: {}", e);
939 let _ = self.async_tx.send(AsyncMessage::LspCompletion {
941 request_id,
942 items: vec![],
943 });
944 Err(e)
945 }
946 }
947 }
948
949 #[allow(clippy::type_complexity)]
951 async fn handle_goto_definition(
952 &mut self,
953 request_id: u64,
954 uri: Uri,
955 line: u32,
956 character: u32,
957 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
958 ) -> Result<(), String> {
959 use lsp_types::{
960 GotoDefinitionParams, PartialResultParams, Position, TextDocumentIdentifier,
961 TextDocumentPositionParams, WorkDoneProgressParams,
962 };
963
964 tracing::trace!(
965 "LSP: go-to-definition request at {}:{}:{}",
966 uri.as_str(),
967 line,
968 character
969 );
970
971 let params = GotoDefinitionParams {
972 text_document_position_params: TextDocumentPositionParams {
973 text_document: TextDocumentIdentifier { uri },
974 position: Position { line, character },
975 },
976 work_done_progress_params: WorkDoneProgressParams::default(),
977 partial_result_params: PartialResultParams::default(),
978 };
979
980 match self
982 .send_request_sequential::<_, Value>("textDocument/definition", Some(params), pending)
983 .await
984 {
985 Ok(result) => {
986 let locations = if let Ok(loc) =
988 serde_json::from_value::<lsp_types::Location>(result.clone())
989 {
990 vec![loc]
991 } else if let Ok(locs) =
992 serde_json::from_value::<Vec<lsp_types::Location>>(result.clone())
993 {
994 locs
995 } else if let Ok(links) =
996 serde_json::from_value::<Vec<lsp_types::LocationLink>>(result)
997 {
998 links
1000 .into_iter()
1001 .map(|link| lsp_types::Location {
1002 uri: link.target_uri,
1003 range: link.target_selection_range,
1004 })
1005 .collect()
1006 } else {
1007 vec![]
1008 };
1009
1010 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1012 request_id,
1013 locations,
1014 });
1015 Ok(())
1016 }
1017 Err(e) => {
1018 tracing::error!("Go-to-definition request failed: {}", e);
1019 let _ = self.async_tx.send(AsyncMessage::LspGotoDefinition {
1021 request_id,
1022 locations: vec![],
1023 });
1024 Err(e)
1025 }
1026 }
1027 }
1028
1029 #[allow(clippy::type_complexity)]
1031 async fn handle_rename(
1032 &mut self,
1033 request_id: u64,
1034 uri: Uri,
1035 line: u32,
1036 character: u32,
1037 new_name: String,
1038 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1039 ) -> Result<(), String> {
1040 use lsp_types::{
1041 Position, RenameParams, TextDocumentIdentifier, TextDocumentPositionParams,
1042 WorkDoneProgressParams,
1043 };
1044
1045 tracing::trace!(
1046 "LSP: rename request at {}:{}:{} to '{}'",
1047 uri.as_str(),
1048 line,
1049 character,
1050 new_name
1051 );
1052
1053 let params = RenameParams {
1054 text_document_position: TextDocumentPositionParams {
1055 text_document: TextDocumentIdentifier { uri },
1056 position: Position { line, character },
1057 },
1058 new_name,
1059 work_done_progress_params: WorkDoneProgressParams::default(),
1060 };
1061
1062 match self
1064 .send_request_sequential::<_, Value>("textDocument/rename", Some(params), pending)
1065 .await
1066 {
1067 Ok(result) => {
1068 match serde_json::from_value::<lsp_types::WorkspaceEdit>(result) {
1070 Ok(workspace_edit) => {
1071 let _ = self.async_tx.send(AsyncMessage::LspRename {
1073 request_id,
1074 result: Ok(workspace_edit),
1075 });
1076 Ok(())
1077 }
1078 Err(e) => {
1079 tracing::error!("Failed to parse rename response: {}", e);
1080 let _ = self.async_tx.send(AsyncMessage::LspRename {
1081 request_id,
1082 result: Err(format!("Failed to parse rename response: {}", e)),
1083 });
1084 Err(format!("Failed to parse rename response: {}", e))
1085 }
1086 }
1087 }
1088 Err(e) => {
1089 tracing::error!("Rename request failed: {}", e);
1090 let _ = self.async_tx.send(AsyncMessage::LspRename {
1092 request_id,
1093 result: Err(e.clone()),
1094 });
1095 Err(e)
1096 }
1097 }
1098 }
1099
1100 #[allow(clippy::type_complexity)]
1102 async fn handle_hover(
1103 &mut self,
1104 request_id: u64,
1105 uri: Uri,
1106 line: u32,
1107 character: u32,
1108 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1109 ) -> Result<(), String> {
1110 use lsp_types::{
1111 HoverParams, Position, TextDocumentIdentifier, TextDocumentPositionParams,
1112 WorkDoneProgressParams,
1113 };
1114
1115 tracing::trace!(
1116 "LSP: hover request at {}:{}:{}",
1117 uri.as_str(),
1118 line,
1119 character
1120 );
1121
1122 let params = HoverParams {
1123 text_document_position_params: TextDocumentPositionParams {
1124 text_document: TextDocumentIdentifier { uri },
1125 position: Position { line, character },
1126 },
1127 work_done_progress_params: WorkDoneProgressParams::default(),
1128 };
1129
1130 match self
1132 .send_request_sequential::<_, Value>("textDocument/hover", Some(params), pending)
1133 .await
1134 {
1135 Ok(result) => {
1136 tracing::debug!("Raw LSP hover response: {:?}", result);
1137 let (contents, is_markdown, range) = if result.is_null() {
1139 (String::new(), false, None)
1141 } else {
1142 match serde_json::from_value::<lsp_types::Hover>(result) {
1143 Ok(hover) => {
1144 let (contents, is_markdown) =
1146 Self::extract_hover_contents(&hover.contents);
1147 let range = hover.range.map(|r| {
1149 (
1150 (r.start.line, r.start.character),
1151 (r.end.line, r.end.character),
1152 )
1153 });
1154 (contents, is_markdown, range)
1155 }
1156 Err(e) => {
1157 tracing::error!("Failed to parse hover response: {}", e);
1158 (String::new(), false, None)
1159 }
1160 }
1161 };
1162
1163 let _ = self.async_tx.send(AsyncMessage::LspHover {
1165 request_id,
1166 contents,
1167 is_markdown,
1168 range,
1169 });
1170 Ok(())
1171 }
1172 Err(e) => {
1173 tracing::error!("Hover request failed: {}", e);
1174 let _ = self.async_tx.send(AsyncMessage::LspHover {
1176 request_id,
1177 contents: String::new(),
1178 is_markdown: false,
1179 range: None,
1180 });
1181 Err(e)
1182 }
1183 }
1184 }
1185
1186 fn extract_hover_contents(contents: &lsp_types::HoverContents) -> (String, bool) {
1189 use lsp_types::{HoverContents, MarkedString, MarkupContent, MarkupKind};
1190
1191 match contents {
1192 HoverContents::Scalar(marked) => match marked {
1193 MarkedString::String(s) => (s.clone(), false),
1194 MarkedString::LanguageString(ls) => {
1195 (format!("```{}\n{}\n```", ls.language, ls.value), true)
1197 }
1198 },
1199 HoverContents::Array(arr) => {
1200 let content = arr
1202 .iter()
1203 .map(|marked| match marked {
1204 MarkedString::String(s) => s.clone(),
1205 MarkedString::LanguageString(ls) => {
1206 format!("```{}\n{}\n```", ls.language, ls.value)
1207 }
1208 })
1209 .collect::<Vec<_>>()
1210 .join("\n\n");
1211 (content, true)
1212 }
1213 HoverContents::Markup(MarkupContent { kind, value }) => {
1214 let is_markdown = matches!(kind, MarkupKind::Markdown);
1216 (value.clone(), is_markdown)
1217 }
1218 }
1219 }
1220
1221 #[allow(clippy::type_complexity)]
1223 async fn handle_references(
1224 &mut self,
1225 request_id: u64,
1226 uri: Uri,
1227 line: u32,
1228 character: u32,
1229 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1230 ) -> Result<(), String> {
1231 use lsp_types::{
1232 PartialResultParams, Position, ReferenceContext, ReferenceParams,
1233 TextDocumentIdentifier, WorkDoneProgressParams,
1234 };
1235
1236 tracing::trace!(
1237 "LSP: find references request at {}:{}:{}",
1238 uri.as_str(),
1239 line,
1240 character
1241 );
1242
1243 let params = ReferenceParams {
1244 text_document_position: lsp_types::TextDocumentPositionParams {
1245 text_document: TextDocumentIdentifier { uri },
1246 position: Position { line, character },
1247 },
1248 work_done_progress_params: WorkDoneProgressParams::default(),
1249 partial_result_params: PartialResultParams::default(),
1250 context: ReferenceContext {
1251 include_declaration: true,
1252 },
1253 };
1254
1255 match self
1257 .send_request_sequential::<_, Value>("textDocument/references", Some(params), pending)
1258 .await
1259 {
1260 Ok(result) => {
1261 let locations = if result.is_null() {
1263 Vec::new()
1264 } else {
1265 serde_json::from_value::<Vec<lsp_types::Location>>(result).unwrap_or_default()
1266 };
1267
1268 tracing::trace!("LSP: found {} references", locations.len());
1269
1270 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1272 request_id,
1273 locations,
1274 });
1275 Ok(())
1276 }
1277 Err(e) => {
1278 tracing::error!("Find references request failed: {}", e);
1279 let _ = self.async_tx.send(AsyncMessage::LspReferences {
1281 request_id,
1282 locations: Vec::new(),
1283 });
1284 Err(e)
1285 }
1286 }
1287 }
1288
1289 #[allow(clippy::type_complexity)]
1291 async fn handle_signature_help(
1292 &mut self,
1293 request_id: u64,
1294 uri: Uri,
1295 line: u32,
1296 character: u32,
1297 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1298 ) -> Result<(), String> {
1299 use lsp_types::{
1300 Position, SignatureHelpParams, TextDocumentIdentifier, TextDocumentPositionParams,
1301 WorkDoneProgressParams,
1302 };
1303
1304 tracing::trace!(
1305 "LSP: signature help request at {}:{}:{}",
1306 uri.as_str(),
1307 line,
1308 character
1309 );
1310
1311 let params = SignatureHelpParams {
1312 text_document_position_params: TextDocumentPositionParams {
1313 text_document: TextDocumentIdentifier { uri },
1314 position: Position { line, character },
1315 },
1316 work_done_progress_params: WorkDoneProgressParams::default(),
1317 context: None, };
1319
1320 match self
1322 .send_request_sequential::<_, Value>(
1323 "textDocument/signatureHelp",
1324 Some(params),
1325 pending,
1326 )
1327 .await
1328 {
1329 Ok(result) => {
1330 let signature_help = if result.is_null() {
1332 None
1333 } else {
1334 serde_json::from_value::<lsp_types::SignatureHelp>(result).ok()
1335 };
1336
1337 tracing::trace!(
1338 "LSP: signature help received: {} signatures",
1339 signature_help
1340 .as_ref()
1341 .map(|h| h.signatures.len())
1342 .unwrap_or(0)
1343 );
1344
1345 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1347 request_id,
1348 signature_help,
1349 });
1350 Ok(())
1351 }
1352 Err(e) => {
1353 tracing::error!("Signature help request failed: {}", e);
1354 let _ = self.async_tx.send(AsyncMessage::LspSignatureHelp {
1356 request_id,
1357 signature_help: None,
1358 });
1359 Err(e)
1360 }
1361 }
1362 }
1363
1364 #[allow(clippy::type_complexity)]
1366 #[allow(clippy::too_many_arguments)]
1367 async fn handle_code_actions(
1368 &mut self,
1369 request_id: u64,
1370 uri: Uri,
1371 start_line: u32,
1372 start_char: u32,
1373 end_line: u32,
1374 end_char: u32,
1375 diagnostics: Vec<lsp_types::Diagnostic>,
1376 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1377 ) -> Result<(), String> {
1378 use lsp_types::{
1379 CodeActionContext, CodeActionParams, PartialResultParams, Position, Range,
1380 TextDocumentIdentifier, WorkDoneProgressParams,
1381 };
1382
1383 tracing::trace!(
1384 "LSP: code actions request at {}:{}:{}-{}:{}",
1385 uri.as_str(),
1386 start_line,
1387 start_char,
1388 end_line,
1389 end_char
1390 );
1391
1392 let params = CodeActionParams {
1393 text_document: TextDocumentIdentifier { uri },
1394 range: Range {
1395 start: Position {
1396 line: start_line,
1397 character: start_char,
1398 },
1399 end: Position {
1400 line: end_line,
1401 character: end_char,
1402 },
1403 },
1404 context: CodeActionContext {
1405 diagnostics,
1406 only: None,
1407 trigger_kind: None,
1408 },
1409 work_done_progress_params: WorkDoneProgressParams::default(),
1410 partial_result_params: PartialResultParams::default(),
1411 };
1412
1413 match self
1415 .send_request_sequential::<_, Value>("textDocument/codeAction", Some(params), pending)
1416 .await
1417 {
1418 Ok(result) => {
1419 let actions = if result.is_null() {
1421 Vec::new()
1422 } else {
1423 serde_json::from_value::<Vec<lsp_types::CodeActionOrCommand>>(result)
1424 .unwrap_or_default()
1425 };
1426
1427 tracing::trace!("LSP: received {} code actions", actions.len());
1428
1429 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1431 request_id,
1432 actions,
1433 });
1434 Ok(())
1435 }
1436 Err(e) => {
1437 tracing::error!("Code actions request failed: {}", e);
1438 let _ = self.async_tx.send(AsyncMessage::LspCodeActions {
1440 request_id,
1441 actions: Vec::new(),
1442 });
1443 Err(e)
1444 }
1445 }
1446 }
1447
1448 #[allow(clippy::type_complexity)]
1450 async fn handle_document_diagnostic(
1451 &mut self,
1452 request_id: u64,
1453 uri: Uri,
1454 previous_result_id: Option<String>,
1455 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1456 ) -> Result<(), String> {
1457 use lsp_types::{
1458 DocumentDiagnosticParams, PartialResultParams, TextDocumentIdentifier,
1459 WorkDoneProgressParams,
1460 };
1461
1462 if self
1464 .capabilities
1465 .as_ref()
1466 .and_then(|c| c.diagnostic_provider.as_ref())
1467 .is_none()
1468 {
1469 tracing::trace!(
1470 "LSP: server does not support pull diagnostics, skipping request for {}",
1471 uri.as_str()
1472 );
1473 return Ok(());
1474 }
1475
1476 tracing::trace!(
1477 "LSP: document diagnostic request for {} (previous_result_id: {:?})",
1478 uri.as_str(),
1479 previous_result_id
1480 );
1481
1482 let params = DocumentDiagnosticParams {
1483 text_document: TextDocumentIdentifier { uri: uri.clone() },
1484 identifier: None,
1485 previous_result_id,
1486 work_done_progress_params: WorkDoneProgressParams::default(),
1487 partial_result_params: PartialResultParams::default(),
1488 };
1489
1490 match self
1492 .send_request_sequential::<_, Value>("textDocument/diagnostic", Some(params), pending)
1493 .await
1494 {
1495 Ok(result) => {
1496 let uri_string = uri.as_str().to_string();
1499
1500 if let Ok(full_report) = serde_json::from_value::<
1502 lsp_types::RelatedFullDocumentDiagnosticReport,
1503 >(result.clone())
1504 {
1505 let diagnostics = full_report.full_document_diagnostic_report.items;
1506 let result_id = full_report.full_document_diagnostic_report.result_id;
1507
1508 tracing::trace!(
1509 "LSP: received {} diagnostics for {} (result_id: {:?})",
1510 diagnostics.len(),
1511 uri_string,
1512 result_id
1513 );
1514
1515 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1516 request_id,
1517 uri: uri_string,
1518 result_id,
1519 diagnostics,
1520 unchanged: false,
1521 });
1522 } else if let Ok(unchanged_report) = serde_json::from_value::<
1523 lsp_types::RelatedUnchangedDocumentDiagnosticReport,
1524 >(result.clone())
1525 {
1526 let result_id = unchanged_report
1527 .unchanged_document_diagnostic_report
1528 .result_id;
1529
1530 tracing::trace!(
1531 "LSP: diagnostics unchanged for {} (result_id: {:?})",
1532 uri_string,
1533 result_id
1534 );
1535
1536 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1537 request_id,
1538 uri: uri_string,
1539 result_id: Some(result_id),
1540 diagnostics: Vec::new(),
1541 unchanged: true,
1542 });
1543 } else {
1544 tracing::warn!(
1546 "LSP: could not parse diagnostic report, sending empty: {}",
1547 result
1548 );
1549 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1550 request_id,
1551 uri: uri_string,
1552 result_id: None,
1553 diagnostics: Vec::new(),
1554 unchanged: false,
1555 });
1556 }
1557
1558 Ok(())
1559 }
1560 Err(e) => {
1561 tracing::error!("Document diagnostic request failed: {}", e);
1562 let _ = self.async_tx.send(AsyncMessage::LspPulledDiagnostics {
1564 request_id,
1565 uri: uri.as_str().to_string(),
1566 result_id: None,
1567 diagnostics: Vec::new(),
1568 unchanged: false,
1569 });
1570 Err(e)
1571 }
1572 }
1573 }
1574
1575 #[allow(clippy::type_complexity)]
1577 #[allow(clippy::too_many_arguments)]
1578 async fn handle_inlay_hints(
1579 &mut self,
1580 request_id: u64,
1581 uri: Uri,
1582 start_line: u32,
1583 start_char: u32,
1584 end_line: u32,
1585 end_char: u32,
1586 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1587 ) -> Result<(), String> {
1588 use lsp_types::{
1589 InlayHintParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
1590 };
1591
1592 tracing::trace!(
1593 "LSP: inlay hints request for {} ({}:{} - {}:{})",
1594 uri.as_str(),
1595 start_line,
1596 start_char,
1597 end_line,
1598 end_char
1599 );
1600
1601 let params = InlayHintParams {
1602 text_document: TextDocumentIdentifier { uri: uri.clone() },
1603 range: Range {
1604 start: Position {
1605 line: start_line,
1606 character: start_char,
1607 },
1608 end: Position {
1609 line: end_line,
1610 character: end_char,
1611 },
1612 },
1613 work_done_progress_params: WorkDoneProgressParams::default(),
1614 };
1615
1616 match self
1617 .send_request_sequential::<_, Option<Vec<lsp_types::InlayHint>>>(
1618 "textDocument/inlayHint",
1619 Some(params),
1620 pending,
1621 )
1622 .await
1623 {
1624 Ok(hints) => {
1625 let hints = hints.unwrap_or_default();
1626 let uri_string = uri.as_str().to_string();
1627
1628 tracing::trace!(
1629 "LSP: received {} inlay hints for {}",
1630 hints.len(),
1631 uri_string
1632 );
1633
1634 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
1635 request_id,
1636 uri: uri_string,
1637 hints,
1638 });
1639
1640 Ok(())
1641 }
1642 Err(e) => {
1643 tracing::error!("Inlay hints request failed: {}", e);
1644 let _ = self.async_tx.send(AsyncMessage::LspInlayHints {
1646 request_id,
1647 uri: uri.as_str().to_string(),
1648 hints: Vec::new(),
1649 });
1650 Err(e)
1651 }
1652 }
1653 }
1654
1655 #[allow(clippy::type_complexity)]
1656 async fn handle_semantic_tokens_full(
1657 &mut self,
1658 request_id: u64,
1659 uri: Uri,
1660 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1661 ) -> Result<(), String> {
1662 use lsp_types::{
1663 request::SemanticTokensFullRequest, PartialResultParams, TextDocumentIdentifier,
1664 WorkDoneProgressParams,
1665 };
1666
1667 tracing::trace!("LSP: semanticTokens/full request for {}", uri.as_str());
1668
1669 let params = SemanticTokensParams {
1670 work_done_progress_params: WorkDoneProgressParams::default(),
1671 partial_result_params: PartialResultParams::default(),
1672 text_document: TextDocumentIdentifier { uri: uri.clone() },
1673 };
1674
1675 match self
1676 .send_request_sequential_tracked::<_, Option<SemanticTokensResult>>(
1677 SemanticTokensFullRequest::METHOD,
1678 Some(params),
1679 pending,
1680 Some(request_id),
1681 )
1682 .await
1683 {
1684 Ok(result) => {
1685 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1686 request_id,
1687 uri: uri.as_str().to_string(),
1688 response: LspSemanticTokensResponse::Full(Ok(result)),
1689 });
1690 Ok(())
1691 }
1692 Err(e) => {
1693 tracing::error!("Semantic tokens request failed: {}", e);
1694 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1695 request_id,
1696 uri: uri.as_str().to_string(),
1697 response: LspSemanticTokensResponse::Full(Err(e.clone())),
1698 });
1699 Err(e)
1700 }
1701 }
1702 }
1703
1704 #[allow(clippy::type_complexity)]
1705 async fn handle_semantic_tokens_full_delta(
1706 &mut self,
1707 request_id: u64,
1708 uri: Uri,
1709 previous_result_id: String,
1710 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1711 ) -> Result<(), String> {
1712 use lsp_types::{
1713 request::SemanticTokensFullDeltaRequest, PartialResultParams,
1714 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, TextDocumentIdentifier,
1715 WorkDoneProgressParams,
1716 };
1717
1718 tracing::trace!(
1719 "LSP: semanticTokens/full/delta request for {}",
1720 uri.as_str()
1721 );
1722
1723 let params = SemanticTokensDeltaParams {
1724 work_done_progress_params: WorkDoneProgressParams::default(),
1725 partial_result_params: PartialResultParams::default(),
1726 text_document: TextDocumentIdentifier { uri: uri.clone() },
1727 previous_result_id,
1728 };
1729
1730 match self
1731 .send_request_sequential_tracked::<_, Option<SemanticTokensFullDeltaResult>>(
1732 SemanticTokensFullDeltaRequest::METHOD,
1733 Some(params),
1734 pending,
1735 Some(request_id),
1736 )
1737 .await
1738 {
1739 Ok(result) => {
1740 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1741 request_id,
1742 uri: uri.as_str().to_string(),
1743 response: LspSemanticTokensResponse::FullDelta(Ok(result)),
1744 });
1745 Ok(())
1746 }
1747 Err(e) => {
1748 tracing::error!("Semantic tokens delta request failed: {}", e);
1749 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1750 request_id,
1751 uri: uri.as_str().to_string(),
1752 response: LspSemanticTokensResponse::FullDelta(Err(e.clone())),
1753 });
1754 Err(e)
1755 }
1756 }
1757 }
1758
1759 #[allow(clippy::type_complexity)]
1760 async fn handle_semantic_tokens_range(
1761 &mut self,
1762 request_id: u64,
1763 uri: Uri,
1764 range: lsp_types::Range,
1765 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1766 ) -> Result<(), String> {
1767 use lsp_types::{
1768 request::SemanticTokensRangeRequest, PartialResultParams, SemanticTokensRangeParams,
1769 TextDocumentIdentifier, WorkDoneProgressParams,
1770 };
1771
1772 tracing::trace!("LSP: semanticTokens/range request for {}", uri.as_str());
1773
1774 let params = SemanticTokensRangeParams {
1775 work_done_progress_params: WorkDoneProgressParams::default(),
1776 partial_result_params: PartialResultParams::default(),
1777 text_document: TextDocumentIdentifier { uri: uri.clone() },
1778 range,
1779 };
1780
1781 match self
1782 .send_request_sequential_tracked::<_, Option<lsp_types::SemanticTokensRangeResult>>(
1783 SemanticTokensRangeRequest::METHOD,
1784 Some(params),
1785 pending,
1786 Some(request_id),
1787 )
1788 .await
1789 {
1790 Ok(result) => {
1791 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1792 request_id,
1793 uri: uri.as_str().to_string(),
1794 response: LspSemanticTokensResponse::Range(Ok(result)),
1795 });
1796 Ok(())
1797 }
1798 Err(e) => {
1799 tracing::error!("Semantic tokens range request failed: {}", e);
1800 let _ = self.async_tx.send(AsyncMessage::LspSemanticTokens {
1801 request_id,
1802 uri: uri.as_str().to_string(),
1803 response: LspSemanticTokensResponse::Range(Err(e.clone())),
1804 });
1805 Err(e)
1806 }
1807 }
1808 }
1809
1810 #[allow(clippy::type_complexity)]
1812 async fn handle_plugin_request(
1813 &mut self,
1814 request_id: u64,
1815 method: String,
1816 params: Option<Value>,
1817 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
1818 ) {
1819 tracing::trace!(
1820 "Plugin request {} => method={} params={:?}",
1821 request_id,
1822 method,
1823 params
1824 );
1825 let result = self
1826 .send_request_sequential_tracked::<Value, Value>(
1827 &method,
1828 params,
1829 pending,
1830 Some(request_id),
1831 )
1832 .await;
1833
1834 tracing::trace!(
1835 "Plugin request {} completed with result {:?}",
1836 request_id,
1837 &result
1838 );
1839 let _ = self.async_tx.send(AsyncMessage::PluginLspResponse {
1840 language: self.language.clone(),
1841 request_id,
1842 result,
1843 });
1844 }
1845
1846 async fn handle_shutdown(&mut self) -> Result<(), String> {
1848 tracing::info!("Shutting down async LSP server");
1849
1850 let notification = JsonRpcNotification {
1851 jsonrpc: "2.0".to_string(),
1852 method: "shutdown".to_string(),
1853 params: None,
1854 };
1855
1856 self.write_message(¬ification).await?;
1857
1858 let exit = JsonRpcNotification {
1859 jsonrpc: "2.0".to_string(),
1860 method: "exit".to_string(),
1861 params: None,
1862 };
1863
1864 self.write_message(&exit).await
1865 }
1866
1867 async fn send_cancel_request(&mut self, lsp_id: i64) -> Result<(), String> {
1869 tracing::trace!("Sending $/cancelRequest for LSP id {}", lsp_id);
1870
1871 let notification = JsonRpcNotification {
1872 jsonrpc: "2.0".to_string(),
1873 method: "$/cancelRequest".to_string(),
1874 params: Some(serde_json::json!({ "id": lsp_id })),
1875 };
1876
1877 self.write_message(¬ification).await
1878 }
1879
1880 async fn handle_cancel_request(&mut self, request_id: u64) -> Result<(), String> {
1882 if let Some(lsp_id) = self.active_requests.remove(&request_id) {
1883 tracing::info!(
1884 "Cancelling request: editor_id={}, lsp_id={}",
1885 request_id,
1886 lsp_id
1887 );
1888 self.send_cancel_request(lsp_id).await
1889 } else {
1890 tracing::trace!(
1891 "Cancel request ignored: no active LSP request for editor_id={}",
1892 request_id
1893 );
1894 Ok(())
1895 }
1896 }
1897}
1898
1899struct LspTask {
1901 _process: Child,
1903
1904 stdin: ChildStdin,
1906
1907 stdout: BufReader<ChildStdout>,
1909
1910 next_id: i64,
1912
1913 pending: HashMap<i64, oneshot::Sender<Result<Value, String>>>,
1915
1916 capabilities: Option<ServerCapabilities>,
1918
1919 document_versions: HashMap<PathBuf, i64>,
1921
1922 pending_opens: HashMap<PathBuf, Instant>,
1925
1926 initialized: bool,
1928
1929 async_tx: std_mpsc::Sender<AsyncMessage>,
1931
1932 language: String,
1934
1935 server_command: String,
1937
1938 stderr_log_path: std::path::PathBuf,
1940}
1941
1942impl LspTask {
1943 async fn spawn(
1945 command: &str,
1946 args: &[String],
1947 language: String,
1948 async_tx: std_mpsc::Sender<AsyncMessage>,
1949 process_limits: &ProcessLimits,
1950 stderr_log_path: std::path::PathBuf,
1951 ) -> Result<Self, String> {
1952 tracing::info!("Spawning async LSP server: {} {:?}", command, args);
1953 tracing::info!("Process limits: {:?}", process_limits);
1954 tracing::info!("LSP stderr will be logged to: {:?}", stderr_log_path);
1955
1956 if !Self::command_exists(command) {
1959 return Err(format!(
1960 "LSP server executable '{}' not found. Please install it or check your PATH.",
1961 command
1962 ));
1963 }
1964
1965 let stderr_file = std::fs::File::create(&stderr_log_path).map_err(|e| {
1967 format!(
1968 "Failed to create LSP stderr log file {:?}: {}",
1969 stderr_log_path, e
1970 )
1971 })?;
1972
1973 let mut cmd = Command::new(command);
1974 cmd.args(args)
1975 .stdin(std::process::Stdio::piped())
1976 .stdout(std::process::Stdio::piped())
1977 .stderr(std::process::Stdio::from(stderr_file))
1978 .kill_on_drop(true);
1979
1980 process_limits
1982 .apply_to_command(&mut cmd)
1983 .map_err(|e| format!("Failed to apply process limits: {}", e))?;
1984
1985 let mut process = cmd.spawn().map_err(|e| {
1986 format!(
1987 "Failed to spawn LSP server '{}': {}",
1988 command,
1989 match e.kind() {
1990 std::io::ErrorKind::NotFound => "executable not found in PATH".to_string(),
1991 std::io::ErrorKind::PermissionDenied =>
1992 "permission denied (check file permissions)".to_string(),
1993 _ => e.to_string(),
1994 }
1995 )
1996 })?;
1997
1998 let stdin = process
1999 .stdin
2000 .take()
2001 .ok_or_else(|| "Failed to get stdin".to_string())?;
2002
2003 let stdout = BufReader::new(
2004 process
2005 .stdout
2006 .take()
2007 .ok_or_else(|| "Failed to get stdout".to_string())?,
2008 );
2009
2010 Ok(Self {
2011 _process: process,
2012 stdin,
2013 stdout,
2014 next_id: 0,
2015 pending: HashMap::new(),
2016 capabilities: None,
2017 document_versions: HashMap::new(),
2018 pending_opens: HashMap::new(),
2019 initialized: false,
2020 async_tx,
2021 language,
2022 server_command: command.to_string(),
2023 stderr_log_path,
2024 })
2025 }
2026
2027 fn command_exists(command: &str) -> bool {
2029 use std::path::Path;
2030
2031 if command.contains('/') || command.contains('\\') {
2033 let path = Path::new(command);
2034 return path.exists() && path.is_file();
2035 }
2036
2037 if let Ok(path_var) = std::env::var("PATH") {
2039 #[cfg(unix)]
2040 let separator = ':';
2041 #[cfg(windows)]
2042 let separator = ';';
2043
2044 for dir in path_var.split(separator) {
2045 let full_path = Path::new(dir).join(command);
2046 if full_path.exists() && full_path.is_file() {
2047 return true;
2048 }
2049 #[cfg(windows)]
2051 {
2052 let with_exe = Path::new(dir).join(format!("{}.exe", command));
2053 if with_exe.exists() && with_exe.is_file() {
2054 return true;
2055 }
2056 }
2057 }
2058 }
2059
2060 false
2061 }
2062
2063 #[allow(clippy::type_complexity)]
2065 #[allow(clippy::too_many_arguments)]
2066 fn spawn_stdout_reader(
2067 mut stdout: BufReader<ChildStdout>,
2068 pending: Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2069 async_tx: std_mpsc::Sender<AsyncMessage>,
2070 language: String,
2071 server_command: String,
2072 stdin_writer: Arc<tokio::sync::Mutex<ChildStdin>>,
2073 stderr_log_path: std::path::PathBuf,
2074 shutting_down: Arc<AtomicBool>,
2075 ) {
2076 tokio::spawn(async move {
2077 tracing::info!("LSP stdout reader task started for {}", language);
2078 loop {
2079 match read_message_from_stdout(&mut stdout).await {
2080 Ok(message) => {
2081 tracing::trace!("Read message from LSP server: {:?}", message);
2082 if let Err(e) = handle_message_dispatch(
2083 message,
2084 &pending,
2085 &async_tx,
2086 &language,
2087 &server_command,
2088 &stdin_writer,
2089 )
2090 .await
2091 {
2092 tracing::error!("Error handling LSP message: {}", e);
2093 }
2094 }
2095 Err(e) => {
2096 if shutting_down.load(Ordering::SeqCst) {
2098 tracing::info!(
2099 "LSP stdout reader exiting due to graceful shutdown for {}",
2100 language
2101 );
2102 } else {
2103 tracing::error!("Error reading from LSP server: {}", e);
2104 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2105 language: language.clone(),
2106 status: LspServerStatus::Error,
2107 message: None,
2108 });
2109 let _ = async_tx.send(AsyncMessage::LspError {
2110 language: language.clone(),
2111 error: format!("Read error: {}", e),
2112 stderr_log_path: Some(stderr_log_path.clone()),
2113 });
2114 }
2115 break;
2116 }
2117 }
2118 }
2119 tracing::info!("LSP stdout reader task exiting for {}", language);
2120 });
2121 }
2122
2123 async fn run(self, mut command_rx: mpsc::Receiver<LspCommand>) {
2125 tracing::info!("LspTask::run() started for language: {}", self.language);
2126
2127 let stdin_writer = Arc::new(tokio::sync::Mutex::new(self.stdin));
2129
2130 let mut state = LspState {
2132 stdin: stdin_writer.clone(),
2133 next_id: self.next_id,
2134 capabilities: self.capabilities,
2135 document_versions: self.document_versions,
2136 pending_opens: self.pending_opens,
2137 initialized: self.initialized,
2138 async_tx: self.async_tx.clone(),
2139 language: self.language.clone(),
2140 active_requests: HashMap::new(),
2141 };
2142
2143 let pending = Arc::new(Mutex::new(self.pending));
2144 let async_tx = state.async_tx.clone();
2145 let language_clone = state.language.clone();
2146
2147 let shutting_down = Arc::new(AtomicBool::new(false));
2149
2150 Self::spawn_stdout_reader(
2152 self.stdout,
2153 pending.clone(),
2154 async_tx.clone(),
2155 language_clone.clone(),
2156 self.server_command.clone(),
2157 stdin_writer.clone(),
2158 self.stderr_log_path,
2159 shutting_down.clone(),
2160 );
2161
2162 let mut pending_commands = Vec::new();
2167 loop {
2168 tokio::select! {
2169 Some(cmd) = command_rx.recv() => {
2171 tracing::trace!("LspTask received command: {:?}", cmd);
2172 match cmd {
2173 LspCommand::Initialize { root_uri, initialization_options, response } => {
2174 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
2176 language: language_clone.clone(),
2177 status: LspServerStatus::Initializing,
2178 message: None,
2179 });
2180 tracing::info!("Processing Initialize command");
2181 let result =
2182 state.handle_initialize_sequential(root_uri, initialization_options, &pending).await;
2183 let success = result.is_ok();
2184 let _ = response.send(result);
2185
2186 if success {
2188 let queued = std::mem::take(&mut pending_commands);
2189 state.replay_pending_commands(queued, &pending).await;
2190 }
2191 }
2192 LspCommand::DidOpen {
2193 uri,
2194 text,
2195 language_id,
2196 } => {
2197 if state.initialized {
2198 tracing::info!("Processing DidOpen for {}", uri.as_str());
2199 let _ = state
2200 .handle_did_open_sequential(uri, text, language_id, &pending)
2201 .await;
2202 } else {
2203 tracing::trace!(
2204 "Queueing DidOpen for {} until initialization completes",
2205 uri.as_str()
2206 );
2207 pending_commands.push(LspCommand::DidOpen {
2208 uri,
2209 text,
2210 language_id,
2211 });
2212 }
2213 }
2214 LspCommand::DidChange {
2215 uri,
2216 content_changes,
2217 } => {
2218 if state.initialized {
2219 tracing::trace!("Processing DidChange for {}", uri.as_str());
2220 let _ = state
2221 .handle_did_change_sequential(uri, content_changes, &pending)
2222 .await;
2223 } else {
2224 tracing::trace!(
2225 "Queueing DidChange for {} until initialization completes",
2226 uri.as_str()
2227 );
2228 pending_commands.push(LspCommand::DidChange {
2229 uri,
2230 content_changes,
2231 });
2232 }
2233 }
2234 LspCommand::DidSave { uri, text } => {
2235 if state.initialized {
2236 tracing::info!("Processing DidSave for {}", uri.as_str());
2237 let _ = state.handle_did_save(uri, text).await;
2238 } else {
2239 tracing::trace!(
2240 "Queueing DidSave for {} until initialization completes",
2241 uri.as_str()
2242 );
2243 pending_commands.push(LspCommand::DidSave { uri, text });
2244 }
2245 }
2246 LspCommand::Completion {
2247 request_id,
2248 uri,
2249 line,
2250 character,
2251 } => {
2252 if state.initialized {
2253 tracing::info!(
2254 "Processing Completion request for {}",
2255 uri.as_str()
2256 );
2257 let _ = state
2258 .handle_completion(request_id, uri, line, character, &pending)
2259 .await;
2260 } else {
2261 tracing::trace!("LSP not initialized, sending empty completion");
2262 let _ = state.async_tx.send(AsyncMessage::LspCompletion {
2263 request_id,
2264 items: vec![],
2265 });
2266 }
2267 }
2268 LspCommand::GotoDefinition {
2269 request_id,
2270 uri,
2271 line,
2272 character,
2273 } => {
2274 if state.initialized {
2275 tracing::info!(
2276 "Processing GotoDefinition request for {}",
2277 uri.as_str()
2278 );
2279 let _ = state
2280 .handle_goto_definition(
2281 request_id, uri, line, character, &pending,
2282 )
2283 .await;
2284 } else {
2285 tracing::trace!("LSP not initialized, sending empty locations");
2286 let _ = state.async_tx.send(AsyncMessage::LspGotoDefinition {
2287 request_id,
2288 locations: vec![],
2289 });
2290 }
2291 }
2292 LspCommand::Rename {
2293 request_id,
2294 uri,
2295 line,
2296 character,
2297 new_name,
2298 } => {
2299 if state.initialized {
2300 tracing::info!("Processing Rename request for {}", uri.as_str());
2301 let _ = state
2302 .handle_rename(
2303 request_id, uri, line, character, new_name, &pending,
2304 )
2305 .await;
2306 } else {
2307 tracing::trace!("LSP not initialized, cannot rename");
2308 let _ = state.async_tx.send(AsyncMessage::LspRename {
2309 request_id,
2310 result: Err("LSP not initialized".to_string()),
2311 });
2312 }
2313 }
2314 LspCommand::Hover {
2315 request_id,
2316 uri,
2317 line,
2318 character,
2319 } => {
2320 if state.initialized {
2321 tracing::info!("Processing Hover request for {}", uri.as_str());
2322 let _ = state
2323 .handle_hover(request_id, uri, line, character, &pending)
2324 .await;
2325 } else {
2326 tracing::trace!("LSP not initialized, cannot get hover");
2327 let _ = state.async_tx.send(AsyncMessage::LspHover {
2328 request_id,
2329 contents: String::new(),
2330 is_markdown: false,
2331 range: None,
2332 });
2333 }
2334 }
2335 LspCommand::References {
2336 request_id,
2337 uri,
2338 line,
2339 character,
2340 } => {
2341 if state.initialized {
2342 tracing::info!("Processing References request for {}", uri.as_str());
2343 let _ = state
2344 .handle_references(request_id, uri, line, character, &pending)
2345 .await;
2346 } else {
2347 tracing::trace!("LSP not initialized, cannot get references");
2348 let _ = state.async_tx.send(AsyncMessage::LspReferences {
2349 request_id,
2350 locations: Vec::new(),
2351 });
2352 }
2353 }
2354 LspCommand::SignatureHelp {
2355 request_id,
2356 uri,
2357 line,
2358 character,
2359 } => {
2360 if state.initialized {
2361 tracing::info!("Processing SignatureHelp request for {}", uri.as_str());
2362 let _ = state
2363 .handle_signature_help(request_id, uri, line, character, &pending)
2364 .await;
2365 } else {
2366 tracing::trace!("LSP not initialized, cannot get signature help");
2367 let _ = state.async_tx.send(AsyncMessage::LspSignatureHelp {
2368 request_id,
2369 signature_help: None,
2370 });
2371 }
2372 }
2373 LspCommand::CodeActions {
2374 request_id,
2375 uri,
2376 start_line,
2377 start_char,
2378 end_line,
2379 end_char,
2380 diagnostics,
2381 } => {
2382 if state.initialized {
2383 tracing::info!("Processing CodeActions request for {}", uri.as_str());
2384 let _ = state
2385 .handle_code_actions(
2386 request_id,
2387 uri,
2388 start_line,
2389 start_char,
2390 end_line,
2391 end_char,
2392 diagnostics,
2393 &pending,
2394 )
2395 .await;
2396 } else {
2397 tracing::trace!("LSP not initialized, cannot get code actions");
2398 let _ = state.async_tx.send(AsyncMessage::LspCodeActions {
2399 request_id,
2400 actions: Vec::new(),
2401 });
2402 }
2403 }
2404 LspCommand::DocumentDiagnostic {
2405 request_id,
2406 uri,
2407 previous_result_id,
2408 } => {
2409 if state.initialized {
2410 tracing::info!(
2411 "Processing DocumentDiagnostic request for {}",
2412 uri.as_str()
2413 );
2414 let _ = state
2415 .handle_document_diagnostic(
2416 request_id,
2417 uri,
2418 previous_result_id,
2419 &pending,
2420 )
2421 .await;
2422 } else {
2423 tracing::trace!(
2424 "LSP not initialized, cannot get document diagnostics"
2425 );
2426 let _ = state.async_tx.send(AsyncMessage::LspPulledDiagnostics {
2427 request_id,
2428 uri: uri.as_str().to_string(),
2429 result_id: None,
2430 diagnostics: Vec::new(),
2431 unchanged: false,
2432 });
2433 }
2434 }
2435 LspCommand::InlayHints {
2436 request_id,
2437 uri,
2438 start_line,
2439 start_char,
2440 end_line,
2441 end_char,
2442 } => {
2443 if state.initialized {
2444 tracing::info!(
2445 "Processing InlayHints request for {}",
2446 uri.as_str()
2447 );
2448 let _ = state
2449 .handle_inlay_hints(
2450 request_id,
2451 uri,
2452 start_line,
2453 start_char,
2454 end_line,
2455 end_char,
2456 &pending,
2457 )
2458 .await;
2459 } else {
2460 tracing::trace!(
2461 "LSP not initialized, cannot get inlay hints"
2462 );
2463 let _ = state.async_tx.send(AsyncMessage::LspInlayHints {
2464 request_id,
2465 uri: uri.as_str().to_string(),
2466 hints: Vec::new(),
2467 });
2468 }
2469 }
2470 LspCommand::SemanticTokensFull { request_id, uri } => {
2471 if state.initialized {
2472 tracing::info!(
2473 "Processing SemanticTokens request for {}",
2474 uri.as_str()
2475 );
2476 let _ = state
2477 .handle_semantic_tokens_full(request_id, uri, &pending)
2478 .await;
2479 } else {
2480 tracing::trace!(
2481 "LSP not initialized, cannot get semantic tokens"
2482 );
2483 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2484 request_id,
2485 uri: uri.as_str().to_string(),
2486 response: LspSemanticTokensResponse::Full(Err(
2487 "LSP not initialized".to_string(),
2488 )),
2489 });
2490 }
2491 }
2492 LspCommand::SemanticTokensFullDelta {
2493 request_id,
2494 uri,
2495 previous_result_id,
2496 } => {
2497 if state.initialized {
2498 tracing::info!(
2499 "Processing SemanticTokens delta request for {}",
2500 uri.as_str()
2501 );
2502 let _ = state
2503 .handle_semantic_tokens_full_delta(
2504 request_id,
2505 uri,
2506 previous_result_id,
2507 &pending,
2508 )
2509 .await;
2510 } else {
2511 tracing::trace!(
2512 "LSP not initialized, cannot get semantic tokens"
2513 );
2514 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2515 request_id,
2516 uri: uri.as_str().to_string(),
2517 response: LspSemanticTokensResponse::FullDelta(Err(
2518 "LSP not initialized".to_string(),
2519 )),
2520 });
2521 }
2522 }
2523 LspCommand::SemanticTokensRange {
2524 request_id,
2525 uri,
2526 range,
2527 } => {
2528 if state.initialized {
2529 tracing::info!(
2530 "Processing SemanticTokens range request for {}",
2531 uri.as_str()
2532 );
2533 let _ = state
2534 .handle_semantic_tokens_range(request_id, uri, range, &pending)
2535 .await;
2536 } else {
2537 tracing::trace!(
2538 "LSP not initialized, cannot get semantic tokens"
2539 );
2540 let _ = state.async_tx.send(AsyncMessage::LspSemanticTokens {
2541 request_id,
2542 uri: uri.as_str().to_string(),
2543 response: LspSemanticTokensResponse::Range(Err(
2544 "LSP not initialized".to_string(),
2545 )),
2546 });
2547 }
2548 }
2549 LspCommand::CancelRequest { request_id } => {
2550 tracing::info!(
2551 "Processing CancelRequest for editor_id={}",
2552 request_id
2553 );
2554 let _ = state.handle_cancel_request(request_id).await;
2555 }
2556 LspCommand::PluginRequest {
2557 request_id,
2558 method,
2559 params,
2560 } => {
2561 if state.initialized {
2562 tracing::trace!(
2563 "Processing plugin request {} ({})",
2564 request_id,
2565 method
2566 );
2567 let _ = state
2568 .handle_plugin_request(
2569 request_id,
2570 method,
2571 params,
2572 &pending,
2573 )
2574 .await;
2575 } else {
2576 tracing::trace!(
2577 "Plugin LSP request {} received before initialization",
2578 request_id
2579 );
2580 let _ = state.async_tx.send(AsyncMessage::PluginLspResponse {
2581 language: language_clone.clone(),
2582 request_id,
2583 result: Err("LSP not initialized".to_string()),
2584 });
2585 }
2586 }
2587 LspCommand::Shutdown => {
2588 tracing::info!("Processing Shutdown command");
2589 shutting_down.store(true, Ordering::SeqCst);
2591 let _ = state.handle_shutdown().await;
2592 break;
2593 }
2594 }
2595 }
2596 else => {
2598 tracing::info!("Command channel closed");
2599 break;
2600 }
2601 }
2602 }
2603
2604 tracing::info!("LSP task exiting for language: {}", self.language);
2605 }
2606}
2607
2608async fn read_message_from_stdout(
2610 stdout: &mut BufReader<ChildStdout>,
2611) -> Result<JsonRpcMessage, String> {
2612 let mut content_length: Option<usize> = None;
2614
2615 loop {
2616 let mut line = String::new();
2617 let bytes_read = stdout
2618 .read_line(&mut line)
2619 .await
2620 .map_err(|e| format!("Failed to read from stdout: {}", e))?;
2621
2622 if bytes_read == 0 {
2624 return Err("LSP server closed stdout (EOF)".to_string());
2625 }
2626
2627 if line == "\r\n" {
2628 break;
2629 }
2630
2631 if let Some(len_str) = line.strip_prefix("Content-Length: ") {
2632 content_length = Some(
2633 len_str
2634 .trim()
2635 .parse()
2636 .map_err(|e| format!("Invalid Content-Length: {}", e))?,
2637 );
2638 }
2639 }
2640
2641 let content_length =
2642 content_length.ok_or_else(|| "Missing Content-Length header".to_string())?;
2643
2644 let mut content = vec![0u8; content_length];
2646 stdout
2647 .read_exact(&mut content)
2648 .await
2649 .map_err(|e| format!("Failed to read content: {}", e))?;
2650
2651 let json = String::from_utf8(content).map_err(|e| format!("Invalid UTF-8: {}", e))?;
2652
2653 tracing::trace!("Received LSP message: {}", json);
2654
2655 serde_json::from_str(&json).map_err(|e| format!("Failed to deserialize message: {}", e))
2656}
2657
2658#[allow(clippy::type_complexity)]
2660async fn handle_message_dispatch(
2661 message: JsonRpcMessage,
2662 pending: &Arc<Mutex<HashMap<i64, oneshot::Sender<Result<Value, String>>>>>,
2663 async_tx: &std_mpsc::Sender<AsyncMessage>,
2664 language: &str,
2665 server_command: &str,
2666 stdin_writer: &Arc<tokio::sync::Mutex<ChildStdin>>,
2667) -> Result<(), String> {
2668 match message {
2669 JsonRpcMessage::Response(response) => {
2670 tracing::trace!("Received LSP response for request id={}", response.id);
2671 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
2672 let result = if let Some(error) = response.error {
2673 tracing::warn!(
2674 "LSP response error: {} (code {})",
2675 error.message,
2676 error.code
2677 );
2678 Err(format!(
2679 "LSP error: {} (code {})",
2680 error.message, error.code
2681 ))
2682 } else {
2683 tracing::trace!("LSP response success for request id={}", response.id);
2684 Ok(response.result.unwrap_or(serde_json::Value::Null))
2686 };
2687 let _ = tx.send(result);
2688 } else {
2689 tracing::warn!(
2690 "Received LSP response for unknown request id={}",
2691 response.id
2692 );
2693 }
2694 }
2695 JsonRpcMessage::Notification(notification) => {
2696 tracing::trace!("Received LSP notification: {}", notification.method);
2697 handle_notification_dispatch(notification, async_tx, language).await?;
2698 }
2699 JsonRpcMessage::Request(request) => {
2700 tracing::trace!("Received request from server: {}", request.method);
2702 let response = match request.method.as_str() {
2703 "window/workDoneProgress/create" => {
2704 tracing::trace!("Acknowledging workDoneProgress/create (id={})", request.id);
2706 JsonRpcResponse {
2707 jsonrpc: "2.0".to_string(),
2708 id: request.id,
2709 result: Some(Value::Null),
2710 error: None,
2711 }
2712 }
2713 "workspace/configuration" => {
2714 tracing::trace!(
2718 "Responding to workspace/configuration with inlay hints enabled"
2719 );
2720
2721 let num_items = request
2723 .params
2724 .as_ref()
2725 .and_then(|p| p.get("items"))
2726 .and_then(|items| items.as_array())
2727 .map(|arr| arr.len())
2728 .unwrap_or(1);
2729
2730 let ra_config = serde_json::json!({
2732 "inlayHints": {
2733 "typeHints": {
2734 "enable": true
2735 },
2736 "parameterHints": {
2737 "enable": true
2738 },
2739 "chainingHints": {
2740 "enable": true
2741 },
2742 "closureReturnTypeHints": {
2743 "enable": "always"
2744 }
2745 }
2746 });
2747
2748 let configs: Vec<Value> = (0..num_items).map(|_| ra_config.clone()).collect();
2750
2751 JsonRpcResponse {
2752 jsonrpc: "2.0".to_string(),
2753 id: request.id,
2754 result: Some(Value::Array(configs)),
2755 error: None,
2756 }
2757 }
2758 "client/registerCapability" => {
2759 tracing::trace!(
2761 "Acknowledging client/registerCapability (id={})",
2762 request.id
2763 );
2764 JsonRpcResponse {
2765 jsonrpc: "2.0".to_string(),
2766 id: request.id,
2767 result: Some(Value::Null),
2768 error: None,
2769 }
2770 }
2771 _ => {
2772 tracing::debug!("Server request for plugins: {}", request.method);
2774 let _ = async_tx.send(AsyncMessage::LspServerRequest {
2775 language: language.to_string(),
2776 server_command: server_command.to_string(),
2777 method: request.method.clone(),
2778 params: request.params.clone(),
2779 });
2780 JsonRpcResponse {
2781 jsonrpc: "2.0".to_string(),
2782 id: request.id,
2783 result: Some(Value::Null),
2784 error: None,
2785 }
2786 }
2787 };
2788
2789 let json = serde_json::to_string(&response)
2791 .map_err(|e| format!("Failed to serialize response: {}", e))?;
2792 let message = format!("Content-Length: {}\r\n\r\n{}", json.len(), json);
2793
2794 let mut stdin = stdin_writer.lock().await;
2795 use tokio::io::AsyncWriteExt;
2796 if let Err(e) = stdin.write_all(message.as_bytes()).await {
2797 tracing::error!("Failed to write server response: {}", e);
2798 }
2799 if let Err(e) = stdin.flush().await {
2800 tracing::error!("Failed to flush server response: {}", e);
2801 }
2802 tracing::trace!("Sent response to server request id={}", response.id);
2803 }
2804 }
2805 Ok(())
2806}
2807
2808async fn handle_notification_dispatch(
2810 notification: JsonRpcNotification,
2811 async_tx: &std_mpsc::Sender<AsyncMessage>,
2812 language: &str,
2813) -> Result<(), String> {
2814 match notification.method.as_str() {
2815 PublishDiagnostics::METHOD => {
2816 if let Some(params) = notification.params {
2817 let params: PublishDiagnosticsParams = serde_json::from_value(params)
2818 .map_err(|e| format!("Failed to deserialize diagnostics: {}", e))?;
2819
2820 tracing::trace!(
2821 "Received {} diagnostics for {}",
2822 params.diagnostics.len(),
2823 params.uri.as_str()
2824 );
2825
2826 let _ = async_tx.send(AsyncMessage::LspDiagnostics {
2828 uri: params.uri.to_string(),
2829 diagnostics: params.diagnostics,
2830 });
2831 }
2832 }
2833 "window/showMessage" => {
2834 if let Some(params) = notification.params {
2835 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
2836 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(3);
2837 let message = msg
2838 .get("message")
2839 .and_then(|v| v.as_str())
2840 .unwrap_or("(no message)")
2841 .to_string();
2842
2843 let message_type = match message_type_num {
2844 1 => LspMessageType::Error,
2845 2 => LspMessageType::Warning,
2846 3 => LspMessageType::Info,
2847 _ => LspMessageType::Log,
2848 };
2849
2850 match message_type {
2852 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
2853 LspMessageType::Warning => {
2854 tracing::warn!("LSP ({}): {}", language, message)
2855 }
2856 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
2857 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
2858 }
2859
2860 let _ = async_tx.send(AsyncMessage::LspWindowMessage {
2862 language: language.to_string(),
2863 message_type,
2864 message,
2865 });
2866 }
2867 }
2868 }
2869 "window/logMessage" => {
2870 if let Some(params) = notification.params {
2871 if let Ok(msg) = serde_json::from_value::<serde_json::Map<String, Value>>(params) {
2872 let message_type_num = msg.get("type").and_then(|v| v.as_i64()).unwrap_or(4);
2873 let message = msg
2874 .get("message")
2875 .and_then(|v| v.as_str())
2876 .unwrap_or("(no message)")
2877 .to_string();
2878
2879 let message_type = match message_type_num {
2880 1 => LspMessageType::Error,
2881 2 => LspMessageType::Warning,
2882 3 => LspMessageType::Info,
2883 _ => LspMessageType::Log,
2884 };
2885
2886 match message_type {
2888 LspMessageType::Error => tracing::error!("LSP ({}): {}", language, message),
2889 LspMessageType::Warning => {
2890 tracing::warn!("LSP ({}): {}", language, message)
2891 }
2892 LspMessageType::Info => tracing::info!("LSP ({}): {}", language, message),
2893 LspMessageType::Log => tracing::trace!("LSP ({}): {}", language, message),
2894 }
2895
2896 let _ = async_tx.send(AsyncMessage::LspLogMessage {
2898 language: language.to_string(),
2899 message_type,
2900 message,
2901 });
2902 }
2903 }
2904 }
2905 "$/progress" => {
2906 if let Some(params) = notification.params {
2907 if let Ok(progress) =
2908 serde_json::from_value::<serde_json::Map<String, Value>>(params)
2909 {
2910 let token = progress
2911 .get("token")
2912 .and_then(|v| {
2913 v.as_str()
2914 .map(|s| s.to_string())
2915 .or_else(|| v.as_i64().map(|n| n.to_string()))
2916 })
2917 .unwrap_or_else(|| "unknown".to_string());
2918
2919 if let Some(value_obj) = progress.get("value").and_then(|v| v.as_object()) {
2920 let kind = value_obj.get("kind").and_then(|v| v.as_str());
2921
2922 let value = match kind {
2923 Some("begin") => {
2924 let title = value_obj
2925 .get("title")
2926 .and_then(|v| v.as_str())
2927 .unwrap_or("Working...")
2928 .to_string();
2929 let message = value_obj
2930 .get("message")
2931 .and_then(|v| v.as_str())
2932 .map(|s| s.to_string());
2933 let percentage = value_obj
2934 .get("percentage")
2935 .and_then(|v| v.as_u64())
2936 .map(|p| p as u32);
2937
2938 tracing::info!(
2939 "LSP ({}) progress begin: {} {:?} {:?}",
2940 language,
2941 title,
2942 message,
2943 percentage
2944 );
2945
2946 Some(LspProgressValue::Begin {
2947 title,
2948 message,
2949 percentage,
2950 })
2951 }
2952 Some("report") => {
2953 let message = value_obj
2954 .get("message")
2955 .and_then(|v| v.as_str())
2956 .map(|s| s.to_string());
2957 let percentage = value_obj
2958 .get("percentage")
2959 .and_then(|v| v.as_u64())
2960 .map(|p| p as u32);
2961
2962 tracing::trace!(
2963 "LSP ({}) progress report: {:?} {:?}",
2964 language,
2965 message,
2966 percentage
2967 );
2968
2969 Some(LspProgressValue::Report {
2970 message,
2971 percentage,
2972 })
2973 }
2974 Some("end") => {
2975 let message = value_obj
2976 .get("message")
2977 .and_then(|v| v.as_str())
2978 .map(|s| s.to_string());
2979
2980 tracing::info!("LSP ({}) progress end: {:?}", language, message);
2981
2982 Some(LspProgressValue::End { message })
2983 }
2984 _ => None,
2985 };
2986
2987 if let Some(value) = value {
2988 let _ = async_tx.send(AsyncMessage::LspProgress {
2989 language: language.to_string(),
2990 token,
2991 value,
2992 });
2993 }
2994 }
2995 }
2996 }
2997 }
2998 "experimental/serverStatus" => {
2999 if let Some(params) = notification.params {
3002 if let Ok(status) = serde_json::from_value::<serde_json::Map<String, Value>>(params)
3003 {
3004 let quiescent = status
3005 .get("quiescent")
3006 .and_then(|v| v.as_bool())
3007 .unwrap_or(false);
3008
3009 tracing::info!("LSP ({}) server status: quiescent={}", language, quiescent);
3010
3011 if quiescent {
3012 let _ = async_tx.send(AsyncMessage::LspServerQuiescent {
3014 language: language.to_string(),
3015 });
3016 }
3017 }
3018 }
3019 }
3020 _ => {
3021 tracing::debug!("Unhandled notification: {}", notification.method);
3022 }
3023 }
3024
3025 Ok(())
3026}
3027
3028static NEXT_HANDLE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
3030
3031pub struct LspHandle {
3033 id: u64,
3035
3036 command_tx: mpsc::Sender<LspCommand>,
3038
3039 state: Arc<Mutex<LspClientState>>,
3041
3042 runtime: tokio::runtime::Handle,
3044}
3045
3046impl LspHandle {
3047 pub fn spawn(
3049 runtime: &tokio::runtime::Handle,
3050 command: &str,
3051 args: &[String],
3052 language: String,
3053 async_bridge: &AsyncBridge,
3054 process_limits: ProcessLimits,
3055 ) -> Result<Self, String> {
3056 let (command_tx, command_rx) = mpsc::channel(100); let async_tx = async_bridge.sender();
3058 let language_clone = language.clone();
3059 let command = command.to_string();
3060 let args = args.to_vec();
3061 let state = Arc::new(Mutex::new(LspClientState::Starting));
3062
3063 let stderr_log_path = crate::services::log_dirs::lsp_log_path(&language);
3065
3066 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3068 language: language.clone(),
3069 status: LspServerStatus::Starting,
3070 message: None,
3071 });
3072
3073 let state_clone = state.clone();
3074 let stderr_log_path_clone = stderr_log_path.clone();
3075 runtime.spawn(async move {
3076 match LspTask::spawn(
3077 &command,
3078 &args,
3079 language_clone.clone(),
3080 async_tx.clone(),
3081 &process_limits,
3082 stderr_log_path_clone.clone(),
3083 )
3084 .await
3085 {
3086 Ok(task) => {
3087 task.run(command_rx).await;
3088 }
3089 Err(e) => {
3090 tracing::error!("Failed to spawn LSP task: {}", e);
3091
3092 if let Ok(mut s) = state_clone.lock() {
3094 let _ = s.transition_to(LspClientState::Error);
3095 }
3096
3097 let _ = async_tx.send(AsyncMessage::LspStatusUpdate {
3098 language: language_clone.clone(),
3099 status: LspServerStatus::Error,
3100 message: None,
3101 });
3102 let _ = async_tx.send(AsyncMessage::LspError {
3103 language: language_clone,
3104 error: e,
3105 stderr_log_path: Some(stderr_log_path_clone),
3106 });
3107 }
3108 }
3109 });
3110
3111 let id = NEXT_HANDLE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3112
3113 Ok(Self {
3114 id,
3115 command_tx,
3116 state,
3117 runtime: runtime.clone(),
3118 })
3119 }
3120
3121 pub fn id(&self) -> u64 {
3123 self.id
3124 }
3125
3126 pub fn initialize(
3135 &self,
3136 root_uri: Option<Uri>,
3137 initialization_options: Option<Value>,
3138 ) -> Result<(), String> {
3139 {
3141 let mut state = self.state.lock().unwrap();
3142 if !state.can_initialize() {
3143 return Err(format!(
3144 "Cannot initialize: client is in state {:?}",
3145 *state
3146 ));
3147 }
3148 state.transition_to(LspClientState::Initializing)?;
3150 }
3151
3152 let state = self.state.clone();
3153
3154 let (tx, rx) = oneshot::channel();
3156
3157 self.command_tx
3158 .try_send(LspCommand::Initialize {
3159 root_uri,
3160 initialization_options,
3161 response: tx,
3162 })
3163 .map_err(|_| "Failed to send initialize command".to_string())?;
3164
3165 let runtime = self.runtime.clone();
3167 runtime.spawn(async move {
3168 match tokio::time::timeout(std::time::Duration::from_secs(10), rx).await {
3169 Ok(Ok(Ok(_))) => {
3170 if let Ok(mut s) = state.lock() {
3172 let _ = s.transition_to(LspClientState::Running);
3173 }
3174 tracing::info!("LSP initialization completed successfully");
3175 }
3176 Ok(Ok(Err(e))) => {
3177 tracing::error!("LSP initialization failed: {}", e);
3178 if let Ok(mut s) = state.lock() {
3179 let _ = s.transition_to(LspClientState::Error);
3180 }
3181 }
3182 Ok(Err(_)) => {
3183 tracing::error!("LSP initialization response channel closed");
3184 if let Ok(mut s) = state.lock() {
3185 let _ = s.transition_to(LspClientState::Error);
3186 }
3187 }
3188 Err(_) => {
3189 tracing::error!("LSP initialization timed out after 10 seconds");
3190 if let Ok(mut s) = state.lock() {
3191 let _ = s.transition_to(LspClientState::Error);
3192 }
3193 }
3194 }
3195 });
3196
3197 Ok(())
3198 }
3199
3200 pub fn is_initialized(&self) -> bool {
3202 self.state.lock().unwrap().can_send_requests()
3203 }
3204
3205 pub fn state(&self) -> LspClientState {
3207 *self.state.lock().unwrap()
3208 }
3209
3210 pub fn did_open(&self, uri: Uri, text: String, language_id: String) -> Result<(), String> {
3212 self.command_tx
3214 .try_send(LspCommand::DidOpen {
3215 uri,
3216 text,
3217 language_id,
3218 })
3219 .map_err(|_| "Failed to send did_open command".to_string())
3220 }
3221
3222 pub fn did_change(
3224 &self,
3225 uri: Uri,
3226 content_changes: Vec<TextDocumentContentChangeEvent>,
3227 ) -> Result<(), String> {
3228 self.command_tx
3230 .try_send(LspCommand::DidChange {
3231 uri,
3232 content_changes,
3233 })
3234 .map_err(|_| "Failed to send did_change command".to_string())
3235 }
3236
3237 pub fn did_save(&self, uri: Uri, text: Option<String>) -> Result<(), String> {
3239 self.command_tx
3240 .try_send(LspCommand::DidSave { uri, text })
3241 .map_err(|_| "Failed to send did_save command".to_string())
3242 }
3243
3244 pub fn completion(
3246 &self,
3247 request_id: u64,
3248 uri: Uri,
3249 line: u32,
3250 character: u32,
3251 ) -> Result<(), String> {
3252 self.command_tx
3253 .try_send(LspCommand::Completion {
3254 request_id,
3255 uri,
3256 line,
3257 character,
3258 })
3259 .map_err(|_| "Failed to send completion command".to_string())
3260 }
3261
3262 pub fn goto_definition(
3264 &self,
3265 request_id: u64,
3266 uri: Uri,
3267 line: u32,
3268 character: u32,
3269 ) -> Result<(), String> {
3270 self.command_tx
3271 .try_send(LspCommand::GotoDefinition {
3272 request_id,
3273 uri,
3274 line,
3275 character,
3276 })
3277 .map_err(|_| "Failed to send goto_definition command".to_string())
3278 }
3279
3280 pub fn rename(
3282 &self,
3283 request_id: u64,
3284 uri: Uri,
3285 line: u32,
3286 character: u32,
3287 new_name: String,
3288 ) -> Result<(), String> {
3289 self.command_tx
3290 .try_send(LspCommand::Rename {
3291 request_id,
3292 uri,
3293 line,
3294 character,
3295 new_name,
3296 })
3297 .map_err(|_| "Failed to send rename command".to_string())
3298 }
3299
3300 pub fn hover(
3302 &self,
3303 request_id: u64,
3304 uri: Uri,
3305 line: u32,
3306 character: u32,
3307 ) -> Result<(), String> {
3308 self.command_tx
3309 .try_send(LspCommand::Hover {
3310 request_id,
3311 uri,
3312 line,
3313 character,
3314 })
3315 .map_err(|_| "Failed to send hover command".to_string())
3316 }
3317
3318 pub fn references(
3320 &self,
3321 request_id: u64,
3322 uri: Uri,
3323 line: u32,
3324 character: u32,
3325 ) -> Result<(), String> {
3326 self.command_tx
3327 .try_send(LspCommand::References {
3328 request_id,
3329 uri,
3330 line,
3331 character,
3332 })
3333 .map_err(|_| "Failed to send references command".to_string())
3334 }
3335
3336 pub fn signature_help(
3338 &self,
3339 request_id: u64,
3340 uri: Uri,
3341 line: u32,
3342 character: u32,
3343 ) -> Result<(), String> {
3344 self.command_tx
3345 .try_send(LspCommand::SignatureHelp {
3346 request_id,
3347 uri,
3348 line,
3349 character,
3350 })
3351 .map_err(|_| "Failed to send signature_help command".to_string())
3352 }
3353
3354 #[allow(clippy::too_many_arguments)]
3356 pub fn code_actions(
3357 &self,
3358 request_id: u64,
3359 uri: Uri,
3360 start_line: u32,
3361 start_char: u32,
3362 end_line: u32,
3363 end_char: u32,
3364 diagnostics: Vec<lsp_types::Diagnostic>,
3365 ) -> Result<(), String> {
3366 self.command_tx
3367 .try_send(LspCommand::CodeActions {
3368 request_id,
3369 uri,
3370 start_line,
3371 start_char,
3372 end_line,
3373 end_char,
3374 diagnostics,
3375 })
3376 .map_err(|_| "Failed to send code_actions command".to_string())
3377 }
3378
3379 pub fn document_diagnostic(
3384 &self,
3385 request_id: u64,
3386 uri: Uri,
3387 previous_result_id: Option<String>,
3388 ) -> Result<(), String> {
3389 self.command_tx
3390 .try_send(LspCommand::DocumentDiagnostic {
3391 request_id,
3392 uri,
3393 previous_result_id,
3394 })
3395 .map_err(|_| "Failed to send document_diagnostic command".to_string())
3396 }
3397
3398 pub fn inlay_hints(
3402 &self,
3403 request_id: u64,
3404 uri: Uri,
3405 start_line: u32,
3406 start_char: u32,
3407 end_line: u32,
3408 end_char: u32,
3409 ) -> Result<(), String> {
3410 self.command_tx
3411 .try_send(LspCommand::InlayHints {
3412 request_id,
3413 uri,
3414 start_line,
3415 start_char,
3416 end_line,
3417 end_char,
3418 })
3419 .map_err(|_| "Failed to send inlay_hints command".to_string())
3420 }
3421
3422 pub fn semantic_tokens_full(&self, request_id: u64, uri: Uri) -> Result<(), String> {
3424 self.command_tx
3425 .try_send(LspCommand::SemanticTokensFull { request_id, uri })
3426 .map_err(|_| "Failed to send semantic_tokens command".to_string())
3427 }
3428
3429 pub fn semantic_tokens_full_delta(
3431 &self,
3432 request_id: u64,
3433 uri: Uri,
3434 previous_result_id: String,
3435 ) -> Result<(), String> {
3436 self.command_tx
3437 .try_send(LspCommand::SemanticTokensFullDelta {
3438 request_id,
3439 uri,
3440 previous_result_id,
3441 })
3442 .map_err(|_| "Failed to send semantic_tokens delta command".to_string())
3443 }
3444
3445 pub fn semantic_tokens_range(
3447 &self,
3448 request_id: u64,
3449 uri: Uri,
3450 range: lsp_types::Range,
3451 ) -> Result<(), String> {
3452 self.command_tx
3453 .try_send(LspCommand::SemanticTokensRange {
3454 request_id,
3455 uri,
3456 range,
3457 })
3458 .map_err(|_| "Failed to send semantic_tokens_range command".to_string())
3459 }
3460
3461 pub fn cancel_request(&self, request_id: u64) -> Result<(), String> {
3466 self.command_tx
3467 .try_send(LspCommand::CancelRequest { request_id })
3468 .map_err(|_| "Failed to send cancel_request command".to_string())
3469 }
3470
3471 pub fn send_plugin_request(
3473 &self,
3474 request_id: u64,
3475 method: String,
3476 params: Option<Value>,
3477 ) -> Result<(), String> {
3478 tracing::trace!(
3479 "LspHandle sending plugin request {}: method={}",
3480 request_id,
3481 method
3482 );
3483 match self.command_tx.try_send(LspCommand::PluginRequest {
3484 request_id,
3485 method,
3486 params,
3487 }) {
3488 Ok(()) => {
3489 tracing::trace!(
3490 "LspHandle enqueued plugin request {} successfully",
3491 request_id
3492 );
3493 Ok(())
3494 }
3495 Err(e) => {
3496 tracing::error!("Failed to enqueue plugin request {}: {}", request_id, e);
3497 Err("Failed to send plugin LSP request".to_string())
3498 }
3499 }
3500 }
3501
3502 pub fn shutdown(&self) -> Result<(), String> {
3504 {
3506 let mut state = self.state.lock().unwrap();
3507 if let Err(e) = state.transition_to(LspClientState::Stopping) {
3508 tracing::warn!("State transition warning during shutdown: {}", e);
3509 }
3511 }
3512
3513 self.command_tx
3514 .try_send(LspCommand::Shutdown)
3515 .map_err(|_| "Failed to send shutdown command".to_string())?;
3516
3517 {
3520 let mut state = self.state.lock().unwrap();
3521 let _ = state.transition_to(LspClientState::Stopped);
3522 }
3523
3524 Ok(())
3525 }
3526}
3527
3528impl Drop for LspHandle {
3529 fn drop(&mut self) {
3530 let _ = self.command_tx.try_send(LspCommand::Shutdown);
3536
3537 if let Ok(mut state) = self.state.lock() {
3539 let _ = state.transition_to(LspClientState::Stopped);
3540 }
3541 }
3542}
3543
3544#[cfg(test)]
3545mod tests {
3546 use super::*;
3547
3548 #[test]
3549 fn test_json_rpc_request_serialization() {
3550 let request = JsonRpcRequest {
3551 jsonrpc: "2.0".to_string(),
3552 id: 1,
3553 method: "initialize".to_string(),
3554 params: Some(serde_json::json!({"rootUri": "file:///test"})),
3555 };
3556
3557 let json = serde_json::to_string(&request).unwrap();
3558 assert!(json.contains("\"jsonrpc\":\"2.0\""));
3559 assert!(json.contains("\"id\":1"));
3560 assert!(json.contains("\"method\":\"initialize\""));
3561 assert!(json.contains("\"rootUri\":\"file:///test\""));
3562 }
3563
3564 #[test]
3565 fn test_json_rpc_response_serialization() {
3566 let response = JsonRpcResponse {
3567 jsonrpc: "2.0".to_string(),
3568 id: 1,
3569 result: Some(serde_json::json!({"success": true})),
3570 error: None,
3571 };
3572
3573 let json = serde_json::to_string(&response).unwrap();
3574 assert!(json.contains("\"jsonrpc\":\"2.0\""));
3575 assert!(json.contains("\"id\":1"));
3576 assert!(json.contains("\"success\":true"));
3577 assert!(!json.contains("\"error\""));
3578 }
3579
3580 #[test]
3581 fn test_json_rpc_error_response() {
3582 let response = JsonRpcResponse {
3583 jsonrpc: "2.0".to_string(),
3584 id: 1,
3585 result: None,
3586 error: Some(JsonRpcError {
3587 code: -32600,
3588 message: "Invalid request".to_string(),
3589 data: None,
3590 }),
3591 };
3592
3593 let json = serde_json::to_string(&response).unwrap();
3594 assert!(json.contains("\"error\""));
3595 assert!(json.contains("\"code\":-32600"));
3596 assert!(json.contains("\"message\":\"Invalid request\""));
3597 }
3598
3599 #[test]
3600 fn test_json_rpc_notification_serialization() {
3601 let notification = JsonRpcNotification {
3602 jsonrpc: "2.0".to_string(),
3603 method: "textDocument/didOpen".to_string(),
3604 params: Some(serde_json::json!({"uri": "file:///test.rs"})),
3605 };
3606
3607 let json = serde_json::to_string(¬ification).unwrap();
3608 assert!(json.contains("\"jsonrpc\":\"2.0\""));
3609 assert!(json.contains("\"method\":\"textDocument/didOpen\""));
3610 assert!(json.contains("\"uri\":\"file:///test.rs\""));
3611 assert!(!json.contains("\"id\"")); }
3613
3614 #[test]
3615 fn test_json_rpc_message_deserialization_request() {
3616 let json =
3617 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"rootUri":"file:///test"}}"#;
3618 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
3619
3620 match message {
3621 JsonRpcMessage::Request(request) => {
3622 assert_eq!(request.jsonrpc, "2.0");
3623 assert_eq!(request.id, 1);
3624 assert_eq!(request.method, "initialize");
3625 assert!(request.params.is_some());
3626 }
3627 _ => panic!("Expected Request"),
3628 }
3629 }
3630
3631 #[test]
3632 fn test_json_rpc_message_deserialization_response() {
3633 let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
3634 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
3635
3636 match message {
3637 JsonRpcMessage::Response(response) => {
3638 assert_eq!(response.jsonrpc, "2.0");
3639 assert_eq!(response.id, 1);
3640 assert!(response.result.is_some());
3641 assert!(response.error.is_none());
3642 }
3643 _ => panic!("Expected Response"),
3644 }
3645 }
3646
3647 #[test]
3648 fn test_json_rpc_message_deserialization_notification() {
3649 let json = r#"{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"uri":"file:///test.rs"}}"#;
3650 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
3651
3652 match message {
3653 JsonRpcMessage::Notification(notification) => {
3654 assert_eq!(notification.jsonrpc, "2.0");
3655 assert_eq!(notification.method, "textDocument/didOpen");
3656 assert!(notification.params.is_some());
3657 }
3658 _ => panic!("Expected Notification"),
3659 }
3660 }
3661
3662 #[test]
3663 fn test_json_rpc_error_deserialization() {
3664 let json =
3665 r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}"#;
3666 let message: JsonRpcMessage = serde_json::from_str(json).unwrap();
3667
3668 match message {
3669 JsonRpcMessage::Response(response) => {
3670 assert_eq!(response.jsonrpc, "2.0");
3671 assert_eq!(response.id, 1);
3672 assert!(response.result.is_none());
3673 assert!(response.error.is_some());
3674 let error = response.error.unwrap();
3675 assert_eq!(error.code, -32600);
3676 assert_eq!(error.message, "Invalid request");
3677 }
3678 _ => panic!("Expected Response with error"),
3679 }
3680 }
3681
3682 #[tokio::test]
3683 async fn test_lsp_handle_spawn_and_drop() {
3684 let runtime = tokio::runtime::Handle::current();
3687 let async_bridge = AsyncBridge::new();
3688
3689 let result = LspHandle::spawn(
3692 &runtime,
3693 "cat",
3694 &[],
3695 "test".to_string(),
3696 &async_bridge,
3697 ProcessLimits::unlimited(),
3698 );
3699
3700 assert!(result.is_ok());
3702
3703 let handle = result.unwrap();
3704
3705 drop(handle);
3707
3708 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
3710 }
3711
3712 #[tokio::test]
3713 async fn test_lsp_handle_did_open_queues_before_initialization() {
3714 let runtime = tokio::runtime::Handle::current();
3715 let async_bridge = AsyncBridge::new();
3716
3717 let handle = LspHandle::spawn(
3718 &runtime,
3719 "cat",
3720 &[],
3721 "test".to_string(),
3722 &async_bridge,
3723 ProcessLimits::unlimited(),
3724 )
3725 .unwrap();
3726
3727 let result = handle.did_open(
3729 "file:///test.rs".parse().unwrap(),
3730 "fn main() {}".to_string(),
3731 "rust".to_string(),
3732 );
3733
3734 assert!(result.is_ok());
3736 }
3737
3738 #[tokio::test]
3739 async fn test_lsp_handle_did_change_queues_before_initialization() {
3740 let runtime = tokio::runtime::Handle::current();
3741 let async_bridge = AsyncBridge::new();
3742
3743 let handle = LspHandle::spawn(
3744 &runtime,
3745 "cat",
3746 &[],
3747 "test".to_string(),
3748 &async_bridge,
3749 ProcessLimits::unlimited(),
3750 )
3751 .unwrap();
3752
3753 let result = handle.did_change(
3755 "file:///test.rs".parse().unwrap(),
3756 vec![TextDocumentContentChangeEvent {
3757 range: Some(lsp_types::Range::new(
3758 lsp_types::Position::new(0, 0),
3759 lsp_types::Position::new(0, 0),
3760 )),
3761 range_length: None,
3762 text: "fn main() {}".to_string(),
3763 }],
3764 );
3765
3766 assert!(result.is_ok());
3768 }
3769
3770 #[tokio::test]
3771 async fn test_lsp_handle_incremental_change_with_range() {
3772 let runtime = tokio::runtime::Handle::current();
3773 let async_bridge = AsyncBridge::new();
3774
3775 let handle = LspHandle::spawn(
3776 &runtime,
3777 "cat",
3778 &[],
3779 "test".to_string(),
3780 &async_bridge,
3781 ProcessLimits::unlimited(),
3782 )
3783 .unwrap();
3784
3785 let result = handle.did_change(
3787 "file:///test.rs".parse().unwrap(),
3788 vec![TextDocumentContentChangeEvent {
3789 range: Some(lsp_types::Range::new(
3790 lsp_types::Position::new(0, 3),
3791 lsp_types::Position::new(0, 7),
3792 )),
3793 range_length: None,
3794 text: String::new(), }],
3796 );
3797
3798 assert!(result.is_ok());
3800 }
3801
3802 #[tokio::test]
3803 async fn test_lsp_handle_spawn_invalid_command() {
3804 let runtime = tokio::runtime::Handle::current();
3805 let async_bridge = AsyncBridge::new();
3806
3807 let result = LspHandle::spawn(
3809 &runtime,
3810 "this-command-does-not-exist-12345",
3811 &[],
3812 "test".to_string(),
3813 &async_bridge,
3814 ProcessLimits::unlimited(),
3815 );
3816
3817 assert!(result.is_ok());
3820
3821 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
3823
3824 let messages = async_bridge.try_recv_all();
3826 assert!(!messages.is_empty());
3827
3828 let has_error = messages
3829 .iter()
3830 .any(|msg| matches!(msg, AsyncMessage::LspError { .. }));
3831 assert!(has_error, "Expected LspError message");
3832 }
3833
3834 #[test]
3835 fn test_lsp_handle_shutdown_from_sync_context() {
3836 std::thread::spawn(|| {
3839 let rt = tokio::runtime::Runtime::new().unwrap();
3841 let async_bridge = AsyncBridge::new();
3842
3843 let handle = rt.block_on(async {
3844 let runtime = tokio::runtime::Handle::current();
3845 LspHandle::spawn(
3846 &runtime,
3847 "cat",
3848 &[],
3849 "test".to_string(),
3850 &async_bridge,
3851 ProcessLimits::unlimited(),
3852 )
3853 .unwrap()
3854 });
3855
3856 assert!(handle.shutdown().is_ok());
3858
3859 std::thread::sleep(std::time::Duration::from_millis(50));
3861 })
3862 .join()
3863 .unwrap();
3864 }
3865
3866 #[test]
3867 fn test_lsp_command_debug_format() {
3868 let cmd = LspCommand::Shutdown;
3870 let debug_str = format!("{:?}", cmd);
3871 assert!(debug_str.contains("Shutdown"));
3872 }
3873
3874 #[test]
3875 fn test_lsp_client_state_can_initialize_from_starting() {
3876 let state = LspClientState::Starting;
3882
3883 assert!(
3885 state.can_initialize(),
3886 "Starting state must allow initialization to avoid race condition"
3887 );
3888
3889 let mut state = LspClientState::Starting;
3891
3892 assert!(state.can_transition_to(LspClientState::Initializing));
3894 assert!(state.transition_to(LspClientState::Initializing).is_ok());
3895
3896 assert!(state.can_transition_to(LspClientState::Running));
3898 assert!(state.transition_to(LspClientState::Running).is_ok());
3899 }
3900
3901 #[tokio::test]
3902 async fn test_lsp_handle_initialize_from_starting_state() {
3903 let runtime = tokio::runtime::Handle::current();
3911 let async_bridge = AsyncBridge::new();
3912
3913 let handle = LspHandle::spawn(
3915 &runtime,
3916 "cat", &[],
3918 "test".to_string(),
3919 &async_bridge,
3920 ProcessLimits::unlimited(),
3921 )
3922 .unwrap();
3923
3924 let result = handle.initialize(None, None);
3927
3928 assert!(
3929 result.is_ok(),
3930 "initialize() must succeed from Starting state. Got error: {:?}",
3931 result.err()
3932 );
3933 }
3934
3935 #[tokio::test]
3936 async fn test_lsp_state_machine_race_condition_fix() {
3937 let runtime = tokio::runtime::Handle::current();
3944 let async_bridge = AsyncBridge::new();
3945
3946 let fake_lsp_script = r#"
3948 read -r line # Read Content-Length header
3949 read -r empty # Read empty line
3950 read -r json # Read JSON body
3951
3952 # Send a valid initialize response
3953 response='{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}'
3954 echo "Content-Length: ${#response}"
3955 echo ""
3956 echo -n "$response"
3957
3958 # Keep running to avoid EOF
3959 sleep 10
3960 "#;
3961
3962 let handle = LspHandle::spawn(
3964 &runtime,
3965 "bash",
3966 &["-c".to_string(), fake_lsp_script.to_string()],
3967 "fake".to_string(),
3968 &async_bridge,
3969 ProcessLimits::unlimited(),
3970 )
3971 .unwrap();
3972
3973 let init_result = handle.initialize(None, None);
3975 assert!(
3976 init_result.is_ok(),
3977 "initialize() failed from Starting state: {:?}",
3978 init_result.err()
3979 );
3980
3981 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
3983
3984 let messages = async_bridge.try_recv_all();
3986 let has_status_update = messages
3987 .iter()
3988 .any(|msg| matches!(msg, AsyncMessage::LspStatusUpdate { .. }));
3989
3990 assert!(
3991 has_status_update,
3992 "Expected status update messages from LSP initialization"
3993 );
3994
3995 let _ = handle.shutdown();
3997 }
3998}