Repository: rust-analyzer/rust-analyzer
Pull request #7110 (2020-12-31 21:00:38)

Deduplicate macros when offering completion
Closes https://github.com/rust-analyzer/rust-analyzer/issues/7081

When iterating over the names within the `hir_def::resolver::Scope` for a module, track what macros are in the `hir_def::item_scope::ItemScope::legacy_macros` collection for the module. When iterating over names from the prelude, do not proccess the name if it had been in the `legacy_macros` collection.

This is implemented with a `FxHashSet` in the `Scope::process_names` function that is populated when iterating over `legacy_macros` and checked when iterating over the prelude.

Alternative implementation could instead query the `legacy_macros` `FxHashMap` directly when processing names in the prelude.

Also, I'd like to add a test for this, but I'm not sure where it could be added.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 1aa0c072d4..fd04ec3c52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,9 +26,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.37"
+version = "1.0.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86"
+checksum = "68803225a7b13e47191bab76f2687382b60d259e8cf37f6e1893658b84bb9479"
 
 [[package]]
 name = "anymap"
@@ -53,7 +53,7 @@ dependencies = [
  "either",
  "hir",
  "ide_db",
- "itertools 0.10.0",
+ "itertools",
  "profile",
  "rustc-hash",
  "stdx",
@@ -211,7 +211,7 @@ dependencies = [
  "chalk-derive",
  "chalk-ir",
  "ena",
- "itertools 0.9.0",
+ "itertools",
  "petgraph",
  "rustc-hash",
  "tracing",
@@ -250,7 +250,7 @@ dependencies = [
  "expect-test",
  "hir",
  "ide_db",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "profile",
  "rustc-hash",
@@ -348,12 +348,6 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
-[[package]]
-name = "dissimilar"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
-
 [[package]]
 name = "drop_bomb"
 version = "0.1.5"
@@ -540,7 +534,7 @@ dependencies = [
  "hir_def",
  "hir_expand",
  "hir_ty",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "profile",
  "rustc-hash",
@@ -563,7 +557,7 @@ dependencies = [
  "fst",
  "hir_expand",
  "indexmap",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "mbe",
  "once_cell",
@@ -607,7 +601,7 @@ dependencies = [
  "expect-test",
  "hir_def",
  "hir_expand",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "once_cell",
  "profile",
@@ -643,7 +637,7 @@ dependencies = [
  "hir",
  "ide_db",
  "indexmap",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "oorandom",
  "profile",
@@ -667,7 +661,7 @@ dependencies = [
  "expect-test",
  "fst",
  "hir",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "once_cell",
  "profile",
@@ -747,20 +741,11 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "itertools"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
-version = "0.4.7"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
 
 [[package]]
 name = "jod-thread"
@@ -1234,7 +1219,7 @@ dependencies = [
  "base_db",
  "cargo_metadata",
  "cfg",
- "itertools 0.10.0",
+ "itertools",
  "log",
  "paths",
  "proc_macro_api",
@@ -1349,7 +1334,6 @@ dependencies = [
  "anyhow",
  "cfg",
  "crossbeam-channel 0.5.0",
- "dissimilar",
  "env_logger",
  "expect-test",
  "flycheck",
@@ -1358,7 +1342,7 @@ dependencies = [
  "hir_ty",
  "ide",
  "ide_db",
- "itertools 0.10.0",
+ "itertools",
  "jod-thread",
  "log",
  "lsp-server",
@@ -1534,9 +1518,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.61"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
 dependencies = [
  "indexmap",
  "itoa",
@@ -1602,7 +1586,7 @@ dependencies = [
  "expect-test",
  "hir",
  "ide_db",
- "itertools 0.10.0",
+ "itertools",
  "rustc-hash",
  "syntax",
  "test_utils",
@@ -1615,9 +1599,9 @@ version = "0.0.0"
 
 [[package]]
 name = "syn"
-version = "1.0.56"
+version = "1.0.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
+checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1643,7 +1627,7 @@ dependencies = [
  "arrayvec",
  "expect-test",
  "indexmap",
- "itertools 0.10.0",
+ "itertools",
  "once_cell",
  "parser",
  "profile",
diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml
index ed8ad666f7..3fd8327d6a 100644
--- a/crates/assists/Cargo.toml
+++ b/crates/assists/Cargo.toml
@@ -11,7 +11,7 @@ doctest = false
 
 [dependencies]
 rustc-hash = "1.1.0"
-itertools = "0.10.0"
+itertools = "0.9.0"
 either = "1.6.1"
 
 stdx = { path = "../stdx", version = "0.0.0" }
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml
index 78e93e78ea..35e169a28c 100644
--- a/crates/completion/Cargo.toml
+++ b/crates/completion/Cargo.toml
@@ -10,7 +10,7 @@ edition = "2018"
 doctest = false
 
 [dependencies]
-itertools = "0.10.0"
+itertools = "0.9.0"
 log = "0.4.8"
 rustc-hash = "1.1.0"
 either = "1.6.1"
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 8695eed398..19ce2482fd 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -234,7 +234,7 @@ fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<Strin
                         current_derive = String::new();
                     }
                 } else {
-                    current_derive.push_str(token.text().trim());
+                    current_derive.push_str(token.to_string().trim());
                 }
             }
 
diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml
index d4ea7327eb..6dc5ad63ba 100644
--- a/crates/hir/Cargo.toml
+++ b/crates/hir/Cargo.toml
@@ -14,7 +14,7 @@ log = "0.4.8"
 rustc-hash = "1.1.0"
 either = "1.5.3"
 arrayvec = "0.5.1"
-itertools = "0.10.0"
+itertools = "0.9.0"
 
 stdx = { path = "../stdx", version = "0.0.0" }
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/hir_def/Cargo.toml b/crates/hir_def/Cargo.toml
index e8b581e2fa..a88b5f57ee 100644
--- a/crates/hir_def/Cargo.toml
+++ b/crates/hir_def/Cargo.toml
@@ -17,7 +17,7 @@ either = "1.5.3"
 anymap = "0.12.1"
 drop_bomb = "0.1.4"
 fst = { version = "0.4", default-features = false }
-itertools = "0.10.0"
+itertools = "0.9.0"
 indexmap = "1.4.0"
 smallvec = "1.4.0"
 
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index 30b22f51df..c4dc894df5 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -432,7 +432,6 @@ fn item_import_kind(item: ItemInNs) -> Option<ImportKind> {
 mod tests {
     use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
     use expect_test::{expect, Expect};
-    use stdx::format_to;
 
     use crate::{data::FunctionData, test_db::TestDB, AssocContainerId, Lookup};
 
@@ -468,10 +467,9 @@ mod tests {
                             if let ItemInNs::Types(ModuleDefId::FunctionId(function_id))
                             | ItemInNs::Values(ModuleDefId::FunctionId(function_id)) = item
                             {
-                                format_to!(
-                                    full_path,
+                                full_path += &format!(
                                     "::{}",
-                                    FunctionData::fn_data_query(&db, function_id).name,
+                                    FunctionData::fn_data_query(&db, function_id).name
                                 );
                             }
                             full_path
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 5682e122db..9bf3587759 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -454,7 +454,7 @@ mod diagnostics {
                                     });
                                 for token in tokens {
                                     if token.kind() == SyntaxKind::IDENT
-                                        && token.text() == name.as_str()
+                                        && token.to_string() == *name
                                     {
                                         precise_location = Some(token.text_range());
                                         break 'outer;
diff --git a/crates/hir_def/src/per_ns.rs b/crates/hir_def/src/per_ns.rs
index 74665c5885..a594afce66 100644
--- a/crates/hir_def/src/per_ns.rs
+++ b/crates/hir_def/src/per_ns.rs
@@ -7,7 +7,7 @@ use hir_expand::MacroDefId;
 
 use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId};
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct PerNs {
     pub types: Option<(ModuleDefId, Visibility)>,
     pub values: Option<(ModuleDefId, Visibility)>,
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index f8cc5e075e..779754ada7 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -483,6 +483,7 @@ pub enum ScopeDef {
 
 impl Scope {
     fn process_names(&self, db: &dyn DefDatabase, f: &mut dyn FnMut(Name, ScopeDef)) {
+        let mut seen = FxHashSet::default();
         match self {
             Scope::ModuleScope(m) => {
                 // FIXME: should we provide `self` here?
@@ -496,7 +497,9 @@ impl Scope {
                     f(name.clone(), ScopeDef::PerNs(def));
                 });
                 m.crate_def_map[m.module_id].scope.legacy_macros().for_each(|(name, macro_)| {
-                    f(name.clone(), ScopeDef::PerNs(PerNs::macros(macro_, Visibility::Public)));
+                    let scope = PerNs::macros(macro_, Visibility::Public);
+                    seen.insert((name.clone(), scope));
+                    f(name.clone(), ScopeDef::PerNs(scope));
                 });
                 m.crate_def_map.extern_prelude.iter().for_each(|(name, &def)| {
                     f(name.clone(), ScopeDef::PerNs(PerNs::types(def, Visibility::Public)));
@@ -507,7 +510,10 @@ impl Scope {
                 if let Some(prelude) = m.crate_def_map.prelude {
                     let prelude_def_map = db.crate_def_map(prelude.krate);
                     prelude_def_map[prelude.local_id].scope.entries().for_each(|(name, def)| {
-                        f(name.clone(), ScopeDef::PerNs(def));
+                        let seen_tuple = (name.clone(), def);
+                        if !seen.contains(&seen_tuple) {
+                            f(seen_tuple.0, ScopeDef::PerNs(def));
+                        }
                     });
                 }
             }
diff --git a/crates/hir_def/src/visibility.rs b/crates/hir_def/src/visibility.rs
index e6e0853a30..f3bc9d6800 100644
--- a/crates/hir_def/src/visibility.rs
+++ b/crates/hir_def/src/visibility.rs
@@ -85,7 +85,7 @@ impl RawVisibility {
 }
 
 /// Visibility of an item, with the path resolved.
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum Visibility {
     /// Visibility is restricted to a certain module.
     Module(ModuleId),
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index 80b60d59fe..6382521fbb 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -259,8 +259,7 @@ fn format_args_expand(
     }
     for arg in &mut args {
         // Remove `key =`.
-        if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint)
-        {
+        if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') {
             arg.drain(..2);
         }
     }
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 2dfccd191b..965c1780a7 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -10,7 +10,7 @@ edition = "2018"
 doctest = false
 
 [dependencies]
-itertools = "0.10.0"
+itertools = "0.9.0"
 arrayvec = "0.5.1"
 smallvec = "1.2.0"
 ena = "0.14.0"
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index f2fc69b2f4..70a3f3075c 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -648,8 +648,6 @@ impl<'a> InferenceContext<'a> {
             }
             Expr::Array(array) => {
                 let elem_ty = match &expected.ty {
-                    // FIXME: remove when https://github.com/rust-lang/rust/issues/80501 is fixed
-                    #[allow(unreachable_patterns)]
                     ty_app!(TypeCtor::Array, st) | ty_app!(TypeCtor::Slice, st) => {
                         st.as_single().clone()
                     }
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index f1544dbe01..4d483580df 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
 [dependencies]
 either = "1.5.3"
 indexmap = "1.4.0"
-itertools = "0.10.0"
+itertools = "0.9.0"
 log = "0.4.8"
 rustc-hash = "1.1.0"
 oorandom = "11.1.2"
diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml
index ebe53c8ee8..0ad6e10009 100644
--- a/crates/ide_db/Cargo.toml
+++ b/crates/ide_db/Cargo.toml
@@ -19,7 +19,7 @@ fst = { version = "0.4", default-features = false }
 rustc-hash = "1.1.0"
 once_cell = "1.3.1"
 either = "1.6.1"
-itertools = "0.10.0"
+itertools = "0.9.0"
 
 stdx = { path = "../stdx", version = "0.0.0" }
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index b3472879d1..7878faaa4c 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -217,14 +217,18 @@ impl MacroRules {
 
 impl Rule {
     fn parse(src: &mut TtIter) -> Result<Rule, ParseError> {
-        let lhs = src
+        let mut lhs = src
             .expect_subtree()
-            .map_err(|()| ParseError::Expected("expected subtree".to_string()))?;
+            .map_err(|()| ParseError::Expected("expected subtree".to_string()))?
+            .clone();
+        lhs.delimiter = None;
         src.expect_char('=').map_err(|()| ParseError::Expected("expected `=`".to_string()))?;
         src.expect_char('>').map_err(|()| ParseError::Expected("expected `>`".to_string()))?;
-        let rhs = src
+        let mut rhs = src
             .expect_subtree()
-            .map_err(|()| ParseError::Expected("expected subtree".to_string()))?;
+            .map_err(|()| ParseError::Expected("expected subtree".to_string()))?
+            .clone();
+        rhs.delimiter = None;
 
         let lhs = MetaTemplate { tokens: parse_pattern(&lhs), delimiter: None };
         let rhs = MetaTemplate { tokens: parse_template(&rhs), delimiter: None };
diff --git a/crates/proc_macro_srv/src/rustc_server.rs b/crates/proc_macro_srv/src/rustc_server.rs
index b54aa1f3bf..503f4c1014 100644
--- a/crates/proc_macro_srv/src/rustc_server.rs
+++ b/crates/proc_macro_srv/src/rustc_server.rs
@@ -204,18 +204,17 @@ pub mod token_stream {
                 let content = subtree
                     .token_trees
                     .iter()
-                    .fold((String::new(), true), |(last, last_to_joint), tkn| {
-                        let s = [last, to_text(tkn)].join(if last_to_joint { "" } else { " " });
-                        let mut is_joint = false;
+                    .map(|tkn| {
+                        let s = to_text(tkn);
                         if let tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) = tkn {
-                            if punct.spacing == tt::Spacing::Joint {
-                                is_joint = true;
+                            if punct.spacing == tt::Spacing::Alone {
+                                return s + " ";
                             }
                         }
-                        (s, is_joint)
+                        s
                     })
-                    .0;
-
+                    .collect::<Vec<_>>()
+                    .concat();
                 let (open, close) = match subtree.delimiter.map(|it| it.kind) {
                     None => ("", ""),
                     Some(tt::DelimiterKind::Brace) => ("{", "}"),
@@ -711,32 +710,4 @@ mod tests {
         assert_eq!(srv.character('c').text, "'c'");
         assert_eq!(srv.byte_string(b"1234586\x88").text, "b\"1234586\\x88\"");
     }
-
-    #[test]
-    fn test_rustc_server_to_string() {
-        let s = TokenStream {
-            subtree: tt::Subtree {
-                delimiter: None,
-                token_trees: vec![
-                    tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
-                        text: "struct".into(),
-                        id: tt::TokenId::unspecified(),
-                    })),
-                    tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
-                        text: "T".into(),
-                        id: tt::TokenId::unspecified(),
-                    })),
-                    tt::TokenTree::Subtree(tt::Subtree {
-                        delimiter: Some(tt::Delimiter {
-                            id: tt::TokenId::unspecified(),
-                            kind: tt::DelimiterKind::Brace,
-                        }),
-                        token_trees: vec![],
-                    }),
-                ],
-            },
-        };
-
-        assert_eq!(s.to_string(), "struct T {}");
-    }
 }
diff --git a/crates/project_model/Cargo.toml b/crates/project_model/Cargo.toml
index a65e422612..c55e857092 100644
--- a/crates/project_model/Cargo.toml
+++ b/crates/project_model/Cargo.toml
@@ -16,7 +16,7 @@ cargo_metadata = "=0.12.0"
 serde = { version = "1.0.106", features = ["derive"] }
 serde_json = "1.0.48"
 anyhow = "1.0.26"
-itertools = "0.10.0"
+itertools = "0.9.0"
 
 arena = { path = "../arena", version = "0.0.0" }
 cfg = { path = "../cfg", version = "0.0.0" }
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index 1700cb8a71..bb3b6f2efb 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -1,7 +1,6 @@
 //! FIXME: write short doc here
 
 use std::{
-    convert::TryInto,
     ffi::OsStr,
     ops,
     path::{Path, PathBuf},
@@ -197,23 +196,8 @@ impl CargoWorkspace {
         if let Some(target) = target {
             meta.other_options(vec![String::from("--filter-platform"), target]);
         }
-
         let mut meta = meta.exec().with_context(|| {
-            let cwd: Option<AbsPathBuf> =
-                std::env::current_dir().ok().and_then(|p| p.try_into().ok());
-
-            let workdir = cargo_toml
-                .parent()
-                .map(|p| p.to_path_buf())
-                .or(cwd)
-                .map(|dir| dir.to_string_lossy().to_string())
-                .unwrap_or_else(|| "<failed to get path>".into());
-
-            format!(
-                "Failed to run `cargo metadata --manifest-path {}` in `{}`",
-                cargo_toml.display(),
-                workdir
-            )
+            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
         })?;
 
         let mut out_dir_by_id = FxHashMap::default();
@@ -350,11 +334,6 @@ pub(crate) fn load_extern_resources(
     let mut cmd = Command::new(toolchain::cargo());
     cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
 
-    // --all-targets includes tests, benches and examples in addition to the
-    // default lib and bins. This is an independent concept from the --targets
-    // flag below.
-    cmd.arg("--all-targets");
-
     if let Some(target) = &cargo_features.target {
         cmd.args(&["--target", target]);
     }
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 0a63593fb4..53e70eaf7b 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -17,9 +17,8 @@ path = "src/bin/main.rs"
 [dependencies]
 anyhow = "1.0.26"
 crossbeam-channel = "0.5.0"
-dissimilar = "1.0.2"
 env_logger = { version = "0.8.1", default-features = false }
-itertools = "0.10.0"
+itertools = "0.9.0"
 jod-thread = "0.1.0"
 log = "0.4.8"
 lsp-types = { version = "0.86.0", features = ["proposed"] }
diff --git a/crates/rust-analyzer/src/diff.rs b/crates/rust-analyzer/src/diff.rs
deleted file mode 100644
index 231be58077..0000000000
--- a/crates/rust-analyzer/src/diff.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-//! Generate minimal `TextEdit`s from different text versions
-use dissimilar::Chunk;
-use ide::{TextEdit, TextRange, TextSize};
-
-pub(crate) fn diff(left: &str, right: &str) -> TextEdit {
-    let chunks = dissimilar::diff(left, right);
-    textedit_from_chunks(chunks)
-}
-
-fn textedit_from_chunks(chunks: Vec<dissimilar::Chunk>) -> TextEdit {
-    let mut builder = TextEdit::builder();
-    let mut pos = TextSize::default();
-
-    let mut chunks = chunks.into_iter().peekable();
-    while let Some(chunk) = chunks.next() {
-        if let (Chunk::Delete(deleted), Some(&Chunk::Insert(inserted))) = (chunk, chunks.peek()) {
-            chunks.next().unwrap();
-            let deleted_len = TextSize::of(deleted);
-            builder.replace(TextRange::at(pos, deleted_len), inserted.into());
-            pos += deleted_len;
-            continue;
-        }
-
-        match chunk {
-            Chunk::Equal(text) => {
-                pos += TextSize::of(text);
-            }
-            Chunk::Delete(deleted) => {
-                let deleted_len = TextSize::of(deleted);
-                builder.delete(TextRange::at(pos, deleted_len));
-                pos += deleted_len;
-            }
-            Chunk::Insert(inserted) => {
-                builder.insert(pos, inserted.into());
-            }
-        }
-    }
-    builder.finish()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn diff_applies() {
-        let mut original = String::from("fn foo(a:u32){\n}");
-        let result = "fn foo(a: u32) {}";
-        let edit = diff(&original, result);
-        edit.apply(&mut original);
-        assert_eq!(original, result);
-    }
-}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 948cfc17ce..23f323f556 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -31,7 +31,6 @@ use serde_json::to_value;
 use stdx::{format_to, split_once};
 use syntax::{algo, ast, AstNode, TextRange, TextSize};
 
-use crate::diff::diff;
 use crate::{
     cargo_target_spec::CargoTargetSpec,
     config::RustfmtConfig,
@@ -841,7 +840,7 @@ pub(crate) fn handle_formatting(
     let crate_ids = snap.analysis.crate_for(file_id)?;
 
     let file_line_index = snap.analysis.file_line_index(file_id)?;
-    let file_line_endings = snap.file_line_endings(file_id);
+    let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
 
     let mut rustfmt = match &snap.config.rustfmt {
         RustfmtConfig::Rustfmt { extra_args } => {
@@ -862,18 +861,16 @@ pub(crate) fn handle_formatting(
         }
     };
 
-    let mut rustfmt =
-        rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
+    let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
 
     rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
 
     let output = rustfmt.wait_with_output()?;
     let captured_stdout = String::from_utf8(output.stdout)?;
-    let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
 
     if !output.status.success() {
         match output.status.code() {
-            Some(1) if !captured_stderr.contains("not installed") => {
+            Some(1) => {
                 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
                 // likely cause exiting with 1. Most Language Servers swallow parse errors on
                 // formatting because otherwise an error is surfaced to the user on top of the
@@ -889,9 +886,8 @@ pub(crate) fn handle_formatting(
                     format!(
                         r#"rustfmt exited with:
                            Status: {}
-                           stdout: {}
-                           stderr: {}"#,
-                        output.status, captured_stdout, captured_stderr,
+                           stdout: {}"#,
+                        output.status, captured_stdout,
                     ),
                 )
                 .into());
@@ -903,11 +899,10 @@ pub(crate) fn handle_formatting(
         // The document is already formatted correctly -- no edits needed.
         Ok(None)
     } else {
-        Ok(Some(to_proto::text_edit_vec(
-            &file_line_index,
-            file_line_endings,
-            diff(&file, &captured_stdout),
-        )))
+        Ok(Some(vec![lsp_types::TextEdit {
+            range: Range::new(Position::new(0, 0), end_position),
+            new_text: captured_stdout,
+        }]))
     }
 }
 
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index c9494e3005..d538ad69a1 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -34,7 +34,6 @@ mod request_metrics;
 mod lsp_utils;
 mod thread_pool;
 mod document;
-mod diff;
 pub mod lsp_ext;
 pub mod config;
 
diff --git a/crates/ssr/Cargo.toml b/crates/ssr/Cargo.toml
index 339eda86a3..98ed25fb69 100644
--- a/crates/ssr/Cargo.toml
+++ b/crates/ssr/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
 
 [dependencies]
 rustc-hash = "1.1.0"
-itertools = "0.10.0"
+itertools = "0.9.0"
 
 text_edit = { path = "../text_edit", version = "0.0.0" }
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index 6cf8314317..99b187311a 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -473,9 +473,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
                         }
                         SyntaxElement::Node(n) => {
                             if let Some(first_token) = n.first_token() {
-                                if Some(first_token.text().as_str())
-                                    == next_pattern_token.as_deref()
-                                {
+                                if Some(first_token.to_string()) == next_pattern_token {
                                     if let Some(SyntaxElement::Node(p)) = pattern.next() {
                                         // We have a subtree that starts with the next token in our pattern.
                                         self.attempt_match_token_tree(phase, &p, &n)?;
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 5d8389ade5..21015591c7 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -11,7 +11,7 @@ edition = "2018"
 doctest = false
 
 [dependencies]
-itertools = "0.10.0"
+itertools = "0.9.0"
 rowan = "0.10.0"
 rustc_lexer = { version = "695.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
diff --git a/editors/code/package.json b/editors/code/package.json
index 587f11b902..13749a0849 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -283,14 +283,6 @@
                     "default": null,
                     "markdownDescription": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then `#rust-analyzer.updates.channel#` setting is not used"
                 },
-                "rust-analyzer.server.extraEnv": {
-                    "type": [
-                        "null",
-                        "object"
-                    ],
-                    "default": null,
-                    "markdownDescription": "Extra environment variables that will be passed to the rust-analyzer executable. Useful for passing e.g. `RA_LOG` for debugging."
-                },
                 "rust-analyzer.trace.server": {
                     "type": "string",
                     "scope": "window",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 539e487ec9..63ab82dde7 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -6,10 +6,6 @@ import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature
 import { assert } from './util';
 import { WorkspaceEdit } from 'vscode';
 
-export interface Env {
-    [name: string]: string;
-}
-
 function renderCommand(cmd: ra.CommandLink) {
     return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
 }
@@ -31,17 +27,14 @@ async function semanticHighlightingWorkaround<R, F extends (...args: any[]) => v
     return res;
 }
 
-export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient {
+export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
     // '.' Is the fallback if no folder is open
     // TODO?: Workspace folders support Uri's (eg: file://test.txt).
     // It might be a good idea to test if the uri points to a file.
 
-    const newEnv = Object.assign({}, process.env);
-    Object.assign(newEnv, extraEnv);
-
     const run: lc.Executable = {
         command: serverPath,
-        options: { cwd, env: newEnv },
+        options: { cwd },
     };
     const serverOptions: lc.ServerOptions = {
         run,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index fe9f3b4a87..848e92af99 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,5 +1,4 @@
 import * as vscode from 'vscode';
-import { Env } from './client';
 import { log } from "./util";
 
 export type UpdatesChannel = "stable" | "nightly";
@@ -14,7 +13,6 @@ export class Config {
     readonly rootSection = "rust-analyzer";
     private readonly requiresReloadOpts = [
         "serverPath",
-        "server",
         "cargo",
         "procMacro",
         "files",
@@ -94,7 +92,6 @@ export class Config {
     }
 
     get serverPath() { return this.get<null | string>("serverPath"); }
-    get serverExtraEnv() { return this.get<Env | null>("server.extraEnv") ?? {}; }
     get channel() { return this.get<UpdatesChannel>("updates.channel"); }
     get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); }
     get traceExtension() { return this.get<boolean>("trace.extension"); }
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index e7585184bd..d39864d330 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -24,7 +24,7 @@ export class Ctx {
         serverPath: string,
         cwd: string,
     ): Promise<Ctx> {
-        const client = createClient(serverPath, cwd, config.serverExtraEnv);
+        const client = createClient(serverPath, cwd);
 
         const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
         extCtx.subscriptions.push(statusBar);


Test patch:
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs
index 84db0856d4..e51eb26261 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/main.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs
@@ -190,10 +190,15 @@ pub use std::collections::HashMap;
         },
         json!([
             {
-                "newText": "",
+                "newText": r#"mod bar;
+
+fn main() {}
+
+pub use std::collections::HashMap;
+"#,
                 "range": {
-                    "end": { "character": 0, "line": 3 },
-                    "start": { "character": 11, "line": 2 }
+                    "end": { "character": 0, "line": 6 },
+                    "start": { "character": 0, "line": 0 }
                 }
             }
         ]),
@@ -243,17 +248,17 @@ pub use std::collections::HashMap;
         },
         json!([
             {
-                "newText": "",
-                "range": {
-                    "end": { "character": 0, "line": 3 },
-                    "start": { "character": 17, "line": 2 }
-                }
-            },
-            {
-                "newText": "",
+                "newText": r#"mod bar;
+
+async fn test() {}
+
+fn main() {}
+
+pub use std::collections::HashMap;
+"#,
                 "range": {
-                    "end": { "character": 0, "line": 6 },
-                    "start": { "character": 11, "line": 5 }
+                    "end": { "character": 0, "line": 9 },
+                    "start": { "character": 0, "line": 0 }
                 }
             }
         ]),