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    // TODO: Add `workspace_diagnostic_refresh()` here when supported by `lsp-types`.
319
320    /// Submits validation diagnostics for an open file with the given URI.
321    ///
322    /// This corresponds to the [`textDocument/publishDiagnostics`] notification.
323    ///
324    /// [`textDocument/publishDiagnostics`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics
325    ///
326    /// # Initialization
327    ///
328    /// This notification will only be sent if the server is initialized.
329    pub async fn publish_diagnostics(
330        &self,
331        uri: Url,
332        diags: Vec<Diagnostic>,
333        version: Option<i32>,
334    ) {
335        self.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams::new(
336            uri, diags, version,
337        ))
338        .await;
339    }
340
341    // Workspace Features
342
343    /// Fetches configuration settings from the client.
344    ///
345    /// The request can fetch several configuration settings in one roundtrip. The order of the
346    /// returned configuration settings correspond to the order of the passed
347    /// [`ConfigurationItem`]s (e.g. the first item in the response is the result for the first
348    /// configuration item in the params).
349    ///
350    /// This corresponds to the [`workspace/configuration`] request.
351    ///
352    /// [`workspace/configuration`]: https://microsoft.github.io/language-server-protocol/specification#workspace_configuration
353    ///
354    /// # Initialization
355    ///
356    /// If the request is sent to the client before the server has been initialized, this will
357    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
358    ///
359    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
360    ///
361    /// # Compatibility
362    ///
363    /// This request was introduced in specification version 3.6.0.
364    pub async fn configuration(
365        &self,
366        items: Vec<ConfigurationItem>,
367    ) -> jsonrpc::Result<Vec<Value>> {
368        self.send_request::<WorkspaceConfiguration>(ConfigurationParams { items })
369            .await
370    }
371
372    /// Fetches the current open list of workspace folders.
373    ///
374    /// Returns `None` if only a single file is open in the tool. Returns an empty `Vec` if a
375    /// workspace is open but no folders are configured.
376    ///
377    /// This corresponds to the [`workspace/workspaceFolders`] request.
378    ///
379    /// [`workspace/workspaceFolders`]: https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders
380    ///
381    /// # Initialization
382    ///
383    /// If the request is sent to the client before the server has been initialized, this will
384    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
385    ///
386    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
387    ///
388    /// # Compatibility
389    ///
390    /// This request was introduced in specification version 3.6.0.
391    pub async fn workspace_folders(&self) -> jsonrpc::Result<Option<Vec<WorkspaceFolder>>> {
392        self.send_request::<WorkspaceFoldersRequest>(()).await
393    }
394
395    /// Requests a workspace resource be edited on the client side and returns whether the edit was
396    /// applied.
397    ///
398    /// This corresponds to the [`workspace/applyEdit`] request.
399    ///
400    /// [`workspace/applyEdit`]: https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit
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    pub async fn apply_edit(
409        &self,
410        edit: WorkspaceEdit,
411    ) -> jsonrpc::Result<ApplyWorkspaceEditResponse> {
412        self.send_request::<ApplyWorkspaceEdit>(ApplyWorkspaceEditParams { edit, label: None })
413            .await
414    }
415
416    /// Sends a custom notification to the client.
417    ///
418    /// # Initialization
419    ///
420    /// This notification will only be sent if the server is initialized.
421    pub async fn send_notification<N>(&self, params: N::Params)
422    where
423        N: lsp_types::notification::Notification,
424    {
425        if let State::Initialized | State::ShutDown = self.inner.state.get() {
426            self.send_notification_unchecked::<N>(params).await;
427        } else {
428            let msg = Request::from_notification::<N>(params);
429            trace!("server not initialized, supressing message: {}", msg);
430        }
431    }
432
433    async fn send_notification_unchecked<N>(&self, params: N::Params)
434    where
435        N: lsp_types::notification::Notification,
436    {
437        let request = Request::from_notification::<N>(params);
438        if self.clone().call(request).await.is_err() {
439            error!("failed to send notification");
440        }
441    }
442
443    /// Sends a custom request to the client.
444    ///
445    /// # Initialization
446    ///
447    /// If the request is sent to the client before the server has been initialized, this will
448    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
449    ///
450    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
451    pub async fn send_request<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
452    where
453        R: lsp_types::request::Request,
454    {
455        if let State::Initialized | State::ShutDown = self.inner.state.get() {
456            self.send_request_unchecked::<R>(params).await
457        } else {
458            let id = self.inner.request_id.load(Ordering::SeqCst) as i64 + 1;
459            let msg = Request::from_request::<R>(id.into(), params);
460            trace!("server not initialized, supressing message: {}", msg);
461            Err(jsonrpc::not_initialized_error())
462        }
463    }
464
465    async fn send_request_unchecked<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
466    where
467        R: lsp_types::request::Request,
468    {
469        let id = self.next_request_id();
470        let request = Request::from_request::<R>(id, params);
471
472        let response = match self.clone().call(request).await {
473            Ok(Some(response)) => response,
474            Ok(None) | Err(_) => return Err(Error::internal_error()),
475        };
476
477        let (_, result) = response.into_parts();
478        result.and_then(|v| {
479            serde_json::from_value(v).map_err(|e| Error {
480                code: ErrorCode::ParseError,
481                message: e.to_string(),
482                data: None,
483            })
484        })
485    }
486}
487
488impl Client {
489    /// Increments the internal request ID counter and returns the previous value.
490    ///
491    /// This method can be used to build custom [`Request`] objects with numeric IDs that are
492    /// guaranteed to be unique every time.
493    pub fn next_request_id(&self) -> Id {
494        let num = self.inner.request_id.fetch_add(1, Ordering::Relaxed);
495        Id::Number(num as i64)
496    }
497}
498
499impl Debug for Client {
500    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
501        f.debug_struct("Client")
502            .field("tx", &self.inner.tx)
503            .field("pending", &self.inner.pending)
504            .field("request_id", &self.inner.request_id)
505            .field("state", &self.inner.state)
506            .finish()
507    }
508}
509
510impl Service<Request> for Client {
511    type Response = Option<Response>;
512    type Error = ExitedError;
513    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
514
515    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
516        self.inner
517            .tx
518            .clone()
519            .poll_ready(cx)
520            .map_err(|_| ExitedError(()))
521    }
522
523    fn call(&mut self, req: Request) -> Self::Future {
524        let mut tx = self.inner.tx.clone();
525        let response_waiter = req.id().cloned().map(|id| self.inner.pending.wait(id));
526
527        Box::pin(async move {
528            if tx.send(req).await.is_err() {
529                return Err(ExitedError(()));
530            }
531
532            match response_waiter {
533                Some(fut) => Ok(Some(fut.await)),
534                None => Ok(None),
535            }
536        })
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use std::future::Future;
543
544    use futures::stream::StreamExt;
545    use serde_json::json;
546
547    use super::*;
548
549    async fn assert_client_message<F, Fut>(f: F, expected: Request)
550    where
551        F: FnOnce(Client) -> Fut,
552        Fut: Future,
553    {
554        let state = Arc::new(ServerState::new());
555        state.set(State::Initialized);
556
557        let (client, socket) = Client::new(state);
558        f(client).await;
559
560        let messages: Vec<_> = socket.collect().await;
561        assert_eq!(messages, vec![expected]);
562    }
563
564    #[tokio::test(flavor = "current_thread")]
565    async fn log_message() {
566        let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
567        let expected = Request::from_notification::<LogMessage>(LogMessageParams {
568            typ,
569            message: msg.clone(),
570        });
571
572        assert_client_message(|p| async move { p.log_message(typ, msg).await }, expected).await;
573    }
574
575    #[tokio::test(flavor = "current_thread")]
576    async fn show_message() {
577        let (typ, msg) = (MessageType::LOG, "foo bar".to_owned());
578        let expected = Request::from_notification::<ShowMessage>(ShowMessageParams {
579            typ,
580            message: msg.clone(),
581        });
582
583        assert_client_message(|p| async move { p.show_message(typ, msg).await }, expected).await;
584    }
585
586    #[tokio::test(flavor = "current_thread")]
587    async fn telemetry_event() {
588        let null = json!(null);
589        let expected = Request::from_notification::<TelemetryEvent>(null.clone());
590        assert_client_message(|p| async move { p.telemetry_event(null).await }, expected).await;
591
592        let array = json!([1, 2, 3]);
593        let expected = Request::from_notification::<TelemetryEvent>(array.clone());
594        assert_client_message(|p| async move { p.telemetry_event(array).await }, expected).await;
595
596        let object = json!({});
597        let expected = Request::from_notification::<TelemetryEvent>(object.clone());
598        assert_client_message(|p| async move { p.telemetry_event(object).await }, expected).await;
599
600        let other = json!("hello");
601        let wrapped = Value::Array(vec![other.clone()]);
602        let expected = Request::from_notification::<TelemetryEvent>(wrapped);
603        assert_client_message(|p| async move { p.telemetry_event(other).await }, expected).await;
604    }
605
606    #[tokio::test(flavor = "current_thread")]
607    async fn publish_diagnostics() {
608        let uri: Url = "file:///path/to/file".parse().unwrap();
609        let diagnostics = vec![Diagnostic::new_simple(Default::default(), "example".into())];
610
611        let params = PublishDiagnosticsParams::new(uri.clone(), diagnostics.clone(), None);
612        let expected = Request::from_notification::<PublishDiagnostics>(params);
613
614        assert_client_message(
615            |p| async move { p.publish_diagnostics(uri, diagnostics, None).await },
616            expected,
617        )
618        .await;
619    }
620}