Repository: rust-analyzer/rust-analyzer
Pull request #4128 (2020-04-24 20:20:03)

Include correct item path for variant completions
The test would previously suggest `E::V`, which is not enough to name the variant as the enum is in a module. Now it correctly suggests the full path `m::E::V`.


Patch:
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 2791632576..c3e6532999 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -10,7 +10,7 @@ use ra_syntax::{
 };
 use ra_text_edit::TextEditBuilder;
 
-use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
+use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
 use algo::SyntaxRewriter;
 
 #[derive(Clone, Debug)]
@@ -180,7 +180,6 @@ pub(crate) struct ActionBuilder {
     edit: TextEditBuilder,
     cursor_position: Option<TextUnit>,
     target: Option<TextRange>,
-    file: AssistFile,
 }
 
 impl ActionBuilder {
@@ -242,16 +241,11 @@ impl ActionBuilder {
         algo::diff(&node, &new).into_text_edit(&mut self.edit)
     }
 
-    pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
-        self.file = assist_file
-    }
-
     fn build(self) -> AssistAction {
         AssistAction {
             edit: self.edit.finish(),
             cursor_position: self.cursor_position,
             target: self.target,
-            file: self.file,
         }
     }
 }
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index f185cffdb7..ad4ab66ede 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -3,8 +3,8 @@ use ra_syntax::{
     SyntaxKind, SyntaxNode, TextUnit,
 };
 
-use crate::{Assist, AssistCtx, AssistFile, AssistId};
-use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
+use crate::{Assist, AssistCtx, AssistId};
+use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
 use hir::HirDisplay;
 use rustc_hash::{FxHashMap, FxHashSet};
 
@@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 // struct Baz;
 // fn baz() -> Baz { Baz }
 // fn foo() {
-//     bar<|>("", baz());
+//      bar<|>("", baz());
 // }
 //
 // ```
@@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 // struct Baz;
 // fn baz() -> Baz { Baz }
 // fn foo() {
-//     bar("", baz());
+//      bar("", baz());
 // }
 //
 // fn bar(arg: &str, baz: Baz) {
@@ -38,30 +38,21 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
     let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
     let path = path_expr.path()?;
 
+    if path.qualifier().is_some() {
+        return None;
+    }
+
     if ctx.sema.resolve_path(&path).is_some() {
         // The function call already resolves, no need to add a function
         return None;
     }
 
-    let target_module = if let Some(qualifier) = path.qualifier() {
-        if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) =
-            ctx.sema.resolve_path(&qualifier)
-        {
-            Some(module.definition_source(ctx.sema.db))
-        } else {
-            return None;
-        }
-    } else {
-        None
-    };
-
-    let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
+    let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
 
     ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
         edit.target(call.syntax().text_range());
 
         if let Some(function_template) = function_builder.render() {
-            edit.set_file(function_template.file);
             edit.set_cursor(function_template.cursor_offset);
             edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
         }
@@ -72,67 +63,29 @@ struct FunctionTemplate {
     insert_offset: TextUnit,
     cursor_offset: TextUnit,
     fn_def: ast::SourceFile,
-    file: AssistFile,
 }
 
 struct FunctionBuilder {
-    target: GeneratedFunctionTarget,
+    append_fn_at: SyntaxNode,
     fn_name: ast::Name,
     type_params: Option<ast::TypeParamList>,
     params: ast::ParamList,
-    file: AssistFile,
-    needs_pub: bool,
 }
 
 impl FunctionBuilder {
-    /// Prepares a generated function that matches `call` in `generate_in`
-    /// (or as close to `call` as possible, if `generate_in` is `None`)
-    fn from_call(
-        ctx: &AssistCtx,
-        call: &ast::CallExpr,
-        path: &ast::Path,
-        target_module: Option<hir::InFile<hir::ModuleSource>>,
-    ) -> Option<Self> {
-        let needs_pub = target_module.is_some();
-        let mut file = AssistFile::default();
-        let target = if let Some(target_module) = target_module {
-            let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
-            file = in_file;
-            target
-        } else {
-            next_space_for_fn_after_call_site(&call)?
-        };
-        let fn_name = fn_name(&path)?;
+    fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
+        let append_fn_at = next_space_for_fn(&call)?;
+        let fn_name = fn_name(&call)?;
         let (type_params, params) = fn_args(ctx, &call)?;
-        Some(Self { target, fn_name, type_params, params, file, needs_pub })
+        Some(Self { append_fn_at, fn_name, type_params, params })
     }
-
     fn render(self) -> Option<FunctionTemplate> {
         let placeholder_expr = ast::make::expr_todo();
         let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
-        let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
-        if self.needs_pub {
-            fn_def = ast::make::add_pub_crate_modifier(fn_def);
-        }
-
-        let (fn_def, insert_offset) = match self.target {
-            GeneratedFunctionTarget::BehindItem(it) => {
-                let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
-                let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line);
-                (indented, it.text_range().end())
-            }
-            GeneratedFunctionTarget::InEmptyItemList(it) => {
-                let indent_once = IndentLevel(1);
-                let indent = IndentLevel::from_node(it.syntax());
-
-                let fn_def = ast::make::add_leading_newlines(1, fn_def);
-                let fn_def = indent_once.increase_indent(fn_def);
-                let fn_def = ast::make::add_trailing_newlines(1, fn_def);
-                let fn_def = indent.increase_indent(fn_def);
-                (fn_def, it.syntax().text_range().start() + TextUnit::from_usize(1))
-            }
-        };
-
+        let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
+        let fn_def = ast::make::add_newlines(2, fn_def);
+        let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
+        let insert_offset = self.append_fn_at.text_range().end();
         let cursor_offset_from_fn_start = fn_def
             .syntax()
             .descendants()
@@ -141,24 +94,19 @@ impl FunctionBuilder {
             .text_range()
             .start();
         let cursor_offset = insert_offset + cursor_offset_from_fn_start;
-        Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file })
+        Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
     }
 }
 
-enum GeneratedFunctionTarget {
-    BehindItem(SyntaxNode),
-    InEmptyItemList(ast::ItemList),
-}
-
-fn fn_name(call: &ast::Path) -> Option<ast::Name> {
-    let name = call.segment()?.syntax().to_string();
+fn fn_name(call: &CallExpr) -> Option<ast::Name> {
+    let name = call.expr()?.syntax().to_string();
     Some(ast::make::name(&name))
 }
 
 /// Computes the type variables and arguments required for the generated function
 fn fn_args(
     ctx: &AssistCtx,
-    call: &ast::CallExpr,
+    call: &CallExpr,
 ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
     let mut arg_names = Vec::new();
     let mut arg_types = Vec::new();
@@ -210,9 +158,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
     }
 }
 
-fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
+fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
     match fn_arg {
-        ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
+        Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
         _ => Some(
             fn_arg
                 .syntax()
@@ -224,7 +172,7 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
     }
 }
 
-fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
+fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
     let ty = ctx.sema.type_of_expr(fn_arg)?;
     if ty.is_unknown() {
         return None;
@@ -236,7 +184,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
 /// directly after the current block
 /// We want to write the generated function directly after
 /// fns, impls or macro calls, but inside mods
-fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
+fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
     let mut ancestors = expr.syntax().ancestors().peekable();
     let mut last_ancestor: Option<SyntaxNode> = None;
     while let Some(next_ancestor) = ancestors.next() {
@@ -253,32 +201,7 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
         }
         last_ancestor = Some(next_ancestor);
     }
-    last_ancestor.map(GeneratedFunctionTarget::BehindItem)
-}
-
-fn next_space_for_fn_in_module(
-    db: &dyn hir::db::AstDatabase,
-    module: hir::InFile<hir::ModuleSource>,
-) -> Option<(AssistFile, GeneratedFunctionTarget)> {
-    let file = module.file_id.original_file(db);
-    let assist_file = AssistFile::TargetFile(file);
-    let assist_item = match module.value {
-        hir::ModuleSource::SourceFile(it) => {
-            if let Some(last_item) = it.items().last() {
-                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
-            } else {
-                GeneratedFunctionTarget::BehindItem(it.syntax().clone())
-            }
-        }
-        hir::ModuleSource::Module(it) => {
-            if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
-                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
-            } else {
-                GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
-            }
-        }
-    };
-    Some((assist_file, assist_item))
+    last_ancestor
 }
 
 #[cfg(test)]
@@ -790,111 +713,6 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
         )
     }
 
-    #[test]
-    fn add_function_in_module() {
-        check_assist(
-            add_function,
-            r"
-mod bar {}
-
-fn foo() {
-    bar::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    pub(crate) fn my_fn() {
-        <|>todo!()
-    }
-}
-
-fn foo() {
-    bar::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_module_containing_other_items() {
-        check_assist(
-            add_function,
-            r"
-mod bar {
-    fn something_else() {}
-}
-
-fn foo() {
-    bar::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    fn something_else() {}
-
-    pub(crate) fn my_fn() {
-        <|>todo!()
-    }
-}
-
-fn foo() {
-    bar::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_nested_module() {
-        check_assist(
-            add_function,
-            r"
-mod bar {
-    mod baz {}
-}
-
-fn foo() {
-    bar::baz::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    mod baz {
-        pub(crate) fn my_fn() {
-            <|>todo!()
-        }
-    }
-}
-
-fn foo() {
-    bar::baz::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_another_file() {
-        check_assist(
-            add_function,
-            r"
-//- /main.rs
-mod foo;
-
-fn main() {
-    foo::bar<|>()
-}
-//- /foo.rs
-",
-            r"
-
-
-pub(crate) fn bar() {
-    <|>todo!()
-}",
-        )
-    }
-
     #[test]
     fn add_function_not_applicable_if_function_already_exists() {
         check_assist_not_applicable(
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index ccc95735f7..a00136da1c 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -17,7 +17,7 @@ mod doc_tests;
 pub mod utils;
 pub mod ast_transform;
 
-use ra_db::{FileId, FileRange};
+use ra_db::FileRange;
 use ra_ide_db::RootDatabase;
 use ra_syntax::{TextRange, TextUnit};
 use ra_text_edit::TextEdit;
@@ -54,7 +54,6 @@ pub struct AssistAction {
     pub cursor_position: Option<TextUnit>,
     // FIXME: This belongs to `AssistLabel`
     pub target: Option<TextRange>,
-    pub file: AssistFile,
 }
 
 #[derive(Debug, Clone)]
@@ -64,18 +63,6 @@ pub struct ResolvedAssist {
     pub action: AssistAction,
 }
 
-#[derive(Debug, Clone, Copy)]
-pub enum AssistFile {
-    CurrentFile,
-    TargetFile(FileId),
-}
-
-impl Default for AssistFile {
-    fn default() -> Self {
-        Self::CurrentFile
-    }
-}
-
 /// Return all the assists applicable at the given position.
 ///
 /// Assists are returned in the "unresolved" state, that is only labels are
@@ -197,7 +184,7 @@ mod helpers {
     use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
     use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
 
-    use crate::{AssistCtx, AssistFile, AssistHandler};
+    use crate::{AssistCtx, AssistHandler};
     use hir::Semantics;
 
     pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@@ -259,13 +246,7 @@ mod helpers {
             (Some(assist), ExpectedResult::After(after)) => {
                 let action = assist.0[0].action.clone().unwrap();
 
-                let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
-                    db.file_text(file_id).as_ref().to_owned()
-                } else {
-                    text_without_caret
-                };
-
-                let mut actual = action.edit.apply(&assisted_file_text);
+                let mut actual = action.edit.apply(&text_without_caret);
                 match action.cursor_position {
                     None => {
                         if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index 2b5d11681a..40d56a4f7b 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -37,10 +37,6 @@ fn action_to_edit(
     file_id: FileId,
     assist_label: &AssistLabel,
 ) -> SourceChange {
-    let file_id = match action.file {
-        ra_assists::AssistFile::TargetFile(it) => it,
-        _ => file_id,
-    };
     let file_edit = SourceFileEdit { file_id, edit: action.edit };
     SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
         .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index 5a5139e14c..9f795e4414 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -57,7 +57,9 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
                     }
                     match item {
                         hir::AssocItem::Function(func) => {
-                            acc.add_function(ctx, func, None);
+                            if !func.has_self_param(ctx.db) {
+                                acc.add_function(ctx, func, None);
+                            }
                         }
                         hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
                         hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -84,7 +86,9 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
                 }
                 match item {
                     hir::AssocItem::Function(func) => {
-                        acc.add_function(ctx, func, None);
+                        if !func.has_self_param(ctx.db) {
+                            acc.add_function(ctx, func, None);
+                        }
                     }
                     hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
                     hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -478,42 +482,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn completes_struct_associated_method_with_self() {
-        assert_debug_snapshot!(
-            do_reference_completion(
-                "
-                //- /lib.rs
-                /// A Struct
-                struct S;
-
-                impl S {
-                    /// An associated method
-                    fn m(&self) { }
-                }
-
-                fn foo() { let _ = S::<|> }
-                "
-            ),
-            @r###"
-        [
-            CompletionItem {
-                label: "m()",
-                source_range: [105; 105),
-                delete: [105; 105),
-                insert: "m()$0",
-                kind: Method,
-                lookup: "m",
-                detail: "fn m(&self)",
-                documentation: Documentation(
-                    "An associated method",
-                ),
-            },
-        ]
-        "###
-        );
-    }
-
     #[test]
     fn completes_struct_associated_const() {
         assert_debug_snapshot!(
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index 638f86eda0..8e3dcf96e0 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -38,7 +38,15 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext) {
     if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
         if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
             let variants = enum_data.variants(ctx.db);
-            let module = enum_data.module(ctx.db);
+
+            let module = if let Some(module) = ctx.scope().module() {
+                // Compute path from the completion site if available.
+                module
+            } else {
+                // Otherwise fall back to the enum's definition site.
+                enum_data.module(ctx.db)
+            };
+
             for variant in variants {
                 if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
                     // Variants with trivial paths are already added by the existing completion logic,
@@ -1308,4 +1316,47 @@ mod tests {
         "###
         )
     }
+
+    #[test]
+    fn completes_enum_variant_from_module() {
+        assert_debug_snapshot!(
+            do_reference_completion(
+                r"
+                mod m { pub enum E { V } }
+
+                fn f() -> m::E {
+                    V<|>
+                }
+                "
+            ),
+            @r###"
+        [
+            CompletionItem {
+                label: "f()",
+                source_range: [98; 99),
+                delete: [98; 99),
+                insert: "f()$0",
+                kind: Function,
+                lookup: "f",
+                detail: "fn f() -> m::E",
+            },
+            CompletionItem {
+                label: "m",
+                source_range: [98; 99),
+                delete: [98; 99),
+                insert: "m",
+                kind: Module,
+            },
+            CompletionItem {
+                label: "m::E::V",
+                source_range: [98; 99),
+                delete: [98; 99),
+                insert: "m::E::V",
+                kind: EnumVariant,
+                detail: "()",
+            },
+        ]
+        "###
+        )
+    }
 }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
deleted file mode 100644
index 433f2e0c5e..0000000000
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ /dev/null
@@ -1,82 +0,0 @@
-
-<style>
-body                { margin: 0; }
-pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
-
-.lifetime           { color: #DFAF8F; font-style: italic; }
-.comment            { color: #7F9F7F; }
-.struct, .enum      { color: #7CB8BB; }
-.enum_variant       { color: #BDE0F3; }
-.string_literal     { color: #CC9393; }
-.field              { color: #94BFF3; }
-.function           { color: #93E0E3; }
-.parameter          { color: #94BFF3; }
-.text               { color: #DCDCCC; }
-.type               { color: #7CB8BB; }
-.builtin_type       { color: #8CD0D3; }
-.type_param         { color: #DFAF8F; }
-.attribute          { color: #94BFF3; }
-.numeric_literal    { color: #BFEBBF; }
-.macro              { color: #94BFF3; }
-.module             { color: #AFD8AF; }
-.variable           { color: #DCDCCC; }
-.mutable            { text-decoration: underline; }
-
-.keyword            { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe     { color: #BC8383; font-weight: bold; }
-.control            { font-style: italic; }
-</style>
-<pre><code><span class="macro">macro_rules!</span> println {
-    ($($arg:tt)*) =&gt; ({
-        $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
-    })
-}
-#[rustc_builtin_macro]
-<span class="macro">macro_rules!</span> format_args_nl {
-    ($fmt:expr) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
-    ($fmt:expr, $($args:tt)*) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
-}
-
-<span class="keyword">fn</span> <span class="function declaration">main</span>() {
-    <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello"</span>);                 <span class="comment">// =&gt; "Hello"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);   <span class="comment">// =&gt; "Hello, world!"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"The number is </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>);   <span class="comment">// =&gt; "The number is 1"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">?</span><span class="attribute">}</span><span class="string_literal">"</span>, (<span class="numeric_literal">3</span>, <span class="numeric_literal">4</span>));          <span class="comment">// =&gt; "(3, 4)"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">value</span><span class="attribute">}</span><span class="string_literal">"</span>, value=<span class="numeric_literal">4</span>);      <span class="comment">// =&gt; "4"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>);           <span class="comment">// =&gt; "1 2"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">42</span>);             <span class="comment">// =&gt; "0042" with leading zerosV</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>);   <span class="comment">// =&gt; "2 1 1 2"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">argument</span><span class="attribute">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>);   <span class="comment">// =&gt; "test"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>);          <span class="comment">// =&gt; "2 1"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">a</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">c</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">b</span><span class="attribute">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>);  <span class="comment">// =&gt; "a 3 b"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="variable">width</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, width = <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">-</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">^</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">+</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>,    <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>,   <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="variable">number</span><span class="attribute">:</span><span class="attribute">.</span><span class="variable">prec</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, prec = <span class="numeric_literal">5</span>, number = <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 fractional digits"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="numeric_literal">1234.56</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
-
-    <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
-
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
-}</code></pre>
\ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index c0728bfb12..d9912155b9 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -12,7 +12,7 @@ use ra_ide_db::{
 };
 use ra_prof::profile;
 use ra_syntax::{
-    ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
+    ast::{self, HasQuotes, HasStringValue},
     AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
     SyntaxKind::*,
     SyntaxToken, TextRange, WalkEvent, T,
@@ -21,7 +21,6 @@ use rustc_hash::FxHashMap;
 
 use crate::{call_info::ActiveParameter, Analysis, FileId};
 
-use ast::FormatSpecifier;
 pub(crate) use html::highlight_as_html;
 pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
 
@@ -32,81 +31,6 @@ pub struct HighlightedRange {
     pub binding_hash: Option<u64>,
 }
 
-#[derive(Debug)]
-struct HighlightedRangeStack {
-    stack: Vec<Vec<HighlightedRange>>,
-}
-
-/// We use a stack to implement the flattening logic for the highlighted
-/// syntax ranges.
-impl HighlightedRangeStack {
-    fn new() -> Self {
-        Self { stack: vec![Vec::new()] }
-    }
-
-    fn push(&mut self) {
-        self.stack.push(Vec::new());
-    }
-
-    /// Flattens the highlighted ranges.
-    ///
-    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
-    /// 1) parent-range: Attribute [0, 23)
-    /// 2) child-range: String [16, 21)
-    ///
-    /// The following code implements the flattening, for our example this results to:
-    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
-    fn pop(&mut self) {
-        let children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        let needs_flattening = !children.is_empty()
-            && !prev.is_empty()
-            && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
-        if !needs_flattening {
-            prev.extend(children);
-        } else {
-            let mut parent = prev.pop().unwrap();
-            for ele in children {
-                assert!(ele.range.is_subrange(&parent.range));
-                let mut cloned = parent.clone();
-                parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
-                cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
-                if !parent.range.is_empty() {
-                    prev.push(parent);
-                }
-                prev.push(ele);
-                parent = cloned;
-            }
-            if !parent.range.is_empty() {
-                prev.push(parent);
-            }
-        }
-    }
-
-    fn add(&mut self, range: HighlightedRange) {
-        self.stack
-            .last_mut()
-            .expect("during DFS traversal, the stack must not be empty")
-            .push(range)
-    }
-
-    fn flattened(mut self) -> Vec<HighlightedRange> {
-        assert_eq!(
-            self.stack.len(),
-            1,
-            "after DFS traversal, the stack should only contain a single element"
-        );
-        let mut res = self.stack.pop().unwrap();
-        res.sort_by_key(|range| range.range.start());
-        // Check that ranges are sorted and disjoint
-        assert!(res
-            .iter()
-            .zip(res.iter().skip(1))
-            .all(|(left, right)| left.range.end() <= right.range.start()));
-        res
-    }
-}
-
 pub(crate) fn highlight(
     db: &RootDatabase,
     file_id: FileId,
@@ -133,18 +57,52 @@ pub(crate) fn highlight(
     let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
     // We use a stack for the DFS traversal below.
     // When we leave a node, the we use it to flatten the highlighted ranges.
-    let mut stack = HighlightedRangeStack::new();
+    let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()];
 
     let mut current_macro_call: Option<ast::MacroCall> = None;
-    let mut format_string: Option<SyntaxElement> = None;
 
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
     for event in root.preorder_with_tokens() {
         match &event {
-            WalkEvent::Enter(_) => stack.push(),
-            WalkEvent::Leave(_) => stack.pop(),
+            WalkEvent::Enter(_) => res.push(Vec::new()),
+            WalkEvent::Leave(_) => {
+                /* Flattens the highlighted ranges.
+                 *
+                 * For example `#[cfg(feature = "foo")]` contains the nested ranges:
+                 * 1) parent-range: Attribute [0, 23)
+                 * 2) child-range: String [16, 21)
+                 *
+                 * The following code implements the flattening, for our example this results to:
+                 * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
+                 */
+                let children = res.pop().unwrap();
+                let prev = res.last_mut().unwrap();
+                let needs_flattening = !children.is_empty()
+                    && !prev.is_empty()
+                    && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
+                if !needs_flattening {
+                    prev.extend(children);
+                } else {
+                    let mut parent = prev.pop().unwrap();
+                    for ele in children {
+                        assert!(ele.range.is_subrange(&parent.range));
+                        let mut cloned = parent.clone();
+                        parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
+                        cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
+                        if !parent.range.is_empty() {
+                            prev.push(parent);
+                        }
+                        prev.push(ele);
+                        parent = cloned;
+                    }
+                    if !parent.range.is_empty() {
+                        prev.push(parent);
+                    }
+                }
+            }
         };
+        let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
 
         let event_range = match &event {
             WalkEvent::Enter(it) => it.text_range(),
@@ -161,7 +119,7 @@ pub(crate) fn highlight(
             WalkEvent::Enter(Some(mc)) => {
                 current_macro_call = Some(mc.clone());
                 if let Some(range) = macro_call_range(&mc) {
-                    stack.add(HighlightedRange {
+                    current.push(HighlightedRange {
                         range,
                         highlight: HighlightTag::Macro.into(),
                         binding_hash: None,
@@ -172,7 +130,6 @@ pub(crate) fn highlight(
             WalkEvent::Leave(Some(mc)) => {
                 assert!(current_macro_call == Some(mc));
                 current_macro_call = None;
-                format_string = None;
                 continue;
             }
             _ => (),
@@ -193,30 +150,6 @@ pub(crate) fn highlight(
             };
             let token = sema.descend_into_macros(token.clone());
             let parent = token.parent();
-
-            // Check if macro takes a format string and remember it for highlighting later.
-            // The macros that accept a format string expand to a compiler builtin macros
-            // `format_args` and `format_args_nl`.
-            if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) {
-                if let Some(name) =
-                    fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
-                {
-                    match name.text().as_str() {
-                        "format_args" | "format_args_nl" => {
-                            format_string = parent
-                                .children_with_tokens()
-                                .filter(|t| t.kind() != WHITESPACE)
-                                .nth(1)
-                                .filter(|e| {
-                                    ast::String::can_cast(e.kind())
-                                        || ast::RawString::can_cast(e.kind())
-                                })
-                        }
-                        _ => {}
-                    }
-                }
-            }
-
             // We only care Name and Name_ref
             match (token.kind(), parent.kind()) {
                 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -228,72 +161,27 @@ pub(crate) fn highlight(
 
         if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
             let expanded = element_to_highlight.as_token().unwrap().clone();
-            if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
+            if highlight_injection(current, &sema, token, expanded).is_some() {
                 continue;
             }
         }
 
-        let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
-
         if let Some((highlight, binding_hash)) =
-            highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
+            highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
         {
-            stack.add(HighlightedRange { range, highlight, binding_hash });
-            if let Some(string) =
-                element_to_highlight.as_token().cloned().and_then(ast::String::cast)
-            {
-                stack.push();
-                if is_format_string {
-                    string.lex_format_specifier(|piece_range, kind| {
-                        if let Some(highlight) = highlight_format_specifier(kind) {
-                            stack.add(HighlightedRange {
-                                range: piece_range + range.start(),
-                                highlight: highlight.into(),
-                                binding_hash: None,
-                            });
-                        }
-                    });
-                }
-                stack.pop();
-            } else if let Some(string) =
-                element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
-            {
-                stack.push();
-                if is_format_string {
-                    string.lex_format_specifier(|piece_range, kind| {
-                        if let Some(highlight) = highlight_format_specifier(kind) {
-                            stack.add(HighlightedRange {
-                                range: piece_range + range.start(),
-                                highlight: highlight.into(),
-                                binding_hash: None,
-                            });
-                        }
-                    });
-                }
-                stack.pop();
-            }
+            current.push(HighlightedRange { range, highlight, binding_hash });
         }
     }
 
-    stack.flattened()
-}
-
-fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
-    Some(match kind {
-        FormatSpecifier::Open
-        | FormatSpecifier::Close
-        | FormatSpecifier::Colon
-        | FormatSpecifier::Fill
-        | FormatSpecifier::Align
-        | FormatSpecifier::Sign
-        | FormatSpecifier::NumberSign
-        | FormatSpecifier::DollarSign
-        | FormatSpecifier::Dot
-        | FormatSpecifier::Asterisk
-        | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
-        FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
-        FormatSpecifier::Identifier => HighlightTag::Local,
-    })
+    assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
+    let mut res = res.pop().unwrap();
+    res.sort_by_key(|range| range.range.start());
+    // Check that ranges are sorted and disjoint
+    assert!(res
+        .iter()
+        .zip(res.iter().skip(1))
+        .all(|(left, right)| left.range.end() <= right.range.start()));
+    res
 }
 
 fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -471,7 +359,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
 }
 
 fn highlight_injection(
-    acc: &mut HighlightedRangeStack,
+    acc: &mut Vec<HighlightedRange>,
     sema: &Semantics<RootDatabase>,
     literal: ast::RawString,
     expanded: SyntaxToken,
@@ -484,7 +372,7 @@ fn highlight_injection(
     let (analysis, tmp_file_id) = Analysis::from_single_file(value);
 
     if let Some(range) = literal.open_quote_text_range() {
-        acc.add(HighlightedRange {
+        acc.push(HighlightedRange {
             range,
             highlight: HighlightTag::StringLiteral.into(),
             binding_hash: None,
@@ -494,12 +382,12 @@ fn highlight_injection(
     for mut h in analysis.highlight(tmp_file_id).unwrap() {
         if let Some(r) = literal.map_range_up(h.range) {
             h.range = r;
-            acc.add(h)
+            acc.push(h)
         }
     }
 
     if let Some(range) = literal.close_quote_text_range() {
-        acc.add(HighlightedRange {
+        acc.push(HighlightedRange {
             range,
             highlight: HighlightTag::StringLiteral.into(),
             binding_hash: None,
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 0613310f1f..84008b2e34 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -141,11 +141,6 @@ impl CargoWorkspace {
         cargo_toml: &Path,
         cargo_features: &CargoConfig,
     ) -> Result<CargoWorkspace> {
-        let _ = Command::new(cargo_binary())
-            .arg("--version")
-            .status()
-            .context("failed to run `cargo --version`, is `cargo` in PATH?")?;
-
         let mut meta = MetadataCommand::new();
         meta.manifest_path(cargo_toml);
         if cargo_features.all_features {
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index ee0f5cc406..0f4a50be47 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -293,20 +293,11 @@ pub fn fn_def(
     ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
 }
 
-pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
+pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
     let newlines = "\n".repeat(amount_of_newlines);
     ast_from_text(&format!("{}{}", newlines, t.syntax()))
 }
 
-pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
-    let newlines = "\n".repeat(amount_of_newlines);
-    ast_from_text(&format!("{}{}", t.syntax(), newlines))
-}
-
-pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef {
-    ast_from_text(&format!("pub(crate) {}", fn_def))
-}
-
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index aa34b682d9..e8320b57ed 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -172,362 +172,3 @@ impl RawString {
         Some(range + contents_range.start())
     }
 }
-
-#[derive(Debug)]
-pub enum FormatSpecifier {
-    Open,
-    Close,
-    Integer,
-    Identifier,
-    Colon,
-    Fill,
-    Align,
-    Sign,
-    NumberSign,
-    Zero,
-    DollarSign,
-    Dot,
-    Asterisk,
-    QuestionMark,
-}
-
-pub trait HasFormatSpecifier: AstToken {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
-
-    fn lex_format_specifier<F>(&self, mut callback: F)
-    where
-        F: FnMut(TextRange, FormatSpecifier),
-    {
-        let char_ranges = if let Some(char_ranges) = self.char_ranges() {
-            char_ranges
-        } else {
-            return;
-        };
-        let mut chars = char_ranges.iter().peekable();
-
-        while let Some((range, first_char)) = chars.next() {
-            match first_char {
-                Ok('{') => {
-                    // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
-                    if let Some((_, Ok('{'))) = chars.peek() {
-                        // Escaped format specifier, `{{`
-                        chars.next();
-                        continue;
-                    }
-
-                    callback(*range, FormatSpecifier::Open);
-
-                    // check for integer/identifier
-                    match chars
-                        .peek()
-                        .and_then(|next| next.1.as_ref().ok())
-                        .copied()
-                        .unwrap_or_default()
-                    {
-                        '0'..='9' => {
-                            // integer
-                            read_integer(&mut chars, &mut callback);
-                        }
-                        c if c == '_' || c.is_alphabetic() => {
-                            // identifier
-                            read_identifier(&mut chars, &mut callback);
-                        }
-                        _ => {}
-                    }
-
-                    if let Some((_, Ok(':'))) = chars.peek() {
-                        skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
-
-                        // check for fill/align
-                        let mut cloned = chars.clone().take(2);
-                        let first = cloned
-                            .next()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default();
-                        let second = cloned
-                            .next()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default();
-                        match second {
-                            '<' | '^' | '>' => {
-                                // alignment specifier, first char specifies fillment
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Fill,
-                                    &mut callback,
-                                );
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Align,
-                                    &mut callback,
-                                );
-                            }
-                            _ => match first {
-                                '<' | '^' | '>' => {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::Align,
-                                        &mut callback,
-                                    );
-                                }
-                                _ => {}
-                            },
-                        }
-
-                        // check for sign
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '+' | '-' => {
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Sign,
-                                    &mut callback,
-                                );
-                            }
-                            _ => {}
-                        }
-
-                        // check for `#`
-                        if let Some((_, Ok('#'))) = chars.peek() {
-                            skip_char_and_emit(
-                                &mut chars,
-                                FormatSpecifier::NumberSign,
-                                &mut callback,
-                            );
-                        }
-
-                        // check for `0`
-                        let mut cloned = chars.clone().take(2);
-                        let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                        let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-
-                        if first == Some('0') && second != Some('$') {
-                            skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
-                        }
-
-                        // width
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '0'..='9' => {
-                                read_integer(&mut chars, &mut callback);
-                                if let Some((_, Ok('$'))) = chars.peek() {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::DollarSign,
-                                        &mut callback,
-                                    );
-                                }
-                            }
-                            c if c == '_' || c.is_alphabetic() => {
-                                read_identifier(&mut chars, &mut callback);
-                                if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
-                                    != Some('$')
-                                {
-                                    continue;
-                                }
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::DollarSign,
-                                    &mut callback,
-                                );
-                            }
-                            _ => {}
-                        }
-
-                        // precision
-                        if let Some((_, Ok('.'))) = chars.peek() {
-                            skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
-
-                            match chars
-                                .peek()
-                                .and_then(|next| next.1.as_ref().ok())
-                                .copied()
-                                .unwrap_or_default()
-                            {
-                                '*' => {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::Asterisk,
-                                        &mut callback,
-                                    );
-                                }
-                                '0'..='9' => {
-                                    read_integer(&mut chars, &mut callback);
-                                    if let Some((_, Ok('$'))) = chars.peek() {
-                                        skip_char_and_emit(
-                                            &mut chars,
-                                            FormatSpecifier::DollarSign,
-                                            &mut callback,
-                                        );
-                                    }
-                                }
-                                c if c == '_' || c.is_alphabetic() => {
-                                    read_identifier(&mut chars, &mut callback);
-                                    if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
-                                        != Some('$')
-                                    {
-                                        continue;
-                                    }
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::DollarSign,
-                                        &mut callback,
-                                    );
-                                }
-                                _ => {
-                                    continue;
-                                }
-                            }
-                        }
-
-                        // type
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '?' => {
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::QuestionMark,
-                                    &mut callback,
-                                );
-                            }
-                            c if c == '_' || c.is_alphabetic() => {
-                                read_identifier(&mut chars, &mut callback);
-                            }
-                            _ => {}
-                        }
-                    }
-
-                    let mut cloned = chars.clone().take(2);
-                    let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                    let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                    if first != Some('}') {
-                        continue;
-                    }
-                    if second == Some('}') {
-                        // Escaped format end specifier, `}}`
-                        continue;
-                    }
-                    skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
-                }
-                _ => {
-                    while let Some((_, Ok(next_char))) = chars.peek() {
-                        match next_char {
-                            '{' => break,
-                            _ => {}
-                        }
-                        chars.next();
-                    }
-                }
-            };
-        }
-
-        fn skip_char_and_emit<'a, I, F>(
-            chars: &mut std::iter::Peekable<I>,
-            emit: FormatSpecifier,
-            callback: &mut F,
-        ) where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (range, _) = chars.next().unwrap();
-            callback(*range, emit);
-        }
-
-        fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
-        where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (mut range, c) = chars.next().unwrap();
-            assert!(c.as_ref().unwrap().is_ascii_digit());
-            while let Some((r, Ok(next_char))) = chars.peek() {
-                if next_char.is_ascii_digit() {
-                    chars.next();
-                    range = range.extend_to(r);
-                } else {
-                    break;
-                }
-            }
-            callback(range, FormatSpecifier::Integer);
-        }
-
-        fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
-        where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (mut range, c) = chars.next().unwrap();
-            assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
-            while let Some((r, Ok(next_char))) = chars.peek() {
-                if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
-                    chars.next();
-                    range = range.extend_to(r);
-                } else {
-                    break;
-                }
-            }
-            callback(range, FormatSpecifier::Identifier);
-        }
-    }
-}
-
-impl HasFormatSpecifier for String {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
-        let text = self.text().as_str();
-        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-        let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
-
-        let mut res = Vec::with_capacity(text.len());
-        rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| {
-            res.push((
-                TextRange::from_to(
-                    TextUnit::from_usize(range.start),
-                    TextUnit::from_usize(range.end),
-                ) + offset,
-                unescaped_char,
-            ))
-        });
-
-        Some(res)
-    }
-}
-
-impl HasFormatSpecifier for RawString {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
-        let text = self.text().as_str();
-        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-        let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
-
-        let mut res = Vec::with_capacity(text.len());
-        for (idx, c) in text.char_indices() {
-            res.push((
-                TextRange::from_to(
-                    TextUnit::from_usize(idx),
-                    TextUnit::from_usize(idx + c.len_utf8()),
-                ) + offset,
-                Ok(c),
-            ));
-        }
-        Some(res)
-    }
-}
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 6c69436223..6483ba4f3e 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -65,7 +65,7 @@ Adds a stub function with a signature matching the function under the cursor.
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar┃("", baz());
+     bar┃("", baz());
 }
 
 
@@ -73,7 +73,7 @@ fn foo() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar("", baz());
+     bar("", baz());
 }
 
 fn bar(arg: &str, baz: Baz) {
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts
index af8f1df511..399a150c65 100644
--- a/editors/code/src/source_change.ts
+++ b/editors/code/src/source_change.ts
@@ -37,13 +37,11 @@ export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) {
             toReveal.position,
         );
         const editor = vscode.window.activeTextEditor;
-        if (!editor || !editor.selection.isEmpty) {
+        if (!editor || editor.document.uri.toString() !== uri.toString()) {
             return;
         }
-
-        if (editor.document.uri !== uri) {
-            const doc = await vscode.workspace.openTextDocument(uri);
-            await vscode.window.showTextDocument(doc);
+        if (!editor.selection.isEmpty) {
+            return;
         }
         editor.selection = new vscode.Selection(position, position);
         editor.revealRange(


Test patch:
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index e4fa9ee366..b39e60870e 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -66,7 +66,7 @@ fn doctest_add_function() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar<|>("", baz());
+     bar<|>("", baz());
 }
 
 "#####,
@@ -74,7 +74,7 @@ fn foo() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar("", baz());
+     bar("", baz());
 }
 
 fn bar(arg: &str, baz: Baz) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index a9aae957f0..73611e23a5 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -168,73 +168,3 @@ macro_rules! test {}
     );
     let _ = analysis.highlight(file_id).unwrap();
 }
-
-#[test]
-fn test_string_highlighting() {
-    // The format string detection is based on macro-expansion,
-    // thus, we have to copy the macro definition from `std`
-    let (analysis, file_id) = single_file(
-        r#"
-macro_rules! println {
-    ($($arg:tt)*) => ({
-        $crate::io::_print($crate::format_args_nl!($($arg)*));
-    })
-}
-#[rustc_builtin_macro]
-macro_rules! format_args_nl {
-    ($fmt:expr) => {{ /* compiler built-in */ }};
-    ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
-}
-
-fn main() {
-    // from https://doc.rust-lang.org/std/fmt/index.html
-    println!("Hello");                 // => "Hello"
-    println!("Hello, {}!", "world");   // => "Hello, world!"
-    println!("The number is {}", 1);   // => "The number is 1"
-    println!("{:?}", (3, 4));          // => "(3, 4)"
-    println!("{value}", value=4);      // => "4"
-    println!("{} {}", 1, 2);           // => "1 2"
-    println!("{:04}", 42);             // => "0042" with leading zerosV
-    println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
-    println!("{argument}", argument = "test");   // => "test"
-    println!("{name} {}", 1, name = 2);          // => "2 1"
-    println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
-    println!("Hello {:5}!", "x");
-    println!("Hello {:1$}!", "x", 5);
-    println!("Hello {1:0$}!", 5, "x");
-    println!("Hello {:width$}!", "x", width = 5);
-    println!("Hello {:<5}!", "x");
-    println!("Hello {:-<5}!", "x");
-    println!("Hello {:^5}!", "x");
-    println!("Hello {:>5}!", "x");
-    println!("Hello {:+}!", 5);
-    println!("{:#x}!", 27);
-    println!("Hello {:05}!", 5);
-    println!("Hello {:05}!", -5);
-    println!("{:#010x}!", 27);
-    println!("Hello {0} is {1:.5}", "x", 0.01);
-    println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
-    println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
-    println!("Hello {} is {:.*}",    "x", 5, 0.01);
-    println!("Hello {} is {2:.*}",   "x", 5, 0.01);
-    println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
-    println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
-    println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
-    println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
-    println!("Hello {{}}");
-    println!("{{ Hello");
-
-    println!(r"Hello, {}!", "world");
-
-    println!("{\x41}", A = 92);
-    println!("{ничоси}", ничоси = 92);
-}"#
-        .trim(),
-    );
-
-    let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
-    let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
-    let expected_html = &read_text(&dst_file);
-    fs::write(dst_file, &actual_html).unwrap();
-    assert_eq_text!(expected_html, actual_html);
-}