realhydroper_lsp/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::atomic::{AtomicU32, Ordering};
7use std::sync::Arc;
8use std::rc::Rc;
9use std::task::{Context, Poll};
10
11use futures::channel::mpsc::{self, Sender};
12use futures::future::LocalBoxFuture;
13use futures::sink::SinkExt;
14use lsp_types::*;
15use lsp_types::request::WorkspaceDiagnosticRefresh;
16use serde::Serialize;
17use serde_json::Value;
18use tower::Service;
19use tracing::{error, trace};
20
21use self::pending::Pending;
22use self::progress::Progress;
23use super::state::{ServerState, State};
24use super::ExitedError;
25use crate::jsonrpc::{self, Error, ErrorCode, Id, Request, Response};
26
27pub mod progress;
28
29mod pending;
30mod socket;
31
32struct ClientInner {
33 tx: Sender<Request>,
34 request_id: AtomicU32,
35 pending: Arc<Pending>,
36 state: Rc<ServerState>,
37}
38
39/// Handle for communicating with the language client.
40///
41/// This type provides a very cheap implementation of [`Clone`] so API consumers can cheaply clone
42/// and pass it around as needed.
43///
44/// It also implements [`tower::Service`] in order to remain independent from the underlying
45/// transport and to facilitate further abstraction with middleware.
46#[derive(Clone)]
47pub struct Client {
48 inner: Rc<ClientInner>,
49}
50
51impl Client {
52 pub(super) fn new(state: Rc<ServerState>) -> (Self, ClientSocket) {
53 let (tx, rx) = mpsc::channel(1);
54 let pending = Arc::new(Pending::new());
55
56 let client = Client {
57 inner: Rc::new(ClientInner {
58 tx,
59 request_id: AtomicU32::new(0),
60 pending: pending.clone(),
61 state: state.clone(),
62 }),
63 };
64
65 (client, ClientSocket { rx, pending, state })
66 }
67
68 /// Disconnects the `Client` from its corresponding `LspService`.
69 ///
70 /// Closing the client is not required, but doing so will ensure that no more messages can be
71 /// produced. The receiver of the messages will be able to consume any in-flight messages and
72 /// then will observe the end of the stream.
73 ///
74 /// If the client is never closed and never dropped, the receiver of the messages will never
75 /// observe the end of the stream.
76 pub(crate) fn close(&self) {
77 self.inner.tx.clone().close_channel();
78 }
79}
80
81impl Client {
82 // Lifecycle Messages
83
84 /// Registers a new capability with the client.
85 ///
86 /// This corresponds to the [`client/registerCapability`] request.
87 ///
88 /// [`client/registerCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_registerCapability
89 ///
90 /// # Initialization
91 ///
92 /// If the request is sent to the client before the server has been initialized, this will
93 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
94 ///
95 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
96 pub async fn register_capability(
97 &self,
98 registrations: Vec<Registration>,
99 ) -> jsonrpc::Result<()> {
100 use lsp_types::request::RegisterCapability;
101 self.send_request::<RegisterCapability>(RegistrationParams { registrations })
102 .await
103 }
104
105 /// Unregisters a capability with the client.
106 ///
107 /// This corresponds to the [`client/unregisterCapability`] request.
108 ///
109 /// [`client/unregisterCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_unregisterCapability
110 ///
111 /// # Initialization
112 ///
113 /// If the request is sent to the client before the server has been initialized, this will
114 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
115 ///
116 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
117 pub async fn unregister_capability(
118 &self,
119 unregisterations: Vec<Unregistration>,
120 ) -> jsonrpc::Result<()> {
121 use lsp_types::request::UnregisterCapability;
122 self.send_request::<UnregisterCapability>(UnregistrationParams { unregisterations })
123 .await
124 }
125
126 // Window Features
127
128 /// Notifies the client to display a particular message in the user interface.
129 ///
130 /// This corresponds to the [`window/showMessage`] notification.
131 ///
132 /// [`window/showMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessage
133 pub async fn show_message<M: Display>(&self, typ: MessageType, message: M) {
134 use lsp_types::notification::ShowMessage;
135 self.send_notification_unchecked::<ShowMessage>(ShowMessageParams {
136 typ,
137 message: message.to_string(),
138 })
139 .await;
140 }
141
142 /// Requests the client to display a particular message in the user interface.
143 ///
144 /// Unlike the `show_message` notification, this request can also pass a list of actions and
145 /// wait for an answer from the client.
146 ///
147 /// This corresponds to the [`window/showMessageRequest`] request.
148 ///
149 /// [`window/showMessageRequest`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
150 pub async fn show_message_request<M: Display>(
151 &self,
152 typ: MessageType,
153 message: M,
154 actions: Option<Vec<MessageActionItem>>,
155 ) -> jsonrpc::Result<Option<MessageActionItem>> {
156 use lsp_types::request::ShowMessageRequest;
157 self.send_request_unchecked::<ShowMessageRequest>(ShowMessageRequestParams {
158 typ,
159 message: message.to_string(),
160 actions,
161 })
162 .await
163 }
164
165 /// Notifies the client to log a particular message.
166 ///
167 /// This corresponds to the [`window/logMessage`] notification.
168 ///
169 /// [`window/logMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_logMessage
170 pub async fn log_message<M: Display>(&self, typ: MessageType, message: M) {
171 use lsp_types::notification::LogMessage;
172 self.send_notification_unchecked::<LogMessage>(LogMessageParams {
173 typ,
174 message: message.to_string(),
175 })
176 .await;
177 }
178
179 /// Asks the client to display a particular resource referenced by a URI in the user interface.
180 ///
181 /// Returns `Ok(true)` if the document was successfully shown, or `Ok(false)` otherwise.
182 ///
183 /// This corresponds to the [`window/showDocument`] request.
184 ///
185 /// [`window/showDocument`]: https://microsoft.github.io/language-server-protocol/specification#window_showDocument
186 ///
187 /// # Initialization
188 ///
189 /// If the request is sent to the client before the server has been initialized, this will
190 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
191 ///
192 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
193 ///
194 /// # Compatibility
195 ///
196 /// This request was introduced in specification version 3.16.0.
197 pub async fn show_document(&self, params: ShowDocumentParams) -> jsonrpc::Result<bool> {
198 use lsp_types::request::ShowDocument;
199 let response = self.send_request::<ShowDocument>(params).await?;
200 Ok(response.success)
201 }
202
203 // TODO: Add `work_done_progress_create()` here (since 3.15.0) when supported by `realhydroper-lsp`.
204 // https://github.com/realhydroper/rustlsp/issues/176
205
206 /// Notifies the client to log a telemetry event.
207 ///
208 /// This corresponds to the [`telemetry/event`] notification.
209 ///
210 /// [`telemetry/event`]: https://microsoft.github.io/language-server-protocol/specification#telemetry_event
211 pub async fn telemetry_event<S: Serialize>(&self, data: S) {
212 use lsp_types::notification::TelemetryEvent;
213 match serde_json::to_value(data) {
214 Err(e) => error!("invalid JSON in `telemetry/event` notification: {}", e),
215 Ok(mut value) => {
216 if !value.is_null() && !value.is_array() && !value.is_object() {
217 value = Value::Array(vec![value]);
218 }
219 self.send_notification_unchecked::<TelemetryEvent>(value)
220 .await;
221 }
222 }
223 }
224
225 /// Asks the client to refresh the code lenses currently shown in editors. As a result, the
226 /// client should ask the server to recompute the code lenses for these editors.
227 ///
228 /// This is useful if a server detects a configuration change which requires a re-calculation
229 /// of all code lenses.
230 ///
231 /// Note that the client still has the freedom to delay the re-calculation of the code lenses
232 /// if for example an editor is currently not visible.
233 ///
234 /// This corresponds to the [`workspace/codeLens/refresh`] request.
235 ///
236 /// [`workspace/codeLens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#codeLens_refresh
237 ///
238 /// # Initialization
239 ///
240 /// If the request is sent to the client before the server has been initialized, this will
241 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
242 ///
243 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
244 ///
245 /// # Compatibility
246 ///
247 /// This request was introduced in specification version 3.16.0.
248 pub async fn code_lens_refresh(&self) -> jsonrpc::Result<()> {
249 use lsp_types::request::CodeLensRefresh;
250 self.send_request::<CodeLensRefresh>(()).await
251 }
252
253 /// Asks the client to refresh the editors for which this server provides semantic tokens. As a
254 /// result, the client should ask the server to recompute the semantic tokens for these
255 /// editors.
256 ///
257 /// This is useful if a server detects a project-wide configuration change which requires a
258 /// re-calculation of all semantic tokens. Note that the client still has the freedom to delay
259 /// the re-calculation of the semantic tokens if for example an editor is currently not visible.
260 ///
261 /// This corresponds to the [`workspace/semanticTokens/refresh`] request.
262 ///
263 /// [`workspace/semanticTokens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
264 ///
265 /// # Initialization
266 ///
267 /// If the request is sent to the client before the server has been initialized, this will
268 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
269 ///
270 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
271 ///
272 /// # Compatibility
273 ///
274 /// This request was introduced in specification version 3.16.0.
275 pub async fn semantic_tokens_refresh(&self) -> jsonrpc::Result<()> {
276 use lsp_types::request::SemanticTokensRefresh;
277 self.send_request::<SemanticTokensRefresh>(()).await
278 }
279
280 /// Asks the client to refresh the inline values currently shown in editors. As a result, the
281 /// client should ask the server to recompute the inline values for these editors.
282 ///
283 /// This is useful if a server detects a configuration change which requires a re-calculation
284 /// of all inline values. Note that the client still has the freedom to delay the
285 /// re-calculation of the inline values if for example an editor is currently not visible.
286 ///
287 /// This corresponds to the [`workspace/inlineValue/refresh`] request.
288 ///
289 /// [`workspace/inlineValue/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlineValue_refresh
290 ///
291 /// # Initialization
292 ///
293 /// If the request is sent to the client before the server has been initialized, this will
294 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
295 ///
296 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
297 ///
298 /// # Compatibility
299 ///
300 /// This request was introduced in specification version 3.17.0.
301 pub async fn inline_value_refresh(&self) -> jsonrpc::Result<()> {
302 use lsp_types::request::InlineValueRefreshRequest;
303 self.send_request::<InlineValueRefreshRequest>(()).await
304 }
305
306 /// Asks the client to refresh the inlay hints currently shown in editors. As a result, the
307 /// client should ask the server to recompute the inlay hints for these editors.
308 ///
309 /// This is useful if a server detects a configuration change which requires a re-calculation
310 /// of all inlay hints. Note that the client still has the freedom to delay the re-calculation
311 /// of the inlay hints if for example an editor is currently not visible.
312 ///
313 /// This corresponds to the [`workspace/inlayHint/refresh`] request.
314 ///
315 /// [`workspace/inlayHint/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlayHint_refresh
316 ///
317 /// # Initialization
318 ///
319 /// If the request is sent to the client before the server has been initialized, this will
320 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
321 ///
322 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
323 ///
324 /// # Compatibility
325 ///
326 /// This request was introduced in specification version 3.17.0.
327 pub async fn inlay_hint_refresh(&self) -> jsonrpc::Result<()> {
328 use lsp_types::request::InlayHintRefreshRequest;
329 self.send_request::<InlayHintRefreshRequest>(()).await
330 }
331
332 /// Asks the client to refresh all needed document and workspace diagnostics.
333 ///
334 /// This is useful if a server detects a project wide configuration change which requires a
335 /// re-calculation of all diagnostics.
336 ///
337 /// This corresponds to the [`workspace/diagnostic/refresh`] request.
338 ///
339 /// [`workspace/diagnostic/refresh`]: https://microsoft.github.io/language-server-protocol/specification#diagnostic_refresh
340 ///
341 /// # Initialization
342 ///
343 /// If the request is sent to the client before the server has been initialized, this will
344 /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
345 ///
346 /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
347 ///
348 /// # Compatibility
349 ///
350 /// This request was introduced in specification version 3.17.0.
351 pub async fn workspace_diagnostic_refresh(&self) -> jsonrpc::Result<()> {
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: Url,
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<Value>> {
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 realhydroper_lsp::{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, supressing 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 = self.inner.request_id.load(Ordering::SeqCst) as i64 + 1;
546 let msg = Request::from_request::<R>(id.into(), params);
547 trace!("server not initialized, supressing 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 pub fn next_request_id(&self) -> Id {
581 let num = self.inner.request_id.fetch_add(1, Ordering::Relaxed);
582 Id::Number(num as i64)
583 }
584}
585
586impl Debug for Client {
587 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
588 f.debug_struct("Client")
589 .field("tx", &self.inner.tx)
590 .field("pending", &self.inner.pending)
591 .field("request_id", &self.inner.request_id)
592 .field("state", &self.inner.state)
593 .finish()
594 }
595}
596
597impl Service<Request> for Client {
598 type Response = Option<Response>;
599 type Error = ExitedError;
600 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
601
602 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
603 self.inner
604 .tx
605 .clone()
606 .poll_ready(cx)
607 .map_err(|_| ExitedError(()))
608 }
609
610 fn call(&mut self, req: Request) -> Self::Future {
611 let mut tx = self.inner.tx.clone();
612 let response_waiter = req.id().cloned().map(|id| self.inner.pending.wait(id));
613
614 Box::pin(async move {
615 if tx.send(req).await.is_err() {
616 return Err(ExitedError(()));
617 }
618
619 match response_waiter {
620 Some(fut) => Ok(Some(fut.await)),
621 None => Ok(None),
622 }
623 })
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use std::future::Future;
630
631 use futures::stream::StreamExt;
632 use lsp_types::notification::{LogMessage, PublishDiagnostics, ShowMessage, TelemetryEvent};
633 use serde_json::json;
634
635 use super::*;
636
637 async fn assert_client_message<F, Fut>(f: F, expected: Request)
638 where
639 F: FnOnce(Client) -> Fut,
640 Fut: Future,
641 {
642 let state = Rc::new(ServerState::new());
643 state.set(State::Initialized);
644
645 let (client, socket) = Client::new(state);
646 f(client).await;
647
648 let messages: Vec<_> = socket.collect().await;
649 assert_eq!(messages, vec![expected]);
650 }
651
652 #[tokio::test(flavor = "current_thread")]
653 async fn log_message() {
654 let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
655 let expected = Request::from_notification::<LogMessage>(LogMessageParams {
656 typ,
657 message: msg.clone(),
658 });
659
660 assert_client_message(|p| async move { p.log_message(typ, msg).await }, expected).await;
661 }
662
663 #[tokio::test(flavor = "current_thread")]
664 async fn show_message() {
665 let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
666 let expected = Request::from_notification::<ShowMessage>(ShowMessageParams {
667 typ,
668 message: msg.clone(),
669 });
670
671 assert_client_message(|p| async move { p.show_message(typ, msg).await }, expected).await;
672 }
673
674 #[tokio::test(flavor = "current_thread")]
675 async fn telemetry_event() {
676 let null = json!(null);
677 let expected = Request::from_notification::<TelemetryEvent>(null.clone());
678 assert_client_message(|p| async move { p.telemetry_event(null).await }, expected).await;
679
680 let array = json!([1, 2, 3]);
681 let expected = Request::from_notification::<TelemetryEvent>(array.clone());
682 assert_client_message(|p| async move { p.telemetry_event(array).await }, expected).await;
683
684 let object = json!({});
685 let expected = Request::from_notification::<TelemetryEvent>(object.clone());
686 assert_client_message(|p| async move { p.telemetry_event(object).await }, expected).await;
687
688 let other = json!("hello");
689 let wrapped = Value::Array(vec![other.clone()]);
690 let expected = Request::from_notification::<TelemetryEvent>(wrapped);
691 assert_client_message(|p| async move { p.telemetry_event(other).await }, expected).await;
692 }
693
694 #[tokio::test(flavor = "current_thread")]
695 async fn publish_diagnostics() {
696 let uri: Url = "file:///path/to/file".parse().unwrap();
697 let diagnostics = vec![Diagnostic::new_simple(Default::default(), "example".into())];
698
699 let params = PublishDiagnosticsParams::new(uri.clone(), diagnostics.clone(), None);
700 let expected = Request::from_notification::<PublishDiagnostics>(params);
701
702 assert_client_message(
703 |p| async move { p.publish_diagnostics(uri, diagnostics, None).await },
704 expected,
705 )
706 .await;
707 }
708}