aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion/reference_completion.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/completion/reference_completion.rs')
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs633
1 files changed, 0 insertions, 633 deletions
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
deleted file mode 100644
index c2ac95453..000000000
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ /dev/null
@@ -1,633 +0,0 @@
1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner},
7 SyntaxKind::*,
8};
9use hir::{
10 self,
11 FnScopes, Def, Path
12};
13
14use crate::{
15 db::RootDatabase,
16 completion::{CompletionItem, Completions, CompletionKind::*},
17 Cancelable
18};
19
20pub(super) fn completions(
21 acc: &mut Completions,
22 db: &RootDatabase,
23 module: &hir::Module,
24 file: &SourceFileNode,
25 name_ref: ast::NameRef,
26) -> Cancelable<()> {
27 let kind = match classify_name_ref(name_ref) {
28 Some(it) => it,
29 None => return Ok(()),
30 };
31
32 match kind {
33 NameRefKind::LocalRef { enclosing_fn } => {
34 if let Some(fn_def) = enclosing_fn {
35 let scopes = FnScopes::new(fn_def);
36 complete_fn(name_ref, &scopes, acc);
37 complete_expr_keywords(&file, fn_def, name_ref, acc);
38 complete_expr_snippets(acc);
39 }
40
41 let module_scope = module.scope(db)?;
42 module_scope
43 .entries()
44 .filter(|(_name, res)| {
45 // Don't expose this item
46 match res.import {
47 None => true,
48 Some(import) => {
49 let range = import.range(db, module.source().file_id());
50 !range.is_subrange(&name_ref.syntax().range())
51 }
52 }
53 })
54 .for_each(|(name, _res)| {
55 CompletionItem::new(name.to_string())
56 .kind(Reference)
57 .add_to(acc)
58 });
59 }
60 NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
61 NameRefKind::BareIdentInMod => {
62 let name_range = name_ref.syntax().range();
63 let top_node = name_ref
64 .syntax()
65 .ancestors()
66 .take_while(|it| it.range() == name_range)
67 .last()
68 .unwrap();
69 match top_node.parent().map(|it| it.kind()) {
70 Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc),
71 _ => (),
72 }
73 }
74 }
75 Ok(())
76}
77
78enum NameRefKind<'a> {
79 /// NameRef is a part of single-segment path, for example, a refernece to a
80 /// local variable.
81 LocalRef {
82 enclosing_fn: Option<ast::FnDef<'a>>,
83 },
84 /// NameRef is the last segment in some path
85 Path(Path),
86 /// NameRef is bare identifier at the module's root.
87 /// Used for keyword completion
88 BareIdentInMod,
89}
90
91fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
92 let name_range = name_ref.syntax().range();
93 let top_node = name_ref
94 .syntax()
95 .ancestors()
96 .take_while(|it| it.range() == name_range)
97 .last()
98 .unwrap();
99 match top_node.parent().map(|it| it.kind()) {
100 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
101 _ => (),
102 }
103
104 let parent = name_ref.syntax().parent()?;
105 if let Some(segment) = ast::PathSegment::cast(parent) {
106 let path = segment.parent_path();
107 if let Some(path) = Path::from_ast(path) {
108 if !path.is_ident() {
109 return Some(NameRefKind::Path(path));
110 }
111 }
112 if path.qualifier().is_none() {
113 let enclosing_fn = name_ref
114 .syntax()
115 .ancestors()
116 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
117 .find_map(ast::FnDef::cast);
118 return Some(NameRefKind::LocalRef { enclosing_fn });
119 }
120 }
121 None
122}
123
124fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) {
125 let mut shadowed = FxHashSet::default();
126 scopes
127 .scope_chain(name_ref.syntax())
128 .flat_map(|scope| scopes.entries(scope).iter())
129 .filter(|entry| shadowed.insert(entry.name()))
130 .for_each(|entry| {
131 CompletionItem::new(entry.name().to_string())
132 .kind(Reference)
133 .add_to(acc)
134 });
135 if scopes.self_param.is_some() {
136 CompletionItem::new("self").kind(Reference).add_to(acc);
137 }
138}
139
140fn complete_path(
141 acc: &mut Completions,
142 db: &RootDatabase,
143 module: &hir::Module,
144 mut path: Path,
145) -> Cancelable<()> {
146 if path.segments.is_empty() {
147 return Ok(());
148 }
149 path.segments.pop();
150 let def_id = match module.resolve_path(db, path)? {
151 None => return Ok(()),
152 Some(it) => it,
153 };
154 let target_module = match def_id.resolve(db)? {
155 Def::Module(it) => it,
156 _ => return Ok(()),
157 };
158 let module_scope = target_module.scope(db)?;
159 module_scope.entries().for_each(|(name, _res)| {
160 CompletionItem::new(name.to_string())
161 .kind(Reference)
162 .add_to(acc)
163 });
164 Ok(())
165}
166
167fn complete_mod_item_snippets(acc: &mut Completions) {
168 CompletionItem::new("Test function")
169 .lookup_by("tfn")
170 .snippet(
171 "\
172#[test]
173fn ${1:feature}() {
174 $0
175}",
176 )
177 .kind(Snippet)
178 .add_to(acc);
179 CompletionItem::new("pub(crate)")
180 .snippet("pub(crate) $0")
181 .kind(Snippet)
182 .add_to(acc);
183}
184
185fn complete_expr_keywords(
186 file: &SourceFileNode,
187 fn_def: ast::FnDef,
188 name_ref: ast::NameRef,
189 acc: &mut Completions,
190) {
191 acc.add(keyword("if", "if $0 {}"));
192 acc.add(keyword("match", "match $0 {}"));
193 acc.add(keyword("while", "while $0 {}"));
194 acc.add(keyword("loop", "loop {$0}"));
195
196 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
197 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
198 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
199 acc.add(keyword("else", "else {$0}"));
200 acc.add(keyword("else if", "else if $0 {}"));
201 }
202 }
203 }
204 if is_in_loop_body(name_ref) {
205 acc.add(keyword("continue", "continue"));
206 acc.add(keyword("break", "break"));
207 }
208 acc.add_all(complete_return(fn_def, name_ref));
209}
210
211fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
212 for node in name_ref.syntax().ancestors() {
213 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
214 break;
215 }
216 let loop_body = visitor()
217 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
218 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
219 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
220 .accept(node);
221 if let Some(Some(body)) = loop_body {
222 if name_ref
223 .syntax()
224 .range()
225 .is_subrange(&body.syntax().range())
226 {
227 return true;
228 }
229 }
230 }
231 false
232}
233
234fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
235 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
236 // .next()
237 // .and_then(|it| it.syntax().parent())
238 // .and_then(ast::Block::cast)
239 // .is_some();
240
241 // if is_last_in_block {
242 // return None;
243 // }
244
245 let is_stmt = match name_ref
246 .syntax()
247 .ancestors()
248 .filter_map(ast::ExprStmt::cast)
249 .next()
250 {
251 None => false,
252 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
253 };
254 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
255 (true, true) => "return $0;",
256 (true, false) => "return;",
257 (false, true) => "return $0",
258 (false, false) => "return",
259 };
260 Some(keyword("return", snip))
261}
262
263fn keyword(kw: &str, snippet: &str) -> CompletionItem {
264 CompletionItem::new(kw)
265 .kind(Keyword)
266 .snippet(snippet)
267 .build()
268}
269
270fn complete_expr_snippets(acc: &mut Completions) {
271 CompletionItem::new("pd")
272 .snippet("eprintln!(\"$0 = {:?}\", $0);")
273 .kind(Snippet)
274 .add_to(acc);
275 CompletionItem::new("ppd")
276 .snippet("eprintln!(\"$0 = {:#?}\", $0);")
277 .kind(Snippet)
278 .add_to(acc);
279}
280
281#[cfg(test)]
282mod tests {
283 use crate::completion::{CompletionKind, check_completion};
284
285 fn check_reference_completion(code: &str, expected_completions: &str) {
286 check_completion(code, expected_completions, CompletionKind::Reference);
287 }
288
289 fn check_keyword_completion(code: &str, expected_completions: &str) {
290 check_completion(code, expected_completions, CompletionKind::Keyword);
291 }
292
293 fn check_snippet_completion(code: &str, expected_completions: &str) {
294 check_completion(code, expected_completions, CompletionKind::Snippet);
295 }
296
297 #[test]
298 fn test_completion_let_scope() {
299 check_reference_completion(
300 r"
301 fn quux(x: i32) {
302 let y = 92;
303 1 + <|>;
304 let z = ();
305 }
306 ",
307 "y;x;quux",
308 );
309 }
310
311 #[test]
312 fn test_completion_if_let_scope() {
313 check_reference_completion(
314 r"
315 fn quux() {
316 if let Some(x) = foo() {
317 let y = 92;
318 };
319 if let Some(a) = bar() {
320 let b = 62;
321 1 + <|>
322 }
323 }
324 ",
325 "b;a;quux",
326 );
327 }
328
329 #[test]
330 fn test_completion_for_scope() {
331 check_reference_completion(
332 r"
333 fn quux() {
334 for x in &[1, 2, 3] {
335 <|>
336 }
337 }
338 ",
339 "x;quux",
340 );
341 }
342
343 #[test]
344 fn test_completion_mod_scope() {
345 check_reference_completion(
346 r"
347 struct Foo;
348 enum Baz {}
349 fn quux() {
350 <|>
351 }
352 ",
353 "quux;Foo;Baz",
354 );
355 }
356
357 #[test]
358 fn test_completion_mod_scope_no_self_use() {
359 check_reference_completion(
360 r"
361 use foo<|>;
362 ",
363 "",
364 );
365 }
366
367 #[test]
368 fn test_completion_self_path() {
369 check_reference_completion(
370 r"
371 use self::m::<|>;
372
373 mod m {
374 struct Bar;
375 }
376 ",
377 "Bar",
378 );
379 }
380
381 #[test]
382 fn test_completion_mod_scope_nested() {
383 check_reference_completion(
384 r"
385 struct Foo;
386 mod m {
387 struct Bar;
388 fn quux() { <|> }
389 }
390 ",
391 "quux;Bar",
392 );
393 }
394
395 #[test]
396 fn test_complete_type() {
397 check_reference_completion(
398 r"
399 struct Foo;
400 fn x() -> <|>
401 ",
402 "Foo;x",
403 )
404 }
405
406 #[test]
407 fn test_complete_shadowing() {
408 check_reference_completion(
409 r"
410 fn foo() -> {
411 let bar = 92;
412 {
413 let bar = 62;
414 <|>
415 }
416 }
417 ",
418 "bar;foo",
419 )
420 }
421
422 #[test]
423 fn test_complete_self() {
424 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
425 }
426
427 #[test]
428 fn test_complete_crate_path() {
429 check_reference_completion(
430 "
431 //- /lib.rs
432 mod foo;
433 struct Spam;
434 //- /foo.rs
435 use crate::Sp<|>
436 ",
437 "Spam;foo",
438 );
439 }
440
441 #[test]
442 fn test_complete_crate_path_with_braces() {
443 check_reference_completion(
444 "
445 //- /lib.rs
446 mod foo;
447 struct Spam;
448 //- /foo.rs
449 use crate::{Sp<|>};
450 ",
451 "Spam;foo",
452 );
453 }
454
455 #[test]
456 fn test_complete_crate_path_in_nested_tree() {
457 check_reference_completion(
458 "
459 //- /lib.rs
460 mod foo;
461 pub mod bar {
462 pub mod baz {
463 pub struct Spam;
464 }
465 }
466 //- /foo.rs
467 use crate::{bar::{baz::Sp<|>}};
468 ",
469 "Spam",
470 );
471 }
472
473 #[test]
474 fn test_completion_kewords() {
475 check_keyword_completion(
476 r"
477 fn quux() {
478 <|>
479 }
480 ",
481 r#"
482 if "if $0 {}"
483 match "match $0 {}"
484 while "while $0 {}"
485 loop "loop {$0}"
486 return "return"
487 "#,
488 );
489 }
490
491 #[test]
492 fn test_completion_else() {
493 check_keyword_completion(
494 r"
495 fn quux() {
496 if true {
497 ()
498 } <|>
499 }
500 ",
501 r#"
502 if "if $0 {}"
503 match "match $0 {}"
504 while "while $0 {}"
505 loop "loop {$0}"
506 else "else {$0}"
507 else if "else if $0 {}"
508 return "return"
509 "#,
510 );
511 }
512
513 #[test]
514 fn test_completion_return_value() {
515 check_keyword_completion(
516 r"
517 fn quux() -> i32 {
518 <|>
519 92
520 }
521 ",
522 r#"
523 if "if $0 {}"
524 match "match $0 {}"
525 while "while $0 {}"
526 loop "loop {$0}"
527 return "return $0;"
528 "#,
529 );
530 check_keyword_completion(
531 r"
532 fn quux() {
533 <|>
534 92
535 }
536 ",
537 r#"
538 if "if $0 {}"
539 match "match $0 {}"
540 while "while $0 {}"
541 loop "loop {$0}"
542 return "return;"
543 "#,
544 );
545 }
546
547 #[test]
548 fn test_completion_return_no_stmt() {
549 check_keyword_completion(
550 r"
551 fn quux() -> i32 {
552 match () {
553 () => <|>
554 }
555 }
556 ",
557 r#"
558 if "if $0 {}"
559 match "match $0 {}"
560 while "while $0 {}"
561 loop "loop {$0}"
562 return "return $0"
563 "#,
564 );
565 }
566
567 #[test]
568 fn test_continue_break_completion() {
569 check_keyword_completion(
570 r"
571 fn quux() -> i32 {
572 loop { <|> }
573 }
574 ",
575 r#"
576 if "if $0 {}"
577 match "match $0 {}"
578 while "while $0 {}"
579 loop "loop {$0}"
580 continue "continue"
581 break "break"
582 return "return $0"
583 "#,
584 );
585 check_keyword_completion(
586 r"
587 fn quux() -> i32 {
588 loop { || { <|> } }
589 }
590 ",
591 r#"
592 if "if $0 {}"
593 match "match $0 {}"
594 while "while $0 {}"
595 loop "loop {$0}"
596 return "return $0"
597 "#,
598 );
599 }
600
601 #[test]
602 fn completes_snippets_in_expressions() {
603 check_snippet_completion(
604 r"fn foo(x: i32) { <|> }",
605 r##"
606 pd "eprintln!(\"$0 = {:?}\", $0);"
607 ppd "eprintln!(\"$0 = {:#?}\", $0);"
608 "##,
609 );
610 }
611
612 #[test]
613 fn completes_snippets_in_items() {
614 // check_snippet_completion(r"
615 // <|>
616 // ",
617 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
618 // );
619 check_snippet_completion(
620 r"
621 #[cfg(test)]
622 mod tests {
623 <|>
624 }
625 ",
626 r##"
627 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
628 pub(crate) "pub(crate) $0"
629 "##,
630 );
631 }
632
633}