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}