tower_lsp_server/service/client.rs
1//! Types for sending data to and from the language client.
2
3pub use self::socket::{ClientSocket, RequestStream, ResponseSink};
4
5use std::{
6 fmt::{self, Debug, Display, Formatter},
7 sync::{
8 Arc,
9 atomic::{AtomicU32, Ordering},
10 },
11 task::{Context, Poll},
12};
13
14use futures::{
15 channel::mpsc::{self, Sender},
16 future::BoxFuture,
17 sink::SinkExt,
18};
19use ls_types::{notification, request, *};
20use serde::Serialize;
21use tower::Service;
22use tracing::{error, trace};
23
24use self::pending::Pending;
25use self::progress::Progress;
26use super::ExitedError;
27use super::state::{ServerState, State};
28use crate::jsonrpc::{self, Error, ErrorCode, Id, Request, Response};
29
30pub mod progress;
31
32mod pending;
33mod socket;
34
35struct ClientInner {
36 tx: Sender<Request>,
37 request_id: AtomicU32,
38 pending: Arc<Pending>,
39 state: Arc<ServerState>,
40}
41
42/// Handle for communicating with the language client.
43///
44/// This type provides a very cheap implementation of [`Clone`] so API consumers can cheaply clone
45/// and pass it around as needed.
46///
47/// It also implements [`tower::Service`] in order to remain independent from the underlying
48/// transport and to facilitate further abstraction with middleware.
49#[derive(Clone)]
50pub struct Client {
51 inner: Arc<ClientInner>,
52}
53
54impl Client {
55 pub(super) fn new(state: Arc<ServerState>) -> (Self, ClientSocket) {
56 let (tx, rx) = mpsc::channel(1);
57 let pending = Arc::new(Pending::new());
58
59 let client = Self {
60 inner: Arc::new(ClientInner {
61 tx,
62 request_id: AtomicU32::new(0),
63 pending: pending.clone(),
64 state: state.clone(),
65 }),
66 };
67
68 (client, ClientSocket { rx, pending, state })
69 }
70
71 /// Disconnects the `Client` from its corresponding `LspService`.
72 ///
73 /// Closing the client is not required, but doing so will ensure that no more messages can be
74 /// produced. The receiver of the messages will be able to consume any in-flight messages and
75 /// then will observe the end of the stream.
76 ///
77 /// If the client is never closed and never dropped, the receiver of the messages will never
78 /// observe the end of the stream.
79 pub(crate) fn close(&self) {
80 self.inner.tx.clone().close_channel();
81 }
82}
83
84impl Client {
85 // Lifecycle Messages
86
87 /// Registers a new capability with the client.
88 ///
89 /// This corresponds to the [`client/registerCapability`] request.
90 ///
91 /// [`client/registerCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_registerCapability
92 ///
93 /// # Initialization
94 ///
95 /// If the request is sent to the client before the server has been initialized, this will
96 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
97 ///
98 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
99 ///
100 /// # Errors
101 ///
102 /// - The request to the client fails
103 pub async fn register_capability(
104 &self,
105 registrations: Vec<Registration>,
106 ) -> jsonrpc::Result<()> {
107 self.send_request::<request::RegisterCapability>(RegistrationParams { registrations })
108 .await
109 }
110
111 /// Unregisters a capability with the client.
112 ///
113 /// This corresponds to the [`client/unregisterCapability`] request.
114 ///
115 /// [`client/unregisterCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_unregisterCapability
116 ///
117 /// # Initialization
118 ///
119 /// If the request is sent to the client before the server has been initialized, this will
120 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
121 ///
122 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
123 ///
124 /// # Errors
125 ///
126 /// - The request to the client fails
127 pub async fn unregister_capability(
128 &self,
129 unregisterations: Vec<Unregistration>,
130 ) -> jsonrpc::Result<()> {
131 self.send_request::<request::UnregisterCapability>(UnregistrationParams {
132 unregisterations,
133 })
134 .await
135 }
136
137 // Window Features
138
139 /// Notifies the client to display a particular message in the user interface.
140 ///
141 /// This corresponds to the [`window/showMessage`] notification.
142 ///
143 /// [`window/showMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessage
144 pub async fn show_message<M: Display>(&self, typ: MessageType, message: M) {
145 self.send_notification_unchecked::<notification::ShowMessage>(ShowMessageParams {
146 typ,
147 message: message.to_string(),
148 })
149 .await;
150 }
151
152 /// Requests the client to display a particular message in the user interface.
153 ///
154 /// Unlike the `show_message` notification, this request can also pass a list of actions and
155 /// wait for an answer from the client.
156 ///
157 /// This corresponds to the [`window/showMessageRequest`] request.
158 ///
159 /// [`window/showMessageRequest`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
160 ///
161 /// # Errors
162 ///
163 /// - The request to the client fails
164 pub async fn show_message_request<M: Display>(
165 &self,
166 typ: MessageType,
167 message: M,
168 actions: Option<Vec<MessageActionItem>>,
169 ) -> jsonrpc::Result<Option<MessageActionItem>> {
170 self.send_request_unchecked::<request::ShowMessageRequest>(ShowMessageRequestParams {
171 typ,
172 message: message.to_string(),
173 actions,
174 })
175 .await
176 }
177
178 /// Notifies the client to log a particular message.
179 ///
180 /// This corresponds to the [`window/logMessage`] notification.
181 ///
182 /// [`window/logMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_logMessage
183 pub async fn log_message<M: Display>(&self, typ: MessageType, message: M) {
184 self.send_notification_unchecked::<notification::LogMessage>(LogMessageParams {
185 typ,
186 message: message.to_string(),
187 })
188 .await;
189 }
190
191 /// Asks the client to display a particular resource referenced by a URI in the user interface.
192 ///
193 /// Returns `Ok(true)` if the document was successfully shown, or `Ok(false)` otherwise.
194 ///
195 /// This corresponds to the [`window/showDocument`] request.
196 ///
197 /// [`window/showDocument`]: https://microsoft.github.io/language-server-protocol/specification#window_showDocument
198 ///
199 /// # Initialization
200 ///
201 /// If the request is sent to the client before the server has been initialized, this will
202 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
203 ///
204 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
205 ///
206 /// # Compatibility
207 ///
208 /// This request was introduced in specification version 3.16.0.
209 ///
210 /// # Errors
211 ///
212 /// - The request to the client fails
213 pub async fn show_document(&self, params: ShowDocumentParams) -> jsonrpc::Result<bool> {
214 self.send_request::<request::ShowDocument>(params)
215 .await
216 .map(|res| res.success)
217 }
218
219 // TODO: Add `work_done_progress_create()` here (since 3.15.0) when supported by `tower-lsp`.
220 // https://github.com/ebkalderon/tower-lsp/issues/176
221
222 /// Notifies the client to log a telemetry event.
223 ///
224 /// This corresponds to the [`telemetry/event`] notification.
225 ///
226 /// [`telemetry/event`]: https://microsoft.github.io/language-server-protocol/specification#telemetry_event
227 pub async fn telemetry_event<S: Serialize>(&self, data: S) {
228 match serde_json::to_value(data) {
229 Err(e) => error!("invalid JSON in `telemetry/event` notification: {}", e),
230 Ok(value) => {
231 let value = match value {
232 LSPAny::Object(value) => OneOf::Left(value),
233 LSPAny::Array(value) => OneOf::Right(value),
234 value => OneOf::Right(vec![value]),
235 };
236 self.send_notification_unchecked::<notification::TelemetryEvent>(value)
237 .await;
238 }
239 }
240 }
241
242 /// Asks the client to refresh the code lenses currently shown in editors. As a result, the
243 /// client should ask the server to recompute the code lenses for these editors.
244 ///
245 /// This is useful if a server detects a configuration change which requires a re-calculation
246 /// of all code lenses.
247 ///
248 /// Note that the client still has the freedom to delay the re-calculation of the code lenses
249 /// if for example an editor is currently not visible.
250 ///
251 /// This corresponds to the [`workspace/codeLens/refresh`] request.
252 ///
253 /// [`workspace/codeLens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#codeLens_refresh
254 ///
255 /// # Initialization
256 ///
257 /// If the request is sent to the client before the server has been initialized, this will
258 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
259 ///
260 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
261 ///
262 /// # Compatibility
263 ///
264 /// This request was introduced in specification version 3.16.0.
265 ///
266 /// # Errors
267 ///
268 /// - The request to the client fails
269 pub async fn code_lens_refresh(&self) -> jsonrpc::Result<()> {
270 self.send_request::<ls_types::request::CodeLensRefresh>(())
271 .await
272 }
273
274 /// Asks the client to refresh the editors for which this server provides semantic tokens. As a
275 /// result, the client should ask the server to recompute the semantic tokens for these
276 /// editors.
277 ///
278 /// This is useful if a server detects a project-wide configuration change which requires a
279 /// re-calculation of all semantic tokens. Note that the client still has the freedom to delay
280 /// the re-calculation of the semantic tokens if for example an editor is currently not visible.
281 ///
282 /// This corresponds to the [`workspace/semanticTokens/refresh`] request.
283 ///
284 /// [`workspace/semanticTokens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
285 ///
286 /// # Initialization
287 ///
288 /// If the request is sent to the client before the server has been initialized, this will
289 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
290 ///
291 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
292 ///
293 /// # Compatibility
294 ///
295 /// This request was introduced in specification version 3.16.0.
296 ///
297 /// # Errors
298 ///
299 /// - The request to the client fails
300 pub async fn semantic_tokens_refresh(&self) -> jsonrpc::Result<()> {
301 self.send_request::<ls_types::request::SemanticTokensRefresh>(())
302 .await
303 }
304
305 /// Asks the client to refresh the inline values currently shown in editors. As a result, the
306 /// client should ask the server to recompute the inline values for these editors.
307 ///
308 /// This is useful if a server detects a configuration change which requires a re-calculation
309 /// of all inline values. Note that the client still has the freedom to delay the
310 /// re-calculation of the inline values if for example an editor is currently not visible.
311 ///
312 /// This corresponds to the [`workspace/inlineValue/refresh`] request.
313 ///
314 /// [`workspace/inlineValue/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlineValue_refresh
315 ///
316 /// # Initialization
317 ///
318 /// If the request is sent to the client before the server has been initialized, this will
319 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
320 ///
321 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
322 ///
323 /// # Compatibility
324 ///
325 /// This request was introduced in specification version 3.17.0.
326 ///
327 /// # Errors
328 ///
329 /// - The request to the client fails
330 pub async fn inline_value_refresh(&self) -> jsonrpc::Result<()> {
331 self.send_request::<request::InlineValueRefreshRequest>(())
332 .await
333 }
334
335 /// Asks the client to refresh the inlay hints currently shown in editors. As a result, the
336 /// client should ask the server to recompute the inlay hints for these editors.
337 ///
338 /// This is useful if a server detects a configuration change which requires a re-calculation
339 /// of all inlay hints. Note that the client still has the freedom to delay the re-calculation
340 /// of the inlay hints if for example an editor is currently not visible.
341 ///
342 /// This corresponds to the [`workspace/inlayHint/refresh`] request.
343 ///
344 /// [`workspace/inlayHint/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlayHint_refresh
345 ///
346 /// # Initialization
347 ///
348 /// If the request is sent to the client before the server has been initialized, this will
349 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
350 ///
351 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
352 ///
353 /// # Compatibility
354 ///
355 /// This request was introduced in specification version 3.17.0.
356 ///
357 /// # Errors
358 ///
359 /// - The request to the client fails
360 pub async fn inlay_hint_refresh(&self) -> jsonrpc::Result<()> {
361 self.send_request::<request::InlayHintRefreshRequest>(())
362 .await
363 }
364
365 /// Asks the client to refresh all needed document and workspace diagnostics.
366 ///
367 /// This is useful if a server detects a project wide configuration change which requires a
368 /// re-calculation of all diagnostics.
369 ///
370 /// This corresponds to the [`workspace/diagnostic/refresh`] request.
371 ///
372 /// [`workspace/diagnostic/refresh`]: https://microsoft.github.io/language-server-protocol/specification#diagnostic_refresh
373 ///
374 /// # Initialization
375 ///
376 /// If the request is sent to the client before the server has been initialized, this will
377 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
378 ///
379 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
380 ///
381 /// # Compatibility
382 ///
383 /// This request was introduced in specification version 3.17.0.
384 ///
385 /// # Errors
386 ///
387 /// - The request to the client fails
388 pub async fn workspace_diagnostic_refresh(&self) -> jsonrpc::Result<()> {
389 self.send_request::<request::WorkspaceDiagnosticRefresh>(())
390 .await
391 }
392
393 /// Submits validation diagnostics for an open file with the given URI.
394 ///
395 /// This corresponds to the [`textDocument/publishDiagnostics`] notification.
396 ///
397 /// [`textDocument/publishDiagnostics`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics
398 ///
399 /// # Initialization
400 ///
401 /// This notification will only be sent if the server is initialized.
402 pub async fn publish_diagnostics(
403 &self,
404 uri: Uri,
405 diags: Vec<Diagnostic>,
406 version: Option<i32>,
407 ) {
408 self.send_notification::<notification::PublishDiagnostics>(PublishDiagnosticsParams::new(
409 uri, diags, version,
410 ))
411 .await;
412 }
413
414 /// Creates a work done progress for the given token. Per the spec, the server must not send
415 /// any progress notifications to the given token if an error occurs in this request.
416 ///
417 /// This corresponds to the [`window/workDoneProgress/create`] request.
418 ///
419 /// [`window/workDoneProgress/create`]: https://microsoft.github.io/language-server-protocol/specification#window_workDoneProgress_create
420 ///
421 /// # Initialization
422 ///
423 /// If the request is sent to the client before the server has been initialized, this will
424 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
425 ///
426 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
427 ///
428 /// # Compatibility
429 ///
430 /// This request was introduced in specification version 3.15.0.
431 ///
432 /// # Errors
433 ///
434 /// - The request to the client fails
435 pub async fn create_work_done_progress(&self, token: ProgressToken) -> jsonrpc::Result<()> {
436 self.send_request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams { token })
437 .await
438 }
439
440 // Workspace Features
441
442 /// Fetches configuration settings from the client.
443 ///
444 /// The request can fetch several configuration settings in one roundtrip. The order of the
445 /// returned configuration settings correspond to the order of the passed
446 /// [`ConfigurationItem`]s (e.g. the first item in the response is the result for the first
447 /// configuration item in the params).
448 ///
449 /// This corresponds to the [`workspace/configuration`] request.
450 ///
451 /// [`workspace/configuration`]: https://microsoft.github.io/language-server-protocol/specification#workspace_configuration
452 ///
453 /// # Initialization
454 ///
455 /// If the request is sent to the client before the server has been initialized, this will
456 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
457 ///
458 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
459 ///
460 /// # Compatibility
461 ///
462 /// This request was introduced in specification version 3.6.0.
463 ///
464 /// # Errors
465 ///
466 /// - The request to the client fails
467 pub async fn configuration(
468 &self,
469 items: Vec<ConfigurationItem>,
470 ) -> jsonrpc::Result<Vec<LSPAny>> {
471 self.send_request::<request::WorkspaceConfiguration>(ConfigurationParams { items })
472 .await
473 }
474
475 /// Fetches the current open list of workspace folders.
476 ///
477 /// Returns `None` if only a single file is open in the tool. Returns an empty `Vec` if a
478 /// workspace is open but no folders are configured.
479 ///
480 /// This corresponds to the [`workspace/workspaceFolders`] request.
481 ///
482 /// [`workspace/workspaceFolders`]: https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders
483 ///
484 /// # Initialization
485 ///
486 /// If the request is sent to the client before the server has been initialized, this will
487 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
488 ///
489 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
490 ///
491 /// # Compatibility
492 ///
493 /// This request was introduced in specification version 3.6.0.
494 ///
495 /// # Errors
496 ///
497 /// - The request to the client fails
498 pub async fn workspace_folders(&self) -> jsonrpc::Result<Option<Vec<WorkspaceFolder>>> {
499 self.send_request::<request::WorkspaceFoldersRequest>(())
500 .await
501 }
502
503 /// Requests a workspace resource be edited on the client side and returns whether the edit was
504 /// applied.
505 ///
506 /// This corresponds to the [`workspace/applyEdit`] request.
507 ///
508 /// [`workspace/applyEdit`]: https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit
509 ///
510 /// # Initialization
511 ///
512 /// If the request is sent to the client before the server has been initialized, this will
513 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
514 ///
515 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
516 ///
517 /// # Errors
518 ///
519 /// - The request to the client fails
520 pub async fn apply_edit(
521 &self,
522 edit: WorkspaceEdit,
523 ) -> jsonrpc::Result<ApplyWorkspaceEditResponse> {
524 self.send_request::<request::ApplyWorkspaceEdit>(ApplyWorkspaceEditParams {
525 edit,
526 label: None,
527 })
528 .await
529 }
530
531 /// Starts a stream of `$/progress` notifications for a client-provided [`ProgressToken`].
532 ///
533 /// This method also takes a `title` argument briefly describing the kind of operation being
534 /// performed, e.g. "Indexing" or "Linking Dependencies".
535 ///
536 /// [`ProgressToken`]: https://docs.rs/lsp-types/latest/ls_types/lsp/type.ProgressToken.html
537 ///
538 /// # Initialization
539 ///
540 /// These notifications will only be sent if the server is initialized.
541 ///
542 /// # Examples
543 ///
544 /// ```no_run
545 /// # use tower_lsp_server::{ls_types::*, Client};
546 /// #
547 /// # struct Mock {
548 /// # client: Client,
549 /// # }
550 /// #
551 /// # impl Mock {
552 /// # async fn completion(&self, params: CompletionParams) {
553 /// # let work_done_token = ProgressToken::Number(1);
554 /// #
555 /// let progress = self
556 /// .client
557 /// .progress(work_done_token, "Progress Title")
558 /// .with_message("Working...")
559 /// .with_percentage(0)
560 /// .begin()
561 /// .await;
562 ///
563 /// for percent in 1..=100 {
564 /// let msg = format!("Working... [{percent}/100]");
565 /// progress.report_with_message(msg, percent).await;
566 /// }
567 ///
568 /// progress.finish_with_message("Done!").await;
569 /// # }
570 /// # }
571 /// ```
572 pub fn progress<T>(&self, token: ProgressToken, title: T) -> Progress
573 where
574 T: Into<String>,
575 {
576 Progress::new(self.clone(), token, title.into())
577 }
578
579 /// Sends a custom notification to the client.
580 ///
581 /// # Initialization
582 ///
583 /// This notification will only be sent if the server is initialized.
584 pub async fn send_notification<N>(&self, params: N::Params)
585 where
586 N: notification::Notification,
587 {
588 if let State::Initialized | State::ShutDown = self.inner.state.get() {
589 self.send_notification_unchecked::<N>(params).await;
590 } else {
591 let msg = Request::from_notification::<N>(params);
592 trace!("server not initialized, supressing message: {}", msg);
593 }
594 }
595
596 async fn send_notification_unchecked<N>(&self, params: N::Params)
597 where
598 N: notification::Notification,
599 {
600 let request = Request::from_notification::<N>(params);
601 if self.clone().call(request).await.is_err() {
602 error!("failed to send notification");
603 }
604 }
605
606 /// Sends a custom request to the client.
607 ///
608 /// # Initialization
609 ///
610 /// If the request is sent to the client before the server has been initialized, this will
611 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
612 ///
613 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
614 ///
615 /// # Errors
616 ///
617 /// - The client is not yet initialized
618 /// - The client returns an error
619 pub async fn send_request<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
620 where
621 R: request::Request,
622 {
623 if let State::Initialized | State::ShutDown = self.inner.state.get() {
624 self.send_request_unchecked::<R>(params).await
625 } else {
626 let id = i64::from(self.inner.request_id.load(Ordering::SeqCst)) + 1;
627 let msg = Request::from_request::<R>(id.into(), params);
628 trace!("server not initialized, supressing message: {}", msg);
629 Err(jsonrpc::not_initialized_error())
630 }
631 }
632
633 async fn send_request_unchecked<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
634 where
635 R: request::Request,
636 {
637 let id = self.next_request_id();
638 let request = Request::from_request::<R>(id, params);
639
640 let Ok(Some(response)) = self.clone().call(request).await else {
641 return Err(Error::internal_error());
642 };
643
644 let (_, result) = response.into_parts();
645 result.and_then(|v| {
646 serde_json::from_value(v).map_err(|e| Error {
647 code: ErrorCode::ParseError,
648 message: e.to_string().into(),
649 data: None,
650 })
651 })
652 }
653}
654
655impl Client {
656 /// Increments the internal request ID counter and returns the previous value.
657 ///
658 /// This method can be used to build custom [`Request`] objects with numeric IDs that are
659 /// guaranteed to be unique every time.
660 #[must_use]
661 pub fn next_request_id(&self) -> Id {
662 let num = self.inner.request_id.fetch_add(1, Ordering::Relaxed);
663 Id::Number(i64::from(num))
664 }
665}
666
667impl Debug for Client {
668 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
669 f.debug_struct("Client")
670 .field("tx", &self.inner.tx)
671 .field("pending", &self.inner.pending)
672 .field("request_id", &self.inner.request_id)
673 .field("state", &self.inner.state)
674 .finish()
675 }
676}
677
678impl Service<Request> for Client {
679 type Response = Option<Response>;
680 type Error = ExitedError;
681 type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
682
683 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
684 self.inner
685 .tx
686 .clone()
687 .poll_ready(cx)
688 .map_err(|_| ExitedError(()))
689 }
690
691 fn call(&mut self, req: Request) -> Self::Future {
692 let mut tx = self.inner.tx.clone();
693 let response_waiter = req.id().cloned().map(|id| self.inner.pending.wait(id));
694
695 Box::pin(async move {
696 if tx.send(req).await.is_err() {
697 return Err(ExitedError(()));
698 }
699
700 match response_waiter {
701 Some(fut) => Ok(Some(fut.await)),
702 None => Ok(None),
703 }
704 })
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use std::future::Future;
711
712 use futures::stream::StreamExt;
713 use ls_types::notification::{LogMessage, PublishDiagnostics, ShowMessage, TelemetryEvent};
714 use serde_json::json;
715
716 use super::*;
717
718 async fn assert_client_message<F, Fut>(f: F, expected: Request)
719 where
720 F: FnOnce(Client) -> Fut,
721 Fut: Future,
722 {
723 let state = Arc::new(ServerState::new());
724 state.set(State::Initialized);
725
726 let (client, socket) = Client::new(state);
727 f(client).await;
728
729 let messages: Vec<_> = socket.collect().await;
730 assert_eq!(messages, vec![expected]);
731 }
732
733 #[tokio::test(flavor = "current_thread")]
734 async fn log_message() {
735 let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
736 let expected = Request::from_notification::<LogMessage>(LogMessageParams {
737 typ,
738 message: msg.clone(),
739 });
740
741 assert_client_message(|p| async move { p.log_message(typ, msg).await }, expected).await;
742 }
743
744 #[tokio::test(flavor = "current_thread")]
745 async fn show_message() {
746 let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
747 let expected = Request::from_notification::<ShowMessage>(ShowMessageParams {
748 typ,
749 message: msg.clone(),
750 });
751
752 assert_client_message(|p| async move { p.show_message(typ, msg).await }, expected).await;
753 }
754
755 #[tokio::test(flavor = "current_thread")]
756 async fn telemetry_event() {
757 let null = json!(null);
758 let value = OneOf::Right(vec![null.clone()]);
759 let expected = Request::from_notification::<TelemetryEvent>(value);
760 assert_client_message(|p| async move { p.telemetry_event(null).await }, expected).await;
761
762 let array = json!([1, 2, 3]);
763 let value = OneOf::Right(array.as_array().unwrap().to_owned());
764 let expected = Request::from_notification::<TelemetryEvent>(value);
765 assert_client_message(|p| async move { p.telemetry_event(array).await }, expected).await;
766
767 let object = json!({});
768 let value = OneOf::Left(object.as_object().unwrap().to_owned());
769 let expected = Request::from_notification::<TelemetryEvent>(value);
770 assert_client_message(|p| async move { p.telemetry_event(object).await }, expected).await;
771
772 let other = json!("hello");
773 let wrapped = LSPAny::Array(vec![other.clone()]);
774 let value = OneOf::Right(wrapped.as_array().unwrap().to_owned());
775 let expected = Request::from_notification::<TelemetryEvent>(value);
776 assert_client_message(|p| async move { p.telemetry_event(other).await }, expected).await;
777 }
778
779 #[tokio::test(flavor = "current_thread")]
780 async fn publish_diagnostics() {
781 let uri: Uri = "file:///path/to/file".parse().unwrap();
782 let diagnostics = vec![Diagnostic::new_simple(Range::default(), "example".into())];
783
784 let params = PublishDiagnosticsParams::new(uri.clone(), diagnostics.clone(), None);
785 let expected = Request::from_notification::<PublishDiagnostics>(params);
786
787 assert_client_message(
788 |p| async move { p.publish_diagnostics(uri, diagnostics, None).await },
789 expected,
790 )
791 .await;
792 }
793}