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