From 9dc226be9793592262ac9fc6d8fab195f33f36b2 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 13 Jun 2024 09:32:43 +0530 Subject: [PATCH] Add supported commands in server capabilities (#11850) ## Summary This PR updates the server capabilities to include the commands that Ruff supports. This is similar to how there's a list of possible code actions supported by the server. I noticed this when I was trying to find whether Helix supported workspace commands or not based on Jane's comment (https://github.com/astral-sh/ruff/pull/11831#discussion_r1634984921) and I found the `:lsp-workspace-command` in the editor but it didn't show up anything in the picker. So, I looked at the implementation in Helix (https://github.com/helix-editor/helix/blob/9c479e6d2de3bca9dec304f9182cee2b1c0ad766/helix-term/src/commands/typed.rs#L1372-L1384) which made me realize that Ruff doesn't provide this in its capabilities. Currently, this does require `ruff` to be first in the list of language servers in the user config but that should be resolved by https://github.com/helix-editor/helix/pull/10176. So, the following config should work: ```toml [[language]] name = "python" # Ruff should come first until https://github.com/helix-editor/helix/pull/10176 is released language-servers = ["ruff", "pyright"] ``` ## Test Plan 1. Neovim's server capabilities output should include the supported commands: ``` executeCommandProvider = { commands = { "ruff.applyFormat", "ruff.applyAutofix", "ruff.applyOrganizeImports", "ruff.printDebugInformation" }, workDoneProgress = false }, ``` 2. Helix should now display the commands to pick from when `:lsp-workspace-command` is invoked: Screenshot 2024-06-13 at 08 47 14 --- crates/ruff_server/src/server.rs | 62 +++++++++++++++++++ .../server/api/requests/execute_command.rs | 49 +++------------ 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 0b1e5810ad8bf..1a778e8c00a94 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -1,6 +1,7 @@ //! Scheduling, I/O, and API endpoints. use std::num::NonZeroUsize; +use std::str::FromStr; use lsp_server as lsp; use lsp_types as types; @@ -276,6 +277,14 @@ impl Server { }, }, )), + execute_command_provider: Some(types::ExecuteCommandOptions { + commands: SupportedCommand::all() + .map(|command| command.identifier().to_string()) + .to_vec(), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: Some(false), + }, + }), hover_provider: Some(types::HoverProviderCapability::Simple(true)), notebook_document_sync: Some(types::OneOf::Left(NotebookDocumentSyncOptions { save: Some(false), @@ -354,3 +363,56 @@ impl SupportedCodeAction { .into_iter() } } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum SupportedCommand { + Debug, + Format, + FixAll, + OrganizeImports, +} + +impl SupportedCommand { + const fn label(self) -> &'static str { + match self { + Self::FixAll => "Fix all auto-fixable problems", + Self::Format => "Format document", + Self::OrganizeImports => "Format imports", + Self::Debug => "Print debug information", + } + } + + /// Returns the identifier of the command. + const fn identifier(self) -> &'static str { + match self { + SupportedCommand::Format => "ruff.applyFormat", + SupportedCommand::FixAll => "ruff.applyAutofix", + SupportedCommand::OrganizeImports => "ruff.applyOrganizeImports", + SupportedCommand::Debug => "ruff.printDebugInformation", + } + } + + /// Returns all the commands that the server currently supports. + const fn all() -> [SupportedCommand; 4] { + [ + SupportedCommand::Format, + SupportedCommand::FixAll, + SupportedCommand::OrganizeImports, + SupportedCommand::Debug, + ] + } +} + +impl FromStr for SupportedCommand { + type Err = anyhow::Error; + + fn from_str(name: &str) -> anyhow::Result { + Ok(match name { + "ruff.applyAutofix" => Self::FixAll, + "ruff.applyFormat" => Self::Format, + "ruff.applyOrganizeImports" => Self::OrganizeImports, + "ruff.printDebugInformation" => Self::Debug, + _ => return Err(anyhow::anyhow!("Invalid command `{name}`")), + }) + } +} diff --git a/crates/ruff_server/src/server/api/requests/execute_command.rs b/crates/ruff_server/src/server/api/requests/execute_command.rs index 4f27b9e756550..854961a98fcef 100644 --- a/crates/ruff_server/src/server/api/requests/execute_command.rs +++ b/crates/ruff_server/src/server/api/requests/execute_command.rs @@ -2,8 +2,8 @@ use std::str::FromStr; use crate::edit::WorkspaceEditTracker; use crate::server::api::LSPResult; -use crate::server::client; use crate::server::schedule::Task; +use crate::server::{client, SupportedCommand}; use crate::session::Session; use crate::DIAGNOSTIC_NAME; use crate::{edit::DocumentVersion, server}; @@ -11,14 +11,6 @@ use lsp_server::ErrorCode; use lsp_types::{self as types, request as req}; use serde::Deserialize; -#[derive(Debug, PartialEq)] -enum Command { - Debug, - Format, - FixAll, - OrganizeImports, -} - pub(crate) struct ExecuteCommand; #[derive(Deserialize)] @@ -38,10 +30,10 @@ impl super::SyncRequestHandler for ExecuteCommand { requester: &mut client::Requester, params: types::ExecuteCommandParams, ) -> server::Result> { - let command = - Command::from_str(¶ms.command).with_failure_code(ErrorCode::InvalidParams)?; + let command = SupportedCommand::from_str(¶ms.command) + .with_failure_code(ErrorCode::InvalidParams)?; - if command == Command::Debug { + if command == SupportedCommand::Debug { let output = debug_information(session); notifier .notify::(types::LogMessageParams { @@ -74,7 +66,7 @@ impl super::SyncRequestHandler for ExecuteCommand { return Ok(None); }; match command { - Command::FixAll => { + SupportedCommand::FixAll => { let fixes = super::code_action_resolve::fix_all_edit( snapshot.query(), snapshot.encoding(), @@ -84,13 +76,13 @@ impl super::SyncRequestHandler for ExecuteCommand { .set_fixes_for_document(fixes, snapshot.query().version()) .with_failure_code(ErrorCode::InternalError)?; } - Command::Format => { + SupportedCommand::Format => { let fixes = super::format::format_full_document(&snapshot)?; edit_tracker .set_fixes_for_document(fixes, version) .with_failure_code(ErrorCode::InternalError)?; } - Command::OrganizeImports => { + SupportedCommand::OrganizeImports => { let fixes = super::code_action_resolve::organize_imports_edit( snapshot.query(), snapshot.encoding(), @@ -100,7 +92,7 @@ impl super::SyncRequestHandler for ExecuteCommand { .set_fixes_for_document(fixes, snapshot.query().version()) .with_failure_code(ErrorCode::InternalError)?; } - Command::Debug => { + SupportedCommand::Debug => { unreachable!("The debug command should have already been handled") } } @@ -119,31 +111,6 @@ impl super::SyncRequestHandler for ExecuteCommand { } } -impl Command { - fn label(&self) -> &str { - match self { - Self::FixAll => "Fix all auto-fixable problems", - Self::Format => "Format document", - Self::OrganizeImports => "Format imports", - Self::Debug => "Print debug information", - } - } -} - -impl FromStr for Command { - type Err = anyhow::Error; - - fn from_str(name: &str) -> Result { - Ok(match name { - "ruff.applyAutofix" => Self::FixAll, - "ruff.applyFormat" => Self::Format, - "ruff.applyOrganizeImports" => Self::OrganizeImports, - "ruff.printDebugInformation" => Self::Debug, - _ => return Err(anyhow::anyhow!("Invalid command `{name}`")), - }) - } -} - fn apply_edit( requester: &mut client::Requester, label: &str,