diff --git a/Cargo.toml b/Cargo.toml index 1213979c390f..7af0a284203c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,9 +117,9 @@ text-size = "1.1.1" tracing = "0.1.40" tracing-tree = "0.3.0" tracing-subscriber = { version = "0.3.18", default-features = false, features = [ - "registry", - "fmt", - "tracing-log", + "registry", + "fmt", + "tracing-log", ] } triomphe = { version = "0.1.10", default-features = false, features = ["std"] } xshell = "0.2.5" diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index de41a5bd70c4..5769cea8ce06 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -164,6 +164,9 @@ pub struct CompletionRelevance { pub postfix_match: Option, /// This is set for type inference results pub is_definite: bool, + /// Any other bonuses we want to add, + /// eg. bonus for good behavior! + pub bonus_score: u32, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -228,6 +231,7 @@ impl CompletionRelevance { is_private_editable, postfix_match, is_definite, + bonus_score, } = self; // lower rank private things @@ -269,7 +273,8 @@ impl CompletionRelevance { if is_definite { score += 10; } - score + + score + bonus_score } /// Returns true when the score for this threshold is above diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 2ea3f74d18bc..e0490a3207b6 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -17,7 +17,7 @@ use ide_db::{ imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; -use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; +use syntax::{AstNode, SmolStr, SyntaxKind, SyntaxToken, TextRange}; use text_edit::TextEdit; use crate::{ @@ -72,6 +72,10 @@ impl<'a> RenderContext<'a> { self.completion.db } + fn token(&self) -> &SyntaxToken { + &self.completion.token + } + fn source_range(&self) -> TextRange { self.completion.source_range() } @@ -1176,6 +1180,7 @@ fn main() { let _: m::Spam = S$0 } is_private_editable: false, postfix_match: None, is_definite: false, + bonus_score: 0, }, trigger_call_info: true, }, @@ -1202,6 +1207,7 @@ fn main() { let _: m::Spam = S$0 } is_private_editable: false, postfix_match: None, is_definite: false, + bonus_score: 0, }, trigger_call_info: true, }, @@ -1280,6 +1286,7 @@ fn foo() { A { the$0 } } is_private_editable: false, postfix_match: None, is_definite: false, + bonus_score: 0, }, }, ] @@ -2002,6 +2009,60 @@ fn main() { ); } + #[test] + fn colon_complete_preferred_order_relevances() { + check_relevance( + r#" +struct A; +struct ABuilder; +impl A { + fn foo(&self) {} + fn new_1(input: u32) -> A { A } + fn new_2() -> Self { A } + fn aaaabuilder() -> ABuilder { A } +} + +fn test() { + let a = A::$0; +} +"#, + // preference: + // fn with no param that returns itself + // builder like fn + // fn with param that returns itself + expect![[r#" + fn new_2() [type_could_unify] + fn aaaabuilder() [type_could_unify] + fn new_1(…) [type_could_unify] + me foo(…) [type_could_unify] + "#]], + ); + + // Generic + check_relevance( + r#" +struct A{item: T} +struct ABuilder; +impl A { + fn foo(&self) {} + fn new_1(input: u32, l: T) -> A { A } + fn new_2() -> Self { A { item: <_>::default()} } + fn aaaabuilder() -> ABuilder { A } +} + +fn test() { + let a = A::$0; +} +"#, + expect![[r#" + fn new_2() [type_could_unify] + fn aaaabuilder() [type_could_unify] + fn new_1(…) [type_could_unify] + me foo(…) [type_could_unify] + "#]], + ); + } + #[test] fn struct_field_method_ref() { check_kinds( @@ -2095,6 +2156,7 @@ fn foo() { is_private_editable: false, postfix_match: None, is_definite: false, + bonus_score: 0, }, }, ] @@ -2132,6 +2194,19 @@ fn main() { ), lookup: "foo", detail: "fn() -> S", + relevance: CompletionRelevance { + exact_name_match: false, + type_match: None, + is_local: false, + is_item_from_trait: false, + is_name_already_imported: false, + requires_import: false, + is_op_method: false, + is_private_editable: false, + postfix_match: None, + is_definite: false, + bonus_score: 30, + }, ref_match: "&@92", }, ] diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index d23ed71fdcc6..c6e94b2d69fb 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -105,6 +105,7 @@ fn render( }, exact_name_match: compute_exact_name_match(completion, &call), is_op_method, + bonus_score: calculate_bonus(&ctx, func, db), ..ctx.completion_relevance() }); @@ -153,6 +154,34 @@ fn render( item } +/// When typing `::` of a type, the preferred order is: +/// * Constructors: new like functions to be able to create the type, +/// * Builder Methods, +/// * Constructors that take args: Any other function that creates Self +/// * Regular methods & Associated functions +/// +fn calculate_bonus(ctx: &RenderContext<'_>, func: hir::Function, db: &dyn HirDatabase) -> u32 { + if ctx.token().kind() != syntax::SyntaxKind::COLON2 || func.self_param(db).is_some() { + return 0; + } + + let mut bonus = 0; + + let has_args = !func.assoc_fn_params(db).is_empty(); + let ret_type = func.ret_type(db); + if !has_args && !ret_type.is_unit() { + // fn() -> A + bonus += 30; + } else if has_args && !ret_type.is_unit() { + // fn(..) -> A + bonus += 20; + } else if !has_args && ret_type.display(db).to_string().ends_with("Builder") { + // -> [..]Builder + bonus += 10; + } + bonus +} + pub(super) fn add_call_parens<'b>( builder: &'b mut Builder, ctx: &CompletionContext<'_>,