on master help sign in
LSP code action commands (#1304)
75a8b78Matouš Dzivjak
helix-lsp/src/client.rs+13−1
@@ -202,7 +202,7 @@ impl Client {
202 Ok(result) => Output::Success(Success {
202 Ok(result) => Output::Success(Success {
203 jsonrpc: Some(Version::V2),
203 jsonrpc: Some(Version::V2),
204 id,
204 id,
205− result,
205+ result: serde_json::to_value(result)?,
206 }),
206 }),
207 Err(error) => Output::Failure(Failure {
207 Err(error) => Output::Failure(Failure {
208 jsonrpc: Some(Version::V2),
208 jsonrpc: Some(Version::V2),
@@ -800,4 +800,16 @@ impl Client {
800 let response = self.request::<lsp::request::Rename>(params).await?;
800 let response = self.request::<lsp::request::Rename>(params).await?;
801 Ok(response.unwrap_or_default())
801 Ok(response.unwrap_or_default())
802 }
802 }
803+
804+ pub fn command(&self, command: lsp::Command) -> impl Future<Output = Result<Value>> {
805+ let params = lsp::ExecuteCommandParams {
806+ command: command.command,
807+ arguments: command.arguments.unwrap_or_default(),
808+ work_done_progress_params: lsp::WorkDoneProgressParams {
809+ work_done_token: None,
810+ },
811+ };
812+
813+ self.call::<lsp::request::ExecuteCommand>(params)
814+ }
803}
815}
helix-lsp/src/lib.rs+7
@@ -203,6 +203,7 @@ pub mod util {
203#[derive(Debug, PartialEq, Clone)]
203#[derive(Debug, PartialEq, Clone)]
204pub enum MethodCall {
204pub enum MethodCall {
205 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
205 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
206+ ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
206}
207}
207
208
208impl MethodCall {
209impl MethodCall {
@@ -215,6 +216,12 @@ impl MethodCall {
215 .expect("Failed to parse WorkDoneCreate params");
216 .expect("Failed to parse WorkDoneCreate params");
216 Self::WorkDoneProgressCreate(params)
217 Self::WorkDoneProgressCreate(params)
217 }
218 }
219+ lsp::request::ApplyWorkspaceEdit::METHOD => {
220+ let params: lsp::ApplyWorkspaceEditParams = params
221+ .parse()
222+ .expect("Failed to parse ApplyWorkspaceEdit params");
223+ Self::ApplyWorkspaceEdit(params)
224+ }
218 _ => {
225 _ => {
219 log::warn!("unhandled lsp request: {}", method);
226 log::warn!("unhandled lsp request: {}", method);
220 return None;
227 return None;
helix-term/src/application.rs+39−9
@@ -1,8 +1,12 @@
1use helix_core::{merge_toml_values, syntax};
1use helix_core::{merge_toml_values, syntax};
2use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
2use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
3use helix_view::{theme, Editor};
3use helix_view::{theme, Editor};
4+use serde_json::json;
4
5
6+use crate::{
5−use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
7+ args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs,
8+ ui,
9+};
6
10
7use log::{error, warn};
11use log::{error, warn};
8
12
@@ -530,14 +534,6 @@ impl Application {
530 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
534 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
531 method, params, id, ..
535 method, params, id, ..
532 }) => {
536 }) => {
533− let language_server = match self.editor.language_servers.get_by_id(server_id) {
534− Some(language_server) => language_server,
535− None => {
536− warn!("can't find language server with id `{}`", server_id);
537− return;
538− }
539− };
540−
541 let call = match MethodCall::parse(&method, params) {
537 let call = match MethodCall::parse(&method, params) {
542 Some(call) => call,
538 Some(call) => call,
543 None => {
539 None => {
@@ -567,8 +563,42 @@ impl Application {
567 if spinner.is_stopped() {
563 if spinner.is_stopped() {
568 spinner.start();
564 spinner.start();
569 }
565 }
566+ let language_server =
567+ match self.editor.language_servers.get_by_id(server_id) {
568+ Some(language_server) => language_server,
569+ None => {
570+ warn!("can't find language server with id `{}`", server_id);
571+ return;
572+ }
573+ };
574+
570 tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
575 tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
571 }
576 }
577+ MethodCall::ApplyWorkspaceEdit(params) => {
578+ apply_workspace_edit(
579+ &mut self.editor,
580+ helix_lsp::OffsetEncoding::Utf8,
581+ &params.edit,
582+ );
583+
584+ let language_server =
585+ match self.editor.language_servers.get_by_id(server_id) {
586+ Some(language_server) => language_server,
587+ None => {
588+ warn!("can't find language server with id `{}`", server_id);
589+ return;
590+ }
591+ };
592+
593+ tokio::spawn(language_server.reply(
594+ id,
595+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
596+ applied: true,
597+ failure_reason: None,
598+ failed_change: None,
599+ })),
600+ ));
601+ }
572 }
602 }
573 }
603 }
574 e => unreachable!("{:?}", e),
604 e => unreachable!("{:?}", e),
helix-term/src/commands.rs+30−3
@@ -3277,12 +3277,19 @@ pub fn code_action(cx: &mut Context) {
3277 move |editor, code_action, _action| match code_action {
3277 move |editor, code_action, _action| match code_action {
3278 lsp::CodeActionOrCommand::Command(command) => {
3278 lsp::CodeActionOrCommand::Command(command) => {
3279 log::debug!("code action command: {:?}", command);
3279 log::debug!("code action command: {:?}", command);
3280− editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
3280+ execute_lsp_command(editor, command.clone());
3281 }
3281 }
3282 lsp::CodeActionOrCommand::CodeAction(code_action) => {
3282 lsp::CodeActionOrCommand::CodeAction(code_action) => {
3283 log::debug!("code action: {:?}", code_action);
3283 log::debug!("code action: {:?}", code_action);
3284 if let Some(ref workspace_edit) = code_action.edit {
3284 if let Some(ref workspace_edit) = code_action.edit {
3285+ log::debug!("edit: {:?}", workspace_edit);
3285− apply_workspace_edit(editor, offset_encoding, workspace_edit)
3286+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
3287+ }
3288+
3289+ // if code action provides both edit and command first the edit
3290+ // should be applied and then the command
3291+ if let Some(command) = &code_action.command {
3292+ execute_lsp_command(editor, command.clone());
3286 }
3293 }
3287 }
3294 }
3288 },
3295 },
@@ -3293,6 +3300,26 @@ pub fn code_action(cx: &mut Context) {
3293 )
3300 )
3294}
3301}
3295
3302
3303+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
3304+ let (_view, doc) = current!(editor);
3305+
3306+ let language_server = match doc.language_server() {
3307+ Some(language_server) => language_server,
3308+ None => return,
3309+ };
3310+
3311+ // the command is executed on the server and communicated back
3312+ // to the client asynchronously using workspace edits
3313+ let command_future = language_server.command(cmd);
3314+ tokio::spawn(async move {
3315+ let res = command_future.await;
3316+
3317+ if let Err(e) = res {
3318+ log::error!("execute LSP command: {}", e);
3319+ }
3320+ });
3321+}
3322+
3296pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3323pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3297 use lsp::ResourceOp;
3324 use lsp::ResourceOp;
3298 use std::fs;
3325 use std::fs;
@@ -3346,7 +3373,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3346 }
3373 }
3347}
3374}
3348
3375
3349−fn apply_workspace_edit(
3376+pub fn apply_workspace_edit(
3350 editor: &mut Editor,
3377 editor: &mut Editor,
3351 offset_encoding: OffsetEncoding,
3378 offset_encoding: OffsetEncoding,
3352 workspace_edit: &lsp::WorkspaceEdit,
3379 workspace_edit: &lsp::WorkspaceEdit,