diff options
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 276 |
1 files changed, 202 insertions, 74 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index 19e42ba43..caf0ef39f 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -4,16 +4,24 @@ 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)] |
16 | pub(crate) enum ImmediateLocation { | 23 | pub(crate) enum ImmediateLocation { |
24 | Use, | ||
17 | Impl, | 25 | Impl, |
18 | Trait, | 26 | Trait, |
19 | RecordField, | 27 | RecordField, |
@@ -23,30 +31,70 @@ pub(crate) enum ImmediateLocation { | |||
23 | ItemList, | 31 | ItemList, |
24 | } | 32 | } |
25 | 33 | ||
26 | pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { | 34 | pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { |
27 | // First "expand" the element we are completing to its maximum so that we can check in what | 35 | let node = maximize_name_ref(name_like)?; |
28 | // context it immediately lies. This for example means if the token is a NameRef at the end of | 36 | let node = match node.parent().and_then(ast::MacroCall::cast) { |
29 | // a path, we want to look at where the path is in the tree. | 37 | // When a path is being typed after the name of a trait/type of an impl it is being |
30 | let node = match tok.parent().and_then(ast::NameLike::cast)? { | 38 | // parsed as a macro, so when the trait/impl has a block following it an we are between the |
31 | ast::NameLike::NameRef(name_ref) => { | 39 | // name and block the macro will attach the block to itself so maximizing fails to take |
32 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { | 40 | // that into account |
33 | let p = segment.parent_path(); | 41 | // FIXME path expr and statement have a similar problem with attrs |
34 | if p.parent_path().is_none() { | 42 | Some(call) |
35 | p.syntax() | 43 | if call.excl_token().is_none() |
36 | .ancestors() | 44 | && call.token_tree().map_or(false, |t| t.l_curly_token().is_some()) |
37 | .take_while(|it| it.text_range() == p.syntax().text_range()) | 45 | && call.semicolon_token().is_none() => |
38 | .last()? | 46 | { |
39 | } else { | 47 | call.syntax().clone() |
40 | return None; | 48 | } |
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().filter(|_| it.semicolon_token().is_none())?.syntax().clone(); | ||
56 | match_ast! { | ||
57 | match node { | ||
58 | ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr, | ||
59 | _ => return None, | ||
60 | } | ||
41 | } | 61 | } |
42 | } else { | 62 | }, |
43 | return None; | 63 | ast::Trait(it) => if it.assoc_item_list().is_none() { |
44 | } | 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, | ||
45 | } | 75 | } |
46 | it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), | ||
47 | }; | 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)?; | ||
48 | let parent = match node.parent() { | 82 | let parent = match node.parent() { |
49 | Some(parent) => parent, | 83 | Some(parent) => match ast::MacroCall::cast(parent.clone()) { |
84 | // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. | ||
85 | // This is usually fine as the node expansion code above already accounts for that with | ||
86 | // the ancestors call, but there is one exception to this which is that when an attribute | ||
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 | ||
89 | Some(call) | ||
90 | if call.excl_token().is_none() | ||
91 | && call.token_tree().is_none() | ||
92 | && call.semicolon_token().is_none() => | ||
93 | { | ||
94 | call.syntax().parent()? | ||
95 | } | ||
96 | _ => parent, | ||
97 | }, | ||
50 | // SourceFile | 98 | // SourceFile |
51 | None => { | 99 | None => { |
52 | return match node.kind() { | 100 | return match node.kind() { |
@@ -58,6 +106,7 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> | |||
58 | let res = match_ast! { | 106 | let res = match_ast! { |
59 | match parent { | 107 | match parent { |
60 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, | 108 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, |
109 | ast::Use(_it) => ImmediateLocation::Use, | ||
61 | ast::BlockExpr(_it) => ImmediateLocation::BlockExpr, | 110 | ast::BlockExpr(_it) => ImmediateLocation::BlockExpr, |
62 | ast::SourceFile(_it) => ImmediateLocation::ItemList, | 111 | ast::SourceFile(_it) => ImmediateLocation::ItemList, |
63 | ast::ItemList(_it) => ImmediateLocation::ItemList, | 112 | ast::ItemList(_it) => ImmediateLocation::ItemList, |
@@ -74,51 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> | |||
74 | Some(res) | 123 | Some(res) |
75 | } | 124 | } |
76 | 125 | ||
77 | #[cfg(test)] | 126 | fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { |
78 | fn check_location(code: &str, loc: ImmediateLocation) { | 127 | // First walk the element we are completing up to its highest node that has the same text range |
79 | check_pattern_is_applicable(code, |e| { | 128 | // as the element so that we can check in what context it immediately lies. We only do this for |
80 | assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc)); | 129 | // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. |
81 | true | 130 | // We only wanna do this if the NameRef is the last segment of the path. |
82 | }); | 131 | let node = match name_like { |
83 | } | 132 | ast::NameLike::NameRef(name_ref) => { |
84 | 133 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { | |
85 | #[test] | 134 | let p = segment.parent_path(); |
86 | fn test_has_trait_parent() { | 135 | if p.parent_path().is_none() { |
87 | check_location(r"trait A { f$0 }", ImmediateLocation::Trait); | 136 | p.syntax() |
88 | } | 137 | .ancestors() |
89 | 138 | .take_while(|it| it.text_range() == p.syntax().text_range()) | |
90 | #[test] | 139 | .last()? |
91 | fn test_has_impl_parent() { | 140 | } else { |
92 | check_location(r"impl A { f$0 }", ImmediateLocation::Impl); | 141 | return None; |
93 | } | 142 | } |
94 | #[test] | 143 | } else { |
95 | fn test_has_field_list_parent() { | 144 | return None; |
96 | check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField); | 145 | } |
97 | check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField); | 146 | } |
98 | } | 147 | it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), |
99 | 148 | }; | |
100 | #[test] | 149 | Some(node) |
101 | fn test_has_block_expr_parent() { | ||
102 | check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr); | ||
103 | } | ||
104 | |||
105 | #[test] | ||
106 | fn test_has_ident_pat_parent() { | ||
107 | check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat); | ||
108 | check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat); | ||
109 | check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat); | ||
110 | check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat); | ||
111 | } | ||
112 | |||
113 | #[test] | ||
114 | fn test_has_ref_expr_parent() { | ||
115 | check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); | ||
116 | } | ||
117 | |||
118 | #[test] | ||
119 | fn test_has_item_list_or_source_file_parent() { | ||
120 | check_location(r"i$0", ImmediateLocation::ItemList); | ||
121 | check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList); | ||
122 | } | 150 | } |
123 | 151 | ||
124 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 152 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { |
@@ -170,14 +198,6 @@ fn test_for_is_prev2() { | |||
170 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); | 198 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); |
171 | } | 199 | } |
172 | 200 | ||
173 | pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool { | ||
174 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some() | ||
175 | } | ||
176 | #[test] | ||
177 | fn test_has_impl_as_prev_sibling() { | ||
178 | check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL)); | ||
179 | } | ||
180 | |||
181 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | 201 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { |
182 | element | 202 | element |
183 | .ancestors() | 203 | .ancestors() |
@@ -226,3 +246,111 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<Syntax | |||
226 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | 246 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) |
227 | } | 247 | } |
228 | } | 248 | } |
249 | |||
250 | #[cfg(test)] | ||
251 | mod tests { | ||
252 | use super::*; | ||
253 | |||
254 | fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) { | ||
255 | check_pattern_is_applicable(code, |e| { | ||
256 | let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); | ||
257 | assert_eq!(determine_location(name), loc.into()); | ||
258 | true | ||
259 | }); | ||
260 | } | ||
261 | |||
262 | fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { | ||
263 | check_pattern_is_applicable(code, |e| { | ||
264 | let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); | ||
265 | assert_eq!(determine_prev_sibling(name), sibling.into()); | ||
266 | true | ||
267 | }); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_trait_loc() { | ||
272 | check_location(r"trait A { f$0 }", ImmediateLocation::Trait); | ||
273 | check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait); | ||
274 | check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait); | ||
275 | check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait); | ||
276 | check_location(r"trait A$0 {}", None); | ||
277 | check_location(r"trait A { fn f$0 }", None); | ||
278 | } | ||
279 | |||
280 | #[test] | ||
281 | fn test_impl_loc() { | ||
282 | check_location(r"impl A { f$0 }", ImmediateLocation::Impl); | ||
283 | check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl); | ||
284 | check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl); | ||
285 | check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl); | ||
286 | check_location(r"impl A$0 {}", None); | ||
287 | check_location(r"impl A { fn f$0 }", None); | ||
288 | } | ||
289 | |||
290 | #[test] | ||
291 | fn test_use_loc() { | ||
292 | check_location(r"use f$0", ImmediateLocation::Use); | ||
293 | check_location(r"use f$0;", ImmediateLocation::Use); | ||
294 | check_location(r"use f::{f$0}", None); | ||
295 | check_location(r"use {f$0}", None); | ||
296 | } | ||
297 | |||
298 | #[test] | ||
299 | fn test_record_field_loc() { | ||
300 | check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField); | ||
301 | check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField); | ||
302 | check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_block_expr_loc() { | ||
307 | check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr); | ||
308 | check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr); | ||
309 | } | ||
310 | |||
311 | #[test] | ||
312 | fn test_ident_pat_loc() { | ||
313 | check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat); | ||
314 | check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat); | ||
315 | check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat); | ||
316 | check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat); | ||
317 | } | ||
318 | |||
319 | #[test] | ||
320 | fn test_ref_expr_loc() { | ||
321 | check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); | ||
322 | } | ||
323 | |||
324 | #[test] | ||
325 | fn test_item_list_loc() { | ||
326 | check_location(r"i$0", ImmediateLocation::ItemList); | ||
327 | check_location(r"#[attr] i$0", ImmediateLocation::ItemList); | ||
328 | check_location(r"fn f() {} i$0", ImmediateLocation::ItemList); | ||
329 | check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList); | ||
330 | check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList); | ||
331 | check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList); | ||
332 | check_location(r"mod foo$0 {}", None); | ||
333 | } | ||
334 | |||
335 | #[test] | ||
336 | fn test_impl_prev_sibling() { | ||
337 | check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType); | ||
338 | check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType); | ||
339 | check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType); | ||
340 | check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType); | ||
341 | check_prev_sibling(r"impl A for w$0 {}", None); | ||
342 | check_prev_sibling(r"impl A for w$0", None); | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | fn test_trait_prev_sibling() { | ||
347 | check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName); | ||
348 | check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn test_if_expr_prev_sibling() { | ||
353 | check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr); | ||
354 | check_prev_sibling(r"fn foo() { if true {}; w$0", None); | ||
355 | } | ||
356 | } | ||