mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 07:04:53 +00:00
Client request sender and inbound response handling for ruff server
(#10620)
## Summary Fixes #10618. This PR introduces a proper API for sending requests to the client and handling any response sent back. Dynamic capability registration now uses this new API, fixing an issue where a much more simplistic response handler silently flushes a code action request that needed a response. ## Test Plan #10618 can no longer be reproduced. No errors about unhandled responses should appear in the extension output, and you should see this new log when the server starts: ``` <DATE> <TIME> [info] <DURATION> INFO ruff_server::server Configuration file watcher successfully registered ```
This commit is contained in:
parent
72aa1ce00f
commit
4d59142255
3 changed files with 193 additions and 73 deletions
|
@ -1,11 +1,19 @@
|
|||
use std::any::TypeId;
|
||||
|
||||
use lsp_server::{Notification, RequestId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::schedule::Task;
|
||||
|
||||
pub(crate) type ClientSender = crossbeam::channel::Sender<lsp_server::Message>;
|
||||
|
||||
pub(crate) struct Client {
|
||||
type ResponseBuilder<'s> = Box<dyn FnOnce(lsp_server::Response) -> Task<'s>>;
|
||||
|
||||
pub(crate) struct Client<'s> {
|
||||
notifier: Notifier,
|
||||
responder: Responder,
|
||||
pub(super) requester: Requester<'s>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -14,11 +22,22 @@ pub(crate) struct Notifier(ClientSender);
|
|||
#[derive(Clone)]
|
||||
pub(crate) struct Responder(ClientSender);
|
||||
|
||||
impl Client {
|
||||
pub(crate) struct Requester<'s> {
|
||||
sender: ClientSender,
|
||||
next_request_id: i32,
|
||||
response_handlers: FxHashMap<lsp_server::RequestId, ResponseBuilder<'s>>,
|
||||
}
|
||||
|
||||
impl<'s> Client<'s> {
|
||||
pub(super) fn new(sender: &ClientSender) -> Self {
|
||||
Self {
|
||||
notifier: Notifier(sender.clone()),
|
||||
responder: Responder(sender.clone()),
|
||||
requester: Requester {
|
||||
sender: sender.clone(),
|
||||
next_request_id: 1,
|
||||
response_handlers: FxHashMap::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,3 +93,80 @@ impl Responder {
|
|||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Requester<'s> {
|
||||
/// Sends a request of kind `R` to the client, with associated parameters.
|
||||
/// The task provided by `response_handler` will be dispatched as soon as the response
|
||||
/// comes back from the client.
|
||||
pub(crate) fn request<R>(
|
||||
&mut self,
|
||||
params: R::Params,
|
||||
response_handler: impl Fn(R::Result) -> Task<'s> + 'static,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
R: lsp_types::request::Request,
|
||||
{
|
||||
let serialized_params = serde_json::to_value(params)?;
|
||||
|
||||
self.response_handlers.insert(
|
||||
self.next_request_id.into(),
|
||||
Box::new(move |response: lsp_server::Response| {
|
||||
match (response.error, response.result) {
|
||||
(Some(err), _) => {
|
||||
tracing::error!(
|
||||
"Got an error from the client (code {}): {}",
|
||||
err.code,
|
||||
err.message
|
||||
);
|
||||
Task::nothing()
|
||||
}
|
||||
(None, Some(response)) => match serde_json::from_value(response) {
|
||||
Ok(response) => response_handler(response),
|
||||
Err(error) => {
|
||||
tracing::error!("Failed to deserialize response from server: {error}");
|
||||
Task::nothing()
|
||||
}
|
||||
},
|
||||
(None, None) => {
|
||||
if TypeId::of::<R::Result>() == TypeId::of::<()>() {
|
||||
// We can't call `response_handler(())` directly here, but
|
||||
// since we _know_ the type expected is `()`, we can use
|
||||
// `from_value(Value::Null)`. `R::Result` implements `DeserializeOwned`,
|
||||
// so this branch works in the general case but we'll only
|
||||
// hit it if the concrete type is `()`, so the `unwrap()` is safe here.
|
||||
response_handler(serde_json::from_value(Value::Null).unwrap());
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Server response was invalid: did not contain a result or error"
|
||||
);
|
||||
}
|
||||
Task::nothing()
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
self.sender
|
||||
.send(lsp_server::Message::Request(lsp_server::Request {
|
||||
id: self.next_request_id.into(),
|
||||
method: R::METHOD.into(),
|
||||
params: serialized_params,
|
||||
}))?;
|
||||
|
||||
self.next_request_id += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn pop_response_task(&mut self, response: lsp_server::Response) -> Task<'s> {
|
||||
if let Some(handler) = self.response_handlers.remove(&response.id) {
|
||||
handler(response)
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Received a response with ID {}, which was not expected",
|
||||
response.id
|
||||
);
|
||||
Task::nothing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue