add completion relevance score

This commit is contained in:
Josh Mcguigan 2021-03-11 19:11:14 -08:00
parent c0e9530fd0
commit 3679821eea
5 changed files with 82 additions and 37 deletions

View File

@ -87,8 +87,8 @@ pub use crate::{
pub use hir::{Documentation, Semantics}; pub use hir::{Documentation, Semantics};
pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind};
pub use ide_completion::{ pub use ide_completion::{
CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionScore,
InsertTextFormat, ImportEdit, InsertTextFormat,
}; };
pub use ide_db::{ pub use ide_db::{
base_db::{ base_db::{

View File

@ -70,7 +70,7 @@ pub struct CompletionItem {
/// Note that Relevance ignores fuzzy match score. We compute Relevance for /// Note that Relevance ignores fuzzy match score. We compute Relevance for
/// all possible items, and then separately build an ordered completion list /// all possible items, and then separately build an ordered completion list
/// based on relevance and fuzzy matching with the already typed identifier. /// based on relevance and fuzzy matching with the already typed identifier.
relevance: Relevance, relevance: CompletionRelevance,
/// Indicates that a reference or mutable reference to this variable is a /// Indicates that a reference or mutable reference to this variable is a
/// possible match. /// possible match.
@ -107,9 +107,11 @@ impl fmt::Debug for CompletionItem {
if self.deprecated { if self.deprecated {
s.field("deprecated", &true); s.field("deprecated", &true);
} }
if self.relevance.is_relevant() {
if self.relevance != CompletionRelevance::default() {
s.field("relevance", &self.relevance); s.field("relevance", &self.relevance);
} }
if let Some(mutability) = &self.ref_match { if let Some(mutability) = &self.ref_match {
s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref())); s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref()));
} }
@ -129,7 +131,7 @@ pub enum CompletionScore {
} }
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
pub struct Relevance { pub struct CompletionRelevance {
/// This is set in cases like these: /// This is set in cases like these:
/// ///
/// ``` /// ```
@ -152,9 +154,34 @@ pub struct Relevance {
pub exact_type_match: bool, pub exact_type_match: bool,
} }
impl Relevance { impl CompletionRelevance {
/// Provides a relevance score. Higher values are more relevant.
///
/// The absolute value of the relevance score is not meaningful, for
/// example a value of 0 doesn't mean "not relevant", rather
/// it means "least relevant". The score value should only be used
/// for relative ordering.
///
/// See is_relevant if you need to make some judgement about score
/// in an absolute sense.
pub fn score(&self) -> u8 {
let mut score = 0;
if self.exact_name_match {
score += 1;
}
if self.exact_type_match {
score += 1;
}
score
}
/// Returns true when the score for this threshold is above
/// some threshold such that we think it is especially likely
/// to be relevant.
pub fn is_relevant(&self) -> bool { pub fn is_relevant(&self) -> bool {
self != &Relevance::default() self.score() > 0
} }
} }
@ -249,7 +276,7 @@ impl CompletionItem {
text_edit: None, text_edit: None,
deprecated: false, deprecated: false,
trigger_call_info: None, trigger_call_info: None,
relevance: Relevance::default(), relevance: CompletionRelevance::default(),
ref_match: None, ref_match: None,
import_to_add: None, import_to_add: None,
} }
@ -292,7 +319,7 @@ impl CompletionItem {
self.deprecated self.deprecated
} }
pub fn relevance(&self) -> Relevance { pub fn relevance(&self) -> CompletionRelevance {
self.relevance self.relevance
} }
@ -300,8 +327,14 @@ impl CompletionItem {
self.trigger_call_info self.trigger_call_info
} }
pub fn ref_match(&self) -> Option<Mutability> { pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> {
self.ref_match // Relevance of the ref match should be the same as the original
// match, but with exact type match set because self.ref_match
// is only set if there is an exact type match.
let mut relevance = self.relevance;
relevance.exact_type_match = true;
self.ref_match.map(|mutability| (mutability, relevance))
} }
pub fn import_to_add(&self) -> Option<&ImportEdit> { pub fn import_to_add(&self) -> Option<&ImportEdit> {
@ -349,7 +382,7 @@ pub(crate) struct Builder {
text_edit: Option<TextEdit>, text_edit: Option<TextEdit>,
deprecated: bool, deprecated: bool,
trigger_call_info: Option<bool>, trigger_call_info: Option<bool>,
relevance: Relevance, relevance: CompletionRelevance,
ref_match: Option<Mutability>, ref_match: Option<Mutability>,
} }
@ -457,7 +490,7 @@ impl Builder {
self.deprecated = deprecated; self.deprecated = deprecated;
self self
} }
pub(crate) fn set_relevance(&mut self, relevance: Relevance) -> &mut Builder { pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
self.relevance = relevance; self.relevance = relevance;
self self
} }

View File

@ -24,8 +24,8 @@ use crate::{completions::Completions, context::CompletionContext, item::Completi
pub use crate::{ pub use crate::{
config::CompletionConfig, config::CompletionConfig,
item::{ item::{
CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionScore, ImportEdit,
Relevance, InsertTextFormat,
}, },
}; };

View File

@ -20,7 +20,7 @@ use ide_db::{
use syntax::TextRange; use syntax::TextRange;
use crate::{ use crate::{
item::{ImportEdit, Relevance}, item::{CompletionRelevance, ImportEdit},
CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
}; };
@ -322,9 +322,9 @@ impl<'a> Render<'a> {
} }
} }
fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option<Relevance> { fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionRelevance> {
let (expected_name, expected_type) = ctx.expected_name_and_type()?; let (expected_name, expected_type) = ctx.expected_name_and_type()?;
let mut res = Relevance::default(); let mut res = CompletionRelevance::default();
res.exact_type_match = ty == &expected_type; res.exact_type_match = ty == &expected_type;
res.exact_name_match = name == &expected_name; res.exact_name_match = name == &expected_name;
Some(res) Some(res)
@ -338,7 +338,7 @@ mod tests {
use crate::{ use crate::{
test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG},
CompletionKind, Relevance, CompletionKind, CompletionRelevance,
}; };
fn check(ra_fixture: &str, expect: Expect) { fn check(ra_fixture: &str, expect: Expect) {
@ -347,12 +347,14 @@ mod tests {
} }
fn check_relevance(ra_fixture: &str, expect: Expect) { fn check_relevance(ra_fixture: &str, expect: Expect) {
fn display_relevance(relevance: Relevance) -> &'static str { fn display_relevance(relevance: CompletionRelevance) -> &'static str {
match relevance { match relevance {
Relevance { exact_type_match: true, exact_name_match: true } => "[type+name]", CompletionRelevance { exact_type_match: true, exact_name_match: true } => {
Relevance { exact_type_match: true, exact_name_match: false } => "[type]", "[type+name]"
Relevance { exact_type_match: false, exact_name_match: true } => "[name]", }
Relevance { exact_type_match: false, exact_name_match: false } => "[]", CompletionRelevance { exact_type_match: true, exact_name_match: false } => "[type]",
CompletionRelevance { exact_type_match: false, exact_name_match: true } => "[name]",
CompletionRelevance { exact_type_match: false, exact_name_match: false } => "[]",
} }
} }
@ -975,7 +977,7 @@ fn main() {
Local, Local,
), ),
detail: "S", detail: "S",
relevance: Relevance { relevance: CompletionRelevance {
exact_name_match: true, exact_name_match: true,
exact_type_match: false, exact_type_match: false,
}, },

View File

@ -6,9 +6,10 @@ use std::{
use ide::{ use ide::{
Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind,
HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, Markup, NavigationTarget, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat,
ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange,
TextEdit, TextRange, TextSize,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use itertools::Itertools; use itertools::Itertools;
@ -213,12 +214,22 @@ pub(crate) fn completion_item(
..Default::default() ..Default::default()
}; };
if item.relevance().is_relevant() { fn set_score(res: &mut lsp_types::CompletionItem, relevance: CompletionRelevance) {
lsp_item.preselect = Some(true); if relevance.is_relevant() {
// HACK: sort preselect items first res.preselect = Some(true);
lsp_item.sort_text = Some(format!(" {}", item.label())); }
// The relevance needs to be inverted to come up with a sort score
// because the client will sort ascending.
let sort_score = relevance.score() ^ 0xFF;
// Zero pad the string to ensure values are sorted numerically
// even though the client is sorting alphabetically. Three
// characters is enough to fit the largest u8, which is the
// type of the relevance score.
res.sort_text = Some(format!("{:03}", sort_score));
} }
set_score(&mut lsp_item, 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])
} }
@ -228,10 +239,9 @@ pub(crate) fn completion_item(
} }
let mut res = match item.ref_match() { let mut res = match item.ref_match() {
Some(mutability) => { Some((mutability, relevance)) => {
let mut lsp_item_with_ref = lsp_item.clone(); let mut lsp_item_with_ref = lsp_item.clone();
lsp_item.preselect = Some(true); set_score(&mut lsp_item_with_ref, relevance);
lsp_item.sort_text = Some(format!(" {}", item.label()));
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(lsp_types::CompletionTextEdit::Edit(it)) = &mut lsp_item_with_ref.text_edit if let Some(lsp_types::CompletionTextEdit::Edit(it)) = &mut lsp_item_with_ref.text_edit
@ -1107,13 +1117,13 @@ mod tests {
( (
"&arg", "&arg",
Some( Some(
" arg", "253",
), ),
), ),
( (
"arg", "arg",
Some( Some(
" arg", "254",
), ),
), ),
] ]