diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index d1a250d4874..16302eb2fb6 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -87,8 +87,8 @@ macro_rules! eprintln { pub use hir::{Documentation, Semantics}; pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; pub use ide_completion::{ - CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, - InsertTextFormat, + CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionScore, + ImportEdit, InsertTextFormat, }; pub use ide_db::{ base_db::{ diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index cf1aaa13149..9a4dc915c96 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs @@ -70,7 +70,7 @@ pub struct CompletionItem { /// Note that Relevance ignores fuzzy match score. We compute Relevance for /// all possible items, and then separately build an ordered completion list /// 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 /// possible match. @@ -107,9 +107,11 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.deprecated { s.field("deprecated", &true); } - if self.relevance.is_relevant() { + + if self.relevance != CompletionRelevance::default() { s.field("relevance", &self.relevance); } + if let Some(mutability) = &self.ref_match { 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)] -pub struct Relevance { +pub struct CompletionRelevance { /// This is set in cases like these: /// /// ``` @@ -152,9 +154,34 @@ pub struct Relevance { 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 { - self != &Relevance::default() + self.score() > 0 } } @@ -249,7 +276,7 @@ pub(crate) fn new( text_edit: None, deprecated: false, trigger_call_info: None, - relevance: Relevance::default(), + relevance: CompletionRelevance::default(), ref_match: None, import_to_add: None, } @@ -292,7 +319,7 @@ pub fn deprecated(&self) -> bool { self.deprecated } - pub fn relevance(&self) -> Relevance { + pub fn relevance(&self) -> CompletionRelevance { self.relevance } @@ -300,8 +327,14 @@ pub fn trigger_call_info(&self) -> bool { self.trigger_call_info } - pub fn ref_match(&self) -> Option { - self.ref_match + pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> { + // 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> { @@ -349,7 +382,7 @@ pub(crate) struct Builder { text_edit: Option, deprecated: bool, trigger_call_info: Option, - relevance: Relevance, + relevance: CompletionRelevance, ref_match: Option, } @@ -457,7 +490,7 @@ pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder { self.deprecated = deprecated; 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 } diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index d46f521a02a..21e489755ad 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -24,8 +24,8 @@ pub use crate::{ config::CompletionConfig, item::{ - CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat, - Relevance, + CompletionItem, CompletionItemKind, CompletionRelevance, CompletionScore, ImportEdit, + InsertTextFormat, }, }; diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index f7f9084d90e..db31896e532 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -20,7 +20,7 @@ use syntax::TextRange; use crate::{ - item::{ImportEdit, Relevance}, + item::{CompletionRelevance, ImportEdit}, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, }; @@ -322,9 +322,9 @@ fn is_deprecated(&self, resolution: &ScopeDef) -> bool { } } -fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option { +fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option { 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_name_match = name == &expected_name; Some(res) @@ -338,7 +338,7 @@ mod tests { use crate::{ test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, - CompletionKind, Relevance, + CompletionKind, CompletionRelevance, }; fn check(ra_fixture: &str, expect: Expect) { @@ -347,12 +347,14 @@ fn check(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 { - Relevance { exact_type_match: true, exact_name_match: true } => "[type+name]", - Relevance { exact_type_match: true, exact_name_match: false } => "[type]", - 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: true } => { + "[type+name]" + } + 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, ), detail: "S", - relevance: Relevance { + relevance: CompletionRelevance { exact_name_match: true, exact_type_match: false, }, diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index a9846fa7055..a467bc685cc 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -6,9 +6,10 @@ use ide::{ Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, - Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, - HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, Markup, NavigationTarget, - ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize, + CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, + Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, + Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, + TextEdit, TextRange, TextSize, }; use ide_db::SymbolKind; use itertools::Itertools; @@ -213,12 +214,22 @@ pub(crate) fn completion_item( ..Default::default() }; - if item.relevance().is_relevant() { - lsp_item.preselect = Some(true); - // HACK: sort preselect items first - lsp_item.sort_text = Some(format!(" {}", item.label())); + fn set_score(res: &mut lsp_types::CompletionItem, relevance: CompletionRelevance) { + if relevance.is_relevant() { + res.preselect = Some(true); + } + // 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() { 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() { - Some(mutability) => { + Some((mutability, relevance)) => { let mut lsp_item_with_ref = lsp_item.clone(); - lsp_item.preselect = Some(true); - lsp_item.sort_text = Some(format!(" {}", item.label())); + set_score(&mut lsp_item_with_ref, relevance); 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 @@ -1107,13 +1117,13 @@ fn main() { ( "&arg", Some( - " arg", + "253", ), ), ( "arg", Some( - " arg", + "254", ), ), ]