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.rs126
1 files changed, 101 insertions, 25 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index f04471b57..8c4bdbed2 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -4,12 +4,19 @@ use syntax::{
4 algo::non_trivia_sibling, 4 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner}, 5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::{self, *}, 7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, T, 8 SyntaxNode, SyntaxToken, T,
9}; 9};
10 10
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/// Direct parent container of the cursor position
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub(crate) enum ImmediatePrevSibling {
16 IfExpr,
17 TraitDefName,
18 ImplDefType,
19}
13 20
14/// Direct parent container of the cursor position 21/// Direct parent container of the cursor position
15#[derive(Copy, Clone, Debug, PartialEq, Eq)] 22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
24 ItemList, 31 ItemList,
25} 32}
26 33
27pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { 34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
28 // First walk the element we are completing up to its highest node that has the same text range 35 let node = maximize_name_ref(name_like)?;
29 // as the element so that we can check in what context it immediately lies. We only do this for 36 let node = match node.parent().and_then(ast::MacroCall::cast) {
30 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. 37 // When a path is being typed after the name of a trait/type of an impl it is being
31 // We only wanna do this if the NameRef is the last segment of the path. 38 // parsed as a macro, so when the trait/impl has a block following it an we are between the
32 let node = match name_like { 39 // name and block the macro will attach the block to itself so maximizing fails to take
33 ast::NameLike::NameRef(name_ref) => { 40 // that into account
34 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { 41 // FIXME path expr and statement have a similar problem with attrs
35 let p = segment.parent_path(); 42 Some(call)
36 if p.parent_path().is_none() { 43 if call.excl_token().is_none()
37 p.syntax() 44 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
38 .ancestors() 45 && call.semicolon_token().is_none() =>
39 .take_while(|it| it.text_range() == p.syntax().text_range()) 46 {
40 .last()? 47 call.syntax().clone()
41 } else { 48 }
42 return None; 49 _ => node,
50 };
51 let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
52 let res = match_ast! {
53 match prev_sibling {
54 ast::ExprStmt(it) => {
55 let node = it.expr()?.syntax().clone();
56 match_ast! {
57 match node {
58 ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
59 _ => return None,
60 }
43 } 61 }
44 } else { 62 },
45 return None; 63 ast::Trait(it) => if it.assoc_item_list().is_none() {
46 } 64 ImmediatePrevSibling::TraitDefName
65 } else {
66 return None
67 },
68 ast::Impl(it) => if it.assoc_item_list().is_none()
69 && (it.for_token().is_none() || it.self_ty().is_some()) {
70 ImmediatePrevSibling::ImplDefType
71 } else {
72 return None
73 },
74 _ => return None,
47 } 75 }
48 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
49 }; 76 };
77 Some(res)
78}
79
80pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
81 let node = maximize_name_ref(name_like)?;
50 let parent = match node.parent() { 82 let parent = match node.parent() {
51 Some(parent) => match ast::MacroCall::cast(parent.clone()) { 83 Some(parent) => match ast::MacroCall::cast(parent.clone()) {
52 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. 84 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
53 // This is usually fine as the node expansion code above already accounts for that with 85 // This is usually fine as the node expansion code above already accounts for that with
54 // the ancestors call, but there is one exception to this which is that when an attribute 86 // the ancestors call, but there is one exception to this which is that when an attribute
55 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ. 87 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
88 // FIXME path expr and statement have a similar problem
56 Some(call) 89 Some(call)
57 if call.excl_token().is_none() 90 if call.excl_token().is_none()
58 && call.token_tree().is_none() 91 && call.token_tree().is_none()
@@ -90,6 +123,32 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL
90 Some(res) 123 Some(res)
91} 124}
92 125
126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
127 // First walk the element we are completing up to its highest node that has the same text range
128 // as the element so that we can check in what context it immediately lies. We only do this for
129 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
130 // We only wanna do this if the NameRef is the last segment of the path.
131 let node = match name_like {
132 ast::NameLike::NameRef(name_ref) => {
133 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
134 let p = segment.parent_path();
135 if p.parent_path().is_none() {
136 p.syntax()
137 .ancestors()
138 .take_while(|it| it.text_range() == p.syntax().text_range())
139 .last()?
140 } else {
141 return None;
142 }
143 } else {
144 return None;
145 }
146 }
147 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
148 };
149 Some(node)
150}
151
93#[cfg(test)] 152#[cfg(test)]
94fn check_location(code: &str, loc: ImmediateLocation) { 153fn check_location(code: &str, loc: ImmediateLocation) {
95 check_pattern_is_applicable(code, |e| { 154 check_pattern_is_applicable(code, |e| {
@@ -192,17 +251,34 @@ fn test_for_is_prev2() {
192 check_pattern_is_applicable(r"for i i$0", for_is_prev2); 251 check_pattern_is_applicable(r"for i i$0", for_is_prev2);
193} 252}
194 253
195pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool { 254#[cfg(test)]
196 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some() 255fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
256 check_pattern_is_applicable(code, |e| {
257 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
258 assert_eq!(determine_prev_sibling(name), sibling.into());
259 true
260 });
197} 261}
262
198#[test] 263#[test]
199fn test_has_impl_as_prev_sibling() { 264fn test_has_impl_as_prev_sibling() {
200 check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL)); 265 check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
266 check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
267 check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
268 check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
269 check_prev_sibling(r"impl A for w$0 {}", None);
270 check_prev_sibling(r"impl A for w$0", None);
201} 271}
202 272
203#[test] 273#[test]
204fn test_has_trait_as_prev_sibling() { 274fn test_has_trait_as_prev_sibling() {
205 check_pattern_is_applicable(r"trait A w$0 {}", |it| has_prev_sibling(it, TRAIT)); 275 check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
276 check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
277}
278
279#[test]
280fn test_has_if_expr_as_prev_sibling() {
281 check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
206} 282}
207 283
208pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 284pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {