From 75a8b789d20edf8b2e1d3da75497a9936953de68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Tue, 21 Dec 2021 10:21:45 +0100 Subject: [PATCH] LSP code action commands (#1304) * feat(lsp): codeAction commands * Don't block on command call * Fix lifetime of command execution * Fix lint issues --- helix-lsp/src/client.rs | 14 +++++++++- helix-lsp/src/lib.rs | 7 +++++ helix-term/src/application.rs | 48 ++++++++++++++++++++++++++++------- helix-term/src/commands.rs | 33 +++++++++++++++++++++--- 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 271fd9d59..f1de87524 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -202,7 +202,7 @@ impl Client { Ok(result) => Output::Success(Success { jsonrpc: Some(Version::V2), id, - result, + result: serde_json::to_value(result)?, }), Err(error) => Output::Failure(Failure { jsonrpc: Some(Version::V2), @@ -800,4 +800,16 @@ impl Client { let response = self.request::(params).await?; Ok(response.unwrap_or_default()) } + + pub fn command(&self, command: lsp::Command) -> impl Future> { + let params = lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + self.call::(params) + } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 15cae582b..8fb321bcc 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -203,6 +203,7 @@ pub mod util { #[derive(Debug, PartialEq, Clone)] pub enum MethodCall { WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams), + ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams), } impl MethodCall { @@ -215,6 +216,12 @@ impl MethodCall { .expect("Failed to parse WorkDoneCreate params"); Self::WorkDoneProgressCreate(params) } + lsp::request::ApplyWorkspaceEdit::METHOD => { + let params: lsp::ApplyWorkspaceEditParams = params + .parse() + .expect("Failed to parse ApplyWorkspaceEdit params"); + Self::ApplyWorkspaceEdit(params) + } _ => { log::warn!("unhandled lsp request: {}", method); return None; diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 3e0b6d592..9a46a7fe6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,8 +1,12 @@ use helix_core::{merge_toml_values, syntax}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{theme, Editor}; +use serde_json::json; -use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; +use crate::{ + args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs, + ui, +}; use log::{error, warn}; @@ -530,14 +534,6 @@ impl Application { Call::MethodCall(helix_lsp::jsonrpc::MethodCall { method, params, id, .. }) => { - let language_server = match self.editor.language_servers.get_by_id(server_id) { - Some(language_server) => language_server, - None => { - warn!("can't find language server with id `{}`", server_id); - return; - } - }; - let call = match MethodCall::parse(&method, params) { Some(call) => call, None => { @@ -567,8 +563,42 @@ impl Application { if spinner.is_stopped() { spinner.start(); } + let language_server = + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + }; + tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null))); } + MethodCall::ApplyWorkspaceEdit(params) => { + apply_workspace_edit( + &mut self.editor, + helix_lsp::OffsetEncoding::Utf8, + ¶ms.edit, + ); + + let language_server = + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + }; + + tokio::spawn(language_server.reply( + id, + Ok(json!(lsp::ApplyWorkspaceEditResponse { + applied: true, + failure_reason: None, + failed_change: None, + })), + )); + } } } e => unreachable!("{:?}", e), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 297b49a15..ee6a59894 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3277,12 +3277,19 @@ pub fn code_action(cx: &mut Context) { move |editor, code_action, _action| match code_action { lsp::CodeActionOrCommand::Command(command) => { log::debug!("code action command: {:?}", command); - editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + execute_lsp_command(editor, command.clone()); } lsp::CodeActionOrCommand::CodeAction(code_action) => { log::debug!("code action: {:?}", code_action); if let Some(ref workspace_edit) = code_action.edit { - apply_workspace_edit(editor, offset_encoding, workspace_edit) + log::debug!("edit: {:?}", workspace_edit); + apply_workspace_edit(editor, offset_encoding, workspace_edit); + } + + // if code action provides both edit and command first the edit + // should be applied and then the command + if let Some(command) = &code_action.command { + execute_lsp_command(editor, command.clone()); } } }, @@ -3293,6 +3300,26 @@ pub fn code_action(cx: &mut Context) { ) } +pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { + let (_view, doc) = current!(editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + // the command is executed on the server and communicated back + // to the client asynchronously using workspace edits + let command_future = language_server.command(cmd); + tokio::spawn(async move { + let res = command_future.await; + + if let Err(e) = res { + log::error!("execute LSP command: {}", e); + } + }); +} + pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { use lsp::ResourceOp; use std::fs; @@ -3346,7 +3373,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { } } -fn apply_workspace_edit( +pub fn apply_workspace_edit( editor: &mut Editor, offset_encoding: OffsetEncoding, workspace_edit: &lsp::WorkspaceEdit,