mirror of https://github.com/helix-editor/helix
sort codeaction by their kind instead of alphabetically
parent
ce399471f0
commit
8673c1ec0c
|
@ -452,43 +452,83 @@ pub fn code_action(cx: &mut Context) {
|
||||||
cx.callback(
|
cx.callback(
|
||||||
future,
|
future,
|
||||||
move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
|
move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
|
||||||
let actions = match response {
|
let mut actions = match response {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if actions.is_empty() {
|
if actions.is_empty() {
|
||||||
editor.set_status("No code actions available");
|
editor.set_status("No code actions available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
|
// sort by CodeActionKind
|
||||||
if event != PromptEvent::Validate {
|
// this ensures that the most relevant codeactions (quickfix) show up first
|
||||||
return;
|
// while more situational commands (like refactors) show up later
|
||||||
}
|
// this behaviour is modeled after the behaviour of vscode (editor/contrib/codeAction/browser/codeActionWidget.ts)
|
||||||
|
|
||||||
// always present here
|
let mut categories = vec![Vec::new(); 8];
|
||||||
let code_action = code_action.unwrap();
|
for action in actions.drain(..) {
|
||||||
|
let category = match &action {
|
||||||
|
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||||
|
kind: Some(kind),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let mut components = kind.as_str().split('.');
|
||||||
|
|
||||||
match code_action {
|
match components.next() {
|
||||||
lsp::CodeActionOrCommand::Command(command) => {
|
Some("quickfix") => 0,
|
||||||
log::debug!("code action command: {:?}", command);
|
Some("refactor") => match components.next() {
|
||||||
execute_lsp_command(editor, command.clone());
|
Some("extract") => 1,
|
||||||
}
|
Some("inline") => 2,
|
||||||
lsp::CodeActionOrCommand::CodeAction(code_action) => {
|
Some("rewrite") => 3,
|
||||||
log::debug!("code action: {:?}", code_action);
|
Some("move") => 4,
|
||||||
if let Some(ref workspace_edit) = code_action.edit {
|
Some("surround") => 5,
|
||||||
log::debug!("edit: {:?}", workspace_edit);
|
_ => 7,
|
||||||
apply_workspace_edit(editor, offset_encoding, workspace_edit);
|
},
|
||||||
|
Some("source") => 6,
|
||||||
|
_ => 7,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_ => 7,
|
||||||
|
};
|
||||||
|
|
||||||
// if code action provides both edit and command first the edit
|
categories[category].push(action);
|
||||||
// should be applied and then the command
|
}
|
||||||
if let Some(command) = &code_action.command {
|
|
||||||
|
for category in categories {
|
||||||
|
actions.extend(category.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut picker =
|
||||||
|
ui::Menu::new(actions, false, (), move |editor, code_action, event| {
|
||||||
|
if event != PromptEvent::Validate {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always present here
|
||||||
|
let code_action = code_action.unwrap();
|
||||||
|
|
||||||
|
match code_action {
|
||||||
|
lsp::CodeActionOrCommand::Command(command) => {
|
||||||
|
log::debug!("code action command: {:?}", command);
|
||||||
execute_lsp_command(editor, command.clone());
|
execute_lsp_command(editor, command.clone());
|
||||||
}
|
}
|
||||||
|
lsp::CodeActionOrCommand::CodeAction(code_action) => {
|
||||||
|
log::debug!("code action: {:?}", code_action);
|
||||||
|
if let Some(ref workspace_edit) = code_action.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(command) = &code_action.command {
|
||||||
|
execute_lsp_command(editor, command.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
picker.move_down(); // pre-select the first item
|
picker.move_down(); // pre-select the first item
|
||||||
|
|
||||||
let popup = Popup::new("code-action", picker);
|
let popup = Popup::new("code-action", picker);
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl Completion {
|
||||||
start_offset: usize,
|
start_offset: usize,
|
||||||
trigger_offset: usize,
|
trigger_offset: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
|
let menu = Menu::new(items, true, (), move |editor: &mut Editor, item, event| {
|
||||||
fn item_to_transaction(
|
fn item_to_transaction(
|
||||||
doc: &Document,
|
doc: &Document,
|
||||||
item: &CompletionItem,
|
item: &CompletionItem,
|
||||||
|
|
|
@ -74,6 +74,7 @@ impl<T: Item> Menu<T> {
|
||||||
// rendering)
|
// rendering)
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: Vec<T>,
|
options: Vec<T>,
|
||||||
|
sort: bool,
|
||||||
editor_data: <T as Item>::Data,
|
editor_data: <T as Item>::Data,
|
||||||
callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
|
callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -91,8 +92,12 @@ impl<T: Item> Menu<T> {
|
||||||
recalculate: true,
|
recalculate: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: scoring on empty input should just use a fastpath
|
if sort {
|
||||||
menu.score("");
|
// TODO: scoring on empty input should just use a fastpath
|
||||||
|
menu.score("");
|
||||||
|
} else {
|
||||||
|
menu.matches = (0..menu.options.len()).map(|i| (i, 0)).collect();
|
||||||
|
}
|
||||||
|
|
||||||
menu
|
menu
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue