mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-15 01:55:03 +00:00
Merge #9041
9041: internal: Implement prev sibling determination for `CompletionContext ` r=Veykril a=Veykril bors r+ Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
7869b01b70
@ -34,19 +34,13 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
||||
|
||||
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
|
||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!("Expected a cursor position, got a range instead"),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(db, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
fn with_range(ra_fixture: &str) -> (Self, FileRange) {
|
||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||
let range = match range_or_offset {
|
||||
RangeOrOffset::Range(it) => it,
|
||||
RangeOrOffset::Offset(_) => panic!("Expected a cursor range, got a position instead"),
|
||||
};
|
||||
let range = range_or_offset.expect_range();
|
||||
(db, FileRange { file_id, range })
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Utilities for creating `Analysis` instances for tests.
|
||||
use ide_db::base_db::fixture::ChangeFixture;
|
||||
use syntax::{TextRange, TextSize};
|
||||
use test_utils::{extract_annotations, RangeOrOffset};
|
||||
use test_utils::extract_annotations;
|
||||
|
||||
use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
|
||||
|
||||
@ -27,10 +27,7 @@ pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
|
||||
let change_fixture = ChangeFixture::parse(ra_fixture);
|
||||
host.db.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(host.analysis(), FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
@ -40,10 +37,7 @@ pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
|
||||
let change_fixture = ChangeFixture::parse(ra_fixture);
|
||||
host.db.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let range = match range_or_offset {
|
||||
RangeOrOffset::Range(it) => it,
|
||||
RangeOrOffset::Offset(_) => panic!(),
|
||||
};
|
||||
let range = range_or_offset.expect_range();
|
||||
(host.analysis(), FileRange { file_id, range })
|
||||
}
|
||||
|
||||
@ -53,10 +47,7 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil
|
||||
let change_fixture = ChangeFixture::parse(ra_fixture);
|
||||
host.db.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
|
||||
let annotations = change_fixture
|
||||
.files
|
||||
|
@ -48,91 +48,92 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
|
||||
cov_mark::hit!(no_keyword_completion_in_record_lit);
|
||||
return;
|
||||
}
|
||||
let mut add_keyword = |kw, snippet| add_keyword(ctx, acc, kw, snippet);
|
||||
|
||||
let expects_assoc_item = ctx.expects_assoc_item();
|
||||
let has_block_expr_parent = ctx.has_block_expr_parent();
|
||||
let expects_item = ctx.expects_item();
|
||||
|
||||
if ctx.has_impl_or_trait_prev_sibling() {
|
||||
add_keyword(ctx, acc, "where", "where ");
|
||||
// FIXME this also incorrectly shows up after a complete trait/impl
|
||||
add_keyword("where", "where ");
|
||||
return;
|
||||
}
|
||||
if ctx.previous_token_is(T![unsafe]) {
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "fn", "fn $1($2) {\n $0\n}")
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent {
|
||||
add_keyword("fn", "fn $1($2) {\n $0\n}")
|
||||
}
|
||||
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "trait", "trait $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}");
|
||||
add_keyword("trait", "trait $1 {\n $0\n}");
|
||||
add_keyword("impl", "impl $1 {\n $0\n}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "fn", "fn $1($2) {\n $0\n}");
|
||||
|
||||
if expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_record_field() {
|
||||
add_keyword("pub(crate)", "pub(crate) ");
|
||||
add_keyword("pub", "pub ");
|
||||
}
|
||||
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm {
|
||||
add_keyword("unsafe", "unsafe ");
|
||||
}
|
||||
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent {
|
||||
add_keyword("fn", "fn $1($2) {\n $0\n}");
|
||||
add_keyword("const", "const $0");
|
||||
add_keyword("type", "type $0");
|
||||
}
|
||||
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "use", "use ");
|
||||
add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "trait", "trait $1 {\n $0\n}");
|
||||
add_keyword("use", "use $0");
|
||||
add_keyword("impl", "impl $1 {\n $0\n}");
|
||||
add_keyword("trait", "trait $1 {\n $0\n}");
|
||||
add_keyword("static", "static $0");
|
||||
add_keyword("extern", "extern $0");
|
||||
add_keyword("mod", "mod $0");
|
||||
}
|
||||
|
||||
if expects_item {
|
||||
add_keyword(ctx, acc, "enum", "enum $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "struct", "struct $0");
|
||||
add_keyword(ctx, acc, "union", "union $1 {\n $0\n}");
|
||||
add_keyword("enum", "enum $1 {\n $0\n}");
|
||||
add_keyword("struct", "struct $0");
|
||||
add_keyword("union", "union $1 {\n $0\n}");
|
||||
}
|
||||
|
||||
if ctx.is_expr {
|
||||
add_keyword(ctx, acc, "match", "match $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "while", "while $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "while let", "while let $1 = $2 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "loop", "loop {\n $0\n}");
|
||||
add_keyword(ctx, acc, "if", "if $1 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "if let", "if let $1 = $2 {\n $0\n}");
|
||||
add_keyword(ctx, acc, "for", "for $1 in $2 {\n $0\n}");
|
||||
if ctx.expects_expression() {
|
||||
add_keyword("match", "match $1 {\n $0\n}");
|
||||
add_keyword("while", "while $1 {\n $0\n}");
|
||||
add_keyword("while let", "while let $1 = $2 {\n $0\n}");
|
||||
add_keyword("loop", "loop {\n $0\n}");
|
||||
add_keyword("if", "if $1 {\n $0\n}");
|
||||
add_keyword("if let", "if let $1 = $2 {\n $0\n}");
|
||||
add_keyword("for", "for $1 in $2 {\n $0\n}");
|
||||
}
|
||||
|
||||
if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "let", "let ");
|
||||
add_keyword("let", "let ");
|
||||
}
|
||||
|
||||
if ctx.after_if {
|
||||
add_keyword(ctx, acc, "else", "else {\n $0\n}");
|
||||
add_keyword(ctx, acc, "else if", "else if $1 {\n $0\n}");
|
||||
}
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "mod", "mod $0");
|
||||
if ctx.after_if() {
|
||||
add_keyword("else", "else {\n $0\n}");
|
||||
add_keyword("else if", "else if $1 {\n $0\n}");
|
||||
}
|
||||
|
||||
if ctx.expects_ident_pat_or_ref_expr() {
|
||||
add_keyword(ctx, acc, "mut", "mut ");
|
||||
}
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "const", "const ");
|
||||
add_keyword(ctx, acc, "type", "type ");
|
||||
}
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "static", "static ");
|
||||
};
|
||||
if expects_item || has_block_expr_parent {
|
||||
add_keyword(ctx, acc, "extern", "extern ");
|
||||
}
|
||||
if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm {
|
||||
add_keyword(ctx, acc, "unsafe", "unsafe ");
|
||||
add_keyword("mut", "mut ");
|
||||
}
|
||||
|
||||
if ctx.in_loop_body {
|
||||
if ctx.can_be_stmt {
|
||||
add_keyword(ctx, acc, "continue", "continue;");
|
||||
add_keyword(ctx, acc, "break", "break;");
|
||||
add_keyword("continue", "continue;");
|
||||
add_keyword("break", "break;");
|
||||
} else {
|
||||
add_keyword(ctx, acc, "continue", "continue");
|
||||
add_keyword(ctx, acc, "break", "break");
|
||||
add_keyword("continue", "continue");
|
||||
add_keyword("break", "break");
|
||||
}
|
||||
}
|
||||
if expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_record_field() {
|
||||
add_keyword(ctx, acc, "pub(crate)", "pub(crate) ");
|
||||
add_keyword(ctx, acc, "pub", "pub ");
|
||||
}
|
||||
|
||||
if !ctx.is_trivial_path {
|
||||
return;
|
||||
@ -143,8 +144,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
|
||||
};
|
||||
|
||||
add_keyword(
|
||||
ctx,
|
||||
acc,
|
||||
"return",
|
||||
match (ctx.can_be_stmt, fn_def.ret_type().is_some()) {
|
||||
(true, true) => "return $0;",
|
||||
@ -161,15 +160,12 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet
|
||||
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
let tmp;
|
||||
let snippet = if snippet.ends_with('}') && ctx.incomplete_let {
|
||||
if snippet.ends_with('}') && ctx.incomplete_let {
|
||||
cov_mark::hit!(let_semi);
|
||||
tmp = format!("{};", snippet);
|
||||
&tmp
|
||||
item.insert_snippet(cap, format!("{};", snippet));
|
||||
} else {
|
||||
snippet
|
||||
};
|
||||
item.insert_snippet(cap, snippet);
|
||||
item.insert_snippet(cap, snippet);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
item.insert_text(if snippet.contains('$') { kw } else { snippet });
|
||||
@ -232,21 +228,21 @@ mod tests {
|
||||
check(
|
||||
r"m$0",
|
||||
expect![[r#"
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw static
|
||||
kw extern
|
||||
kw mod
|
||||
kw enum
|
||||
kw struct
|
||||
kw union
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
@ -256,10 +252,16 @@ mod tests {
|
||||
check(
|
||||
r"fn quux() { $0 }",
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw static
|
||||
kw extern
|
||||
kw mod
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
@ -268,12 +270,6 @@ mod tests {
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
@ -284,10 +280,16 @@ mod tests {
|
||||
check(
|
||||
r"fn quux() { if true { $0 } }",
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw static
|
||||
kw extern
|
||||
kw mod
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
@ -296,12 +298,6 @@ mod tests {
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
@ -312,10 +308,16 @@ mod tests {
|
||||
check(
|
||||
r#"fn quux() { if true { () } $0 }"#,
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw static
|
||||
kw extern
|
||||
kw mod
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
@ -326,12 +328,6 @@ mod tests {
|
||||
kw let
|
||||
kw else
|
||||
kw else if
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
@ -353,6 +349,7 @@ fn quux() -> i32 {
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
@ -360,7 +357,6 @@ fn quux() -> i32 {
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
@ -371,10 +367,10 @@ fn quux() -> i32 {
|
||||
check(
|
||||
r"trait My { $0 }",
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw unsafe
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
@ -384,12 +380,12 @@ fn quux() -> i32 {
|
||||
check(
|
||||
r"impl My { $0 }",
|
||||
expect![[r#"
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw unsafe
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
@ -399,12 +395,12 @@ fn quux() -> i32 {
|
||||
check(
|
||||
r"impl My { #[foo] $0 }",
|
||||
expect![[r#"
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw unsafe
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
@ -414,10 +410,16 @@ fn quux() -> i32 {
|
||||
check(
|
||||
r"fn my() { loop { $0 } }",
|
||||
expect![[r#"
|
||||
kw unsafe
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw static
|
||||
kw extern
|
||||
kw mod
|
||||
kw match
|
||||
kw while
|
||||
kw while let
|
||||
@ -426,12 +428,6 @@ fn quux() -> i32 {
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw continue
|
||||
kw break
|
||||
kw return
|
||||
|
@ -17,8 +17,8 @@ use text_edit::Indel;
|
||||
|
||||
use crate::{
|
||||
patterns::{
|
||||
determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block,
|
||||
is_in_loop_body, is_match_arm, previous_token, ImmediateLocation,
|
||||
determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block,
|
||||
is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling,
|
||||
},
|
||||
CompletionConfig,
|
||||
};
|
||||
@ -29,12 +29,6 @@ pub(crate) enum PatternRefutability {
|
||||
Irrefutable,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum PrevSibling {
|
||||
Trait,
|
||||
Impl,
|
||||
}
|
||||
|
||||
/// `CompletionContext` is created early during completion to figure out, where
|
||||
/// exactly is the cursor, syntax-wise.
|
||||
#[derive(Debug)]
|
||||
@ -76,6 +70,7 @@ pub(crate) struct CompletionContext<'a> {
|
||||
pub(super) is_param: bool,
|
||||
|
||||
pub(super) completion_location: Option<ImmediateLocation>,
|
||||
pub(super) prev_sibling: Option<ImmediatePrevSibling>,
|
||||
|
||||
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
|
||||
pub(super) active_parameter: Option<ActiveParameter>,
|
||||
@ -83,7 +78,6 @@ pub(crate) struct CompletionContext<'a> {
|
||||
pub(super) is_trivial_path: bool,
|
||||
/// If not a trivial path, the prefix (qualifier).
|
||||
pub(super) path_qual: Option<ast::Path>,
|
||||
pub(super) after_if: bool,
|
||||
/// `true` if we are a statement or a last expr in the block.
|
||||
pub(super) can_be_stmt: bool,
|
||||
/// `true` if we expect an expression at the cursor position.
|
||||
@ -107,7 +101,6 @@ pub(crate) struct CompletionContext<'a> {
|
||||
|
||||
// keyword patterns
|
||||
pub(super) previous_token: Option<SyntaxToken>,
|
||||
pub(super) prev_sibling: Option<PrevSibling>,
|
||||
pub(super) in_loop_body: bool,
|
||||
pub(super) is_match_arm: bool,
|
||||
pub(super) incomplete_let: bool,
|
||||
@ -173,7 +166,6 @@ impl<'a> CompletionContext<'a> {
|
||||
is_pat_or_const: None,
|
||||
is_trivial_path: false,
|
||||
path_qual: None,
|
||||
after_if: false,
|
||||
can_be_stmt: false,
|
||||
is_expr: false,
|
||||
is_new_item: false,
|
||||
@ -288,6 +280,10 @@ impl<'a> CompletionContext<'a> {
|
||||
matches!(self.completion_location, Some(ImmediateLocation::ItemList))
|
||||
}
|
||||
|
||||
pub(crate) fn expects_expression(&self) -> bool {
|
||||
self.is_expr
|
||||
}
|
||||
|
||||
pub(crate) fn has_block_expr_parent(&self) -> bool {
|
||||
matches!(self.completion_location, Some(ImmediateLocation::BlockExpr))
|
||||
}
|
||||
@ -304,7 +300,14 @@ impl<'a> CompletionContext<'a> {
|
||||
}
|
||||
|
||||
pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
|
||||
self.prev_sibling.is_some()
|
||||
matches!(
|
||||
self.prev_sibling,
|
||||
Some(ImmediatePrevSibling::ImplDefType) | Some(ImmediatePrevSibling::TraitDefName)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn after_if(&self) -> bool {
|
||||
matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr))
|
||||
}
|
||||
|
||||
pub(crate) fn is_path_disallowed(&self) -> bool {
|
||||
@ -316,15 +319,10 @@ impl<'a> CompletionContext<'a> {
|
||||
|
||||
fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
|
||||
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
|
||||
let syntax_element = NodeOrToken::Token(fake_ident_token.clone());
|
||||
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
||||
self.previous_token = previous_token(syntax_element.clone());
|
||||
self.in_loop_body = is_in_loop_body(syntax_element.clone());
|
||||
self.is_match_arm = is_match_arm(syntax_element.clone());
|
||||
if has_prev_sibling(syntax_element.clone(), IMPL) {
|
||||
self.prev_sibling = Some(PrevSibling::Impl)
|
||||
} else if has_prev_sibling(syntax_element.clone(), TRAIT) {
|
||||
self.prev_sibling = Some(PrevSibling::Trait)
|
||||
}
|
||||
|
||||
self.mod_declaration_under_caret =
|
||||
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
|
||||
@ -338,8 +336,6 @@ impl<'a> CompletionContext<'a> {
|
||||
let fn_is_prev = self.previous_token_is(T![fn]);
|
||||
let for_is_prev2 = for_is_prev2(syntax_element.clone());
|
||||
self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2;
|
||||
|
||||
self.completion_location = determine_location(fake_ident_token);
|
||||
}
|
||||
|
||||
fn fill_impl_def(&mut self) {
|
||||
@ -465,6 +461,8 @@ impl<'a> CompletionContext<'a> {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
self.completion_location = determine_location(&name_like);
|
||||
self.prev_sibling = determine_prev_sibling(&name_like);
|
||||
match name_like {
|
||||
ast::NameLike::Lifetime(lifetime) => {
|
||||
self.classify_lifetime(original_file, lifetime, offset);
|
||||
@ -653,17 +651,6 @@ impl<'a> CompletionContext<'a> {
|
||||
})
|
||||
.unwrap_or(false);
|
||||
self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some();
|
||||
|
||||
if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
|
||||
if let Some(if_expr) =
|
||||
self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
|
||||
{
|
||||
if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start()
|
||||
{
|
||||
self.after_if = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
|
||||
|
@ -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(tok: SyntaxToken) -> 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 tok.parent().and_then(ast::NameLike::cast)? {
|
||||
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().filter(|_| it.semicolon_token().is_none())?.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,56 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
|
||||
Some(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_location(code: &str, loc: ImmediateLocation) {
|
||||
check_pattern_is_applicable(code, |e| {
|
||||
assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc));
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_trait_parent() {
|
||||
check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_use_parent() {
|
||||
check_location(r"use f$0", ImmediateLocation::Use);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_impl_parent() {
|
||||
check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
|
||||
}
|
||||
#[test]
|
||||
fn test_has_field_list_parent() {
|
||||
check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
|
||||
check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_block_expr_parent() {
|
||||
check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_ident_pat_parent() {
|
||||
check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_ref_expr_parent() {
|
||||
check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_item_list_or_source_file_parent() {
|
||||
check_location(r"i$0", ImmediateLocation::ItemList);
|
||||
check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
|
||||
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)
|
||||
}
|
||||
|
||||
pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
|
||||
@ -191,14 +198,6 @@ 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()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_impl_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.ancestors()
|
||||
@ -247,3 +246,111 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<Syntax
|
||||
non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
|
||||
check_pattern_is_applicable(code, |e| {
|
||||
let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
|
||||
assert_eq!(determine_location(name), loc.into());
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
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_trait_loc() {
|
||||
check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
|
||||
check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
|
||||
check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
|
||||
check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
|
||||
check_location(r"trait A$0 {}", None);
|
||||
check_location(r"trait A { fn f$0 }", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_impl_loc() {
|
||||
check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
|
||||
check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
|
||||
check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
|
||||
check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
|
||||
check_location(r"impl A$0 {}", None);
|
||||
check_location(r"impl A { fn f$0 }", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_use_loc() {
|
||||
check_location(r"use f$0", ImmediateLocation::Use);
|
||||
check_location(r"use f$0;", ImmediateLocation::Use);
|
||||
check_location(r"use f::{f$0}", None);
|
||||
check_location(r"use {f$0}", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_field_loc() {
|
||||
check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
|
||||
check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
|
||||
check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_expr_loc() {
|
||||
check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
|
||||
check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ident_pat_loc() {
|
||||
check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
|
||||
check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_expr_loc() {
|
||||
check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_item_list_loc() {
|
||||
check_location(r"i$0", ImmediateLocation::ItemList);
|
||||
check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
|
||||
check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
|
||||
check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
|
||||
check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
|
||||
check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
|
||||
check_location(r"mod foo$0 {}", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_impl_prev_sibling() {
|
||||
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_trait_prev_sibling() {
|
||||
check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
|
||||
check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_expr_prev_sibling() {
|
||||
check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
|
||||
check_prev_sibling(r"fn foo() { if true {}; w$0", None);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use ide_db::{
|
||||
use itertools::Itertools;
|
||||
use stdx::{format_to, trim_indent};
|
||||
use syntax::{AstNode, NodeOrToken, SyntaxElement};
|
||||
use test_utils::{assert_eq_text, RangeOrOffset};
|
||||
use test_utils::assert_eq_text;
|
||||
|
||||
use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
|
||||
|
||||
@ -36,10 +36,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
||||
let mut database = RootDatabase::default();
|
||||
database.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(database, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
@ -52,10 +49,11 @@ pub(crate) fn do_completion_with_config(
|
||||
code: &str,
|
||||
kind: CompletionKind,
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut kind_completions: Vec<CompletionItem> =
|
||||
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
|
||||
kind_completions
|
||||
get_all_items(config, code)
|
||||
.into_iter()
|
||||
.filter(|c| c.completion_kind == kind)
|
||||
.sorted_by(|l, r| l.label().cmp(r.label()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use base_db::{fixture::ChangeFixture, FilePosition};
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::RangeOrOffset;
|
||||
|
||||
use crate::RootDatabase;
|
||||
|
||||
@ -10,10 +9,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
||||
let mut database = RootDatabase::default();
|
||||
database.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(database, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use base_db::{fixture::ChangeFixture, FilePosition};
|
||||
use expect_test::{expect, Expect};
|
||||
use hir::Semantics;
|
||||
use syntax::ast::{self, AstNode};
|
||||
use test_utils::RangeOrOffset;
|
||||
|
||||
use crate::RootDatabase;
|
||||
|
||||
@ -12,10 +11,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
||||
let mut database = RootDatabase::default();
|
||||
database.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(database, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,21 @@ pub enum RangeOrOffset {
|
||||
Offset(TextSize),
|
||||
}
|
||||
|
||||
impl RangeOrOffset {
|
||||
pub fn expect_offset(self) -> TextSize {
|
||||
match self {
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
RangeOrOffset::Range(_) => panic!("expected an offset but got a range instead"),
|
||||
}
|
||||
}
|
||||
pub fn expect_range(self) -> TextRange {
|
||||
match self {
|
||||
RangeOrOffset::Range(it) => it,
|
||||
RangeOrOffset::Offset(_) => panic!("expected a range but got an offset"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeOrOffset> for TextRange {
|
||||
fn from(selection: RangeOrOffset) -> Self {
|
||||
match selection {
|
||||
|
Loading…
Reference in New Issue
Block a user