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}