Sort by fixed diagnostics/is_preffered within codeaction categories
This commit is contained in:
parent
189aa0bfcf
commit
9af7c1c9f3
1 changed files with 100 additions and 28 deletions
|
@ -1,6 +1,6 @@
|
||||||
use helix_lsp::{
|
use helix_lsp::{
|
||||||
block_on,
|
block_on,
|
||||||
lsp::{self, DiagnosticSeverity, NumberOrString},
|
lsp::{self, CodeAction, CodeActionOrCommand, DiagnosticSeverity, NumberOrString},
|
||||||
util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
|
util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
|
||||||
OffsetEncoding,
|
OffsetEncoding,
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::BTreeMap, path::PathBuf, sync::Arc};
|
use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
/// Gets the language server that is attached to a document, and
|
/// Gets the language server that is attached to a document, and
|
||||||
/// if it's not active displays a status message. Using this macro
|
/// if it's not active displays a status message. Using this macro
|
||||||
|
@ -211,7 +211,6 @@ fn sym_picker(
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let err = format!("unable to convert URI to filepath: {}", uri);
|
let err = format!("unable to convert URI to filepath: {}", uri);
|
||||||
log::error!("{}", err);
|
|
||||||
cx.editor.set_error(err);
|
cx.editor.set_error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -421,6 +420,63 @@ impl ui::menu::Item for lsp::CodeActionOrCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines the category of the `CodeAction` using the `CodeAction::kind` field.
|
||||||
|
/// Returns a number that represent these categories.
|
||||||
|
/// Categories with a lower number should be displayed first.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// While the `kind` field is defined as open ended in the LSP spec (any value may be used)
|
||||||
|
/// in practice a closed set of common values (mostly suggested in the LSP spec) are used.
|
||||||
|
/// VSCode displays each of these categories seperatly (seperated by a heading in the codeactions picker)
|
||||||
|
/// to make them easier to navigate. Helix does not display these headings to the user.
|
||||||
|
/// However it does sort code actions by their categories to achieve the same order as the VScode picker,
|
||||||
|
/// just without the headings.
|
||||||
|
///
|
||||||
|
/// The order used here is modeled after the [vscode sourcecode](https://github.com/microsoft/vscode/blob/eaec601dd69aeb4abb63b9601a6f44308c8d8c6e/src/vs/editor/contrib/codeAction/browser/codeActionWidget.ts>)
|
||||||
|
fn action_category(action: &CodeActionOrCommand) -> u32 {
|
||||||
|
if let CodeActionOrCommand::CodeAction(CodeAction {
|
||||||
|
kind: Some(kind), ..
|
||||||
|
}) = action
|
||||||
|
{
|
||||||
|
let mut components = kind.as_str().split('.');
|
||||||
|
match components.next() {
|
||||||
|
Some("quickfix") => 0,
|
||||||
|
Some("refactor") => match components.next() {
|
||||||
|
Some("extract") => 1,
|
||||||
|
Some("inline") => 2,
|
||||||
|
Some("rewrite") => 3,
|
||||||
|
Some("move") => 4,
|
||||||
|
Some("surround") => 5,
|
||||||
|
_ => 7,
|
||||||
|
},
|
||||||
|
Some("source") => 6,
|
||||||
|
_ => 7,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_prefered(action: &CodeActionOrCommand) -> bool {
|
||||||
|
matches!(
|
||||||
|
action,
|
||||||
|
CodeActionOrCommand::CodeAction(CodeAction {
|
||||||
|
is_preferred: Some(true),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool {
|
||||||
|
matches!(
|
||||||
|
action,
|
||||||
|
CodeActionOrCommand::CodeAction(CodeAction {
|
||||||
|
diagnostics: Some(diagnostics),
|
||||||
|
..
|
||||||
|
}) if !diagnostics.is_empty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn code_action(cx: &mut Context) {
|
pub fn code_action(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
||||||
|
@ -457,37 +513,53 @@ pub fn code_action(cx: &mut Context) {
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// remove disabled code actions
|
||||||
|
actions.retain(|action| {
|
||||||
|
matches!(
|
||||||
|
action,
|
||||||
|
CodeActionOrCommand::Command(_)
|
||||||
|
| CodeActionOrCommand::CodeAction(CodeAction { disabled: None, .. })
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
if actions.is_empty() {
|
if actions.is_empty() {
|
||||||
editor.set_status("No code actions available");
|
editor.set_status("No code actions available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by CodeActionKind
|
// Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
|
||||||
// this ensures that the most relevant codeactions (quickfix) show up first
|
// Many details are modeled after vscode because langauge servers are usually tested against it.
|
||||||
// while more situational commands (like refactors) show up later
|
// VScode sorts the codeaction two times:
|
||||||
// this behaviour is modeled after the behaviour of vscode (https://github.com/microsoft/vscode/blob/eaec601dd69aeb4abb63b9601a6f44308c8d8c6e/src/vs/editor/contrib/codeAction/browser/codeActionWidget.ts)
|
//
|
||||||
|
// First the codeactions that fix some diagnostics are moved to the front.
|
||||||
actions.sort_by_key(|action| match &action {
|
// If both codeactions fix some diagnostics (or both fix none) the codeaction
|
||||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
// that is marked with `is_preffered` is shown first. The codeactions are then shown in seperate
|
||||||
kind: Some(kind), ..
|
// submenus that only contain a certain category (see `action_category`) of actions.
|
||||||
}) => {
|
//
|
||||||
let mut components = kind.as_str().split('.');
|
// Below this done in in a single sorting step
|
||||||
|
actions.sort_by(|action1, action2| {
|
||||||
match components.next() {
|
// sort actions by category
|
||||||
Some("quickfix") => 0,
|
let order = action_category(action1).cmp(&action_category(action2));
|
||||||
Some("refactor") => match components.next() {
|
if order != Ordering::Equal {
|
||||||
Some("extract") => 1,
|
return order;
|
||||||
Some("inline") => 2,
|
|
||||||
Some("rewrite") => 3,
|
|
||||||
Some("move") => 4,
|
|
||||||
Some("surround") => 5,
|
|
||||||
_ => 7,
|
|
||||||
},
|
|
||||||
Some("source") => 6,
|
|
||||||
_ => 7,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => 7,
|
// within the categories sort by relevancy.
|
||||||
|
// Modeled after the `codeActionsComparator` function in vscode:
|
||||||
|
// https://github.com/microsoft/vscode/blob/eaec601dd69aeb4abb63b9601a6f44308c8d8c6e/src/vs/editor/contrib/codeAction/browser/codeAction.ts
|
||||||
|
|
||||||
|
// if one code action fixes a diagnostic but the other one doesn't show it first
|
||||||
|
let order = action_fixes_diagnostics(action1)
|
||||||
|
.cmp(&action_fixes_diagnostics(action2))
|
||||||
|
.reverse();
|
||||||
|
if order != Ordering::Equal {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if one of the codeactions is marked as prefered show it first
|
||||||
|
// otherwise keep the original LSP sorting
|
||||||
|
action_prefered(action1)
|
||||||
|
.cmp(&action_prefered(action2))
|
||||||
|
.reverse()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
|
let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
|
||||||
|
|
Loading…
Add table
Reference in a new issue