mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 22:01:37 +00:00
Implement prev sibling determination for CompletionContext
This commit is contained in:
parent
a6b92a8cc0
commit
47ad752e6c
3 changed files with 115 additions and 55 deletions
|
@ -4,12 +4,19 @@ use syntax::{
|
|||
algo::non_trivia_sibling,
|
||||
ast::{self, LoopBodyOwner},
|
||||
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
|
||||
SyntaxKind::{self, *},
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, T,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
|
||||
/// Direct parent container of the cursor position
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ImmediatePrevSibling {
|
||||
IfExpr,
|
||||
TraitDefName,
|
||||
ImplDefType,
|
||||
}
|
||||
|
||||
/// Direct parent container of the cursor position
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
|
|||
ItemList,
|
||||
}
|
||||
|
||||
pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
|
||||
// First walk the element we are completing up to its highest node that has the same text range
|
||||
// as the element so that we can check in what context it immediately lies. We only do this for
|
||||
// NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
|
||||
// We only wanna do this if the NameRef is the last segment of the path.
|
||||
let node = match name_like {
|
||||
ast::NameLike::NameRef(name_ref) => {
|
||||
if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
|
||||
let p = segment.parent_path();
|
||||
if p.parent_path().is_none() {
|
||||
p.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == p.syntax().text_range())
|
||||
.last()?
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
|
||||
let node = maximize_name_ref(name_like)?;
|
||||
let node = match node.parent().and_then(ast::MacroCall::cast) {
|
||||
// When a path is being typed after the name of a trait/type of an impl it is being
|
||||
// parsed as a macro, so when the trait/impl has a block following it an we are between the
|
||||
// name and block the macro will attach the block to itself so maximizing fails to take
|
||||
// that into account
|
||||
// FIXME path expr and statement have a similar problem with attrs
|
||||
Some(call)
|
||||
if call.excl_token().is_none()
|
||||
&& call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
|
||||
&& call.semicolon_token().is_none() =>
|
||||
{
|
||||
call.syntax().clone()
|
||||
}
|
||||
it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
|
||||
_ => node,
|
||||
};
|
||||
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
|
||||
let res = match_ast! {
|
||||
match prev_sibling {
|
||||
ast::ExprStmt(it) => {
|
||||
let node = it.expr()?.syntax().clone();
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
},
|
||||
ast::Trait(it) => if it.assoc_item_list().is_none() {
|
||||
ImmediatePrevSibling::TraitDefName
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
ast::Impl(it) => if it.assoc_item_list().is_none()
|
||||
&& (it.for_token().is_none() || it.self_ty().is_some()) {
|
||||
ImmediatePrevSibling::ImplDefType
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
|
||||
let node = maximize_name_ref(name_like)?;
|
||||
let parent = match node.parent() {
|
||||
Some(parent) => match ast::MacroCall::cast(parent.clone()) {
|
||||
// When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
|
||||
// This is usually fine as the node expansion code above already accounts for that with
|
||||
// the ancestors call, but there is one exception to this which is that when an attribute
|
||||
// precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
|
||||
// FIXME path expr and statement have a similar problem
|
||||
Some(call)
|
||||
if call.excl_token().is_none()
|
||||
&& call.token_tree().is_none()
|
||||
|
@ -90,6 +123,32 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL
|
|||
Some(res)
|
||||
}
|
||||
|
||||
fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
|
||||
// First walk the element we are completing up to its highest node that has the same text range
|
||||
// as the element so that we can check in what context it immediately lies. We only do this for
|
||||
// NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
|
||||
// We only wanna do this if the NameRef is the last segment of the path.
|
||||
let node = match name_like {
|
||||
ast::NameLike::NameRef(name_ref) => {
|
||||
if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
|
||||
let p = segment.parent_path();
|
||||
if p.parent_path().is_none() {
|
||||
p.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == p.syntax().text_range())
|
||||
.last()?
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
|
||||
};
|
||||
Some(node)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_location(code: &str, loc: ImmediateLocation) {
|
||||
check_pattern_is_applicable(code, |e| {
|
||||
|
@ -192,17 +251,34 @@ fn test_for_is_prev2() {
|
|||
check_pattern_is_applicable(r"for i i$0", for_is_prev2);
|
||||
}
|
||||
|
||||
pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
|
||||
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
|
||||
#[cfg(test)]
|
||||
fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
|
||||
check_pattern_is_applicable(code, |e| {
|
||||
let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
|
||||
assert_eq!(determine_prev_sibling(name), sibling.into());
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_impl_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
|
||||
check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
|
||||
check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
|
||||
check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
|
||||
check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
|
||||
check_prev_sibling(r"impl A for w$0 {}", None);
|
||||
check_prev_sibling(r"impl A for w$0", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_trait_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"trait A w$0 {}", |it| has_prev_sibling(it, TRAIT));
|
||||
check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
|
||||
check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_if_expr_as_prev_sibling() {
|
||||
check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue