Skip to content

Commit f0dbe91

Browse files
committed
Split code_action to code_action and code_action_picker
Fixes helix-editor#3502
1 parent 7693b48 commit f0dbe91

File tree

2 files changed

+111
-46
lines changed

2 files changed

+111
-46
lines changed

helix-term/src/commands.rs

+1
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ impl MappableCommand {
316316
file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory",
317317
file_picker_in_current_directory, "Open file picker at current working directory",
318318
code_action, "Perform code action",
319+
code_action_picker, "Perform code action in a picker",
319320
buffer_picker, "Open buffer picker",
320321
jumplist_picker, "Open jumplist picker",
321322
symbol_picker, "Open symbol picker",

helix-term/src/commands/lsp.rs

+110-46
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,14 @@ fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool {
569569
}
570570

571571
pub fn code_action(cx: &mut Context) {
572+
code_action_inner(cx, false);
573+
}
574+
575+
pub fn code_action_picker(cx: &mut Context) {
576+
code_action_inner(cx, true);
577+
}
578+
579+
pub fn code_action_inner(cx: &mut Context, use_picker: bool) {
572580
let (view, doc) = current!(cx.editor);
573581

574582
let selection_range = doc.selection(view.id).primary();
@@ -676,61 +684,117 @@ pub fn code_action(cx: &mut Context) {
676684
actions.append(&mut lsp_items);
677685
}
678686

679-
let call = move |editor: &mut Editor, compositor: &mut Compositor| {
680-
if actions.is_empty() {
681-
editor.set_error("No code actions available");
687+
if use_picker {
688+
code_action_inner_picker(actions)
689+
} else {
690+
code_action_inner_menu(actions)
691+
}
692+
});
693+
}
694+
695+
fn code_action_inner_menu(
696+
actions: Vec<CodeActionOrCommandItem>,
697+
) -> Result<Callback, anyhow::Error> {
698+
let call = move |editor: &mut Editor, compositor: &mut Compositor| {
699+
if actions.is_empty() {
700+
editor.set_error("No code actions available");
701+
return;
702+
}
703+
let mut picker = ui::Menu::new(actions, (), move |editor, action, event| {
704+
if event != PromptEvent::Validate {
682705
return;
683706
}
684-
let picker = ui::Picker::new(actions, (), move |cx, lsp_item, _event| {
685-
let Some(language_server) = cx.editor.language_server_by_id(lsp_item.language_server_id)
686-
else {
687-
cx.editor.set_error("Language Server disappeared");
688-
return;
689-
};
690-
let offset_encoding = language_server.offset_encoding();
691707

692-
match &lsp_item.lsp_item {
693-
lsp::CodeActionOrCommand::Command(command) => {
694-
log::debug!("code action command: {:?}", command);
695-
execute_lsp_command(cx.editor, lsp_item.language_server_id, command.clone());
696-
}
697-
lsp::CodeActionOrCommand::CodeAction(code_action) => {
698-
log::debug!("code action: {:?}", code_action);
699-
// we support lsp "codeAction/resolve" for `edit` and `command` fields
700-
let mut resolved_code_action = None;
701-
if code_action.edit.is_none() || code_action.command.is_none() {
702-
if let Some(future) =
703-
language_server.resolve_code_action(code_action.clone())
704-
{
705-
if let Ok(response) = helix_lsp::block_on(future) {
706-
if let Ok(code_action) =
707-
serde_json::from_value::<CodeAction>(response)
708-
{
709-
resolved_code_action = Some(code_action);
710-
}
711-
}
712-
}
713-
}
714-
let resolved_code_action =
715-
resolved_code_action.as_ref().unwrap_or(&code_action);
708+
// always present here
709+
let action = action.unwrap();
716710

717-
if let Some(ref workspace_edit) = resolved_code_action.edit {
718-
let _ = cx.editor.apply_workspace_edit(offset_encoding, workspace_edit);
719-
}
711+
code_action_handle_lsp_item(editor, &action.lsp_item, action.language_server_id);
712+
});
713+
picker.move_down(); // pre-select the first item
714+
715+
let margin = if editor.menu_border() {
716+
Margin::vertical(1)
717+
} else {
718+
Margin::none()
719+
};
720+
721+
let popup = Popup::new("code-action", picker)
722+
.with_scrollbar(false)
723+
.margin(margin);
724+
725+
compositor.replace_or_push("code-action", popup);
726+
};
727+
728+
Ok(Callback::EditorCompositor(Box::new(call)))
729+
}
730+
731+
fn code_action_inner_picker(
732+
actions: Vec<CodeActionOrCommandItem>,
733+
) -> Result<Callback, anyhow::Error> {
734+
let call = move |editor: &mut Editor, compositor: &mut Compositor| {
735+
if actions.is_empty() {
736+
editor.set_error("No code actions available");
737+
return;
738+
}
739+
let picker = ui::Picker::new(
740+
actions,
741+
(),
742+
move |cx: &mut crate::compositor::Context, lsp_item, _event| {
743+
code_action_handle_lsp_item(
744+
&mut cx.editor,
745+
&lsp_item.lsp_item,
746+
lsp_item.language_server_id,
747+
);
748+
},
749+
);
750+
compositor.push(Box::new(overlaid(picker)));
751+
};
752+
Ok(Callback::EditorCompositor(Box::new(call)))
753+
}
720754

721-
// if code action provides both edit and command first the edit
722-
// should be applied and then the command
723-
if let Some(command) = &code_action.command {
724-
execute_lsp_command(cx.editor, lsp_item.language_server_id, command.clone());
755+
fn code_action_handle_lsp_item(
756+
editor: &mut Editor,
757+
lsp_item: &CodeActionOrCommand,
758+
language_server_id: usize,
759+
) {
760+
let Some(language_server) = editor.language_server_by_id(language_server_id)
761+
else {
762+
editor.set_error("Language Server disappeared");
763+
return;
764+
};
765+
let offset_encoding = language_server.offset_encoding();
766+
767+
match lsp_item {
768+
lsp::CodeActionOrCommand::Command(command) => {
769+
log::debug!("code action command: {:?}", command);
770+
execute_lsp_command(editor, language_server_id, command.clone());
771+
}
772+
lsp::CodeActionOrCommand::CodeAction(code_action) => {
773+
log::debug!("code action: {:?}", code_action);
774+
// we support lsp "codeAction/resolve" for `edit` and `command` fields
775+
let mut resolved_code_action = None;
776+
if code_action.edit.is_none() || code_action.command.is_none() {
777+
if let Some(future) = language_server.resolve_code_action(code_action.clone()) {
778+
if let Ok(response) = helix_lsp::block_on(future) {
779+
if let Ok(code_action) = serde_json::from_value::<CodeAction>(response) {
780+
resolved_code_action = Some(code_action);
725781
}
726782
}
727783
}
728-
});
729-
compositor.push(Box::new(overlaid(picker)));
730-
};
784+
}
785+
let resolved_code_action = resolved_code_action.as_ref().unwrap_or(&code_action);
731786

732-
Ok(Callback::EditorCompositor(Box::new(call)))
733-
});
787+
if let Some(ref workspace_edit) = resolved_code_action.edit {
788+
let _ = editor.apply_workspace_edit(offset_encoding, workspace_edit);
789+
}
790+
791+
// if code action provides both edit and command first the edit
792+
// should be applied and then the command
793+
if let Some(command) = &code_action.command {
794+
execute_lsp_command(editor, language_server_id, command.clone());
795+
}
796+
}
797+
}
734798
}
735799

736800
impl ui::menu::Item for lsp::Command {

0 commit comments

Comments
 (0)