diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a721e23c6934..e1fcf379d76e 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -15,6 +15,7 @@ mod complete_unqualified_path; mod complete_postfix; mod complete_macro_in_item_position; mod complete_trait_impl; +mod patterns; #[cfg(test)] mod test_utils; diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index fd95bc4103c2..b2f621a119c7 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs @@ -1,11 +1,6 @@ //! FIXME: write short doc here -use ra_syntax::{ - ast::{self, LoopBodyOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, -}; +use ra_syntax::ast; use crate::completion::{ CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, @@ -41,68 +36,122 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC } } -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 }), +pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { + 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; } - .build() -} + 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 && !has_trait_or_impl_parent) + || ctx.block_expr_parent + { + add_keyword(ctx, acc, "trait", "trait $0 {}"); + add_keyword(ctx, acc, "impl", "impl $0 {}"); + } -pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { return; } + 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 && !has_trait_or_impl_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 {}"); + } - let fn_def = match &ctx.function_syntax { - Some(it) => it, - None => return, - }; - acc.add(keyword(ctx, "if", "if $0 {}")); - acc.add(keyword(ctx, "match", "match $0 {}")); - acc.add(keyword(ctx, "while", "while $0 {}")); - acc.add(keyword(ctx, "loop", "loop {$0}")); + if ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent { + add_keyword(ctx, acc, "enum", "enum $0 {}"); + add_keyword(ctx, acc, "struct", "struct $0 {}"); + add_keyword(ctx, acc, "union", "union $0 {}"); + } + if ctx.block_expr_parent || ctx.is_match_arm { + add_keyword(ctx, acc, "match", "match $0 {}"); + add_keyword(ctx, acc, "loop", "loop {$0}"); + } + if ctx.block_expr_parent { + add_keyword(ctx, acc, "while", "while $0 {}"); + } + if ctx.if_is_prev || ctx.block_expr_parent { + add_keyword(ctx, acc, "let", "let "); + } + if ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm { + add_keyword(ctx, acc, "if", "if "); + add_keyword(ctx, acc, "if let", "if let "); + } if ctx.after_if { - acc.add(keyword(ctx, "else", "else {$0}")); - acc.add(keyword(ctx, "else if", "else if $0 {}")); + 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 && !has_trait_or_impl_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 is_in_loop_body(&ctx.token) { + if ctx.has_item_list_or_source_file_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 && !has_trait_or_impl_parent) + || ctx.block_expr_parent + { + add_keyword(ctx, acc, "static", "static "); + }; + if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) + || ctx.block_expr_parent + { + add_keyword(ctx, acc, "extern", "extern "); + } + if ctx.has_item_list_or_source_file_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 { - acc.add(keyword(ctx, "continue", "continue;")); - acc.add(keyword(ctx, "break", "break;")); + add_keyword(ctx, acc, "continue", "continue;"); + add_keyword(ctx, acc, "break", "break;"); } else { - acc.add(keyword(ctx, "continue", "continue")); - acc.add(keyword(ctx, "break", "break")); + add_keyword(ctx, acc, "continue", "continue"); + add_keyword(ctx, acc, "break", "break"); } } + if ctx.has_item_list_or_source_file_parent && !ctx.has_trait_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 is_in_loop_body(leaf: &SyntaxToken) -> bool { - // FIXME move this to CompletionContext and make it handle macros - for node in leaf.parent().ancestors() { - if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { - break; - } - let loop_body = match_ast! { - match node { - ast::ForExpr(it) => it.loop_body(), - ast::WhileExpr(it) => it.loop_body(), - ast::LoopExpr(it) => it.loop_body(), - _ => None, - } - }; - if let Some(body) = loop_body { - if body.syntax().text_range().contains_range(leaf.text_range()) { - return true; - } - } +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 }), } - false + .build() +} + +fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { + acc.add(keyword(ctx, kw, snippet)); } fn complete_return( @@ -121,327 +170,156 @@ fn complete_return( #[cfg(test)] mod tests { - use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; + use crate::completion::{test_utils::completion_list, CompletionKind}; + use insta::assert_snapshot; - fn do_keyword_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Keyword) + fn get_keyword_completions(code: &str) -> String { + completion_list(code, CompletionKind::Keyword) } #[test] - fn completes_keywords_in_use_stmt() { - assert_debug_snapshot!( - do_keyword_completion( - r" - use <|> - ", - ), + fn test_keywords_in_use_stmt() { + assert_snapshot!( + get_keyword_completions(r"use <|>"), @r###" - [ - CompletionItem { - label: "crate", - source_range: 21..21, - delete: 21..21, - insert: "crate::", - kind: Keyword, - }, - CompletionItem { - label: "self", - source_range: 21..21, - delete: 21..21, - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: 21..21, - delete: 21..21, - insert: "super::", - kind: Keyword, - }, - ] + kw crate + kw self + kw super "### ); - assert_debug_snapshot!( - do_keyword_completion( - r" - use a::<|> - ", - ), + assert_snapshot!( + get_keyword_completions(r"use a::<|>"), @r###" - [ - CompletionItem { - label: "self", - source_range: 24..24, - delete: 24..24, - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: 24..24, - delete: 24..24, - insert: "super::", - kind: Keyword, - }, - ] + kw self + kw super "### ); - assert_debug_snapshot!( - do_keyword_completion( - r" - use a::{b, <|>} - ", - ), + assert_snapshot!( + get_keyword_completions(r"use a::{b, <|>}"), @r###" - [ - CompletionItem { - label: "self", - source_range: 28..28, - delete: 28..28, - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: 28..28, - delete: 28..28, - insert: "super::", - kind: Keyword, - }, - ] + kw self + kw super "### ); } #[test] - fn completes_various_keywords_in_function() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() { - <|> - } - ", - ), + fn test_keywords_at_source_file_level() { + assert_snapshot!( + get_keyword_completions(r"m<|>"), @r###" - [ - CompletionItem { - label: "if", - source_range: 49..49, - delete: 49..49, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 49..49, - delete: 49..49, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 49..49, - delete: 49..49, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 49..49, - delete: 49..49, - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 49..49, - delete: 49..49, - insert: "while $0 {}", - kind: Keyword, - }, - ] + 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 completes_else_after_if() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() { - if true { - () - } <|> - } - ", - ), + fn test_keywords_in_function() { + assert_snapshot!( + get_keyword_completions(r"fn quux() { <|> }"), @r###" - [ - CompletionItem { - label: "else", - source_range: 108..108, - delete: 108..108, - insert: "else {$0}", - kind: Keyword, - }, - CompletionItem { - label: "else if", - source_range: 108..108, - delete: 108..108, - insert: "else if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: 108..108, - delete: 108..108, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 108..108, - delete: 108..108, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 108..108, - delete: 108..108, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 108..108, - delete: 108..108, - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 108..108, - delete: 108..108, - insert: "while $0 {}", - kind: Keyword, - }, - ] + 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_completion_return_value() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - <|> - 92 - } - ", - ), + fn test_keywords_inside_block() { + assert_snapshot!( + get_keyword_completions(r"fn quux() { if true { <|> } }"), @r###" - [ - CompletionItem { - label: "if", - source_range: 56..56, - delete: 56..56, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 56..56, - delete: 56..56, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 56..56, - delete: 56..56, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 56..56, - delete: 56..56, - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 56..56, - delete: 56..56, - insert: "while $0 {}", - kind: Keyword, - }, - ] + 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 "### ); - assert_debug_snapshot!( - do_keyword_completion( + } + + #[test] + fn test_keywords_after_if() { + assert_snapshot!( + get_keyword_completions( r" fn quux() { - <|> - 92 + if true { + () + } <|> } ", ), @r###" - [ - CompletionItem { - label: "if", - source_range: 49..49, - delete: 49..49, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 49..49, - delete: 49..49, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 49..49, - delete: 49..49, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 49..49, - delete: 49..49, - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 49..49, - delete: 49..49, - insert: "while $0 {}", - kind: Keyword, - }, - ] + 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 "### ); } #[test] - fn dont_add_semi_after_return_if_not_a_statement() { - assert_debug_snapshot!( - do_keyword_completion( + fn test_keywords_in_match_arm() { + assert_snapshot!( + get_keyword_completions( r" fn quux() -> i32 { match () { @@ -451,336 +329,130 @@ mod tests { ", ), @r###" - [ - CompletionItem { - label: "if", - source_range: 97..97, - delete: 97..97, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 97..97, - delete: 97..97, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 97..97, - delete: 97..97, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 97..97, - delete: 97..97, - insert: "return $0", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 97..97, - delete: 97..97, - insert: "while $0 {}", - kind: Keyword, - }, - ] + kw if + kw if let + kw loop + kw match + kw return + kw unsafe "### ); } #[test] - fn last_return_in_block_has_semi() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - } - ", - ), + fn test_keywords_in_trait_def() { + assert_snapshot!( + get_keyword_completions(r"trait My { <|> }"), @r###" - [ - CompletionItem { - label: "if", - source_range: 95..95, - delete: 95..95, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 95..95, - delete: 95..95, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 95..95, - delete: 95..95, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 95..95, - delete: 95..95, - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 95..95, - delete: 95..95, - insert: "while $0 {}", - kind: Keyword, - }, - ] + kw const + kw fn + kw type + kw unsafe "### ); - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - let x = 92; - x - } - ", - ), + } + + #[test] + fn test_keywords_in_impl_def() { + assert_snapshot!( + get_keyword_completions(r"impl My { <|> }"), @r###" - [ - CompletionItem { - label: "if", - source_range: 95..95, - delete: 95..95, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 95..95, - delete: 95..95, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 95..95, - delete: 95..95, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 95..95, - delete: 95..95, - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 95..95, - delete: 95..95, - insert: "while $0 {}", - kind: Keyword, - }, - ] + kw const + kw fn + kw pub + kw type + kw unsafe "### ); } #[test] - fn completes_break_and_continue_in_loops() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - loop { <|> } - } - ", - ), + fn test_keywords_in_loop() { + assert_snapshot!( + get_keyword_completions(r"fn my() { loop { <|> } }"), @r###" - [ - CompletionItem { - label: "break", - source_range: 63..63, - delete: 63..63, - insert: "break;", - kind: Keyword, - }, - CompletionItem { - label: "continue", - source_range: 63..63, - delete: 63..63, - insert: "continue;", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: 63..63, - delete: 63..63, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 63..63, - delete: 63..63, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 63..63, - delete: 63..63, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 63..63, - delete: 63..63, - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 63..63, - delete: 63..63, - insert: "while $0 {}", - kind: Keyword, - }, - ] + 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 "### ); + } - // No completion: lambda isolates control flow - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - loop { || { <|> } } - } - ", - ), + #[test] + fn test_keywords_after_unsafe_in_item_list() { + assert_snapshot!( + get_keyword_completions(r"unsafe <|>"), @r###" - [ - CompletionItem { - label: "if", - source_range: 68..68, - delete: 68..68, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 68..68, - delete: 68..68, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 68..68, - delete: 68..68, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 68..68, - delete: 68..68, - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 68..68, - delete: 68..68, - insert: "while $0 {}", - kind: Keyword, - }, - ] + kw fn + kw impl + kw trait "### ); } #[test] - fn no_semi_after_break_continue_in_expr() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn f() { - loop { - match () { - () => br<|> - } - } - } - ", - ), + fn test_keywords_after_unsafe_in_block_expr() { + assert_snapshot!( + get_keyword_completions(r"fn my_fn() { unsafe <|> }"), @r###" - [ - CompletionItem { - label: "break", - source_range: 122..124, - delete: 122..124, - insert: "break", - kind: Keyword, - }, - CompletionItem { - label: "continue", - source_range: 122..124, - delete: 122..124, - insert: "continue", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: 122..124, - delete: 122..124, - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: 122..124, - delete: 122..124, - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: 122..124, - delete: 122..124, - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: 122..124, - delete: 122..124, - insert: "return", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: 122..124, - delete: 122..124, - insert: "while $0 {}", - kind: Keyword, - }, - ] + kw fn + kw impl + kw trait "### - ) + ); + } + + #[test] + fn test_mut_in_ref_and_in_fn_parameters_list() { + assert_snapshot!( + get_keyword_completions(r"fn my_fn(&<|>) {}"), + @r###" + kw mut + "### + ); + assert_snapshot!( + get_keyword_completions(r"fn my_fn(<|>) {}"), + @r###" + kw mut + "### + ); + assert_snapshot!( + get_keyword_completions(r"fn my_fn() { let &<|> }"), + @r###" + kw mut + "### + ); + } + + #[test] + fn test_where_keyword() { + assert_snapshot!( + get_keyword_completions(r"trait A <|>"), + @r###" + kw where + "### + ); + assert_snapshot!( + get_keyword_completions(r"impl A <|>"), + @r###" + kw where + "### + ); } } diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c4646b727c63..9f4c582d0838 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -5,12 +5,17 @@ use ra_db::SourceDatabase; use ra_ide_db::RootDatabase; use ra_syntax::{ algo::{find_covering_element, find_node_at_offset}, - ast, match_ast, AstNode, + ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, }; use ra_text_edit::Indel; +use super::patterns::{ + has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, + has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, + has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, +}; use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; use test_utils::mark; @@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_path_type: bool, pub(super) has_type_args: bool, pub(super) attribute_under_caret: Option, + pub(super) unsafe_is_prev: bool, + pub(super) if_is_prev: bool, + pub(super) block_expr_parent: bool, + pub(super) bind_pat_parent: bool, + pub(super) ref_pat_parent: bool, + pub(super) in_loop_body: bool, + pub(super) has_trait_parent: bool, + pub(super) has_impl_parent: bool, + pub(super) trait_as_prev_sibling: bool, + pub(super) impl_as_prev_sibling: bool, + pub(super) is_match_arm: bool, + pub(super) has_item_list_or_source_file_parent: bool, } impl<'a> CompletionContext<'a> { @@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> { has_type_args: false, dot_receiver_is_ambiguous_float_literal: false, attribute_under_caret: None, + unsafe_is_prev: false, + in_loop_body: false, + ref_pat_parent: false, + bind_pat_parent: false, + block_expr_parent: false, + has_trait_parent: false, + has_impl_parent: false, + trait_as_prev_sibling: false, + impl_as_prev_sibling: false, + if_is_prev: false, + is_match_arm: false, + has_item_list_or_source_file_parent: false, }; let mut original_file = original_file.syntax().clone(); @@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> { break; } } - + ctx.fill_keyword_patterns(&hypothetical_file, offset); ctx.fill(&original_file, hypothetical_file, offset); Some(ctx) } @@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> { self.sema.scope_at_offset(&self.token.parent(), self.offset) } + 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()); + self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); + self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); + self.if_is_prev = if_is_prev(syntax_element.clone()); + self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); + self.ref_pat_parent = has_ref_parent(syntax_element.clone()); + self.in_loop_body = is_in_loop_body(syntax_element.clone()); + self.has_trait_parent = has_trait_parent(syntax_element.clone()); + self.has_impl_parent = has_impl_parent(syntax_element.clone()); + self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); + self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); + self.is_match_arm = is_match_arm(syntax_element.clone()); + self.has_item_list_or_source_file_parent = + has_item_list_or_source_file_parent(syntax_element.clone()); + } + fn fill( &mut self, original_file: &SyntaxNode, diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index cfb7c1e380b6..98348b34924d 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs @@ -125,6 +125,32 @@ pub enum CompletionItemKind { Attribute, } +impl CompletionItemKind { + #[cfg(test)] + pub(crate) fn tag(&self) -> &'static str { + match self { + CompletionItemKind::Snippet => "sn", + CompletionItemKind::Keyword => "kw", + CompletionItemKind::Module => "md", + CompletionItemKind::Function => "fn", + CompletionItemKind::BuiltinType => "bt", + CompletionItemKind::Struct => "st", + CompletionItemKind::Enum => "en", + CompletionItemKind::EnumVariant => "ev", + CompletionItemKind::Binding => "bn", + CompletionItemKind::Field => "fd", + CompletionItemKind::Static => "sc", + CompletionItemKind::Const => "ct", + CompletionItemKind::Trait => "tt", + CompletionItemKind::TypeAlias => "ta", + CompletionItemKind::Method => "me", + CompletionItemKind::TypeParam => "tp", + CompletionItemKind::Macro => "ma", + CompletionItemKind::Attribute => "at", + } + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum CompletionKind { /// Parser-based keyword completion. diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs new file mode 100644 index 000000000000..b2fe13280a88 --- /dev/null +++ b/crates/ra_ide/src/completion/patterns.rs @@ -0,0 +1,194 @@ +//! Patterns telling us certain facts about current syntax element, they are used in completion context + +use ra_syntax::{ + algo::non_trivia_sibling, + ast::{self, LoopBodyOwner}, + match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, + SyntaxKind::*, + SyntaxNode, SyntaxToken, +}; + +#[cfg(test)] +use crate::completion::test_utils::check_pattern_is_applicable; + +pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == ITEM_LIST) + .and_then(|it| it.parent()) + .filter(|it| it.kind() == TRAIT_DEF) + .is_some() +} +#[test] +fn test_has_trait_parent() { + check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); +} + +pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == ITEM_LIST) + .and_then(|it| it.parent()) + .filter(|it| it.kind() == IMPL_DEF) + .is_some() +} +#[test] +fn test_has_impl_parent() { + check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); +} + +pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() +} +#[test] +fn test_has_block_expr_parent() { + check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); +} + +pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { + element.ancestors().find(|it| it.kind() == BIND_PAT).is_some() +} +#[test] +fn test_has_bind_pat_parent() { + check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); + check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); +} + +pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { + not_same_range_ancestor(element) + .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) + .is_some() +} +#[test] +fn test_has_ref_parent() { + check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); + check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); +} + +pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { + let ancestor = not_same_range_ancestor(element); + if !ancestor.is_some() { + return true; + } + ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() +} +#[test] +fn test_has_item_list_or_source_file_parent() { + check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); + check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent); +} + +pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { + not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() + && previous_sibling_or_ancestor_sibling(element) + .and_then(|it| it.into_token()) + .filter(|it| it.kind() == FAT_ARROW) + .is_some() +} +#[test] +fn test_is_match_arm() { + check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); +} + +pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { + element + .into_token() + .and_then(|it| previous_non_trivia_token(it)) + .filter(|it| it.kind() == UNSAFE_KW) + .is_some() +} +#[test] +fn test_unsafe_is_prev() { + check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); +} + +pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { + element + .into_token() + .and_then(|it| previous_non_trivia_token(it)) + .filter(|it| it.kind() == IF_KW) + .is_some() +} +#[test] +fn test_if_is_prev() { + check_pattern_is_applicable(r"if l<|>", if_is_prev); +} + +pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { + previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some() +} +#[test] +fn test_has_trait_as_prev_sibling() { + check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); +} + +pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { + previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some() +} +#[test] +fn test_has_impl_as_prev_sibling() { + check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); +} + +pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { + let leaf = match element { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => token.parent(), + }; + for node in leaf.ancestors() { + if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { + break; + } + let loop_body = match_ast! { + match node { + ast::ForExpr(it) => it.loop_body(), + ast::WhileExpr(it) => it.loop_body(), + ast::LoopExpr(it) => it.loop_body(), + _ => None, + } + }; + if let Some(body) = loop_body { + if body.syntax().text_range().contains_range(leaf.text_range()) { + return true; + } + } + } + false +} + +fn not_same_range_ancestor(element: SyntaxElement) -> Option { + element + .ancestors() + .take_while(|it| it.text_range() == element.text_range()) + .last() + .and_then(|it| it.parent()) +} + +fn previous_non_trivia_token(token: SyntaxToken) -> Option { + let mut token = token.prev_token(); + while let Some(inner) = token.clone() { + if !inner.kind().is_trivia() { + return Some(inner); + } else { + token = inner.prev_token(); + } + } + None +} + +fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option { + let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); + if let Some(sibling) = token_sibling { + Some(sibling) + } else { + // if not trying to find first ancestor which has such a sibling + let node = match element { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => token.parent(), + }; + let range = node.text_range(); + let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; + let prev_sibling_node = top_node.ancestors().find(|it| { + non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() + })?; + non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) + } +} diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index bf22452a281c..1e16a43cabe8 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs @@ -5,25 +5,63 @@ use crate::{ mock_analysis::{analysis_and_position, single_file_with_position}, CompletionItem, }; +use hir::Semantics; +use ra_syntax::{AstNode, NodeOrToken, SyntaxElement}; pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec { do_completion_with_options(code, kind, &CompletionConfig::default()) } +pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { + completion_list_with_options(code, kind, &CompletionConfig::default()) +} + pub(crate) fn do_completion_with_options( code: &str, kind: CompletionKind, options: &CompletionConfig, ) -> Vec { + let mut kind_completions: Vec = get_all_completion_items(code, options) + .into_iter() + .filter(|c| c.completion_kind == kind) + .collect(); + kind_completions.sort_by(|l, r| l.label().cmp(r.label())); + kind_completions +} + +fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec { let (analysis, position) = if code.contains("//-") { analysis_and_position(code) } else { single_file_with_position(code) }; - let completions = analysis.completions(options, position).unwrap().unwrap(); - let completion_items: Vec = completions.into(); - let mut kind_completions: Vec = - completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); + analysis.completions(options, position).unwrap().unwrap().into() +} + +pub(crate) fn completion_list_with_options( + code: &str, + kind: CompletionKind, + options: &CompletionConfig, +) -> String { + let mut kind_completions: Vec = get_all_completion_items(code, options) + .into_iter() + .filter(|c| c.completion_kind == kind) + .collect(); kind_completions.sort_by_key(|c| c.label().to_owned()); kind_completions + .into_iter() + .map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label())) + .collect() +} + +pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { + let (analysis, pos) = single_file_with_position(code); + analysis + .with_db(|db| { + let sema = Semantics::new(db); + let original_file = sema.parse(pos.file_id); + let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); + assert!(check(NodeOrToken::Token(token))); + }) + .unwrap(); }