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}