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