aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/completion')
-rw-r--r--crates/ra_analysis/src/completion/complete_dot.rs98
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs2
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs54
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs36
4 files changed, 169 insertions, 21 deletions
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..93d657576
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_dot.rs
@@ -0,0 +1,98 @@
1use ra_syntax::ast::AstNode;
2use hir::{Ty, Def};
3
4use crate::Cancelable;
5use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind};
6
7/// Complete dot accesses, i.e. fields or methods (currently only fields).
8pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
9 let module = if let Some(module) = &ctx.module {
10 module
11 } else {
12 return Ok(());
13 };
14 let function = if let Some(fn_def) = ctx.enclosing_fn {
15 hir::source_binder::function_from_module(ctx.db, module, fn_def)
16 } else {
17 return Ok(());
18 };
19 let receiver = if let Some(receiver) = ctx.dot_receiver {
20 receiver
21 } else {
22 return Ok(());
23 };
24 let infer_result = function.infer(ctx.db)?;
25 let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) {
26 ty
27 } else {
28 return Ok(());
29 };
30 if !ctx.is_method_call {
31 complete_fields(acc, ctx, receiver_ty)?;
32 }
33 Ok(())
34}
35
36fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> {
37 // TODO: autoderef etc.
38 match receiver {
39 Ty::Adt { def_id, .. } => {
40 match def_id.resolve(ctx.db)? {
41 Def::Struct(s) => {
42 let variant_data = s.variant_data(ctx.db)?;
43 for field in variant_data.fields() {
44 CompletionItem::new(CompletionKind::Reference, field.name().to_string())
45 .kind(CompletionItemKind::Field)
46 .add_to(acc);
47 }
48 }
49 // TODO unions
50 _ => {}
51 }
52 }
53 Ty::Tuple(fields) => {
54 for (i, _ty) in fields.iter().enumerate() {
55 CompletionItem::new(CompletionKind::Reference, i.to_string())
56 .kind(CompletionItemKind::Field)
57 .add_to(acc);
58 }
59 }
60 _ => {}
61 };
62 Ok(())
63}
64
65#[cfg(test)]
66mod tests {
67 use crate::completion::*;
68
69 fn check_ref_completion(code: &str, expected_completions: &str) {
70 check_completion(code, expected_completions, CompletionKind::Reference);
71 }
72
73 #[test]
74 fn test_struct_field_completion() {
75 check_ref_completion(
76 r"
77 struct A { the_field: u32 }
78 fn foo(a: A) {
79 a.<|>
80 }
81 ",
82 r#"the_field"#,
83 );
84 }
85
86 #[test]
87 fn test_no_struct_field_completion_for_method_call() {
88 check_ref_completion(
89 r"
90 struct A { the_field: u32 }
91 fn foo(a: A) {
92 a.<|>()
93 }
94 ",
95 r#""#,
96 );
97 }
98}
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
index ad4d68a33..aaa2c7cee 100644
--- a/crates/ra_analysis/src/completion/complete_path.rs
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -8,7 +8,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> C
8 (Some(path), Some(module)) => (path.clone(), module), 8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()), 9 _ => return Ok(()),
10 }; 10 };
11 let def_id = match module.resolve_path(ctx.db, path)? { 11 let def_id = match module.resolve_path(ctx.db, &path)?.take_types() {
12 Some(it) => it, 12 Some(it) => it,
13 None => return Ok(()), 13 None => return Ok(()),
14 }; 14 };
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
index 064fbc6f7..978772fd4 100644
--- a/crates/ra_analysis/src/completion/completion_context.rs
+++ b/crates/ra_analysis/src/completion/completion_context.rs
@@ -1,12 +1,13 @@
1use ra_editor::find_node_at_offset; 1use ra_editor::find_node_at_offset;
2use ra_text_edit::AtomTextEdit; 2use ra_text_edit::AtomTextEdit;
3use ra_syntax::{ 3use ra_syntax::{
4 algo::find_leaf_at_offset, 4 algo::{find_leaf_at_offset, find_covering_node},
5 ast, 5 ast,
6 AstNode, 6 AstNode,
7 SyntaxNodeRef, 7 SyntaxNodeRef,
8 SourceFileNode, 8 SourceFileNode,
9 TextUnit, 9 TextUnit,
10 TextRange,
10 SyntaxKind::*, 11 SyntaxKind::*,
11}; 12};
12use hir::source_binder; 13use hir::source_binder;
@@ -31,6 +32,10 @@ pub(super) struct CompletionContext<'a> {
31 pub(super) is_stmt: bool, 32 pub(super) is_stmt: bool,
32 /// Something is typed at the "top" level, in module or impl/trait. 33 /// Something is typed at the "top" level, in module or impl/trait.
33 pub(super) is_new_item: bool, 34 pub(super) is_new_item: bool,
35 /// The receiver if this is a field or method access, i.e. writing something.<|>
36 pub(super) dot_receiver: Option<ast::Expr<'a>>,
37 /// If this is a method call in particular, i.e. the () are already there.
38 pub(super) is_method_call: bool,
34} 39}
35 40
36impl<'a> CompletionContext<'a> { 41impl<'a> CompletionContext<'a> {
@@ -54,12 +59,14 @@ impl<'a> CompletionContext<'a> {
54 after_if: false, 59 after_if: false,
55 is_stmt: false, 60 is_stmt: false,
56 is_new_item: false, 61 is_new_item: false,
62 dot_receiver: None,
63 is_method_call: false,
57 }; 64 };
58 ctx.fill(original_file, position.offset); 65 ctx.fill(original_file, position.offset);
59 Ok(Some(ctx)) 66 Ok(Some(ctx))
60 } 67 }
61 68
62 fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) { 69 fn fill(&mut self, original_file: &'a SourceFileNode, offset: TextUnit) {
63 // Insert a fake ident to get a valid parse tree. We will use this file 70 // Insert a fake ident to get a valid parse tree. We will use this file
64 // to determine context, though the original_file will be used for 71 // to determine context, though the original_file will be used for
65 // actual completion. 72 // actual completion.
@@ -76,7 +83,7 @@ impl<'a> CompletionContext<'a> {
76 self.is_param = true; 83 self.is_param = true;
77 return; 84 return;
78 } 85 }
79 self.classify_name_ref(&file, name_ref); 86 self.classify_name_ref(original_file, name_ref);
80 } 87 }
81 88
82 // Otherwise, see if this is a declaration. We can use heuristics to 89 // Otherwise, see if this is a declaration. We can use heuristics to
@@ -88,7 +95,7 @@ impl<'a> CompletionContext<'a> {
88 } 95 }
89 } 96 }
90 } 97 }
91 fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { 98 fn classify_name_ref(&mut self, original_file: &'a SourceFileNode, name_ref: ast::NameRef) {
92 let name_range = name_ref.syntax().range(); 99 let name_range = name_ref.syntax().range();
93 let top_node = name_ref 100 let top_node = name_ref
94 .syntax() 101 .syntax()
@@ -105,6 +112,12 @@ impl<'a> CompletionContext<'a> {
105 _ => (), 112 _ => (),
106 } 113 }
107 114
115 self.enclosing_fn = self
116 .leaf
117 .ancestors()
118 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
119 .find_map(ast::FnDef::cast);
120
108 let parent = match name_ref.syntax().parent() { 121 let parent = match name_ref.syntax().parent() {
109 Some(it) => it, 122 Some(it) => it,
110 None => return, 123 None => return,
@@ -120,11 +133,6 @@ impl<'a> CompletionContext<'a> {
120 } 133 }
121 if path.qualifier().is_none() { 134 if path.qualifier().is_none() {
122 self.is_trivial_path = true; 135 self.is_trivial_path = true;
123 self.enclosing_fn = self
124 .leaf
125 .ancestors()
126 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
127 .find_map(ast::FnDef::cast);
128 136
129 self.is_stmt = match name_ref 137 self.is_stmt = match name_ref
130 .syntax() 138 .syntax()
@@ -137,7 +145,9 @@ impl<'a> CompletionContext<'a> {
137 }; 145 };
138 146
139 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { 147 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
140 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { 148 if let Some(if_expr) =
149 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off)
150 {
141 if if_expr.syntax().range().end() < name_ref.syntax().range().start() { 151 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
142 self.after_if = true; 152 self.after_if = true;
143 } 153 }
@@ -145,9 +155,33 @@ impl<'a> CompletionContext<'a> {
145 } 155 }
146 } 156 }
147 } 157 }
158 if let Some(field_expr) = ast::FieldExpr::cast(parent) {
159 // The receiver comes before the point of insertion of the fake
160 // ident, so it should have the same range in the non-modified file
161 self.dot_receiver = field_expr
162 .expr()
163 .map(|e| e.syntax().range())
164 .and_then(|r| find_node_with_range(original_file.syntax(), r));
165 }
166 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
167 // As above
168 self.dot_receiver = method_call_expr
169 .expr()
170 .map(|e| e.syntax().range())
171 .and_then(|r| find_node_with_range(original_file.syntax(), r));
172 self.is_method_call = true;
173 }
148 } 174 }
149} 175}
150 176
177fn find_node_with_range<'a, N: AstNode<'a>>(
178 syntax: SyntaxNodeRef<'a>,
179 range: TextRange,
180) -> Option<N> {
181 let node = find_covering_node(syntax, range);
182 node.ancestors().find_map(N::cast)
183}
184
151fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { 185fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
152 match node.ancestors().filter_map(N::cast).next() { 186 match node.ancestors().filter_map(N::cast).next() {
153 None => false, 187 None => false,
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs
index 911f08468..c9f9f495d 100644
--- a/crates/ra_analysis/src/completion/completion_item.rs
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -1,5 +1,7 @@
1use crate::db; 1use crate::db;
2 2
3use hir::PerNs;
4
3/// `CompletionItem` describes a single completion variant in the editor pop-up. 5/// `CompletionItem` describes a single completion variant in the editor pop-up.
4/// It is basically a POD with various properties. To construct a 6/// It is basically a POD with various properties. To construct a
5/// `CompletionItem`, use `new` method and the `Builder` struct. 7/// `CompletionItem`, use `new` method and the `Builder` struct.
@@ -25,7 +27,10 @@ pub enum CompletionItemKind {
25 Keyword, 27 Keyword,
26 Module, 28 Module,
27 Function, 29 Function,
30 Struct,
31 Enum,
28 Binding, 32 Binding,
33 Field,
29} 34}
30 35
31#[derive(Debug, PartialEq, Eq)] 36#[derive(Debug, PartialEq, Eq)]
@@ -117,16 +122,27 @@ impl Builder {
117 db: &db::RootDatabase, 122 db: &db::RootDatabase,
118 resolution: &hir::Resolution, 123 resolution: &hir::Resolution,
119 ) -> Builder { 124 ) -> Builder {
120 if let Some(def_id) = resolution.def_id { 125 let resolved = resolution.def_id.and_then(|d| d.resolve(db).ok());
121 if let Ok(def) = def_id.resolve(db) { 126 let kind = match resolved {
122 let kind = match def { 127 PerNs {
123 hir::Def::Module(..) => CompletionItemKind::Module, 128 types: Some(hir::Def::Module(..)),
124 hir::Def::Function(..) => CompletionItemKind::Function, 129 ..
125 _ => return self, 130 } => CompletionItemKind::Module,
126 }; 131 PerNs {
127 self.kind = Some(kind); 132 types: Some(hir::Def::Struct(..)),
128 } 133 ..
129 } 134 } => CompletionItemKind::Struct,
135 PerNs {
136 types: Some(hir::Def::Enum(..)),
137 ..
138 } => CompletionItemKind::Enum,
139 PerNs {
140 values: Some(hir::Def::Function(..)),
141 ..
142 } => CompletionItemKind::Function,
143 _ => return self,
144 };
145 self.kind = Some(kind);
130 self 146 self
131 } 147 }
132} 148}