diff options
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 126 |
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)] |
12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 12 | use 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)] | ||
15 | pub(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 | ||
27 | pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { | 34 | pub(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 | |||
80 | pub(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 | ||
126 | fn 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)] |
94 | fn check_location(code: &str, loc: ImmediateLocation) { | 153 | fn 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 | ||
195 | pub(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() | 255 | fn 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] |
199 | fn test_has_impl_as_prev_sibling() { | 264 | fn 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] |
204 | fn test_has_trait_as_prev_sibling() { | 274 | fn 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] | ||
280 | fn test_has_if_expr_as_prev_sibling() { | ||
281 | check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr); | ||
206 | } | 282 | } |
207 | 283 | ||
208 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | 284 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { |