diff options
Diffstat (limited to 'crates/ra_analysis/src/completion')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_item.rs | 80 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/reference_completion.rs | 376 |
2 files changed, 444 insertions, 12 deletions
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 8aa9da005..d5d751759 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs | |||
@@ -6,6 +6,8 @@ pub struct CompletionItem { | |||
6 | label: String, | 6 | label: String, |
7 | lookup: Option<String>, | 7 | lookup: Option<String>, |
8 | snippet: Option<String>, | 8 | snippet: Option<String>, |
9 | /// Used only internally in test, to check only specific kind of completion. | ||
10 | kind: CompletionKind, | ||
9 | } | 11 | } |
10 | 12 | ||
11 | pub enum InsertText { | 13 | pub enum InsertText { |
@@ -13,6 +15,18 @@ pub enum InsertText { | |||
13 | Snippet { text: String }, | 15 | Snippet { text: String }, |
14 | } | 16 | } |
15 | 17 | ||
18 | #[derive(Debug, PartialEq, Eq)] | ||
19 | pub(crate) enum CompletionKind { | ||
20 | /// Parser-based keyword completion. | ||
21 | Keyword, | ||
22 | /// Your usual "complete all valid identifiers". | ||
23 | Reference, | ||
24 | /// "Secret sauce" completions. | ||
25 | Magic, | ||
26 | Snippet, | ||
27 | Unspecified, | ||
28 | } | ||
29 | |||
16 | impl CompletionItem { | 30 | impl CompletionItem { |
17 | pub(crate) fn new(label: impl Into<String>) -> Builder { | 31 | pub(crate) fn new(label: impl Into<String>) -> Builder { |
18 | let label = label.into(); | 32 | let label = label.into(); |
@@ -20,6 +34,7 @@ impl CompletionItem { | |||
20 | label, | 34 | label, |
21 | lookup: None, | 35 | lookup: None, |
22 | snippet: None, | 36 | snippet: None, |
37 | kind: CompletionKind::Unspecified, | ||
23 | } | 38 | } |
24 | } | 39 | } |
25 | /// What user sees in pop-up in the UI. | 40 | /// What user sees in pop-up in the UI. |
@@ -50,28 +65,34 @@ pub(crate) struct Builder { | |||
50 | label: String, | 65 | label: String, |
51 | lookup: Option<String>, | 66 | lookup: Option<String>, |
52 | snippet: Option<String>, | 67 | snippet: Option<String>, |
68 | kind: CompletionKind, | ||
53 | } | 69 | } |
54 | 70 | ||
55 | impl Builder { | 71 | impl Builder { |
56 | pub fn add_to(self, acc: &mut Completions) { | 72 | pub(crate) fn add_to(self, acc: &mut Completions) { |
57 | acc.add(self.build()) | 73 | acc.add(self.build()) |
58 | } | 74 | } |
59 | 75 | ||
60 | pub fn build(self) -> CompletionItem { | 76 | pub(crate) fn build(self) -> CompletionItem { |
61 | CompletionItem { | 77 | CompletionItem { |
62 | label: self.label, | 78 | label: self.label, |
63 | lookup: self.lookup, | 79 | lookup: self.lookup, |
64 | snippet: self.snippet, | 80 | snippet: self.snippet, |
81 | kind: self.kind, | ||
65 | } | 82 | } |
66 | } | 83 | } |
67 | pub fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 84 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
68 | self.lookup = Some(lookup.into()); | 85 | self.lookup = Some(lookup.into()); |
69 | self | 86 | self |
70 | } | 87 | } |
71 | pub fn snippet(mut self, snippet: impl Into<String>) -> Builder { | 88 | pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder { |
72 | self.snippet = Some(snippet.into()); | 89 | self.snippet = Some(snippet.into()); |
73 | self | 90 | self |
74 | } | 91 | } |
92 | pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder { | ||
93 | self.kind = kind; | ||
94 | self | ||
95 | } | ||
75 | } | 96 | } |
76 | 97 | ||
77 | impl Into<CompletionItem> for Builder { | 98 | impl Into<CompletionItem> for Builder { |
@@ -97,6 +118,57 @@ impl Completions { | |||
97 | { | 118 | { |
98 | items.into_iter().for_each(|item| self.add(item.into())) | 119 | items.into_iter().for_each(|item| self.add(item.into())) |
99 | } | 120 | } |
121 | |||
122 | #[cfg(test)] | ||
123 | pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { | ||
124 | let expected = normalize(expected); | ||
125 | let actual = self.debug_render(kind); | ||
126 | test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); | ||
127 | |||
128 | /// Normalize the textual representation of `Completions`: | ||
129 | /// replace `;` with newlines, normalize whitespace | ||
130 | fn normalize(expected: &str) -> String { | ||
131 | use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; | ||
132 | let mut res = String::new(); | ||
133 | for line in expected.trim().lines() { | ||
134 | let line = line.trim(); | ||
135 | let mut start_offset: TextUnit = 0.into(); | ||
136 | // Yep, we use rust tokenize in completion tests :-) | ||
137 | for token in tokenize(line) { | ||
138 | let range = TextRange::offset_len(start_offset, token.len); | ||
139 | start_offset += token.len; | ||
140 | if token.kind == SEMI { | ||
141 | res.push('\n'); | ||
142 | } else { | ||
143 | res.push_str(&line[range]); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | res.push('\n'); | ||
148 | } | ||
149 | res | ||
150 | } | ||
151 | } | ||
152 | |||
153 | #[cfg(test)] | ||
154 | fn debug_render(&self, kind: CompletionKind) -> String { | ||
155 | let mut res = String::new(); | ||
156 | for c in self.buf.iter() { | ||
157 | if c.kind == kind { | ||
158 | if let Some(lookup) = &c.lookup { | ||
159 | res.push_str(lookup); | ||
160 | res.push_str(&format!(" {:?}", c.label)); | ||
161 | } else { | ||
162 | res.push_str(&c.label); | ||
163 | } | ||
164 | if let Some(snippet) = &c.snippet { | ||
165 | res.push_str(&format!(" {:?}", snippet)); | ||
166 | } | ||
167 | res.push('\n'); | ||
168 | } | ||
169 | } | ||
170 | res | ||
171 | } | ||
100 | } | 172 | } |
101 | 173 | ||
102 | impl Into<Vec<CompletionItem>> for Completions { | 174 | impl Into<Vec<CompletionItem>> for Completions { |
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index c578e9e8b..c2a650b6d 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs | |||
@@ -13,7 +13,7 @@ use hir::{ | |||
13 | 13 | ||
14 | use crate::{ | 14 | use crate::{ |
15 | db::RootDatabase, | 15 | db::RootDatabase, |
16 | completion::{CompletionItem, Completions}, | 16 | completion::{CompletionItem, Completions, CompletionKind::*}, |
17 | Cancelable | 17 | Cancelable |
18 | }; | 18 | }; |
19 | 19 | ||
@@ -51,7 +51,11 @@ pub(super) fn completions( | |||
51 | } | 51 | } |
52 | } | 52 | } |
53 | }) | 53 | }) |
54 | .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); | 54 | .for_each(|(name, _res)| { |
55 | CompletionItem::new(name.to_string()) | ||
56 | .kind(Reference) | ||
57 | .add_to(acc) | ||
58 | }); | ||
55 | } | 59 | } |
56 | NameRefKind::Path(path) => complete_path(acc, db, module, path)?, | 60 | NameRefKind::Path(path) => complete_path(acc, db, module, path)?, |
57 | NameRefKind::BareIdentInMod => { | 61 | NameRefKind::BareIdentInMod => { |
@@ -123,9 +127,13 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) | |||
123 | .scope_chain(name_ref.syntax()) | 127 | .scope_chain(name_ref.syntax()) |
124 | .flat_map(|scope| scopes.entries(scope).iter()) | 128 | .flat_map(|scope| scopes.entries(scope).iter()) |
125 | .filter(|entry| shadowed.insert(entry.name())) | 129 | .filter(|entry| shadowed.insert(entry.name())) |
126 | .for_each(|entry| CompletionItem::new(entry.name().to_string()).add_to(acc)); | 130 | .for_each(|entry| { |
131 | CompletionItem::new(entry.name().to_string()) | ||
132 | .kind(Reference) | ||
133 | .add_to(acc) | ||
134 | }); | ||
127 | if scopes.self_param.is_some() { | 135 | if scopes.self_param.is_some() { |
128 | CompletionItem::new("self").add_to(acc); | 136 | CompletionItem::new("self").kind(Reference).add_to(acc); |
129 | } | 137 | } |
130 | } | 138 | } |
131 | 139 | ||
@@ -148,9 +156,11 @@ fn complete_path( | |||
148 | _ => return Ok(()), | 156 | _ => return Ok(()), |
149 | }; | 157 | }; |
150 | let module_scope = target_module.scope(db)?; | 158 | let module_scope = target_module.scope(db)?; |
151 | module_scope | 159 | module_scope.entries().for_each(|(name, _res)| { |
152 | .entries() | 160 | CompletionItem::new(name.to_string()) |
153 | .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); | 161 | .kind(Reference) |
162 | .add_to(acc) | ||
163 | }); | ||
154 | Ok(()) | 164 | Ok(()) |
155 | } | 165 | } |
156 | 166 | ||
@@ -164,9 +174,11 @@ fn ${1:feature}() { | |||
164 | $0 | 174 | $0 |
165 | }", | 175 | }", |
166 | ) | 176 | ) |
177 | .kind(Snippet) | ||
167 | .add_to(acc); | 178 | .add_to(acc); |
168 | CompletionItem::new("pub(crate)") | 179 | CompletionItem::new("pub(crate)") |
169 | .snippet("pub(crate) $0") | 180 | .snippet("pub(crate) $0") |
181 | .kind(Snippet) | ||
170 | .add_to(acc); | 182 | .add_to(acc); |
171 | } | 183 | } |
172 | 184 | ||
@@ -249,14 +261,362 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet | |||
249 | } | 261 | } |
250 | 262 | ||
251 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { | 263 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { |
252 | CompletionItem::new(kw).snippet(snippet).build() | 264 | CompletionItem::new(kw) |
265 | .kind(Keyword) | ||
266 | .snippet(snippet) | ||
267 | .build() | ||
253 | } | 268 | } |
254 | 269 | ||
255 | fn complete_expr_snippets(acc: &mut Completions) { | 270 | fn complete_expr_snippets(acc: &mut Completions) { |
256 | CompletionItem::new("pd") | 271 | CompletionItem::new("pd") |
257 | .snippet("eprintln!(\"$0 = {:?}\", $0);") | 272 | .snippet("eprintln!(\"$0 = {:?}\", $0);") |
273 | .kind(Snippet) | ||
258 | .add_to(acc); | 274 | .add_to(acc); |
259 | CompletionItem::new("ppd") | 275 | CompletionItem::new("ppd") |
260 | .snippet("eprintln!(\"$0 = {:#?}\", $0);") | 276 | .snippet("eprintln!(\"$0 = {:#?}\", $0);") |
277 | .kind(Snippet) | ||
261 | .add_to(acc); | 278 | .add_to(acc); |
262 | } | 279 | } |
280 | |||
281 | #[cfg(test)] | ||
282 | mod 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 test_item_snippets() { | ||
603 | // check_snippet_completion(r" | ||
604 | // <|> | ||
605 | // ", | ||
606 | // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, | ||
607 | // ); | ||
608 | check_snippet_completion( | ||
609 | r" | ||
610 | #[cfg(test)] | ||
611 | mod tests { | ||
612 | <|> | ||
613 | } | ||
614 | ", | ||
615 | r##" | ||
616 | tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" | ||
617 | pub(crate) "pub(crate) $0" | ||
618 | "##, | ||
619 | ); | ||
620 | } | ||
621 | |||
622 | } | ||