diff options
author | Lukas Wirth <[email protected]> | 2021-05-28 21:03:31 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-05-28 21:03:31 +0100 |
commit | 47ad752e6cca5eb5499168ec7f859879e384a5ab (patch) | |
tree | 1f54d1080184735cffd2c6ee3891e94510c2eefe /crates/ide_completion | |
parent | a6b92a8cc00c4a4c451e6da2dd4e2a2e8e7bf749 (diff) |
Implement prev sibling determination for `CompletionContext`
Diffstat (limited to 'crates/ide_completion')
-rw-r--r-- | crates/ide_completion/src/completions/keyword.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 40 | ||||
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 126 |
3 files changed, 114 insertions, 54 deletions
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 662c389fe..06789b704 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -118,7 +118,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
118 | add_keyword("let", "let "); | 118 | add_keyword("let", "let "); |
119 | } | 119 | } |
120 | 120 | ||
121 | if ctx.after_if { | 121 | if ctx.after_if() { |
122 | add_keyword("else", "else {\n $0\n}"); | 122 | add_keyword("else", "else {\n $0\n}"); |
123 | add_keyword("else if", "else if $1 {\n $0\n}"); | 123 | add_keyword("else if", "else if $1 {\n $0\n}"); |
124 | } | 124 | } |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index faf8469a5..8d6440cb2 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -17,8 +17,8 @@ use text_edit::Indel; | |||
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
19 | patterns::{ | 19 | patterns::{ |
20 | determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block, | 20 | determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, |
21 | is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, | 21 | is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling, |
22 | }, | 22 | }, |
23 | CompletionConfig, | 23 | CompletionConfig, |
24 | }; | 24 | }; |
@@ -29,12 +29,6 @@ pub(crate) enum PatternRefutability { | |||
29 | Irrefutable, | 29 | Irrefutable, |
30 | } | 30 | } |
31 | 31 | ||
32 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
33 | pub(crate) enum PrevSibling { | ||
34 | Trait, | ||
35 | Impl, | ||
36 | } | ||
37 | |||
38 | /// `CompletionContext` is created early during completion to figure out, where | 32 | /// `CompletionContext` is created early during completion to figure out, where |
39 | /// exactly is the cursor, syntax-wise. | 33 | /// exactly is the cursor, syntax-wise. |
40 | #[derive(Debug)] | 34 | #[derive(Debug)] |
@@ -76,6 +70,7 @@ pub(crate) struct CompletionContext<'a> { | |||
76 | pub(super) is_param: bool, | 70 | pub(super) is_param: bool, |
77 | 71 | ||
78 | pub(super) completion_location: Option<ImmediateLocation>, | 72 | pub(super) completion_location: Option<ImmediateLocation>, |
73 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, | ||
79 | 74 | ||
80 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 75 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
81 | pub(super) active_parameter: Option<ActiveParameter>, | 76 | pub(super) active_parameter: Option<ActiveParameter>, |
@@ -83,7 +78,6 @@ pub(crate) struct CompletionContext<'a> { | |||
83 | pub(super) is_trivial_path: bool, | 78 | pub(super) is_trivial_path: bool, |
84 | /// If not a trivial path, the prefix (qualifier). | 79 | /// If not a trivial path, the prefix (qualifier). |
85 | pub(super) path_qual: Option<ast::Path>, | 80 | pub(super) path_qual: Option<ast::Path>, |
86 | pub(super) after_if: bool, | ||
87 | /// `true` if we are a statement or a last expr in the block. | 81 | /// `true` if we are a statement or a last expr in the block. |
88 | pub(super) can_be_stmt: bool, | 82 | pub(super) can_be_stmt: bool, |
89 | /// `true` if we expect an expression at the cursor position. | 83 | /// `true` if we expect an expression at the cursor position. |
@@ -107,7 +101,6 @@ pub(crate) struct CompletionContext<'a> { | |||
107 | 101 | ||
108 | // keyword patterns | 102 | // keyword patterns |
109 | pub(super) previous_token: Option<SyntaxToken>, | 103 | pub(super) previous_token: Option<SyntaxToken>, |
110 | pub(super) prev_sibling: Option<PrevSibling>, | ||
111 | pub(super) in_loop_body: bool, | 104 | pub(super) in_loop_body: bool, |
112 | pub(super) is_match_arm: bool, | 105 | pub(super) is_match_arm: bool, |
113 | pub(super) incomplete_let: bool, | 106 | pub(super) incomplete_let: bool, |
@@ -173,7 +166,6 @@ impl<'a> CompletionContext<'a> { | |||
173 | is_pat_or_const: None, | 166 | is_pat_or_const: None, |
174 | is_trivial_path: false, | 167 | is_trivial_path: false, |
175 | path_qual: None, | 168 | path_qual: None, |
176 | after_if: false, | ||
177 | can_be_stmt: false, | 169 | can_be_stmt: false, |
178 | is_expr: false, | 170 | is_expr: false, |
179 | is_new_item: false, | 171 | is_new_item: false, |
@@ -308,7 +300,14 @@ impl<'a> CompletionContext<'a> { | |||
308 | } | 300 | } |
309 | 301 | ||
310 | pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { | 302 | pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { |
311 | self.prev_sibling.is_some() | 303 | matches!( |
304 | self.prev_sibling, | ||
305 | Some(ImmediatePrevSibling::ImplDefType) | Some(ImmediatePrevSibling::TraitDefName) | ||
306 | ) | ||
307 | } | ||
308 | |||
309 | pub(crate) fn after_if(&self) -> bool { | ||
310 | matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr)) | ||
312 | } | 311 | } |
313 | 312 | ||
314 | pub(crate) fn is_path_disallowed(&self) -> bool { | 313 | pub(crate) fn is_path_disallowed(&self) -> bool { |
@@ -324,11 +323,6 @@ impl<'a> CompletionContext<'a> { | |||
324 | self.previous_token = previous_token(syntax_element.clone()); | 323 | self.previous_token = previous_token(syntax_element.clone()); |
325 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | 324 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); |
326 | self.is_match_arm = is_match_arm(syntax_element.clone()); | 325 | self.is_match_arm = is_match_arm(syntax_element.clone()); |
327 | if has_prev_sibling(syntax_element.clone(), IMPL) { | ||
328 | self.prev_sibling = Some(PrevSibling::Impl) | ||
329 | } else if has_prev_sibling(syntax_element.clone(), TRAIT) { | ||
330 | self.prev_sibling = Some(PrevSibling::Trait) | ||
331 | } | ||
332 | 326 | ||
333 | self.mod_declaration_under_caret = | 327 | self.mod_declaration_under_caret = |
334 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | 328 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) |
@@ -468,6 +462,7 @@ impl<'a> CompletionContext<'a> { | |||
468 | None => return, | 462 | None => return, |
469 | }; | 463 | }; |
470 | self.completion_location = determine_location(&name_like); | 464 | self.completion_location = determine_location(&name_like); |
465 | self.prev_sibling = determine_prev_sibling(&name_like); | ||
471 | match name_like { | 466 | match name_like { |
472 | ast::NameLike::Lifetime(lifetime) => { | 467 | ast::NameLike::Lifetime(lifetime) => { |
473 | self.classify_lifetime(original_file, lifetime, offset); | 468 | self.classify_lifetime(original_file, lifetime, offset); |
@@ -656,17 +651,6 @@ impl<'a> CompletionContext<'a> { | |||
656 | }) | 651 | }) |
657 | .unwrap_or(false); | 652 | .unwrap_or(false); |
658 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | 653 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); |
659 | |||
660 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
661 | if let Some(if_expr) = | ||
662 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
663 | { | ||
664 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
665 | { | ||
666 | self.after_if = true; | ||
667 | } | ||
668 | } | ||
669 | } | ||
670 | } | 654 | } |
671 | 655 | ||
672 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | 656 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { |
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 { |