|
| 1 | +use crate::Session; |
| 2 | +use crate::server::{Action, ConnectionSender}; |
| 3 | +use crate::server::{Event, MainLoopSender}; |
| 4 | +use anyhow::{Context, anyhow}; |
| 5 | +use lsp_server::{Message, Notification, RequestId}; |
| 6 | +use serde_json::Value; |
| 7 | +use std::any::TypeId; |
| 8 | +use std::fmt::Display; |
| 9 | + |
| 10 | +pub(crate) type ClientResponseHandler = Box<dyn FnOnce(&Session, lsp_server::Response) + Send>; |
| 11 | + |
| 12 | +#[derive(Debug)] |
| 13 | +pub(crate) struct Client { |
| 14 | + /// Channel to send messages back to the main loop. |
| 15 | + main_loop_sender: MainLoopSender, |
| 16 | + /// Channel to send messages directly to the LSP client without going through the main loop. |
| 17 | + /// |
| 18 | + /// This is generally preferred because it reduces pressure on the main loop but it may not always be |
| 19 | + /// possible if access to data on [`Session`] is required, which background tasks don't have. |
| 20 | + client_sender: ConnectionSender, |
| 21 | +} |
| 22 | + |
| 23 | +impl Client { |
| 24 | + pub(crate) fn new(main_loop_sender: MainLoopSender, client_sender: ConnectionSender) -> Self { |
| 25 | + Self { |
| 26 | + main_loop_sender, |
| 27 | + client_sender, |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + /// Sends a request of kind `R` to the client, with associated parameters. |
| 32 | + /// |
| 33 | + /// The request is sent immediately. |
| 34 | + /// The `response_handler` will be dispatched as soon as the client response |
| 35 | + /// is processed on the main-loop. The handler always runs on the main-loop thread. |
| 36 | + /// |
| 37 | + /// # Note |
| 38 | + /// This method takes a `session` so that we can register the pending-request |
| 39 | + /// and send the response directly to the client. If this ever becomes too limiting (because we |
| 40 | + /// need to send a request from somewhere where we don't have access to session), consider introducing |
| 41 | + /// a new `send_deferred_request` method that doesn't take a session and instead sends |
| 42 | + /// an `Action` to the main loop to send the request (the main loop has always access to session). |
| 43 | + pub(crate) fn send_request<R>( |
| 44 | + &self, |
| 45 | + session: &Session, |
| 46 | + params: R::Params, |
| 47 | + response_handler: impl FnOnce(&Session, R::Result) + Send + 'static, |
| 48 | + ) -> crate::Result<()> |
| 49 | + where |
| 50 | + R: lsp_types::request::Request, |
| 51 | + { |
| 52 | + let response_handler = |
| 53 | + Box::new(move |session: &Session, response: lsp_server::Response| { |
| 54 | + match (response.error, response.result) { |
| 55 | + (Some(err), _) => { |
| 56 | + tracing::error!( |
| 57 | + "Got an error from the client (code {}): {}", |
| 58 | + err.code, |
| 59 | + err.message |
| 60 | + ); |
| 61 | + } |
| 62 | + (None, Some(response)) => match serde_json::from_value(response) { |
| 63 | + Ok(response) => response_handler(session, response), |
| 64 | + Err(error) => { |
| 65 | + tracing::error!("Failed to deserialize response from server: {error}"); |
| 66 | + } |
| 67 | + }, |
| 68 | + (None, None) => { |
| 69 | + if TypeId::of::<R::Result>() == TypeId::of::<()>() { |
| 70 | + // We can't call `response_handler(())` directly here, but |
| 71 | + // since we _know_ the type expected is `()`, we can use |
| 72 | + // `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`, |
| 73 | + // so this branch works in the general case but we'll only |
| 74 | + // hit it if the concrete type is `()`, so the `unwrap()` is safe here. |
| 75 | + response_handler(session, serde_json::from_value(Value::Null).unwrap()); |
| 76 | + } else { |
| 77 | + tracing::error!( |
| 78 | + "Server response was invalid: did not contain a result or error" |
| 79 | + ); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + }); |
| 84 | + |
| 85 | + let id = session |
| 86 | + .request_queue() |
| 87 | + .outgoing() |
| 88 | + .register(response_handler); |
| 89 | + |
| 90 | + self.client_sender |
| 91 | + .send(Message::Request(lsp_server::Request { |
| 92 | + id, |
| 93 | + method: R::METHOD.to_string(), |
| 94 | + params: serde_json::to_value(params).context("Failed to serialize params")?, |
| 95 | + }))?; |
| 96 | + |
| 97 | + Ok(()) |
| 98 | + } |
| 99 | + |
| 100 | + /// Sends a notification to the client. |
| 101 | + pub(crate) fn send_notification<N>(&self, params: N::Params) -> crate::Result<()> |
| 102 | + where |
| 103 | + N: lsp_types::notification::Notification, |
| 104 | + { |
| 105 | + let method = N::METHOD.to_string(); |
| 106 | + |
| 107 | + self.client_sender |
| 108 | + .send(lsp_server::Message::Notification(Notification::new( |
| 109 | + method, params, |
| 110 | + ))) |
| 111 | + .map_err(|error| anyhow!("Failed to send notification: {error}")) |
| 112 | + } |
| 113 | + |
| 114 | + /// Sends a notification without any parameters to the client. |
| 115 | + /// |
| 116 | + /// This is useful for notifications that don't require any data. |
| 117 | + #[expect(dead_code)] |
| 118 | + pub(crate) fn send_notification_no_params(&self, method: String) -> crate::Result<()> { |
| 119 | + self.client_sender |
| 120 | + .send(lsp_server::Message::Notification(Notification::new( |
| 121 | + method, |
| 122 | + Value::Null, |
| 123 | + ))) |
| 124 | + .map_err(|error| anyhow!("Failed to send notification: {error}")) |
| 125 | + } |
| 126 | + |
| 127 | + /// Sends a response to the client for a given request ID. |
| 128 | + /// |
| 129 | + /// The response isn't sent immediately. Instead, it's queued up in the main loop |
| 130 | + /// and checked for cancellation (each request must have exactly one response). |
| 131 | + pub(crate) fn respond<R>( |
| 132 | + &self, |
| 133 | + id: RequestId, |
| 134 | + result: crate::server::Result<R>, |
| 135 | + ) -> crate::Result<()> |
| 136 | + where |
| 137 | + R: serde::Serialize, |
| 138 | + { |
| 139 | + let response = match result { |
| 140 | + Ok(res) => lsp_server::Response::new_ok(id, res), |
| 141 | + Err(crate::server::Error { code, error }) => { |
| 142 | + lsp_server::Response::new_err(id, code as i32, error.to_string()) |
| 143 | + } |
| 144 | + }; |
| 145 | + |
| 146 | + self.main_loop_sender |
| 147 | + .send(Event::Action(Action::SendResponse(response))) |
| 148 | + .map_err(|error| anyhow!("Failed to send response: {error}")) |
| 149 | + } |
| 150 | + |
| 151 | + /// Sends an error response to the client for a given request ID. |
| 152 | + /// |
| 153 | + /// The response isn't sent immediately. Instead, it's queued up in the main loop. |
| 154 | + pub(crate) fn respond_err( |
| 155 | + &self, |
| 156 | + id: RequestId, |
| 157 | + error: lsp_server::ResponseError, |
| 158 | + ) -> crate::Result<()> { |
| 159 | + let response = lsp_server::Response::new_err(id, error.code, error.message); |
| 160 | + |
| 161 | + self.main_loop_sender |
| 162 | + .send(Event::Action(Action::SendResponse(response))) |
| 163 | + .map_err(|error| anyhow!("Failed to send response: {error}")) |
| 164 | + } |
| 165 | + |
| 166 | + /// Shows a message to the user. |
| 167 | + /// |
| 168 | + /// This opens a pop up in VS Code showing `message`. |
| 169 | + pub(crate) fn show_message( |
| 170 | + &self, |
| 171 | + message: impl Display, |
| 172 | + message_type: lsp_types::MessageType, |
| 173 | + ) -> crate::Result<()> { |
| 174 | + self.send_notification::<lsp_types::notification::ShowMessage>( |
| 175 | + lsp_types::ShowMessageParams { |
| 176 | + typ: message_type, |
| 177 | + message: message.to_string(), |
| 178 | + }, |
| 179 | + ) |
| 180 | + } |
| 181 | + |
| 182 | + /// Re-queues this request after a salsa cancellation for a retry. |
| 183 | + /// |
| 184 | + /// The main loop will skip the retry if the client cancelled the request in the meantime. |
| 185 | + pub(crate) fn retry(&self, request: lsp_server::Request) -> crate::Result<()> { |
| 186 | + self.main_loop_sender |
| 187 | + .send(Event::Action(Action::RetryRequest(request))) |
| 188 | + .map_err(|error| anyhow!("Failed to send retry request: {error}")) |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +/// Sends an error to the client with a formatted message. The error is sent in a |
| 193 | +/// `window/showMessage` notification. |
| 194 | +#[macro_export] |
| 195 | +macro_rules! show_err_msg { |
| 196 | + ($client:expr, $msg:expr$(, $($arg:tt)*)?) => {{ |
| 197 | + let result = $client.show_message(::core::format_args!($msg$(, $($arg)*)?), lsp_types::MessageType::ERROR); |
| 198 | + |
| 199 | + if let Err(err) = result { |
| 200 | + tracing::error!("Failed to send error message to client: {err}"); |
| 201 | + } |
| 202 | + }}; |
| 203 | +} |
0 commit comments