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:
bors[bot] 2021-05-28 21:21:37 +00:00 committed by GitHub
commit 7869b01b70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 330 additions and 250 deletions

View File

@ -34,19 +34,13 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
fn with_position(ra_fixture: &str) -> (Self, FilePosition) { fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!("Expected a cursor position, got a range instead"),
RangeOrOffset::Offset(it) => it,
};
(db, FilePosition { file_id, offset }) (db, FilePosition { file_id, offset })
} }
fn with_range(ra_fixture: &str) -> (Self, FileRange) { fn with_range(ra_fixture: &str) -> (Self, FileRange) {
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
let range = match range_or_offset { let range = range_or_offset.expect_range();
RangeOrOffset::Range(it) => it,
RangeOrOffset::Offset(_) => panic!("Expected a cursor range, got a position instead"),
};
(db, FileRange { file_id, range }) (db, FileRange { file_id, range })
} }

View File

@ -1,7 +1,7 @@
//! Utilities for creating `Analysis` instances for tests. //! Utilities for creating `Analysis` instances for tests.
use ide_db::base_db::fixture::ChangeFixture; use ide_db::base_db::fixture::ChangeFixture;
use syntax::{TextRange, TextSize}; use syntax::{TextRange, TextSize};
use test_utils::{extract_annotations, RangeOrOffset}; use test_utils::extract_annotations;
use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; 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); let change_fixture = ChangeFixture::parse(ra_fixture);
host.db.apply_change(change_fixture.change); host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
(host.analysis(), FilePosition { file_id, 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); let change_fixture = ChangeFixture::parse(ra_fixture);
host.db.apply_change(change_fixture.change); host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let range = match range_or_offset { let range = range_or_offset.expect_range();
RangeOrOffset::Range(it) => it,
RangeOrOffset::Offset(_) => panic!(),
};
(host.analysis(), FileRange { file_id, 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); let change_fixture = ChangeFixture::parse(ra_fixture);
host.db.apply_change(change_fixture.change); host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
let annotations = change_fixture let annotations = change_fixture
.files .files

View File

@ -48,91 +48,92 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
cov_mark::hit!(no_keyword_completion_in_record_lit); cov_mark::hit!(no_keyword_completion_in_record_lit);
return; return;
} }
let mut add_keyword = |kw, snippet| add_keyword(ctx, acc, kw, snippet);
let expects_assoc_item = ctx.expects_assoc_item(); let expects_assoc_item = ctx.expects_assoc_item();
let has_block_expr_parent = ctx.has_block_expr_parent(); let has_block_expr_parent = ctx.has_block_expr_parent();
let expects_item = ctx.expects_item(); let expects_item = ctx.expects_item();
if ctx.has_impl_or_trait_prev_sibling() { 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; return;
} }
if ctx.previous_token_is(T![unsafe]) { if ctx.previous_token_is(T![unsafe]) {
if expects_item || has_block_expr_parent { if expects_item || expects_assoc_item || has_block_expr_parent {
add_keyword(ctx, acc, "fn", "fn $1($2) {\n $0\n}") add_keyword("fn", "fn $1($2) {\n $0\n}")
} }
if expects_item || has_block_expr_parent { if expects_item || has_block_expr_parent {
add_keyword(ctx, acc, "trait", "trait $1 {\n $0\n}"); add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}"); add_keyword("impl", "impl $1 {\n $0\n}");
} }
return; 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 { if expects_item || has_block_expr_parent {
add_keyword(ctx, acc, "use", "use "); add_keyword("use", "use $0");
add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}"); add_keyword("impl", "impl $1 {\n $0\n}");
add_keyword(ctx, acc, "trait", "trait $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 { if expects_item {
add_keyword(ctx, acc, "enum", "enum $1 {\n $0\n}"); add_keyword("enum", "enum $1 {\n $0\n}");
add_keyword(ctx, acc, "struct", "struct $0"); add_keyword("struct", "struct $0");
add_keyword(ctx, acc, "union", "union $1 {\n $0\n}"); add_keyword("union", "union $1 {\n $0\n}");
} }
if ctx.is_expr { if ctx.expects_expression() {
add_keyword(ctx, acc, "match", "match $1 {\n $0\n}"); add_keyword("match", "match $1 {\n $0\n}");
add_keyword(ctx, acc, "while", "while $1 {\n $0\n}"); add_keyword("while", "while $1 {\n $0\n}");
add_keyword(ctx, acc, "while let", "while let $1 = $2 {\n $0\n}"); add_keyword("while let", "while let $1 = $2 {\n $0\n}");
add_keyword(ctx, acc, "loop", "loop {\n $0\n}"); add_keyword("loop", "loop {\n $0\n}");
add_keyword(ctx, acc, "if", "if $1 {\n $0\n}"); add_keyword("if", "if $1 {\n $0\n}");
add_keyword(ctx, acc, "if let", "if let $1 = $2 {\n $0\n}"); add_keyword("if let", "if let $1 = $2 {\n $0\n}");
add_keyword(ctx, acc, "for", "for $1 in $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 { 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 { if ctx.after_if() {
add_keyword(ctx, acc, "else", "else {\n $0\n}"); add_keyword("else", "else {\n $0\n}");
add_keyword(ctx, acc, "else if", "else if $1 {\n $0\n}"); add_keyword("else if", "else if $1 {\n $0\n}");
}
if expects_item || has_block_expr_parent {
add_keyword(ctx, acc, "mod", "mod $0");
} }
if ctx.expects_ident_pat_or_ref_expr() { if ctx.expects_ident_pat_or_ref_expr() {
add_keyword(ctx, acc, "mut", "mut "); add_keyword("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 ");
} }
if ctx.in_loop_body { if ctx.in_loop_body {
if ctx.can_be_stmt { if ctx.can_be_stmt {
add_keyword(ctx, acc, "continue", "continue;"); add_keyword("continue", "continue;");
add_keyword(ctx, acc, "break", "break;"); add_keyword("break", "break;");
} else { } else {
add_keyword(ctx, acc, "continue", "continue"); add_keyword("continue", "continue");
add_keyword(ctx, acc, "break", "break"); 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 { if !ctx.is_trivial_path {
return; return;
@ -143,8 +144,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
}; };
add_keyword( add_keyword(
ctx,
acc,
"return", "return",
match (ctx.can_be_stmt, fn_def.ret_type().is_some()) { match (ctx.can_be_stmt, fn_def.ret_type().is_some()) {
(true, true) => "return $0;", (true, true) => "return $0;",
@ -161,16 +160,13 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => { Some(cap) => {
let tmp; if snippet.ends_with('}') && ctx.incomplete_let {
let snippet = if snippet.ends_with('}') && ctx.incomplete_let {
cov_mark::hit!(let_semi); cov_mark::hit!(let_semi);
tmp = format!("{};", snippet); item.insert_snippet(cap, format!("{};", snippet));
&tmp
} else { } else {
snippet
};
item.insert_snippet(cap, snippet); item.insert_snippet(cap, snippet);
} }
}
None => { None => {
item.insert_text(if snippet.contains('$') { kw } else { snippet }); item.insert_text(if snippet.contains('$') { kw } else { snippet });
} }
@ -232,21 +228,21 @@ mod tests {
check( check(
r"m$0", r"m$0",
expect![[r#" expect![[r#"
kw pub(crate)
kw pub
kw unsafe
kw fn kw fn
kw const
kw type
kw use kw use
kw impl kw impl
kw trait kw trait
kw static
kw extern
kw mod
kw enum kw enum
kw struct kw struct
kw union 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( check(
r"fn quux() { $0 }", r"fn quux() { $0 }",
expect![[r#" expect![[r#"
kw unsafe
kw fn kw fn
kw const
kw type
kw use kw use
kw impl kw impl
kw trait kw trait
kw static
kw extern
kw mod
kw match kw match
kw while kw while
kw while let kw while let
@ -268,12 +270,6 @@ mod tests {
kw if let kw if let
kw for kw for
kw let kw let
kw mod
kw const
kw type
kw static
kw extern
kw unsafe
kw return kw return
"#]], "#]],
); );
@ -284,10 +280,16 @@ mod tests {
check( check(
r"fn quux() { if true { $0 } }", r"fn quux() { if true { $0 } }",
expect![[r#" expect![[r#"
kw unsafe
kw fn kw fn
kw const
kw type
kw use kw use
kw impl kw impl
kw trait kw trait
kw static
kw extern
kw mod
kw match kw match
kw while kw while
kw while let kw while let
@ -296,12 +298,6 @@ mod tests {
kw if let kw if let
kw for kw for
kw let kw let
kw mod
kw const
kw type
kw static
kw extern
kw unsafe
kw return kw return
"#]], "#]],
); );
@ -312,10 +308,16 @@ mod tests {
check( check(
r#"fn quux() { if true { () } $0 }"#, r#"fn quux() { if true { () } $0 }"#,
expect![[r#" expect![[r#"
kw unsafe
kw fn kw fn
kw const
kw type
kw use kw use
kw impl kw impl
kw trait kw trait
kw static
kw extern
kw mod
kw match kw match
kw while kw while
kw while let kw while let
@ -326,12 +328,6 @@ mod tests {
kw let kw let
kw else kw else
kw else if kw else if
kw mod
kw const
kw type
kw static
kw extern
kw unsafe
kw return kw return
"#]], "#]],
); );
@ -353,6 +349,7 @@ fn quux() -> i32 {
} }
"#, "#,
expect![[r#" expect![[r#"
kw unsafe
kw match kw match
kw while kw while
kw while let kw while let
@ -360,7 +357,6 @@ fn quux() -> i32 {
kw if kw if
kw if let kw if let
kw for kw for
kw unsafe
kw return kw return
"#]], "#]],
); );
@ -371,10 +367,10 @@ fn quux() -> i32 {
check( check(
r"trait My { $0 }", r"trait My { $0 }",
expect![[r#" expect![[r#"
kw unsafe
kw fn kw fn
kw const kw const
kw type kw type
kw unsafe
"#]], "#]],
); );
} }
@ -384,12 +380,12 @@ fn quux() -> i32 {
check( check(
r"impl My { $0 }", r"impl My { $0 }",
expect![[r#" expect![[r#"
kw pub(crate)
kw pub
kw unsafe
kw fn kw fn
kw const kw const
kw type kw type
kw unsafe
kw pub(crate)
kw pub
"#]], "#]],
); );
} }
@ -399,12 +395,12 @@ fn quux() -> i32 {
check( check(
r"impl My { #[foo] $0 }", r"impl My { #[foo] $0 }",
expect![[r#" expect![[r#"
kw pub(crate)
kw pub
kw unsafe
kw fn kw fn
kw const kw const
kw type kw type
kw unsafe
kw pub(crate)
kw pub
"#]], "#]],
); );
} }
@ -414,10 +410,16 @@ fn quux() -> i32 {
check( check(
r"fn my() { loop { $0 } }", r"fn my() { loop { $0 } }",
expect![[r#" expect![[r#"
kw unsafe
kw fn kw fn
kw const
kw type
kw use kw use
kw impl kw impl
kw trait kw trait
kw static
kw extern
kw mod
kw match kw match
kw while kw while
kw while let kw while let
@ -426,12 +428,6 @@ fn quux() -> i32 {
kw if let kw if let
kw for kw for
kw let kw let
kw mod
kw const
kw type
kw static
kw extern
kw unsafe
kw continue kw continue
kw break kw break
kw return kw return

View File

@ -17,8 +17,8 @@ use text_edit::Indel;
use crate::{ use crate::{
patterns::{ patterns::{
determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block, determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block,
is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling,
}, },
CompletionConfig, CompletionConfig,
}; };
@ -29,12 +29,6 @@ pub(crate) enum PatternRefutability {
Irrefutable, Irrefutable,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum PrevSibling {
Trait,
Impl,
}
/// `CompletionContext` is created early during completion to figure out, where /// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise. /// exactly is the cursor, syntax-wise.
#[derive(Debug)] #[derive(Debug)]
@ -76,6 +70,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) is_param: bool, pub(super) is_param: bool,
pub(super) completion_location: Option<ImmediateLocation>, pub(super) completion_location: Option<ImmediateLocation>,
pub(super) prev_sibling: Option<ImmediatePrevSibling>,
/// FIXME: `ActiveParameter` is string-based, which is very very wrong /// FIXME: `ActiveParameter` is string-based, which is very very wrong
pub(super) active_parameter: Option<ActiveParameter>, pub(super) active_parameter: Option<ActiveParameter>,
@ -83,7 +78,6 @@ pub(crate) struct CompletionContext<'a> {
pub(super) is_trivial_path: bool, pub(super) is_trivial_path: bool,
/// If not a trivial path, the prefix (qualifier). /// If not a trivial path, the prefix (qualifier).
pub(super) path_qual: Option<ast::Path>, 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. /// `true` if we are a statement or a last expr in the block.
pub(super) can_be_stmt: bool, pub(super) can_be_stmt: bool,
/// `true` if we expect an expression at the cursor position. /// `true` if we expect an expression at the cursor position.
@ -107,7 +101,6 @@ pub(crate) struct CompletionContext<'a> {
// keyword patterns // keyword patterns
pub(super) previous_token: Option<SyntaxToken>, pub(super) previous_token: Option<SyntaxToken>,
pub(super) prev_sibling: Option<PrevSibling>,
pub(super) in_loop_body: bool, pub(super) in_loop_body: bool,
pub(super) is_match_arm: bool, pub(super) is_match_arm: bool,
pub(super) incomplete_let: bool, pub(super) incomplete_let: bool,
@ -173,7 +166,6 @@ impl<'a> CompletionContext<'a> {
is_pat_or_const: None, is_pat_or_const: None,
is_trivial_path: false, is_trivial_path: false,
path_qual: None, path_qual: None,
after_if: false,
can_be_stmt: false, can_be_stmt: false,
is_expr: false, is_expr: false,
is_new_item: false, is_new_item: false,
@ -288,6 +280,10 @@ impl<'a> CompletionContext<'a> {
matches!(self.completion_location, Some(ImmediateLocation::ItemList)) 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 { pub(crate) fn has_block_expr_parent(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) 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 { 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 { 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) { 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 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.previous_token = previous_token(syntax_element.clone());
self.in_loop_body = is_in_loop_body(syntax_element.clone()); self.in_loop_body = is_in_loop_body(syntax_element.clone());
self.is_match_arm = is_match_arm(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 = self.mod_declaration_under_caret =
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) 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 fn_is_prev = self.previous_token_is(T![fn]);
let for_is_prev2 = for_is_prev2(syntax_element.clone()); 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.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) { fn fill_impl_def(&mut self) {
@ -465,6 +461,8 @@ impl<'a> CompletionContext<'a> {
Some(it) => it, Some(it) => it,
None => return, None => return,
}; };
self.completion_location = determine_location(&name_like);
self.prev_sibling = determine_prev_sibling(&name_like);
match name_like { match name_like {
ast::NameLike::Lifetime(lifetime) => { ast::NameLike::Lifetime(lifetime) => {
self.classify_lifetime(original_file, lifetime, offset); self.classify_lifetime(original_file, lifetime, offset);
@ -653,17 +651,6 @@ impl<'a> CompletionContext<'a> {
}) })
.unwrap_or(false); .unwrap_or(false);
self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); 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()) { if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {

View File

@ -4,12 +4,19 @@ use syntax::{
algo::non_trivia_sibling, algo::non_trivia_sibling,
ast::{self, LoopBodyOwner}, ast::{self, LoopBodyOwner},
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
SyntaxKind::{self, *}, SyntaxKind::*,
SyntaxNode, SyntaxToken, T, SyntaxNode, SyntaxToken, T,
}; };
#[cfg(test)] #[cfg(test)]
use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 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 /// Direct parent container of the cursor position
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
ItemList, ItemList,
} }
pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
// First walk the element we are completing up to its highest node that has the same text range let node = maximize_name_ref(name_like)?;
// as the element so that we can check in what context it immediately lies. We only do this for let node = match node.parent().and_then(ast::MacroCall::cast) {
// NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. // When a path is being typed after the name of a trait/type of an impl it is being
// We only wanna do this if the NameRef is the last segment of the path. // parsed as a macro, so when the trait/impl has a block following it an we are between the
let node = match tok.parent().and_then(ast::NameLike::cast)? { // name and block the macro will attach the block to itself so maximizing fails to take
ast::NameLike::NameRef(name_ref) => { // that into account
if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { // FIXME path expr and statement have a similar problem with attrs
let p = segment.parent_path(); Some(call)
if p.parent_path().is_none() { if call.excl_token().is_none()
p.syntax() && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
.ancestors() && call.semicolon_token().is_none() =>
.take_while(|it| it.text_range() == p.syntax().text_range()) {
.last()? call.syntax().clone()
} else {
return None;
} }
} else { _ => node,
return None;
}
}
it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
}; };
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() { let parent = match node.parent() {
Some(parent) => match ast::MacroCall::cast(parent.clone()) { 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. // 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 // 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 // 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. // 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) Some(call)
if call.excl_token().is_none() if call.excl_token().is_none()
&& call.token_tree().is_none() && call.token_tree().is_none()
@ -90,56 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
Some(res) Some(res)
} }
#[cfg(test)] fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
fn check_location(code: &str, loc: ImmediateLocation) { // First walk the element we are completing up to its highest node that has the same text range
check_pattern_is_applicable(code, |e| { // as the element so that we can check in what context it immediately lies. We only do this for
assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc)); // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
true // 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) {
#[test] let p = segment.parent_path();
fn test_has_trait_parent() { if p.parent_path().is_none() {
check_location(r"trait A { f$0 }", ImmediateLocation::Trait); p.syntax()
} .ancestors()
.take_while(|it| it.text_range() == p.syntax().text_range())
#[test] .last()?
fn test_has_use_parent() { } else {
check_location(r"use f$0", ImmediateLocation::Use); return None;
} }
} else {
#[test] return None;
fn test_has_impl_parent() { }
check_location(r"impl A { f$0 }", ImmediateLocation::Impl); }
} it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
#[test] };
fn test_has_field_list_parent() { Some(node)
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);
} }
pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 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); 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 { pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
element element
.ancestors() .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) 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);
}
}

View File

@ -12,7 +12,7 @@ use ide_db::{
use itertools::Itertools; use itertools::Itertools;
use stdx::{format_to, trim_indent}; use stdx::{format_to, trim_indent};
use syntax::{AstNode, NodeOrToken, SyntaxElement}; use syntax::{AstNode, NodeOrToken, SyntaxElement};
use test_utils::{assert_eq_text, RangeOrOffset}; use test_utils::assert_eq_text;
use crate::{item::CompletionKind, CompletionConfig, CompletionItem}; use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
@ -36,10 +36,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
let mut database = RootDatabase::default(); let mut database = RootDatabase::default();
database.apply_change(change_fixture.change); database.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
(database, FilePosition { file_id, offset }) (database, FilePosition { file_id, offset })
} }
@ -52,10 +49,11 @@ pub(crate) fn do_completion_with_config(
code: &str, code: &str,
kind: CompletionKind, kind: CompletionKind,
) -> Vec<CompletionItem> { ) -> Vec<CompletionItem> {
let mut kind_completions: Vec<CompletionItem> = get_all_items(config, code)
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect(); .into_iter()
kind_completions.sort_by(|l, r| l.label().cmp(r.label())); .filter(|c| c.completion_kind == kind)
kind_completions .sorted_by(|l, r| l.label().cmp(r.label()))
.collect()
} }
pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {

View File

@ -1,6 +1,5 @@
use base_db::{fixture::ChangeFixture, FilePosition}; use base_db::{fixture::ChangeFixture, FilePosition};
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use test_utils::RangeOrOffset;
use crate::RootDatabase; use crate::RootDatabase;
@ -10,10 +9,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
let mut database = RootDatabase::default(); let mut database = RootDatabase::default();
database.apply_change(change_fixture.change); database.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
(database, FilePosition { file_id, offset }) (database, FilePosition { file_id, offset })
} }

View File

@ -2,7 +2,6 @@ use base_db::{fixture::ChangeFixture, FilePosition};
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use hir::Semantics; use hir::Semantics;
use syntax::ast::{self, AstNode}; use syntax::ast::{self, AstNode};
use test_utils::RangeOrOffset;
use crate::RootDatabase; use crate::RootDatabase;
@ -12,10 +11,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
let mut database = RootDatabase::default(); let mut database = RootDatabase::default();
database.apply_change(change_fixture.change); database.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = match range_or_offset { let offset = range_or_offset.expect_offset();
RangeOrOffset::Range(_) => panic!(),
RangeOrOffset::Offset(it) => it,
};
(database, FilePosition { file_id, offset }) (database, FilePosition { file_id, offset })
} }

View File

@ -96,6 +96,21 @@ pub enum RangeOrOffset {
Offset(TextSize), 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 { impl From<RangeOrOffset> for TextRange {
fn from(selection: RangeOrOffset) -> Self { fn from(selection: RangeOrOffset) -> Self {
match selection { match selection {