aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/patterns.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r--crates/ide_completion/src/patterns.rs173
1 files changed, 103 insertions, 70 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 04f2c532b..ed289d561 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -11,96 +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 match not_same_range_ancestor(element) { 126 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
96 Some(it) => it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST, 127 // where we only check the first parent with different text range.
97 None => true, 128 element
98 } 129 .ancestors()
130 .find(|it| it.kind() == IMPL)
131 .map(|it| ast::Impl::cast(it).unwrap())
132 .map(|it| it.trait_().is_some())
133 .unwrap_or(false)
99} 134}
100#[test] 135#[test]
101fn test_has_item_list_or_source_file_parent() { 136fn test_inside_impl_trait_block() {
102 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);
103 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);
104} 141}
105 142
106pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { 143pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
@@ -160,12 +197,8 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
160 .is_some() 197 .is_some()
161} 198}
162 199
163fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { 200pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
164 element 201 element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next()
165 .ancestors()
166 .take_while(|it| it.text_range() == element.text_range())
167 .last()
168 .and_then(|it| it.parent())
169} 202}
170 203
171fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 204fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {