Repository: rust-analyzer/rust-analyzer
Pull request #4700 (2020-06-01 23:35:28)

Add top level keywords completion
This fixes the following issue: https://github.com/rust-analyzer/rust-analyzer/issues/4566. 

Also added simple logic which filters the keywords which can be used with unsafe on the top level.   


Patch:
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index a721e23c69..e1fcf379d7 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 fd95bc4103..b2f621a119 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<CompletionItem> {
-        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 c4646b727c..9f4c582d08 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<ast::Attr>,
+    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 cfb7c1e380..98348b3492 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 0000000000..b2fe13280a
--- /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<SyntaxNode> {
+    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<SyntaxToken> {
+    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<SyntaxElement> {
+    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)
+    }
+}


Test patch:
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index bf22452a28..1e16a43cab 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<CompletionItem> {
     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<CompletionItem> {
+    let mut kind_completions: Vec<CompletionItem> = 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<CompletionItem> {
     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<CompletionItem> = completions.into();
-    let mut kind_completions: Vec<CompletionItem> =
-        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<CompletionItem> = 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();
 }