aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/completion')
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs456
-rw-r--r--crates/ra_ide/src/completion/complete_fn_param.rs136
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs781
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs143
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs785
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs89
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs282
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs159
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs93
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs876
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs120
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs274
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs322
-rw-r--r--crates/ra_ide/src/completion/presentation.rs676
14 files changed, 5192 insertions, 0 deletions
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
new file mode 100644
index 000000000..b6fe48627
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -0,0 +1,456 @@
1//! FIXME: write short doc here
2
3use hir::Type;
4
5use crate::completion::completion_item::CompletionKind;
6use crate::{
7 completion::{completion_context::CompletionContext, completion_item::Completions},
8 CompletionItem,
9};
10use rustc_hash::FxHashSet;
11
12/// Complete dot accesses, i.e. fields or methods (and .await syntax).
13pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
14 let dot_receiver = match &ctx.dot_receiver {
15 Some(expr) => expr,
16 _ => return,
17 };
18
19 let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) {
20 Some(ty) => ty,
21 _ => return,
22 };
23
24 if !ctx.is_call {
25 complete_fields(acc, ctx, &receiver_ty);
26 }
27 complete_methods(acc, ctx, &receiver_ty);
28
29 // Suggest .await syntax for types that implement Future trait
30 if ctx.analyzer.impls_future(ctx.db, receiver_ty.into_ty()) {
31 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await")
32 .detail("expr.await")
33 .insert_text("await")
34 .add_to(acc);
35 }
36}
37
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) {
41 acc.add_field(ctx, field, &ty);
42 }
43 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
44 acc.add_tuple_field(ctx, i, &ty);
45 }
46 }
47}
48
49fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
50 let mut seen_methods = FxHashSet::default();
51 ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| {
52 if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) {
53 acc.add_function(ctx, func);
54 }
55 None::<()>
56 });
57}
58
59#[cfg(test)]
60mod tests {
61 use crate::completion::{do_completion, CompletionItem, CompletionKind};
62 use insta::assert_debug_snapshot;
63
64 fn do_ref_completion(code: &str) -> Vec<CompletionItem> {
65 do_completion(code, CompletionKind::Reference)
66 }
67
68 #[test]
69 fn test_struct_field_completion() {
70 assert_debug_snapshot!(
71 do_ref_completion(
72 r"
73 struct A { the_field: u32 }
74 fn foo(a: A) {
75 a.<|>
76 }
77 ",
78 ),
79 @r###"
80 [
81 CompletionItem {
82 label: "the_field",
83 source_range: [94; 94),
84 delete: [94; 94),
85 insert: "the_field",
86 kind: Field,
87 detail: "u32",
88 },
89 ]
90 "###
91 );
92 }
93
94 #[test]
95 fn test_struct_field_completion_self() {
96 assert_debug_snapshot!(
97 do_ref_completion(
98 r"
99 struct A {
100 /// This is the_field
101 the_field: (u32,)
102 }
103 impl A {
104 fn foo(self) {
105 self.<|>
106 }
107 }
108 ",
109 ),
110 @r###"
111 [
112 CompletionItem {
113 label: "foo()",
114 source_range: [187; 187),
115 delete: [187; 187),
116 insert: "foo()$0",
117 kind: Method,
118 lookup: "foo",
119 detail: "fn foo(self)",
120 },
121 CompletionItem {
122 label: "the_field",
123 source_range: [187; 187),
124 delete: [187; 187),
125 insert: "the_field",
126 kind: Field,
127 detail: "(u32,)",
128 documentation: Documentation(
129 "This is the_field",
130 ),
131 },
132 ]
133 "###
134 );
135 }
136
137 #[test]
138 fn test_struct_field_completion_autoderef() {
139 assert_debug_snapshot!(
140 do_ref_completion(
141 r"
142 struct A { the_field: (u32, i32) }
143 impl A {
144 fn foo(&self) {
145 self.<|>
146 }
147 }
148 ",
149 ),
150 @r###"
151 [
152 CompletionItem {
153 label: "foo()",
154 source_range: [126; 126),
155 delete: [126; 126),
156 insert: "foo()$0",
157 kind: Method,
158 lookup: "foo",
159 detail: "fn foo(&self)",
160 },
161 CompletionItem {
162 label: "the_field",
163 source_range: [126; 126),
164 delete: [126; 126),
165 insert: "the_field",
166 kind: Field,
167 detail: "(u32, i32)",
168 },
169 ]
170 "###
171 );
172 }
173
174 #[test]
175 fn test_no_struct_field_completion_for_method_call() {
176 assert_debug_snapshot!(
177 do_ref_completion(
178 r"
179 struct A { the_field: u32 }
180 fn foo(a: A) {
181 a.<|>()
182 }
183 ",
184 ),
185 @"[]"
186 );
187 }
188
189 #[test]
190 fn test_method_completion() {
191 assert_debug_snapshot!(
192 do_ref_completion(
193 r"
194 struct A {}
195 impl A {
196 fn the_method(&self) {}
197 }
198 fn foo(a: A) {
199 a.<|>
200 }
201 ",
202 ),
203 @r###"
204 [
205 CompletionItem {
206 label: "the_method()",
207 source_range: [144; 144),
208 delete: [144; 144),
209 insert: "the_method()$0",
210 kind: Method,
211 lookup: "the_method",
212 detail: "fn the_method(&self)",
213 },
214 ]
215 "###
216 );
217 }
218
219 #[test]
220 fn test_trait_method_completion() {
221 assert_debug_snapshot!(
222 do_ref_completion(
223 r"
224 struct A {}
225 trait Trait { fn the_method(&self); }
226 impl Trait for A {}
227 fn foo(a: A) {
228 a.<|>
229 }
230 ",
231 ),
232 @r###"
233 [
234 CompletionItem {
235 label: "the_method()",
236 source_range: [151; 151),
237 delete: [151; 151),
238 insert: "the_method()$0",
239 kind: Method,
240 lookup: "the_method",
241 detail: "fn the_method(&self)",
242 },
243 ]
244 "###
245 );
246 }
247
248 #[test]
249 fn test_trait_method_completion_deduplicated() {
250 assert_debug_snapshot!(
251 do_ref_completion(
252 r"
253 struct A {}
254 trait Trait { fn the_method(&self); }
255 impl<T> Trait for T {}
256 fn foo(a: &A) {
257 a.<|>
258 }
259 ",
260 ),
261 @r###"
262 [
263 CompletionItem {
264 label: "the_method()",
265 source_range: [155; 155),
266 delete: [155; 155),
267 insert: "the_method()$0",
268 kind: Method,
269 lookup: "the_method",
270 detail: "fn the_method(&self)",
271 },
272 ]
273 "###
274 );
275 }
276
277 #[test]
278 fn test_no_non_self_method() {
279 assert_debug_snapshot!(
280 do_ref_completion(
281 r"
282 struct A {}
283 impl A {
284 fn the_method() {}
285 }
286 fn foo(a: A) {
287 a.<|>
288 }
289 ",
290 ),
291 @"[]"
292 );
293 }
294
295 #[test]
296 fn test_method_attr_filtering() {
297 assert_debug_snapshot!(
298 do_ref_completion(
299 r"
300 struct A {}
301 impl A {
302 #[inline]
303 fn the_method(&self) {
304 let x = 1;
305 let y = 2;
306 }
307 }
308 fn foo(a: A) {
309 a.<|>
310 }
311 ",
312 ),
313 @r###"
314 [
315 CompletionItem {
316 label: "the_method()",
317 source_range: [249; 249),
318 delete: [249; 249),
319 insert: "the_method()$0",
320 kind: Method,
321 lookup: "the_method",
322 detail: "fn the_method(&self)",
323 },
324 ]
325 "###
326 );
327 }
328
329 #[test]
330 fn test_tuple_field_completion() {
331 assert_debug_snapshot!(
332 do_ref_completion(
333 r"
334 fn foo() {
335 let b = (0, 3.14);
336 b.<|>
337 }
338 ",
339 ),
340 @r###"
341 [
342 CompletionItem {
343 label: "0",
344 source_range: [75; 75),
345 delete: [75; 75),
346 insert: "0",
347 kind: Field,
348 detail: "i32",
349 },
350 CompletionItem {
351 label: "1",
352 source_range: [75; 75),
353 delete: [75; 75),
354 insert: "1",
355 kind: Field,
356 detail: "f64",
357 },
358 ]
359 "###
360 );
361 }
362
363 #[test]
364 fn test_tuple_field_inference() {
365 assert_debug_snapshot!(
366 do_ref_completion(
367 r"
368 pub struct S;
369 impl S {
370 pub fn blah(&self) {}
371 }
372
373 struct T(S);
374
375 impl T {
376 fn foo(&self) {
377 // FIXME: This doesn't work without the trailing `a` as `0.` is a float
378 self.0.a<|>
379 }
380 }
381 ",
382 ),
383 @r###"
384 [
385 CompletionItem {
386 label: "blah()",
387 source_range: [299; 300),
388 delete: [299; 300),
389 insert: "blah()$0",
390 kind: Method,
391 lookup: "blah",
392 detail: "pub fn blah(&self)",
393 },
394 ]
395 "###
396 );
397 }
398
399 #[test]
400 fn test_completion_works_in_consts() {
401 assert_debug_snapshot!(
402 do_ref_completion(
403 r"
404 struct A { the_field: u32 }
405 const X: u32 = {
406 A { the_field: 92 }.<|>
407 };
408 ",
409 ),
410 @r###"
411 [
412 CompletionItem {
413 label: "the_field",
414 source_range: [106; 106),
415 delete: [106; 106),
416 insert: "the_field",
417 kind: Field,
418 detail: "u32",
419 },
420 ]
421 "###
422 );
423 }
424
425 #[test]
426 fn test_completion_await_impls_future() {
427 assert_debug_snapshot!(
428 do_completion(
429 r###"
430 //- /main.rs
431 use std::future::*;
432 struct A {}
433 impl Future for A {}
434 fn foo(a: A) {
435 a.<|>
436 }
437
438 //- /std/lib.rs
439 pub mod future {
440 pub trait Future {}
441 }
442 "###, CompletionKind::Keyword),
443 @r###"
444 [
445 CompletionItem {
446 label: "await",
447 source_range: [74; 74),
448 delete: [74; 74),
449 insert: "await",
450 detail: "expr.await",
451 },
452 ]
453 "###
454 )
455 }
456}
diff --git a/crates/ra_ide/src/completion/complete_fn_param.rs b/crates/ra_ide/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..502458706
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_fn_param.rs
@@ -0,0 +1,136 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{ast, match_ast, AstNode};
4use rustc_hash::FxHashMap;
5
6use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
7
8/// Complete repeated parameters, both name and type. For example, if all
9/// functions in a file have a `spam: &mut Spam` parameter, a completion with
10/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
11/// suggested.
12pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
13 if !ctx.is_param {
14 return;
15 }
16
17 let mut params = FxHashMap::default();
18 for node in ctx.token.parent().ancestors() {
19 match_ast! {
20 match node {
21 ast::SourceFile(it) => { process(it, &mut params) },
22 ast::ItemList(it) => { process(it, &mut params) },
23 _ => (),
24 }
25 }
26 }
27 params
28 .into_iter()
29 .filter_map(|(label, (count, param))| {
30 let lookup = param.pat()?.syntax().text().to_string();
31 if count < 2 {
32 None
33 } else {
34 Some((label, lookup))
35 }
36 })
37 .for_each(|(label, lookup)| {
38 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
39 .lookup_by(lookup)
40 .add_to(acc)
41 });
42
43 fn process<N: ast::FnDefOwner>(node: N, params: &mut FxHashMap<String, (u32, ast::Param)>) {
44 node.functions().filter_map(|it| it.param_list()).flat_map(|it| it.params()).for_each(
45 |param| {
46 let text = param.syntax().text().to_string();
47 params.entry(text).or_insert((0, param)).0 += 1;
48 },
49 )
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::completion::{do_completion, CompletionItem, CompletionKind};
56 use insta::assert_debug_snapshot;
57
58 fn do_magic_completion(code: &str) -> Vec<CompletionItem> {
59 do_completion(code, CompletionKind::Magic)
60 }
61
62 #[test]
63 fn test_param_completion_last_param() {
64 assert_debug_snapshot!(
65 do_magic_completion(
66 r"
67 fn foo(file_id: FileId) {}
68 fn bar(file_id: FileId) {}
69 fn baz(file<|>) {}
70 ",
71 ),
72 @r###"
73 [
74 CompletionItem {
75 label: "file_id: FileId",
76 source_range: [110; 114),
77 delete: [110; 114),
78 insert: "file_id: FileId",
79 lookup: "file_id",
80 },
81 ]
82 "###
83 );
84 }
85
86 #[test]
87 fn test_param_completion_nth_param() {
88 assert_debug_snapshot!(
89 do_magic_completion(
90 r"
91 fn foo(file_id: FileId) {}
92 fn bar(file_id: FileId) {}
93 fn baz(file<|>, x: i32) {}
94 ",
95 ),
96 @r###"
97 [
98 CompletionItem {
99 label: "file_id: FileId",
100 source_range: [110; 114),
101 delete: [110; 114),
102 insert: "file_id: FileId",
103 lookup: "file_id",
104 },
105 ]
106 "###
107 );
108 }
109
110 #[test]
111 fn test_param_completion_trait_param() {
112 assert_debug_snapshot!(
113 do_magic_completion(
114 r"
115 pub(crate) trait SourceRoot {
116 pub fn contains(&self, file_id: FileId) -> bool;
117 pub fn module_map(&self) -> &ModuleMap;
118 pub fn lines(&self, file_id: FileId) -> &LineIndex;
119 pub fn syntax(&self, file<|>)
120 }
121 ",
122 ),
123 @r###"
124 [
125 CompletionItem {
126 label: "file_id: FileId",
127 source_range: [289; 293),
128 delete: [289; 293),
129 insert: "file_id: FileId",
130 lookup: "file_id",
131 },
132 ]
133 "###
134 );
135 }
136}
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..eb7cd9ac2
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -0,0 +1,781 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{
4 ast::{self, LoopBodyOwner},
5 match_ast, AstNode,
6 SyntaxKind::*,
7 SyntaxToken,
8};
9
10use crate::completion::{
11 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
12};
13
14pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
15 // complete keyword "crate" in use stmt
16 let source_range = ctx.source_range();
17 match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) {
18 (Some(_), None) => {
19 CompletionItem::new(CompletionKind::Keyword, source_range, "crate")
20 .kind(CompletionItemKind::Keyword)
21 .insert_text("crate::")
22 .add_to(acc);
23 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
24 .kind(CompletionItemKind::Keyword)
25 .add_to(acc);
26 CompletionItem::new(CompletionKind::Keyword, source_range, "super")
27 .kind(CompletionItemKind::Keyword)
28 .insert_text("super::")
29 .add_to(acc);
30 }
31 (Some(_), Some(_)) => {
32 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
33 .kind(CompletionItemKind::Keyword)
34 .add_to(acc);
35 CompletionItem::new(CompletionKind::Keyword, source_range, "super")
36 .kind(CompletionItemKind::Keyword)
37 .insert_text("super::")
38 .add_to(acc);
39 }
40 _ => {}
41 }
42}
43
44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
45 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
46 .kind(CompletionItemKind::Keyword)
47 .insert_snippet(snippet)
48 .build()
49}
50
51pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
52 if !ctx.is_trivial_path {
53 return;
54 }
55
56 let fn_def = match &ctx.function_syntax {
57 Some(it) => it,
58 None => return,
59 };
60 acc.add(keyword(ctx, "if", "if $0 {}"));
61 acc.add(keyword(ctx, "match", "match $0 {}"));
62 acc.add(keyword(ctx, "while", "while $0 {}"));
63 acc.add(keyword(ctx, "loop", "loop {$0}"));
64
65 if ctx.after_if {
66 acc.add(keyword(ctx, "else", "else {$0}"));
67 acc.add(keyword(ctx, "else if", "else if $0 {}"));
68 }
69 if is_in_loop_body(&ctx.token) {
70 if ctx.can_be_stmt {
71 acc.add(keyword(ctx, "continue", "continue;"));
72 acc.add(keyword(ctx, "break", "break;"));
73 } else {
74 acc.add(keyword(ctx, "continue", "continue"));
75 acc.add(keyword(ctx, "break", "break"));
76 }
77 }
78 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
79}
80
81fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82 for node in leaf.parent().ancestors() {
83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
84 break;
85 }
86 let loop_body = match_ast! {
87 match node {
88 ast::ForExpr(it) => { it.loop_body() },
89 ast::WhileExpr(it) => { it.loop_body() },
90 ast::LoopExpr(it) => { it.loop_body() },
91 _ => None,
92 }
93 };
94 if let Some(body) = loop_body {
95 if leaf.text_range().is_subrange(&body.syntax().text_range()) {
96 return true;
97 }
98 }
99 }
100 false
101}
102
103fn complete_return(
104 ctx: &CompletionContext,
105 fn_def: &ast::FnDef,
106 can_be_stmt: bool,
107) -> Option<CompletionItem> {
108 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
109 (true, true) => "return $0;",
110 (true, false) => "return;",
111 (false, true) => "return $0",
112 (false, false) => "return",
113 };
114 Some(keyword(ctx, "return", snip))
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::completion::{do_completion, CompletionItem, CompletionKind};
120 use insta::assert_debug_snapshot;
121
122 fn do_keyword_completion(code: &str) -> Vec<CompletionItem> {
123 do_completion(code, CompletionKind::Keyword)
124 }
125
126 #[test]
127 fn completes_keywords_in_use_stmt() {
128 assert_debug_snapshot!(
129 do_keyword_completion(
130 r"
131 use <|>
132 ",
133 ),
134 @r###"
135 [
136 CompletionItem {
137 label: "crate",
138 source_range: [21; 21),
139 delete: [21; 21),
140 insert: "crate::",
141 kind: Keyword,
142 },
143 CompletionItem {
144 label: "self",
145 source_range: [21; 21),
146 delete: [21; 21),
147 insert: "self",
148 kind: Keyword,
149 },
150 CompletionItem {
151 label: "super",
152 source_range: [21; 21),
153 delete: [21; 21),
154 insert: "super::",
155 kind: Keyword,
156 },
157 ]
158 "###
159 );
160
161 assert_debug_snapshot!(
162 do_keyword_completion(
163 r"
164 use a::<|>
165 ",
166 ),
167 @r###"
168 [
169 CompletionItem {
170 label: "self",
171 source_range: [24; 24),
172 delete: [24; 24),
173 insert: "self",
174 kind: Keyword,
175 },
176 CompletionItem {
177 label: "super",
178 source_range: [24; 24),
179 delete: [24; 24),
180 insert: "super::",
181 kind: Keyword,
182 },
183 ]
184 "###
185 );
186
187 assert_debug_snapshot!(
188 do_keyword_completion(
189 r"
190 use a::{b, <|>}
191 ",
192 ),
193 @r###"
194 [
195 CompletionItem {
196 label: "self",
197 source_range: [28; 28),
198 delete: [28; 28),
199 insert: "self",
200 kind: Keyword,
201 },
202 CompletionItem {
203 label: "super",
204 source_range: [28; 28),
205 delete: [28; 28),
206 insert: "super::",
207 kind: Keyword,
208 },
209 ]
210 "###
211 );
212 }
213
214 #[test]
215 fn completes_various_keywords_in_function() {
216 assert_debug_snapshot!(
217 do_keyword_completion(
218 r"
219 fn quux() {
220 <|>
221 }
222 ",
223 ),
224 @r###"
225 [
226 CompletionItem {
227 label: "if",
228 source_range: [49; 49),
229 delete: [49; 49),
230 insert: "if $0 {}",
231 kind: Keyword,
232 },
233 CompletionItem {
234 label: "loop",
235 source_range: [49; 49),
236 delete: [49; 49),
237 insert: "loop {$0}",
238 kind: Keyword,
239 },
240 CompletionItem {
241 label: "match",
242 source_range: [49; 49),
243 delete: [49; 49),
244 insert: "match $0 {}",
245 kind: Keyword,
246 },
247 CompletionItem {
248 label: "return",
249 source_range: [49; 49),
250 delete: [49; 49),
251 insert: "return;",
252 kind: Keyword,
253 },
254 CompletionItem {
255 label: "while",
256 source_range: [49; 49),
257 delete: [49; 49),
258 insert: "while $0 {}",
259 kind: Keyword,
260 },
261 ]
262 "###
263 );
264 }
265
266 #[test]
267 fn completes_else_after_if() {
268 assert_debug_snapshot!(
269 do_keyword_completion(
270 r"
271 fn quux() {
272 if true {
273 ()
274 } <|>
275 }
276 ",
277 ),
278 @r###"
279 [
280 CompletionItem {
281 label: "else",
282 source_range: [108; 108),
283 delete: [108; 108),
284 insert: "else {$0}",
285 kind: Keyword,
286 },
287 CompletionItem {
288 label: "else if",
289 source_range: [108; 108),
290 delete: [108; 108),
291 insert: "else if $0 {}",
292 kind: Keyword,
293 },
294 CompletionItem {
295 label: "if",
296 source_range: [108; 108),
297 delete: [108; 108),
298 insert: "if $0 {}",
299 kind: Keyword,
300 },
301 CompletionItem {
302 label: "loop",
303 source_range: [108; 108),
304 delete: [108; 108),
305 insert: "loop {$0}",
306 kind: Keyword,
307 },
308 CompletionItem {
309 label: "match",
310 source_range: [108; 108),
311 delete: [108; 108),
312 insert: "match $0 {}",
313 kind: Keyword,
314 },
315 CompletionItem {
316 label: "return",
317 source_range: [108; 108),
318 delete: [108; 108),
319 insert: "return;",
320 kind: Keyword,
321 },
322 CompletionItem {
323 label: "while",
324 source_range: [108; 108),
325 delete: [108; 108),
326 insert: "while $0 {}",
327 kind: Keyword,
328 },
329 ]
330 "###
331 );
332 }
333
334 #[test]
335 fn test_completion_return_value() {
336 assert_debug_snapshot!(
337 do_keyword_completion(
338 r"
339 fn quux() -> i32 {
340 <|>
341 92
342 }
343 ",
344 ),
345 @r###"
346 [
347 CompletionItem {
348 label: "if",
349 source_range: [56; 56),
350 delete: [56; 56),
351 insert: "if $0 {}",
352 kind: Keyword,
353 },
354 CompletionItem {
355 label: "loop",
356 source_range: [56; 56),
357 delete: [56; 56),
358 insert: "loop {$0}",
359 kind: Keyword,
360 },
361 CompletionItem {
362 label: "match",
363 source_range: [56; 56),
364 delete: [56; 56),
365 insert: "match $0 {}",
366 kind: Keyword,
367 },
368 CompletionItem {
369 label: "return",
370 source_range: [56; 56),
371 delete: [56; 56),
372 insert: "return $0;",
373 kind: Keyword,
374 },
375 CompletionItem {
376 label: "while",
377 source_range: [56; 56),
378 delete: [56; 56),
379 insert: "while $0 {}",
380 kind: Keyword,
381 },
382 ]
383 "###
384 );
385 assert_debug_snapshot!(
386 do_keyword_completion(
387 r"
388 fn quux() {
389 <|>
390 92
391 }
392 ",
393 ),
394 @r###"
395 [
396 CompletionItem {
397 label: "if",
398 source_range: [49; 49),
399 delete: [49; 49),
400 insert: "if $0 {}",
401 kind: Keyword,
402 },
403 CompletionItem {
404 label: "loop",
405 source_range: [49; 49),
406 delete: [49; 49),
407 insert: "loop {$0}",
408 kind: Keyword,
409 },
410 CompletionItem {
411 label: "match",
412 source_range: [49; 49),
413 delete: [49; 49),
414 insert: "match $0 {}",
415 kind: Keyword,
416 },
417 CompletionItem {
418 label: "return",
419 source_range: [49; 49),
420 delete: [49; 49),
421 insert: "return;",
422 kind: Keyword,
423 },
424 CompletionItem {
425 label: "while",
426 source_range: [49; 49),
427 delete: [49; 49),
428 insert: "while $0 {}",
429 kind: Keyword,
430 },
431 ]
432 "###
433 );
434 }
435
436 #[test]
437 fn dont_add_semi_after_return_if_not_a_statement() {
438 assert_debug_snapshot!(
439 do_keyword_completion(
440 r"
441 fn quux() -> i32 {
442 match () {
443 () => <|>
444 }
445 }
446 ",
447 ),
448 @r###"
449 [
450 CompletionItem {
451 label: "if",
452 source_range: [97; 97),
453 delete: [97; 97),
454 insert: "if $0 {}",
455 kind: Keyword,
456 },
457 CompletionItem {
458 label: "loop",
459 source_range: [97; 97),
460 delete: [97; 97),
461 insert: "loop {$0}",
462 kind: Keyword,
463 },
464 CompletionItem {
465 label: "match",
466 source_range: [97; 97),
467 delete: [97; 97),
468 insert: "match $0 {}",
469 kind: Keyword,
470 },
471 CompletionItem {
472 label: "return",
473 source_range: [97; 97),
474 delete: [97; 97),
475 insert: "return $0",
476 kind: Keyword,
477 },
478 CompletionItem {
479 label: "while",
480 source_range: [97; 97),
481 delete: [97; 97),
482 insert: "while $0 {}",
483 kind: Keyword,
484 },
485 ]
486 "###
487 );
488 }
489
490 #[test]
491 fn last_return_in_block_has_semi() {
492 assert_debug_snapshot!(
493 do_keyword_completion(
494 r"
495 fn quux() -> i32 {
496 if condition {
497 <|>
498 }
499 }
500 ",
501 ),
502 @r###"
503 [
504 CompletionItem {
505 label: "if",
506 source_range: [95; 95),
507 delete: [95; 95),
508 insert: "if $0 {}",
509 kind: Keyword,
510 },
511 CompletionItem {
512 label: "loop",
513 source_range: [95; 95),
514 delete: [95; 95),
515 insert: "loop {$0}",
516 kind: Keyword,
517 },
518 CompletionItem {
519 label: "match",
520 source_range: [95; 95),
521 delete: [95; 95),
522 insert: "match $0 {}",
523 kind: Keyword,
524 },
525 CompletionItem {
526 label: "return",
527 source_range: [95; 95),
528 delete: [95; 95),
529 insert: "return $0;",
530 kind: Keyword,
531 },
532 CompletionItem {
533 label: "while",
534 source_range: [95; 95),
535 delete: [95; 95),
536 insert: "while $0 {}",
537 kind: Keyword,
538 },
539 ]
540 "###
541 );
542 assert_debug_snapshot!(
543 do_keyword_completion(
544 r"
545 fn quux() -> i32 {
546 if condition {
547 <|>
548 }
549 let x = 92;
550 x
551 }
552 ",
553 ),
554 @r###"
555 [
556 CompletionItem {
557 label: "if",
558 source_range: [95; 95),
559 delete: [95; 95),
560 insert: "if $0 {}",
561 kind: Keyword,
562 },
563 CompletionItem {
564 label: "loop",
565 source_range: [95; 95),
566 delete: [95; 95),
567 insert: "loop {$0}",
568 kind: Keyword,
569 },
570 CompletionItem {
571 label: "match",
572 source_range: [95; 95),
573 delete: [95; 95),
574 insert: "match $0 {}",
575 kind: Keyword,
576 },
577 CompletionItem {
578 label: "return",
579 source_range: [95; 95),
580 delete: [95; 95),
581 insert: "return $0;",
582 kind: Keyword,
583 },
584 CompletionItem {
585 label: "while",
586 source_range: [95; 95),
587 delete: [95; 95),
588 insert: "while $0 {}",
589 kind: Keyword,
590 },
591 ]
592 "###
593 );
594 }
595
596 #[test]
597 fn completes_break_and_continue_in_loops() {
598 assert_debug_snapshot!(
599 do_keyword_completion(
600 r"
601 fn quux() -> i32 {
602 loop { <|> }
603 }
604 ",
605 ),
606 @r###"
607 [
608 CompletionItem {
609 label: "break",
610 source_range: [63; 63),
611 delete: [63; 63),
612 insert: "break;",
613 kind: Keyword,
614 },
615 CompletionItem {
616 label: "continue",
617 source_range: [63; 63),
618 delete: [63; 63),
619 insert: "continue;",
620 kind: Keyword,
621 },
622 CompletionItem {
623 label: "if",
624 source_range: [63; 63),
625 delete: [63; 63),
626 insert: "if $0 {}",
627 kind: Keyword,
628 },
629 CompletionItem {
630 label: "loop",
631 source_range: [63; 63),
632 delete: [63; 63),
633 insert: "loop {$0}",
634 kind: Keyword,
635 },
636 CompletionItem {
637 label: "match",
638 source_range: [63; 63),
639 delete: [63; 63),
640 insert: "match $0 {}",
641 kind: Keyword,
642 },
643 CompletionItem {
644 label: "return",
645 source_range: [63; 63),
646 delete: [63; 63),
647 insert: "return $0;",
648 kind: Keyword,
649 },
650 CompletionItem {
651 label: "while",
652 source_range: [63; 63),
653 delete: [63; 63),
654 insert: "while $0 {}",
655 kind: Keyword,
656 },
657 ]
658 "###
659 );
660
661 // No completion: lambda isolates control flow
662 assert_debug_snapshot!(
663 do_keyword_completion(
664 r"
665 fn quux() -> i32 {
666 loop { || { <|> } }
667 }
668 ",
669 ),
670 @r###"
671 [
672 CompletionItem {
673 label: "if",
674 source_range: [68; 68),
675 delete: [68; 68),
676 insert: "if $0 {}",
677 kind: Keyword,
678 },
679 CompletionItem {
680 label: "loop",
681 source_range: [68; 68),
682 delete: [68; 68),
683 insert: "loop {$0}",
684 kind: Keyword,
685 },
686 CompletionItem {
687 label: "match",
688 source_range: [68; 68),
689 delete: [68; 68),
690 insert: "match $0 {}",
691 kind: Keyword,
692 },
693 CompletionItem {
694 label: "return",
695 source_range: [68; 68),
696 delete: [68; 68),
697 insert: "return $0;",
698 kind: Keyword,
699 },
700 CompletionItem {
701 label: "while",
702 source_range: [68; 68),
703 delete: [68; 68),
704 insert: "while $0 {}",
705 kind: Keyword,
706 },
707 ]
708 "###
709 );
710 }
711
712 #[test]
713 fn no_semi_after_break_continue_in_expr() {
714 assert_debug_snapshot!(
715 do_keyword_completion(
716 r"
717 fn f() {
718 loop {
719 match () {
720 () => br<|>
721 }
722 }
723 }
724 ",
725 ),
726 @r###"
727 [
728 CompletionItem {
729 label: "break",
730 source_range: [122; 124),
731 delete: [122; 124),
732 insert: "break",
733 kind: Keyword,
734 },
735 CompletionItem {
736 label: "continue",
737 source_range: [122; 124),
738 delete: [122; 124),
739 insert: "continue",
740 kind: Keyword,
741 },
742 CompletionItem {
743 label: "if",
744 source_range: [122; 124),
745 delete: [122; 124),
746 insert: "if $0 {}",
747 kind: Keyword,
748 },
749 CompletionItem {
750 label: "loop",
751 source_range: [122; 124),
752 delete: [122; 124),
753 insert: "loop {$0}",
754 kind: Keyword,
755 },
756 CompletionItem {
757 label: "match",
758 source_range: [122; 124),
759 delete: [122; 124),
760 insert: "match $0 {}",
761 kind: Keyword,
762 },
763 CompletionItem {
764 label: "return",
765 source_range: [122; 124),
766 delete: [122; 124),
767 insert: "return",
768 kind: Keyword,
769 },
770 CompletionItem {
771 label: "while",
772 source_range: [122; 124),
773 delete: [122; 124),
774 insert: "while $0 {}",
775 kind: Keyword,
776 },
777 ]
778 "###
779 )
780 }
781}
diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
new file mode 100644
index 000000000..faadd1e3f
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
@@ -0,0 +1,143 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
6 // Show only macros in top level.
7 if ctx.is_new_item {
8 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
9 if let hir::ScopeDef::MacroDef(mac) = res {
10 acc.add_macro(ctx, Some(name.to_string()), mac);
11 }
12 })
13 }
14}
15
16#[cfg(test)]
17mod tests {
18 use crate::completion::{do_completion, CompletionItem, CompletionKind};
19 use insta::assert_debug_snapshot;
20
21 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
22 do_completion(code, CompletionKind::Reference)
23 }
24
25 #[test]
26 fn completes_macros_as_item() {
27 assert_debug_snapshot!(
28 do_reference_completion(
29 "
30 //- /main.rs
31 macro_rules! foo {
32 () => {}
33 }
34
35 fn foo() {}
36
37 <|>
38 "
39 ),
40 @r###"
41 [
42 CompletionItem {
43 label: "foo!",
44 source_range: [46; 46),
45 delete: [46; 46),
46 insert: "foo!($0)",
47 kind: Macro,
48 detail: "macro_rules! foo",
49 },
50 ]
51 "###
52 );
53 }
54
55 #[test]
56 fn completes_vec_macros_with_square_brackets() {
57 assert_debug_snapshot!(
58 do_reference_completion(
59 "
60 //- /main.rs
61 /// Creates a [`Vec`] containing the arguments.
62 ///
63 /// - Create a [`Vec`] containing a given list of elements:
64 ///
65 /// ```
66 /// let v = vec![1, 2, 3];
67 /// assert_eq!(v[0], 1);
68 /// assert_eq!(v[1], 2);
69 /// assert_eq!(v[2], 3);
70 /// ```
71 macro_rules! vec {
72 () => {}
73 }
74
75 fn foo() {}
76
77 <|>
78 "
79 ),
80 @r###"
81 [
82 CompletionItem {
83 label: "vec!",
84 source_range: [280; 280),
85 delete: [280; 280),
86 insert: "vec![$0]",
87 kind: Macro,
88 detail: "macro_rules! vec",
89 documentation: Documentation(
90 "Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```",
91 ),
92 },
93 ]
94 "###
95 );
96 }
97
98 #[test]
99 fn completes_macros_braces_guessing() {
100 assert_debug_snapshot!(
101 do_reference_completion(
102 "
103 //- /main.rs
104 /// Foo
105 ///
106 /// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.
107 /// Call as `let _=foo! { hello world };`
108 macro_rules! foo {
109 () => {}
110 }
111
112 fn main() {
113 <|>
114 }
115 "
116 ),
117 @r###"
118 [
119 CompletionItem {
120 label: "foo!",
121 source_range: [163; 163),
122 delete: [163; 163),
123 insert: "foo! {$0}",
124 kind: Macro,
125 detail: "macro_rules! foo",
126 documentation: Documentation(
127 "Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`",
128 ),
129 },
130 CompletionItem {
131 label: "main()",
132 source_range: [163; 163),
133 delete: [163; 163),
134 insert: "main()$0",
135 kind: Function,
136 lookup: "main",
137 detail: "fn main()",
138 },
139 ]
140 "###
141 );
142 }
143}
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs
new file mode 100644
index 000000000..89e0009a1
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_path.rs
@@ -0,0 +1,785 @@
1//! FIXME: write short doc here
2
3use hir::{Adt, Either, HasSource, PathResolution};
4use ra_syntax::AstNode;
5use test_utils::tested_by;
6
7use crate::completion::{CompletionContext, Completions};
8
9pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
10 let path = match &ctx.path_prefix {
11 Some(path) => path.clone(),
12 _ => return,
13 };
14 let def = match ctx.analyzer.resolve_hir_path(ctx.db, &path) {
15 Some(PathResolution::Def(def)) => def,
16 _ => return,
17 };
18 match def {
19 hir::ModuleDef::Module(module) => {
20 let module_scope = module.scope(ctx.db);
21 for (name, def, import) in module_scope {
22 if let hir::ScopeDef::ModuleDef(hir::ModuleDef::BuiltinType(..)) = def {
23 if ctx.use_item_syntax.is_some() {
24 tested_by!(dont_complete_primitive_in_use);
25 continue;
26 }
27 }
28 if Some(module) == ctx.module {
29 if let Some(import) = import {
30 if let Either::A(use_tree) = import.source(ctx.db).value {
31 if use_tree.syntax().text_range().contains_inclusive(ctx.offset) {
32 // for `use self::foo<|>`, don't suggest `foo` as a completion
33 tested_by!(dont_complete_current_use);
34 continue;
35 }
36 }
37 }
38 }
39 acc.add_resolution(ctx, name.to_string(), &def);
40 }
41 }
42 hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => {
43 if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
44 for variant in e.variants(ctx.db) {
45 acc.add_enum_variant(ctx, variant);
46 }
47 }
48 let ty = match def {
49 hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
50 hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
51 _ => unreachable!(),
52 };
53 ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| {
54 match item {
55 hir::AssocItem::Function(func) => {
56 if !func.has_self_param(ctx.db) {
57 acc.add_function(ctx, func);
58 }
59 }
60 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
61 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
62 }
63 None::<()>
64 });
65 // Iterate assoc types separately
66 // FIXME: complete T::AssocType
67 let krate = ctx.module.map(|m| m.krate());
68 if let Some(krate) = krate {
69 ty.iterate_impl_items(ctx.db, krate, |item| {
70 match item {
71 hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
72 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
73 }
74 None::<()>
75 });
76 }
77 }
78 hir::ModuleDef::Trait(t) => {
79 for item in t.items(ctx.db) {
80 match item {
81 hir::AssocItem::Function(func) => {
82 if !func.has_self_param(ctx.db) {
83 acc.add_function(ctx, func);
84 }
85 }
86 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
87 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
88 }
89 }
90 }
91 _ => {}
92 };
93}
94
95#[cfg(test)]
96mod tests {
97 use test_utils::covers;
98
99 use crate::completion::{do_completion, CompletionItem, CompletionKind};
100 use insta::assert_debug_snapshot;
101
102 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
103 do_completion(code, CompletionKind::Reference)
104 }
105
106 #[test]
107 fn dont_complete_current_use() {
108 covers!(dont_complete_current_use);
109 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference);
110 assert!(completions.is_empty());
111 }
112
113 #[test]
114 fn dont_complete_current_use_in_braces_with_glob() {
115 let completions = do_completion(
116 r"
117 mod foo { pub struct S; }
118 use self::{foo::*, bar<|>};
119 ",
120 CompletionKind::Reference,
121 );
122 assert_eq!(completions.len(), 2);
123 }
124
125 #[test]
126 fn dont_complete_primitive_in_use() {
127 covers!(dont_complete_primitive_in_use);
128 let completions = do_completion(r"use self::<|>;", CompletionKind::BuiltinType);
129 assert!(completions.is_empty());
130 }
131
132 #[test]
133 fn completes_primitives() {
134 let completions =
135 do_completion(r"fn main() { let _: <|> = 92; }", CompletionKind::BuiltinType);
136 assert_eq!(completions.len(), 17);
137 }
138
139 #[test]
140 fn completes_mod_with_docs() {
141 assert_debug_snapshot!(
142 do_reference_completion(
143 r"
144 use self::my<|>;
145
146 /// Some simple
147 /// docs describing `mod my`.
148 mod my {
149 struct Bar;
150 }
151 "
152 ),
153 @r###"
154 [
155 CompletionItem {
156 label: "my",
157 source_range: [27; 29),
158 delete: [27; 29),
159 insert: "my",
160 kind: Module,
161 documentation: Documentation(
162 "Some simple\ndocs describing `mod my`.",
163 ),
164 },
165 ]
166 "###
167 );
168 }
169
170 #[test]
171 fn completes_use_item_starting_with_self() {
172 assert_debug_snapshot!(
173 do_reference_completion(
174 r"
175 use self::m::<|>;
176
177 mod m {
178 struct Bar;
179 }
180 "
181 ),
182 @r###"
183 [
184 CompletionItem {
185 label: "Bar",
186 source_range: [30; 30),
187 delete: [30; 30),
188 insert: "Bar",
189 kind: Struct,
190 },
191 ]
192 "###
193 );
194 }
195
196 #[test]
197 fn completes_use_item_starting_with_crate() {
198 assert_debug_snapshot!(
199 do_reference_completion(
200 "
201 //- /lib.rs
202 mod foo;
203 struct Spam;
204 //- /foo.rs
205 use crate::Sp<|>
206 "
207 ),
208 @r###"
209 [
210 CompletionItem {
211 label: "Spam",
212 source_range: [11; 13),
213 delete: [11; 13),
214 insert: "Spam",
215 kind: Struct,
216 },
217 CompletionItem {
218 label: "foo",
219 source_range: [11; 13),
220 delete: [11; 13),
221 insert: "foo",
222 kind: Module,
223 },
224 ]
225 "###
226 );
227 }
228
229 #[test]
230 fn completes_nested_use_tree() {
231 assert_debug_snapshot!(
232 do_reference_completion(
233 "
234 //- /lib.rs
235 mod foo;
236 struct Spam;
237 //- /foo.rs
238 use crate::{Sp<|>};
239 "
240 ),
241 @r###"
242 [
243 CompletionItem {
244 label: "Spam",
245 source_range: [12; 14),
246 delete: [12; 14),
247 insert: "Spam",
248 kind: Struct,
249 },
250 CompletionItem {
251 label: "foo",
252 source_range: [12; 14),
253 delete: [12; 14),
254 insert: "foo",
255 kind: Module,
256 },
257 ]
258 "###
259 );
260 }
261
262 #[test]
263 fn completes_deeply_nested_use_tree() {
264 assert_debug_snapshot!(
265 do_reference_completion(
266 "
267 //- /lib.rs
268 mod foo;
269 pub mod bar {
270 pub mod baz {
271 pub struct Spam;
272 }
273 }
274 //- /foo.rs
275 use crate::{bar::{baz::Sp<|>}};
276 "
277 ),
278 @r###"
279 [
280 CompletionItem {
281 label: "Spam",
282 source_range: [23; 25),
283 delete: [23; 25),
284 insert: "Spam",
285 kind: Struct,
286 },
287 ]
288 "###
289 );
290 }
291
292 #[test]
293 fn completes_enum_variant() {
294 assert_debug_snapshot!(
295 do_reference_completion(
296 "
297 //- /lib.rs
298 /// An enum
299 enum E {
300 /// Foo Variant
301 Foo,
302 /// Bar Variant with i32
303 Bar(i32)
304 }
305 fn foo() { let _ = E::<|> }
306 "
307 ),
308 @r###"
309 [
310 CompletionItem {
311 label: "Bar",
312 source_range: [116; 116),
313 delete: [116; 116),
314 insert: "Bar",
315 kind: EnumVariant,
316 detail: "(i32)",
317 documentation: Documentation(
318 "Bar Variant with i32",
319 ),
320 },
321 CompletionItem {
322 label: "Foo",
323 source_range: [116; 116),
324 delete: [116; 116),
325 insert: "Foo",
326 kind: EnumVariant,
327 detail: "()",
328 documentation: Documentation(
329 "Foo Variant",
330 ),
331 },
332 ]
333 "###
334 );
335 }
336
337 #[test]
338 fn completes_enum_variant_with_details() {
339 assert_debug_snapshot!(
340 do_reference_completion(
341 "
342 //- /lib.rs
343 struct S { field: u32 }
344 /// An enum
345 enum E {
346 /// Foo Variant (empty)
347 Foo,
348 /// Bar Variant with i32 and u32
349 Bar(i32, u32),
350 ///
351 S(S),
352 }
353 fn foo() { let _ = E::<|> }
354 "
355 ),
356 @r###"
357 [
358 CompletionItem {
359 label: "Bar",
360 source_range: [180; 180),
361 delete: [180; 180),
362 insert: "Bar",
363 kind: EnumVariant,
364 detail: "(i32, u32)",
365 documentation: Documentation(
366 "Bar Variant with i32 and u32",
367 ),
368 },
369 CompletionItem {
370 label: "Foo",
371 source_range: [180; 180),
372 delete: [180; 180),
373 insert: "Foo",
374 kind: EnumVariant,
375 detail: "()",
376 documentation: Documentation(
377 "Foo Variant (empty)",
378 ),
379 },
380 CompletionItem {
381 label: "S",
382 source_range: [180; 180),
383 delete: [180; 180),
384 insert: "S",
385 kind: EnumVariant,
386 detail: "(S)",
387 documentation: Documentation(
388 "",
389 ),
390 },
391 ]
392 "###
393 );
394 }
395
396 #[test]
397 fn completes_struct_associated_method() {
398 assert_debug_snapshot!(
399 do_reference_completion(
400 "
401 //- /lib.rs
402 /// A Struct
403 struct S;
404
405 impl S {
406 /// An associated method
407 fn m() { }
408 }
409
410 fn foo() { let _ = S::<|> }
411 "
412 ),
413 @r###"
414 [
415 CompletionItem {
416 label: "m()",
417 source_range: [100; 100),
418 delete: [100; 100),
419 insert: "m()$0",
420 kind: Function,
421 lookup: "m",
422 detail: "fn m()",
423 documentation: Documentation(
424 "An associated method",
425 ),
426 },
427 ]
428 "###
429 );
430 }
431
432 #[test]
433 fn completes_struct_associated_const() {
434 assert_debug_snapshot!(
435 do_reference_completion(
436 "
437 //- /lib.rs
438 /// A Struct
439 struct S;
440
441 impl S {
442 /// An associated const
443 const C: i32 = 42;
444 }
445
446 fn foo() { let _ = S::<|> }
447 "
448 ),
449 @r###"
450 [
451 CompletionItem {
452 label: "C",
453 source_range: [107; 107),
454 delete: [107; 107),
455 insert: "C",
456 kind: Const,
457 detail: "const C: i32 = 42;",
458 documentation: Documentation(
459 "An associated const",
460 ),
461 },
462 ]
463 "###
464 );
465 }
466
467 #[test]
468 fn completes_struct_associated_type() {
469 assert_debug_snapshot!(
470 do_reference_completion(
471 "
472 //- /lib.rs
473 /// A Struct
474 struct S;
475
476 impl S {
477 /// An associated type
478 type T = i32;
479 }
480
481 fn foo() { let _ = S::<|> }
482 "
483 ),
484 @r###"
485 [
486 CompletionItem {
487 label: "T",
488 source_range: [101; 101),
489 delete: [101; 101),
490 insert: "T",
491 kind: TypeAlias,
492 detail: "type T = i32;",
493 documentation: Documentation(
494 "An associated type",
495 ),
496 },
497 ]
498 "###
499 );
500 }
501
502 #[test]
503 fn completes_enum_associated_method() {
504 assert_debug_snapshot!(
505 do_reference_completion(
506 "
507 //- /lib.rs
508 /// An enum
509 enum S {};
510
511 impl S {
512 /// An associated method
513 fn m() { }
514 }
515
516 fn foo() { let _ = S::<|> }
517 "
518 ),
519 @r###"
520 [
521 CompletionItem {
522 label: "m()",
523 source_range: [100; 100),
524 delete: [100; 100),
525 insert: "m()$0",
526 kind: Function,
527 lookup: "m",
528 detail: "fn m()",
529 documentation: Documentation(
530 "An associated method",
531 ),
532 },
533 ]
534 "###
535 );
536 }
537
538 #[test]
539 fn completes_union_associated_method() {
540 assert_debug_snapshot!(
541 do_reference_completion(
542 "
543 //- /lib.rs
544 /// A union
545 union U {};
546
547 impl U {
548 /// An associated method
549 fn m() { }
550 }
551
552 fn foo() { let _ = U::<|> }
553 "
554 ),
555 @r###"
556 [
557 CompletionItem {
558 label: "m()",
559 source_range: [101; 101),
560 delete: [101; 101),
561 insert: "m()$0",
562 kind: Function,
563 lookup: "m",
564 detail: "fn m()",
565 documentation: Documentation(
566 "An associated method",
567 ),
568 },
569 ]
570 "###
571 );
572 }
573
574 #[test]
575 fn completes_use_paths_across_crates() {
576 assert_debug_snapshot!(
577 do_reference_completion(
578 "
579 //- /main.rs
580 use foo::<|>;
581
582 //- /foo/lib.rs
583 pub mod bar {
584 pub struct S;
585 }
586 "
587 ),
588 @r###"
589 [
590 CompletionItem {
591 label: "bar",
592 source_range: [9; 9),
593 delete: [9; 9),
594 insert: "bar",
595 kind: Module,
596 },
597 ]
598 "###
599 );
600 }
601
602 #[test]
603 fn completes_trait_associated_method_1() {
604 assert_debug_snapshot!(
605 do_reference_completion(
606 "
607 //- /lib.rs
608 trait Trait {
609 /// A trait method
610 fn m();
611 }
612
613 fn foo() { let _ = Trait::<|> }
614 "
615 ),
616 @r###"
617 [
618 CompletionItem {
619 label: "m()",
620 source_range: [73; 73),
621 delete: [73; 73),
622 insert: "m()$0",
623 kind: Function,
624 lookup: "m",
625 detail: "fn m()",
626 documentation: Documentation(
627 "A trait method",
628 ),
629 },
630 ]
631 "###
632 );
633 }
634
635 #[test]
636 fn completes_trait_associated_method_2() {
637 assert_debug_snapshot!(
638 do_reference_completion(
639 "
640 //- /lib.rs
641 trait Trait {
642 /// A trait method
643 fn m();
644 }
645
646 struct S;
647 impl Trait for S {}
648
649 fn foo() { let _ = S::<|> }
650 "
651 ),
652 @r###"
653 [
654 CompletionItem {
655 label: "m()",
656 source_range: [99; 99),
657 delete: [99; 99),
658 insert: "m()$0",
659 kind: Function,
660 lookup: "m",
661 detail: "fn m()",
662 documentation: Documentation(
663 "A trait method",
664 ),
665 },
666 ]
667 "###
668 );
669 }
670
671 #[test]
672 fn completes_trait_associated_method_3() {
673 assert_debug_snapshot!(
674 do_reference_completion(
675 "
676 //- /lib.rs
677 trait Trait {
678 /// A trait method
679 fn m();
680 }
681
682 struct S;
683 impl Trait for S {}
684
685 fn foo() { let _ = <S as Trait>::<|> }
686 "
687 ),
688 @r###"
689 [
690 CompletionItem {
691 label: "m()",
692 source_range: [110; 110),
693 delete: [110; 110),
694 insert: "m()$0",
695 kind: Function,
696 lookup: "m",
697 detail: "fn m()",
698 documentation: Documentation(
699 "A trait method",
700 ),
701 },
702 ]
703 "###
704 );
705 }
706
707 #[test]
708 fn completes_type_alias() {
709 assert_debug_snapshot!(
710 do_reference_completion(
711 "
712 struct S;
713 impl S { fn foo() {} }
714 type T = S;
715 impl T { fn bar() {} }
716
717 fn main() {
718 T::<|>;
719 }
720 "
721 ),
722 @r###"
723 [
724 CompletionItem {
725 label: "bar()",
726 source_range: [185; 185),
727 delete: [185; 185),
728 insert: "bar()$0",
729 kind: Function,
730 lookup: "bar",
731 detail: "fn bar()",
732 },
733 CompletionItem {
734 label: "foo()",
735 source_range: [185; 185),
736 delete: [185; 185),
737 insert: "foo()$0",
738 kind: Function,
739 lookup: "foo",
740 detail: "fn foo()",
741 },
742 ]
743 "###
744 );
745 }
746
747 #[test]
748 fn completes_qualified_macros() {
749 assert_debug_snapshot!(
750 do_reference_completion(
751 "
752 #[macro_export]
753 macro_rules! foo {
754 () => {}
755 }
756
757 fn main() {
758 let _ = crate::<|>
759 }
760 "
761 ),
762 @r###"
763 [
764 CompletionItem {
765 label: "foo!",
766 source_range: [179; 179),
767 delete: [179; 179),
768 insert: "foo!($0)",
769 kind: Macro,
770 detail: "#[macro_export]\nmacro_rules! foo",
771 },
772 CompletionItem {
773 label: "main()",
774 source_range: [179; 179),
775 delete: [179; 179),
776 insert: "main()$0",
777 kind: Function,
778 lookup: "main",
779 detail: "fn main()",
780 },
781 ]
782 "###
783 );
784 }
785}
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
new file mode 100644
index 000000000..fd03b1c40
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -0,0 +1,89 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Completes constats and paths in patterns.
6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
7 if !ctx.is_pat_binding {
8 return;
9 }
10 // FIXME: ideally, we should look at the type we are matching against and
11 // suggest variants + auto-imports
12 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
13 let def = match &res {
14 hir::ScopeDef::ModuleDef(def) => def,
15 _ => return,
16 };
17 match def {
18 hir::ModuleDef::Adt(hir::Adt::Enum(..))
19 | hir::ModuleDef::EnumVariant(..)
20 | hir::ModuleDef::Const(..)
21 | hir::ModuleDef::Module(..) => (),
22 _ => return,
23 }
24 acc.add_resolution(ctx, name.to_string(), &res)
25 });
26}
27
28#[cfg(test)]
29mod tests {
30 use crate::completion::{do_completion, CompletionItem, CompletionKind};
31 use insta::assert_debug_snapshot;
32
33 fn complete(code: &str) -> Vec<CompletionItem> {
34 do_completion(code, CompletionKind::Reference)
35 }
36
37 #[test]
38 fn completes_enum_variants_and_modules() {
39 let completions = complete(
40 r"
41 enum E { X }
42 use self::E::X;
43 const Z: E = E::X;
44 mod m {}
45
46 static FOO: E = E::X;
47 struct Bar { f: u32 }
48
49 fn foo() {
50 match E::X {
51 <|>
52 }
53 }
54 ",
55 );
56 assert_debug_snapshot!(completions, @r###"
57 [
58 CompletionItem {
59 label: "E",
60 source_range: [246; 246),
61 delete: [246; 246),
62 insert: "E",
63 kind: Enum,
64 },
65 CompletionItem {
66 label: "X",
67 source_range: [246; 246),
68 delete: [246; 246),
69 insert: "X",
70 kind: EnumVariant,
71 },
72 CompletionItem {
73 label: "Z",
74 source_range: [246; 246),
75 delete: [246; 246),
76 insert: "Z",
77 kind: Const,
78 },
79 CompletionItem {
80 label: "m",
81 source_range: [246; 246),
82 delete: [246; 246),
83 insert: "m",
84 kind: Module,
85 },
86 ]
87 "###);
88 }
89}
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
new file mode 100644
index 000000000..646a30c76
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -0,0 +1,282 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{ast::AstNode, TextRange, TextUnit};
4use ra_text_edit::TextEdit;
5
6use crate::{
7 completion::{
8 completion_context::CompletionContext,
9 completion_item::{Builder, CompletionKind, Completions},
10 },
11 CompletionItem,
12};
13
14pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
15 if ctx.db.feature_flags.get("completion.enable-postfix") == false {
16 return;
17 }
18
19 let dot_receiver = match &ctx.dot_receiver {
20 Some(it) => it,
21 None => return,
22 };
23
24 let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal {
25 let text = dot_receiver.syntax().text();
26 let without_dot = ..text.len() - TextUnit::of_char('.');
27 text.slice(without_dot).to_string()
28 } else {
29 dot_receiver.syntax().text().to_string()
30 };
31
32 let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) {
33 Some(it) => it,
34 None => return,
35 };
36
37 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38 postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39 .add_to(acc);
40 postfix_snippet(
41 ctx,
42 "while",
43 "while expr {}",
44 &format!("while {} {{\n$0\n}}", receiver_text),
45 )
46 .add_to(acc);
47 }
48
49 postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
50
51 postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52 postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
53
54 postfix_snippet(
55 ctx,
56 "match",
57 "match expr {}",
58 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
59 )
60 .add_to(acc);
61
62 postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
63
64 postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text))
65 .add_to(acc);
66}
67
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
69 let edit = {
70 let receiver_range =
71 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range();
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string())
74 };
75 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
76 .detail(detail)
77 .snippet_edit(edit)
78}
79
80#[cfg(test)]
81mod tests {
82 use insta::assert_debug_snapshot;
83
84 use crate::completion::{do_completion, CompletionItem, CompletionKind};
85
86 fn do_postfix_completion(code: &str) -> Vec<CompletionItem> {
87 do_completion(code, CompletionKind::Postfix)
88 }
89
90 #[test]
91 fn postfix_completion_works_for_trivial_path_expression() {
92 assert_debug_snapshot!(
93 do_postfix_completion(
94 r#"
95 fn main() {
96 let bar = true;
97 bar.<|>
98 }
99 "#,
100 ),
101 @r###"
102 [
103 CompletionItem {
104 label: "box",
105 source_range: [89; 89),
106 delete: [85; 89),
107 insert: "Box::new(bar)",
108 detail: "Box::new(expr)",
109 },
110 CompletionItem {
111 label: "dbg",
112 source_range: [89; 89),
113 delete: [85; 89),
114 insert: "dbg!(bar)",
115 detail: "dbg!(expr)",
116 },
117 CompletionItem {
118 label: "if",
119 source_range: [89; 89),
120 delete: [85; 89),
121 insert: "if bar {$0}",
122 detail: "if expr {}",
123 },
124 CompletionItem {
125 label: "match",
126 source_range: [89; 89),
127 delete: [85; 89),
128 insert: "match bar {\n ${1:_} => {$0\\},\n}",
129 detail: "match expr {}",
130 },
131 CompletionItem {
132 label: "not",
133 source_range: [89; 89),
134 delete: [85; 89),
135 insert: "!bar",
136 detail: "!expr",
137 },
138 CompletionItem {
139 label: "ref",
140 source_range: [89; 89),
141 delete: [85; 89),
142 insert: "&bar",
143 detail: "&expr",
144 },
145 CompletionItem {
146 label: "refm",
147 source_range: [89; 89),
148 delete: [85; 89),
149 insert: "&mut bar",
150 detail: "&mut expr",
151 },
152 CompletionItem {
153 label: "while",
154 source_range: [89; 89),
155 delete: [85; 89),
156 insert: "while bar {\n$0\n}",
157 detail: "while expr {}",
158 },
159 ]
160 "###
161 );
162 }
163
164 #[test]
165 fn some_postfix_completions_ignored() {
166 assert_debug_snapshot!(
167 do_postfix_completion(
168 r#"
169 fn main() {
170 let bar: u8 = 12;
171 bar.<|>
172 }
173 "#,
174 ),
175 @r###"
176 [
177 CompletionItem {
178 label: "box",
179 source_range: [91; 91),
180 delete: [87; 91),
181 insert: "Box::new(bar)",
182 detail: "Box::new(expr)",
183 },
184 CompletionItem {
185 label: "dbg",
186 source_range: [91; 91),
187 delete: [87; 91),
188 insert: "dbg!(bar)",
189 detail: "dbg!(expr)",
190 },
191 CompletionItem {
192 label: "match",
193 source_range: [91; 91),
194 delete: [87; 91),
195 insert: "match bar {\n ${1:_} => {$0\\},\n}",
196 detail: "match expr {}",
197 },
198 CompletionItem {
199 label: "not",
200 source_range: [91; 91),
201 delete: [87; 91),
202 insert: "!bar",
203 detail: "!expr",
204 },
205 CompletionItem {
206 label: "ref",
207 source_range: [91; 91),
208 delete: [87; 91),
209 insert: "&bar",
210 detail: "&expr",
211 },
212 CompletionItem {
213 label: "refm",
214 source_range: [91; 91),
215 delete: [87; 91),
216 insert: "&mut bar",
217 detail: "&mut expr",
218 },
219 ]
220 "###
221 );
222 }
223
224 #[test]
225 fn postfix_completion_works_for_ambiguous_float_literal() {
226 assert_debug_snapshot!(
227 do_postfix_completion(
228 r#"
229 fn main() {
230 42.<|>
231 }
232 "#,
233 ),
234 @r###"
235 [
236 CompletionItem {
237 label: "box",
238 source_range: [52; 52),
239 delete: [49; 52),
240 insert: "Box::new(42)",
241 detail: "Box::new(expr)",
242 },
243 CompletionItem {
244 label: "dbg",
245 source_range: [52; 52),
246 delete: [49; 52),
247 insert: "dbg!(42)",
248 detail: "dbg!(expr)",
249 },
250 CompletionItem {
251 label: "match",
252 source_range: [52; 52),
253 delete: [49; 52),
254 insert: "match 42 {\n ${1:_} => {$0\\},\n}",
255 detail: "match expr {}",
256 },
257 CompletionItem {
258 label: "not",
259 source_range: [52; 52),
260 delete: [49; 52),
261 insert: "!42",
262 detail: "!expr",
263 },
264 CompletionItem {
265 label: "ref",
266 source_range: [52; 52),
267 delete: [49; 52),
268 insert: "&42",
269 detail: "&expr",
270 },
271 CompletionItem {
272 label: "refm",
273 source_range: [52; 52),
274 delete: [49; 52),
275 insert: "&mut 42",
276 detail: "&mut expr",
277 },
278 ]
279 "###
280 );
281 }
282}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
new file mode 100644
index 000000000..577c394d2
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -0,0 +1,159 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Complete fields in fields literals.
6pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
7 let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| {
8 Some((
9 ctx.analyzer.type_of(ctx.db, &it.clone().into())?,
10 ctx.analyzer.resolve_record_literal(it)?,
11 ))
12 }) {
13 Some(it) => it,
14 _ => return,
15 };
16
17 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
18 acc.add_field(ctx, field, &field_ty);
19 }
20}
21
22#[cfg(test)]
23mod tests {
24 use crate::completion::{do_completion, CompletionItem, CompletionKind};
25 use insta::assert_debug_snapshot;
26
27 fn complete(code: &str) -> Vec<CompletionItem> {
28 do_completion(code, CompletionKind::Reference)
29 }
30
31 #[test]
32 fn test_record_literal_deprecated_field() {
33 let completions = complete(
34 r"
35 struct A {
36 #[deprecated]
37 the_field: u32,
38 }
39 fn foo() {
40 A { the<|> }
41 }
42 ",
43 );
44 assert_debug_snapshot!(completions, @r###"
45 [
46 CompletionItem {
47 label: "the_field",
48 source_range: [142; 145),
49 delete: [142; 145),
50 insert: "the_field",
51 kind: Field,
52 detail: "u32",
53 deprecated: true,
54 },
55 ]
56 "###);
57 }
58
59 #[test]
60 fn test_record_literal_field() {
61 let completions = complete(
62 r"
63 struct A { the_field: u32 }
64 fn foo() {
65 A { the<|> }
66 }
67 ",
68 );
69 assert_debug_snapshot!(completions, @r###"
70 [
71 CompletionItem {
72 label: "the_field",
73 source_range: [83; 86),
74 delete: [83; 86),
75 insert: "the_field",
76 kind: Field,
77 detail: "u32",
78 },
79 ]
80 "###);
81 }
82
83 #[test]
84 fn test_record_literal_enum_variant() {
85 let completions = complete(
86 r"
87 enum E {
88 A { a: u32 }
89 }
90 fn foo() {
91 let _ = E::A { <|> }
92 }
93 ",
94 );
95 assert_debug_snapshot!(completions, @r###"
96 [
97 CompletionItem {
98 label: "a",
99 source_range: [119; 119),
100 delete: [119; 119),
101 insert: "a",
102 kind: Field,
103 detail: "u32",
104 },
105 ]
106 "###);
107 }
108
109 #[test]
110 fn test_record_literal_two_structs() {
111 let completions = complete(
112 r"
113 struct A { a: u32 }
114 struct B { b: u32 }
115
116 fn foo() {
117 let _: A = B { <|> }
118 }
119 ",
120 );
121 assert_debug_snapshot!(completions, @r###"
122 [
123 CompletionItem {
124 label: "b",
125 source_range: [119; 119),
126 delete: [119; 119),
127 insert: "b",
128 kind: Field,
129 detail: "u32",
130 },
131 ]
132 "###);
133 }
134
135 #[test]
136 fn test_record_literal_generic_struct() {
137 let completions = complete(
138 r"
139 struct A<T> { a: T }
140
141 fn foo() {
142 let _: A<u32> = A { <|> }
143 }
144 ",
145 );
146 assert_debug_snapshot!(completions, @r###"
147 [
148 CompletionItem {
149 label: "a",
150 source_range: [93; 93),
151 delete: [93; 93),
152 insert: "a",
153 kind: Field,
154 detail: "u32",
155 },
156 ]
157 "###);
158 }
159}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
new file mode 100644
index 000000000..a56c7e3a1
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record_pattern.rs
@@ -0,0 +1,93 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) {
6 let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| {
7 Some((
8 ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?,
9 ctx.analyzer.resolve_record_pattern(it)?,
10 ))
11 }) {
12 Some(it) => it,
13 _ => return,
14 };
15
16 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
17 acc.add_field(ctx, field, &field_ty);
18 }
19}
20
21#[cfg(test)]
22mod tests {
23 use crate::completion::{do_completion, CompletionItem, CompletionKind};
24 use insta::assert_debug_snapshot;
25
26 fn complete(code: &str) -> Vec<CompletionItem> {
27 do_completion(code, CompletionKind::Reference)
28 }
29
30 #[test]
31 fn test_record_pattern_field() {
32 let completions = complete(
33 r"
34 struct S { foo: u32 }
35
36 fn process(f: S) {
37 match f {
38 S { f<|>: 92 } => (),
39 }
40 }
41 ",
42 );
43 assert_debug_snapshot!(completions, @r###"
44 [
45 CompletionItem {
46 label: "foo",
47 source_range: [117; 118),
48 delete: [117; 118),
49 insert: "foo",
50 kind: Field,
51 detail: "u32",
52 },
53 ]
54 "###);
55 }
56
57 #[test]
58 fn test_record_pattern_enum_variant() {
59 let completions = complete(
60 r"
61 enum E {
62 S { foo: u32, bar: () }
63 }
64
65 fn process(e: E) {
66 match e {
67 E::S { <|> } => (),
68 }
69 }
70 ",
71 );
72 assert_debug_snapshot!(completions, @r###"
73 [
74 CompletionItem {
75 label: "bar",
76 source_range: [161; 161),
77 delete: [161; 161),
78 insert: "bar",
79 kind: Field,
80 detail: "()",
81 },
82 CompletionItem {
83 label: "foo",
84 source_range: [161; 161),
85 delete: [161; 161),
86 insert: "foo",
87 kind: Field,
88 detail: "u32",
89 },
90 ]
91 "###);
92 }
93}
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs
new file mode 100644
index 000000000..d5739b58a
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_scope.rs
@@ -0,0 +1,876 @@
1//! FIXME: write short doc here
2
3use ra_assists::auto_import_text_edit;
4use ra_syntax::{ast, AstNode, SmolStr};
5use ra_text_edit::TextEditBuilder;
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
9
10pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) {
11 if !ctx.is_trivial_path {
12 return;
13 }
14
15 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
16 acc.add_resolution(ctx, name.to_string(), &res)
17 });
18
19 // auto-import
20 // We fetch ident from the original file, because we need to pre-filter auto-imports
21 if ast::NameRef::cast(ctx.token.parent()).is_some() {
22 let import_resolver = ImportResolver::new();
23 let import_names = import_resolver.all_names(ctx.token.text());
24 import_names.into_iter().for_each(|(name, path)| {
25 let edit = {
26 let mut builder = TextEditBuilder::default();
27 builder.replace(ctx.source_range(), name.to_string());
28 auto_import_text_edit(
29 &ctx.token.parent(),
30 &ctx.token.parent(),
31 &path,
32 &mut builder,
33 );
34 builder.finish()
35 };
36
37 // Hack: copied this check form conv.rs beacause auto import can produce edits
38 // that invalidate assert in conv_with.
39 if edit
40 .as_atoms()
41 .iter()
42 .filter(|atom| !ctx.source_range().is_subrange(&atom.delete))
43 .all(|atom| ctx.source_range().intersection(&atom.delete).is_none())
44 {
45 CompletionItem::new(
46 CompletionKind::Reference,
47 ctx.source_range(),
48 build_import_label(&name, &path),
49 )
50 .text_edit(edit)
51 .add_to(acc);
52 }
53 });
54 }
55}
56
57fn build_import_label(name: &str, path: &[SmolStr]) -> String {
58 let mut buf = String::with_capacity(64);
59 buf.push_str(name);
60 buf.push_str(" (");
61 fmt_import_path(path, &mut buf);
62 buf.push_str(")");
63 buf
64}
65
66fn fmt_import_path(path: &[SmolStr], buf: &mut String) {
67 let mut segments = path.iter();
68 if let Some(s) = segments.next() {
69 buf.push_str(&s);
70 }
71 for s in segments {
72 buf.push_str("::");
73 buf.push_str(&s);
74 }
75}
76
77#[derive(Debug, Clone, Default)]
78pub(crate) struct ImportResolver {
79 // todo: use fst crate or something like that
80 dummy_names: Vec<(SmolStr, Vec<SmolStr>)>,
81}
82
83impl ImportResolver {
84 pub(crate) fn new() -> Self {
85 let dummy_names = vec![
86 (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]),
87 (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]),
88 (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]),
89 (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]),
90 (
91 SmolStr::new("Debug"),
92 vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")],
93 ),
94 (
95 SmolStr::new("Display"),
96 vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")],
97 ),
98 (
99 SmolStr::new("Hash"),
100 vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")],
101 ),
102 (
103 SmolStr::new("Hasher"),
104 vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")],
105 ),
106 (
107 SmolStr::new("Iterator"),
108 vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")],
109 ),
110 ];
111
112 ImportResolver { dummy_names }
113 }
114
115 // Returns a map of importable items filtered by name.
116 // The map associates item name with its full path.
117 // todo: should return Resolutions
118 pub(crate) fn all_names(&self, name: &str) -> FxHashMap<SmolStr, Vec<SmolStr>> {
119 if name.len() > 1 {
120 self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect()
121 } else {
122 FxHashMap::default()
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::completion::{do_completion, CompletionItem, CompletionKind};
130 use insta::assert_debug_snapshot;
131
132 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
133 do_completion(code, CompletionKind::Reference)
134 }
135
136 #[test]
137 fn completes_bindings_from_let() {
138 assert_debug_snapshot!(
139 do_reference_completion(
140 r"
141 fn quux(x: i32) {
142 let y = 92;
143 1 + <|>;
144 let z = ();
145 }
146 "
147 ),
148 @r###"
149 [
150 CompletionItem {
151 label: "quux(…)",
152 source_range: [91; 91),
153 delete: [91; 91),
154 insert: "quux($0)",
155 kind: Function,
156 lookup: "quux",
157 detail: "fn quux(x: i32)",
158 },
159 CompletionItem {
160 label: "x",
161 source_range: [91; 91),
162 delete: [91; 91),
163 insert: "x",
164 kind: Binding,
165 detail: "i32",
166 },
167 CompletionItem {
168 label: "y",
169 source_range: [91; 91),
170 delete: [91; 91),
171 insert: "y",
172 kind: Binding,
173 detail: "i32",
174 },
175 ]
176 "###
177 );
178 }
179
180 #[test]
181 fn completes_bindings_from_if_let() {
182 assert_debug_snapshot!(
183 do_reference_completion(
184 r"
185 fn quux() {
186 if let Some(x) = foo() {
187 let y = 92;
188 };
189 if let Some(a) = bar() {
190 let b = 62;
191 1 + <|>
192 }
193 }
194 "
195 ),
196 @r###"
197 [
198 CompletionItem {
199 label: "a",
200 source_range: [242; 242),
201 delete: [242; 242),
202 insert: "a",
203 kind: Binding,
204 },
205 CompletionItem {
206 label: "b",
207 source_range: [242; 242),
208 delete: [242; 242),
209 insert: "b",
210 kind: Binding,
211 detail: "i32",
212 },
213 CompletionItem {
214 label: "quux()",
215 source_range: [242; 242),
216 delete: [242; 242),
217 insert: "quux()$0",
218 kind: Function,
219 lookup: "quux",
220 detail: "fn quux()",
221 },
222 ]
223 "###
224 );
225 }
226
227 #[test]
228 fn completes_bindings_from_for() {
229 assert_debug_snapshot!(
230 do_reference_completion(
231 r"
232 fn quux() {
233 for x in &[1, 2, 3] {
234 <|>
235 }
236 }
237 "
238 ),
239 @r###"
240 [
241 CompletionItem {
242 label: "quux()",
243 source_range: [95; 95),
244 delete: [95; 95),
245 insert: "quux()$0",
246 kind: Function,
247 lookup: "quux",
248 detail: "fn quux()",
249 },
250 CompletionItem {
251 label: "x",
252 source_range: [95; 95),
253 delete: [95; 95),
254 insert: "x",
255 kind: Binding,
256 },
257 ]
258 "###
259 );
260 }
261
262 #[test]
263 fn completes_generic_params() {
264 assert_debug_snapshot!(
265 do_reference_completion(
266 r"
267 fn quux<T>() {
268 <|>
269 }
270 "
271 ),
272 @r###"
273 [
274 CompletionItem {
275 label: "T",
276 source_range: [52; 52),
277 delete: [52; 52),
278 insert: "T",
279 kind: TypeParam,
280 },
281 CompletionItem {
282 label: "quux()",
283 source_range: [52; 52),
284 delete: [52; 52),
285 insert: "quux()$0",
286 kind: Function,
287 lookup: "quux",
288 detail: "fn quux<T>()",
289 },
290 ]
291 "###
292 );
293 }
294
295 #[test]
296 fn completes_generic_params_in_struct() {
297 assert_debug_snapshot!(
298 do_reference_completion(
299 r"
300 struct X<T> {
301 x: <|>
302 }
303 "
304 ),
305 @r###"
306 [
307 CompletionItem {
308 label: "Self",
309 source_range: [54; 54),
310 delete: [54; 54),
311 insert: "Self",
312 kind: TypeParam,
313 },
314 CompletionItem {
315 label: "T",
316 source_range: [54; 54),
317 delete: [54; 54),
318 insert: "T",
319 kind: TypeParam,
320 },
321 CompletionItem {
322 label: "X<…>",
323 source_range: [54; 54),
324 delete: [54; 54),
325 insert: "X<$0>",
326 kind: Struct,
327 lookup: "X",
328 },
329 ]
330 "###
331 );
332 }
333
334 #[test]
335 fn completes_self_in_enum() {
336 assert_debug_snapshot!(
337 do_reference_completion(
338 r"
339 enum X {
340 Y(<|>)
341 }
342 "
343 ),
344 @r###"
345 [
346 CompletionItem {
347 label: "Self",
348 source_range: [48; 48),
349 delete: [48; 48),
350 insert: "Self",
351 kind: TypeParam,
352 },
353 CompletionItem {
354 label: "X",
355 source_range: [48; 48),
356 delete: [48; 48),
357 insert: "X",
358 kind: Enum,
359 },
360 ]
361 "###
362 );
363 }
364
365 #[test]
366 fn completes_module_items() {
367 assert_debug_snapshot!(
368 do_reference_completion(
369 r"
370 struct Foo;
371 enum Baz {}
372 fn quux() {
373 <|>
374 }
375 "
376 ),
377 @r###"
378 [
379 CompletionItem {
380 label: "Baz",
381 source_range: [105; 105),
382 delete: [105; 105),
383 insert: "Baz",
384 kind: Enum,
385 },
386 CompletionItem {
387 label: "Foo",
388 source_range: [105; 105),
389 delete: [105; 105),
390 insert: "Foo",
391 kind: Struct,
392 },
393 CompletionItem {
394 label: "quux()",
395 source_range: [105; 105),
396 delete: [105; 105),
397 insert: "quux()$0",
398 kind: Function,
399 lookup: "quux",
400 detail: "fn quux()",
401 },
402 ]
403 "###
404 );
405 }
406
407 #[test]
408 fn completes_extern_prelude() {
409 assert_debug_snapshot!(
410 do_reference_completion(
411 r"
412 //- /lib.rs
413 use <|>;
414
415 //- /other_crate/lib.rs
416 // nothing here
417 "
418 ),
419 @r###"
420 [
421 CompletionItem {
422 label: "other_crate",
423 source_range: [4; 4),
424 delete: [4; 4),
425 insert: "other_crate",
426 kind: Module,
427 },
428 ]
429 "###
430 );
431 }
432
433 #[test]
434 fn completes_module_items_in_nested_modules() {
435 assert_debug_snapshot!(
436 do_reference_completion(
437 r"
438 struct Foo;
439 mod m {
440 struct Bar;
441 fn quux() { <|> }
442 }
443 "
444 ),
445 @r###"
446 [
447 CompletionItem {
448 label: "Bar",
449 source_range: [117; 117),
450 delete: [117; 117),
451 insert: "Bar",
452 kind: Struct,
453 },
454 CompletionItem {
455 label: "quux()",
456 source_range: [117; 117),
457 delete: [117; 117),
458 insert: "quux()$0",
459 kind: Function,
460 lookup: "quux",
461 detail: "fn quux()",
462 },
463 ]
464 "###
465 );
466 }
467
468 #[test]
469 fn completes_return_type() {
470 assert_debug_snapshot!(
471 do_reference_completion(
472 r"
473 struct Foo;
474 fn x() -> <|>
475 "
476 ),
477 @r###"
478 [
479 CompletionItem {
480 label: "Foo",
481 source_range: [55; 55),
482 delete: [55; 55),
483 insert: "Foo",
484 kind: Struct,
485 },
486 CompletionItem {
487 label: "x()",
488 source_range: [55; 55),
489 delete: [55; 55),
490 insert: "x()$0",
491 kind: Function,
492 lookup: "x",
493 detail: "fn x()",
494 },
495 ]
496 "###
497 );
498 }
499
500 #[test]
501 fn dont_show_both_completions_for_shadowing() {
502 assert_debug_snapshot!(
503 do_reference_completion(
504 r"
505 fn foo() {
506 let bar = 92;
507 {
508 let bar = 62;
509 <|>
510 }
511 }
512 "
513 ),
514 @r###"
515 [
516 CompletionItem {
517 label: "bar",
518 source_range: [146; 146),
519 delete: [146; 146),
520 insert: "bar",
521 kind: Binding,
522 detail: "i32",
523 },
524 CompletionItem {
525 label: "foo()",
526 source_range: [146; 146),
527 delete: [146; 146),
528 insert: "foo()$0",
529 kind: Function,
530 lookup: "foo",
531 detail: "fn foo()",
532 },
533 ]
534 "###
535 );
536 }
537
538 #[test]
539 fn completes_self_in_methods() {
540 assert_debug_snapshot!(
541 do_reference_completion(r"impl S { fn foo(&self) { <|> } }"),
542 @r###"
543 [
544 CompletionItem {
545 label: "Self",
546 source_range: [25; 25),
547 delete: [25; 25),
548 insert: "Self",
549 kind: TypeParam,
550 },
551 CompletionItem {
552 label: "self",
553 source_range: [25; 25),
554 delete: [25; 25),
555 insert: "self",
556 kind: Binding,
557 detail: "&{unknown}",
558 },
559 ]
560 "###
561 );
562 }
563
564 #[test]
565 fn completes_prelude() {
566 assert_debug_snapshot!(
567 do_reference_completion(
568 "
569 //- /main.rs
570 fn foo() { let x: <|> }
571
572 //- /std/lib.rs
573 #[prelude_import]
574 use prelude::*;
575
576 mod prelude {
577 struct Option;
578 }
579 "
580 ),
581 @r###"
582 [
583 CompletionItem {
584 label: "Option",
585 source_range: [18; 18),
586 delete: [18; 18),
587 insert: "Option",
588 kind: Struct,
589 },
590 CompletionItem {
591 label: "foo()",
592 source_range: [18; 18),
593 delete: [18; 18),
594 insert: "foo()$0",
595 kind: Function,
596 lookup: "foo",
597 detail: "fn foo()",
598 },
599 CompletionItem {
600 label: "std",
601 source_range: [18; 18),
602 delete: [18; 18),
603 insert: "std",
604 kind: Module,
605 },
606 ]
607 "###
608 );
609 }
610
611 #[test]
612 fn completes_std_prelude_if_core_is_defined() {
613 assert_debug_snapshot!(
614 do_reference_completion(
615 "
616 //- /main.rs
617 fn foo() { let x: <|> }
618
619 //- /core/lib.rs
620 #[prelude_import]
621 use prelude::*;
622
623 mod prelude {
624 struct Option;
625 }
626
627 //- /std/lib.rs
628 #[prelude_import]
629 use prelude::*;
630
631 mod prelude {
632 struct String;
633 }
634 "
635 ),
636 @r###"
637 [
638 CompletionItem {
639 label: "String",
640 source_range: [18; 18),
641 delete: [18; 18),
642 insert: "String",
643 kind: Struct,
644 },
645 CompletionItem {
646 label: "core",
647 source_range: [18; 18),
648 delete: [18; 18),
649 insert: "core",
650 kind: Module,
651 },
652 CompletionItem {
653 label: "foo()",
654 source_range: [18; 18),
655 delete: [18; 18),
656 insert: "foo()$0",
657 kind: Function,
658 lookup: "foo",
659 detail: "fn foo()",
660 },
661 CompletionItem {
662 label: "std",
663 source_range: [18; 18),
664 delete: [18; 18),
665 insert: "std",
666 kind: Module,
667 },
668 ]
669 "###
670 );
671 }
672
673 #[test]
674 fn completes_macros_as_value() {
675 assert_debug_snapshot!(
676 do_reference_completion(
677 "
678 //- /main.rs
679 macro_rules! foo {
680 () => {}
681 }
682
683 #[macro_use]
684 mod m1 {
685 macro_rules! bar {
686 () => {}
687 }
688 }
689
690 mod m2 {
691 macro_rules! nope {
692 () => {}
693 }
694
695 #[macro_export]
696 macro_rules! baz {
697 () => {}
698 }
699 }
700
701 fn main() {
702 let v = <|>
703 }
704 "
705 ),
706 @r###"
707 [
708 CompletionItem {
709 label: "bar!",
710 source_range: [252; 252),
711 delete: [252; 252),
712 insert: "bar!($0)",
713 kind: Macro,
714 detail: "macro_rules! bar",
715 },
716 CompletionItem {
717 label: "baz!",
718 source_range: [252; 252),
719 delete: [252; 252),
720 insert: "baz!($0)",
721 kind: Macro,
722 detail: "#[macro_export]\nmacro_rules! baz",
723 },
724 CompletionItem {
725 label: "foo!",
726 source_range: [252; 252),
727 delete: [252; 252),
728 insert: "foo!($0)",
729 kind: Macro,
730 detail: "macro_rules! foo",
731 },
732 CompletionItem {
733 label: "m1",
734 source_range: [252; 252),
735 delete: [252; 252),
736 insert: "m1",
737 kind: Module,
738 },
739 CompletionItem {
740 label: "m2",
741 source_range: [252; 252),
742 delete: [252; 252),
743 insert: "m2",
744 kind: Module,
745 },
746 CompletionItem {
747 label: "main()",
748 source_range: [252; 252),
749 delete: [252; 252),
750 insert: "main()$0",
751 kind: Function,
752 lookup: "main",
753 detail: "fn main()",
754 },
755 ]
756 "###
757 );
758 }
759
760 #[test]
761 fn completes_both_macro_and_value() {
762 assert_debug_snapshot!(
763 do_reference_completion(
764 "
765 //- /main.rs
766 macro_rules! foo {
767 () => {}
768 }
769
770 fn foo() {
771 <|>
772 }
773 "
774 ),
775 @r###"
776 [
777 CompletionItem {
778 label: "foo!",
779 source_range: [49; 49),
780 delete: [49; 49),
781 insert: "foo!($0)",
782 kind: Macro,
783 detail: "macro_rules! foo",
784 },
785 CompletionItem {
786 label: "foo()",
787 source_range: [49; 49),
788 delete: [49; 49),
789 insert: "foo()$0",
790 kind: Function,
791 lookup: "foo",
792 detail: "fn foo()",
793 },
794 ]
795 "###
796 );
797 }
798
799 #[test]
800 fn completes_macros_as_type() {
801 assert_debug_snapshot!(
802 do_reference_completion(
803 "
804 //- /main.rs
805 macro_rules! foo {
806 () => {}
807 }
808
809 fn main() {
810 let x: <|>
811 }
812 "
813 ),
814 @r###"
815 [
816 CompletionItem {
817 label: "foo!",
818 source_range: [57; 57),
819 delete: [57; 57),
820 insert: "foo!($0)",
821 kind: Macro,
822 detail: "macro_rules! foo",
823 },
824 CompletionItem {
825 label: "main()",
826 source_range: [57; 57),
827 delete: [57; 57),
828 insert: "main()$0",
829 kind: Function,
830 lookup: "main",
831 detail: "fn main()",
832 },
833 ]
834 "###
835 );
836 }
837
838 #[test]
839 fn completes_macros_as_stmt() {
840 assert_debug_snapshot!(
841 do_reference_completion(
842 "
843 //- /main.rs
844 macro_rules! foo {
845 () => {}
846 }
847
848 fn main() {
849 <|>
850 }
851 "
852 ),
853 @r###"
854 [
855 CompletionItem {
856 label: "foo!",
857 source_range: [50; 50),
858 delete: [50; 50),
859 insert: "foo!($0)",
860 kind: Macro,
861 detail: "macro_rules! foo",
862 },
863 CompletionItem {
864 label: "main()",
865 source_range: [50; 50),
866 delete: [50; 50),
867 insert: "main()$0",
868 kind: Function,
869 lookup: "main",
870 detail: "fn main()",
871 },
872 ]
873 "###
874 );
875 }
876}
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..1f2988b36
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_snippet.rs
@@ -0,0 +1,120 @@
1//! FIXME: write short doc here
2
3use crate::completion::{
4 completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
5 CompletionKind, Completions,
6};
7
8fn snippet(ctx: &CompletionContext, label: &str, snippet: &str) -> Builder {
9 CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label)
10 .insert_snippet(snippet)
11 .kind(CompletionItemKind::Snippet)
12}
13
14pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
15 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
16 return;
17 }
18
19 snippet(ctx, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
20 snippet(ctx, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
21}
22
23pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
24 if !ctx.is_new_item {
25 return;
26 }
27 snippet(
28 ctx,
29 "Test function",
30 "\
31#[test]
32fn ${1:feature}() {
33 $0
34}",
35 )
36 .lookup_by("tfn")
37 .add_to(acc);
38
39 snippet(ctx, "pub(crate)", "pub(crate) $0").add_to(acc);
40}
41
42#[cfg(test)]
43mod tests {
44 use crate::completion::{do_completion, CompletionItem, CompletionKind};
45 use insta::assert_debug_snapshot;
46
47 fn do_snippet_completion(code: &str) -> Vec<CompletionItem> {
48 do_completion(code, CompletionKind::Snippet)
49 }
50
51 #[test]
52 fn completes_snippets_in_expressions() {
53 assert_debug_snapshot!(
54 do_snippet_completion(r"fn foo(x: i32) { <|> }"),
55 @r###"
56 [
57 CompletionItem {
58 label: "pd",
59 source_range: [17; 17),
60 delete: [17; 17),
61 insert: "eprintln!(\"$0 = {:?}\", $0);",
62 kind: Snippet,
63 },
64 CompletionItem {
65 label: "ppd",
66 source_range: [17; 17),
67 delete: [17; 17),
68 insert: "eprintln!(\"$0 = {:#?}\", $0);",
69 kind: Snippet,
70 },
71 ]
72 "###
73 );
74 }
75
76 #[test]
77 fn should_not_complete_snippets_in_path() {
78 assert_debug_snapshot!(
79 do_snippet_completion(r"fn foo(x: i32) { ::foo<|> }"),
80 @"[]"
81 );
82 assert_debug_snapshot!(
83 do_snippet_completion(r"fn foo(x: i32) { ::<|> }"),
84 @"[]"
85 );
86 }
87
88 #[test]
89 fn completes_snippets_in_items() {
90 assert_debug_snapshot!(
91 do_snippet_completion(
92 r"
93 #[cfg(test)]
94 mod tests {
95 <|>
96 }
97 "
98 ),
99 @r###"
100 [
101 CompletionItem {
102 label: "Test function",
103 source_range: [78; 78),
104 delete: [78; 78),
105 insert: "#[test]\nfn ${1:feature}() {\n $0\n}",
106 kind: Snippet,
107 lookup: "tfn",
108 },
109 CompletionItem {
110 label: "pub(crate)",
111 source_range: [78; 78),
112 delete: [78; 78),
113 insert: "pub(crate) $0",
114 kind: Snippet,
115 },
116 ]
117 "###
118 );
119 }
120}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
new file mode 100644
index 000000000..b8345c91d
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -0,0 +1,274 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{
4 algo::{find_covering_element, find_node_at_offset},
5 ast, AstNode, Parse, SourceFile,
6 SyntaxKind::*,
7 SyntaxNode, SyntaxToken, TextRange, TextUnit,
8};
9use ra_text_edit::AtomTextEdit;
10
11use crate::{db, FilePosition};
12
13/// `CompletionContext` is created early during completion to figure out, where
14/// exactly is the cursor, syntax-wise.
15#[derive(Debug)]
16pub(crate) struct CompletionContext<'a> {
17 pub(super) db: &'a db::RootDatabase,
18 pub(super) analyzer: hir::SourceAnalyzer,
19 pub(super) offset: TextUnit,
20 pub(super) token: SyntaxToken,
21 pub(super) module: Option<hir::Module>,
22 pub(super) function_syntax: Option<ast::FnDef>,
23 pub(super) use_item_syntax: Option<ast::UseItem>,
24 pub(super) record_lit_syntax: Option<ast::RecordLit>,
25 pub(super) record_lit_pat: Option<ast::RecordPat>,
26 pub(super) is_param: bool,
27 /// If a name-binding or reference to a const in a pattern.
28 /// Irrefutable patterns (like let) are excluded.
29 pub(super) is_pat_binding: bool,
30 /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
31 pub(super) is_trivial_path: bool,
32 /// If not a trivial path, the prefix (qualifier).
33 pub(super) path_prefix: Option<hir::Path>,
34 pub(super) after_if: bool,
35 /// `true` if we are a statement or a last expr in the block.
36 pub(super) can_be_stmt: bool,
37 /// Something is typed at the "top" level, in module or impl/trait.
38 pub(super) is_new_item: bool,
39 /// The receiver if this is a field or method access, i.e. writing something.<|>
40 pub(super) dot_receiver: Option<ast::Expr>,
41 pub(super) dot_receiver_is_ambiguous_float_literal: bool,
42 /// If this is a call (method or function) in particular, i.e. the () are already there.
43 pub(super) is_call: bool,
44 pub(super) is_path_type: bool,
45 pub(super) has_type_args: bool,
46}
47
48impl<'a> CompletionContext<'a> {
49 pub(super) fn new(
50 db: &'a db::RootDatabase,
51 original_parse: &'a Parse<ast::SourceFile>,
52 position: FilePosition,
53 ) -> Option<CompletionContext<'a>> {
54 let src = hir::ModuleSource::from_position(db, position);
55 let module = hir::Module::from_definition(
56 db,
57 hir::Source { file_id: position.file_id.into(), value: src },
58 );
59 let token =
60 original_parse.tree().syntax().token_at_offset(position.offset).left_biased()?;
61 let analyzer = hir::SourceAnalyzer::new(
62 db,
63 hir::Source::new(position.file_id.into(), &token.parent()),
64 Some(position.offset),
65 );
66 let mut ctx = CompletionContext {
67 db,
68 analyzer,
69 token,
70 offset: position.offset,
71 module,
72 function_syntax: None,
73 use_item_syntax: None,
74 record_lit_syntax: None,
75 record_lit_pat: None,
76 is_param: false,
77 is_pat_binding: false,
78 is_trivial_path: false,
79 path_prefix: None,
80 after_if: false,
81 can_be_stmt: false,
82 is_new_item: false,
83 dot_receiver: None,
84 is_call: false,
85 is_path_type: false,
86 has_type_args: false,
87 dot_receiver_is_ambiguous_float_literal: false,
88 };
89 ctx.fill(&original_parse, position.offset);
90 Some(ctx)
91 }
92
93 // The range of the identifier that is being completed.
94 pub(crate) fn source_range(&self) -> TextRange {
95 match self.token.kind() {
96 // workaroud when completion is triggered by trigger characters.
97 IDENT => self.token.text_range(),
98 _ => TextRange::offset_len(self.offset, 0.into()),
99 }
100 }
101
102 fn fill(&mut self, original_parse: &'a Parse<ast::SourceFile>, offset: TextUnit) {
103 // Insert a fake ident to get a valid parse tree. We will use this file
104 // to determine context, though the original_file will be used for
105 // actual completion.
106 let file = {
107 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
108 original_parse.reparse(&edit).tree()
109 };
110
111 // First, let's try to complete a reference to some declaration.
112 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
113 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
114 // See RFC#1685.
115 if is_node::<ast::Param>(name_ref.syntax()) {
116 self.is_param = true;
117 return;
118 }
119 self.classify_name_ref(original_parse.tree(), name_ref);
120 }
121
122 // Otherwise, see if this is a declaration. We can use heuristics to
123 // suggest declaration names, see `CompletionKind::Magic`.
124 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
125 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
126 let parent = bind_pat.syntax().parent();
127 if parent.clone().and_then(ast::MatchArm::cast).is_some()
128 || parent.and_then(ast::Condition::cast).is_some()
129 {
130 self.is_pat_binding = true;
131 }
132 }
133 if is_node::<ast::Param>(name.syntax()) {
134 self.is_param = true;
135 return;
136 }
137 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
138 self.record_lit_pat =
139 find_node_at_offset(original_parse.tree().syntax(), self.offset);
140 }
141 }
142 }
143
144 fn classify_name_ref(&mut self, original_file: SourceFile, name_ref: ast::NameRef) {
145 let name_range = name_ref.syntax().text_range();
146 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
147 self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset);
148 }
149
150 let top_node = name_ref
151 .syntax()
152 .ancestors()
153 .take_while(|it| it.text_range() == name_range)
154 .last()
155 .unwrap();
156
157 match top_node.parent().map(|it| it.kind()) {
158 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
159 self.is_new_item = true;
160 return;
161 }
162 _ => (),
163 }
164
165 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast);
166
167 self.function_syntax = self
168 .token
169 .parent()
170 .ancestors()
171 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
172 .find_map(ast::FnDef::cast);
173
174 let parent = match name_ref.syntax().parent() {
175 Some(it) => it,
176 None => return,
177 };
178
179 if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
180 let path = segment.parent_path();
181 self.is_call = path
182 .syntax()
183 .parent()
184 .and_then(ast::PathExpr::cast)
185 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
186 .is_some();
187
188 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
189 self.has_type_args = segment.type_arg_list().is_some();
190
191 if let Some(mut path) = hir::Path::from_ast(path.clone()) {
192 if !path.is_ident() {
193 path.segments.pop().unwrap();
194 self.path_prefix = Some(path);
195 return;
196 }
197 }
198
199 if path.qualifier().is_none() {
200 self.is_trivial_path = true;
201
202 // Find either enclosing expr statement (thing with `;`) or a
203 // block. If block, check that we are the last expr.
204 self.can_be_stmt = name_ref
205 .syntax()
206 .ancestors()
207 .find_map(|node| {
208 if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
209 return Some(
210 stmt.syntax().text_range() == name_ref.syntax().text_range(),
211 );
212 }
213 if let Some(block) = ast::Block::cast(node) {
214 return Some(
215 block.expr().map(|e| e.syntax().text_range())
216 == Some(name_ref.syntax().text_range()),
217 );
218 }
219 None
220 })
221 .unwrap_or(false);
222
223 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
224 if let Some(if_expr) =
225 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off)
226 {
227 if if_expr.syntax().text_range().end()
228 < name_ref.syntax().text_range().start()
229 {
230 self.after_if = true;
231 }
232 }
233 }
234 }
235 }
236 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
237 // The receiver comes before the point of insertion of the fake
238 // ident, so it should have the same range in the non-modified file
239 self.dot_receiver = field_expr
240 .expr()
241 .map(|e| e.syntax().text_range())
242 .and_then(|r| find_node_with_range(original_file.syntax(), r));
243 self.dot_receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) =
244 &self.dot_receiver
245 {
246 match l.kind() {
247 ast::LiteralKind::FloatNumber { suffix: _ } => l.token().text().ends_with('.'),
248 _ => false,
249 }
250 } else {
251 false
252 }
253 }
254 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
255 // As above
256 self.dot_receiver = method_call_expr
257 .expr()
258 .map(|e| e.syntax().text_range())
259 .and_then(|r| find_node_with_range(original_file.syntax(), r));
260 self.is_call = true;
261 }
262 }
263}
264
265fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
266 find_covering_element(syntax, range).ancestors().find_map(N::cast)
267}
268
269fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
270 match node.ancestors().find_map(N::cast) {
271 None => false,
272 Some(n) => n.syntax().text_range() == node.text_range(),
273 }
274}
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
new file mode 100644
index 000000000..93f336370
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -0,0 +1,322 @@
1//! FIXME: write short doc here
2
3use std::fmt;
4
5use hir::Documentation;
6use ra_syntax::TextRange;
7use ra_text_edit::TextEdit;
8
9/// `CompletionItem` describes a single completion variant in the editor pop-up.
10/// It is basically a POD with various properties. To construct a
11/// `CompletionItem`, use `new` method and the `Builder` struct.
12pub struct CompletionItem {
13 /// Used only internally in tests, to check only specific kind of
14 /// completion (postfix, keyword, reference, etc).
15 #[allow(unused)]
16 completion_kind: CompletionKind,
17 /// Label in the completion pop up which identifies completion.
18 label: String,
19 /// Range of identifier that is being completed.
20 ///
21 /// It should be used primarily for UI, but we also use this to convert
22 /// genetic TextEdit into LSP's completion edit (see conv.rs).
23 ///
24 /// `source_range` must contain the completion offset. `insert_text` should
25 /// start with what `source_range` points to, or VSCode will filter out the
26 /// completion silently.
27 source_range: TextRange,
28 /// What happens when user selects this item.
29 ///
30 /// Typically, replaces `source_range` with new identifier.
31 text_edit: TextEdit,
32 insert_text_format: InsertTextFormat,
33
34 /// What item (struct, function, etc) are we completing.
35 kind: Option<CompletionItemKind>,
36
37 /// Lookup is used to check if completion item indeed can complete current
38 /// ident.
39 ///
40 /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it
41 /// contains `bar` sub sequence), and `quux` will rejected.
42 lookup: Option<String>,
43
44 /// Additional info to show in the UI pop up.
45 detail: Option<String>,
46 documentation: Option<Documentation>,
47
48 /// Whether this item is marked as deprecated
49 deprecated: bool,
50}
51
52// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
53impl fmt::Debug for CompletionItem {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let mut s = f.debug_struct("CompletionItem");
56 s.field("label", &self.label()).field("source_range", &self.source_range());
57 if self.text_edit().as_atoms().len() == 1 {
58 let atom = &self.text_edit().as_atoms()[0];
59 s.field("delete", &atom.delete);
60 s.field("insert", &atom.insert);
61 } else {
62 s.field("text_edit", &self.text_edit);
63 }
64 if let Some(kind) = self.kind().as_ref() {
65 s.field("kind", kind);
66 }
67 if self.lookup() != self.label() {
68 s.field("lookup", &self.lookup());
69 }
70 if let Some(detail) = self.detail() {
71 s.field("detail", &detail);
72 }
73 if let Some(documentation) = self.documentation() {
74 s.field("documentation", &documentation);
75 }
76 if self.deprecated {
77 s.field("deprecated", &true);
78 }
79 s.finish()
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum CompletionItemKind {
85 Snippet,
86 Keyword,
87 Module,
88 Function,
89 BuiltinType,
90 Struct,
91 Enum,
92 EnumVariant,
93 Binding,
94 Field,
95 Static,
96 Const,
97 Trait,
98 TypeAlias,
99 Method,
100 TypeParam,
101 Macro,
102}
103
104#[derive(Debug, PartialEq, Eq, Copy, Clone)]
105pub(crate) enum CompletionKind {
106 /// Parser-based keyword completion.
107 Keyword,
108 /// Your usual "complete all valid identifiers".
109 Reference,
110 /// "Secret sauce" completions.
111 Magic,
112 Snippet,
113 Postfix,
114 BuiltinType,
115}
116
117#[derive(Debug, PartialEq, Eq, Copy, Clone)]
118pub enum InsertTextFormat {
119 PlainText,
120 Snippet,
121}
122
123impl CompletionItem {
124 pub(crate) fn new(
125 completion_kind: CompletionKind,
126 source_range: TextRange,
127 label: impl Into<String>,
128 ) -> Builder {
129 let label = label.into();
130 Builder {
131 source_range,
132 completion_kind,
133 label,
134 insert_text: None,
135 insert_text_format: InsertTextFormat::PlainText,
136 detail: None,
137 documentation: None,
138 lookup: None,
139 kind: None,
140 text_edit: None,
141 deprecated: None,
142 }
143 }
144 /// What user sees in pop-up in the UI.
145 pub fn label(&self) -> &str {
146 &self.label
147 }
148 pub fn source_range(&self) -> TextRange {
149 self.source_range
150 }
151
152 pub fn insert_text_format(&self) -> InsertTextFormat {
153 self.insert_text_format
154 }
155
156 pub fn text_edit(&self) -> &TextEdit {
157 &self.text_edit
158 }
159
160 /// Short one-line additional information, like a type
161 pub fn detail(&self) -> Option<&str> {
162 self.detail.as_ref().map(|it| it.as_str())
163 }
164 /// A doc-comment
165 pub fn documentation(&self) -> Option<Documentation> {
166 self.documentation.clone()
167 }
168 /// What string is used for filtering.
169 pub fn lookup(&self) -> &str {
170 self.lookup.as_ref().map(|it| it.as_str()).unwrap_or_else(|| self.label())
171 }
172
173 pub fn kind(&self) -> Option<CompletionItemKind> {
174 self.kind
175 }
176
177 pub fn deprecated(&self) -> bool {
178 self.deprecated
179 }
180}
181
182/// A helper to make `CompletionItem`s.
183#[must_use]
184pub(crate) struct Builder {
185 source_range: TextRange,
186 completion_kind: CompletionKind,
187 label: String,
188 insert_text: Option<String>,
189 insert_text_format: InsertTextFormat,
190 detail: Option<String>,
191 documentation: Option<Documentation>,
192 lookup: Option<String>,
193 kind: Option<CompletionItemKind>,
194 text_edit: Option<TextEdit>,
195 deprecated: Option<bool>,
196}
197
198impl Builder {
199 pub(crate) fn add_to(self, acc: &mut Completions) {
200 acc.add(self.build())
201 }
202
203 pub(crate) fn build(self) -> CompletionItem {
204 let label = self.label;
205 let text_edit = match self.text_edit {
206 Some(it) => it,
207 None => TextEdit::replace(
208 self.source_range,
209 self.insert_text.unwrap_or_else(|| label.clone()),
210 ),
211 };
212
213 CompletionItem {
214 source_range: self.source_range,
215 label,
216 insert_text_format: self.insert_text_format,
217 text_edit,
218 detail: self.detail,
219 documentation: self.documentation,
220 lookup: self.lookup,
221 kind: self.kind,
222 completion_kind: self.completion_kind,
223 deprecated: self.deprecated.unwrap_or(false),
224 }
225 }
226 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
227 self.lookup = Some(lookup.into());
228 self
229 }
230 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
231 self.label = label.into();
232 self
233 }
234 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
235 self.insert_text = Some(insert_text.into());
236 self
237 }
238 pub(crate) fn insert_snippet(mut self, snippet: impl Into<String>) -> Builder {
239 self.insert_text_format = InsertTextFormat::Snippet;
240 self.insert_text(snippet)
241 }
242 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
243 self.kind = Some(kind);
244 self
245 }
246 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
247 self.text_edit = Some(edit);
248 self
249 }
250 pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder {
251 self.insert_text_format = InsertTextFormat::Snippet;
252 self.text_edit(edit)
253 }
254 #[allow(unused)]
255 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
256 self.set_detail(Some(detail))
257 }
258 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
259 self.detail = detail.map(Into::into);
260 self
261 }
262 #[allow(unused)]
263 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
264 self.set_documentation(Some(docs))
265 }
266 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
267 self.documentation = docs.map(Into::into);
268 self
269 }
270 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
271 self.deprecated = Some(deprecated);
272 self
273 }
274}
275
276impl<'a> Into<CompletionItem> for Builder {
277 fn into(self) -> CompletionItem {
278 self.build()
279 }
280}
281
282/// Represents an in-progress set of completions being built.
283#[derive(Debug, Default)]
284pub(crate) struct Completions {
285 buf: Vec<CompletionItem>,
286}
287
288impl Completions {
289 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
290 self.buf.push(item.into())
291 }
292 pub(crate) fn add_all<I>(&mut self, items: I)
293 where
294 I: IntoIterator,
295 I::Item: Into<CompletionItem>,
296 {
297 items.into_iter().for_each(|item| self.add(item.into()))
298 }
299}
300
301impl Into<Vec<CompletionItem>> for Completions {
302 fn into(self) -> Vec<CompletionItem> {
303 self.buf
304 }
305}
306
307#[cfg(test)]
308pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
309 use crate::completion::completions;
310 use crate::mock_analysis::{analysis_and_position, single_file_with_position};
311 let (analysis, position) = if code.contains("//-") {
312 analysis_and_position(code)
313 } else {
314 single_file_with_position(code)
315 };
316 let completions = completions(&analysis.db, position).unwrap();
317 let completion_items: Vec<CompletionItem> = completions.into();
318 let mut kind_completions: Vec<CompletionItem> =
319 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
320 kind_completions.sort_by_key(|c| c.label.clone());
321 kind_completions
322}
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
new file mode 100644
index 000000000..5f056730a
--- /dev/null
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -0,0 +1,676 @@
1//! This modules takes care of rendering various definitions as completion items.
2
3use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Type};
4use join_to_string::join;
5use ra_syntax::ast::NameOwner;
6use test_utils::tested_by;
7
8use crate::completion::{
9 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
10};
11
12use crate::display::{const_label, function_label, macro_label, type_label};
13
14impl Completions {
15 pub(crate) fn add_field(
16 &mut self,
17 ctx: &CompletionContext,
18 field: hir::StructField,
19 ty: &Type,
20 ) {
21 let is_deprecated = is_deprecated(field, ctx.db);
22 CompletionItem::new(
23 CompletionKind::Reference,
24 ctx.source_range(),
25 field.name(ctx.db).to_string(),
26 )
27 .kind(CompletionItemKind::Field)
28 .detail(ty.display(ctx.db).to_string())
29 .set_documentation(field.docs(ctx.db))
30 .set_deprecated(is_deprecated)
31 .add_to(self);
32 }
33
34 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
35 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string())
36 .kind(CompletionItemKind::Field)
37 .detail(ty.display(ctx.db).to_string())
38 .add_to(self);
39 }
40
41 pub(crate) fn add_resolution(
42 &mut self,
43 ctx: &CompletionContext,
44 local_name: String,
45 resolution: &ScopeDef,
46 ) {
47 use hir::ModuleDef::*;
48
49 let completion_kind = match resolution {
50 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType,
51 _ => CompletionKind::Reference,
52 };
53
54 let kind = match resolution {
55 ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
56 ScopeDef::ModuleDef(Function(func)) => {
57 return self.add_function_with_name(ctx, Some(local_name), *func);
58 }
59 ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
60 // FIXME: add CompletionItemKind::Union
61 ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct,
62 ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
63
64 ScopeDef::ModuleDef(EnumVariant(..)) => CompletionItemKind::EnumVariant,
65 ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
66 ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
67 ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait,
68 ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias,
69 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
70 ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam,
71 ScopeDef::Local(..) => CompletionItemKind::Binding,
72 // (does this need its own kind?)
73 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
74 ScopeDef::MacroDef(mac) => {
75 return self.add_macro(ctx, Some(local_name), *mac);
76 }
77 ScopeDef::Unknown => {
78 return self.add(CompletionItem::new(
79 CompletionKind::Reference,
80 ctx.source_range(),
81 local_name,
82 ));
83 }
84 };
85
86 let docs = match resolution {
87 ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db),
88 ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db),
89 ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db),
90 ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db),
91 ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db),
92 ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db),
93 ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db),
94 _ => None,
95 };
96
97 let mut completion_item =
98 CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone());
99 if let ScopeDef::Local(local) = resolution {
100 let ty = local.ty(ctx.db);
101 if !ty.is_unknown() {
102 completion_item = completion_item.detail(ty.display(ctx.db).to_string());
103 }
104 };
105
106 // If not an import, add parenthesis automatically.
107 if ctx.is_path_type
108 && !ctx.has_type_args
109 && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis")
110 {
111 let has_non_default_type_params = match resolution {
112 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
113 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
114 _ => false,
115 };
116 if has_non_default_type_params {
117 tested_by!(inserts_angle_brackets_for_generics);
118 completion_item = completion_item
119 .lookup_by(local_name.clone())
120 .label(format!("{}<…>", local_name))
121 .insert_snippet(format!("{}<$0>", local_name));
122 }
123 }
124
125 completion_item.kind(kind).set_documentation(docs).add_to(self)
126 }
127
128 pub(crate) fn add_function(&mut self, ctx: &CompletionContext, func: hir::Function) {
129 self.add_function_with_name(ctx, None, func)
130 }
131
132 fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
133 let mut votes = [0, 0, 0];
134 for (idx, s) in docs.match_indices(&macro_name) {
135 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
136 // Ensure to match the full word
137 if after.starts_with('!')
138 && before
139 .chars()
140 .rev()
141 .next()
142 .map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric())
143 {
144 // It may have spaces before the braces like `foo! {}`
145 match after[1..].chars().find(|&c| !c.is_whitespace()) {
146 Some('{') => votes[0] += 1,
147 Some('[') => votes[1] += 1,
148 Some('(') => votes[2] += 1,
149 _ => {}
150 }
151 }
152 }
153
154 // Insert a space before `{}`.
155 // We prefer the last one when some votes equal.
156 *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
157 }
158
159 pub(crate) fn add_macro(
160 &mut self,
161 ctx: &CompletionContext,
162 name: Option<String>,
163 macro_: hir::MacroDef,
164 ) {
165 let name = match name {
166 Some(it) => it,
167 None => return,
168 };
169
170 let ast_node = macro_.source(ctx.db).value;
171 let detail = macro_label(&ast_node);
172
173 let docs = macro_.docs(ctx.db);
174 let macro_declaration = format!("{}!", name);
175
176 let mut builder =
177 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration)
178 .kind(CompletionItemKind::Macro)
179 .set_documentation(docs.clone())
180 .set_deprecated(is_deprecated(macro_, ctx.db))
181 .detail(detail);
182
183 builder = if ctx.use_item_syntax.is_some() {
184 builder.insert_text(name)
185 } else {
186 let macro_braces_to_insert =
187 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
188 builder.insert_snippet(macro_declaration + macro_braces_to_insert)
189 };
190
191 self.add(builder);
192 }
193
194 fn add_function_with_name(
195 &mut self,
196 ctx: &CompletionContext,
197 name: Option<String>,
198 func: hir::Function,
199 ) {
200 let func_name = func.name(ctx.db);
201 let has_self_param = func.has_self_param(ctx.db);
202 let params = func.params(ctx.db);
203
204 let name = name.unwrap_or_else(|| func_name.to_string());
205 let ast_node = func.source(ctx.db).value;
206 let detail = function_label(&ast_node);
207
208 let mut builder =
209 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
210 .kind(if has_self_param {
211 CompletionItemKind::Method
212 } else {
213 CompletionItemKind::Function
214 })
215 .set_documentation(func.docs(ctx.db))
216 .set_deprecated(is_deprecated(func, ctx.db))
217 .detail(detail);
218
219 // Add `<>` for generic types
220 if ctx.use_item_syntax.is_none()
221 && !ctx.is_call
222 && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis")
223 {
224 tested_by!(inserts_parens_for_function_calls);
225 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 {
226 (format!("{}()$0", func_name), format!("{}()", name))
227 } else {
228 (format!("{}($0)", func_name), format!("{}(…)", name))
229 };
230 builder = builder.lookup_by(name).label(label).insert_snippet(snippet);
231 }
232
233 self.add(builder)
234 }
235
236 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
237 let ast_node = constant.source(ctx.db).value;
238 let name = match ast_node.name() {
239 Some(name) => name,
240 _ => return,
241 };
242 let detail = const_label(&ast_node);
243
244 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
245 .kind(CompletionItemKind::Const)
246 .set_documentation(constant.docs(ctx.db))
247 .set_deprecated(is_deprecated(constant, ctx.db))
248 .detail(detail)
249 .add_to(self);
250 }
251
252 pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) {
253 let type_def = type_alias.source(ctx.db).value;
254 let name = match type_def.name() {
255 Some(name) => name,
256 _ => return,
257 };
258 let detail = type_label(&type_def);
259
260 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
261 .kind(CompletionItemKind::TypeAlias)
262 .set_documentation(type_alias.docs(ctx.db))
263 .set_deprecated(is_deprecated(type_alias, ctx.db))
264 .detail(detail)
265 .add_to(self);
266 }
267
268 pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) {
269 let is_deprecated = is_deprecated(variant, ctx.db);
270 let name = match variant.name(ctx.db) {
271 Some(it) => it,
272 None => return,
273 };
274 let detail_types = variant.fields(ctx.db).into_iter().map(|field| field.ty(ctx.db));
275 let detail = join(detail_types.map(|t| t.display(ctx.db).to_string()))
276 .separator(", ")
277 .surround_with("(", ")")
278 .to_string();
279 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
280 .kind(CompletionItemKind::EnumVariant)
281 .set_documentation(variant.docs(ctx.db))
282 .set_deprecated(is_deprecated)
283 .detail(detail)
284 .add_to(self);
285 }
286}
287
288fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool {
289 node.attrs(db).by_key("deprecated").exists()
290}
291
292#[cfg(test)]
293mod tests {
294 use insta::assert_debug_snapshot;
295 use test_utils::covers;
296
297 use crate::completion::{do_completion, CompletionItem, CompletionKind};
298
299 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
300 do_completion(code, CompletionKind::Reference)
301 }
302
303 #[test]
304 fn sets_deprecated_flag_in_completion_items() {
305 assert_debug_snapshot!(
306 do_reference_completion(
307 r#"
308 #[deprecated]
309 fn something_deprecated() {}
310
311 #[deprecated(since = "1.0.0")]
312 fn something_else_deprecated() {}
313
314 fn main() { som<|> }
315 "#,
316 ),
317 @r###"
318 [
319 CompletionItem {
320 label: "main()",
321 source_range: [203; 206),
322 delete: [203; 206),
323 insert: "main()$0",
324 kind: Function,
325 lookup: "main",
326 detail: "fn main()",
327 },
328 CompletionItem {
329 label: "something_deprecated()",
330 source_range: [203; 206),
331 delete: [203; 206),
332 insert: "something_deprecated()$0",
333 kind: Function,
334 lookup: "something_deprecated",
335 detail: "fn something_deprecated()",
336 deprecated: true,
337 },
338 CompletionItem {
339 label: "something_else_deprecated()",
340 source_range: [203; 206),
341 delete: [203; 206),
342 insert: "something_else_deprecated()$0",
343 kind: Function,
344 lookup: "something_else_deprecated",
345 detail: "fn something_else_deprecated()",
346 deprecated: true,
347 },
348 ]
349 "###
350 );
351 }
352
353 #[test]
354 fn inserts_parens_for_function_calls() {
355 covers!(inserts_parens_for_function_calls);
356 assert_debug_snapshot!(
357 do_reference_completion(
358 r"
359 fn no_args() {}
360 fn main() { no_<|> }
361 "
362 ),
363 @r###"
364 [
365 CompletionItem {
366 label: "main()",
367 source_range: [61; 64),
368 delete: [61; 64),
369 insert: "main()$0",
370 kind: Function,
371 lookup: "main",
372 detail: "fn main()",
373 },
374 CompletionItem {
375 label: "no_args()",
376 source_range: [61; 64),
377 delete: [61; 64),
378 insert: "no_args()$0",
379 kind: Function,
380 lookup: "no_args",
381 detail: "fn no_args()",
382 },
383 ]
384 "###
385 );
386 assert_debug_snapshot!(
387 do_reference_completion(
388 r"
389 fn with_args(x: i32, y: String) {}
390 fn main() { with_<|> }
391 "
392 ),
393 @r###"
394 [
395 CompletionItem {
396 label: "main()",
397 source_range: [80; 85),
398 delete: [80; 85),
399 insert: "main()$0",
400 kind: Function,
401 lookup: "main",
402 detail: "fn main()",
403 },
404 CompletionItem {
405 label: "with_args(…)",
406 source_range: [80; 85),
407 delete: [80; 85),
408 insert: "with_args($0)",
409 kind: Function,
410 lookup: "with_args",
411 detail: "fn with_args(x: i32, y: String)",
412 },
413 ]
414 "###
415 );
416 assert_debug_snapshot!(
417 do_reference_completion(
418 r"
419 struct S {}
420 impl S {
421 fn foo(&self) {}
422 }
423 fn bar(s: &S) {
424 s.f<|>
425 }
426 "
427 ),
428 @r###"
429 [
430 CompletionItem {
431 label: "foo()",
432 source_range: [163; 164),
433 delete: [163; 164),
434 insert: "foo()$0",
435 kind: Method,
436 lookup: "foo",
437 detail: "fn foo(&self)",
438 },
439 ]
440 "###
441 );
442 }
443
444 #[test]
445 fn dont_render_function_parens_in_use_item() {
446 assert_debug_snapshot!(
447 do_reference_completion(
448 "
449 //- /lib.rs
450 mod m { pub fn foo() {} }
451 use crate::m::f<|>;
452 "
453 ),
454 @r###"
455 [
456 CompletionItem {
457 label: "foo",
458 source_range: [40; 41),
459 delete: [40; 41),
460 insert: "foo",
461 kind: Function,
462 detail: "pub fn foo()",
463 },
464 ]
465 "###
466 );
467 }
468
469 #[test]
470 fn dont_render_function_parens_if_already_call() {
471 assert_debug_snapshot!(
472 do_reference_completion(
473 "
474 //- /lib.rs
475 fn frobnicate() {}
476 fn main() {
477 frob<|>();
478 }
479 "
480 ),
481 @r###"
482 [
483 CompletionItem {
484 label: "frobnicate",
485 source_range: [35; 39),
486 delete: [35; 39),
487 insert: "frobnicate",
488 kind: Function,
489 detail: "fn frobnicate()",
490 },
491 CompletionItem {
492 label: "main",
493 source_range: [35; 39),
494 delete: [35; 39),
495 insert: "main",
496 kind: Function,
497 detail: "fn main()",
498 },
499 ]
500 "###
501 );
502 assert_debug_snapshot!(
503 do_reference_completion(
504 "
505 //- /lib.rs
506 struct Foo {}
507 impl Foo { fn new() -> Foo {} }
508 fn main() {
509 Foo::ne<|>();
510 }
511 "
512 ),
513 @r###"
514 [
515 CompletionItem {
516 label: "new",
517 source_range: [67; 69),
518 delete: [67; 69),
519 insert: "new",
520 kind: Function,
521 detail: "fn new() -> Foo",
522 },
523 ]
524 "###
525 );
526 }
527
528 #[test]
529 fn inserts_angle_brackets_for_generics() {
530 covers!(inserts_angle_brackets_for_generics);
531 assert_debug_snapshot!(
532 do_reference_completion(
533 r"
534 struct Vec<T> {}
535 fn foo(xs: Ve<|>)
536 "
537 ),
538 @r###"
539 [
540 CompletionItem {
541 label: "Vec<…>",
542 source_range: [61; 63),
543 delete: [61; 63),
544 insert: "Vec<$0>",
545 kind: Struct,
546 lookup: "Vec",
547 },
548 CompletionItem {
549 label: "foo(…)",
550 source_range: [61; 63),
551 delete: [61; 63),
552 insert: "foo($0)",
553 kind: Function,
554 lookup: "foo",
555 detail: "fn foo(xs: Ve)",
556 },
557 ]
558 "###
559 );
560 assert_debug_snapshot!(
561 do_reference_completion(
562 r"
563 type Vec<T> = (T,);
564 fn foo(xs: Ve<|>)
565 "
566 ),
567 @r###"
568 [
569 CompletionItem {
570 label: "Vec<…>",
571 source_range: [64; 66),
572 delete: [64; 66),
573 insert: "Vec<$0>",
574 kind: TypeAlias,
575 lookup: "Vec",
576 },
577 CompletionItem {
578 label: "foo(…)",
579 source_range: [64; 66),
580 delete: [64; 66),
581 insert: "foo($0)",
582 kind: Function,
583 lookup: "foo",
584 detail: "fn foo(xs: Ve)",
585 },
586 ]
587 "###
588 );
589 assert_debug_snapshot!(
590 do_reference_completion(
591 r"
592 struct Vec<T = i128> {}
593 fn foo(xs: Ve<|>)
594 "
595 ),
596 @r###"
597 [
598 CompletionItem {
599 label: "Vec",
600 source_range: [68; 70),
601 delete: [68; 70),
602 insert: "Vec",
603 kind: Struct,
604 },
605 CompletionItem {
606 label: "foo(…)",
607 source_range: [68; 70),
608 delete: [68; 70),
609 insert: "foo($0)",
610 kind: Function,
611 lookup: "foo",
612 detail: "fn foo(xs: Ve)",
613 },
614 ]
615 "###
616 );
617 assert_debug_snapshot!(
618 do_reference_completion(
619 r"
620 struct Vec<T> {}
621 fn foo(xs: Ve<|><i128>)
622 "
623 ),
624 @r###"
625 [
626 CompletionItem {
627 label: "Vec",
628 source_range: [61; 63),
629 delete: [61; 63),
630 insert: "Vec",
631 kind: Struct,
632 },
633 CompletionItem {
634 label: "foo(…)",
635 source_range: [61; 63),
636 delete: [61; 63),
637 insert: "foo($0)",
638 kind: Function,
639 lookup: "foo",
640 detail: "fn foo(xs: Ve<i128>)",
641 },
642 ]
643 "###
644 );
645 }
646
647 #[test]
648 fn dont_insert_macro_call_braces_in_use() {
649 assert_debug_snapshot!(
650 do_reference_completion(
651 r"
652 //- /main.rs
653 use foo::<|>;
654
655 //- /foo/lib.rs
656 #[macro_export]
657 macro_rules frobnicate {
658 () => ()
659 }
660 "
661 ),
662 @r###"
663 [
664 CompletionItem {
665 label: "frobnicate!",
666 source_range: [9; 9),
667 delete: [9; 9),
668 insert: "frobnicate",
669 kind: Macro,
670 detail: "#[macro_export]\nmacro_rules! frobnicate",
671 },
672 ]
673 "###
674 )
675 }
676}