Skip to content

Commit 47ad752

Browse files
committed
Implement prev sibling determination for CompletionContext
1 parent a6b92a8 commit 47ad752

File tree

3 files changed

+114
-54
lines changed

3 files changed

+114
-54
lines changed

crates/ide_completion/src/completions/keyword.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
118118
add_keyword("let", "let ");
119119
}
120120

121-
if ctx.after_if {
121+
if ctx.after_if() {
122122
add_keyword("else", "else {\n $0\n}");
123123
add_keyword("else if", "else if $1 {\n $0\n}");
124124
}

crates/ide_completion/src/context.rs

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use text_edit::Indel;
1717

1818
use crate::{
1919
patterns::{
20-
determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block,
21-
is_in_loop_body, is_match_arm, previous_token, ImmediateLocation,
20+
determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block,
21+
is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling,
2222
},
2323
CompletionConfig,
2424
};
@@ -29,12 +29,6 @@ pub(crate) enum PatternRefutability {
2929
Irrefutable,
3030
}
3131

32-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33-
pub(crate) enum PrevSibling {
34-
Trait,
35-
Impl,
36-
}
37-
3832
/// `CompletionContext` is created early during completion to figure out, where
3933
/// exactly is the cursor, syntax-wise.
4034
#[derive(Debug)]
@@ -76,14 +70,14 @@ pub(crate) struct CompletionContext<'a> {
7670
pub(super) is_param: bool,
7771

7872
pub(super) completion_location: Option<ImmediateLocation>,
73+
pub(super) prev_sibling: Option<ImmediatePrevSibling>,
7974

8075
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
8176
pub(super) active_parameter: Option<ActiveParameter>,
8277
/// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
8378
pub(super) is_trivial_path: bool,
8479
/// If not a trivial path, the prefix (qualifier).
8580
pub(super) path_qual: Option<ast::Path>,
86-
pub(super) after_if: bool,
8781
/// `true` if we are a statement or a last expr in the block.
8882
pub(super) can_be_stmt: bool,
8983
/// `true` if we expect an expression at the cursor position.
@@ -107,7 +101,6 @@ pub(crate) struct CompletionContext<'a> {
107101

108102
// keyword patterns
109103
pub(super) previous_token: Option<SyntaxToken>,
110-
pub(super) prev_sibling: Option<PrevSibling>,
111104
pub(super) in_loop_body: bool,
112105
pub(super) is_match_arm: bool,
113106
pub(super) incomplete_let: bool,
@@ -173,7 +166,6 @@ impl<'a> CompletionContext<'a> {
173166
is_pat_or_const: None,
174167
is_trivial_path: false,
175168
path_qual: None,
176-
after_if: false,
177169
can_be_stmt: false,
178170
is_expr: false,
179171
is_new_item: false,
@@ -308,7 +300,14 @@ impl<'a> CompletionContext<'a> {
308300
}
309301

310302
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))
312311
}
313312

314313
pub(crate) fn is_path_disallowed(&self) -> bool {
@@ -324,11 +323,6 @@ impl<'a> CompletionContext<'a> {
324323
self.previous_token = previous_token(syntax_element.clone());
325324
self.in_loop_body = is_in_loop_body(syntax_element.clone());
326325
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-
}
332326

333327
self.mod_declaration_under_caret =
334328
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
@@ -468,6 +462,7 @@ impl<'a> CompletionContext<'a> {
468462
None => return,
469463
};
470464
self.completion_location = determine_location(&name_like);
465+
self.prev_sibling = determine_prev_sibling(&name_like);
471466
match name_like {
472467
ast::NameLike::Lifetime(lifetime) => {
473468
self.classify_lifetime(original_file, lifetime, offset);
@@ -656,17 +651,6 @@ impl<'a> CompletionContext<'a> {
656651
})
657652
.unwrap_or(false);
658653
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-
}
670654
}
671655

672656
if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {

crates/ide_completion/src/patterns.rs

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ use syntax::{
44
algo::non_trivia_sibling,
55
ast::{self, LoopBodyOwner},
66
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7-
SyntaxKind::{self, *},
7+
SyntaxKind::*,
88
SyntaxNode, SyntaxToken, T,
99
};
1010

1111
#[cfg(test)]
1212
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+
}
1320

1421
/// Direct parent container of the cursor position
1522
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
2431
ItemList,
2532
}
2633

27-
pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
28-
// First walk the element we are completing up to its highest node that has the same text range
29-
// as the element so that we can check in what context it immediately lies. We only do this for
30-
// NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
31-
// We only wanna do this if the NameRef is the last segment of the path.
32-
let node = match name_like {
33-
ast::NameLike::NameRef(name_ref) => {
34-
if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
35-
let p = segment.parent_path();
36-
if p.parent_path().is_none() {
37-
p.syntax()
38-
.ancestors()
39-
.take_while(|it| it.text_range() == p.syntax().text_range())
40-
.last()?
41-
} else {
42-
return None;
34+
pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
35+
let node = maximize_name_ref(name_like)?;
36+
let node = match node.parent().and_then(ast::MacroCall::cast) {
37+
// When a path is being typed after the name of a trait/type of an impl it is being
38+
// parsed as a macro, so when the trait/impl has a block following it an we are between the
39+
// name and block the macro will attach the block to itself so maximizing fails to take
40+
// that into account
41+
// FIXME path expr and statement have a similar problem with attrs
42+
Some(call)
43+
if call.excl_token().is_none()
44+
&& call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
45+
&& call.semicolon_token().is_none() =>
46+
{
47+
call.syntax().clone()
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()?.syntax().clone();
56+
match_ast! {
57+
match node {
58+
ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
59+
_ => return None,
60+
}
4361
}
44-
} else {
45-
return None;
46-
}
62+
},
63+
ast::Trait(it) => if it.assoc_item_list().is_none() {
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,
4775
}
48-
it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
4976
};
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)?;
5082
let parent = match node.parent() {
5183
Some(parent) => match ast::MacroCall::cast(parent.clone()) {
5284
// When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
5385
// This is usually fine as the node expansion code above already accounts for that with
5486
// the ancestors call, but there is one exception to this which is that when an attribute
5587
// 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
5689
Some(call)
5790
if call.excl_token().is_none()
5891
&& call.token_tree().is_none()
@@ -90,6 +123,32 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL
90123
Some(res)
91124
}
92125

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+
93152
#[cfg(test)]
94153
fn check_location(code: &str, loc: ImmediateLocation) {
95154
check_pattern_is_applicable(code, |e| {
@@ -192,17 +251,34 @@ fn test_for_is_prev2() {
192251
check_pattern_is_applicable(r"for i i$0", for_is_prev2);
193252
}
194253

195-
pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
196-
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
254+
#[cfg(test)]
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+
});
197261
}
262+
198263
#[test]
199264
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);
201271
}
202272

203273
#[test]
204274
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);
206282
}
207283

208284
pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {

0 commit comments

Comments
 (0)