mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[ty] Add background request task support (#19041)
## Summary This PR adds a new trait to support running a request in the background. Currently, there exists a `BackgroundDocumentRequestHandler` trait which is similar but is scoped to a specific document (file in an editor context). The new trait `BackgroundRequestHandler` is not tied to a specific document nor a specific project but it's for the entire workspace. This is added to support running workspace wide requests like computing the [workspace diagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_diagnostic) or [workspace symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol). **Note:** There's a slight difference with what a "workspace" means between the server and ty. Currently, there's a 1-1 relationship between a workspace in an editor and the project database corresponding to that workspace in ty but this could change in the future when Micha adds support for multiple workspaces or multi-root workspaces. The data that would be required by the request handler (based on implementing workspace diagnostics) is the list of databases (`ProjectDatabse`) corresponding to the projects in the workspace and the index (`Index`) that contains the open documents. The `WorkspaceSnapshot` represents this and is passed to the handler similar to `DocumentSnapshot`. ## Test Plan This is used in implementing the workspace diagnostics which is where this is tested.
This commit is contained in:
parent
e212dc2e8e
commit
a95c18a8e1
8 changed files with 136 additions and 26 deletions
|
@ -28,23 +28,23 @@ pub(super) fn request(req: server::Request) -> Task {
|
|||
let id = req.id.clone();
|
||||
|
||||
match req.method.as_str() {
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::<
|
||||
requests::GotoTypeDefinitionRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::GotoTypeDefinitionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::HoverRequestHandler::METHOD => background_request_task::<
|
||||
requests::HoverRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::HoverRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::InlayHintRequestHandler::METHOD => background_request_task::<
|
||||
requests::InlayHintRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::InlayHintRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::CompletionRequestHandler::METHOD => background_request_task::<
|
||||
requests::CompletionRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::CompletionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::LatencySensitive
|
||||
|
@ -135,7 +135,52 @@ where
|
|||
}))
|
||||
}
|
||||
|
||||
fn background_request_task<R: traits::BackgroundDocumentRequestHandler>(
|
||||
#[expect(dead_code)]
|
||||
fn background_request_task<R: traits::BackgroundRequestHandler>(
|
||||
req: server::Request,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> Result<Task>
|
||||
where
|
||||
<<R as RequestHandler>::RequestType as Request>::Params: UnwindSafe,
|
||||
{
|
||||
let retry = R::RETRY_ON_CANCELLATION.then(|| req.clone());
|
||||
let (id, params) = cast_request::<R>(req)?;
|
||||
|
||||
Ok(Task::background(schedule, move |session: &Session| {
|
||||
let cancellation_token = session
|
||||
.request_queue()
|
||||
.incoming()
|
||||
.cancellation_token(&id)
|
||||
.expect("request should have been tested for cancellation before scheduling");
|
||||
|
||||
let snapshot = session.take_workspace_snapshot();
|
||||
|
||||
Box::new(move |client| {
|
||||
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
||||
|
||||
// Test again if the request was cancelled since it was scheduled on the background task
|
||||
// and, if so, return early
|
||||
if cancellation_token.is_cancelled() {
|
||||
tracing::trace!(
|
||||
"Ignoring request id={id} method={} because it was cancelled",
|
||||
R::METHOD
|
||||
);
|
||||
|
||||
// We don't need to send a response here because the `cancel` notification
|
||||
// handler already responded with a message.
|
||||
return;
|
||||
}
|
||||
|
||||
let result = ruff_db::panic::catch_unwind(|| R::run(snapshot, client, params));
|
||||
|
||||
if let Some(response) = request_result_to_response::<R>(&id, client, result, retry) {
|
||||
respond::<R>(&id, response, client);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn background_document_request_task<R: traits::BackgroundDocumentRequestHandler>(
|
||||
req: server::Request,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> Result<Task>
|
||||
|
@ -168,7 +213,7 @@ where
|
|||
};
|
||||
|
||||
let Some(snapshot) = session.take_snapshot(url) else {
|
||||
tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist.");
|
||||
tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist");
|
||||
return Box::new(|_| {});
|
||||
};
|
||||
|
||||
|
@ -209,7 +254,7 @@ fn request_result_to_response<R>(
|
|||
request: Option<lsp_server::Request>,
|
||||
) -> Option<Result<<<R as RequestHandler>::RequestType as Request>::Result>>
|
||||
where
|
||||
R: traits::BackgroundDocumentRequestHandler,
|
||||
R: traits::RetriableRequestHandler,
|
||||
{
|
||||
match result {
|
||||
Ok(response) => Some(response),
|
||||
|
|
|
@ -8,7 +8,9 @@ use ty_project::ProjectDatabase;
|
|||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::PositionExt;
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
|
||||
pub(crate) struct CompletionRequestHandler;
|
||||
|
@ -18,8 +20,6 @@ impl RequestHandler for CompletionRequestHandler {
|
|||
}
|
||||
|
||||
impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
||||
const RETRY_ON_CANCELLATION: bool = true;
|
||||
|
||||
fn document_url(params: &CompletionParams) -> Cow<Url> {
|
||||
Cow::Borrowed(¶ms.text_document_position.text_document.uri)
|
||||
}
|
||||
|
@ -65,3 +65,7 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
|||
Ok(Some(response))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for CompletionRequestHandler {
|
||||
const RETRY_ON_CANCELLATION: bool = true;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ use lsp_types::{
|
|||
|
||||
use crate::server::Result;
|
||||
use crate::server::api::diagnostics::{Diagnostics, compute_diagnostics};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use crate::session::client::Client;
|
||||
use ty_project::ProjectDatabase;
|
||||
|
@ -43,7 +45,9 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
|
|||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for DocumentDiagnosticRequestHandler {
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ServerCancelled as i32,
|
||||
|
|
|
@ -8,7 +8,9 @@ use ty_project::ProjectDatabase;
|
|||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{PositionExt, ToLink};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
|
||||
pub(crate) struct GotoTypeDefinitionRequestHandler;
|
||||
|
@ -70,3 +72,5 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for GotoTypeDefinitionRequestHandler {}
|
||||
|
|
|
@ -2,7 +2,9 @@ use std::borrow::Cow;
|
|||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{PositionExt, ToRangeExt};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
use lsp_types::request::HoverRequest;
|
||||
use lsp_types::{HoverContents, HoverParams, MarkupContent, Url};
|
||||
|
@ -73,3 +75,5 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler {
|
|||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for HoverRequestHandler {}
|
||||
|
|
|
@ -2,7 +2,9 @@ use std::borrow::Cow;
|
|||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{RangeExt, TextSizeExt};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
use lsp_types::request::InlayHintRequest;
|
||||
use lsp_types::{InlayHintParams, Url};
|
||||
|
@ -64,3 +66,5 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
|||
Ok(Some(inlay_hints))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for InlayHintRequestHandler {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! A stateful LSP implementation that calls into the ty API.
|
||||
|
||||
use crate::session::client::Client;
|
||||
use crate::session::{DocumentSnapshot, Session};
|
||||
use crate::session::{DocumentSnapshot, Session, WorkspaceSnapshot};
|
||||
|
||||
use lsp_types::notification::Notification as LSPNotification;
|
||||
use lsp_types::request::Request;
|
||||
|
@ -25,11 +25,24 @@ pub(super) trait SyncRequestHandler: RequestHandler {
|
|||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
/// A request handler that can be run on a background thread.
|
||||
pub(super) trait BackgroundDocumentRequestHandler: RequestHandler {
|
||||
/// Whether this request be retried if it was cancelled due to a modification to the Salsa database.
|
||||
pub(super) trait RetriableRequestHandler: RequestHandler {
|
||||
/// Whether this request can be cancelled if the Salsa database is modified.
|
||||
const RETRY_ON_CANCELLATION: bool = false;
|
||||
|
||||
/// The error to return if the request was cancelled due to a modification to the Salsa database.
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ContentModified as i32,
|
||||
message: "content modified".to_string(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request handler that can be run on a background thread.
|
||||
///
|
||||
/// This handler is specific to requests that operate on a single document.
|
||||
pub(super) trait BackgroundDocumentRequestHandler: RetriableRequestHandler {
|
||||
fn document_url(
|
||||
params: &<<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> std::borrow::Cow<lsp_types::Url>;
|
||||
|
@ -40,14 +53,15 @@ pub(super) trait BackgroundDocumentRequestHandler: RequestHandler {
|
|||
client: &Client,
|
||||
params: <<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ContentModified as i32,
|
||||
message: "content modified".to_string(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
/// A request handler that can be run on a background thread.
|
||||
pub(super) trait BackgroundRequestHandler: RetriableRequestHandler {
|
||||
fn run(
|
||||
snapshot: WorkspaceSnapshot,
|
||||
client: &Client,
|
||||
params: <<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
/// A supertrait for any server notification handler.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
|
@ -223,6 +224,14 @@ impl Session {
|
|||
self.index().key_from_url(url)
|
||||
}
|
||||
|
||||
pub(crate) fn take_workspace_snapshot(&self) -> WorkspaceSnapshot {
|
||||
WorkspaceSnapshot {
|
||||
projects: AssertUnwindSafe(self.projects.values().cloned().collect()),
|
||||
index: self.index.clone().unwrap(),
|
||||
position_encoding: self.position_encoding,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_workspaces(&mut self, workspace_settings: Vec<(Url, ClientOptions)>) {
|
||||
assert!(!self.workspaces.all_initialized());
|
||||
|
||||
|
@ -453,6 +462,28 @@ impl DocumentSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
/// An immutable snapshot of the current state of [`Session`].
|
||||
pub(crate) struct WorkspaceSnapshot {
|
||||
projects: AssertUnwindSafe<Vec<ProjectDatabase>>,
|
||||
index: Arc<index::Index>,
|
||||
position_encoding: PositionEncoding,
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
impl WorkspaceSnapshot {
|
||||
pub(crate) fn projects(&self) -> &[ProjectDatabase] {
|
||||
&self.projects
|
||||
}
|
||||
|
||||
pub(crate) fn index(&self) -> &index::Index {
|
||||
&self.index
|
||||
}
|
||||
|
||||
pub(crate) fn position_encoding(&self) -> PositionEncoding {
|
||||
self.position_encoding
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Workspaces {
|
||||
workspaces: BTreeMap<Url, Workspace>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue