mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-14 17:07:26 +00:00
Merge #11714
11714: fix: Fix completions not always working in for-loop patterns r=Veykril a=Veykril Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11205 bors r+ Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
61b1449387
4 changed files with 63 additions and 17 deletions
|
@ -24,8 +24,8 @@ use text_edit::Indel;
|
|||
|
||||
use crate::{
|
||||
patterns::{
|
||||
determine_location, determine_prev_sibling, for_is_prev2, is_in_loop_body, previous_token,
|
||||
ImmediateLocation, ImmediatePrevSibling,
|
||||
determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
|
||||
previous_token, ImmediateLocation, ImmediatePrevSibling,
|
||||
},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
@ -729,7 +729,7 @@ impl<'a> CompletionContext<'a> {
|
|||
) {
|
||||
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
|
||||
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
||||
if for_is_prev2(syntax_element.clone()) {
|
||||
if is_in_token_of_for_loop(syntax_element.clone()) {
|
||||
// for pat $0
|
||||
// there is nothing to complete here except `in` keyword
|
||||
// don't bother populating the context
|
||||
|
|
|
@ -11,7 +11,7 @@ use syntax::{
|
|||
ast::{self, HasArgList, HasLoopBody},
|
||||
match_ast, AstNode, Direction, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -295,19 +295,37 @@ pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
|
|||
element.into_token().and_then(previous_non_trivia_token)
|
||||
}
|
||||
|
||||
/// Check if the token previous to the previous one is `for`.
|
||||
/// For example, `for _ i$0` => true.
|
||||
pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(previous_non_trivia_token)
|
||||
.and_then(previous_non_trivia_token)
|
||||
.filter(|it| it.kind() == T![for])
|
||||
.is_some()
|
||||
pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
|
||||
// oh my ...
|
||||
(|| {
|
||||
let syntax_token = element.into_token()?;
|
||||
let range = syntax_token.text_range();
|
||||
let for_expr = syntax_token.ancestors().find_map(ast::ForExpr::cast)?;
|
||||
|
||||
// check if the current token is the `in` token of a for loop
|
||||
if let Some(token) = for_expr.in_token() {
|
||||
return Some(syntax_token == token);
|
||||
}
|
||||
let pat = for_expr.pat()?;
|
||||
if range.end() < pat.syntax().text_range().end() {
|
||||
// if we are inside or before the pattern we can't be at the `in` token position
|
||||
return None;
|
||||
}
|
||||
let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
|
||||
Some(match next_sibl {
|
||||
// the loop body is some node, if our token is at the start we are at the `in` position,
|
||||
// otherwise we could be in a recovered expression, we don't wanna ruin completions there
|
||||
syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
|
||||
// the loop body consists of a single token, if we are this we are certainly at the `in` token position
|
||||
syntax::NodeOrToken::Token(t) => t == syntax_token,
|
||||
})
|
||||
})()
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_is_prev2() {
|
||||
check_pattern_is_applicable(r"for i i$0", for_is_prev2);
|
||||
check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
|
||||
|
@ -329,7 +347,7 @@ pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
|
|||
|
||||
fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
|
||||
let mut token = token.prev_token();
|
||||
while let Some(inner) = token.clone() {
|
||||
while let Some(inner) = token {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
|
@ -339,6 +357,18 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
|
|||
None
|
||||
}
|
||||
|
||||
fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
|
||||
let mut e = ele.next_sibling_or_token();
|
||||
while let Some(inner) = e {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
e = inner.next_sibling_or_token();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syntax::algo::find_node_at_offset;
|
||||
|
|
|
@ -76,8 +76,14 @@ fn after_target_name_in_impl() {
|
|||
kw for
|
||||
"#]],
|
||||
);
|
||||
// FIXME: This should emit `kw where`
|
||||
check(r"impl Trait for Type $0", expect![[r#""#]]);
|
||||
// FIXME: This should not emit `kw for`
|
||||
check(
|
||||
r"impl Trait for Type $0",
|
||||
expect![[r#"
|
||||
kw where
|
||||
kw for
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -92,6 +92,16 @@ fn quux() {
|
|||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
check_empty(
|
||||
r#"
|
||||
fn foo() {
|
||||
for &$0 in () {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw mut
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue