Improve autocompletion by looking on the type and name

Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
Benjamin Coenen 2020-04-11 22:54:18 +02:00
parent c1317d6923
commit d42346fed6
6 changed files with 194 additions and 30 deletions

View file

@ -1,6 +1,6 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use hir::{HasVisibility, Type}; use hir::{HasVisibility, HirDisplay, Type};
use crate::completion::completion_item::CompletionKind; use crate::completion::completion_item::CompletionKind;
use crate::{ use crate::{
@ -8,6 +8,7 @@ use crate::{
CompletionItem, CompletionItem,
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::cmp::Ordering;
/// Complete dot accesses, i.e. fields or methods (and .await syntax). /// Complete dot accesses, i.e. fields or methods (and .await syntax).
pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { 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) { fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
for receiver in receiver.autoderef(ctx.db) { 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)) { 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 // Skip private field. FIXME: If the definition location of the
// field is editable, we should show the completion // 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() { for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
// FIXME: Handle visibility // FIXME: Handle visibility
// TODO: add the same behavior with type ?
acc.add_tuple_field(ctx, i, &ty); acc.add_tuple_field(ctx, i, &ty);
} }
} }
@ -70,13 +96,20 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T
#[cfg(test)] #[cfg(test)]
mod tests { 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; use insta::assert_debug_snapshot;
fn do_ref_completion(code: &str) -> Vec<CompletionItem> { fn do_ref_completion(code: &str) -> Vec<CompletionItem> {
do_completion(code, CompletionKind::Reference) do_completion(code, CompletionKind::Reference)
} }
fn do_ref_completion_without_sort(code: &str) -> Vec<CompletionItem> {
do_completion_without_sort(code, CompletionKind::Reference)
}
#[test] #[test]
fn test_struct_field_completion() { fn test_struct_field_completion() {
assert_debug_snapshot!( 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] #[test]
fn test_struct_field_completion_self() { fn test_struct_field_completion_self() {
assert_debug_snapshot!( assert_debug_snapshot!(

View file

@ -1,17 +1,19 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use hir::{Semantics, SemanticsScope}; use hir::{db::HirDatabase, Semantics, SemanticsScope};
use ra_db::SourceDatabase; use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
algo::{find_covering_element, find_node_at_offset}, algo::{find_covering_element, find_node_at_offset},
ast, AstNode, ast,
ast::ArgListOwner,
AstNode,
SyntaxKind::*, SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextUnit, SyntaxNode, SyntaxToken, TextRange, TextUnit,
}; };
use ra_text_edit::AtomTextEdit; 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 /// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise. /// exactly is the cursor, syntax-wise.
@ -21,6 +23,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) db: &'a RootDatabase, pub(super) db: &'a RootDatabase,
pub(super) config: &'a CompletionConfig, pub(super) config: &'a CompletionConfig,
pub(super) offset: TextUnit, pub(super) offset: TextUnit,
pub(super) file_position: FilePosition,
/// The token before the cursor, in the original file. /// The token before the cursor, in the original file.
pub(super) original_token: SyntaxToken, pub(super) original_token: SyntaxToken,
/// The token before the cursor, in the macro-expanded file. /// 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<ast::RecordLit>, pub(super) record_lit_syntax: Option<ast::RecordLit>,
pub(super) record_lit_pat: Option<ast::RecordPat>, pub(super) record_lit_pat: Option<ast::RecordPat>,
pub(super) impl_def: Option<ast::ImplDef>, pub(super) impl_def: Option<ast::ImplDef>,
pub(super) call_info: Option<CallInfo>,
pub(super) is_param: bool, pub(super) is_param: bool,
/// If a name-binding or reference to a const in a pattern. /// If a name-binding or reference to a const in a pattern.
/// Irrefutable patterns (like let) are excluded. /// Irrefutable patterns (like let) are excluded.
@ -88,9 +92,11 @@ impl<'a> CompletionContext<'a> {
original_token, original_token,
token, token,
offset: position.offset, offset: position.offset,
file_position: position,
krate, krate,
name_ref_syntax: None, name_ref_syntax: None,
function_syntax: None, function_syntax: None,
call_info: None,
use_item_syntax: None, use_item_syntax: None,
record_lit_syntax: None, record_lit_syntax: None,
record_lit_pat: None, record_lit_pat: None,
@ -253,6 +259,8 @@ impl<'a> CompletionContext<'a> {
self.use_item_syntax = self.use_item_syntax =
self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast); 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 self.function_syntax = self
.sema .sema
.ancestors_with_macros(self.token.parent()) .ancestors_with_macros(self.token.parent())

View file

@ -367,7 +367,7 @@ mod tests {
ra_fixture: &str, ra_fixture: &str,
options: CompletionConfig, options: CompletionConfig,
) -> Vec<CompletionItem> { ) -> Vec<CompletionItem> {
do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) do_completion_with_options(ra_fixture, CompletionKind::Reference, &options, true)
} }
#[test] #[test]

View file

@ -7,13 +7,18 @@ use crate::{
}; };
pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
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<CompletionItem> {
do_completion_with_options(code, kind, &CompletionConfig::default(), false)
} }
pub(crate) fn do_completion_with_options( pub(crate) fn do_completion_with_options(
code: &str, code: &str,
kind: CompletionKind, kind: CompletionKind,
options: &CompletionConfig, options: &CompletionConfig,
sort_by_key: bool,
) -> Vec<CompletionItem> { ) -> Vec<CompletionItem> {
let (analysis, position) = if code.contains("//-") { let (analysis, position) = if code.contains("//-") {
analysis_and_position(code) analysis_and_position(code)
@ -24,6 +29,8 @@ pub(crate) fn do_completion_with_options(
let completion_items: Vec<CompletionItem> = completions.into(); let completion_items: Vec<CompletionItem> = completions.into();
let mut kind_completions: Vec<CompletionItem> = let mut kind_completions: Vec<CompletionItem> =
completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); 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 kind_completions
} }

View file

@ -36,6 +36,8 @@ pub struct FunctionSignature {
pub parameters: Vec<String>, pub parameters: Vec<String>,
/// Parameter names of the function /// Parameter names of the function
pub parameter_names: Vec<String>, pub parameter_names: Vec<String>,
/// Parameter types of the function
pub parameter_types: Vec<String>,
/// Optional return type /// Optional return type
pub ret_type: Option<String>, pub ret_type: Option<String>,
/// Where predicates /// Where predicates
@ -62,14 +64,14 @@ impl FunctionSignature {
return None; return None;
}; };
let params = st let mut params = vec![];
.fields(db) let mut parameter_types = vec![];
.into_iter() for field in st.fields(db).into_iter() {
.map(|field: hir::StructField| { let ty = field.signature_ty(db);
let ty = field.signature_ty(db); let raw_param = format!("{}", ty.display(db));
format!("{}", ty.display(db)) parameter_types.push(raw_param.split(':').nth(1).unwrap()[1..].to_string());
}) params.push(raw_param);
.collect(); }
Some( Some(
FunctionSignature { FunctionSignature {
@ -79,6 +81,7 @@ impl FunctionSignature {
ret_type: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()),
parameters: params, parameters: params,
parameter_names: vec![], parameter_names: vec![],
parameter_types,
generic_parameters: generic_parameters(&node), generic_parameters: generic_parameters(&node),
where_predicates: where_predicates(&node), where_predicates: where_predicates(&node),
doc: None, doc: None,
@ -99,15 +102,14 @@ impl FunctionSignature {
let name = format!("{}::{}", parent_name, variant.name(db)); let name = format!("{}::{}", parent_name, variant.name(db));
let params = variant let mut params = vec![];
.fields(db) let mut parameter_types = vec![];
.into_iter() for field in variant.fields(db).into_iter() {
.map(|field: hir::StructField| { let ty = field.signature_ty(db);
let name = field.name(db); let raw_param = format!("{}", ty.display(db));
let ty = field.signature_ty(db); parameter_types.push(raw_param.split(':').nth(1).unwrap()[1..].to_string());
format!("{}: {}", name, ty.display(db)) params.push(raw_param);
}) }
.collect();
Some( Some(
FunctionSignature { FunctionSignature {
@ -117,6 +119,7 @@ impl FunctionSignature {
ret_type: None, ret_type: None,
parameters: params, parameters: params,
parameter_names: vec![], parameter_names: vec![],
parameter_types,
generic_parameters: vec![], generic_parameters: vec![],
where_predicates: vec![], where_predicates: vec![],
doc: None, doc: None,
@ -139,6 +142,7 @@ impl FunctionSignature {
ret_type: None, ret_type: None,
parameters: params, parameters: params,
parameter_names: vec![], parameter_names: vec![],
parameter_types: vec![],
generic_parameters: vec![], generic_parameters: vec![],
where_predicates: vec![], where_predicates: vec![],
doc: None, doc: None,
@ -151,18 +155,28 @@ impl FunctionSignature {
impl From<&'_ ast::FnDef> for FunctionSignature { impl From<&'_ ast::FnDef> for FunctionSignature {
fn from(node: &ast::FnDef) -> FunctionSignature { fn from(node: &ast::FnDef) -> FunctionSignature {
fn param_list(node: &ast::FnDef) -> (bool, Vec<String>) { fn param_list(node: &ast::FnDef) -> (bool, Vec<String>, Vec<String>) {
let mut res = vec![]; let mut res = vec![];
let mut res_types = vec![];
let mut has_self_param = false; let mut has_self_param = false;
if let Some(param_list) = node.param_list() { if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() { if let Some(self_param) = param_list.self_param() {
has_self_param = true; 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.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<String> { fn param_name_list(node: &ast::FnDef) -> Vec<String> {
@ -192,7 +206,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
res res
} }
let (has_self_param, parameters) = param_list(node); let (has_self_param, parameters, parameter_types) = param_list(node);
FunctionSignature { FunctionSignature {
kind: CallableKind::Function, kind: CallableKind::Function,
@ -204,6 +218,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
.map(|n| n.syntax().text().to_string()), .map(|n| n.syntax().text().to_string()),
parameters, parameters,
parameter_names: param_name_list(node), parameter_names: param_name_list(node),
parameter_types,
generic_parameters: generic_parameters(node), generic_parameters: generic_parameters(node),
where_predicates: where_predicates(node), where_predicates: where_predicates(node),
// docs are processed separately // docs are processed separately

View file

@ -127,6 +127,21 @@ pub struct CallInfo {
pub active_parameter: Option<usize>, pub active_parameter: Option<usize>,
} }
impl CallInfo {
pub fn active_parameter_type(&self) -> Option<String> {
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<String> {
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. /// `AnalysisHost` stores the current state of the world.
#[derive(Debug)] #[derive(Debug)]
pub struct AnalysisHost { pub struct AnalysisHost {