mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 21:05:02 +00:00
feat: always prefer postfix snippets if there's exact textual match
Note that, while we don't currently have a fuzzy-matching score, it makes sense to special-case postfix templates -- it's very annoying when `.not()` gets sorted before `.not`. We might want to move this infra to fuzzy matching, once we have that!
This commit is contained in:
parent
030217d573
commit
fbb9d69758
4 changed files with 105 additions and 23 deletions
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
context::CompletionContext,
|
context::CompletionContext,
|
||||||
item::{Builder, CompletionKind},
|
item::{Builder, CompletionKind},
|
||||||
patterns::ImmediateLocation,
|
patterns::ImmediateLocation,
|
||||||
CompletionItem, CompletionItemKind, Completions,
|
CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
|
@ -299,6 +299,12 @@ fn postfix_snippet(
|
||||||
};
|
};
|
||||||
let mut item = CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label);
|
let mut item = CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label);
|
||||||
item.detail(detail).kind(CompletionItemKind::Snippet).snippet_edit(cap, edit);
|
item.detail(detail).kind(CompletionItemKind::Snippet).snippet_edit(cap, edit);
|
||||||
|
if ctx.original_token.text() == label {
|
||||||
|
let mut relevance = CompletionRelevance::default();
|
||||||
|
relevance.exact_postfix_snippet_match = true;
|
||||||
|
item.set_relevance(relevance);
|
||||||
|
}
|
||||||
|
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,15 @@ pub struct CompletionRelevance {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub is_local: bool,
|
pub is_local: bool,
|
||||||
|
/// This is set in cases like these:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// (a > b).not$0
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Basically, we want to guarantee that postfix snippets always takes
|
||||||
|
/// precedence over everything else.
|
||||||
|
pub exact_postfix_snippet_match: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
@ -194,7 +203,9 @@ impl CompletionRelevance {
|
||||||
if self.is_local {
|
if self.is_local {
|
||||||
score += 1;
|
score += 1;
|
||||||
}
|
}
|
||||||
|
if self.exact_postfix_snippet_match {
|
||||||
|
score += 100;
|
||||||
|
}
|
||||||
score
|
score
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +609,13 @@ mod tests {
|
||||||
exact_name_match: true,
|
exact_name_match: true,
|
||||||
type_match: Some(CompletionRelevanceTypeMatch::Exact),
|
type_match: Some(CompletionRelevanceTypeMatch::Exact),
|
||||||
is_local: true,
|
is_local: true,
|
||||||
|
..CompletionRelevance::default()
|
||||||
|
}],
|
||||||
|
vec![CompletionRelevance {
|
||||||
|
exact_name_match: false,
|
||||||
|
type_match: None,
|
||||||
|
is_local: false,
|
||||||
|
exact_postfix_snippet_match: true,
|
||||||
}],
|
}],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -339,32 +339,22 @@ mod tests {
|
||||||
CompletionKind, CompletionRelevance,
|
CompletionKind, CompletionRelevance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = do_completion(ra_fixture, CompletionKind::Reference);
|
let actual = do_completion(ra_fixture, CompletionKind::Reference);
|
||||||
expect.assert_debug_eq(&actual);
|
expect.assert_debug_eq(&actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn check_relevance(ra_fixture: &str, expect: Expect) {
|
fn check_relevance(ra_fixture: &str, expect: Expect) {
|
||||||
fn display_relevance(relevance: CompletionRelevance) -> String {
|
check_relevance_for_kinds(&[CompletionKind::Reference], ra_fixture, expect)
|
||||||
let relevance_factors = vec![
|
}
|
||||||
(relevance.type_match == Some(CompletionRelevanceTypeMatch::Exact), "type"),
|
|
||||||
(
|
|
||||||
relevance.type_match == Some(CompletionRelevanceTypeMatch::CouldUnify),
|
|
||||||
"type_could_unify",
|
|
||||||
),
|
|
||||||
(relevance.exact_name_match, "name"),
|
|
||||||
(relevance.is_local, "local"),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(cond, desc)| if cond { Some(desc) } else { None })
|
|
||||||
.join("+");
|
|
||||||
|
|
||||||
format!("[{}]", relevance_factors)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn check_relevance_for_kinds(kinds: &[CompletionKind], ra_fixture: &str, expect: Expect) {
|
||||||
let actual = get_all_items(TEST_CONFIG, ra_fixture)
|
let actual = get_all_items(TEST_CONFIG, ra_fixture)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| it.completion_kind == CompletionKind::Reference)
|
.filter(|it| kinds.contains(&it.completion_kind))
|
||||||
.flat_map(|it| {
|
.flat_map(|it| {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
|
||||||
|
@ -384,6 +374,24 @@ mod tests {
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
expect.assert_eq(&actual);
|
expect.assert_eq(&actual);
|
||||||
|
|
||||||
|
fn display_relevance(relevance: CompletionRelevance) -> String {
|
||||||
|
let relevance_factors = vec![
|
||||||
|
(relevance.type_match == Some(CompletionRelevanceTypeMatch::Exact), "type"),
|
||||||
|
(
|
||||||
|
relevance.type_match == Some(CompletionRelevanceTypeMatch::CouldUnify),
|
||||||
|
"type_could_unify",
|
||||||
|
),
|
||||||
|
(relevance.exact_name_match, "name"),
|
||||||
|
(relevance.is_local, "local"),
|
||||||
|
(relevance.exact_postfix_snippet_match, "snippet"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(cond, desc)| if cond { Some(desc) } else { None })
|
||||||
|
.join("+");
|
||||||
|
|
||||||
|
format!("[{}]", relevance_factors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -528,6 +536,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||||
Exact,
|
Exact,
|
||||||
),
|
),
|
||||||
is_local: false,
|
is_local: false,
|
||||||
|
exact_postfix_snippet_match: false,
|
||||||
},
|
},
|
||||||
trigger_call_info: true,
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
|
@ -556,6 +565,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||||
Exact,
|
Exact,
|
||||||
),
|
),
|
||||||
is_local: false,
|
is_local: false,
|
||||||
|
exact_postfix_snippet_match: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CompletionItem {
|
CompletionItem {
|
||||||
|
@ -649,6 +659,7 @@ fn foo() { A { the$0 } }
|
||||||
CouldUnify,
|
CouldUnify,
|
||||||
),
|
),
|
||||||
is_local: false,
|
is_local: false,
|
||||||
|
exact_postfix_snippet_match: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -1339,4 +1350,44 @@ fn foo() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn postfix_completion_relevance() {
|
||||||
|
check_relevance_for_kinds(
|
||||||
|
&[CompletionKind::Postfix, CompletionKind::Magic],
|
||||||
|
r#"
|
||||||
|
mod ops {
|
||||||
|
pub trait Not {
|
||||||
|
type Output;
|
||||||
|
fn not(self) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Not for bool {
|
||||||
|
type Output = bool;
|
||||||
|
fn not(self) -> bool { if self { false } else { true }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _: bool = (9 > 2).not$0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
sn if []
|
||||||
|
sn while []
|
||||||
|
sn not [snippet]
|
||||||
|
sn ref []
|
||||||
|
sn refm []
|
||||||
|
sn match []
|
||||||
|
sn box []
|
||||||
|
sn ok []
|
||||||
|
sn err []
|
||||||
|
sn some []
|
||||||
|
sn dbg []
|
||||||
|
sn dbgr []
|
||||||
|
sn call []
|
||||||
|
me not() (ops::Not) [type_could_unify]
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,7 @@ pub(crate) fn completion_items(
|
||||||
tdpp: lsp_types::TextDocumentPositionParams,
|
tdpp: lsp_types::TextDocumentPositionParams,
|
||||||
items: Vec<CompletionItem>,
|
items: Vec<CompletionItem>,
|
||||||
) -> Vec<lsp_types::CompletionItem> {
|
) -> Vec<lsp_types::CompletionItem> {
|
||||||
|
let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default();
|
||||||
let mut res = Vec::with_capacity(items.len());
|
let mut res = Vec::with_capacity(items.len());
|
||||||
for item in items {
|
for item in items {
|
||||||
completion_item(
|
completion_item(
|
||||||
|
@ -203,6 +204,7 @@ pub(crate) fn completion_items(
|
||||||
enable_imports_on_the_fly,
|
enable_imports_on_the_fly,
|
||||||
line_index,
|
line_index,
|
||||||
&tdpp,
|
&tdpp,
|
||||||
|
max_relevance,
|
||||||
item,
|
item,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -215,6 +217,7 @@ fn completion_item(
|
||||||
enable_imports_on_the_fly: bool,
|
enable_imports_on_the_fly: bool,
|
||||||
line_index: &LineIndex,
|
line_index: &LineIndex,
|
||||||
tdpp: &lsp_types::TextDocumentPositionParams,
|
tdpp: &lsp_types::TextDocumentPositionParams,
|
||||||
|
max_relevance: u32,
|
||||||
item: CompletionItem,
|
item: CompletionItem,
|
||||||
) {
|
) {
|
||||||
let mut additional_text_edits = Vec::new();
|
let mut additional_text_edits = Vec::new();
|
||||||
|
@ -259,7 +262,7 @@ fn completion_item(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
set_score(&mut lsp_item, item.relevance());
|
set_score(&mut lsp_item, max_relevance, item.relevance());
|
||||||
|
|
||||||
if item.deprecated() {
|
if item.deprecated() {
|
||||||
lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
|
lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
|
||||||
|
@ -288,7 +291,7 @@ fn completion_item(
|
||||||
|
|
||||||
if let Some((mutability, relevance)) = item.ref_match() {
|
if let Some((mutability, relevance)) = item.ref_match() {
|
||||||
let mut lsp_item_with_ref = lsp_item.clone();
|
let mut lsp_item_with_ref = lsp_item.clone();
|
||||||
set_score(&mut lsp_item_with_ref, relevance);
|
set_score(&mut lsp_item_with_ref, max_relevance, relevance);
|
||||||
lsp_item_with_ref.label =
|
lsp_item_with_ref.label =
|
||||||
format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
|
format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
|
||||||
if let Some(it) = &mut lsp_item_with_ref.text_edit {
|
if let Some(it) = &mut lsp_item_with_ref.text_edit {
|
||||||
|
@ -304,8 +307,12 @@ fn completion_item(
|
||||||
|
|
||||||
acc.push(lsp_item);
|
acc.push(lsp_item);
|
||||||
|
|
||||||
fn set_score(res: &mut lsp_types::CompletionItem, relevance: CompletionRelevance) {
|
fn set_score(
|
||||||
if relevance.is_relevant() {
|
res: &mut lsp_types::CompletionItem,
|
||||||
|
max_relevance: u32,
|
||||||
|
relevance: CompletionRelevance,
|
||||||
|
) {
|
||||||
|
if relevance.is_relevant() && relevance.score() == max_relevance {
|
||||||
res.preselect = Some(true);
|
res.preselect = Some(true);
|
||||||
}
|
}
|
||||||
// The relevance needs to be inverted to come up with a sort score
|
// The relevance needs to be inverted to come up with a sort score
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue