From ab0b63992be0cec4999810096a53b40f63f90349 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Tue, 25 Dec 2018 15:15:40 +0100 Subject: Implement basic completion for fields --- crates/ra_analysis/src/completion/complete_dot.rs | 98 ++++++++++++++++++++++ .../src/completion/completion_context.rs | 34 ++++++-- .../ra_analysis/src/completion/completion_item.rs | 1 + 3 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 crates/ra_analysis/src/completion/complete_dot.rs (limited to 'crates/ra_analysis/src/completion') diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs new file mode 100644 index 000000000..fa62da210 --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_dot.rs @@ -0,0 +1,98 @@ +use ra_syntax::ast::AstNode; +use hir::{Ty, Def}; + +use crate::Cancelable; +use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind}; + +/// Complete dot accesses, i.e. fields or methods (currently only fields). +pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { + let module = if let Some(module) = &ctx.module { + module + } else { + return Ok(()); + }; + let function = if let Some(fn_def) = ctx.enclosing_fn { + hir::source_binder::function_from_module(ctx.db, module, fn_def) + } else { + return Ok(()); + }; + let receiver = if let Some(receiver) = ctx.dot_receiver { + receiver + } else { + return Ok(()); + }; + let infer_result = function.infer(ctx.db)?; + let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) { + ty + } else { + return Ok(()); + }; + if !ctx.is_method_call { + complete_fields(acc, ctx, receiver_ty)?; + } + Ok(()) +} + +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, ty: Ty) -> Cancelable<()> { + // TODO: autoderef etc. + match ty { + Ty::Adt { def_id, .. } => { + match def_id.resolve(ctx.db)? { + Def::Struct(s) => { + let variant_data = s.variant_data(ctx.db)?; + for field in variant_data.fields() { + CompletionItem::new(CompletionKind::Reference, field.name().to_string()) + .kind(CompletionItemKind::Field) + .add_to(acc); + } + } + // TODO unions + _ => {} + } + } + Ty::Tuple(fields) => { + for (i, _ty) in fields.iter().enumerate() { + CompletionItem::new(CompletionKind::Reference, i.to_string()) + .kind(CompletionItemKind::Field) + .add_to(acc); + } + } + _ => {} + }; + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::completion::*; + + fn check_ref_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Reference); + } + + #[test] + fn test_struct_field_completion() { + check_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|> + } + ", + r#"the_field"#, + ); + } + + #[test] + fn test_no_struct_field_completion_for_method_call() { + check_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|>() + } + ", + r#""#, + ); + } +} diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs index 064fbc6f7..12e98e870 100644 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ b/crates/ra_analysis/src/completion/completion_context.rs @@ -31,6 +31,10 @@ pub(super) struct CompletionContext<'a> { pub(super) is_stmt: bool, /// Something is typed at the "top" level, in module or impl/trait. pub(super) is_new_item: bool, + /// The receiver if this is a field or method access, i.e. writing something.<|> + pub(super) dot_receiver: Option>, + /// If this is a method call in particular, i.e. the () are already there. + pub(super) is_method_call: bool, } impl<'a> CompletionContext<'a> { @@ -54,6 +58,8 @@ impl<'a> CompletionContext<'a> { after_if: false, is_stmt: false, is_new_item: false, + dot_receiver: None, + is_method_call: false, }; ctx.fill(original_file, position.offset); Ok(Some(ctx)) @@ -105,6 +111,12 @@ impl<'a> CompletionContext<'a> { _ => (), } + self.enclosing_fn = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::FnDef::cast); + let parent = match name_ref.syntax().parent() { Some(it) => it, None => return, @@ -120,11 +132,6 @@ impl<'a> CompletionContext<'a> { } if path.qualifier().is_none() { self.is_trivial_path = true; - self.enclosing_fn = self - .leaf - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FnDef::cast); self.is_stmt = match name_ref .syntax() @@ -145,6 +152,23 @@ impl<'a> CompletionContext<'a> { } } } + if let Some(_field_expr) = ast::FieldExpr::cast(parent) { + self.dot_receiver = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::FieldExpr::cast) + .and_then(ast::FieldExpr::expr); + } + if let Some(_method_call_expr) = ast::MethodCallExpr::cast(parent) { + self.dot_receiver = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::MethodCallExpr::cast) + .and_then(ast::MethodCallExpr::expr); + self.is_method_call = true; + } } } diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 6d466c8bd..c9f9f495d 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -30,6 +30,7 @@ pub enum CompletionItemKind { Struct, Enum, Binding, + Field, } #[derive(Debug, PartialEq, Eq)] -- cgit v1.2.3