From f0dbe91e08fceaa0b60ce329d259abc5cab7c6fa Mon Sep 17 00:00:00 2001 From: John Toohey Date: Thu, 14 Mar 2024 20:04:54 +0000 Subject: [PATCH] Split code_action to code_action and code_action_picker Fixes #3502 --- helix-term/src/commands.rs | 1 + helix-term/src/commands/lsp.rs | 156 +++++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 46 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4ac2496ebd48..6b10db580e6c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -316,6 +316,7 @@ impl MappableCommand { file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory", file_picker_in_current_directory, "Open file picker at current working directory", code_action, "Perform code action", + code_action_picker, "Perform code action in a picker", buffer_picker, "Open buffer picker", jumplist_picker, "Open jumplist picker", symbol_picker, "Open symbol picker", diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 8a24c9421d86..2261778d37be 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -569,6 +569,14 @@ fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool { } pub fn code_action(cx: &mut Context) { + code_action_inner(cx, false); +} + +pub fn code_action_picker(cx: &mut Context) { + code_action_inner(cx, true); +} + +pub fn code_action_inner(cx: &mut Context, use_picker: bool) { let (view, doc) = current!(cx.editor); let selection_range = doc.selection(view.id).primary(); @@ -676,61 +684,117 @@ pub fn code_action(cx: &mut Context) { actions.append(&mut lsp_items); } - let call = move |editor: &mut Editor, compositor: &mut Compositor| { - if actions.is_empty() { - editor.set_error("No code actions available"); + if use_picker { + code_action_inner_picker(actions) + } else { + code_action_inner_menu(actions) + } + }); +} + +fn code_action_inner_menu( + actions: Vec, +) -> Result { + let call = move |editor: &mut Editor, compositor: &mut Compositor| { + if actions.is_empty() { + editor.set_error("No code actions available"); + return; + } + let mut picker = ui::Menu::new(actions, (), move |editor, action, event| { + if event != PromptEvent::Validate { return; } - let picker = ui::Picker::new(actions, (), move |cx, lsp_item, _event| { - let Some(language_server) = cx.editor.language_server_by_id(lsp_item.language_server_id) - else { - cx.editor.set_error("Language Server disappeared"); - return; - }; - let offset_encoding = language_server.offset_encoding(); - match &lsp_item.lsp_item { - lsp::CodeActionOrCommand::Command(command) => { - log::debug!("code action command: {:?}", command); - execute_lsp_command(cx.editor, lsp_item.language_server_id, command.clone()); - } - lsp::CodeActionOrCommand::CodeAction(code_action) => { - log::debug!("code action: {:?}", code_action); - // we support lsp "codeAction/resolve" for `edit` and `command` fields - let mut resolved_code_action = None; - if code_action.edit.is_none() || code_action.command.is_none() { - if let Some(future) = - language_server.resolve_code_action(code_action.clone()) - { - if let Ok(response) = helix_lsp::block_on(future) { - if let Ok(code_action) = - serde_json::from_value::(response) - { - resolved_code_action = Some(code_action); - } - } - } - } - let resolved_code_action = - resolved_code_action.as_ref().unwrap_or(&code_action); + // always present here + let action = action.unwrap(); - if let Some(ref workspace_edit) = resolved_code_action.edit { - let _ = cx.editor.apply_workspace_edit(offset_encoding, workspace_edit); - } + code_action_handle_lsp_item(editor, &action.lsp_item, action.language_server_id); + }); + picker.move_down(); // pre-select the first item + + let margin = if editor.menu_border() { + Margin::vertical(1) + } else { + Margin::none() + }; + + let popup = Popup::new("code-action", picker) + .with_scrollbar(false) + .margin(margin); + + compositor.replace_or_push("code-action", popup); + }; + + Ok(Callback::EditorCompositor(Box::new(call))) +} + +fn code_action_inner_picker( + actions: Vec, +) -> Result { + let call = move |editor: &mut Editor, compositor: &mut Compositor| { + if actions.is_empty() { + editor.set_error("No code actions available"); + return; + } + let picker = ui::Picker::new( + actions, + (), + move |cx: &mut crate::compositor::Context, lsp_item, _event| { + code_action_handle_lsp_item( + &mut cx.editor, + &lsp_item.lsp_item, + lsp_item.language_server_id, + ); + }, + ); + compositor.push(Box::new(overlaid(picker))); + }; + Ok(Callback::EditorCompositor(Box::new(call))) +} - // 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(cx.editor, lsp_item.language_server_id, command.clone()); +fn code_action_handle_lsp_item( + editor: &mut Editor, + lsp_item: &CodeActionOrCommand, + language_server_id: usize, +) { + let Some(language_server) = editor.language_server_by_id(language_server_id) + else { + editor.set_error("Language Server disappeared"); + return; + }; + let offset_encoding = language_server.offset_encoding(); + + match lsp_item { + lsp::CodeActionOrCommand::Command(command) => { + log::debug!("code action command: {:?}", command); + execute_lsp_command(editor, language_server_id, command.clone()); + } + lsp::CodeActionOrCommand::CodeAction(code_action) => { + log::debug!("code action: {:?}", code_action); + // we support lsp "codeAction/resolve" for `edit` and `command` fields + let mut resolved_code_action = None; + if code_action.edit.is_none() || code_action.command.is_none() { + if let Some(future) = language_server.resolve_code_action(code_action.clone()) { + if let Ok(response) = helix_lsp::block_on(future) { + if let Ok(code_action) = serde_json::from_value::(response) { + resolved_code_action = Some(code_action); } } } - }); - compositor.push(Box::new(overlaid(picker))); - }; + } + let resolved_code_action = resolved_code_action.as_ref().unwrap_or(&code_action); - Ok(Callback::EditorCompositor(Box::new(call))) - }); + if let Some(ref workspace_edit) = resolved_code_action.edit { + let _ = editor.apply_workspace_edit(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, language_server_id, command.clone()); + } + } + } } impl ui::menu::Item for lsp::Command {