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_keyword.rs940
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs51
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs26
-rw-r--r--crates/ra_ide/src/completion/patterns.rs194
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs46
5 files changed, 617 insertions, 640 deletions
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
index fd95bc410..b2f621a11 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -1,11 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ 3use ra_syntax::ast;
4 ast::{self, LoopBodyOwner},
5 match_ast, AstNode,
6 SyntaxKind::*,
7 SyntaxToken,
8};
9 4
10use crate::completion::{ 5use crate::completion::{
11 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, 6 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
@@ -41,68 +36,122 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
41 } 36 }
42} 37}
43 38
44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { 39pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
45 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) 40 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
46 .kind(CompletionItemKind::Keyword); 41 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
47 42 add_keyword(ctx, acc, "where", "where ");
48 match ctx.config.snippet_cap { 43 return;
49 Some(cap) => res.insert_snippet(cap, snippet),
50 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
51 } 44 }
52 .build() 45 if ctx.unsafe_is_prev {
53} 46 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
47 add_keyword(ctx, acc, "fn", "fn $0() {}")
48 }
49
50 if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent)
51 || ctx.block_expr_parent
52 {
53 add_keyword(ctx, acc, "trait", "trait $0 {}");
54 add_keyword(ctx, acc, "impl", "impl $0 {}");
55 }
54 56
55pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
56 if !ctx.is_trivial_path {
57 return; 57 return;
58 } 58 }
59 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
60 add_keyword(ctx, acc, "fn", "fn $0() {}");
61 }
62 if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent)
63 || ctx.block_expr_parent
64 {
65 add_keyword(ctx, acc, "use", "use ");
66 add_keyword(ctx, acc, "impl", "impl $0 {}");
67 add_keyword(ctx, acc, "trait", "trait $0 {}");
68 }
59 69
60 let fn_def = match &ctx.function_syntax { 70 if ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent {
61 Some(it) => it, 71 add_keyword(ctx, acc, "enum", "enum $0 {}");
62 None => return, 72 add_keyword(ctx, acc, "struct", "struct $0 {}");
63 }; 73 add_keyword(ctx, acc, "union", "union $0 {}");
64 acc.add(keyword(ctx, "if", "if $0 {}")); 74 }
65 acc.add(keyword(ctx, "match", "match $0 {}"));
66 acc.add(keyword(ctx, "while", "while $0 {}"));
67 acc.add(keyword(ctx, "loop", "loop {$0}"));
68 75
76 if ctx.block_expr_parent || ctx.is_match_arm {
77 add_keyword(ctx, acc, "match", "match $0 {}");
78 add_keyword(ctx, acc, "loop", "loop {$0}");
79 }
80 if ctx.block_expr_parent {
81 add_keyword(ctx, acc, "while", "while $0 {}");
82 }
83 if ctx.if_is_prev || ctx.block_expr_parent {
84 add_keyword(ctx, acc, "let", "let ");
85 }
86 if ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm {
87 add_keyword(ctx, acc, "if", "if ");
88 add_keyword(ctx, acc, "if let", "if let ");
89 }
69 if ctx.after_if { 90 if ctx.after_if {
70 acc.add(keyword(ctx, "else", "else {$0}")); 91 add_keyword(ctx, acc, "else", "else {$0}");
71 acc.add(keyword(ctx, "else if", "else if $0 {}")); 92 add_keyword(ctx, acc, "else if", "else if $0 {}");
93 }
94 if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent)
95 || ctx.block_expr_parent
96 {
97 add_keyword(ctx, acc, "mod", "mod $0 {}");
98 }
99 if ctx.bind_pat_parent || ctx.ref_pat_parent {
100 add_keyword(ctx, acc, "mut", "mut ");
72 } 101 }
73 if is_in_loop_body(&ctx.token) { 102 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
103 add_keyword(ctx, acc, "const", "const ");
104 add_keyword(ctx, acc, "type", "type ");
105 }
106 if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent)
107 || ctx.block_expr_parent
108 {
109 add_keyword(ctx, acc, "static", "static ");
110 };
111 if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent)
112 || ctx.block_expr_parent
113 {
114 add_keyword(ctx, acc, "extern", "extern ");
115 }
116 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent || ctx.is_match_arm {
117 add_keyword(ctx, acc, "unsafe", "unsafe ");
118 }
119 if ctx.in_loop_body {
74 if ctx.can_be_stmt { 120 if ctx.can_be_stmt {
75 acc.add(keyword(ctx, "continue", "continue;")); 121 add_keyword(ctx, acc, "continue", "continue;");
76 acc.add(keyword(ctx, "break", "break;")); 122 add_keyword(ctx, acc, "break", "break;");
77 } else { 123 } else {
78 acc.add(keyword(ctx, "continue", "continue")); 124 add_keyword(ctx, acc, "continue", "continue");
79 acc.add(keyword(ctx, "break", "break")); 125 add_keyword(ctx, acc, "break", "break");
80 } 126 }
81 } 127 }
128 if ctx.has_item_list_or_source_file_parent && !ctx.has_trait_parent {
129 add_keyword(ctx, acc, "pub", "pub ")
130 }
131
132 if !ctx.is_trivial_path {
133 return;
134 }
135 let fn_def = match &ctx.function_syntax {
136 Some(it) => it,
137 None => return,
138 };
82 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); 139 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
83} 140}
84 141
85fn is_in_loop_body(leaf: &SyntaxToken) -> bool { 142fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
86 // FIXME move this to CompletionContext and make it handle macros 143 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
87 for node in leaf.parent().ancestors() { 144 .kind(CompletionItemKind::Keyword);
88 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { 145
89 break; 146 match ctx.config.snippet_cap {
90 } 147 Some(cap) => res.insert_snippet(cap, snippet),
91 let loop_body = match_ast! { 148 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
92 match node {
93 ast::ForExpr(it) => it.loop_body(),
94 ast::WhileExpr(it) => it.loop_body(),
95 ast::LoopExpr(it) => it.loop_body(),
96 _ => None,
97 }
98 };
99 if let Some(body) = loop_body {
100 if body.syntax().text_range().contains_range(leaf.text_range()) {
101 return true;
102 }
103 }
104 } 149 }
105 false 150 .build()
151}
152
153fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
154 acc.add(keyword(ctx, kw, snippet));
106} 155}
107 156
108fn complete_return( 157fn complete_return(
@@ -121,327 +170,156 @@ fn complete_return(
121 170
122#[cfg(test)] 171#[cfg(test)]
123mod tests { 172mod tests {
124 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 173 use crate::completion::{test_utils::completion_list, CompletionKind};
125 use insta::assert_debug_snapshot; 174 use insta::assert_snapshot;
126 175
127 fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { 176 fn get_keyword_completions(code: &str) -> String {
128 do_completion(code, CompletionKind::Keyword) 177 completion_list(code, CompletionKind::Keyword)
129 } 178 }
130 179
131 #[test] 180 #[test]
132 fn completes_keywords_in_use_stmt() { 181 fn test_keywords_in_use_stmt() {
133 assert_debug_snapshot!( 182 assert_snapshot!(
134 do_keyword_completion( 183 get_keyword_completions(r"use <|>"),
135 r"
136 use <|>
137 ",
138 ),
139 @r###" 184 @r###"
140 [ 185 kw crate
141 CompletionItem { 186 kw self
142 label: "crate", 187 kw super
143 source_range: 21..21,
144 delete: 21..21,
145 insert: "crate::",
146 kind: Keyword,
147 },
148 CompletionItem {
149 label: "self",
150 source_range: 21..21,
151 delete: 21..21,
152 insert: "self",
153 kind: Keyword,
154 },
155 CompletionItem {
156 label: "super",
157 source_range: 21..21,
158 delete: 21..21,
159 insert: "super::",
160 kind: Keyword,
161 },
162 ]
163 "### 188 "###
164 ); 189 );
165 190
166 assert_debug_snapshot!( 191 assert_snapshot!(
167 do_keyword_completion( 192 get_keyword_completions(r"use a::<|>"),
168 r"
169 use a::<|>
170 ",
171 ),
172 @r###" 193 @r###"
173 [ 194 kw self
174 CompletionItem { 195 kw super
175 label: "self",
176 source_range: 24..24,
177 delete: 24..24,
178 insert: "self",
179 kind: Keyword,
180 },
181 CompletionItem {
182 label: "super",
183 source_range: 24..24,
184 delete: 24..24,
185 insert: "super::",
186 kind: Keyword,
187 },
188 ]
189 "### 196 "###
190 ); 197 );
191 198
192 assert_debug_snapshot!( 199 assert_snapshot!(
193 do_keyword_completion( 200 get_keyword_completions(r"use a::{b, <|>}"),
194 r"
195 use a::{b, <|>}
196 ",
197 ),
198 @r###" 201 @r###"
199 [ 202 kw self
200 CompletionItem { 203 kw super
201 label: "self",
202 source_range: 28..28,
203 delete: 28..28,
204 insert: "self",
205 kind: Keyword,
206 },
207 CompletionItem {
208 label: "super",
209 source_range: 28..28,
210 delete: 28..28,
211 insert: "super::",
212 kind: Keyword,
213 },
214 ]
215 "### 204 "###
216 ); 205 );
217 } 206 }
218 207
219 #[test] 208 #[test]
220 fn completes_various_keywords_in_function() { 209 fn test_keywords_at_source_file_level() {
221 assert_debug_snapshot!( 210 assert_snapshot!(
222 do_keyword_completion( 211 get_keyword_completions(r"m<|>"),
223 r"
224 fn quux() {
225 <|>
226 }
227 ",
228 ),
229 @r###" 212 @r###"
230 [ 213 kw const
231 CompletionItem { 214 kw enum
232 label: "if", 215 kw extern
233 source_range: 49..49, 216 kw fn
234 delete: 49..49, 217 kw impl
235 insert: "if $0 {}", 218 kw mod
236 kind: Keyword, 219 kw pub
237 }, 220 kw static
238 CompletionItem { 221 kw struct
239 label: "loop", 222 kw trait
240 source_range: 49..49, 223 kw type
241 delete: 49..49, 224 kw union
242 insert: "loop {$0}", 225 kw unsafe
243 kind: Keyword, 226 kw use
244 },
245 CompletionItem {
246 label: "match",
247 source_range: 49..49,
248 delete: 49..49,
249 insert: "match $0 {}",
250 kind: Keyword,
251 },
252 CompletionItem {
253 label: "return",
254 source_range: 49..49,
255 delete: 49..49,
256 insert: "return;",
257 kind: Keyword,
258 },
259 CompletionItem {
260 label: "while",
261 source_range: 49..49,
262 delete: 49..49,
263 insert: "while $0 {}",
264 kind: Keyword,
265 },
266 ]
267 "### 227 "###
268 ); 228 );
269 } 229 }
270 230
271 #[test] 231 #[test]
272 fn completes_else_after_if() { 232 fn test_keywords_in_function() {
273 assert_debug_snapshot!( 233 assert_snapshot!(
274 do_keyword_completion( 234 get_keyword_completions(r"fn quux() { <|> }"),
275 r"
276 fn quux() {
277 if true {
278 ()
279 } <|>
280 }
281 ",
282 ),
283 @r###" 235 @r###"
284 [ 236 kw const
285 CompletionItem { 237 kw extern
286 label: "else", 238 kw fn
287 source_range: 108..108, 239 kw if
288 delete: 108..108, 240 kw if let
289 insert: "else {$0}", 241 kw impl
290 kind: Keyword, 242 kw let
291 }, 243 kw loop
292 CompletionItem { 244 kw match
293 label: "else if", 245 kw mod
294 source_range: 108..108, 246 kw return
295 delete: 108..108, 247 kw static
296 insert: "else if $0 {}", 248 kw trait
297 kind: Keyword, 249 kw type
298 }, 250 kw unsafe
299 CompletionItem { 251 kw use
300 label: "if", 252 kw while
301 source_range: 108..108,
302 delete: 108..108,
303 insert: "if $0 {}",
304 kind: Keyword,
305 },
306 CompletionItem {
307 label: "loop",
308 source_range: 108..108,
309 delete: 108..108,
310 insert: "loop {$0}",
311 kind: Keyword,
312 },
313 CompletionItem {
314 label: "match",
315 source_range: 108..108,
316 delete: 108..108,
317 insert: "match $0 {}",
318 kind: Keyword,
319 },
320 CompletionItem {
321 label: "return",
322 source_range: 108..108,
323 delete: 108..108,
324 insert: "return;",
325 kind: Keyword,
326 },
327 CompletionItem {
328 label: "while",
329 source_range: 108..108,
330 delete: 108..108,
331 insert: "while $0 {}",
332 kind: Keyword,
333 },
334 ]
335 "### 253 "###
336 ); 254 );
337 } 255 }
338 256
339 #[test] 257 #[test]
340 fn test_completion_return_value() { 258 fn test_keywords_inside_block() {
341 assert_debug_snapshot!( 259 assert_snapshot!(
342 do_keyword_completion( 260 get_keyword_completions(r"fn quux() { if true { <|> } }"),
343 r"
344 fn quux() -> i32 {
345 <|>
346 92
347 }
348 ",
349 ),
350 @r###" 261 @r###"
351 [ 262 kw const
352 CompletionItem { 263 kw extern
353 label: "if", 264 kw fn
354 source_range: 56..56, 265 kw if
355 delete: 56..56, 266 kw if let
356 insert: "if $0 {}", 267 kw impl
357 kind: Keyword, 268 kw let
358 }, 269 kw loop
359 CompletionItem { 270 kw match
360 label: "loop", 271 kw mod
361 source_range: 56..56, 272 kw return
362 delete: 56..56, 273 kw static
363 insert: "loop {$0}", 274 kw trait
364 kind: Keyword, 275 kw type
365 }, 276 kw unsafe
366 CompletionItem { 277 kw use
367 label: "match", 278 kw while
368 source_range: 56..56,
369 delete: 56..56,
370 insert: "match $0 {}",
371 kind: Keyword,
372 },
373 CompletionItem {
374 label: "return",
375 source_range: 56..56,
376 delete: 56..56,
377 insert: "return $0;",
378 kind: Keyword,
379 },
380 CompletionItem {
381 label: "while",
382 source_range: 56..56,
383 delete: 56..56,
384 insert: "while $0 {}",
385 kind: Keyword,
386 },
387 ]
388 "### 279 "###
389 ); 280 );
390 assert_debug_snapshot!( 281 }
391 do_keyword_completion( 282
283 #[test]
284 fn test_keywords_after_if() {
285 assert_snapshot!(
286 get_keyword_completions(
392 r" 287 r"
393 fn quux() { 288 fn quux() {
394 <|> 289 if true {
395 92 290 ()
291 } <|>
396 } 292 }
397 ", 293 ",
398 ), 294 ),
399 @r###" 295 @r###"
400 [ 296 kw const
401 CompletionItem { 297 kw else
402 label: "if", 298 kw else if
403 source_range: 49..49, 299 kw extern
404 delete: 49..49, 300 kw fn
405 insert: "if $0 {}", 301 kw if
406 kind: Keyword, 302 kw if let
407 }, 303 kw impl
408 CompletionItem { 304 kw let
409 label: "loop", 305 kw loop
410 source_range: 49..49, 306 kw match
411 delete: 49..49, 307 kw mod
412 insert: "loop {$0}", 308 kw return
413 kind: Keyword, 309 kw static
414 }, 310 kw trait
415 CompletionItem { 311 kw type
416 label: "match", 312 kw unsafe
417 source_range: 49..49, 313 kw use
418 delete: 49..49, 314 kw while
419 insert: "match $0 {}",
420 kind: Keyword,
421 },
422 CompletionItem {
423 label: "return",
424 source_range: 49..49,
425 delete: 49..49,
426 insert: "return;",
427 kind: Keyword,
428 },
429 CompletionItem {
430 label: "while",
431 source_range: 49..49,
432 delete: 49..49,
433 insert: "while $0 {}",
434 kind: Keyword,
435 },
436 ]
437 "### 315 "###
438 ); 316 );
439 } 317 }
440 318
441 #[test] 319 #[test]
442 fn dont_add_semi_after_return_if_not_a_statement() { 320 fn test_keywords_in_match_arm() {
443 assert_debug_snapshot!( 321 assert_snapshot!(
444 do_keyword_completion( 322 get_keyword_completions(
445 r" 323 r"
446 fn quux() -> i32 { 324 fn quux() -> i32 {
447 match () { 325 match () {
@@ -451,336 +329,130 @@ mod tests {
451 ", 329 ",
452 ), 330 ),
453 @r###" 331 @r###"
454 [ 332 kw if
455 CompletionItem { 333 kw if let
456 label: "if", 334 kw loop
457 source_range: 97..97, 335 kw match
458 delete: 97..97, 336 kw return
459 insert: "if $0 {}", 337 kw unsafe
460 kind: Keyword,
461 },
462 CompletionItem {
463 label: "loop",
464 source_range: 97..97,
465 delete: 97..97,
466 insert: "loop {$0}",
467 kind: Keyword,
468 },
469 CompletionItem {
470 label: "match",
471 source_range: 97..97,
472 delete: 97..97,
473 insert: "match $0 {}",
474 kind: Keyword,
475 },
476 CompletionItem {
477 label: "return",
478 source_range: 97..97,
479 delete: 97..97,
480 insert: "return $0",
481 kind: Keyword,
482 },
483 CompletionItem {
484 label: "while",
485 source_range: 97..97,
486 delete: 97..97,
487 insert: "while $0 {}",
488 kind: Keyword,
489 },
490 ]
491 "### 338 "###
492 ); 339 );
493 } 340 }
494 341
495 #[test] 342 #[test]
496 fn last_return_in_block_has_semi() { 343 fn test_keywords_in_trait_def() {
497 assert_debug_snapshot!( 344 assert_snapshot!(
498 do_keyword_completion( 345 get_keyword_completions(r"trait My { <|> }"),
499 r"
500 fn quux() -> i32 {
501 if condition {
502 <|>
503 }
504 }
505 ",
506 ),
507 @r###" 346 @r###"
508 [ 347 kw const
509 CompletionItem { 348 kw fn
510 label: "if", 349 kw type
511 source_range: 95..95, 350 kw unsafe
512 delete: 95..95,
513 insert: "if $0 {}",
514 kind: Keyword,
515 },
516 CompletionItem {
517 label: "loop",
518 source_range: 95..95,
519 delete: 95..95,
520 insert: "loop {$0}",
521 kind: Keyword,
522 },
523 CompletionItem {
524 label: "match",
525 source_range: 95..95,
526 delete: 95..95,
527 insert: "match $0 {}",
528 kind: Keyword,
529 },
530 CompletionItem {
531 label: "return",
532 source_range: 95..95,
533 delete: 95..95,
534 insert: "return $0;",
535 kind: Keyword,
536 },
537 CompletionItem {
538 label: "while",
539 source_range: 95..95,
540 delete: 95..95,
541 insert: "while $0 {}",
542 kind: Keyword,
543 },
544 ]
545 "### 351 "###
546 ); 352 );
547 assert_debug_snapshot!( 353 }
548 do_keyword_completion( 354
549 r" 355 #[test]
550 fn quux() -> i32 { 356 fn test_keywords_in_impl_def() {
551 if condition { 357 assert_snapshot!(
552 <|> 358 get_keyword_completions(r"impl My { <|> }"),
553 }
554 let x = 92;
555 x
556 }
557 ",
558 ),
559 @r###" 359 @r###"
560 [ 360 kw const
561 CompletionItem { 361 kw fn
562 label: "if", 362 kw pub
563 source_range: 95..95, 363 kw type
564 delete: 95..95, 364 kw unsafe
565 insert: "if $0 {}",
566 kind: Keyword,
567 },
568 CompletionItem {
569 label: "loop",
570 source_range: 95..95,
571 delete: 95..95,
572 insert: "loop {$0}",
573 kind: Keyword,
574 },
575 CompletionItem {
576 label: "match",
577 source_range: 95..95,
578 delete: 95..95,
579 insert: "match $0 {}",
580 kind: Keyword,
581 },
582 CompletionItem {
583 label: "return",
584 source_range: 95..95,
585 delete: 95..95,
586 insert: "return $0;",
587 kind: Keyword,
588 },
589 CompletionItem {
590 label: "while",
591 source_range: 95..95,
592 delete: 95..95,
593 insert: "while $0 {}",
594 kind: Keyword,
595 },
596 ]
597 "### 365 "###
598 ); 366 );
599 } 367 }
600 368
601 #[test] 369 #[test]
602 fn completes_break_and_continue_in_loops() { 370 fn test_keywords_in_loop() {
603 assert_debug_snapshot!( 371 assert_snapshot!(
604 do_keyword_completion( 372 get_keyword_completions(r"fn my() { loop { <|> } }"),
605 r"
606 fn quux() -> i32 {
607 loop { <|> }
608 }
609 ",
610 ),
611 @r###" 373 @r###"
612 [ 374 kw break
613 CompletionItem { 375 kw const
614 label: "break", 376 kw continue
615 source_range: 63..63, 377 kw extern
616 delete: 63..63, 378 kw fn
617 insert: "break;", 379 kw if
618 kind: Keyword, 380 kw if let
619 }, 381 kw impl
620 CompletionItem { 382 kw let
621 label: "continue", 383 kw loop
622 source_range: 63..63, 384 kw match
623 delete: 63..63, 385 kw mod
624 insert: "continue;", 386 kw return
625 kind: Keyword, 387 kw static
626 }, 388 kw trait
627 CompletionItem { 389 kw type
628 label: "if", 390 kw unsafe
629 source_range: 63..63, 391 kw use
630 delete: 63..63, 392 kw while
631 insert: "if $0 {}",
632 kind: Keyword,
633 },
634 CompletionItem {
635 label: "loop",
636 source_range: 63..63,
637 delete: 63..63,
638 insert: "loop {$0}",
639 kind: Keyword,
640 },
641 CompletionItem {
642 label: "match",
643 source_range: 63..63,
644 delete: 63..63,
645 insert: "match $0 {}",
646 kind: Keyword,
647 },
648 CompletionItem {
649 label: "return",
650 source_range: 63..63,
651 delete: 63..63,
652 insert: "return $0;",
653 kind: Keyword,
654 },
655 CompletionItem {
656 label: "while",
657 source_range: 63..63,
658 delete: 63..63,
659 insert: "while $0 {}",
660 kind: Keyword,
661 },
662 ]
663 "### 393 "###
664 ); 394 );
395 }
665 396
666 // No completion: lambda isolates control flow 397 #[test]
667 assert_debug_snapshot!( 398 fn test_keywords_after_unsafe_in_item_list() {
668 do_keyword_completion( 399 assert_snapshot!(
669 r" 400 get_keyword_completions(r"unsafe <|>"),
670 fn quux() -> i32 {
671 loop { || { <|> } }
672 }
673 ",
674 ),
675 @r###" 401 @r###"
676 [ 402 kw fn
677 CompletionItem { 403 kw impl
678 label: "if", 404 kw trait
679 source_range: 68..68,
680 delete: 68..68,
681 insert: "if $0 {}",
682 kind: Keyword,
683 },
684 CompletionItem {
685 label: "loop",
686 source_range: 68..68,
687 delete: 68..68,
688 insert: "loop {$0}",
689 kind: Keyword,
690 },
691 CompletionItem {
692 label: "match",
693 source_range: 68..68,
694 delete: 68..68,
695 insert: "match $0 {}",
696 kind: Keyword,
697 },
698 CompletionItem {
699 label: "return",
700 source_range: 68..68,
701 delete: 68..68,
702 insert: "return $0;",
703 kind: Keyword,
704 },
705 CompletionItem {
706 label: "while",
707 source_range: 68..68,
708 delete: 68..68,
709 insert: "while $0 {}",
710 kind: Keyword,
711 },
712 ]
713 "### 405 "###
714 ); 406 );
715 } 407 }
716 408
717 #[test] 409 #[test]
718 fn no_semi_after_break_continue_in_expr() { 410 fn test_keywords_after_unsafe_in_block_expr() {
719 assert_debug_snapshot!( 411 assert_snapshot!(
720 do_keyword_completion( 412 get_keyword_completions(r"fn my_fn() { unsafe <|> }"),
721 r"
722 fn f() {
723 loop {
724 match () {
725 () => br<|>
726 }
727 }
728 }
729 ",
730 ),
731 @r###" 413 @r###"
732 [ 414 kw fn
733 CompletionItem { 415 kw impl
734 label: "break", 416 kw trait
735 source_range: 122..124,
736 delete: 122..124,
737 insert: "break",
738 kind: Keyword,
739 },
740 CompletionItem {
741 label: "continue",
742 source_range: 122..124,
743 delete: 122..124,
744 insert: "continue",
745 kind: Keyword,
746 },
747 CompletionItem {
748 label: "if",
749 source_range: 122..124,
750 delete: 122..124,
751 insert: "if $0 {}",
752 kind: Keyword,
753 },
754 CompletionItem {
755 label: "loop",
756 source_range: 122..124,
757 delete: 122..124,
758 insert: "loop {$0}",
759 kind: Keyword,
760 },
761 CompletionItem {
762 label: "match",
763 source_range: 122..124,
764 delete: 122..124,
765 insert: "match $0 {}",
766 kind: Keyword,
767 },
768 CompletionItem {
769 label: "return",
770 source_range: 122..124,
771 delete: 122..124,
772 insert: "return",
773 kind: Keyword,
774 },
775 CompletionItem {
776 label: "while",
777 source_range: 122..124,
778 delete: 122..124,
779 insert: "while $0 {}",
780 kind: Keyword,
781 },
782 ]
783 "### 417 "###
784 ) 418 );
419 }
420
421 #[test]
422 fn test_mut_in_ref_and_in_fn_parameters_list() {
423 assert_snapshot!(
424 get_keyword_completions(r"fn my_fn(&<|>) {}"),
425 @r###"
426 kw mut
427 "###
428 );
429 assert_snapshot!(
430 get_keyword_completions(r"fn my_fn(<|>) {}"),
431 @r###"
432 kw mut
433 "###
434 );
435 assert_snapshot!(
436 get_keyword_completions(r"fn my_fn() { let &<|> }"),
437 @r###"
438 kw mut
439 "###
440 );
441 }
442
443 #[test]
444 fn test_where_keyword() {
445 assert_snapshot!(
446 get_keyword_completions(r"trait A <|>"),
447 @r###"
448 kw where
449 "###
450 );
451 assert_snapshot!(
452 get_keyword_completions(r"impl A <|>"),
453 @r###"
454 kw where
455 "###
456 );
785 } 457 }
786} 458}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index c4646b727..9f4c582d0 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -5,12 +5,17 @@ use ra_db::SourceDatabase;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{find_covering_element, find_node_at_offset}, 7 algo::{find_covering_element, find_node_at_offset},
8 ast, match_ast, AstNode, 8 ast, match_ast, AstNode, NodeOrToken,
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextSize, 10 SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 11};
12use ra_text_edit::Indel; 12use ra_text_edit::Indel;
13 13
14use super::patterns::{
15 has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent,
16 has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
17 has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev,
18};
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 19use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15use test_utils::mark; 20use test_utils::mark;
16 21
@@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> {
60 pub(super) is_path_type: bool, 65 pub(super) is_path_type: bool,
61 pub(super) has_type_args: bool, 66 pub(super) has_type_args: bool,
62 pub(super) attribute_under_caret: Option<ast::Attr>, 67 pub(super) attribute_under_caret: Option<ast::Attr>,
68 pub(super) unsafe_is_prev: bool,
69 pub(super) if_is_prev: bool,
70 pub(super) block_expr_parent: bool,
71 pub(super) bind_pat_parent: bool,
72 pub(super) ref_pat_parent: bool,
73 pub(super) in_loop_body: bool,
74 pub(super) has_trait_parent: bool,
75 pub(super) has_impl_parent: bool,
76 pub(super) trait_as_prev_sibling: bool,
77 pub(super) impl_as_prev_sibling: bool,
78 pub(super) is_match_arm: bool,
79 pub(super) has_item_list_or_source_file_parent: bool,
63} 80}
64 81
65impl<'a> CompletionContext<'a> { 82impl<'a> CompletionContext<'a> {
@@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> {
118 has_type_args: false, 135 has_type_args: false,
119 dot_receiver_is_ambiguous_float_literal: false, 136 dot_receiver_is_ambiguous_float_literal: false,
120 attribute_under_caret: None, 137 attribute_under_caret: None,
138 unsafe_is_prev: false,
139 in_loop_body: false,
140 ref_pat_parent: false,
141 bind_pat_parent: false,
142 block_expr_parent: false,
143 has_trait_parent: false,
144 has_impl_parent: false,
145 trait_as_prev_sibling: false,
146 impl_as_prev_sibling: false,
147 if_is_prev: false,
148 is_match_arm: false,
149 has_item_list_or_source_file_parent: false,
121 }; 150 };
122 151
123 let mut original_file = original_file.syntax().clone(); 152 let mut original_file = original_file.syntax().clone();
@@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> {
159 break; 188 break;
160 } 189 }
161 } 190 }
162 191 ctx.fill_keyword_patterns(&hypothetical_file, offset);
163 ctx.fill(&original_file, hypothetical_file, offset); 192 ctx.fill(&original_file, hypothetical_file, offset);
164 Some(ctx) 193 Some(ctx)
165 } 194 }
@@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> {
188 self.sema.scope_at_offset(&self.token.parent(), self.offset) 217 self.sema.scope_at_offset(&self.token.parent(), self.offset)
189 } 218 }
190 219
220 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
221 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
222 let syntax_element = NodeOrToken::Token(fake_ident_token.clone());
223 self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
224 self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
225 self.if_is_prev = if_is_prev(syntax_element.clone());
226 self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
227 self.ref_pat_parent = has_ref_parent(syntax_element.clone());
228 self.in_loop_body = is_in_loop_body(syntax_element.clone());
229 self.has_trait_parent = has_trait_parent(syntax_element.clone());
230 self.has_impl_parent = has_impl_parent(syntax_element.clone());
231 self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
232 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
233 self.is_match_arm = is_match_arm(syntax_element.clone());
234 self.has_item_list_or_source_file_parent =
235 has_item_list_or_source_file_parent(syntax_element.clone());
236 }
237
191 fn fill( 238 fn fill(
192 &mut self, 239 &mut self,
193 original_file: &SyntaxNode, 240 original_file: &SyntaxNode,
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index cfb7c1e38..98348b349 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -125,6 +125,32 @@ pub enum CompletionItemKind {
125 Attribute, 125 Attribute,
126} 126}
127 127
128impl CompletionItemKind {
129 #[cfg(test)]
130 pub(crate) fn tag(&self) -> &'static str {
131 match self {
132 CompletionItemKind::Snippet => "sn",
133 CompletionItemKind::Keyword => "kw",
134 CompletionItemKind::Module => "md",
135 CompletionItemKind::Function => "fn",
136 CompletionItemKind::BuiltinType => "bt",
137 CompletionItemKind::Struct => "st",
138 CompletionItemKind::Enum => "en",
139 CompletionItemKind::EnumVariant => "ev",
140 CompletionItemKind::Binding => "bn",
141 CompletionItemKind::Field => "fd",
142 CompletionItemKind::Static => "sc",
143 CompletionItemKind::Const => "ct",
144 CompletionItemKind::Trait => "tt",
145 CompletionItemKind::TypeAlias => "ta",
146 CompletionItemKind::Method => "me",
147 CompletionItemKind::TypeParam => "tp",
148 CompletionItemKind::Macro => "ma",
149 CompletionItemKind::Attribute => "at",
150 }
151 }
152}
153
128#[derive(Debug, PartialEq, Eq, Copy, Clone)] 154#[derive(Debug, PartialEq, Eq, Copy, Clone)]
129pub(crate) enum CompletionKind { 155pub(crate) enum CompletionKind {
130 /// Parser-based keyword completion. 156 /// Parser-based keyword completion.
diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs
new file mode 100644
index 000000000..b2fe13280
--- /dev/null
+++ b/crates/ra_ide/src/completion/patterns.rs
@@ -0,0 +1,194 @@
1//! Patterns telling us certain facts about current syntax element, they are used in completion context
2
3use ra_syntax::{
4 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken,
9};
10
11#[cfg(test)]
12use crate::completion::test_utils::check_pattern_is_applicable;
13
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
15 not_same_range_ancestor(element)
16 .filter(|it| it.kind() == ITEM_LIST)
17 .and_then(|it| it.parent())
18 .filter(|it| it.kind() == TRAIT_DEF)
19 .is_some()
20}
21#[test]
22fn test_has_trait_parent() {
23 check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent);
24}
25
26pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
27 not_same_range_ancestor(element)
28 .filter(|it| it.kind() == ITEM_LIST)
29 .and_then(|it| it.parent())
30 .filter(|it| it.kind() == IMPL_DEF)
31 .is_some()
32}
33#[test]
34fn test_has_impl_parent() {
35 check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
36}
37
38pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
39 not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
40}
41#[test]
42fn test_has_block_expr_parent() {
43 check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent);
44}
45
46pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
47 element.ancestors().find(|it| it.kind() == BIND_PAT).is_some()
48}
49#[test]
50fn test_has_bind_pat_parent() {
51 check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent);
52 check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent);
53}
54
55pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
56 not_same_range_ancestor(element)
57 .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
58 .is_some()
59}
60#[test]
61fn test_has_ref_parent() {
62 check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent);
63 check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent);
64}
65
66pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
67 let ancestor = not_same_range_ancestor(element);
68 if !ancestor.is_some() {
69 return true;
70 }
71 ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some()
72}
73#[test]
74fn test_has_item_list_or_source_file_parent() {
75 check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent);
76 check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent);
77}
78
79pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
80 not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
81 && previous_sibling_or_ancestor_sibling(element)
82 .and_then(|it| it.into_token())
83 .filter(|it| it.kind() == FAT_ARROW)
84 .is_some()
85}
86#[test]
87fn test_is_match_arm() {
88 check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm);
89}
90
91pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
92 element
93 .into_token()
94 .and_then(|it| previous_non_trivia_token(it))
95 .filter(|it| it.kind() == UNSAFE_KW)
96 .is_some()
97}
98#[test]
99fn test_unsafe_is_prev() {
100 check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev);
101}
102
103pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
104 element
105 .into_token()
106 .and_then(|it| previous_non_trivia_token(it))
107 .filter(|it| it.kind() == IF_KW)
108 .is_some()
109}
110#[test]
111fn test_if_is_prev() {
112 check_pattern_is_applicable(r"if l<|>", if_is_prev);
113}
114
115pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
116 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some()
117}
118#[test]
119fn test_has_trait_as_prev_sibling() {
120 check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling);
121}
122
123pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool {
124 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some()
125}
126#[test]
127fn test_has_impl_as_prev_sibling() {
128 check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling);
129}
130
131pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
132 let leaf = match element {
133 NodeOrToken::Node(node) => node,
134 NodeOrToken::Token(token) => token.parent(),
135 };
136 for node in leaf.ancestors() {
137 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
138 break;
139 }
140 let loop_body = match_ast! {
141 match node {
142 ast::ForExpr(it) => it.loop_body(),
143 ast::WhileExpr(it) => it.loop_body(),
144 ast::LoopExpr(it) => it.loop_body(),
145 _ => None,
146 }
147 };
148 if let Some(body) = loop_body {
149 if body.syntax().text_range().contains_range(leaf.text_range()) {
150 return true;
151 }
152 }
153 }
154 false
155}
156
157fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
158 element
159 .ancestors()
160 .take_while(|it| it.text_range() == element.text_range())
161 .last()
162 .and_then(|it| it.parent())
163}
164
165fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
166 let mut token = token.prev_token();
167 while let Some(inner) = token.clone() {
168 if !inner.kind().is_trivia() {
169 return Some(inner);
170 } else {
171 token = inner.prev_token();
172 }
173 }
174 None
175}
176
177fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> {
178 let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev);
179 if let Some(sibling) = token_sibling {
180 Some(sibling)
181 } else {
182 // if not trying to find first ancestor which has such a sibling
183 let node = match element {
184 NodeOrToken::Node(node) => node,
185 NodeOrToken::Token(token) => token.parent(),
186 };
187 let range = node.text_range();
188 let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?;
189 let prev_sibling_node = top_node.ancestors().find(|it| {
190 non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some()
191 })?;
192 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
193 }
194}
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index bf22452a2..1e16a43ca 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -5,25 +5,63 @@ use crate::{
5 mock_analysis::{analysis_and_position, single_file_with_position}, 5 mock_analysis::{analysis_and_position, single_file_with_position},
6 CompletionItem, 6 CompletionItem,
7}; 7};
8use hir::Semantics;
9use ra_syntax::{AstNode, NodeOrToken, SyntaxElement};
8 10
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 11pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
10 do_completion_with_options(code, kind, &CompletionConfig::default()) 12 do_completion_with_options(code, kind, &CompletionConfig::default())
11} 13}
12 14
15pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
16 completion_list_with_options(code, kind, &CompletionConfig::default())
17}
18
13pub(crate) fn do_completion_with_options( 19pub(crate) fn do_completion_with_options(
14 code: &str, 20 code: &str,
15 kind: CompletionKind, 21 kind: CompletionKind,
16 options: &CompletionConfig, 22 options: &CompletionConfig,
17) -> Vec<CompletionItem> { 23) -> Vec<CompletionItem> {
24 let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
25 .into_iter()
26 .filter(|c| c.completion_kind == kind)
27 .collect();
28 kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
29 kind_completions
30}
31
32fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> {
18 let (analysis, position) = if code.contains("//-") { 33 let (analysis, position) = if code.contains("//-") {
19 analysis_and_position(code) 34 analysis_and_position(code)
20 } else { 35 } else {
21 single_file_with_position(code) 36 single_file_with_position(code)
22 }; 37 };
23 let completions = analysis.completions(options, position).unwrap().unwrap(); 38 analysis.completions(options, position).unwrap().unwrap().into()
24 let completion_items: Vec<CompletionItem> = completions.into(); 39}
25 let mut kind_completions: Vec<CompletionItem> = 40
26 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); 41pub(crate) fn completion_list_with_options(
42 code: &str,
43 kind: CompletionKind,
44 options: &CompletionConfig,
45) -> String {
46 let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
47 .into_iter()
48 .filter(|c| c.completion_kind == kind)
49 .collect();
27 kind_completions.sort_by_key(|c| c.label().to_owned()); 50 kind_completions.sort_by_key(|c| c.label().to_owned());
28 kind_completions 51 kind_completions
52 .into_iter()
53 .map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label()))
54 .collect()
55}
56
57pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
58 let (analysis, pos) = single_file_with_position(code);
59 analysis
60 .with_db(|db| {
61 let sema = Semantics::new(db);
62 let original_file = sema.parse(pos.file_id);
63 let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
64 assert!(check(NodeOrToken::Token(token)));
65 })
66 .unwrap();
29} 67}