aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/completion')
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs107
-rw-r--r--crates/ra_analysis/src/completion/complete_keywords.rs206
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs225
3 files changed, 316 insertions, 222 deletions
diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..d05a5e3cf
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -0,0 +1,107 @@
1use ra_syntax::{
2 algo::{
3 visit::{visitor_ctx, VisitorCtx}
4 },
5 ast,
6 AstNode,
7};
8use rustc_hash::{FxHashMap};
9
10use crate::{
11 completion::{SyntaxContext, Completions, CompletionKind, CompletionItem},
12};
13
14/// Complete repeated parametes, both name and type. For example, if all
15/// functions in a file have a `spam: &mut Spam` parameter, a completion with
16/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
17/// suggested.
18pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &SyntaxContext) {
19 if !ctx.is_param {
20 return;
21 }
22
23 let mut params = FxHashMap::default();
24 for node in ctx.leaf.ancestors() {
25 let _ = visitor_ctx(&mut params)
26 .visit::<ast::SourceFile, _>(process)
27 .visit::<ast::ItemList, _>(process)
28 .accept(node);
29 }
30 params
31 .into_iter()
32 .filter_map(|(label, (count, param))| {
33 let lookup = param.pat()?.syntax().text().to_string();
34 if count < 2 {
35 None
36 } else {
37 Some((label, lookup))
38 }
39 })
40 .for_each(|(label, lookup)| {
41 CompletionItem::new(label)
42 .lookup_by(lookup)
43 .kind(CompletionKind::Magic)
44 .add_to(acc)
45 });
46
47 fn process<'a, N: ast::FnDefOwner<'a>>(
48 node: N,
49 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
50 ) {
51 node.functions()
52 .filter_map(|it| it.param_list())
53 .flat_map(|it| it.params())
54 .for_each(|param| {
55 let text = param.syntax().text().to_string();
56 params.entry(text).or_insert((0, param)).0 += 1;
57 })
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use crate::completion::*;
64
65 fn check_magic_completion(code: &str, expected_completions: &str) {
66 check_completion(code, expected_completions, CompletionKind::Magic);
67 }
68
69 #[test]
70 fn test_param_completion_last_param() {
71 check_magic_completion(
72 r"
73 fn foo(file_id: FileId) {}
74 fn bar(file_id: FileId) {}
75 fn baz(file<|>) {}
76 ",
77 r#"file_id "file_id: FileId""#,
78 );
79 }
80
81 #[test]
82 fn test_param_completion_nth_param() {
83 check_magic_completion(
84 r"
85 fn foo(file_id: FileId) {}
86 fn bar(file_id: FileId) {}
87 fn baz(file<|>, x: i32) {}
88 ",
89 r#"file_id "file_id: FileId""#,
90 );
91 }
92
93 #[test]
94 fn test_param_completion_trait_param() {
95 check_magic_completion(
96 r"
97 pub(crate) trait SourceRoot {
98 pub fn contains(&self, file_id: FileId) -> bool;
99 pub fn module_map(&self) -> &ModuleMap;
100 pub fn lines(&self, file_id: FileId) -> &LineIndex;
101 pub fn syntax(&self, file<|>)
102 }
103 ",
104 r#"file_id "file_id: FileId""#,
105 );
106 }
107}
diff --git a/crates/ra_analysis/src/completion/complete_keywords.rs b/crates/ra_analysis/src/completion/complete_keywords.rs
new file mode 100644
index 000000000..d0a6ec19e
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_keywords.rs
@@ -0,0 +1,206 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNodeRef,
6};
7
8use crate::{
9 completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*},
10};
11
12pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) {
13 if !ctx.is_trivial_path {
14 return;
15 }
16 let fn_def = match ctx.enclosing_fn {
17 Some(it) => it,
18 None => return,
19 };
20 acc.add(keyword("if", "if $0 {}"));
21 acc.add(keyword("match", "match $0 {}"));
22 acc.add(keyword("while", "while $0 {}"));
23 acc.add(keyword("loop", "loop {$0}"));
24
25 if ctx.after_if {
26 acc.add(keyword("else", "else {$0}"));
27 acc.add(keyword("else if", "else if $0 {}"));
28 }
29 if is_in_loop_body(ctx.leaf) {
30 acc.add(keyword("continue", "continue"));
31 acc.add(keyword("break", "break"));
32 }
33 acc.add_all(complete_return(fn_def, ctx.is_stmt));
34}
35
36fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
37 for node in leaf.ancestors() {
38 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
39 break;
40 }
41 let loop_body = visitor()
42 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
43 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
44 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
45 .accept(node);
46 if let Some(Some(body)) = loop_body {
47 if leaf.range().is_subrange(&body.syntax().range()) {
48 return true;
49 }
50 }
51 }
52 false
53}
54
55fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> {
56 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
57 (true, true) => "return $0;",
58 (true, false) => "return;",
59 (false, true) => "return $0",
60 (false, false) => "return",
61 };
62 Some(keyword("return", snip))
63}
64
65fn keyword(kw: &str, snippet: &str) -> CompletionItem {
66 CompletionItem::new(kw)
67 .kind(Keyword)
68 .snippet(snippet)
69 .build()
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::completion::{CompletionKind, check_completion};
75 fn check_keyword_completion(code: &str, expected_completions: &str) {
76 check_completion(code, expected_completions, CompletionKind::Keyword);
77 }
78
79 #[test]
80 fn test_completion_kewords() {
81 check_keyword_completion(
82 r"
83 fn quux() {
84 <|>
85 }
86 ",
87 r#"
88 if "if $0 {}"
89 match "match $0 {}"
90 while "while $0 {}"
91 loop "loop {$0}"
92 return "return"
93 "#,
94 );
95 }
96
97 #[test]
98 fn test_completion_else() {
99 check_keyword_completion(
100 r"
101 fn quux() {
102 if true {
103 ()
104 } <|>
105 }
106 ",
107 r#"
108 if "if $0 {}"
109 match "match $0 {}"
110 while "while $0 {}"
111 loop "loop {$0}"
112 else "else {$0}"
113 else if "else if $0 {}"
114 return "return"
115 "#,
116 );
117 }
118
119 #[test]
120 fn test_completion_return_value() {
121 check_keyword_completion(
122 r"
123 fn quux() -> i32 {
124 <|>
125 92
126 }
127 ",
128 r#"
129 if "if $0 {}"
130 match "match $0 {}"
131 while "while $0 {}"
132 loop "loop {$0}"
133 return "return $0;"
134 "#,
135 );
136 check_keyword_completion(
137 r"
138 fn quux() {
139 <|>
140 92
141 }
142 ",
143 r#"
144 if "if $0 {}"
145 match "match $0 {}"
146 while "while $0 {}"
147 loop "loop {$0}"
148 return "return;"
149 "#,
150 );
151 }
152
153 #[test]
154 fn test_completion_return_no_stmt() {
155 check_keyword_completion(
156 r"
157 fn quux() -> i32 {
158 match () {
159 () => <|>
160 }
161 }
162 ",
163 r#"
164 if "if $0 {}"
165 match "match $0 {}"
166 while "while $0 {}"
167 loop "loop {$0}"
168 return "return $0"
169 "#,
170 );
171 }
172
173 #[test]
174 fn test_continue_break_completion() {
175 check_keyword_completion(
176 r"
177 fn quux() -> i32 {
178 loop { <|> }
179 }
180 ",
181 r#"
182 if "if $0 {}"
183 match "match $0 {}"
184 while "while $0 {}"
185 loop "loop {$0}"
186 continue "continue"
187 break "break"
188 return "return $0"
189 "#,
190 );
191 check_keyword_completion(
192 r"
193 fn quux() -> i32 {
194 loop { || { <|> } }
195 }
196 ",
197 r#"
198 if "if $0 {}"
199 match "match $0 {}"
200 while "while $0 {}"
201 loop "loop {$0}"
202 return "return $0"
203 "#,
204 );
205 }
206}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
index c2ac95453..15ff4c5dd 100644
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ b/crates/ra_analysis/src/completion/reference_completion.rs
@@ -1,9 +1,7 @@
1use rustc_hash::{FxHashSet}; 1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{ 2use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode, 3 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner}, 4 ast,
7 SyntaxKind::*, 5 SyntaxKind::*,
8}; 6};
9use hir::{ 7use hir::{
@@ -21,7 +19,7 @@ pub(super) fn completions(
21 acc: &mut Completions, 19 acc: &mut Completions,
22 db: &RootDatabase, 20 db: &RootDatabase,
23 module: &hir::Module, 21 module: &hir::Module,
24 file: &SourceFileNode, 22 _file: &SourceFileNode,
25 name_ref: ast::NameRef, 23 name_ref: ast::NameRef,
26) -> Cancelable<()> { 24) -> Cancelable<()> {
27 let kind = match classify_name_ref(name_ref) { 25 let kind = match classify_name_ref(name_ref) {
@@ -34,7 +32,7 @@ pub(super) fn completions(
34 if let Some(fn_def) = enclosing_fn { 32 if let Some(fn_def) = enclosing_fn {
35 let scopes = FnScopes::new(fn_def); 33 let scopes = FnScopes::new(fn_def);
36 complete_fn(name_ref, &scopes, acc); 34 complete_fn(name_ref, &scopes, acc);
37 complete_expr_keywords(&file, fn_def, name_ref, acc); 35 // complete_expr_keywords(&file, fn_def, name_ref, acc);
38 complete_expr_snippets(acc); 36 complete_expr_snippets(acc);
39 } 37 }
40 38
@@ -182,91 +180,6 @@ fn ${1:feature}() {
182 .add_to(acc); 180 .add_to(acc);
183} 181}
184 182
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) { 183fn complete_expr_snippets(acc: &mut Completions) {
271 CompletionItem::new("pd") 184 CompletionItem::new("pd")
272 .snippet("eprintln!(\"$0 = {:?}\", $0);") 185 .snippet("eprintln!(\"$0 = {:?}\", $0);")
@@ -286,10 +199,6 @@ mod tests {
286 check_completion(code, expected_completions, CompletionKind::Reference); 199 check_completion(code, expected_completions, CompletionKind::Reference);
287 } 200 }
288 201
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) { 202 fn check_snippet_completion(code: &str, expected_completions: &str) {
294 check_completion(code, expected_completions, CompletionKind::Snippet); 203 check_completion(code, expected_completions, CompletionKind::Snippet);
295 } 204 }
@@ -471,134 +380,6 @@ mod tests {
471 } 380 }
472 381
473 #[test] 382 #[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() { 383 fn completes_snippets_in_expressions() {
603 check_snippet_completion( 384 check_snippet_completion(
604 r"fn foo(x: i32) { <|> }", 385 r"fn foo(x: i32) { <|> }",