Repository: rust-analyzer/rust-analyzer
Pull request #4867 (2020-06-13 09:01:25)

Cleanup URL handling


bors r+
🤖

bors[bot]: Build failed:
  * [Rust (windows-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/767878213)
matklad: bors r+
bors[bot]: Canceled.
matklad: bors r+
bors[bot]: Build failed:
  * [Rust (windows-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/767912592)

Patch:
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index e1fcf379d7..a721e23c69 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -15,7 +15,6 @@ 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 b2f621a119..fd95bc4103 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -1,6 +1,11 @@
 //! FIXME: write short doc here
 
-use ra_syntax::ast;
+use ra_syntax::{
+    ast::{self, LoopBodyOwner},
+    match_ast, AstNode,
+    SyntaxKind::*,
+    SyntaxToken,
+};
 
 use crate::completion::{
     CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
@@ -36,122 +41,68 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
     }
 }
 
-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;
-    }
-    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() {}")
-        }
+fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
+    let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
+        .kind(CompletionItemKind::Keyword);
 
-        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 {}");
-        }
+    match ctx.config.snippet_cap {
+        Some(cap) => res.insert_snippet(cap, snippet),
+        _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
+    }
+    .build()
+}
 
+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 {}");
-    }
 
-    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 {}");
-    }
+    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.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 {
-        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 ");
+        acc.add(keyword(ctx, "else", "else {$0}"));
+        acc.add(keyword(ctx, "else if", "else if $0 {}"));
     }
-    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 is_in_loop_body(&ctx.token) {
         if ctx.can_be_stmt {
-            add_keyword(ctx, acc, "continue", "continue;");
-            add_keyword(ctx, acc, "break", "break;");
+            acc.add(keyword(ctx, "continue", "continue;"));
+            acc.add(keyword(ctx, "break", "break;"));
         } else {
-            add_keyword(ctx, acc, "continue", "continue");
-            add_keyword(ctx, acc, "break", "break");
+            acc.add(keyword(ctx, "continue", "continue"));
+            acc.add(keyword(ctx, "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 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 }),
+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;
+            }
+        }
     }
-    .build()
-}
-
-fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
-    acc.add(keyword(ctx, kw, snippet));
+    false
 }
 
 fn complete_return(
@@ -170,120 +121,157 @@ fn complete_return(
 
 #[cfg(test)]
 mod tests {
-    use crate::completion::{test_utils::completion_list, CompletionKind};
-    use insta::assert_snapshot;
+    use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
+    use insta::assert_debug_snapshot;
 
-    fn get_keyword_completions(code: &str) -> String {
-        completion_list(code, CompletionKind::Keyword)
+    fn do_keyword_completion(code: &str) -> Vec<CompletionItem> {
+        do_completion(code, CompletionKind::Keyword)
     }
 
     #[test]
-    fn test_keywords_in_use_stmt() {
-        assert_snapshot!(
-            get_keyword_completions(r"use <|>"),
-            @r###"
-            kw crate
-            kw self
-            kw super
-        "###
-        );
-
-        assert_snapshot!(
-            get_keyword_completions(r"use a::<|>"),
-            @r###"
-            kw self
-            kw super
-        "###
-        );
-
-        assert_snapshot!(
-            get_keyword_completions(r"use a::{b, <|>}"),
+    fn completes_keywords_in_use_stmt() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                use <|>
+                ",
+            ),
             @r###"
-            kw self
-            kw super
+        [
+            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,
+            },
+        ]
         "###
         );
-    }
 
-    #[test]
-    fn test_keywords_at_source_file_level() {
-        assert_snapshot!(
-            get_keyword_completions(r"m<|>"),
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                use a::<|>
+                ",
+            ),
             @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
+        [
+            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,
+            },
+        ]
         "###
         );
-    }
 
-    #[test]
-    fn test_keywords_in_function() {
-        assert_snapshot!(
-            get_keyword_completions(r"fn quux() { <|> }"),
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                use a::{b, <|>}
+                ",
+            ),
             @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
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_inside_block() {
-        assert_snapshot!(
-            get_keyword_completions(r"fn quux() { if true { <|> } }"),
+    fn completes_various_keywords_in_function() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() {
+                    <|>
+                }
+                ",
+            ),
             @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
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_after_if() {
-        assert_snapshot!(
-            get_keyword_completions(
+    fn completes_else_after_if() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
                 r"
                 fn quux() {
                     if true {
@@ -293,166 +281,506 @@ mod tests {
                 ",
             ),
             @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
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_in_match_arm() {
-        assert_snapshot!(
-            get_keyword_completions(
+    fn test_completion_return_value() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
                 r"
                 fn quux() -> i32 {
-                    match () {
-                        () => <|>
-                    }
+                    <|>
+                    92
                 }
                 ",
             ),
             @r###"
-            kw if
-            kw if let
-            kw loop
-            kw match
-            kw return
-            kw unsafe
+        [
+            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,
+            },
+        ]
         "###
         );
-    }
-
-    #[test]
-    fn test_keywords_in_trait_def() {
-        assert_snapshot!(
-            get_keyword_completions(r"trait My { <|> }"),
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() {
+                    <|>
+                    92
+                }
+                ",
+            ),
             @r###"
-            kw const
-            kw fn
-            kw type
-            kw unsafe
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_in_impl_def() {
-        assert_snapshot!(
-            get_keyword_completions(r"impl My { <|> }"),
+    fn dont_add_semi_after_return_if_not_a_statement() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() -> i32 {
+                    match () {
+                        () => <|>
+                    }
+                }
+                ",
+            ),
             @r###"
-            kw const
-            kw fn
-            kw pub
-            kw type
-            kw unsafe
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_in_loop() {
-        assert_snapshot!(
-            get_keyword_completions(r"fn my() { loop { <|> } }"),
+    fn last_return_in_block_has_semi() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() -> i32 {
+                    if condition {
+                        <|>
+                    }
+                }
+                ",
+            ),
             @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
+        [
+            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,
+            },
+        ]
         "###
         );
-    }
-
-    #[test]
-    fn test_keywords_after_unsafe_in_item_list() {
-        assert_snapshot!(
-            get_keyword_completions(r"unsafe <|>"),
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() -> i32 {
+                    if condition {
+                        <|>
+                    }
+                    let x = 92;
+                    x
+                }
+                ",
+            ),
             @r###"
-            kw fn
-            kw impl
-            kw trait
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_keywords_after_unsafe_in_block_expr() {
-        assert_snapshot!(
-            get_keyword_completions(r"fn my_fn() { unsafe <|> }"),
+    fn completes_break_and_continue_in_loops() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() -> i32 {
+                    loop { <|> }
+                }
+                ",
+            ),
             @r###"
-            kw fn
-            kw impl
-            kw trait
+        [
+            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,
+            },
+        ]
         "###
         );
-    }
 
-    #[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 &<|> }"),
+        // No completion: lambda isolates control flow
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn quux() -> i32 {
+                    loop { || { <|> } }
+                }
+                ",
+            ),
             @r###"
-            kw mut
+        [
+            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,
+            },
+        ]
         "###
         );
     }
 
     #[test]
-    fn test_where_keyword() {
-        assert_snapshot!(
-            get_keyword_completions(r"trait A <|>"),
-            @r###"
-            kw where
-        "###
-        );
-        assert_snapshot!(
-            get_keyword_completions(r"impl A <|>"),
+    fn no_semi_after_break_continue_in_expr() {
+        assert_debug_snapshot!(
+            do_keyword_completion(
+                r"
+                fn f() {
+                    loop {
+                        match () {
+                            () => br<|>
+                        }
+                    }
+                }
+                ",
+            ),
             @r###"
-            kw where
+        [
+            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,
+            },
+        ]
         "###
-        );
+        )
     }
 }
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 9f4c582d08..c4646b727c 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -5,17 +5,12 @@ use ra_db::SourceDatabase;
 use ra_ide_db::RootDatabase;
 use ra_syntax::{
     algo::{find_covering_element, find_node_at_offset},
-    ast, match_ast, AstNode, NodeOrToken,
+    ast, match_ast, AstNode,
     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;
 
@@ -65,18 +60,6 @@ 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> {
@@ -135,18 +118,6 @@ 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();
@@ -188,7 +159,7 @@ impl<'a> CompletionContext<'a> {
                 break;
             }
         }
-        ctx.fill_keyword_patterns(&hypothetical_file, offset);
+
         ctx.fill(&original_file, hypothetical_file, offset);
         Some(ctx)
     }
@@ -217,24 +188,6 @@ 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 98348b3492..cfb7c1e380 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -125,32 +125,6 @@ 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
deleted file mode 100644
index b2fe13280a..0000000000
--- a/crates/ra_ide/src/completion/patterns.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-//! 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)
-    }
-}
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 257910e094..789c86a353 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -1,10 +1,6 @@
 //! This module provides the functionality needed to convert diagnostics from
 //! `cargo check` json format to the LSP diagnostic format.
-use std::{
-    collections::HashMap,
-    path::{Component, Path, Prefix},
-    str::FromStr,
-};
+use std::{collections::HashMap, path::Path};
 
 use lsp_types::{
     Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
@@ -13,7 +9,7 @@ use lsp_types::{
 use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion};
 use stdx::format_to;
 
-use crate::{lsp_ext, Result};
+use crate::{lsp_ext, to_proto::url_from_abs_path};
 
 /// Converts a Rust level string to a LSP severity
 fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
@@ -65,7 +61,7 @@ fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Locatio
 fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location {
     let mut file_name = workspace_root.to_path_buf();
     file_name.push(&span.file_name);
-    let uri = url_from_path_with_drive_lowercasing(file_name).unwrap();
+    let uri = url_from_abs_path(&file_name);
 
     // FIXME: this doesn't handle UTF16 offsets correctly
     let range = Range::new(
@@ -184,7 +180,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
         return Vec::new();
     }
 
-    let severity = map_level_to_severity(rd.level);
+    let mut severity = map_level_to_severity(rd.level);
 
     let mut source = String::from("rustc");
     let mut code = rd.code.as_ref().map(|c| c.code.clone());
@@ -226,6 +222,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
     }
 
     if is_unused_or_unnecessary(rd) {
+        severity = Some(DiagnosticSeverity::Hint);
         tags.push(DiagnosticTag::Unnecessary);
     }
 
@@ -274,70 +271,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
         .collect()
 }
 
-/// Returns a `Url` object from a given path, will lowercase drive letters if present.
-/// This will only happen when processing windows paths.
-///
-/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
-pub fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> {
-    let component_has_windows_drive = path.as_ref().components().any(|comp| {
-        if let Component::Prefix(c) = comp {
-            return matches!(c.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_));
-        }
-        false
-    });
-
-    // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
-    let res = if component_has_windows_drive {
-        let url_original = Url::from_file_path(&path)
-            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
-
-        let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
-
-        // There is a drive partition, but we never found a colon.
-        // This should not happen, but in this case we just pass it through.
-        if drive_partition.len() == 1 {
-            return Ok(url_original);
-        }
-
-        let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
-        let url = Url::from_str(&joined).expect("This came from a valid `Url`");
-
-        url
-    } else {
-        Url::from_file_path(&path)
-            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?
-    };
-    Ok(res)
-}
-
 #[cfg(test)]
+#[cfg(not(windows))]
 mod tests {
     use super::*;
 
-    // `Url` is not able to parse windows paths on unix machines.
-    #[test]
-    #[cfg(target_os = "windows")]
-    fn test_lowercase_drive_letter_with_drive() {
-        let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
-
-        assert_eq!(url.to_string(), "file:///c:/Test");
-    }
-
-    #[test]
-    #[cfg(target_os = "windows")]
-    fn test_drive_without_colon_passthrough() {
-        let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
-
-        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
-    }
-
-    #[cfg(not(windows))]
     fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic {
         serde_json::from_str::<ra_flycheck::Diagnostic>(val).unwrap()
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_rustc_incompatible_type_for_trait() {
         let diag = parse_diagnostic(
             r##"{
@@ -391,7 +334,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_rustc_unused_variable() {
         let diag = parse_diagnostic(
             r##"{
@@ -474,7 +416,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_rustc_wrong_number_of_parameters() {
         let diag = parse_diagnostic(
             r##"{
@@ -599,7 +540,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_clippy_pass_by_ref() {
         let diag = parse_diagnostic(
             r##"{
@@ -720,7 +660,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_rustc_mismatched_type() {
         let diag = parse_diagnostic(
             r##"{
@@ -764,7 +703,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_handles_macro_location() {
         let diag = parse_diagnostic(
             r##"{
@@ -1036,7 +974,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_macro_compiler_error() {
         let diag = parse_diagnostic(
             r##"{
@@ -1266,7 +1203,6 @@ mod tests {
     }
 
     #[test]
-    #[cfg(not(windows))]
     fn snap_multi_line_fix() {
         let diag = parse_diagnostic(
             r##"{
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index 206673829c..40f856e6e8 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -17,7 +17,7 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex
 }
 
 pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
-    world.uri_to_file_id(url)
+    world.url_to_file_id(url)
 }
 
 pub(crate) fn file_position(
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 21116e165e..9d5685d88e 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -22,10 +22,9 @@ use stdx::format_to;
 
 use crate::{
     config::Config,
-    diagnostics::{
-        to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection,
-    },
+    diagnostics::{CheckFixes, DiagnosticCollection},
     main_loop::pending_requests::{CompletedRequest, LatestRequests},
+    to_proto::url_from_abs_path,
     vfs_glob::{Glob, RustPackageFilterBuilder},
     LspError, Result,
 };
@@ -274,8 +273,8 @@ impl GlobalStateSnapshot {
         &self.analysis
     }
 
-    pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
-        let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
+    pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
+        let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
         let file = self.vfs.read().path2file(&path).ok_or_else(|| {
             // Show warning as this file is outside current workspace
             // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`.
@@ -287,11 +286,8 @@ impl GlobalStateSnapshot {
         Ok(FileId(file.0))
     }
 
-    pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
-        let path = self.vfs.read().file2path(VfsFile(id.0));
-        let url = url_from_path_with_drive_lowercasing(path)?;
-
-        Ok(url)
+    pub fn file_id_to_url(&self, id: FileId) -> Url {
+        file_id_to_url(&self.vfs.read(), id)
     }
 
     pub fn file_id_to_path(&self, id: FileId) -> PathBuf {
@@ -302,12 +298,10 @@ impl GlobalStateSnapshot {
         self.vfs.read().file_line_endings(VfsFile(id.0))
     }
 
-    pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
+    pub fn path_to_url(&self, root: SourceRootId, path: &RelativePathBuf) -> Url {
         let base = self.vfs.read().root2path(VfsRoot(root.0));
         let path = path.to_path(base);
-        let url = Url::from_file_path(&path)
-            .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
-        Ok(url)
+        url_from_abs_path(&path)
     }
 
     pub fn status(&self) -> String {
@@ -335,3 +329,8 @@ impl GlobalStateSnapshot {
         self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
     }
 }
+
+pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url {
+    let path = vfs.file2path(VfsFile(id.0));
+    url_from_abs_path(&path)
+}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 752dbf1452..8ec571b709 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -27,7 +27,7 @@ use ra_flycheck::{CheckTask, Status};
 use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
 use ra_prof::profile;
 use ra_project_model::{PackageRoot, ProjectWorkspace};
-use ra_vfs::{VfsFile, VfsTask, Watch};
+use ra_vfs::{VfsTask, Watch};
 use relative_path::RelativePathBuf;
 use rustc_hash::FxHashSet;
 use serde::{de::DeserializeOwned, Serialize};
@@ -35,9 +35,9 @@ use threadpool::ThreadPool;
 
 use crate::{
     config::{Config, FilesWatcher, LinkedProject},
-    diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
+    diagnostics::DiagnosticTask,
     from_proto,
-    global_state::{GlobalState, GlobalStateSnapshot},
+    global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot},
     lsp_ext,
     main_loop::{
         pending_requests::{PendingRequest, PendingRequests},
@@ -801,17 +801,9 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
     let subscriptions = state.diagnostics.handle_task(task);
 
     for file_id in subscriptions {
-        let path = state.vfs.read().file2path(VfsFile(file_id.0));
-        let uri = match url_from_path_with_drive_lowercasing(&path) {
-            Ok(uri) => uri,
-            Err(err) => {
-                log::error!("Couldn't convert path to url ({}): {}", err, path.display());
-                continue;
-            }
-        };
-
+        let url = file_id_to_url(&state.vfs.read(), file_id);
         let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect();
-        let params = lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None };
+        let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None };
         let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
         msg_sender.send(not.into()).unwrap();
     }
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index a41adf8b0b..b34b529b5c 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -258,7 +258,7 @@ pub fn handle_document_symbol(
     let res = if snap.config.client_caps.hierarchical_symbols {
         document_symbols.into()
     } else {
-        let url = to_proto::url(&snap, file_id)?;
+        let url = to_proto::url(&snap, file_id);
         let mut symbol_information = Vec::<SymbolInformation>::new();
         for symbol in document_symbols {
             flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
@@ -1160,7 +1160,7 @@ fn show_impl_command_link(
 ) -> Option<lsp_ext::CommandLinkGroup> {
     if snap.config.hover.implementations {
         if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) {
-            let uri = to_proto::url(snap, position.file_id).ok()?;
+            let uri = to_proto::url(snap, position.file_id);
             let line_index = snap.analysis().file_line_index(position.file_id).ok()?;
             let position = to_proto::position(&line_index, position.offset);
             let locations: Vec<_> = nav_data
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 710df1fbde..881aa1c55f 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1,4 +1,7 @@
 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
+use std::path::{self, Path};
+
+use itertools::Itertools;
 use ra_db::{FileId, FileRange};
 use ra_ide::{
     Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
@@ -385,24 +388,55 @@ pub(crate) fn folding_range(
     }
 }
 
-pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> {
-    snap.file_id_to_uri(file_id)
+pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
+    snap.file_id_to_url(file_id)
+}
+
+/// Returns a `Url` object from a given path, will lowercase drive letters if present.
+/// This will only happen when processing windows paths.
+///
+/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
+pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url {
+    assert!(path.is_absolute());
+    let url = lsp_types::Url::from_file_path(path).unwrap();
+    match path.components().next() {
+        Some(path::Component::Prefix(prefix)) if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
+        {
+            // Need to lowercase driver letter
+        }
+        _ => return url,
+    }
+
+    let driver_letter_range = {
+        let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
+            Some(it) => it,
+            None => return url,
+        };
+        let start = scheme.len() + ':'.len_utf8();
+        start..(start + drive_letter.len())
+    };
+
+    // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
+    // machinery *also* canonicalizes the drive letter. So, just massage the
+    // string in place.
+    let mut url = url.into_string();
+    url[driver_letter_range].make_ascii_lowercase();
+    lsp_types::Url::parse(&url).unwrap()
 }
 
 pub(crate) fn versioned_text_document_identifier(
     snap: &GlobalStateSnapshot,
     file_id: FileId,
     version: Option<i64>,
-) -> Result<lsp_types::VersionedTextDocumentIdentifier> {
-    let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version };
-    Ok(res)
+) -> lsp_types::VersionedTextDocumentIdentifier {
+    lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id), version }
 }
 
 pub(crate) fn location(
     snap: &GlobalStateSnapshot,
     frange: FileRange,
 ) -> Result<lsp_types::Location> {
-    let url = url(snap, frange.file_id)?;
+    let url = url(snap, frange.file_id);
     let line_index = snap.analysis().file_line_index(frange.file_id)?;
     let range = range(&line_index, frange.range);
     let loc = lsp_types::Location::new(url, range);
@@ -438,7 +472,7 @@ fn location_info(
 ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
     let line_index = snap.analysis().file_line_index(target.file_id())?;
 
-    let target_uri = url(snap, target.file_id())?;
+    let target_uri = url(snap, target.file_id());
     let target_range = range(&line_index, target.full_range());
     let target_selection_range =
         target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range);
@@ -478,7 +512,7 @@ pub(crate) fn snippet_text_document_edit(
     is_snippet: bool,
     source_file_edit: SourceFileEdit,
 ) -> Result<lsp_ext::SnippetTextDocumentEdit> {
-    let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?;
+    let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None);
     let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?;
     let line_endings = snap.file_line_endings(source_file_edit.file_id);
     let edits = source_file_edit
@@ -492,19 +526,18 @@ pub(crate) fn snippet_text_document_edit(
 pub(crate) fn resource_op(
     snap: &GlobalStateSnapshot,
     file_system_edit: FileSystemEdit,
-) -> Result<lsp_types::ResourceOp> {
-    let res = match file_system_edit {
+) -> lsp_types::ResourceOp {
+    match file_system_edit {
         FileSystemEdit::CreateFile { source_root, path } => {
-            let uri = snap.path_to_uri(source_root, &path)?;
+            let uri = snap.path_to_url(source_root, &path);
             lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None })
         }
         FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
-            let old_uri = snap.file_id_to_uri(src)?;
-            let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?;
+            let old_uri = snap.file_id_to_url(src);
+            let new_uri = snap.path_to_url(dst_source_root, &dst_path);
             lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None })
         }
-    };
-    Ok(res)
+    }
 }
 
 pub(crate) fn snippet_workspace_edit(
@@ -513,7 +546,7 @@ pub(crate) fn snippet_workspace_edit(
 ) -> Result<lsp_ext::SnippetWorkspaceEdit> {
     let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
     for op in source_change.file_system_edits {
-        let op = resource_op(&snap, op)?;
+        let op = resource_op(&snap, op);
         document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op));
     }
     for edit in source_change.source_file_edits {
@@ -568,7 +601,7 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
     }
 }
 
-pub fn call_hierarchy_item(
+pub(crate) fn call_hierarchy_item(
     snap: &GlobalStateSnapshot,
     target: NavigationTarget,
 ) -> Result<lsp_types::CallHierarchyItem> {
@@ -579,50 +612,6 @@ pub fn call_hierarchy_item(
     Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range })
 }
 
-#[cfg(test)]
-mod tests {
-    use test_utils::extract_ranges;
-
-    use super::*;
-
-    #[test]
-    fn conv_fold_line_folding_only_fixup() {
-        let text = r#"<fold>mod a;
-mod b;
-mod c;</fold>
-
-fn main() <fold>{
-    if cond <fold>{
-        a::do_a();
-    }</fold> else <fold>{
-        b::do_b();
-    }</fold>
-}</fold>"#;
-
-        let (ranges, text) = extract_ranges(text, "fold");
-        assert_eq!(ranges.len(), 4);
-        let folds = vec![
-            Fold { range: ranges[0], kind: FoldKind::Mods },
-            Fold { range: ranges[1], kind: FoldKind::Block },
-            Fold { range: ranges[2], kind: FoldKind::Block },
-            Fold { range: ranges[3], kind: FoldKind::Block },
-        ];
-
-        let line_index = LineIndex::new(&text);
-        let converted: Vec<lsp_types::FoldingRange> =
-            folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect();
-
-        let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
-        assert_eq!(converted.len(), expected_lines.len());
-        for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
-            assert_eq!(folding_range.start_line, *start_line);
-            assert_eq!(folding_range.start_character, None);
-            assert_eq!(folding_range.end_line, *end_line);
-            assert_eq!(folding_range.end_character, None);
-        }
-    }
-}
-
 pub(crate) fn unresolved_code_action(
     snap: &GlobalStateSnapshot,
     assist: Assist,
@@ -676,3 +665,62 @@ pub(crate) fn runnable(
         },
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use test_utils::extract_ranges;
+
+    use super::*;
+
+    #[test]
+    fn conv_fold_line_folding_only_fixup() {
+        let text = r#"<fold>mod a;
+mod b;
+mod c;</fold>
+
+fn main() <fold>{
+    if cond <fold>{
+        a::do_a();
+    }</fold> else <fold>{
+        b::do_b();
+    }</fold>
+}</fold>"#;
+
+        let (ranges, text) = extract_ranges(text, "fold");
+        assert_eq!(ranges.len(), 4);
+        let folds = vec![
+            Fold { range: ranges[0], kind: FoldKind::Mods },
+            Fold { range: ranges[1], kind: FoldKind::Block },
+            Fold { range: ranges[2], kind: FoldKind::Block },
+            Fold { range: ranges[3], kind: FoldKind::Block },
+        ];
+
+        let line_index = LineIndex::new(&text);
+        let converted: Vec<lsp_types::FoldingRange> =
+            folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect();
+
+        let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
+        assert_eq!(converted.len(), expected_lines.len());
+        for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
+            assert_eq!(folding_range.start_line, *start_line);
+            assert_eq!(folding_range.start_character, None);
+            assert_eq!(folding_range.end_line, *end_line);
+            assert_eq!(folding_range.end_character, None);
+        }
+    }
+
+    // `Url` is not able to parse windows paths on unix machines.
+    #[test]
+    #[cfg(target_os = "windows")]
+    fn test_lowercase_drive_letter_with_drive() {
+        let url = url_from_abs_path(Path::new("C:\\Test"));
+        assert_eq!(url.to_string(), "file:///c:/Test");
+    }
+
+    #[test]
+    #[cfg(target_os = "windows")]
+    fn test_drive_without_colon_passthrough() {
+        let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#));
+        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
+    }
+}


Test patch:
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index 1e16a43cab..bf22452a28 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -5,63 +5,25 @@ 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)
     };
-    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();
+    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();
     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();
 }
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
index 9a7972ff54..f0273315e9 100644
--- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
@@ -29,7 +29,7 @@ expression: diag
                 },
             },
             severity: Some(
-                Warning,
+                Hint,
             ),
             code: Some(
                 String(