2021-06-21 13:48:25 +02:00

548 lines
13 KiB
Rust

//! Completes keywords.
use std::iter;
use syntax::{SyntaxKind, T};
use crate::{
context::PathCompletionContext, patterns::ImmediateLocation, CompletionContext, CompletionItem,
CompletionItemKind, CompletionKind, Completions,
};
pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
// complete keyword "crate" in use stmt
let source_range = ctx.source_range();
let kw_completion = move |text: &str| {
let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, text);
item.kind(CompletionItemKind::Keyword).insert_text(text);
item
};
if ctx.in_use_tree() {
match &ctx.path_context {
Some(PathCompletionContext { qualifier: Some(qual), use_tree_parent, .. }) => {
if iter::successors(Some(qual.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some())
{
kw_completion("super::").add_to(acc);
}
if *use_tree_parent {
kw_completion("self").add_to(acc);
}
}
_ => {
kw_completion("crate::").add_to(acc);
kw_completion("self::").add_to(acc);
kw_completion("super::").add_to(acc);
}
};
}
}
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
if ctx.token.kind() == SyntaxKind::COMMENT {
cov_mark::hit!(no_keyword_completion_in_comments);
return;
}
if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) {
cov_mark::hit!(no_keyword_completion_in_record_lit);
return;
}
if ctx.attribute_under_caret.is_some() {
cov_mark::hit!(no_keyword_completion_in_attr_of_expr);
return;
}
// Suggest .await syntax for types that implement Future trait
if let Some(receiver) = ctx.dot_receiver() {
if let Some(ty) = ctx.sema.type_of_expr(receiver) {
if ty.impls_future(ctx.db) {
let mut item =
CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await");
item.kind(CompletionItemKind::Keyword).detail("expr.await");
item.add_to(acc);
}
};
}
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("where", "where ");
if ctx.has_impl_prev_sibling() {
add_keyword("for", "for ");
}
return;
}
if ctx.previous_token_is(T![unsafe]) {
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("trait", "trait $1 {\n $0\n}");
add_keyword("impl", "impl $1 {\n $0\n}");
}
return;
}
if !ctx.has_visibility_prev_sibling()
&& (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 {
add_keyword("unsafe", "unsafe ");
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 !ctx.has_visibility_prev_sibling() {
add_keyword("impl", "impl $1 {\n $0\n}");
add_keyword("extern", "extern $0");
}
add_keyword("use", "use $0");
add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword("static", "static $0");
add_keyword("mod", "mod $0");
}
if expects_item {
add_keyword("enum", "enum $1 {\n $0\n}");
add_keyword("struct", "struct $0");
add_keyword("union", "union $1 {\n $0\n}");
}
if ctx.expects_expression() {
if !has_block_expr_parent {
add_keyword("unsafe", "unsafe {\n $0\n}");
}
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("let", "let ");
}
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("mut", "mut ");
}
let (can_be_stmt, in_loop_body) = match ctx.path_context {
Some(PathCompletionContext {
is_trivial_path: true, can_be_stmt, in_loop_body, ..
}) => (can_be_stmt, in_loop_body),
_ => return,
};
if in_loop_body {
if can_be_stmt {
add_keyword("continue", "continue;");
add_keyword("break", "break;");
} else {
add_keyword("continue", "continue");
add_keyword("break", "break");
}
}
let fn_def = match &ctx.function_def {
Some(it) => it,
None => return,
};
add_keyword(
"return",
match (can_be_stmt, fn_def.ret_type().is_some()) {
(true, true) => "return $0;",
(true, false) => "return;",
(false, true) => "return $0",
(false, false) => "return",
},
)
}
fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
let mut item = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw);
item.kind(CompletionItemKind::Keyword);
match ctx.config.snippet_cap {
Some(cap) => {
if snippet.ends_with('}') && ctx.incomplete_let {
cov_mark::hit!(let_semi);
item.insert_snippet(cap, format!("{};", snippet));
} else {
item.insert_snippet(cap, snippet);
}
}
None => {
item.insert_text(if snippet.contains('$') { kw } else { snippet });
}
};
item.add_to(acc);
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use crate::{
tests::{check_edit, filtered_completion_list},
CompletionKind,
};
fn check(ra_fixture: &str, expect: Expect) {
let actual = filtered_completion_list(ra_fixture, CompletionKind::Keyword);
expect.assert_eq(&actual)
}
#[test]
fn test_keywords_in_function() {
check(
r"fn quux() { $0 }",
expect![[r#"
kw unsafe
kw fn
kw const
kw type
kw impl
kw extern
kw use
kw trait
kw static
kw mod
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw let
kw return
"#]],
);
}
#[test]
fn test_keywords_inside_block() {
check(
r"fn quux() { if true { $0 } }",
expect![[r#"
kw unsafe
kw fn
kw const
kw type
kw impl
kw extern
kw use
kw trait
kw static
kw mod
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw let
kw return
"#]],
);
}
#[test]
fn test_keywords_after_if() {
check(
r#"fn quux() { if true { () } $0 }"#,
expect![[r#"
kw unsafe
kw fn
kw const
kw type
kw impl
kw extern
kw use
kw trait
kw static
kw mod
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw let
kw else
kw else if
kw return
"#]],
);
check_edit(
"else",
r#"fn quux() { if true { () } $0 }"#,
r#"fn quux() { if true { () } else {
$0
} }"#,
);
}
#[test]
fn test_keywords_in_match_arm() {
check(
r#"
fn quux() -> i32 {
match () { () => $0 }
}
"#,
expect![[r#"
kw unsafe
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw return
"#]],
);
}
#[test]
fn test_keywords_in_loop() {
check(
r"fn my() { loop { $0 } }",
expect![[r#"
kw unsafe
kw fn
kw const
kw type
kw impl
kw extern
kw use
kw trait
kw static
kw mod
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw let
kw continue
kw break
kw return
"#]],
);
}
#[test]
fn test_keywords_after_unsafe_in_block_expr() {
check(
r"fn my_fn() { unsafe $0 }",
expect![[r#"
kw fn
kw trait
kw impl
"#]],
);
}
#[test]
fn no_keyword_completion_in_comments() {
cov_mark::check!(no_keyword_completion_in_comments);
check(
r#"
fn test() {
let x = 2; // A comment$0
}
"#,
expect![[""]],
);
check(
r#"
/*
Some multi-line comment$0
*/
"#,
expect![[""]],
);
check(
r#"
/// Some doc comment
/// let test$0 = 1
"#,
expect![[""]],
);
}
#[test]
fn test_completion_await_impls_future() {
check(
r#"
//- minicore: future
use core::future::*;
struct A {}
impl Future for A {}
fn foo(a: A) { a.$0 }
"#,
expect![[r#"
kw await expr.await
"#]],
);
check(
r#"
//- minicore: future
use std::future::*;
fn foo() {
let a = async {};
a.$0
}
"#,
expect![[r#"
kw await expr.await
"#]],
)
}
#[test]
fn after_let() {
check(
r#"fn main() { let _ = $0 }"#,
expect![[r#"
kw unsafe
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw return
"#]],
)
}
#[test]
fn skip_struct_initializer() {
cov_mark::check!(no_keyword_completion_in_record_lit);
check(
r#"
struct Foo {
pub f: i32,
}
fn foo() {
Foo {
$0
}
}
"#,
expect![[r#""#]],
);
}
#[test]
fn struct_initializer_field_expr() {
check(
r#"
struct Foo {
pub f: i32,
}
fn foo() {
Foo {
f: $0
}
}
"#,
expect![[r#"
kw unsafe
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw return
"#]],
);
}
#[test]
fn let_semi() {
cov_mark::check!(let_semi);
check_edit(
"match",
r#"
fn main() { let x = $0 }
"#,
r#"
fn main() { let x = match $1 {
$0
}; }
"#,
);
check_edit(
"if",
r#"
fn main() {
let x = $0
let y = 92;
}
"#,
r#"
fn main() {
let x = if $1 {
$0
};
let y = 92;
}
"#,
);
check_edit(
"loop",
r#"
fn main() {
let x = $0
bar();
}
"#,
r#"
fn main() {
let x = loop {
$0
};
bar();
}
"#,
);
}
}