From d42346fed61f706d68fe888631a41ea5f2752d7f Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Sat, 11 Apr 2020 22:54:18 +0200 Subject: Improve autocompletion by looking on the type and name Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/ra_ide/src/completion/complete_dot.rs | 125 ++++++++++++++++++++- crates/ra_ide/src/completion/completion_context.rs | 14 ++- crates/ra_ide/src/completion/presentation.rs | 2 +- crates/ra_ide/src/completion/test_utils.rs | 11 +- crates/ra_ide/src/display/function_signature.rs | 57 ++++++---- crates/ra_ide/src/lib.rs | 15 +++ 6 files changed, 194 insertions(+), 30 deletions(-) (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index f433faef3..358b041aa 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir::{HasVisibility, Type}; +use hir::{HasVisibility, HirDisplay, Type}; use crate::completion::completion_item::CompletionKind; use crate::{ @@ -8,6 +8,7 @@ use crate::{ CompletionItem, }; use rustc_hash::FxHashSet; +use std::cmp::Ordering; /// Complete dot accesses, i.e. fields or methods (and .await syntax). pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { @@ -37,7 +38,31 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { for receiver in receiver.autoderef(ctx.db) { - for (field, ty) in receiver.fields(ctx.db) { + let mut fields = receiver.fields(ctx.db); + if let Some(call_info) = &ctx.call_info { + if let Some(active_parameter_type) = call_info.active_parameter_type() { + let active_parameter_name = call_info.active_parameter_name().unwrap(); + fields.sort_by(|a, b| { + // For the same type + if active_parameter_type == a.1.display(ctx.db).to_string() { + // If same type + same name then go top position + if active_parameter_name == a.0.name(ctx.db).to_string() { + Ordering::Less + } else { + if active_parameter_type == b.1.display(ctx.db).to_string() { + Ordering::Equal + } else { + Ordering::Less + } + } + } else { + Ordering::Greater + } + }); + } + } + + for (field, ty) in fields { if ctx.scope().module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { // Skip private field. FIXME: If the definition location of the // field is editable, we should show the completion @@ -47,6 +72,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty } for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { // FIXME: Handle visibility + // TODO: add the same behavior with type ? acc.add_tuple_field(ctx, i, &ty); } } @@ -70,13 +96,20 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T #[cfg(test)] mod tests { - use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; + use crate::completion::{ + test_utils::{do_completion, do_completion_without_sort}, + CompletionItem, CompletionKind, + }; use insta::assert_debug_snapshot; fn do_ref_completion(code: &str) -> Vec { do_completion(code, CompletionKind::Reference) } + fn do_ref_completion_without_sort(code: &str) -> Vec { + do_completion_without_sort(code, CompletionKind::Reference) + } + #[test] fn test_struct_field_completion() { assert_debug_snapshot!( @@ -103,6 +136,92 @@ mod tests { ); } + #[test] + fn test_struct_field_completion_in_func_call() { + assert_debug_snapshot!( + do_ref_completion_without_sort( + r" + struct A { another_field: i64, the_field: u32, my_string: String } + fn test(my_param: u32) -> u32 { my_param } + fn foo(a: A) { + test(a.<|>) + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [201; 201), + delete: [201; 201), + insert: "the_field", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_field", + source_range: [201; 201), + delete: [201; 201), + insert: "another_field", + kind: Field, + detail: "i64", + }, + CompletionItem { + label: "my_string", + source_range: [201; 201), + delete: [201; 201), + insert: "my_string", + kind: Field, + detail: "{unknown}", + }, + ] + "### + ); + } + + #[test] + fn test_struct_field_completion_in_func_call_with_type_and_name() { + assert_debug_snapshot!( + do_ref_completion_without_sort( + r" + struct A { another_field: i64, another_good_type: u32, the_field: u32 } + fn test(the_field: u32) -> u32 { the_field } + fn foo(a: A) { + test(a.<|>) + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [208; 208), + delete: [208; 208), + insert: "the_field", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_good_type", + source_range: [208; 208), + delete: [208; 208), + insert: "another_good_type", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_field", + source_range: [208; 208), + delete: [208; 208), + insert: "another_field", + kind: Field, + detail: "i64", + }, + ] + "### + ); + } + #[test] fn test_struct_field_completion_self() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index f833d2a9a..fddaf21e4 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -1,17 +1,19 @@ //! FIXME: write short doc here -use hir::{Semantics, SemanticsScope}; +use hir::{db::HirDatabase, Semantics, SemanticsScope}; use ra_db::SourceDatabase; use ra_ide_db::RootDatabase; use ra_syntax::{ algo::{find_covering_element, find_node_at_offset}, - ast, AstNode, + ast, + ast::ArgListOwner, + AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextUnit, }; use ra_text_edit::AtomTextEdit; -use crate::{completion::CompletionConfig, FilePosition}; +use crate::{call_info::call_info, completion::CompletionConfig, CallInfo, FilePosition}; /// `CompletionContext` is created early during completion to figure out, where /// exactly is the cursor, syntax-wise. @@ -21,6 +23,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) db: &'a RootDatabase, pub(super) config: &'a CompletionConfig, pub(super) offset: TextUnit, + pub(super) file_position: FilePosition, /// The token before the cursor, in the original file. pub(super) original_token: SyntaxToken, /// The token before the cursor, in the macro-expanded file. @@ -32,6 +35,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) record_lit_syntax: Option, pub(super) record_lit_pat: Option, pub(super) impl_def: Option, + pub(super) call_info: Option, pub(super) is_param: bool, /// If a name-binding or reference to a const in a pattern. /// Irrefutable patterns (like let) are excluded. @@ -88,9 +92,11 @@ impl<'a> CompletionContext<'a> { original_token, token, offset: position.offset, + file_position: position, krate, name_ref_syntax: None, function_syntax: None, + call_info: None, use_item_syntax: None, record_lit_syntax: None, record_lit_pat: None, @@ -253,6 +259,8 @@ impl<'a> CompletionContext<'a> { self.use_item_syntax = self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast); + self.call_info = call_info(self.db, self.file_position); + self.function_syntax = self .sema .ancestors_with_macros(self.token.parent()) diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 55f75b15a..8be2d02d0 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -367,7 +367,7 @@ mod tests { ra_fixture: &str, options: CompletionConfig, ) -> Vec { - do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) + do_completion_with_options(ra_fixture, CompletionKind::Reference, &options, true) } #[test] diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index eb90b5279..f54d15a90 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs @@ -7,13 +7,18 @@ use crate::{ }; pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec { - do_completion_with_options(code, kind, &CompletionConfig::default()) + do_completion_with_options(code, kind, &CompletionConfig::default(), true) +} + +pub(crate) fn do_completion_without_sort(code: &str, kind: CompletionKind) -> Vec { + do_completion_with_options(code, kind, &CompletionConfig::default(), false) } pub(crate) fn do_completion_with_options( code: &str, kind: CompletionKind, options: &CompletionConfig, + sort_by_key: bool, ) -> Vec { let (analysis, position) = if code.contains("//-") { analysis_and_position(code) @@ -24,6 +29,8 @@ pub(crate) fn do_completion_with_options( let completion_items: Vec = completions.into(); let mut kind_completions: Vec = completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); - kind_completions.sort_by_key(|c| c.label().to_owned()); + if sort_by_key { + kind_completions.sort_by_key(|c| c.label().to_owned()); + } kind_completions } diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index b967a6816..e58a78271 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -36,6 +36,8 @@ pub struct FunctionSignature { pub parameters: Vec, /// Parameter names of the function pub parameter_names: Vec, + /// Parameter types of the function + pub parameter_types: Vec, /// Optional return type pub ret_type: Option, /// Where predicates @@ -62,14 +64,14 @@ impl FunctionSignature { return None; }; - let params = st - .fields(db) - .into_iter() - .map(|field: hir::StructField| { - let ty = field.signature_ty(db); - format!("{}", ty.display(db)) - }) - .collect(); + let mut params = vec![]; + let mut parameter_types = vec![]; + for field in st.fields(db).into_iter() { + let ty = field.signature_ty(db); + let raw_param = format!("{}", ty.display(db)); + parameter_types.push(raw_param.split(':').nth(1).unwrap()[1..].to_string()); + params.push(raw_param); + } Some( FunctionSignature { @@ -79,6 +81,7 @@ impl FunctionSignature { ret_type: node.name().map(|n| n.text().to_string()), parameters: params, parameter_names: vec![], + parameter_types, generic_parameters: generic_parameters(&node), where_predicates: where_predicates(&node), doc: None, @@ -99,15 +102,14 @@ impl FunctionSignature { let name = format!("{}::{}", parent_name, variant.name(db)); - let params = variant - .fields(db) - .into_iter() - .map(|field: hir::StructField| { - let name = field.name(db); - let ty = field.signature_ty(db); - format!("{}: {}", name, ty.display(db)) - }) - .collect(); + let mut params = vec![]; + let mut parameter_types = vec![]; + for field in variant.fields(db).into_iter() { + let ty = field.signature_ty(db); + let raw_param = format!("{}", ty.display(db)); + parameter_types.push(raw_param.split(':').nth(1).unwrap()[1..].to_string()); + params.push(raw_param); + } Some( FunctionSignature { @@ -117,6 +119,7 @@ impl FunctionSignature { ret_type: None, parameters: params, parameter_names: vec![], + parameter_types, generic_parameters: vec![], where_predicates: vec![], doc: None, @@ -139,6 +142,7 @@ impl FunctionSignature { ret_type: None, parameters: params, parameter_names: vec![], + parameter_types: vec![], generic_parameters: vec![], where_predicates: vec![], doc: None, @@ -151,18 +155,28 @@ impl FunctionSignature { impl From<&'_ ast::FnDef> for FunctionSignature { fn from(node: &ast::FnDef) -> FunctionSignature { - fn param_list(node: &ast::FnDef) -> (bool, Vec) { + fn param_list(node: &ast::FnDef) -> (bool, Vec, Vec) { let mut res = vec![]; + let mut res_types = vec![]; let mut has_self_param = false; if let Some(param_list) = node.param_list() { if let Some(self_param) = param_list.self_param() { has_self_param = true; - res.push(self_param.syntax().text().to_string()) + let raw_param = self_param.syntax().text().to_string(); + + // TODO: better solution ? + res_types.push( + raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(), + ); + res.push(raw_param); } res.extend(param_list.params().map(|param| param.syntax().text().to_string())); + res_types.extend(param_list.params().map(|param| { + param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() + })); } - (has_self_param, res) + (has_self_param, res, res_types) } fn param_name_list(node: &ast::FnDef) -> Vec { @@ -192,7 +206,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature { res } - let (has_self_param, parameters) = param_list(node); + let (has_self_param, parameters, parameter_types) = param_list(node); FunctionSignature { kind: CallableKind::Function, @@ -204,6 +218,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature { .map(|n| n.syntax().text().to_string()), parameters, parameter_names: param_name_list(node), + parameter_types, generic_parameters: generic_parameters(node), where_predicates: where_predicates(node), // docs are processed separately diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 5599f143f..357c01cdf 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -127,6 +127,21 @@ pub struct CallInfo { pub active_parameter: Option, } +impl CallInfo { + pub fn active_parameter_type(&self) -> Option { + if let Some(id) = self.active_parameter { + return self.signature.parameter_types.get(id).map(|param_ty| param_ty.clone()); + } + None + } + pub fn active_parameter_name(&self) -> Option { + if let Some(id) = self.active_parameter { + return self.signature.parameter_names.get(id).map(|param_ty| param_ty.clone()); + } + None + } +} + /// `AnalysisHost` stores the current state of the world. #[derive(Debug)] pub struct AnalysisHost { -- cgit v1.2.3