From b86781f2fafff334cdfe61786d5e07c991ab2fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sun, 19 Dec 2021 14:10:44 +0100 Subject: [PATCH 1/4] feat(lsp): codeAction commands --- helix-lsp/src/client.rs | 21 +++++++++++++++++---- helix-lsp/src/lib.rs | 7 +++++++ helix-term/src/application.rs | 10 ++++++++++ helix-term/src/commands.rs | 26 +++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 271fd9d59da0..6908d1dd30d7 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -188,11 +188,11 @@ impl Client { } /// Reply to a language server RPC call. - pub fn reply( + pub fn reply( &self, id: jsonrpc::Id, - result: core::result::Result, - ) -> impl Future> { + result: core::result::Result, + ) -> impl Future> where R: serde::Serialize { use jsonrpc::{Failure, Output, Success, Version}; let server_tx = self.server_tx.clone(); @@ -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,17 @@ impl Client { let response = self.request::(params).await?; Ok(response.unwrap_or_default()) } + + pub async fn command(&self, command: lsp::Command) -> anyhow::Result> { + let params = lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + let response = self.call::(params).await?; + Ok(Some(response)) + } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 15cae582b608..8fb321bcc202 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 3e0b6d59217a..7cff190b4a1d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -569,6 +569,16 @@ impl Application { } tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null))); } + MethodCall::ApplyWorkspaceEdit(params) => { + log::debug!("Received workspace/applyEdit from LSP: {:?}", params); + // TODO: handle the edits + let resp = lsp::ApplyWorkspaceEditResponse { + applied: true, + failure_reason: None, + failed_change: None, + }; + tokio::spawn(language_server.reply(id, Ok(resp))); + } } } e => unreachable!("{:?}", e), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cd566720d498..bf9c6b96f7fd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3282,7 +3282,31 @@ pub fn code_action(cx: &mut Context) { 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(ref command) = code_action.command { + log::debug!("command: {:?}", command); + let (_, doc) = current!(editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + // command is executed on the server, in most cases the server + // creates workspace edit so we just block here and wait + // for the outbound workspace edit to resolve + match block_on(language_server.command(command.clone())) { + Ok(ref edit) => { + log::debug!("command edit: {:?}", edit); + }, + Err(e) => { + log::error!("call LSP command: {:?}", e); + }, + } } } }, From 290ed43883561e868f6603caf706063dc5e275cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sun, 19 Dec 2021 17:59:37 +0100 Subject: [PATCH 2/4] Don't block on command call --- helix-lsp/src/client.rs | 5 ++--- helix-term/src/application.rs | 37 +++++++++++++++++++++++++---------- helix-term/src/commands.rs | 28 +++++++------------------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 6908d1dd30d7..7b34da174b66 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -801,7 +801,7 @@ impl Client { Ok(response.unwrap_or_default()) } - pub async fn command(&self, command: lsp::Command) -> anyhow::Result> { + pub fn command(&self, command: lsp::Command) -> impl Future> { let params = lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), @@ -810,7 +810,6 @@ impl Client { }, }; - let response = self.call::(params).await?; - Ok(Some(response)) + self.call::(params) } } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 7cff190b4a1d..0c525899ce94 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -2,7 +2,10 @@ use helix_core::{merge_toml_values, syntax}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{theme, Editor}; -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 +533,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,16 +562,38 @@ 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) => { log::debug!("Received workspace/applyEdit from LSP: {:?}", params); - // TODO: handle the edits + apply_workspace_edit( + &mut self.editor, + helix_lsp::OffsetEncoding::Utf8, + ¶ms.edit, + ); let resp = lsp::ApplyWorkspaceEditResponse { applied: true, failure_reason: None, failed_change: None, }; + 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(resp))); } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bf9c6b96f7fd..bf3ed64ea702 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -41,7 +41,7 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, StreamExt}; -use std::{collections::HashSet, num::NonZeroUsize}; +use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; use std::{fmt, future::Future}; use std::{ @@ -3287,26 +3287,12 @@ pub fn code_action(cx: &mut Context) { } // if code action provides both edit and command first the edit // should be applied and then the command - if let Some(ref command) = code_action.command { + if let Some(command) = code_action.command { log::debug!("command: {:?}", command); - let (_, doc) = current!(editor); - - let language_server = match doc.language_server() { - Some(language_server) => language_server, - None => return, - }; - - // command is executed on the server, in most cases the server - // creates workspace edit so we just block here and wait - // for the outbound workspace edit to resolve - match block_on(language_server.command(command.clone())) { - Ok(ref edit) => { - log::debug!("command edit: {:?}", edit); - }, - Err(e) => { - log::error!("call LSP command: {:?}", e); - }, - } + + tokio::spawn(async move { + language_server.command(command).await + }); } } }, @@ -3370,7 +3356,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, From b9aa9b9c8e8c673ccd67db9fe3faab131982a9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Mon, 20 Dec 2021 10:08:06 +0100 Subject: [PATCH 3/4] Fix lifetime of command execution --- helix-lsp/src/client.rs | 6 +++--- helix-term/src/application.rs | 17 ++++++++++------- helix-term/src/commands.rs | 34 ++++++++++++++++++++++++++-------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 7b34da174b66..f1de87524d6c 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -188,11 +188,11 @@ impl Client { } /// Reply to a language server RPC call. - pub fn reply( + pub fn reply( &self, id: jsonrpc::Id, - result: core::result::Result, - ) -> impl Future> where R: serde::Serialize { + result: core::result::Result, + ) -> impl Future> { use jsonrpc::{Failure, Output, Success, Version}; let server_tx = self.server_tx.clone(); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 0c525899ce94..9a46a7fe6303 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,6 +1,7 @@ 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, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs, @@ -574,17 +575,12 @@ impl Application { tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null))); } MethodCall::ApplyWorkspaceEdit(params) => { - log::debug!("Received workspace/applyEdit from LSP: {:?}", params); apply_workspace_edit( &mut self.editor, helix_lsp::OffsetEncoding::Utf8, ¶ms.edit, ); - let resp = lsp::ApplyWorkspaceEditResponse { - applied: true, - failure_reason: None, - failed_change: None, - }; + let language_server = match self.editor.language_servers.get_by_id(server_id) { Some(language_server) => language_server, @@ -594,7 +590,14 @@ impl Application { } }; - tokio::spawn(language_server.reply(id, Ok(resp))); + tokio::spawn(language_server.reply( + id, + Ok(json!(lsp::ApplyWorkspaceEditResponse { + applied: true, + failure_reason: None, + failed_change: None, + })), + )); } } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bf3ed64ea702..9d1665454632 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -41,7 +41,7 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, StreamExt}; -use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; +use std::{collections::HashSet, num::NonZeroUsize}; use std::{fmt, future::Future}; use std::{ @@ -3277,7 +3277,7 @@ 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); @@ -3285,14 +3285,11 @@ pub fn code_action(cx: &mut Context) { 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 { - log::debug!("command: {:?}", command); - - tokio::spawn(async move { - language_server.command(command).await - }); + if let Some(command) = &code_action.command { + execute_lsp_command(editor, command.clone()); } } }, @@ -3303,6 +3300,27 @@ 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); + return; + } + }); +} + pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { use lsp::ResourceOp; use std::fs; From c5ce32b242950e4f72660ea892f0121405869131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Mon, 20 Dec 2021 15:08:14 +0100 Subject: [PATCH 4/4] Fix lint issues --- helix-term/src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9d1665454632..a69d61043777 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3316,7 +3316,6 @@ pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { if let Err(e) = res { log::error!("execute LSP command: {}", e); - return; } }); }