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