osmium_libs_lsp_server_wrapper/
client.rs

1use crate::jsonrpc::{self};
2use lsp_server::{Message, RequestId};
3use lsp_types::notification::*;
4use lsp_types::request::*;
5use lsp_types::*;
6use serde::Serialize;
7use serde_json::Value;
8use std::cell::RefCell;
9use std::fmt::Display;
10use std::rc::Weak;
11use tracing::error;
12
13use crate::server::LspServer;
14
15#[derive(Clone)]
16pub(crate) struct ClientInner {
17    server: Option<Weak<dyn LspServer>>,
18    id: RefCell<u32>,
19}
20
21/// Handle for communicating with the language client.
22///
23/// This type provides a very cheap implementation of [`Clone`] so API consumers can cheaply clone
24/// and pass it around as needed.
25///
26/// It also implements [`tower::Service`] in order to remain independent from the underlying
27/// transport and to facilitate further abstraction with middleware.
28#[derive(Clone)]
29pub struct Client {
30    inner: ClientInner,
31}
32
33impl Client {
34    pub(super) fn new() -> Self {
35        Client {
36            inner: ClientInner {
37                server: None,
38                id: RefCell::new(0),
39            },
40        }
41    }
42
43    pub(super) fn set_server(&mut self, server: Weak<dyn LspServer>) {
44        self.inner.server = Some(server);
45    }
46}
47
48impl Client {
49    // Lifecycle Messages
50
51    /// Registers a new capability with the client.
52    ///
53    /// This corresponds to the [`client/registerCapability`] request.
54    ///
55    /// [`client/registerCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_registerCapability
56    ///
57    /// # Initialization
58    ///
59    /// If the request is sent to the client before the server has been initialized, this will
60    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
61    ///
62    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
63    pub fn register_capability(&self, registrations: Vec<Registration>) -> jsonrpc::Result<()> {
64        self.send_request::<RegisterCapability>(RegistrationParams { registrations })
65    }
66
67    /// Unregisters a capability with the client.
68    ///
69    /// This corresponds to the [`client/unregisterCapability`] request.
70    ///
71    /// [`client/unregisterCapability`]: https://microsoft.github.io/language-server-protocol/specification#client_unregisterCapability
72    ///
73    /// # Initialization
74    ///
75    /// If the request is sent to the client before the server has been initialized, this will
76    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
77    ///
78    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
79    pub fn unregister_capability(
80        &self,
81        unregisterations: Vec<Unregistration>,
82    ) -> jsonrpc::Result<()> {
83        self.send_request::<UnregisterCapability>(UnregistrationParams { unregisterations })
84    }
85
86    // Window Features
87
88    /// Notifies the client to display a particular message in the user interface.
89    ///
90    /// This corresponds to the [`window/showMessage`] notification.
91    ///
92    /// [`window/showMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessage
93    pub fn show_message<M: Display>(&self, typ: MessageType, message: M) {
94        self.send_notification_unchecked::<ShowMessage>(ShowMessageParams {
95            typ,
96            message: message.to_string(),
97        });
98    }
99
100    /// Requests the client to display a particular message in the user interface.
101    ///
102    /// Unlike the `show_message` notification, this request can also pass a list of actions and
103    /// wait for an answer from the client.
104    ///
105    /// This corresponds to the [`window/showMessageRequest`] request.
106    ///
107    /// [`window/showMessageRequest`]: https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
108    pub fn show_message_request<M: Display>(
109        &self,
110        typ: MessageType,
111        message: M,
112        actions: Option<Vec<MessageActionItem>>,
113    ) -> jsonrpc::Result<Option<MessageActionItem>> {
114        self.send_request_unchecked::<ShowMessageRequest>(ShowMessageRequestParams {
115            typ,
116            message: message.to_string(),
117            actions,
118        })
119    }
120
121    /// Notifies the client to log a particular message.
122    ///
123    /// This corresponds to the [`window/logMessage`] notification.
124    ///
125    /// [`window/logMessage`]: https://microsoft.github.io/language-server-protocol/specification#window_logMessage
126    pub fn log_message<M: Display>(&self, typ: MessageType, message: M) {
127        self.send_notification_unchecked::<LogMessage>(LogMessageParams {
128            typ,
129            message: message.to_string(),
130        });
131    }
132
133    /// Asks the client to display a particular resource referenced by a URI in the user interface.
134    ///
135    /// Returns `Ok(true)` if the document was successfully shown, or `Ok(false)` otherwise.
136    ///
137    /// This corresponds to the [`window/showDocument`] request.
138    ///
139    /// [`window/showDocument`]: https://microsoft.github.io/language-server-protocol/specification#window_showDocument
140    ///
141    /// # Initialization
142    ///
143    /// If the request is sent to the client before the server has been initialized, this will
144    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
145    ///
146    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
147    ///
148    /// # Compatibility
149    ///
150    /// This request was introduced in specification version 3.16.0.
151    pub fn show_document(&self, params: ShowDocumentParams) -> jsonrpc::Result<bool> {
152        let response = self.send_request::<ShowDocument>(params)?;
153        Ok(response.success)
154    }
155
156    // TODO: Add `work_done_progress_create()` here (since 3.15.0) when supported by `tower-lsp`.
157    // https://github.com/ebkalderon/tower-lsp/issues/176
158
159    /// Notifies the client to log a telemetry event.
160    ///
161    /// This corresponds to the [`telemetry/event`] notification.
162    ///
163    /// [`telemetry/event`]: https://microsoft.github.io/language-server-protocol/specification#telemetry_event
164    pub fn telemetry_event<U: Serialize>(&self, data: U) {
165        match serde_json::to_value(data) {
166            Err(e) => error!("invalid JSON in `telemetry/event` notification: {}", e),
167            Ok(mut value) => {
168                if !value.is_null() && !value.is_array() && !value.is_object() {
169                    value = Value::Array(vec![value]);
170                }
171                self.send_notification_unchecked::<TelemetryEvent>(value);
172            }
173        }
174    }
175
176    /// Asks the client to refresh the code lenses currently shown in editors. As a result, the
177    /// client should ask the server to recompute the code lenses for these editors.
178    ///
179    /// This is useful if a server detects a configuration change which requires a re-calculation
180    /// of all code lenses.
181    ///
182    /// Note that the client still has the freedom to delay the re-calculation of the code lenses
183    /// if for example an editor is currently not visible.
184    ///
185    /// This corresponds to the [`workspace/codeLens/refresh`] request.
186    ///
187    /// [`workspace/codeLens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#codeLens_refresh
188    ///
189    /// # Initialization
190    ///
191    /// If the request is sent to the client before the server has been initialized, this will
192    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
193    ///
194    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
195    ///
196    /// # Compatibility
197    ///
198    /// This request was introduced in specification version 3.16.0.
199    pub fn code_lens_refresh(&self) -> jsonrpc::Result<()> {
200        self.send_request::<CodeLensRefresh>(())
201    }
202
203    /// Asks the client to refresh the editors for which this server provides semantic tokens. As a
204    /// result, the client should ask the server to recompute the semantic tokens for these
205    /// editors.
206    ///
207    /// This is useful if a server detects a project-wide configuration change which requires a
208    /// re-calculation of all semantic tokens. Note that the client still has the freedom to delay
209    /// the re-calculation of the semantic tokens if for example an editor is currently not visible.
210    ///
211    /// This corresponds to the [`workspace/semanticTokens/refresh`] request.
212    ///
213    /// [`workspace/semanticTokens/refresh`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
214    ///
215    /// # Initialization
216    ///
217    /// If the request is sent to the client before the server has been initialized, this will
218    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
219    ///
220    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
221    ///
222    /// # Compatibility
223    ///
224    /// This request was introduced in specification version 3.16.0.
225    pub fn semantic_tokens_refresh(&self) -> jsonrpc::Result<()> {
226        self.send_request::<SemanticTokensRefresh>(())
227    }
228
229    /// Asks the client to refresh the inline values currently shown in editors. As a result, the
230    /// client should ask the server to recompute the inline values for these editors.
231    ///
232    /// This is useful if a server detects a configuration change which requires a re-calculation
233    /// of all inline values. Note that the client still has the freedom to delay the
234    /// re-calculation of the inline values if for example an editor is currently not visible.
235    ///
236    /// This corresponds to the [`workspace/inlineValue/refresh`] request.
237    ///
238    /// [`workspace/inlineValue/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlineValue_refresh
239    ///
240    /// # Initialization
241    ///
242    /// If the request is sent to the client before the server has been initialized, this will
243    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
244    ///
245    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
246    ///
247    /// # Compatibility
248    ///
249    /// This request was introduced in specification version 3.17.0.
250    pub fn inline_value_refresh(&self) -> jsonrpc::Result<()> {
251        self.send_request::<InlineValueRefreshRequest>(())
252    }
253
254    /// Asks the client to refresh the inlay hints currently shown in editors. As a result, the
255    /// client should ask the server to recompute the inlay hints for these editors.
256    ///
257    /// This is useful if a server detects a configuration change which requires a re-calculation
258    /// of all inlay hints. Note that the client still has the freedom to delay the re-calculation
259    /// of the inlay hints if for example an editor is currently not visible.
260    ///
261    /// This corresponds to the [`workspace/inlayHint/refresh`] request.
262    ///
263    /// [`workspace/inlayHint/refresh`]: https://microsoft.github.io/language-server-protocol/specification#workspace_inlayHint_refresh
264    ///
265    /// # Initialization
266    ///
267    /// If the request is sent to the client before the server has been initialized, this will
268    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
269    ///
270    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
271    ///
272    /// # Compatibility
273    ///
274    /// This request was introduced in specification version 3.17.0.
275    pub fn inlay_hint_refresh(&self) -> jsonrpc::Result<()> {
276        self.send_request::<InlayHintRefreshRequest>(())
277    }
278
279    /// Asks the client to refresh all needed document and workspace diagnostics.
280    ///
281    /// This is useful if a server detects a project wide configuration change which requires a
282    /// re-calculation of all diagnostics.
283    ///
284    /// This corresponds to the [`workspace/diagnostic/refresh`] request.
285    ///
286    /// [`workspace/diagnostic/refresh`]: https://microsoft.github.io/language-server-protocol/specification#diagnostic_refresh
287    ///
288    /// # Initialization
289    ///
290    /// If the request is sent to the client before the server has been initialized, this will
291    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
292    ///
293    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
294    ///
295    /// # Compatibility
296    ///
297    /// This request was introduced in specification version 3.17.0.
298    pub fn workspace_diagnostic_refresh(&self) -> jsonrpc::Result<()> {
299        self.send_request::<WorkspaceDiagnosticRefresh>(())
300    }
301
302    /// Submits validation diagnostics for an open file with the given URI.
303    ///
304    /// This corresponds to the [`textDocument/publishDiagnostics`] notification.
305    ///
306    /// [`textDocument/publishDiagnostics`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics
307    ///
308    /// # Initialization
309    ///
310    /// This notification will only be sent if the server is initialized.
311    pub fn publish_diagnostics(&self, uri: Url, diags: Vec<Diagnostic>, version: Option<i32>) {
312        self.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams::new(
313            uri, diags, version,
314        ));
315    }
316
317    // Workspace Features
318
319    /// Fetches configuration settings from the client.
320    ///
321    /// The request can fetch several configuration settings in one roundtrip. The order of the
322    /// returned configuration settings correspond to the order of the passed
323    /// [`ConfigurationItem`]s (e.g. the first item in the response is the result for the first
324    /// configuration item in the params).
325    ///
326    /// This corresponds to the [`workspace/configuration`] request.
327    ///
328    /// [`workspace/configuration`]: https://microsoft.github.io/language-server-protocol/specification#workspace_configuration
329    ///
330    /// # Initialization
331    ///
332    /// If the request is sent to the client before the server has been initialized, this will
333    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
334    ///
335    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
336    ///
337    /// # Compatibility
338    ///
339    /// This request was introduced in specification version 3.6.0.
340    pub fn configuration(&self, items: Vec<ConfigurationItem>) -> jsonrpc::Result<Vec<Value>> {
341        self.send_request::<WorkspaceConfiguration>(ConfigurationParams { items })
342    }
343
344    /// Fetches the current open list of workspace folders.
345    ///
346    /// Returns `None` if only a single file is open in the tool. Returns an empty `Vec` if a
347    /// workspace is open but no folders are configured.
348    ///
349    /// This corresponds to the [`workspace/workspaceFolders`] request.
350    ///
351    /// [`workspace/workspaceFolders`]: https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders
352    ///
353    /// # Initialization
354    ///
355    /// If the request is sent to the client before the server has been initialized, this will
356    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
357    ///
358    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
359    ///
360    /// # Compatibility
361    ///
362    /// This request was introduced in specification version 3.6.0.
363    pub fn workspace_folders(&self) -> jsonrpc::Result<Option<Vec<WorkspaceFolder>>> {
364        self.send_request::<WorkspaceFoldersRequest>(())
365    }
366
367    /// Requests a workspace resource be edited on the client side and returns whether the edit was
368    /// applied.
369    ///
370    /// This corresponds to the [`workspace/applyEdit`] request.
371    ///
372    /// [`workspace/applyEdit`]: https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit
373    ///
374    /// # Initialization
375    ///
376    /// If the request is sent to the client before the server has been initialized, this will
377    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
378    ///
379    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
380    pub fn apply_edit(&self, edit: WorkspaceEdit) -> jsonrpc::Result<ApplyWorkspaceEditResponse> {
381        self.send_request::<ApplyWorkspaceEdit>(ApplyWorkspaceEditParams { edit, label: None })
382    }
383
384    /// Sends a custom notification to the client.
385    ///
386    /// # Initialization
387    ///
388    /// This notification will only be sent if the server is initialized.
389    pub fn send_notification<N>(&self, params: N::Params)
390    where
391        N: lsp_types::notification::Notification,
392    {
393        let server_opt = self.inner.server.clone().unwrap().upgrade();
394        if server_opt.is_none() {
395            eprintln!("Cannot send request, server is not initialized");
396            return;
397        }
398        server_opt
399            .unwrap()
400            .send(Message::Notification(lsp_server::Notification::new(
401                N::METHOD.to_string(),
402                params,
403            )));
404    }
405
406    fn send_notification_unchecked<N>(&self, params: N::Params)
407    where
408        N: lsp_types::notification::Notification,
409    {
410        let server_opt = self.inner.server.clone().unwrap().upgrade();
411        if server_opt.is_none() {
412            eprintln!("Cannot send request, server is not initialized");
413            return;
414        }
415        server_opt
416            .unwrap()
417            .send(Message::Notification(lsp_server::Notification::new(
418                N::METHOD.to_string(),
419                params,
420            )));
421    }
422
423    /// Sends a custom request to the client.
424    ///
425    /// # Initialization
426    ///
427    /// If the request is sent to the client before the server has been initialized, this will
428    /// immediately return `Err` with JSON-RPC error code `-32002` ([read more]).
429    ///
430    /// [read more]: https://microsoft.github.io/language-server-protocol/specification#initialize
431    pub fn send_request<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
432    where
433        R: lsp_types::request::Request,
434    {
435        let server_opt = self.inner.server.clone().unwrap().upgrade();
436        if server_opt.is_none() {
437            eprintln!("Cannot send request, server is not initialized");
438            return Err(jsonrpc::not_initialized_error());
439        }
440        server_opt
441            .as_ref()
442            .unwrap()
443            .send(Message::Request(lsp_server::Request::new(
444                RequestId::from(self.next_request_id().to_string()),
445                R::METHOD.to_string(),
446                params,
447            )));
448        Err(jsonrpc::not_initialized_error())
449    }
450
451    fn send_request_unchecked<R>(&self, params: R::Params) -> jsonrpc::Result<R::Result>
452    where
453        R: lsp_types::request::Request,
454    {
455        let server_opt = self.inner.server.clone().unwrap().upgrade();
456        if server_opt.is_none() {
457            eprintln!("Cannot send request, server is not initialized");
458            return Err(jsonrpc::not_initialized_error());
459        }
460        server_opt
461            .unwrap()
462            .send(Message::Request(lsp_server::Request::new(
463                RequestId::from(self.next_request_id().to_string()),
464                R::METHOD.to_string(),
465                params,
466            )));
467        Err(jsonrpc::not_initialized_error())
468    }
469}
470
471impl Client {
472    fn next_request_id(&self) -> u32 {
473        let id = *self.inner.id.borrow_mut();
474        if id == u32::MAX {
475            *self.inner.id.borrow_mut() = 0;
476        } else {
477            *self.inner.id.borrow_mut() += 1;
478        }
479        id
480    }
481}