Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/completions/keyword.rs')
| -rw-r--r-- | crates/ide-completion/src/completions/keyword.rs | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs new file mode 100644 index 0000000000..51405e3523 --- /dev/null +++ b/crates/ide-completion/src/completions/keyword.rs @@ -0,0 +1,315 @@ +//! Completes keywords, except: +//! - `self`, `super` and `crate`, as these are considered part of path completions. +//! - `await`, as this is a postfix completion we handle this in the postfix completions. + +use syntax::{SyntaxKind, T}; + +use crate::{ + context::{PathCompletionCtx, PathKind}, + patterns::ImmediateLocation, + CompletionContext, CompletionItem, CompletionItemKind, Completions, +}; + +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.fake_attribute_under_caret.is_some() { + cov_mark::hit!(no_keyword_completion_in_attr_of_expr); + return; + } + if ctx.is_non_trivial_path() { + cov_mark::hit!(no_keyword_completion_in_non_trivial_path); + return; + } + if ctx.pattern_ctx.is_some() { + return; + } + + let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, 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 let Some(PathKind::Vis { .. }) = ctx.path_kind() { + return; + } + 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_field()) + { + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub(super)", "pub(super)"); + 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 || has_block_expr_parent { + add_keyword("enum", "enum $1 {\n $0\n}"); + add_keyword("struct", "struct $0"); + add_keyword("union", "union $1 {\n $0\n}"); + } + + if ctx.expects_type() { + return; + } + + 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}"); + add_keyword("true", "true"); + add_keyword("false", "false"); + } + + 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_ref_expr() { + add_keyword("mut", "mut "); + } + + let (can_be_stmt, in_loop_body) = match ctx.path_context { + Some(PathCompletionCtx { is_absolute_path: false, 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(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet: &str) { + let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + + match ctx.config.snippet_cap { + Some(cap) => { + if snippet.ends_with('}') && ctx.incomplete_let { + // complete block expression snippets with a trailing semicolon, if inside an 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, completion_list}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture); + expect.assert_eq(&actual) + } + + #[test] + fn test_else_edit_after_if() { + check_edit( + "else", + r#"fn quux() { if true { () } $0 }"#, + r#"fn quux() { if true { () } else { + $0 +} }"#, + ); + } + + #[test] + fn test_keywords_after_unsafe_in_block_expr() { + check( + r"fn my_fn() { unsafe $0 }", + expect![[r#" + kw fn + kw trait + kw impl + sn pd + sn ppd + "#]], + ); + } + + #[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 + sn ref &expr + sn refm &mut expr + sn match match expr {} + sn box Box::new(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn call function(expr) + sn let let + sn letm let mut + "#]], + ); + + check( + r#" +//- minicore: future +use std::future::*; +fn foo() { + let a = async {}; + a.$0 +} +"#, + expect![[r#" + kw await expr.await + sn ref &expr + sn refm &mut expr + sn match match expr {} + sn box Box::new(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn call function(expr) + sn let let + sn letm let mut + "#]], + ) + } + + #[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(); +} +"#, + ); + } +} |