aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-05-27 17:15:18 +0100
committerLukas Wirth <[email protected]>2021-05-27 17:16:39 +0100
commit3a16950fd919f46fd879c36423810a40105b2c10 (patch)
tree0596de15d333b44681b07d0b69d3b1ef9751bd0c /crates/ide_completion
parentf41c98342476087d0a4387e7d337ce2d897e0346 (diff)
Cleanup `ImmediateLocation` determination
Diffstat (limited to 'crates/ide_completion')
-rw-r--r--crates/ide_completion/src/completions/keyword.rs2
-rw-r--r--crates/ide_completion/src/completions/qualified_path.rs22
-rw-r--r--crates/ide_completion/src/context.rs44
-rw-r--r--crates/ide_completion/src/patterns.rs177
-rw-r--r--crates/ide_completion/src/test_utils.rs2
5 files changed, 132 insertions, 115 deletions
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index 14d6ae54e..96447a603 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -104,7 +104,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
104 if expects_item || has_block_expr_parent { 104 if expects_item || has_block_expr_parent {
105 add_keyword(ctx, acc, "mod", "mod $0"); 105 add_keyword(ctx, acc, "mod", "mod $0");
106 } 106 }
107 if ctx.has_ident_or_ref_pat_parent() { 107 if ctx.expects_ident_pat_or_ref_expr() {
108 add_keyword(ctx, acc, "mut", "mut "); 108 add_keyword(ctx, acc, "mut", "mut ");
109 } 109 }
110 if expects_item || expects_assoc_item || has_block_expr_parent { 110 if expects_item || expects_assoc_item || has_block_expr_parent {
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index a90325e06..c16bb215f 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -20,7 +20,6 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
20 None => return, 20 None => return,
21 }; 21 };
22 let context_module = ctx.scope.module(); 22 let context_module = ctx.scope.module();
23
24 if ctx.expects_item() || ctx.expects_assoc_item() { 23 if ctx.expects_item() || ctx.expects_assoc_item() {
25 if let PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { 24 if let PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
26 let module_scope = module.scope(ctx.db, context_module); 25 let module_scope = module.scope(ctx.db, context_module);
@@ -606,7 +605,7 @@ fn main() { T::$0; }
606macro_rules! foo { () => {} } 605macro_rules! foo { () => {} }
607 606
608fn main() { let _ = crate::$0 } 607fn main() { let _ = crate::$0 }
609 "#, 608"#,
610 expect![[r##" 609 expect![[r##"
611 fn main() fn() 610 fn main() fn()
612 ma foo!(…) #[macro_export] macro_rules! foo 611 ma foo!(…) #[macro_export] macro_rules! foo
@@ -615,6 +614,25 @@ fn main() { let _ = crate::$0 }
615 } 614 }
616 615
617 #[test] 616 #[test]
617 fn completes_qualified_macros_in_impl() {
618 check(
619 r#"
620#[macro_export]
621macro_rules! foo { () => {} }
622
623struct MyStruct {}
624
625impl MyStruct {
626 crate::$0
627}
628"#,
629 expect![[r##"
630 ma foo! #[macro_export] macro_rules! foo
631 "##]],
632 );
633 }
634
635 #[test]
618 fn test_super_super_completion() { 636 fn test_super_super_completion() {
619 check( 637 check(
620 r#" 638 r#"
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index b7e116dba..fbef54408 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -17,9 +17,8 @@ use text_edit::Indel;
17 17
18use crate::{ 18use crate::{
19 patterns::{ 19 patterns::{
20 for_is_prev2, has_bind_pat_parent, has_block_expr_parent, has_field_list_parent, 20 determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block,
21 has_impl_parent, has_item_list_or_source_file_parent, has_prev_sibling, has_ref_parent, 21 is_in_loop_body, is_match_arm, previous_token, ImmediateLocation,
22 has_trait_parent, inside_impl_trait_block, is_in_loop_body, is_match_arm, previous_token,
23 }, 22 },
24 CompletionConfig, 23 CompletionConfig,
25}; 24};
@@ -30,18 +29,6 @@ pub(crate) enum PatternRefutability {
30 Irrefutable, 29 Irrefutable,
31} 30}
32 31
33/// Direct parent container of the cursor position
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub(crate) enum ImmediateLocation {
36 Impl,
37 Trait,
38 RecordFieldList,
39 RefPatOrExpr,
40 IdentPat,
41 BlockExpr,
42 ItemList,
43}
44
45#[derive(Copy, Clone, Debug, PartialEq, Eq)] 32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
46pub(crate) enum PrevSibling { 33pub(crate) enum PrevSibling {
47 Trait, 34 Trait,
@@ -301,15 +288,15 @@ impl<'a> CompletionContext<'a> {
301 matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) 288 matches!(self.completion_location, Some(ImmediateLocation::BlockExpr))
302 } 289 }
303 290
304 pub(crate) fn has_ident_or_ref_pat_parent(&self) -> bool { 291 pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool {
305 matches!( 292 matches!(
306 self.completion_location, 293 self.completion_location,
307 Some(ImmediateLocation::IdentPat) | Some(ImmediateLocation::RefPatOrExpr) 294 Some(ImmediateLocation::IdentPat) | Some(ImmediateLocation::RefExpr)
308 ) 295 )
309 } 296 }
310 297
311 pub(crate) fn expect_record_field(&self) -> bool { 298 pub(crate) fn expect_record_field(&self) -> bool {
312 matches!(self.completion_location, Some(ImmediateLocation::RecordFieldList)) 299 matches!(self.completion_location, Some(ImmediateLocation::RecordField))
313 } 300 }
314 301
315 pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { 302 pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
@@ -324,9 +311,8 @@ impl<'a> CompletionContext<'a> {
324 } 311 }
325 312
326 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { 313 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
327 dbg!(file_with_fake_ident);
328 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); 314 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
329 let syntax_element = NodeOrToken::Token(fake_ident_token); 315 let syntax_element = NodeOrToken::Token(fake_ident_token.clone());
330 self.previous_token = previous_token(syntax_element.clone()); 316 self.previous_token = previous_token(syntax_element.clone());
331 self.in_loop_body = is_in_loop_body(syntax_element.clone()); 317 self.in_loop_body = is_in_loop_body(syntax_element.clone());
332 self.is_match_arm = is_match_arm(syntax_element.clone()); 318 self.is_match_arm = is_match_arm(syntax_element.clone());
@@ -336,22 +322,6 @@ impl<'a> CompletionContext<'a> {
336 self.prev_sibling = Some(PrevSibling::Trait) 322 self.prev_sibling = Some(PrevSibling::Trait)
337 } 323 }
338 324
339 if has_block_expr_parent(syntax_element.clone()) {
340 self.completion_location = Some(ImmediateLocation::BlockExpr);
341 } else if has_bind_pat_parent(syntax_element.clone()) {
342 self.completion_location = Some(ImmediateLocation::IdentPat);
343 } else if has_ref_parent(syntax_element.clone()) {
344 self.completion_location = Some(ImmediateLocation::RefPatOrExpr);
345 } else if has_impl_parent(syntax_element.clone()) {
346 self.completion_location = Some(ImmediateLocation::Impl);
347 } else if has_field_list_parent(syntax_element.clone()) {
348 self.completion_location = Some(ImmediateLocation::RecordFieldList);
349 } else if has_trait_parent(syntax_element.clone()) {
350 self.completion_location = Some(ImmediateLocation::Trait);
351 } else if has_item_list_or_source_file_parent(syntax_element.clone()) {
352 self.completion_location = Some(ImmediateLocation::ItemList);
353 }
354
355 self.mod_declaration_under_caret = 325 self.mod_declaration_under_caret =
356 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) 326 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
357 .filter(|module| module.item_list().is_none()); 327 .filter(|module| module.item_list().is_none());
@@ -364,6 +334,8 @@ impl<'a> CompletionContext<'a> {
364 let fn_is_prev = self.previous_token_is(T![fn]); 334 let fn_is_prev = self.previous_token_is(T![fn]);
365 let for_is_prev2 = for_is_prev2(syntax_element.clone()); 335 let for_is_prev2 = for_is_prev2(syntax_element.clone());
366 self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2; 336 self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2;
337
338 self.completion_location = determine_location(fake_ident_token);
367 } 339 }
368 340
369 fn fill_impl_def(&mut self) { 341 fn fill_impl_def(&mut self) {
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index f7bf4d638..ed289d561 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -11,102 +11,133 @@ use syntax::{
11#[cfg(test)] 11#[cfg(test)]
12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
13 13
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { 14/// Direct parent container of the cursor position
15 not_same_range_ancestor(element) 15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16 .filter(|it| it.kind() == ASSOC_ITEM_LIST) 16pub(crate) enum ImmediateLocation {
17 .and_then(|it| it.parent()) 17 Impl,
18 .filter(|it| it.kind() == TRAIT) 18 Trait,
19 .is_some() 19 RecordField,
20} 20 RefExpr,
21#[test] 21 IdentPat,
22fn test_has_trait_parent() { 22 BlockExpr,
23 check_pattern_is_applicable(r"trait A { f$0 }", has_trait_parent); 23 ItemList,
24}
25
26pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> {
27 // First "expand" the element we are completing to its maximum so that we can check in what
28 // context it immediately lies. This for example means if the token is a NameRef at the end of
29 // a path, we want to look at where the path is in the tree.
30 let node = match tok.parent().and_then(ast::NameLike::cast)? {
31 ast::NameLike::NameRef(name_ref) => {
32 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
33 let p = segment.parent_path();
34 if p.parent_path().is_none() {
35 p.syntax()
36 .ancestors()
37 .take_while(|it| it.text_range() == p.syntax().text_range())
38 .last()?
39 } else {
40 return None;
41 }
42 } else {
43 return None;
44 }
45 }
46 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
47 };
48 let parent = match node.parent() {
49 Some(parent) => parent,
50 // SourceFile
51 None => {
52 return match node.kind() {
53 MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList),
54 _ => None,
55 }
56 }
57 };
58 let res = match_ast! {
59 match parent {
60 ast::IdentPat(_it) => ImmediateLocation::IdentPat,
61 ast::BlockExpr(_it) => ImmediateLocation::BlockExpr,
62 ast::SourceFile(_it) => ImmediateLocation::ItemList,
63 ast::ItemList(_it) => ImmediateLocation::ItemList,
64 ast::RefExpr(_it) => ImmediateLocation::RefExpr,
65 ast::RefPat(_it) => ImmediateLocation::RefExpr,
66 ast::RecordField(_it) => ImmediateLocation::RecordField,
67 ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) {
68 Some(IMPL) => ImmediateLocation::Impl,
69 Some(TRAIT) => ImmediateLocation::Trait,
70 _ => return None,
71 },
72 _ => return None,
73 }
74 };
75 Some(res)
24} 76}
25 77
26pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { 78#[cfg(test)]
27 not_same_range_ancestor(element) 79fn check_location(code: &str, loc: ImmediateLocation) {
28 .filter(|it| it.kind() == ASSOC_ITEM_LIST) 80 check_pattern_is_applicable(code, |e| {
29 .and_then(|it| it.parent()) 81 assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc));
30 .filter(|it| it.kind() == IMPL) 82 true
31 .is_some() 83 });
32}
33#[test]
34fn test_has_impl_parent() {
35 check_pattern_is_applicable(r"impl A { f$0 }", has_impl_parent);
36} 84}
37 85
38pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
39 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
40 // where we only check the first parent with different text range.
41 element
42 .ancestors()
43 .find(|it| it.kind() == IMPL)
44 .map(|it| ast::Impl::cast(it).unwrap())
45 .map(|it| it.trait_().is_some())
46 .unwrap_or(false)
47}
48#[test] 86#[test]
49fn test_inside_impl_trait_block() { 87fn test_has_trait_parent() {
50 check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block); 88 check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
51 check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block);
52 check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block);
53 check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
54} 89}
55 90
56pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { 91#[test]
57 not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() 92fn test_has_impl_parent() {
93 check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
58} 94}
59#[test] 95#[test]
60fn test_has_field_list_parent() { 96fn test_has_field_list_parent() {
61 check_pattern_is_applicable(r"struct Foo { f$0 }", has_field_list_parent); 97 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
62 check_pattern_is_applicable(r"struct Foo { f$0 pub f: i32}", has_field_list_parent); 98 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
63} 99}
64 100
65pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
66 not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
67}
68#[test] 101#[test]
69fn test_has_block_expr_parent() { 102fn test_has_block_expr_parent() {
70 check_pattern_is_applicable(r"fn my_fn() { let a = 2; f$0 }", has_block_expr_parent); 103 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
71} 104}
72 105
73pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { 106#[test]
74 element.ancestors().any(|it| it.kind() == IDENT_PAT) 107fn test_has_ident_pat_parent() {
108 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
109 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
110 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
111 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
75} 112}
76 113
77#[test] 114#[test]
78fn test_has_bind_pat_parent() { 115fn test_has_ref_expr_parent() {
79 check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent); 116 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
80 check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent);
81} 117}
82 118
83pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
84 not_same_range_ancestor(element)
85 .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
86 .is_some()
87}
88#[test] 119#[test]
89fn test_has_ref_parent() { 120fn test_has_item_list_or_source_file_parent() {
90 check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent); 121 check_location(r"i$0", ImmediateLocation::ItemList);
91 check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent); 122 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
92} 123}
93 124
94pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { 125pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
95 let it = element 126 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
127 // where we only check the first parent with different text range.
128 element
96 .ancestors() 129 .ancestors()
97 .take_while(|it| it.text_range() == element.text_range()) 130 .find(|it| it.kind() == IMPL)
98 .last() 131 .map(|it| ast::Impl::cast(it).unwrap())
99 .map(|it| (it.kind(), it.parent())); 132 .map(|it| it.trait_().is_some())
100 match it { 133 .unwrap_or(false)
101 Some((_, Some(it))) => it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST,
102 Some((MACRO_ITEMS, None) | (SOURCE_FILE, None)) => true,
103 _ => false,
104 }
105} 134}
106#[test] 135#[test]
107fn test_has_item_list_or_source_file_parent() { 136fn test_inside_impl_trait_block() {
108 check_pattern_is_applicable(r"i$0", has_item_list_or_source_file_parent); 137 check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block);
109 check_pattern_is_applicable(r"mod foo { f$0 }", has_item_list_or_source_file_parent); 138 check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block);
139 check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block);
140 check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
110} 141}
111 142
112pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { 143pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
@@ -166,12 +197,8 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
166 .is_some() 197 .is_some()
167} 198}
168 199
169fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { 200pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
170 element 201 element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next()
171 .ancestors()
172 .take_while(|it| it.text_range() == element.text_range())
173 .last()
174 .and_then(|it| it.parent())
175} 202}
176 203
177fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 204fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index 37be575e5..6656fd725 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -132,7 +132,7 @@ pub(crate) fn check_edit_with_config(
132 assert_eq_text!(&ra_fixture_after, &actual) 132 assert_eq_text!(&ra_fixture_after, &actual)
133} 133}
134 134
135pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { 135pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
136 let (db, pos) = position(code); 136 let (db, pos) = position(code);
137 137
138 let sema = Semantics::new(&db); 138 let sema = Semantics::new(&db);