on master help sign in
sort codeaction by their kind instead of alphabetically
8673c1ePascal Kuthe
helix-term/src/commands/lsp.rs+62−22
@@ -452,43 +452,83 @@ pub fn code_action(cx: &mut Context) {
452 cx.callback(
452 cx.callback(
453 future,
453 future,
454 move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
454 move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
455− let actions = match response {
455+ let mut actions = match response {
456 Some(a) => a,
456 Some(a) => a,
457 None => return,
457 None => return,
458 };
458 };
459+
459 if actions.is_empty() {
460 if actions.is_empty() {
460 editor.set_status("No code actions available");
461 editor.set_status("No code actions available");
461 return;
462 return;
462 }
463 }
463
464
464− let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
465+ // sort by CodeActionKind
465− if event != PromptEvent::Validate {
466+ // this ensures that the most relevant codeactions (quickfix) show up first
466− return;
467+ // while more situational commands (like refactors) show up later
468+ // this behaviour is modeled after the behaviour of vscode (editor/contrib/codeAction/browser/codeActionWidget.ts)
469+
470+ let mut categories = vec![Vec::new(); 8];
471+ for action in actions.drain(..) {
472+ let category = match &action {
473+ lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
474+ kind: Some(kind),
475+ ..
476+ }) => {
477+ let mut components = kind.as_str().split('.');
478+
479+ match components.next() {
480+ Some("quickfix") => 0,
481+ Some("refactor") => match components.next() {
482+ Some("extract") => 1,
483+ Some("inline") => 2,
484+ Some("rewrite") => 3,
485+ Some("move") => 4,
486+ Some("surround") => 5,
487+ _ => 7,
488+ },
489+ Some("source") => 6,
490+ _ => 7,
491+ }
492+ }
493+ _ => 7,
494+ };
495+
496+ categories[category].push(action);
467− }
497+ }
468
498
469− // always present here
499+ for category in categories {
470− let code_action = code_action.unwrap();
500+ actions.extend(category.into_iter())
501+ }
471
502
472− match code_action {
503+ let mut picker =
473− lsp::CodeActionOrCommand::Command(command) => {
504+ ui::Menu::new(actions, false, (), move |editor, code_action, event| {
474− log::debug!("code action command: {:?}", command);
505+ if event != PromptEvent::Validate {
475− execute_lsp_command(editor, command.clone());
506+ return;
476 }
507 }
477− lsp::CodeActionOrCommand::CodeAction(code_action) => {
478− log::debug!("code action: {:?}", code_action);
479− if let Some(ref workspace_edit) = code_action.edit {
480− log::debug!("edit: {:?}", workspace_edit);
481− apply_workspace_edit(editor, offset_encoding, workspace_edit);
482− }
483
508
484− // if code action provides both edit and command first the edit
509+ // always present here
485− // should be applied and then the command
486− if let Some(command) = &code_action.command {
510+ let code_action = code_action.unwrap();
511+
512+ match code_action {
513+ lsp::CodeActionOrCommand::Command(command) => {
514+ log::debug!("code action command: {:?}", command);
487 execute_lsp_command(editor, command.clone());
515 execute_lsp_command(editor, command.clone());
488 }
516 }
517+ lsp::CodeActionOrCommand::CodeAction(code_action) => {
518+ log::debug!("code action: {:?}", code_action);
519+ if let Some(ref workspace_edit) = code_action.edit {
520+ log::debug!("edit: {:?}", workspace_edit);
521+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
522+ }
523+
524+ // if code action provides both edit and command first the edit
525+ // should be applied and then the command
526+ if let Some(command) = &code_action.command {
527+ execute_lsp_command(editor, command.clone());
528+ }
529+ }
489 }
530 }
490− }
491− });
531+ });
492 picker.move_down(); // pre-select the first item
532 picker.move_down(); // pre-select the first item
493
533
494 let popup = Popup::new("code-action", picker);
534 let popup = Popup::new("code-action", picker);
helix-term/src/ui/completion.rs+1−1
@@ -97,7 +97,7 @@ impl Completion {
97 start_offset: usize,
97 start_offset: usize,
98 trigger_offset: usize,
98 trigger_offset: usize,
99 ) -> Self {
99 ) -> Self {
100− let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
100+ let menu = Menu::new(items, true, (), move |editor: &mut Editor, item, event| {
101 fn item_to_transaction(
101 fn item_to_transaction(
102 doc: &Document,
102 doc: &Document,
103 item: &CompletionItem,
103 item: &CompletionItem,
helix-term/src/ui/menu.rs+7−2
@@ -74,6 +74,7 @@ impl<T: Item> Menu<T> {
74 // rendering)
74 // rendering)
75 pub fn new(
75 pub fn new(
76 options: Vec<T>,
76 options: Vec<T>,
77+ sort: bool,
77 editor_data: <T as Item>::Data,
78 editor_data: <T as Item>::Data,
78 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
79 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
79 ) -> Self {
80 ) -> Self {
@@ -91,8 +92,12 @@ impl<T: Item> Menu<T> {
91 recalculate: true,
92 recalculate: true,
92 };
93 };
93
94
95+ if sort {
94− // TODO: scoring on empty input should just use a fastpath
96+ // TODO: scoring on empty input should just use a fastpath
95− menu.score("");
97+ menu.score("");
98+ } else {
99+ menu.matches = (0..menu.options.len()).map(|i| (i, 0)).collect();
100+ }
96
101
97 menu
102 menu
98 }
103 }