aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-05-28 21:03:31 +0100
committerLukas Wirth <[email protected]>2021-05-28 21:03:31 +0100
commit47ad752e6cca5eb5499168ec7f859879e384a5ab (patch)
tree1f54d1080184735cffd2c6ee3891e94510c2eefe /crates
parenta6b92a8cc00c4a4c451e6da2dd4e2a2e8e7bf749 (diff)
Implement prev sibling determination for `CompletionContext`
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_completion/src/completions/keyword.rs2
-rw-r--r--crates/ide_completion/src/context.rs40
-rw-r--r--crates/ide_completion/src/patterns.rs126
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
18use crate::{ 18use 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)]
33pub(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)]
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 {