Skip to content

Commit

Permalink
Split code_action to code_action and code_action_picker
Browse files Browse the repository at this point in the history
  • Loading branch information
John-Toohey committed Mar 14, 2024
1 parent 7693b48 commit f0dbe91
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 46 deletions.
1 change: 1 addition & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
156 changes: 110 additions & 46 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<CodeActionOrCommandItem>,
) -> Result<Callback, anyhow::Error> {
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::<CodeAction>(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<CodeActionOrCommandItem>,
) -> Result<Callback, anyhow::Error> {
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::<CodeAction>(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 {
Expand Down

0 comments on commit f0dbe91

Please sign in to comment.