From 1b0c7701cc97cd7bef8bb9729011d4cf291a60c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 17:42:52 +0200 Subject: Rename ra_ide -> ide --- crates/ide/src/completion/complete_keyword.rs | 536 ++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 crates/ide/src/completion/complete_keyword.rs (limited to 'crates/ide/src/completion/complete_keyword.rs') diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs new file mode 100644 index 000000000..a80708935 --- /dev/null +++ b/crates/ide/src/completion/complete_keyword.rs @@ -0,0 +1,536 @@ +//! FIXME: write short doc here + +use syntax::{ast, SyntaxKind}; +use test_utils::mark; + +use crate::completion::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, +}; + +pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { + // complete keyword "crate" in use stmt + let source_range = ctx.source_range(); + match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { + (Some(_), None) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") + .kind(CompletionItemKind::Keyword) + .insert_text("crate::") + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super::") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + (Some(_), Some(_)) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super::") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + _ => {} + } + + // 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) { + CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") + .kind(CompletionItemKind::Keyword) + .detail("expr.await") + .insert_text("await") + .add_to(acc); + } + }; + } +} + +pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.token.kind() == SyntaxKind::COMMENT { + mark::hit!(no_keyword_completion_in_comments); + return; + } + + let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; + if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { + add_keyword(ctx, acc, "where", "where "); + return; + } + if ctx.unsafe_is_prev { + if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { + add_keyword(ctx, acc, "fn", "fn $0() {}") + } + + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "trait", "trait $0 {}"); + add_keyword(ctx, acc, "impl", "impl $0 {}"); + } + + return; + } + if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent + { + add_keyword(ctx, acc, "fn", "fn $0() {}"); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "use", "use "); + add_keyword(ctx, acc, "impl", "impl $0 {}"); + add_keyword(ctx, acc, "trait", "trait $0 {}"); + } + + if ctx.has_item_list_or_source_file_parent { + add_keyword(ctx, acc, "enum", "enum $0 {}"); + add_keyword(ctx, acc, "struct", "struct $0"); + add_keyword(ctx, acc, "union", "union $0 {}"); + } + + if ctx.is_expr { + add_keyword(ctx, acc, "match", "match $0 {}"); + add_keyword(ctx, acc, "while", "while $0 {}"); + add_keyword(ctx, acc, "loop", "loop {$0}"); + add_keyword(ctx, acc, "if", "if "); + add_keyword(ctx, acc, "if let", "if let "); + } + + if ctx.if_is_prev || ctx.block_expr_parent { + add_keyword(ctx, acc, "let", "let "); + } + + if ctx.after_if { + add_keyword(ctx, acc, "else", "else {$0}"); + add_keyword(ctx, acc, "else if", "else if $0 {}"); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "mod", "mod $0 {}"); + } + if ctx.bind_pat_parent || ctx.ref_pat_parent { + add_keyword(ctx, acc, "mut", "mut "); + } + if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent + { + add_keyword(ctx, acc, "const", "const "); + add_keyword(ctx, acc, "type", "type "); + } + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "static", "static "); + }; + if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { + add_keyword(ctx, acc, "extern", "extern "); + } + if ctx.has_item_list_or_source_file_parent + || has_trait_or_impl_parent + || ctx.block_expr_parent + || ctx.is_match_arm + { + add_keyword(ctx, acc, "unsafe", "unsafe "); + } + if ctx.in_loop_body { + if ctx.can_be_stmt { + add_keyword(ctx, acc, "continue", "continue;"); + add_keyword(ctx, acc, "break", "break;"); + } else { + add_keyword(ctx, acc, "continue", "continue"); + add_keyword(ctx, acc, "break", "break"); + } + } + if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent { + add_keyword(ctx, acc, "pub", "pub ") + } + + if !ctx.is_trivial_path { + return; + } + let fn_def = match &ctx.function_syntax { + Some(it) => it, + None => return, + }; + acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); +} + +fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { + let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) + .kind(CompletionItemKind::Keyword); + + match ctx.config.snippet_cap { + Some(cap) => res.insert_snippet(cap, snippet), + _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), + } + .build() +} + +fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { + acc.add(keyword(ctx, kw, snippet)); +} + +fn complete_return( + ctx: &CompletionContext, + fn_def: &ast::Fn, + can_be_stmt: bool, +) -> Option { + let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { + (true, true) => "return $0;", + (true, false) => "return;", + (false, true) => "return $0", + (false, false) => "return", + }; + Some(keyword(ctx, "return", snip)) +} + +#[cfg(test)] +mod tests { + use expect::{expect, Expect}; + + use crate::completion::{ + test_utils::{check_edit, completion_list}, + CompletionKind, + }; + use test_utils::mark; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Keyword); + expect.assert_eq(&actual) + } + + #[test] + fn test_keywords_in_use_stmt() { + check( + r"use <|>", + expect![[r#" + kw crate:: + kw self + kw super:: + "#]], + ); + + check( + r"use a::<|>", + expect![[r#" + kw self + kw super:: + "#]], + ); + + check( + r"use a::{b, <|>}", + expect![[r#" + kw self + kw super:: + "#]], + ); + } + + #[test] + fn test_keywords_at_source_file_level() { + check( + r"m<|>", + expect![[r#" + kw const + kw enum + kw extern + kw fn + kw impl + kw mod + kw pub + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); + } + + #[test] + fn test_keywords_in_function() { + check( + r"fn quux() { <|> }", + expect![[r#" + kw const + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_inside_block() { + check( + r"fn quux() { if true { <|> } }", + expect![[r#" + kw const + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_after_if() { + check( + r#"fn quux() { if true { () } <|> }"#, + expect![[r#" + kw const + kw else + kw else if + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + check_edit( + "else", + r#"fn quux() { if true { () } <|> }"#, + r#"fn quux() { if true { () } else {$0} }"#, + ); + } + + #[test] + fn test_keywords_in_match_arm() { + check( + r#" +fn quux() -> i32 { + match () { () => <|> } +} +"#, + expect![[r#" + kw if + kw if let + kw loop + kw match + kw return + kw unsafe + kw while + "#]], + ); + } + + #[test] + fn test_keywords_in_trait_def() { + check( + r"trait My { <|> }", + expect![[r#" + kw const + kw fn + kw type + kw unsafe + "#]], + ); + } + + #[test] + fn test_keywords_in_impl_def() { + check( + r"impl My { <|> }", + expect![[r#" + kw const + kw fn + kw pub + kw type + kw unsafe + "#]], + ); + } + + #[test] + fn test_keywords_in_loop() { + check( + r"fn my() { loop { <|> } }", + expect![[r#" + kw break + kw const + kw continue + kw extern + kw fn + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw static + kw trait + kw type + kw unsafe + kw use + kw while + "#]], + ); + } + + #[test] + fn test_keywords_after_unsafe_in_item_list() { + check( + r"unsafe <|>", + expect![[r#" + kw fn + kw impl + kw trait + "#]], + ); + } + + #[test] + fn test_keywords_after_unsafe_in_block_expr() { + check( + r"fn my_fn() { unsafe <|> }", + expect![[r#" + kw fn + kw impl + kw trait + "#]], + ); + } + + #[test] + fn test_mut_in_ref_and_in_fn_parameters_list() { + check( + r"fn my_fn(&<|>) {}", + expect![[r#" + kw mut + "#]], + ); + check( + r"fn my_fn(<|>) {}", + expect![[r#" + kw mut + "#]], + ); + check( + r"fn my_fn() { let &<|> }", + expect![[r#" + kw mut + "#]], + ); + } + + #[test] + fn test_where_keyword() { + check( + r"trait A <|>", + expect![[r#" + kw where + "#]], + ); + check( + r"impl A <|>", + expect![[r#" + kw where + "#]], + ); + } + + #[test] + fn no_keyword_completion_in_comments() { + mark::check!(no_keyword_completion_in_comments); + check( + r#" +fn test() { + let x = 2; // A comment<|> +} +"#, + expect![[""]], + ); + check( + r#" +/* +Some multi-line comment<|> +*/ +"#, + expect![[""]], + ); + check( + r#" +/// Some doc comment +/// let test<|> = 1 +"#, + expect![[""]], + ); + } + + #[test] + fn test_completion_await_impls_future() { + check( + r#" +//- /main.rs +use std::future::*; +struct A {} +impl Future for A {} +fn foo(a: A) { a.<|> } + +//- /std/lib.rs +pub mod future { + #[lang = "future_trait"] + pub trait Future {} +} +"#, + expect![[r#" + kw await expr.await + "#]], + ) + } + + #[test] + fn after_let() { + check( + r#"fn main() { let _ = <|> }"#, + expect![[r#" + kw if + kw if let + kw loop + kw match + kw return + kw while + "#]], + ) + } +} -- cgit v1.2.3