aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/completion
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-08 19:48:48 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-08 19:48:48 +0000
commit46f74e33ca53a7897e9020d3de75cc76a6b89d79 (patch)
tree2bc001c8ecf58b49ac9a0da1f20d5644ce29fb3a /crates/ra_ide_api/src/completion
parent4f4f7933b1b7ff34f8633b1686b18b2d1b994c47 (diff)
parent0c62b1bb7a49bf527780ce1f8cade5eb4fbfdb2d (diff)
Merge #471
471: rename crates to match reality r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_ide_api/src/completion')
-rw-r--r--crates/ra_ide_api/src/completion/complete_dot.rs121
-rw-r--r--crates/ra_ide_api/src/completion/complete_fn_param.rs102
-rw-r--r--crates/ra_ide_api/src/completion/complete_keyword.rs339
-rw-r--r--crates/ra_ide_api/src/completion/complete_path.rs128
-rw-r--r--crates/ra_ide_api/src/completion/complete_scope.rs192
-rw-r--r--crates/ra_ide_api/src/completion/complete_snippet.rs73
-rw-r--r--crates/ra_ide_api/src/completion/completion_context.rs205
-rw-r--r--crates/ra_ide_api/src/completion/completion_item.rs244
8 files changed, 1404 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs
new file mode 100644
index 000000000..5d4e60dc5
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_dot.rs
@@ -0,0 +1,121 @@
1use hir::{Ty, Def};
2
3use crate::Cancelable;
4use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind};
5
6/// Complete dot accesses, i.e. fields or methods (currently only fields).
7pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
8 let (function, receiver) = match (&ctx.function, ctx.dot_receiver) {
9 (Some(function), Some(receiver)) => (function, receiver),
10 _ => return Ok(()),
11 };
12 let infer_result = function.infer(ctx.db)?;
13 let syntax_mapping = function.body_syntax_mapping(ctx.db)?;
14 let expr = match syntax_mapping.node_expr(receiver) {
15 Some(expr) => expr,
16 None => return Ok(()),
17 };
18 let receiver_ty = infer_result[expr].clone();
19 if !ctx.is_method_call {
20 complete_fields(acc, ctx, receiver_ty)?;
21 }
22 Ok(())
23}
24
25fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> {
26 for receiver in receiver.autoderef(ctx.db) {
27 match receiver {
28 Ty::Adt { def_id, .. } => {
29 match def_id.resolve(ctx.db)? {
30 Def::Struct(s) => {
31 let variant_data = s.variant_data(ctx.db)?;
32 for field in variant_data.fields() {
33 CompletionItem::new(
34 CompletionKind::Reference,
35 field.name().to_string(),
36 )
37 .kind(CompletionItemKind::Field)
38 .add_to(acc);
39 }
40 }
41 // TODO unions
42 _ => {}
43 }
44 }
45 Ty::Tuple(fields) => {
46 for (i, _ty) in fields.iter().enumerate() {
47 CompletionItem::new(CompletionKind::Reference, i.to_string())
48 .kind(CompletionItemKind::Field)
49 .add_to(acc);
50 }
51 }
52 _ => {}
53 };
54 }
55 Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60 use crate::completion::*;
61
62 fn check_ref_completion(code: &str, expected_completions: &str) {
63 check_completion(code, expected_completions, CompletionKind::Reference);
64 }
65
66 #[test]
67 fn test_struct_field_completion() {
68 check_ref_completion(
69 r"
70 struct A { the_field: u32 }
71 fn foo(a: A) {
72 a.<|>
73 }
74 ",
75 r#"the_field"#,
76 );
77 }
78
79 #[test]
80 fn test_struct_field_completion_self() {
81 check_ref_completion(
82 r"
83 struct A { the_field: u32 }
84 impl A {
85 fn foo(self) {
86 self.<|>
87 }
88 }
89 ",
90 r#"the_field"#,
91 );
92 }
93
94 #[test]
95 fn test_struct_field_completion_autoderef() {
96 check_ref_completion(
97 r"
98 struct A { the_field: u32 }
99 impl A {
100 fn foo(&self) {
101 self.<|>
102 }
103 }
104 ",
105 r#"the_field"#,
106 );
107 }
108
109 #[test]
110 fn test_no_struct_field_completion_for_method_call() {
111 check_ref_completion(
112 r"
113 struct A { the_field: u32 }
114 fn foo(a: A) {
115 a.<|>()
116 }
117 ",
118 r#""#,
119 );
120 }
121}
diff --git a/crates/ra_ide_api/src/completion/complete_fn_param.rs b/crates/ra_ide_api/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..c1739e47e
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_fn_param.rs
@@ -0,0 +1,102 @@
1use ra_syntax::{
2 algo::visit::{visitor_ctx, VisitorCtx},
3 ast,
4 AstNode,
5};
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem};
9
10/// Complete repeated parametes, both name and type. For example, if all
11/// functions in a file have a `spam: &mut Spam` parameter, a completion with
12/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
13/// suggested.
14pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
15 if !ctx.is_param {
16 return;
17 }
18
19 let mut params = FxHashMap::default();
20 for node in ctx.leaf.ancestors() {
21 let _ = visitor_ctx(&mut params)
22 .visit::<ast::SourceFile, _>(process)
23 .visit::<ast::ItemList, _>(process)
24 .accept(node);
25 }
26 params
27 .into_iter()
28 .filter_map(|(label, (count, param))| {
29 let lookup = param.pat()?.syntax().text().to_string();
30 if count < 2 {
31 None
32 } else {
33 Some((label, lookup))
34 }
35 })
36 .for_each(|(label, lookup)| {
37 CompletionItem::new(CompletionKind::Magic, label)
38 .lookup_by(lookup)
39 .add_to(acc)
40 });
41
42 fn process<'a, N: ast::FnDefOwner>(
43 node: &'a N,
44 params: &mut FxHashMap<String, (u32, &'a ast::Param)>,
45 ) {
46 node.functions()
47 .filter_map(|it| it.param_list())
48 .flat_map(|it| it.params())
49 .for_each(|param| {
50 let text = param.syntax().text().to_string();
51 params.entry(text).or_insert((0, param)).0 += 1;
52 })
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::completion::*;
59
60 fn check_magic_completion(code: &str, expected_completions: &str) {
61 check_completion(code, expected_completions, CompletionKind::Magic);
62 }
63
64 #[test]
65 fn test_param_completion_last_param() {
66 check_magic_completion(
67 r"
68 fn foo(file_id: FileId) {}
69 fn bar(file_id: FileId) {}
70 fn baz(file<|>) {}
71 ",
72 r#"file_id "file_id: FileId""#,
73 );
74 }
75
76 #[test]
77 fn test_param_completion_nth_param() {
78 check_magic_completion(
79 r"
80 fn foo(file_id: FileId) {}
81 fn bar(file_id: FileId) {}
82 fn baz(file<|>, x: i32) {}
83 ",
84 r#"file_id "file_id: FileId""#,
85 );
86 }
87
88 #[test]
89 fn test_param_completion_trait_param() {
90 check_magic_completion(
91 r"
92 pub(crate) trait SourceRoot {
93 pub fn contains(&self, file_id: FileId) -> bool;
94 pub fn module_map(&self) -> &ModuleMap;
95 pub fn lines(&self, file_id: FileId) -> &LineIndex;
96 pub fn syntax(&self, file<|>)
97 }
98 ",
99 r#"file_id "file_id: FileId""#,
100 );
101 }
102}
diff --git a/crates/ra_ide_api/src/completion/complete_keyword.rs b/crates/ra_ide_api/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..d350f06ce
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_keyword.rs
@@ -0,0 +1,339 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNode,
6};
7
8use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind};
9
10pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
11 // complete keyword "crate" in use stmt
12 match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) {
13 (Some(_), None) => {
14 CompletionItem::new(CompletionKind::Keyword, "crate")
15 .kind(CompletionItemKind::Keyword)
16 .lookup_by("crate")
17 .snippet("crate::")
18 .add_to(acc);
19 CompletionItem::new(CompletionKind::Keyword, "self")
20 .kind(CompletionItemKind::Keyword)
21 .lookup_by("self")
22 .add_to(acc);
23 CompletionItem::new(CompletionKind::Keyword, "super")
24 .kind(CompletionItemKind::Keyword)
25 .lookup_by("super")
26 .add_to(acc);
27 }
28 (Some(_), Some(_)) => {
29 CompletionItem::new(CompletionKind::Keyword, "self")
30 .kind(CompletionItemKind::Keyword)
31 .lookup_by("self")
32 .add_to(acc);
33 CompletionItem::new(CompletionKind::Keyword, "super")
34 .kind(CompletionItemKind::Keyword)
35 .lookup_by("super")
36 .add_to(acc);
37 }
38 _ => {}
39 }
40}
41
42fn keyword(kw: &str, snippet: &str) -> CompletionItem {
43 CompletionItem::new(CompletionKind::Keyword, kw)
44 .kind(CompletionItemKind::Keyword)
45 .snippet(snippet)
46 .build()
47}
48
49pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
50 if !ctx.is_trivial_path {
51 return;
52 }
53
54 let fn_def = match ctx.function_syntax {
55 Some(it) => it,
56 None => return,
57 };
58 acc.add(keyword("if", "if $0 {}"));
59 acc.add(keyword("match", "match $0 {}"));
60 acc.add(keyword("while", "while $0 {}"));
61 acc.add(keyword("loop", "loop {$0}"));
62
63 if ctx.after_if {
64 acc.add(keyword("else", "else {$0}"));
65 acc.add(keyword("else if", "else if $0 {}"));
66 }
67 if is_in_loop_body(ctx.leaf) {
68 if ctx.can_be_stmt {
69 acc.add(keyword("continue", "continue;"));
70 acc.add(keyword("break", "break;"));
71 } else {
72 acc.add(keyword("continue", "continue"));
73 acc.add(keyword("break", "break"));
74 }
75 }
76 acc.add_all(complete_return(fn_def, ctx.can_be_stmt));
77}
78
79fn is_in_loop_body(leaf: &SyntaxNode) -> bool {
80 for node in leaf.ancestors() {
81 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
82 break;
83 }
84 let loop_body = visitor()
85 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
86 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
87 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
88 .accept(node);
89 if let Some(Some(body)) = loop_body {
90 if leaf.range().is_subrange(&body.syntax().range()) {
91 return true;
92 }
93 }
94 }
95 false
96}
97
98fn complete_return(fn_def: &ast::FnDef, can_be_stmt: bool) -> Option<CompletionItem> {
99 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
100 (true, true) => "return $0;",
101 (true, false) => "return;",
102 (false, true) => "return $0",
103 (false, false) => "return",
104 };
105 Some(keyword("return", snip))
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::completion::{CompletionKind, check_completion};
111 fn check_keyword_completion(code: &str, expected_completions: &str) {
112 check_completion(code, expected_completions, CompletionKind::Keyword);
113 }
114
115 #[test]
116 fn completes_keywords_in_use_stmt() {
117 check_keyword_completion(
118 r"
119 use <|>
120 ",
121 r#"
122 crate "crate" "crate::"
123 self "self"
124 super "super"
125 "#,
126 );
127
128 check_keyword_completion(
129 r"
130 use a::<|>
131 ",
132 r#"
133 self "self"
134 super "super"
135 "#,
136 );
137
138 check_keyword_completion(
139 r"
140 use a::{b, <|>}
141 ",
142 r#"
143 self "self"
144 super "super"
145 "#,
146 );
147 }
148
149 #[test]
150 fn completes_various_keywords_in_function() {
151 check_keyword_completion(
152 r"
153 fn quux() {
154 <|>
155 }
156 ",
157 r#"
158 if "if $0 {}"
159 match "match $0 {}"
160 while "while $0 {}"
161 loop "loop {$0}"
162 return "return;"
163 "#,
164 );
165 }
166
167 #[test]
168 fn completes_else_after_if() {
169 check_keyword_completion(
170 r"
171 fn quux() {
172 if true {
173 ()
174 } <|>
175 }
176 ",
177 r#"
178 if "if $0 {}"
179 match "match $0 {}"
180 while "while $0 {}"
181 loop "loop {$0}"
182 else "else {$0}"
183 else if "else if $0 {}"
184 return "return;"
185 "#,
186 );
187 }
188
189 #[test]
190 fn test_completion_return_value() {
191 check_keyword_completion(
192 r"
193 fn quux() -> i32 {
194 <|>
195 92
196 }
197 ",
198 r#"
199 if "if $0 {}"
200 match "match $0 {}"
201 while "while $0 {}"
202 loop "loop {$0}"
203 return "return $0;"
204 "#,
205 );
206 check_keyword_completion(
207 r"
208 fn quux() {
209 <|>
210 92
211 }
212 ",
213 r#"
214 if "if $0 {}"
215 match "match $0 {}"
216 while "while $0 {}"
217 loop "loop {$0}"
218 return "return;"
219 "#,
220 );
221 }
222
223 #[test]
224 fn dont_add_semi_after_return_if_not_a_statement() {
225 check_keyword_completion(
226 r"
227 fn quux() -> i32 {
228 match () {
229 () => <|>
230 }
231 }
232 ",
233 r#"
234 if "if $0 {}"
235 match "match $0 {}"
236 while "while $0 {}"
237 loop "loop {$0}"
238 return "return $0"
239 "#,
240 );
241 }
242
243 #[test]
244 fn last_return_in_block_has_semi() {
245 check_keyword_completion(
246 r"
247 fn quux() -> i32 {
248 if condition {
249 <|>
250 }
251 }
252 ",
253 r#"
254 if "if $0 {}"
255 match "match $0 {}"
256 while "while $0 {}"
257 loop "loop {$0}"
258 return "return $0;"
259 "#,
260 );
261 check_keyword_completion(
262 r"
263 fn quux() -> i32 {
264 if condition {
265 <|>
266 }
267 let x = 92;
268 x
269 }
270 ",
271 r#"
272 if "if $0 {}"
273 match "match $0 {}"
274 while "while $0 {}"
275 loop "loop {$0}"
276 return "return $0;"
277 "#,
278 );
279 }
280
281 #[test]
282 fn completes_break_and_continue_in_loops() {
283 check_keyword_completion(
284 r"
285 fn quux() -> i32 {
286 loop { <|> }
287 }
288 ",
289 r#"
290 if "if $0 {}"
291 match "match $0 {}"
292 while "while $0 {}"
293 loop "loop {$0}"
294 continue "continue;"
295 break "break;"
296 return "return $0;"
297 "#,
298 );
299 // No completion: lambda isolates control flow
300 check_keyword_completion(
301 r"
302 fn quux() -> i32 {
303 loop { || { <|> } }
304 }
305 ",
306 r#"
307 if "if $0 {}"
308 match "match $0 {}"
309 while "while $0 {}"
310 loop "loop {$0}"
311 return "return $0;"
312 "#,
313 );
314 }
315
316 #[test]
317 fn no_semi_after_break_continue_in_expr() {
318 check_keyword_completion(
319 r"
320 fn f() {
321 loop {
322 match () {
323 () => br<|>
324 }
325 }
326 }
327 ",
328 r#"
329 if "if $0 {}"
330 match "match $0 {}"
331 while "while $0 {}"
332 loop "loop {$0}"
333 continue "continue"
334 break "break"
335 return "return"
336 "#,
337 )
338 }
339}
diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs
new file mode 100644
index 000000000..4723a65a6
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_path.rs
@@ -0,0 +1,128 @@
1use crate::{
2 Cancelable,
3 completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext},
4};
5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
7 let (path, module) = match (&ctx.path_prefix, &ctx.module) {
8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()),
10 };
11 let def_id = match module.resolve_path(ctx.db, &path)?.take_types() {
12 Some(it) => it,
13 None => return Ok(()),
14 };
15 match def_id.resolve(ctx.db)? {
16 hir::Def::Module(module) => {
17 let module_scope = module.scope(ctx.db)?;
18 module_scope.entries().for_each(|(name, res)| {
19 CompletionItem::new(CompletionKind::Reference, name.to_string())
20 .from_resolution(ctx, res)
21 .add_to(acc)
22 });
23 }
24 hir::Def::Enum(e) => e
25 .variants(ctx.db)?
26 .into_iter()
27 .for_each(|(name, _variant)| {
28 CompletionItem::new(CompletionKind::Reference, name.to_string())
29 .kind(CompletionItemKind::EnumVariant)
30 .add_to(acc)
31 }),
32 _ => return Ok(()),
33 };
34 Ok(())
35}
36
37#[cfg(test)]
38mod tests {
39 use crate::completion::{CompletionKind, check_completion};
40
41 fn check_reference_completion(code: &str, expected_completions: &str) {
42 check_completion(code, expected_completions, CompletionKind::Reference);
43 }
44
45 #[test]
46 fn completes_use_item_starting_with_self() {
47 check_reference_completion(
48 r"
49 use self::m::<|>;
50
51 mod m {
52 struct Bar;
53 }
54 ",
55 "Bar",
56 );
57 }
58
59 #[test]
60 fn completes_use_item_starting_with_crate() {
61 check_reference_completion(
62 "
63 //- /lib.rs
64 mod foo;
65 struct Spam;
66 //- /foo.rs
67 use crate::Sp<|>
68 ",
69 "Spam;foo",
70 );
71 }
72
73 #[test]
74 fn completes_nested_use_tree() {
75 check_reference_completion(
76 "
77 //- /lib.rs
78 mod foo;
79 struct Spam;
80 //- /foo.rs
81 use crate::{Sp<|>};
82 ",
83 "Spam;foo",
84 );
85 }
86
87 #[test]
88 fn completes_deeply_nested_use_tree() {
89 check_reference_completion(
90 "
91 //- /lib.rs
92 mod foo;
93 pub mod bar {
94 pub mod baz {
95 pub struct Spam;
96 }
97 }
98 //- /foo.rs
99 use crate::{bar::{baz::Sp<|>}};
100 ",
101 "Spam",
102 );
103 }
104
105 #[test]
106 fn completes_enum_variant() {
107 check_reference_completion(
108 "
109 //- /lib.rs
110 enum E { Foo, Bar(i32) }
111 fn foo() { let _ = E::<|> }
112 ",
113 "Foo;Bar",
114 );
115 }
116
117 #[test]
118 fn dont_render_function_parens_in_use_item() {
119 check_reference_completion(
120 "
121 //- /lib.rs
122 mod m { pub fn foo() {} }
123 use crate::m::f<|>;
124 ",
125 "foo",
126 )
127 }
128}
diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs
new file mode 100644
index 000000000..ee9052d3d
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_scope.rs
@@ -0,0 +1,192 @@
1use rustc_hash::FxHashSet;
2use ra_syntax::TextUnit;
3
4use crate::{
5 Cancelable,
6 completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext},
7};
8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
10 if !ctx.is_trivial_path {
11 return Ok(());
12 }
13 let module = match &ctx.module {
14 Some(it) => it,
15 None => return Ok(()),
16 };
17 if let Some(function) = &ctx.function {
18 let scopes = function.scopes(ctx.db)?;
19 complete_fn(acc, &scopes, ctx.offset);
20 }
21
22 let module_scope = module.scope(ctx.db)?;
23 let (file_id, _) = module.defenition_source(ctx.db)?;
24 module_scope
25 .entries()
26 .filter(|(_name, res)| {
27 // Don't expose this item
28 // FIXME: this penetrates through all kinds of abstractions,
29 // we need to figura out the way to do it less ugly.
30 match res.import {
31 None => true,
32 Some(import) => {
33 let range = import.range(ctx.db, file_id);
34 !range.is_subrange(&ctx.leaf.range())
35 }
36 }
37 })
38 .for_each(|(name, res)| {
39 CompletionItem::new(CompletionKind::Reference, name.to_string())
40 .from_resolution(ctx, res)
41 .add_to(acc)
42 });
43 Ok(())
44}
45
46fn complete_fn(acc: &mut Completions, scopes: &hir::ScopesWithSyntaxMapping, offset: TextUnit) {
47 let mut shadowed = FxHashSet::default();
48 scopes
49 .scope_chain_for_offset(offset)
50 .flat_map(|scope| scopes.scopes.entries(scope).iter())
51 .filter(|entry| shadowed.insert(entry.name()))
52 .for_each(|entry| {
53 CompletionItem::new(CompletionKind::Reference, entry.name().to_string())
54 .kind(CompletionItemKind::Binding)
55 .add_to(acc)
56 });
57}
58
59#[cfg(test)]
60mod tests {
61 use crate::completion::{CompletionKind, check_completion};
62
63 fn check_reference_completion(code: &str, expected_completions: &str) {
64 check_completion(code, expected_completions, CompletionKind::Reference);
65 }
66
67 #[test]
68 fn completes_bindings_from_let() {
69 check_reference_completion(
70 r"
71 fn quux(x: i32) {
72 let y = 92;
73 1 + <|>;
74 let z = ();
75 }
76 ",
77 r#"y;x;quux "quux($0)""#,
78 );
79 }
80
81 #[test]
82 fn completes_bindings_from_if_let() {
83 check_reference_completion(
84 r"
85 fn quux() {
86 if let Some(x) = foo() {
87 let y = 92;
88 };
89 if let Some(a) = bar() {
90 let b = 62;
91 1 + <|>
92 }
93 }
94 ",
95 r#"b;a;quux "quux()$0""#,
96 );
97 }
98
99 #[test]
100 fn completes_bindings_from_for() {
101 check_reference_completion(
102 r"
103 fn quux() {
104 for x in &[1, 2, 3] {
105 <|>
106 }
107 }
108 ",
109 r#"x;quux "quux()$0""#,
110 );
111 }
112
113 #[test]
114 fn completes_module_items() {
115 check_reference_completion(
116 r"
117 struct Foo;
118 enum Baz {}
119 fn quux() {
120 <|>
121 }
122 ",
123 r#"quux "quux()$0";Foo;Baz"#,
124 );
125 }
126
127 #[test]
128 fn completes_module_items_in_nested_modules() {
129 check_reference_completion(
130 r"
131 struct Foo;
132 mod m {
133 struct Bar;
134 fn quux() { <|> }
135 }
136 ",
137 r#"quux "quux()$0";Bar"#,
138 );
139 }
140
141 #[test]
142 fn completes_return_type() {
143 check_reference_completion(
144 r"
145 struct Foo;
146 fn x() -> <|>
147 ",
148 r#"Foo;x "x()$0""#,
149 )
150 }
151
152 #[test]
153 fn dont_show_both_completions_for_shadowing() {
154 check_reference_completion(
155 r"
156 fn foo() -> {
157 let bar = 92;
158 {
159 let bar = 62;
160 <|>
161 }
162 }
163 ",
164 r#"bar;foo "foo()$0""#,
165 )
166 }
167
168 #[test]
169 fn completes_self_in_methods() {
170 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
171 }
172
173 #[test]
174 fn inserts_parens_for_function_calls() {
175 check_reference_completion(
176 r"
177 fn no_args() {}
178 fn main() { no_<|> }
179 ",
180 r#"no_args "no_args()$0"
181 main "main()$0""#,
182 );
183 check_reference_completion(
184 r"
185 fn with_args(x: i32, y: String) {}
186 fn main() { with_<|> }
187 ",
188 r#"main "main()$0"
189 with_args "with_args($0)""#,
190 );
191 }
192}
diff --git a/crates/ra_ide_api/src/completion/complete_snippet.rs b/crates/ra_ide_api/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..a495751dd
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/complete_snippet.rs
@@ -0,0 +1,73 @@
1use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionItemKind, CompletionContext, completion_item::Builder};
2
3fn snippet(label: &str, snippet: &str) -> Builder {
4 CompletionItem::new(CompletionKind::Snippet, label)
5 .snippet(snippet)
6 .kind(CompletionItemKind::Snippet)
7}
8
9pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
11 return;
12 }
13 snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
14 snippet("ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
15}
16
17pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_new_item {
19 return;
20 }
21 snippet(
22 "Test function",
23 "\
24#[test]
25fn ${1:feature}() {
26 $0
27}",
28 )
29 .lookup_by("tfn")
30 .add_to(acc);
31
32 snippet("pub(crate)", "pub(crate) $0").add_to(acc);
33}
34
35#[cfg(test)]
36mod tests {
37 use crate::completion::{CompletionKind, check_completion};
38 fn check_snippet_completion(code: &str, expected_completions: &str) {
39 check_completion(code, expected_completions, CompletionKind::Snippet);
40 }
41
42 #[test]
43 fn completes_snippets_in_expressions() {
44 check_snippet_completion(
45 r"fn foo(x: i32) { <|> }",
46 r##"
47 pd "eprintln!(\"$0 = {:?}\", $0);"
48 ppd "eprintln!(\"$0 = {:#?}\", $0);"
49 "##,
50 );
51 }
52
53 #[test]
54 fn completes_snippets_in_items() {
55 // check_snippet_completion(r"
56 // <|>
57 // ",
58 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
59 // );
60 check_snippet_completion(
61 r"
62 #[cfg(test)]
63 mod tests {
64 <|>
65 }
66 ",
67 r##"
68 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
69 pub(crate) "pub(crate) $0"
70 "##,
71 );
72 }
73}
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs
new file mode 100644
index 000000000..01786bb69
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/completion_context.rs
@@ -0,0 +1,205 @@
1use ra_text_edit::AtomTextEdit;
2use ra_syntax::{
3 AstNode, SyntaxNode, SourceFile, TextUnit, TextRange,
4 ast,
5 algo::{find_leaf_at_offset, find_covering_node, find_node_at_offset},
6 SyntaxKind::*,
7};
8use hir::source_binder;
9
10use crate::{db, FilePosition, Cancelable};
11
12/// `CompletionContext` is created early during completion to figure out, where
13/// exactly is the cursor, syntax-wise.
14#[derive(Debug)]
15pub(super) struct CompletionContext<'a> {
16 pub(super) db: &'a db::RootDatabase,
17 pub(super) offset: TextUnit,
18 pub(super) leaf: &'a SyntaxNode,
19 pub(super) module: Option<hir::Module>,
20 pub(super) function: Option<hir::Function>,
21 pub(super) function_syntax: Option<&'a ast::FnDef>,
22 pub(super) use_item_syntax: Option<&'a ast::UseItem>,
23 pub(super) is_param: bool,
24 /// A single-indent path, like `foo`.
25 pub(super) is_trivial_path: bool,
26 /// If not a trivial, path, the prefix (qualifier).
27 pub(super) path_prefix: Option<hir::Path>,
28 pub(super) after_if: bool,
29 /// `true` if we are a statement or a last expr in the block.
30 pub(super) can_be_stmt: bool,
31 /// Something is typed at the "top" level, in module or impl/trait.
32 pub(super) is_new_item: bool,
33 /// The receiver if this is a field or method access, i.e. writing something.<|>
34 pub(super) dot_receiver: Option<&'a ast::Expr>,
35 /// If this is a method call in particular, i.e. the () are already there.
36 pub(super) is_method_call: bool,
37}
38
39impl<'a> CompletionContext<'a> {
40 pub(super) fn new(
41 db: &'a db::RootDatabase,
42 original_file: &'a SourceFile,
43 position: FilePosition,
44 ) -> Cancelable<Option<CompletionContext<'a>>> {
45 let module = source_binder::module_from_position(db, position)?;
46 let leaf =
47 ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
48 let mut ctx = CompletionContext {
49 db,
50 leaf,
51 offset: position.offset,
52 module,
53 function: None,
54 function_syntax: None,
55 use_item_syntax: None,
56 is_param: false,
57 is_trivial_path: false,
58 path_prefix: None,
59 after_if: false,
60 can_be_stmt: false,
61 is_new_item: false,
62 dot_receiver: None,
63 is_method_call: false,
64 };
65 ctx.fill(original_file, position.offset);
66 Ok(Some(ctx))
67 }
68
69 fn fill(&mut self, original_file: &'a SourceFile, offset: TextUnit) {
70 // Insert a fake ident to get a valid parse tree. We will use this file
71 // to determine context, though the original_file will be used for
72 // actual completion.
73 let file = {
74 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
75 original_file.reparse(&edit)
76 };
77
78 // First, let's try to complete a reference to some declaration.
79 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
80 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
81 // See RFC#1685.
82 if is_node::<ast::Param>(name_ref.syntax()) {
83 self.is_param = true;
84 return;
85 }
86 self.classify_name_ref(original_file, name_ref);
87 }
88
89 // Otherwise, see if this is a declaration. We can use heuristics to
90 // suggest declaration names, see `CompletionKind::Magic`.
91 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
92 if is_node::<ast::Param>(name.syntax()) {
93 self.is_param = true;
94 return;
95 }
96 }
97 }
98 fn classify_name_ref(&mut self, original_file: &'a SourceFile, name_ref: &ast::NameRef) {
99 let name_range = name_ref.syntax().range();
100 let top_node = name_ref
101 .syntax()
102 .ancestors()
103 .take_while(|it| it.range() == name_range)
104 .last()
105 .unwrap();
106
107 match top_node.parent().map(|it| it.kind()) {
108 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
109 self.is_new_item = true;
110 return;
111 }
112 _ => (),
113 }
114
115 self.use_item_syntax = self.leaf.ancestors().find_map(ast::UseItem::cast);
116
117 self.function_syntax = self
118 .leaf
119 .ancestors()
120 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
121 .find_map(ast::FnDef::cast);
122 match (&self.module, self.function_syntax) {
123 (Some(module), Some(fn_def)) => {
124 let function = source_binder::function_from_module(self.db, module, fn_def);
125 self.function = Some(function);
126 }
127 _ => (),
128 }
129
130 let parent = match name_ref.syntax().parent() {
131 Some(it) => it,
132 None => return,
133 };
134 if let Some(segment) = ast::PathSegment::cast(parent) {
135 let path = segment.parent_path();
136 if let Some(mut path) = hir::Path::from_ast(path) {
137 if !path.is_ident() {
138 path.segments.pop().unwrap();
139 self.path_prefix = Some(path);
140 return;
141 }
142 }
143 if path.qualifier().is_none() {
144 self.is_trivial_path = true;
145
146 // Find either enclosing expr statement (thing with `;`) or a
147 // block. If block, check that we are the last expr.
148 self.can_be_stmt = name_ref
149 .syntax()
150 .ancestors()
151 .find_map(|node| {
152 if let Some(stmt) = ast::ExprStmt::cast(node) {
153 return Some(stmt.syntax().range() == name_ref.syntax().range());
154 }
155 if let Some(block) = ast::Block::cast(node) {
156 return Some(
157 block.expr().map(|e| e.syntax().range())
158 == Some(name_ref.syntax().range()),
159 );
160 }
161 None
162 })
163 .unwrap_or(false);
164
165 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
166 if let Some(if_expr) =
167 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off)
168 {
169 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
170 self.after_if = true;
171 }
172 }
173 }
174 }
175 }
176 if let Some(field_expr) = ast::FieldExpr::cast(parent) {
177 // The receiver comes before the point of insertion of the fake
178 // ident, so it should have the same range in the non-modified file
179 self.dot_receiver = field_expr
180 .expr()
181 .map(|e| e.syntax().range())
182 .and_then(|r| find_node_with_range(original_file.syntax(), r));
183 }
184 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
185 // As above
186 self.dot_receiver = method_call_expr
187 .expr()
188 .map(|e| e.syntax().range())
189 .and_then(|r| find_node_with_range(original_file.syntax(), r));
190 self.is_method_call = true;
191 }
192 }
193}
194
195fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<&N> {
196 let node = find_covering_node(syntax, range);
197 node.ancestors().find_map(N::cast)
198}
199
200fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
201 match node.ancestors().filter_map(N::cast).next() {
202 None => false,
203 Some(n) => n.syntax().range() == node.range(),
204 }
205}
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
new file mode 100644
index 000000000..a25b87bee
--- /dev/null
+++ b/crates/ra_ide_api/src/completion/completion_item.rs
@@ -0,0 +1,244 @@
1use hir::PerNs;
2
3use crate::completion::CompletionContext;
4
5/// `CompletionItem` describes a single completion variant in the editor pop-up.
6/// It is basically a POD with various properties. To construct a
7/// `CompletionItem`, use `new` method and the `Builder` struct.
8#[derive(Debug)]
9pub struct CompletionItem {
10 /// Used only internally in tests, to check only specific kind of
11 /// completion.
12 completion_kind: CompletionKind,
13 label: String,
14 lookup: Option<String>,
15 snippet: Option<String>,
16 kind: Option<CompletionItemKind>,
17}
18
19pub enum InsertText {
20 PlainText { text: String },
21 Snippet { text: String },
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum CompletionItemKind {
26 Snippet,
27 Keyword,
28 Module,
29 Function,
30 Struct,
31 Enum,
32 EnumVariant,
33 Binding,
34 Field,
35}
36
37#[derive(Debug, PartialEq, Eq)]
38pub(crate) enum CompletionKind {
39 /// Parser-based keyword completion.
40 Keyword,
41 /// Your usual "complete all valid identifiers".
42 Reference,
43 /// "Secret sauce" completions.
44 Magic,
45 Snippet,
46}
47
48impl CompletionItem {
49 pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder {
50 let label = label.into();
51 Builder {
52 completion_kind,
53 label,
54 lookup: None,
55 snippet: None,
56 kind: None,
57 }
58 }
59 /// What user sees in pop-up in the UI.
60 pub fn label(&self) -> &str {
61 &self.label
62 }
63 /// What string is used for filtering.
64 pub fn lookup(&self) -> &str {
65 self.lookup
66 .as_ref()
67 .map(|it| it.as_str())
68 .unwrap_or(self.label())
69 }
70 /// What is inserted.
71 pub fn insert_text(&self) -> InsertText {
72 match &self.snippet {
73 None => InsertText::PlainText {
74 text: self.label.clone(),
75 },
76 Some(it) => InsertText::Snippet { text: it.clone() },
77 }
78 }
79
80 pub fn kind(&self) -> Option<CompletionItemKind> {
81 self.kind
82 }
83}
84
85/// A helper to make `CompletionItem`s.
86#[must_use]
87pub(crate) struct Builder {
88 completion_kind: CompletionKind,
89 label: String,
90 lookup: Option<String>,
91 snippet: Option<String>,
92 kind: Option<CompletionItemKind>,
93}
94
95impl Builder {
96 pub(crate) fn add_to(self, acc: &mut Completions) {
97 acc.add(self.build())
98 }
99
100 pub(crate) fn build(self) -> CompletionItem {
101 CompletionItem {
102 label: self.label,
103 lookup: self.lookup,
104 snippet: self.snippet,
105 kind: self.kind,
106 completion_kind: self.completion_kind,
107 }
108 }
109 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
110 self.lookup = Some(lookup.into());
111 self
112 }
113 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
114 self.snippet = Some(snippet.into());
115 self
116 }
117 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
118 self.kind = Some(kind);
119 self
120 }
121 pub(super) fn from_resolution(
122 mut self,
123 ctx: &CompletionContext,
124 resolution: &hir::Resolution,
125 ) -> Builder {
126 let resolved = resolution.def_id.and_then(|d| d.resolve(ctx.db).ok());
127 let kind = match resolved {
128 PerNs {
129 types: Some(hir::Def::Module(..)),
130 ..
131 } => CompletionItemKind::Module,
132 PerNs {
133 types: Some(hir::Def::Struct(..)),
134 ..
135 } => CompletionItemKind::Struct,
136 PerNs {
137 types: Some(hir::Def::Enum(..)),
138 ..
139 } => CompletionItemKind::Enum,
140 PerNs {
141 values: Some(hir::Def::Function(function)),
142 ..
143 } => return self.from_function(ctx, function),
144 _ => return self,
145 };
146 self.kind = Some(kind);
147 self
148 }
149
150 fn from_function(mut self, ctx: &CompletionContext, function: hir::Function) -> Builder {
151 // If not an import, add parenthesis automatically.
152 if ctx.use_item_syntax.is_none() {
153 if function.signature(ctx.db).args().is_empty() {
154 self.snippet = Some(format!("{}()$0", self.label));
155 } else {
156 self.snippet = Some(format!("{}($0)", self.label));
157 }
158 }
159 self.kind = Some(CompletionItemKind::Function);
160 self
161 }
162}
163
164impl Into<CompletionItem> for Builder {
165 fn into(self) -> CompletionItem {
166 self.build()
167 }
168}
169
170/// Represents an in-progress set of completions being built.
171#[derive(Debug, Default)]
172pub(crate) struct Completions {
173 buf: Vec<CompletionItem>,
174}
175
176impl Completions {
177 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
178 self.buf.push(item.into())
179 }
180 pub(crate) fn add_all<I>(&mut self, items: I)
181 where
182 I: IntoIterator,
183 I::Item: Into<CompletionItem>,
184 {
185 items.into_iter().for_each(|item| self.add(item.into()))
186 }
187
188 #[cfg(test)]
189 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
190 let expected = normalize(expected);
191 let actual = self.debug_render(kind);
192 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
193
194 /// Normalize the textual representation of `Completions`:
195 /// replace `;` with newlines, normalize whitespace
196 fn normalize(expected: &str) -> String {
197 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
198 let mut res = String::new();
199 for line in expected.trim().lines() {
200 let line = line.trim();
201 let mut start_offset: TextUnit = 0.into();
202 // Yep, we use rust tokenize in completion tests :-)
203 for token in tokenize(line) {
204 let range = TextRange::offset_len(start_offset, token.len);
205 start_offset += token.len;
206 if token.kind == SEMI {
207 res.push('\n');
208 } else {
209 res.push_str(&line[range]);
210 }
211 }
212
213 res.push('\n');
214 }
215 res
216 }
217 }
218
219 #[cfg(test)]
220 fn debug_render(&self, kind: CompletionKind) -> String {
221 let mut res = String::new();
222 for c in self.buf.iter() {
223 if c.completion_kind == kind {
224 if let Some(lookup) = &c.lookup {
225 res.push_str(lookup);
226 res.push_str(&format!(" {:?}", c.label));
227 } else {
228 res.push_str(&c.label);
229 }
230 if let Some(snippet) = &c.snippet {
231 res.push_str(&format!(" {:?}", snippet));
232 }
233 res.push('\n');
234 }
235 }
236 res
237 }
238}
239
240impl Into<Vec<CompletionItem>> for Completions {
241 fn into(self) -> Vec<CompletionItem> {
242 self.buf
243 }
244}