mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 01:51:30 +00:00
ruff server
now supports commands for auto-fixing, organizing imports, and formatting (#10654)
## Summary
This builds off of the work in
https://github.com/astral-sh/ruff/pull/10652 to implement a command
executor, backwards compatible with the commands from the previous LSP
(`ruff.applyAutofix`, `ruff.applyFormat` and
`ruff.applyOrganizeImports`).
This involved a lot of refactoring and tweaks to the code action
resolution code - the most notable change is that workspace edits are
specified in a slightly different way, using the more general `changes`
field instead of the `document_changes` field (which isn't supported on
all LSP clients). Additionally, the API for synchronous request handlers
has been updated to include access to the `Requester`, which we use to
send a `workspace/applyEdit` request to the client.
## Test Plan
7932e30f
-d944-4e35-b828-1d81aa56c087
This commit is contained in:
parent
1b31d4e9f1
commit
c11e6d709c
13 changed files with 379 additions and 96 deletions
153
crates/ruff_server/src/server/api/requests/execute_command.rs
Normal file
153
crates/ruff_server/src/server/api/requests/execute_command.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::edit::WorkspaceEditTracker;
|
||||
use crate::server::api::LSPResult;
|
||||
use crate::server::client;
|
||||
use crate::server::schedule::Task;
|
||||
use crate::session::Session;
|
||||
use crate::DIAGNOSTIC_NAME;
|
||||
use crate::{edit::DocumentVersion, server};
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{self as types, request as req};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Command {
|
||||
Format,
|
||||
FixAll,
|
||||
OrganizeImports,
|
||||
}
|
||||
|
||||
pub(crate) struct ExecuteCommand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TextDocumentArgument {
|
||||
uri: types::Url,
|
||||
version: DocumentVersion,
|
||||
}
|
||||
|
||||
impl super::RequestHandler for ExecuteCommand {
|
||||
type RequestType = req::ExecuteCommand;
|
||||
}
|
||||
|
||||
impl super::SyncRequestHandler for ExecuteCommand {
|
||||
fn run(
|
||||
session: &mut Session,
|
||||
_notifier: client::Notifier,
|
||||
requester: &mut client::Requester,
|
||||
params: types::ExecuteCommandParams,
|
||||
) -> server::Result<Option<serde_json::Value>> {
|
||||
let command =
|
||||
Command::from_str(¶ms.command).with_failure_code(ErrorCode::InvalidParams)?;
|
||||
|
||||
// check if we can apply a workspace edit
|
||||
if !session.resolved_client_capabilities().apply_edit {
|
||||
return Err(anyhow::anyhow!("Cannot execute the '{}' command: the client does not support `workspace/applyEdit`", command.label())).with_failure_code(ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
let mut arguments: Vec<TextDocumentArgument> = params
|
||||
.arguments
|
||||
.into_iter()
|
||||
.map(|value| serde_json::from_value(value).with_failure_code(ErrorCode::InvalidParams))
|
||||
.collect::<server::Result<_>>()?;
|
||||
|
||||
arguments.sort_by(|a, b| a.uri.cmp(&b.uri));
|
||||
arguments.dedup_by(|a, b| a.uri == b.uri);
|
||||
|
||||
let mut edit_tracker = WorkspaceEditTracker::new(session.resolved_client_capabilities());
|
||||
for TextDocumentArgument { uri, version } in arguments {
|
||||
let snapshot = session
|
||||
.take_snapshot(&uri)
|
||||
.ok_or(anyhow::anyhow!("Document snapshot not available for {uri}",))
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
match command {
|
||||
Command::FixAll => {
|
||||
let edits = super::code_action_resolve::fix_all_edit(
|
||||
snapshot.document(),
|
||||
&snapshot.configuration().linter,
|
||||
snapshot.encoding(),
|
||||
)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
edit_tracker
|
||||
.set_edits_for_document(uri, version, edits)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
}
|
||||
Command::Format => {
|
||||
let response = super::format::format_document(&snapshot)?;
|
||||
if let Some(edits) = response {
|
||||
edit_tracker
|
||||
.set_edits_for_document(uri, version, edits)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
}
|
||||
}
|
||||
Command::OrganizeImports => {
|
||||
let edits = super::code_action_resolve::organize_imports_edit(
|
||||
snapshot.document(),
|
||||
&snapshot.configuration().linter,
|
||||
snapshot.encoding(),
|
||||
)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
edit_tracker
|
||||
.set_edits_for_document(uri, version, edits)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !edit_tracker.is_empty() {
|
||||
apply_edit(
|
||||
requester,
|
||||
command.label(),
|
||||
edit_tracker.into_workspace_edit(),
|
||||
)
|
||||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
fn label(&self) -> &str {
|
||||
match self {
|
||||
Self::FixAll => "Fix all auto-fixable problems",
|
||||
Self::Format => "Format document",
|
||||
Self::OrganizeImports => "Format imports",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Command {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match name {
|
||||
"ruff.applyAutofix" => Self::FixAll,
|
||||
"ruff.applyFormat" => Self::Format,
|
||||
"ruff.applyOrganizeImports" => Self::OrganizeImports,
|
||||
_ => return Err(anyhow::anyhow!("Invalid command `{name}`")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_edit(
|
||||
requester: &mut client::Requester,
|
||||
label: &str,
|
||||
edit: types::WorkspaceEdit,
|
||||
) -> crate::Result<()> {
|
||||
requester.request::<req::ApplyWorkspaceEdit>(
|
||||
types::ApplyWorkspaceEditParams {
|
||||
label: Some(format!("{DIAGNOSTIC_NAME}: {label}")),
|
||||
edit,
|
||||
},
|
||||
|response| {
|
||||
if !response.applied {
|
||||
let reason = response
|
||||
.failure_reason
|
||||
.unwrap_or_else(|| String::from("unspecified reason"));
|
||||
tracing::error!("Failed to apply workspace edit: {}", reason);
|
||||
}
|
||||
Task::nothing()
|
||||
},
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue