1use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicI64, Ordering};
6
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9use serde_json::Value;
10use tokio::sync::{Mutex, mpsc, oneshot};
11use tokio::task::JoinHandle;
12use tokio::time::{Duration, timeout};
13use tracing::{debug, error, trace, warn};
14
15use crate::config::LspServerConfig;
16use crate::error::{Error, Result};
17use crate::lsp::transport::LspTransport;
18use crate::lsp::types::{
19 InboundMessage, JsonRpcError, JsonRpcRequest, JsonRpcResponse, LspNotification, RequestId,
20};
21
22const JSONRPC_VERSION: &str = "2.0";
24
25const SERVER_CANCELLED_CODE: i32 = -32802;
27
28const SERVER_CANCELLED_MAX_RETRIES: u32 = 3;
30
31const SERVER_CANCELLED_INITIAL_DELAY_MS: u64 = 500;
33
34type PendingRequests = HashMap<RequestId, oneshot::Sender<Result<Value>>>;
36
37#[derive(Debug)]
45pub struct LspClient {
46 config: LspServerConfig,
48
49 state: Arc<Mutex<super::ServerState>>,
51
52 request_counter: Arc<AtomicI64>,
54
55 command_tx: mpsc::Sender<ClientCommand>,
57
58 receiver_task: Option<JoinHandle<Result<()>>>,
60}
61
62impl Clone for LspClient {
63 fn clone(&self) -> Self {
68 Self {
69 config: self.config.clone(),
70 state: Arc::clone(&self.state),
71 request_counter: Arc::clone(&self.request_counter),
72 command_tx: self.command_tx.clone(),
73 receiver_task: None,
74 }
75 }
76}
77
78enum ClientCommand {
80 SendRequest {
82 request: JsonRpcRequest,
83 response_tx: oneshot::Sender<Result<Value>>,
84 },
85 SendNotification {
87 method: String,
88 params: Option<Value>,
89 },
90 Shutdown,
92}
93
94impl LspClient {
95 #[must_use]
100 pub fn new(config: LspServerConfig) -> Self {
101 let (command_tx, _command_rx) = mpsc::channel(1); Self {
107 config,
108 state: Arc::new(Mutex::new(super::ServerState::Uninitialized)),
109 request_counter: Arc::new(AtomicI64::new(1)),
110 command_tx,
111 receiver_task: None,
112 }
113 }
114
115 #[cfg(test)]
119 pub(crate) fn from_transport(config: LspServerConfig, transport: LspTransport) -> Self {
120 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
121 let request_counter = Arc::new(AtomicI64::new(1));
122 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
123
124 let (command_tx, command_rx) = mpsc::channel(100);
125
126 let receiver_task = tokio::spawn(Self::message_loop(
127 transport,
128 command_rx,
129 pending_requests,
130 None,
131 ));
132
133 Self {
134 config,
135 state,
136 request_counter,
137 command_tx,
138 receiver_task: Some(receiver_task),
139 }
140 }
141
142 pub(crate) fn from_transport_with_notifications(
147 config: LspServerConfig,
148 transport: LspTransport,
149 notification_tx: mpsc::Sender<LspNotification>,
150 ) -> Self {
151 let state = Arc::new(Mutex::new(super::ServerState::Initializing));
152 let request_counter = Arc::new(AtomicI64::new(1));
153 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
154
155 let (command_tx, command_rx) = mpsc::channel(100);
156
157 let receiver_task = tokio::spawn(Self::message_loop(
158 transport,
159 command_rx,
160 pending_requests,
161 Some(notification_tx),
162 ));
163
164 Self {
165 config,
166 state,
167 request_counter,
168 command_tx,
169 receiver_task: Some(receiver_task),
170 }
171 }
172
173 #[must_use]
175 pub fn language_id(&self) -> &str {
176 &self.config.language_id
177 }
178
179 pub async fn state(&self) -> super::ServerState {
181 *self.state.lock().await
182 }
183
184 pub async fn request<P, R>(
203 &self,
204 method: &str,
205 params: P,
206 timeout_duration: Duration,
207 ) -> Result<R>
208 where
209 P: Serialize,
210 R: DeserializeOwned,
211 {
212 let params_value = serde_json::to_value(params)?;
213 let mut delay_ms = SERVER_CANCELLED_INITIAL_DELAY_MS;
214
215 for attempt in 0..=SERVER_CANCELLED_MAX_RETRIES {
216 if attempt > 0 {
217 debug!(
218 "Retrying {} after ServerCancelled (attempt {}/{}), backoff={}ms",
219 method, attempt, SERVER_CANCELLED_MAX_RETRIES, delay_ms
220 );
221 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
222 delay_ms *= 2;
223 }
224
225 let id = RequestId::Number(self.request_counter.fetch_add(1, Ordering::SeqCst));
226 let (response_tx, response_rx) = oneshot::channel();
227 let request = JsonRpcRequest {
228 jsonrpc: JSONRPC_VERSION.to_string(),
229 id: id.clone(),
230 method: method.to_string(),
231 params: Some(params_value.clone()),
232 };
233
234 debug!("Sending request: {} (id={:?})", method, id);
235
236 self.command_tx
237 .send(ClientCommand::SendRequest {
238 request,
239 response_tx,
240 })
241 .await
242 .map_err(|_| Error::ServerTerminated)?;
243
244 let outcome = timeout(timeout_duration, response_rx)
245 .await
246 .map_err(|_| Error::Timeout(timeout_duration.as_secs()))?
247 .map_err(|_| Error::ServerTerminated)?;
248
249 match outcome {
250 Ok(result_value) => {
251 return serde_json::from_value(result_value).map_err(|e| {
252 Error::LspProtocolError(format!("Failed to deserialize response: {e}"))
253 });
254 }
255 Err(Error::LspServerError {
256 code,
257 ref message,
258 ref data,
259 }) if code == SERVER_CANCELLED_CODE && Self::should_retrigger(data.as_ref()) => {
260 warn!(
261 "ServerCancelled (-32802) on '{}', will retry: {}",
262 method, message
263 );
264 if attempt == SERVER_CANCELLED_MAX_RETRIES {
265 return Err(Error::LspServerError {
266 code,
267 message: message.clone(),
268 data: data.clone(),
269 });
270 }
271 }
273 Err(e) => return Err(e),
274 }
275 }
276
277 Err(Error::ServerTerminated)
278 }
279
280 fn should_retrigger(data: Option<&Value>) -> bool {
287 data.is_none_or(|v| {
288 v.get("retriggerRequest")
289 .and_then(Value::as_bool)
290 .unwrap_or(true)
291 })
292 }
293
294 pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
300 where
301 P: Serialize,
302 {
303 let params_value = serde_json::to_value(params)?;
304
305 debug!("Sending notification: {}", method);
306
307 self.command_tx
308 .send(ClientCommand::SendNotification {
309 method: method.to_string(),
310 params: Some(params_value),
311 })
312 .await
313 .map_err(|_| Error::ServerTerminated)?;
314
315 Ok(())
316 }
317
318 pub async fn shutdown(mut self) -> Result<()> {
326 debug!("Shutting down LSP client");
327
328 let _ = self.command_tx.send(ClientCommand::Shutdown).await;
329
330 if let Some(task) = self.receiver_task.take() {
331 task.await
332 .map_err(|e| Error::Transport(format!("Receiver task failed: {e}")))??;
333 }
334
335 *self.state.lock().await = super::ServerState::Shutdown;
336
337 Ok(())
338 }
339
340 async fn message_loop(
347 mut transport: LspTransport,
348 mut command_rx: mpsc::Receiver<ClientCommand>,
349 pending_requests: Arc<Mutex<PendingRequests>>,
350 notification_tx: Option<mpsc::Sender<LspNotification>>,
351 ) -> Result<()> {
352 debug!("Message loop started");
353 let result = Self::message_loop_inner(
354 &mut transport,
355 &mut command_rx,
356 &pending_requests,
357 notification_tx.as_ref(),
358 )
359 .await;
360 if let Err(ref e) = result {
361 error!("Message loop exiting with error: {}", e);
362 } else {
363 debug!("Message loop exiting normally");
364 }
365 result
366 }
367
368 async fn message_loop_inner(
369 transport: &mut LspTransport,
370 command_rx: &mut mpsc::Receiver<ClientCommand>,
371 pending_requests: &Arc<Mutex<PendingRequests>>,
372 notification_tx: Option<&mpsc::Sender<LspNotification>>,
373 ) -> Result<()> {
374 loop {
375 tokio::select! {
376 Some(command) = command_rx.recv() => {
377 match command {
378 ClientCommand::SendRequest { request, response_tx } => {
379 pending_requests.lock().await.insert(
380 request.id.clone(),
381 response_tx,
382 );
383
384 let value = serde_json::to_value(&request)?;
385 transport.send(&value).await?;
386 }
387 ClientCommand::SendNotification { method, params } => {
388 let notification = serde_json::json!({
389 "jsonrpc": "2.0",
390 "method": method,
391 "params": params,
392 });
393 transport.send(¬ification).await?;
394 }
395 ClientCommand::Shutdown => {
396 debug!("Client shutdown requested");
397 break;
398 }
399 }
400 }
401
402 message = transport.receive() => {
403 let message = match message {
404 Ok(m) => m,
405 Err(e) => {
406 error!("Transport receive error: {}", e);
407 return Err(e);
408 }
409 };
410 match message {
411 InboundMessage::Response(response) => {
412 trace!("Received response: id={:?}", response.id);
413
414 let sender = pending_requests.lock().await.remove(&response.id);
415
416 if let Some(sender) = sender {
417 if let Some(error) = response.error {
418 let message = if error.message.len() > 200 {
419 format!("{}... (truncated)", &error.message[..200])
420 } else {
421 error.message.clone()
422 };
423 error!("LSP error response: {} (code {})", message, error.code);
424 let _ = sender.send(Err(Error::LspServerError {
425 code: error.code,
426 message: error.message,
427 data: error.data,
428 }));
429 } else if let Some(result) = response.result {
430 let _ = sender.send(Ok(result));
431 } else {
432 trace!("Response with null result: {:?}", response.id);
435 let _ = sender.send(Ok(Value::Null));
436 }
437 } else {
438 warn!("Received response for unknown request ID: {:?}", response.id);
439 }
440 }
441 InboundMessage::Request(request) => {
442 debug!(
443 "Received server request: {} (id={:?})",
444 request.method, request.id
445 );
446 let response = Self::server_request_response(request);
447 let value = serde_json::to_value(&response)?;
448 transport.send(&value).await?;
449 }
450 InboundMessage::Notification(notification) => {
451 debug!("Received notification: {}", notification.method);
452
453 let typed = LspNotification::parse(¬ification.method, notification.params);
455
456 if let Some(tx) = notification_tx {
458 if let LspNotification::PublishDiagnostics(ref params) = typed {
460 debug!(
461 "Forwarding diagnostics for {}: {} items",
462 params.uri.as_str(),
463 params.diagnostics.len()
464 );
465 } else {
466 trace!("Forwarding notification: {:?}", typed);
467 }
468
469 if tx.try_send(typed).is_err() {
471 warn!("Notification channel full or closed, dropping notification");
472 }
473 }
474 }
475 }
476 }
477 }
478 }
479
480 Ok(())
481 }
482
483 fn server_request_response(request: JsonRpcRequest) -> JsonRpcResponse {
484 match Self::server_request_result(&request.method, request.params.as_ref()) {
485 Ok(result) => JsonRpcResponse {
486 jsonrpc: JSONRPC_VERSION.to_string(),
487 id: request.id,
488 result: Some(result),
489 error: None,
490 },
491 Err(error) => JsonRpcResponse {
492 jsonrpc: JSONRPC_VERSION.to_string(),
493 id: request.id,
494 result: None,
495 error: Some(error),
496 },
497 }
498 }
499
500 fn server_request_result(
501 method: &str,
502 params: Option<&Value>,
503 ) -> std::result::Result<Value, JsonRpcError> {
504 match method {
505 "client/registerCapability"
506 | "client/unregisterCapability"
507 | "workspace/workspaceFolders"
508 | "workspace/diagnostic/refresh"
509 | "workspace/semanticTokens/refresh"
510 | "workspace/inlayHint/refresh"
511 | "workspace/codeLens/refresh"
512 | "window/showMessageRequest" => Ok(Value::Null),
513 "workspace/configuration" => Ok(Self::workspace_configuration_result(params)),
514 "workspace/applyEdit" => Ok(serde_json::json!({ "applied": false })),
515 _ => Err(JsonRpcError {
516 code: -32601,
517 message: format!("Unhandled server request: {method}"),
518 data: None,
519 }),
520 }
521 }
522
523 fn workspace_configuration_result(params: Option<&Value>) -> Value {
524 let item_count = params
525 .and_then(|value| value.get("items"))
526 .and_then(Value::as_array)
527 .map_or(0, Vec::len);
528
529 Value::Array(vec![Value::Null; item_count])
530 }
531}
532
533#[cfg(test)]
534#[allow(clippy::unwrap_used)]
535mod tests {
536 use super::*;
537
538 #[test]
539 fn test_request_id_generation() {
540 let counter = AtomicI64::new(1);
541
542 let id1 = counter.fetch_add(1, Ordering::SeqCst);
543 let id2 = counter.fetch_add(1, Ordering::SeqCst);
544 let id3 = counter.fetch_add(1, Ordering::SeqCst);
545
546 assert_eq!(id1, 1);
547 assert_eq!(id2, 2);
548 assert_eq!(id3, 3);
549 }
550
551 #[test]
552 fn test_client_creation() {
553 let config = LspServerConfig::rust_analyzer();
554
555 let client = LspClient::new(config);
556 assert_eq!(client.language_id(), "rust");
557 }
558
559 #[test]
560 fn test_client_clone() {
561 let config = LspServerConfig::rust_analyzer();
562 let client = LspClient::new(config);
563
564 #[allow(clippy::redundant_clone)]
565 let cloned = client.clone();
566 assert_eq!(cloned.language_id(), "rust");
567
568 assert!(
569 cloned.receiver_task.is_none(),
570 "Cloned client should not own receiver task"
571 );
572 }
573
574 #[test]
575 fn test_register_capability_request_is_acknowledged() {
576 let request = JsonRpcRequest {
577 jsonrpc: JSONRPC_VERSION.to_string(),
578 id: RequestId::String("ts1".to_string()),
579 method: "client/registerCapability".to_string(),
580 params: Some(serde_json::json!({ "registrations": [] })),
581 };
582
583 let response = LspClient::server_request_response(request);
584
585 assert_eq!(response.id, RequestId::String("ts1".to_string()));
586 assert_eq!(response.result, Some(Value::Null));
587 assert!(response.error.is_none());
588 }
589
590 #[test]
591 fn test_workspace_configuration_request_returns_null_per_item() {
592 let result = LspClient::workspace_configuration_result(Some(&serde_json::json!({
593 "items": [{ "section": "typescript" }, { "section": "editor" }]
594 })));
595
596 assert_eq!(result, serde_json::json!([null, null]));
597 }
598
599 #[test]
600 fn test_unknown_server_request_returns_method_not_found() {
601 let request = JsonRpcRequest {
602 jsonrpc: JSONRPC_VERSION.to_string(),
603 id: RequestId::String("unknown-1".to_string()),
604 method: "custom/request".to_string(),
605 params: None,
606 };
607
608 let response = LspClient::server_request_response(request);
609
610 assert!(response.result.is_none());
611 match response.error {
612 Some(error) => {
613 assert_eq!(error.code, -32601);
614 assert_eq!(error.message, "Unhandled server request: custom/request");
615 }
616 None => panic!("unknown request should return error"),
617 }
618 }
619
620 #[tokio::test]
621 async fn test_null_response_handling() {
622 use crate::lsp::types::{JsonRpcResponse, RequestId};
623
624 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
625
626 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
627
628 pending_requests
629 .lock()
630 .await
631 .insert(RequestId::Number(1), response_tx);
632
633 let null_response = JsonRpcResponse {
634 jsonrpc: "2.0".to_string(),
635 id: RequestId::Number(1),
636 result: None,
637 error: None,
638 };
639
640 let sender = pending_requests.lock().await.remove(&null_response.id);
641 if let Some(sender) = sender {
642 let _ = sender.send(Ok(Value::Null));
643 }
644
645 let timeout_result =
646 tokio::time::timeout(tokio::time::Duration::from_millis(100), response_rx).await;
647
648 assert!(timeout_result.is_ok(), "Should not timeout");
649
650 let channel_result = timeout_result.unwrap();
651 assert!(
652 channel_result.is_ok(),
653 "Channel should not be closed: {:?}",
654 channel_result.err()
655 );
656
657 let response = channel_result.unwrap();
658 assert!(
659 response.is_ok(),
660 "Should receive Ok(Value::Null), not Err: {:?}",
661 response.err()
662 );
663
664 let value = response.unwrap();
665 assert_eq!(value, Value::Null, "Should receive Value::Null");
666 }
667
668 #[tokio::test]
669 async fn test_error_response_handling() {
670 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
671
672 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
673 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
674
675 pending_requests
676 .lock()
677 .await
678 .insert(RequestId::Number(1), response_tx);
679
680 let error_response = JsonRpcResponse {
681 jsonrpc: "2.0".to_string(),
682 id: RequestId::Number(1),
683 result: None,
684 error: Some(JsonRpcError {
685 code: -32601,
686 message: "Method not found".to_string(),
687 data: None,
688 }),
689 };
690
691 let sender = pending_requests.lock().await.remove(&error_response.id);
692 if let Some(sender) = sender
693 && let Some(error) = error_response.error
694 {
695 let _ = sender.send(Err(Error::LspServerError {
696 code: error.code,
697 message: error.message,
698 data: error.data,
699 }));
700 }
701
702 let result = response_rx.await.unwrap();
703 assert!(result.is_err(), "Should receive error");
704
705 if let Err(Error::LspServerError { code, message, .. }) = result {
706 assert_eq!(code, -32601);
707 assert_eq!(message, "Method not found");
708 } else {
709 panic!("Expected LspServerError");
710 }
711 }
712
713 #[tokio::test]
714 async fn test_unknown_request_id() {
715 use crate::lsp::types::{JsonRpcResponse, RequestId};
716
717 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
718
719 let response = JsonRpcResponse {
720 jsonrpc: "2.0".to_string(),
721 id: RequestId::Number(999),
722 result: Some(Value::Null),
723 error: None,
724 };
725
726 let sender = pending_requests.lock().await.remove(&response.id);
727 assert!(sender.is_none(), "Should not find sender for unknown ID");
728 }
729
730 #[tokio::test]
731 async fn test_long_error_message_truncation() {
732 use crate::lsp::types::{JsonRpcError, JsonRpcResponse, RequestId};
733
734 let pending_requests: Arc<Mutex<PendingRequests>> = Arc::new(Mutex::new(HashMap::new()));
735 let (response_tx, response_rx) = oneshot::channel::<Result<Value>>();
736
737 pending_requests
738 .lock()
739 .await
740 .insert(RequestId::Number(1), response_tx);
741
742 let long_message = "x".repeat(250);
743 let error_response = JsonRpcResponse {
744 jsonrpc: "2.0".to_string(),
745 id: RequestId::Number(1),
746 result: None,
747 error: Some(JsonRpcError {
748 code: -32700,
749 message: long_message.clone(),
750 data: None,
751 }),
752 };
753
754 let sender = pending_requests.lock().await.remove(&error_response.id);
755 if let Some(sender) = sender
756 && let Some(error) = error_response.error
757 {
758 let _ = sender.send(Err(Error::LspServerError {
759 code: error.code,
760 message: error.message,
761 data: error.data,
762 }));
763 }
764
765 let result = response_rx.await.unwrap();
766 assert!(result.is_err());
767
768 if let Err(Error::LspServerError { code, message, .. }) = result {
769 assert_eq!(code, -32700);
770 assert_eq!(
771 message.len(),
772 250,
773 "Full message should be preserved in Error"
774 );
775 } else {
776 panic!("Expected LspServerError");
777 }
778 }
779
780 #[tokio::test]
781 async fn test_concurrent_request_ids() {
782 let counter = Arc::new(AtomicI64::new(1));
783
784 let counter1 = Arc::clone(&counter);
785 let counter2 = Arc::clone(&counter);
786 let counter3 = Arc::clone(&counter);
787
788 let handles = vec![
789 tokio::spawn(async move { counter1.fetch_add(1, Ordering::SeqCst) }),
790 tokio::spawn(async move { counter2.fetch_add(1, Ordering::SeqCst) }),
791 tokio::spawn(async move { counter3.fetch_add(1, Ordering::SeqCst) }),
792 ];
793
794 let mut ids = Vec::new();
795 for handle in handles {
796 ids.push(handle.await.unwrap());
797 }
798
799 ids.sort_unstable();
800 assert_eq!(ids, vec![1, 2, 3], "IDs should be unique and sequential");
801 }
802
803 #[test]
804 fn test_jsonrpc_version_constant() {
805 assert_eq!(JSONRPC_VERSION, "2.0");
806 }
807}