Repository: rust-lang/rust-analyzer
Pull request #15205 (2023-07-03 15:41:07)

internal: Split out project loading capabilities from rust-analyzer crate
External tools currently depend on the entire lsp infra for no good reason so let's lift that out so those tools have something better to depend on


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 1f191cf794..5621224116 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -730,7 +730,7 @@ dependencies = [
  "indexmap 2.0.0",
  "itertools",
  "limit",
- "line-index 0.1.0-pre.1",
+ "line-index 0.1.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "memchr",
  "nohash-hasher",
  "once_cell",
@@ -947,8 +947,6 @@ version = "0.0.0"
 [[package]]
 name = "line-index"
 version = "0.1.0-pre.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cad96769710c1745e11d4f940a8ff36000ade4bbada4285b001cb8aa2f745ce"
 dependencies = [
  "nohash-hasher",
  "text-size",
@@ -956,12 +954,31 @@ dependencies = [
 
 [[package]]
 name = "line-index"
-version = "0.1.0"
+version = "0.1.0-pre.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cad96769710c1745e11d4f940a8ff36000ade4bbada4285b001cb8aa2f745ce"
 dependencies = [
  "nohash-hasher",
  "text-size",
 ]
 
+[[package]]
+name = "load-cargo"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "crossbeam-channel",
+ "ide",
+ "ide-db",
+ "itertools",
+ "proc-macro-api",
+ "project-model",
+ "tracing",
+ "tt",
+ "vfs",
+ "vfs-notify",
+]
+
 [[package]]
 name = "lock_api"
 version = "0.4.10"
@@ -1535,6 +1552,7 @@ dependencies = [
  "ide-db",
  "ide-ssr",
  "itertools",
+ "load-cargo",
  "lsp-server 0.7.1",
  "lsp-types",
  "mbe",
@@ -1564,7 +1582,6 @@ dependencies = [
  "tracing-subscriber",
  "tracing-tree",
  "triomphe",
- "tt",
  "vfs",
  "vfs-notify",
  "winapi",
diff --git a/Cargo.toml b/Cargo.toml
index 83d9fd801e..24889fc142 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,6 +61,7 @@ ide-diagnostics = { path = "./crates/ide-diagnostics", version = "0.0.0" }
 ide-ssr = { path = "./crates/ide-ssr", version = "0.0.0" }
 intern = { path = "./crates/intern", version = "0.0.0" }
 limit = { path = "./crates/limit", version = "0.0.0" }
+load-cargo = { path = "./crates/load-cargo", version = "0.0.0" }
 mbe = { path = "./crates/mbe", version = "0.0.0" }
 parser = { path = "./crates/parser", version = "0.0.0" }
 paths = { path = "./crates/paths", version = "0.0.0" }
diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs
index df2af4c89b..8c49ae1c4a 100644
--- a/crates/hir-def/src/find_path.rs
+++ b/crates/hir-def/src/find_path.rs
@@ -360,7 +360,7 @@ fn calculate_best_path(
                     prefer_no_std,
                 )?;
                 cov_mark::hit!(partially_imported);
-                path.push_segment(info.name.clone());
+                path.push_segment(info.path.segments.last()?.clone());
                 Some(path)
             })
         });
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index d742b2eef0..ec150dc068 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -1,14 +1,13 @@
 //! A map of all publicly exported items in a crate.
 
-use std::collections::hash_map::Entry;
 use std::{fmt, hash::BuildHasherDefault};
 
 use base_db::CrateId;
 use fst::{self, Streamer};
 use hir_expand::name::Name;
-use indexmap::IndexMap;
+use indexmap::{map::Entry, IndexMap};
 use itertools::Itertools;
-use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
+use rustc_hash::{FxHashSet, FxHasher};
 use triomphe::Arc;
 
 use crate::{
@@ -18,23 +17,52 @@ use crate::{
 
 type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
 
-// FIXME: Support aliases: an item may be exported under multiple names, so `ImportInfo` should
-// have `Vec<(Name, ModuleId)>` instead of `(Name, ModuleId)`.
 /// Item import details stored in the `ImportMap`.
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct ImportInfo {
-    /// A name that can be used to import the item, relative to the crate's root.
-    pub name: Name,
+    /// A path that can be used to import the item, relative to the crate's root.
+    pub path: ImportPath,
     /// The module containing this item.
     pub container: ModuleId,
     /// Whether the import is a trait associated item or not.
     pub is_trait_assoc_item: bool,
 }
 
-/// A map from publicly exported items to its name.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct ImportPath {
+    pub segments: Vec<Name>,
+}
+
+impl ImportPath {
+    pub fn display<'a>(&'a self, db: &'a dyn DefDatabase) -> impl fmt::Display + 'a {
+        struct Display<'a> {
+            db: &'a dyn DefDatabase,
+            path: &'a ImportPath,
+        }
+        impl fmt::Display for Display<'_> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                fmt::Display::fmt(
+                    &self.path.segments.iter().map(|it| it.display(self.db.upcast())).format("::"),
+                    f,
+                )
+            }
+        }
+        Display { db, path: self }
+    }
+
+    fn len(&self) -> usize {
+        self.segments.len()
+    }
+}
+
+/// A map from publicly exported items to the path needed to import/name them from a downstream
+/// crate.
 ///
 /// Reexports of items are taken into account, ie. if something is exported under multiple
 /// names, the one with the shortest import path will be used.
+///
+/// Note that all paths are relative to the containing crate's root, so the crate name still needs
+/// to be prepended to the `ModPath` before the path is valid.
 #[derive(Default)]
 pub struct ImportMap {
     map: FxIndexMap<ItemInNs, ImportInfo>,
@@ -42,58 +70,122 @@ pub struct ImportMap {
     /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
     /// values returned by running `fst`.
     ///
-    /// Since a name can refer to multiple items due to namespacing, we store all items with the
-    /// same name right after each other. This allows us to find all items after the FST gives us
+    /// Since a path can refer to multiple items due to namespacing, we store all items with the
+    /// same path right after each other. This allows us to find all items after the FST gives us
     /// the index of the first one.
     importables: Vec<ItemInNs>,
     fst: fst::Map<Vec<u8>>,
 }
 
 impl ImportMap {
-    pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
+    pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
         let _p = profile::span("import_map_query");
 
-        let map = collect_import_map(db, krate);
+        let mut import_map = collect_import_map(db, krate);
 
-        let mut importables: Vec<_> = map
+        let mut importables = import_map
+            .map
             .iter()
-            // We've only collected items, whose name cannot be tuple field.
-            .map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
-            .collect();
-        importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
+            .map(|(item, info)| (item, fst_path(db, &info.path)))
+            .collect::<Vec<_>>();
+        importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2));
 
         // Build the FST, taking care not to insert duplicate values.
+
         let mut builder = fst::MapBuilder::memory();
-        let iter = importables.iter().enumerate().dedup_by(|lhs, rhs| lhs.1 .1 == rhs.1 .1);
-        for (start_idx, (_, name)) in iter {
-            let _ = builder.insert(name, start_idx as u64);
+        let mut last_batch_start = 0;
+
+        for idx in 0..importables.len() {
+            let key = &importables[last_batch_start].1;
+            if let Some((_, fst_path)) = importables.get(idx + 1) {
+                if key == fst_path {
+                    continue;
+                }
+            }
+
+            let _ = builder.insert(key, last_batch_start as u64);
+
+            last_batch_start = idx + 1;
         }
 
-        Arc::new(ImportMap {
-            map,
-            fst: builder.into_map(),
-            importables: importables.into_iter().map(|(item, _)| item).collect(),
-        })
+        import_map.fst = builder.into_map();
+        import_map.importables = importables.iter().map(|&(&item, _)| item).collect();
+
+        Arc::new(import_map)
+    }
+
+    /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
+    pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
+        self.import_info_for(item).map(|it| &it.path)
     }
 
     pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
         self.map.get(&item)
     }
+
+    #[cfg(test)]
+    fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
+        let mut importable_paths: Vec<_> = self
+            .map
+            .iter()
+            .map(|(item, info)| {
+                let ns = match item {
+                    ItemInNs::Types(_) => "t",
+                    ItemInNs::Values(_) => "v",
+                    ItemInNs::Macros(_) => "m",
+                };
+                format!("- {} ({ns})", info.path.display(db))
+            })
+            .collect();
+
+        importable_paths.sort();
+        importable_paths.join("\n")
+    }
+
+    fn collect_trait_assoc_items(
+        &mut self,
+        db: &dyn DefDatabase,
+        tr: TraitId,
+        is_type_in_ns: bool,
+        original_import_info: &ImportInfo,
+    ) {
+        let _p = profile::span("collect_trait_assoc_items");
+        for (assoc_item_name, item) in &db.trait_data(tr).items {
+            let module_def_id = match item {
+                AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
+                AssocItemId::ConstId(c) => ModuleDefId::from(*c),
+                // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
+                // qualifier, ergo no need to store it for imports in import_map
+                AssocItemId::TypeAliasId(_) => {
+                    cov_mark::hit!(type_aliases_ignored);
+                    continue;
+                }
+            };
+            let assoc_item = if is_type_in_ns {
+                ItemInNs::Types(module_def_id)
+            } else {
+                ItemInNs::Values(module_def_id)
+            };
+
+            let mut assoc_item_info = original_import_info.clone();
+            assoc_item_info.path.segments.push(assoc_item_name.to_owned());
+            assoc_item_info.is_trait_assoc_item = true;
+            self.map.insert(assoc_item, assoc_item_info);
+        }
+    }
 }
 
-fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemInNs, ImportInfo> {
+fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
     let _p = profile::span("collect_import_map");
 
     let def_map = db.crate_def_map(krate);
-    let mut map = FxIndexMap::default();
+    let mut import_map = ImportMap::default();
 
     // We look only into modules that are public(ly reexported), starting with the crate root.
+    let empty = ImportPath { segments: vec![] };
     let root = def_map.module_id(DefMap::ROOT);
-    let mut worklist = vec![(root, 0)];
-    // Records items' minimum module depth.
-    let mut depth_map = FxHashMap::default();
-
-    while let Some((module, depth)) = worklist.pop() {
+    let mut worklist = vec![(root, empty)];
+    while let Some((module, mod_path)) = worklist.pop() {
         let ext_def_map;
         let mod_data = if module.krate == krate {
             &def_map[module.local_id]
@@ -113,83 +205,52 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
         });
 
         for (name, per_ns) in visible_items {
-            for item in per_ns.iter_items() {
-                let import_info = ImportInfo {
-                    name: name.clone(),
-                    container: module,
-                    is_trait_assoc_item: false,
-                };
+            let mk_path = || {
+                let mut path = mod_path.clone();
+                path.segments.push(name.clone());
+                path
+            };
 
-                match depth_map.entry(item) {
-                    Entry::Vacant(entry) => {
-                        entry.insert(depth);
-                    }
-                    Entry::Occupied(mut entry) => {
-                        if depth < *entry.get() {
-                            entry.insert(depth);
-                        } else {
-                            continue;
-                        }
-                    }
-                }
+            for item in per_ns.iter_items() {
+                let path = mk_path();
+                let path_len = path.len();
+                let import_info =
+                    ImportInfo { path, container: module, is_trait_assoc_item: false };
 
                 if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
-                    collect_trait_assoc_items(
+                    import_map.collect_trait_assoc_items(
                         db,
-                        &mut map,
                         tr,
                         matches!(item, ItemInNs::Types(_)),
                         &import_info,
                     );
                 }
 
-                map.insert(item, import_info);
+                match import_map.map.entry(item) {
+                    Entry::Vacant(entry) => {
+                        entry.insert(import_info);
+                    }
+                    Entry::Occupied(mut entry) => {
+                        // If the new path is shorter, prefer that one.
+                        if path_len < entry.get().path.len() {
+                            *entry.get_mut() = import_info;
+                        } else {
+                            continue;
+                        }
+                    }
+                }
 
-                // If we've just added a module, descend into it. We might traverse modules
-                // multiple times, but only if the module depth is smaller (else we `continue`
-                // above).
+                // If we've just added a path to a module, descend into it. We might traverse
+                // modules multiple times, but only if the new path to it is shorter than the
+                // first (else we `continue` above).
                 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
-                    worklist.push((mod_id, depth + 1));
+                    worklist.push((mod_id, mk_path()));
                 }
             }
         }
     }
 
-    map
-}
-
-fn collect_trait_assoc_items(
-    db: &dyn DefDatabase,
-    map: &mut FxIndexMap<ItemInNs, ImportInfo>,
-    tr: TraitId,
-    is_type_in_ns: bool,
-    trait_import_info: &ImportInfo,
-) {
-    let _p = profile::span("collect_trait_assoc_items");
-    for (assoc_item_name, item) in &db.trait_data(tr).items {
-        let module_def_id = match item {
-            AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
-            AssocItemId::ConstId(c) => ModuleDefId::from(*c),
-            // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
-            // qualifier, ergo no need to store it for imports in import_map
-            AssocItemId::TypeAliasId(_) => {
-                cov_mark::hit!(type_aliases_ignored);
-                continue;
-            }
-        };
-        let assoc_item = if is_type_in_ns {
-            ItemInNs::Types(module_def_id)
-        } else {
-            ItemInNs::Values(module_def_id)
-        };
-
-        let assoc_item_info = ImportInfo {
-            container: trait_import_info.container,
-            name: assoc_item_name.clone(),
-            is_trait_assoc_item: true,
-        };
-        map.insert(assoc_item, assoc_item_info);
-    }
+    import_map
 }
 
 impl PartialEq for ImportMap {
@@ -203,7 +264,7 @@ impl Eq for ImportMap {}
 
 impl fmt::Debug for ImportMap {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let mut importable_names: Vec<_> = self
+        let mut importable_paths: Vec<_> = self
             .map
             .iter()
             .map(|(item, _)| match item {
@@ -213,40 +274,56 @@ impl fmt::Debug for ImportMap {
             })
             .collect();
 
-        importable_names.sort();
-        f.write_str(&importable_names.join("\n"))
+        importable_paths.sort();
+        f.write_str(&importable_paths.join("\n"))
     }
 }
 
+fn fst_path(db: &dyn DefDatabase, path: &ImportPath) -> String {
+    let _p = profile::span("fst_path");
+    let mut s = path.display(db).to_string();
+    s.make_ascii_lowercase();
+    s
+}
+
+#[derive(Debug, Eq, PartialEq, Hash)]
+pub enum ImportKind {
+    Module,
+    Function,
+    Adt,
+    EnumVariant,
+    Const,
+    Static,
+    Trait,
+    TraitAlias,
+    TypeAlias,
+    BuiltinType,
+    AssociatedItem,
+    Macro,
+}
+
 /// A way to match import map contents against the search query.
 #[derive(Debug)]
-enum SearchMode {
+pub enum SearchMode {
     /// Import map entry should strictly match the query string.
-    Exact,
+    Equals,
+    /// Import map entry should contain the query string.
+    Contains,
     /// Import map entry should contain all letters from the query string,
     /// in the same order, but not necessary adjacent.
     Fuzzy,
 }
 
-/// Three possible ways to search for the name in associated and/or other items.
-#[derive(Debug, Clone, Copy)]
-pub enum AssocSearchMode {
-    /// Search for the name in both associated and other items.
-    Include,
-    /// Search for the name in other items only.
-    Exclude,
-    /// Search for the name in the associated items only.
-    AssocItemsOnly,
-}
-
 #[derive(Debug)]
 pub struct Query {
     query: String,
     lowercased: String,
+    name_only: bool,
+    assoc_items_only: bool,
     search_mode: SearchMode,
-    assoc_mode: AssocSearchMode,
     case_sensitive: bool,
     limit: usize,
+    exclude_import_kinds: FxHashSet<ImportKind>,
 }
 
 impl Query {
@@ -255,21 +332,30 @@ impl Query {
         Self {
             query,
             lowercased,
-            search_mode: SearchMode::Exact,
-            assoc_mode: AssocSearchMode::Include,
+            name_only: false,
+            assoc_items_only: false,
+            search_mode: SearchMode::Contains,
             case_sensitive: false,
-            limit: usize::MAX,
+            limit: usize::max_value(),
+            exclude_import_kinds: FxHashSet::default(),
         }
     }
 
-    /// Fuzzy finds items instead of exact matching.
-    pub fn fuzzy(self) -> Self {
-        Self { search_mode: SearchMode::Fuzzy, ..self }
+    /// Matches entries' names only, ignoring the rest of
+    /// the qualifier.
+    /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
+    pub fn name_only(self) -> Self {
+        Self { name_only: true, ..self }
     }
 
-    /// Specifies whether we want to include associated items in the result.
-    pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
-        Self { assoc_mode, ..self }
+    /// Matches only the entries that are associated items, ignoring the rest.
+    pub fn assoc_items_only(self) -> Self {
+        Self { assoc_items_only: true, ..self }
+    }
+
+    /// Specifies the way to search for the entries using the query.
+    pub fn search_mode(self, search_mode: SearchMode) -> Self {
+        Self { search_mode, ..self }
     }
 
     /// Limits the returned number of items to `limit`.
@@ -282,6 +368,12 @@ impl Query {
         Self { case_sensitive: true, ..self }
     }
 
+    /// Do not include imports of the specified kind in the search results.
+    pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self {
+        self.exclude_import_kinds.insert(import_kind);
+        self
+    }
+
     fn import_matches(
         &self,
         db: &dyn DefDatabase,
@@ -289,36 +381,49 @@ impl Query {
         enforce_lowercase: bool,
     ) -> bool {
         let _p = profile::span("import_map::Query::import_matches");
-        match (import.is_trait_assoc_item, self.assoc_mode) {
-            (true, AssocSearchMode::Exclude) => return false,
-            (false, AssocSearchMode::AssocItemsOnly) => return false,
-            _ => {}
+        if import.is_trait_assoc_item {
+            if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
+                return false;
+            }
+        } else if self.assoc_items_only {
+            return false;
         }
 
-        let mut input = import.name.display(db.upcast()).to_string();
-        let case_insensitive = enforce_lowercase || !self.case_sensitive;
-        if case_insensitive {
+        let mut input = if import.is_trait_assoc_item || self.name_only {
+            import.path.segments.last().unwrap().display(db.upcast()).to_string()
+        } else {
+            import.path.display(db).to_string()
+        };
+        if enforce_lowercase || !self.case_sensitive {
             input.make_ascii_lowercase();
         }
 
-        let query_string = if case_insensitive { &self.lowercased } else { &self.query };
+        let query_string =
+            if !enforce_lowercase && self.case_sensitive { &self.query } else { &self.lowercased };
 
         match self.search_mode {
-            SearchMode::Exact => &input == query_string,
+            SearchMode::Equals => &input == query_string,
+            SearchMode::Contains => input.contains(query_string),
             SearchMode::Fuzzy => {
-                let mut input_chars = input.chars();
-                for query_char in query_string.chars() {
-                    if input_chars.find(|&it| it == query_char).is_none() {
-                        return false;
+                let mut unchecked_query_chars = query_string.chars();
+                let mut mismatching_query_char = unchecked_query_chars.next();
+
+                for input_char in input.chars() {
+                    match mismatching_query_char {
+                        None => return true,
+                        Some(matching_query_char) if matching_query_char == input_char => {
+                            mismatching_query_char = unchecked_query_chars.next();
+                        }
+                        _ => (),
                     }
                 }
-                true
+                mismatching_query_char.is_none()
             }
         }
     }
 }
 
-/// Searches dependencies of `krate` for an importable name matching `query`.
+/// Searches dependencies of `krate` for an importable path matching `query`.
 ///
 /// This returns a list of items that could be imported from dependencies of `krate`.
 pub fn search_dependencies(
@@ -341,44 +446,65 @@ pub fn search_dependencies(
 
     let mut stream = op.union();
 
-    let mut res = FxHashSet::default();
+    let mut all_indexed_values = FxHashSet::default();
     while let Some((_, indexed_values)) = stream.next() {
-        for indexed_value in indexed_values {
-            let import_map = &import_maps[indexed_value.index];
-            let importables = &import_map.importables[indexed_value.value as usize..];
+        all_indexed_values.extend(indexed_values.iter().copied());
+    }
 
-            let common_importable_data = &import_map.map[&importables[0]];
-            if !query.import_matches(db, common_importable_data, true) {
-                continue;
-            }
+    let mut res = FxHashSet::default();
+    for indexed_value in all_indexed_values {
+        let import_map = &import_maps[indexed_value.index];
+        let importables = &import_map.importables[indexed_value.value as usize..];
 
-            // Name shared by the importable items in this group.
-            let common_importable_name =
-                common_importable_data.name.to_smol_str().to_ascii_lowercase();
-            // Add the items from this name group. Those are all subsequent items in
-            // `importables` whose name match `common_importable_name`.
-            let iter = importables
-                .iter()
-                .copied()
-                .take_while(|item| {
-                    common_importable_name
-                        == import_map.map[item].name.to_smol_str().to_ascii_lowercase()
-                })
-                .filter(|item| {
-                    !query.case_sensitive // we've already checked the common importables name case-insensitively
+        let common_importable_data = &import_map.map[&importables[0]];
+        if !query.import_matches(db, common_importable_data, true) {
+            continue;
+        }
+
+        // Path shared by the importable items in this group.
+        let common_importables_path_fst = fst_path(db, &common_importable_data.path);
+        // Add the items from this `ModPath` group. Those are all subsequent items in
+        // `importables` whose paths match `path`.
+        let iter = importables
+            .iter()
+            .copied()
+            .take_while(|item| {
+                common_importables_path_fst == fst_path(db, &import_map.map[item].path)
+            })
+            .filter(|&item| match item_import_kind(item) {
+                Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
+                None => true,
+            })
+            .filter(|item| {
+                !query.case_sensitive // we've already checked the common importables path case-insensitively
                         || query.import_matches(db, &import_map.map[item], false)
-                });
-            res.extend(iter);
+            });
+        res.extend(iter);
 
-            if res.len() >= query.limit {
-                return res;
-            }
+        if res.len() >= query.limit {
+            return res;
         }
     }
 
     res
 }
 
+fn item_import_kind(item: ItemInNs) -> Option<ImportKind> {
+    Some(match item.as_module_def_id()? {
+        ModuleDefId::ModuleId(_) => ImportKind::Module,
+        ModuleDefId::FunctionId(_) => ImportKind::Function,
+        ModuleDefId::AdtId(_) => ImportKind::Adt,
+        ModuleDefId::EnumVariantId(_) => ImportKind::EnumVariant,
+        ModuleDefId::ConstId(_) => ImportKind::Const,
+        ModuleDefId::StaticId(_) => ImportKind::Static,
+        ModuleDefId::TraitId(_) => ImportKind::Trait,
+        ModuleDefId::TraitAliasId(_) => ImportKind::TraitAlias,
+        ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias,
+        ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType,
+        ModuleDefId::MacroId(_) => ImportKind::Macro,
+    })
+}
+
 #[cfg(test)]
 mod tests {
     use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
@@ -388,39 +514,16 @@ mod tests {
 
     use super::*;
 
-    impl ImportMap {
-        fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
-            let mut importable_paths: Vec<_> = self
-                .map
-                .iter()
-                .map(|(item, info)| {
-                    let path = render_path(db, info);
-                    let ns = match item {
-                        ItemInNs::Types(_) => "t",
-                        ItemInNs::Values(_) => "v",
-                        ItemInNs::Macros(_) => "m",
-                    };
-                    format!("- {path} ({ns})")
-                })
-                .collect();
-
-            importable_paths.sort();
-            importable_paths.join("\n")
-        }
-    }
-
     fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
         let db = TestDB::with_files(ra_fixture);
         let crate_graph = db.crate_graph();
         let krate = crate_graph
             .iter()
-            .find(|&krate| {
-                crate_graph[krate]
-                    .display_name
-                    .as_ref()
-                    .is_some_and(|it| &**it.crate_name() == crate_name)
+            .find(|krate| {
+                crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
+                    == Some(crate_name.to_string())
             })
-            .expect("could not find crate");
+            .unwrap();
 
         let actual = search_dependencies(db.upcast(), krate, query)
             .into_iter()
@@ -431,7 +534,7 @@ mod tests {
                 let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
                     Some(assoc_item_path) => (assoc_item_path, "a"),
                     None => (
-                        render_path(&db, dependency_imports.import_info_for(dependency)?),
+                        dependency_imports.path_of(dependency)?.display(&db).to_string(),
                         match dependency {
                             ItemInNs::Types(ModuleDefId::FunctionId(_))
                             | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
@@ -461,25 +564,57 @@ mod tests {
         dependency_imports: &ImportMap,
         dependency: ItemInNs,
     ) -> Option<String> {
-        let (dependency_assoc_item_id, container) = match dependency.as_module_def_id()? {
-            ModuleDefId::FunctionId(id) => (AssocItemId::from(id), id.lookup(db).container),
-            ModuleDefId::ConstId(id) => (AssocItemId::from(id), id.lookup(db).container),
-            ModuleDefId::TypeAliasId(id) => (AssocItemId::from(id), id.lookup(db).container),
+        let dependency_assoc_item_id = match dependency {
+            ItemInNs::Types(ModuleDefId::FunctionId(id))
+            | ItemInNs::Values(ModuleDefId::FunctionId(id)) => AssocItemId::from(id),
+            ItemInNs::Types(ModuleDefId::ConstId(id))
+            | ItemInNs::Values(ModuleDefId::ConstId(id)) => AssocItemId::from(id),
+            ItemInNs::Types(ModuleDefId::TypeAliasId(id))
+            | ItemInNs::Values(ModuleDefId::TypeAliasId(id)) => AssocItemId::from(id),
             _ => return None,
         };
 
-        let ItemContainerId::TraitId(trait_id) = container else {
-            return None;
+        let trait_ = assoc_to_trait(db, dependency)?;
+        if let ModuleDefId::TraitId(tr) = trait_.as_module_def_id()? {
+            let trait_data = db.trait_data(tr);
+            let assoc_item_name =
+                trait_data.items.iter().find_map(|(assoc_item_name, assoc_item_id)| {
+                    if &dependency_assoc_item_id == assoc_item_id {
+                        Some(assoc_item_name)
+                    } else {
+                        None
+                    }
+                })?;
+            return Some(format!(
+                "{}::{}",
+                dependency_imports.path_of(trait_)?.display(db),
+                assoc_item_name.display(db.upcast())
+            ));
+        }
+        None
+    }
+
+    fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
+        let assoc: AssocItemId = match item {
+            ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
+                ModuleDefId::TypeAliasId(it) => it.into(),
+                ModuleDefId::FunctionId(it) => it.into(),
+                ModuleDefId::ConstId(it) => it.into(),
+                _ => return None,
+            },
+            _ => return None,
         };
 
-        let trait_info = dependency_imports.import_info_for(ItemInNs::Types(trait_id.into()))?;
+        let container = match assoc {
+            AssocItemId::FunctionId(it) => it.lookup(db).container,
+            AssocItemId::ConstId(it) => it.lookup(db).container,
+            AssocItemId::TypeAliasId(it) => it.lookup(db).container,
+        };
 
-        let trait_data = db.trait_data(trait_id);
-        let (assoc_item_name, _) = trait_data
-            .items
-            .iter()
-            .find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
-        Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast())))
+        match container {
+            ItemContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
+            _ => None,
+        }
     }
 
     fn check(ra_fixture: &str, expect: Expect) {
@@ -502,24 +637,6 @@ mod tests {
         expect.assert_eq(&actual)
     }
 
-    fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
-        let mut module = info.container;
-        let mut segments = vec![&info.name];
-
-        let def_map = module.def_map(db);
-        assert!(def_map.block_id().is_none(), "block local items should not be in `ImportMap`");
-
-        while let Some(parent) = module.containing_module(db) {
-            let parent_data = &def_map[parent.local_id];
-            let (name, _) =
-                parent_data.children.iter().find(|(_, id)| **id == module.local_id).unwrap();
-            segments.push(name);
-            module = parent;
-        }
-
-        segments.iter().rev().map(|it| it.display(db.upcast())).join("::")
-    }
-
     #[test]
     fn smoke() {
         check(
@@ -636,7 +753,6 @@ mod tests {
     #[test]
     fn module_reexport() {
         // Reexporting modules from a dependency adds all contents to the import map.
-        // XXX: The rendered paths are relative to the defining crate.
         check(
             r"
             //- /main.rs crate:main deps:lib
@@ -652,9 +768,9 @@ mod tests {
                 - module::S (t)
                 - module::S (v)
                 main:
-                - module::S (t)
-                - module::S (v)
                 - reexported_module (t)
+                - reexported_module::S (t)
+                - reexported_module::S (v)
             "#]],
         );
     }
@@ -756,9 +872,10 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string()).fuzzy(),
+            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
             expect![[r#"
                 dep::fmt (t)
+                dep::fmt::Display (t)
                 dep::fmt::Display::FMT_CONST (a)
                 dep::fmt::Display::format_function (a)
                 dep::fmt::Display::format_method (a)
@@ -785,9 +902,7 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string())
-                .fuzzy()
-                .assoc_search_mode(AssocSearchMode::AssocItemsOnly),
+            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
             expect![[r#"
                 dep::fmt::Display::FMT_CONST (a)
                 dep::fmt::Display::format_function (a)
@@ -798,10 +913,23 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string()).fuzzy().assoc_search_mode(AssocSearchMode::Exclude),
+            Query::new("fmt".to_string())
+                .search_mode(SearchMode::Fuzzy)
+                .exclude_import_kind(ImportKind::AssociatedItem),
             expect![[r#"
-                dep::fmt (t)
-            "#]],
+            dep::fmt (t)
+            dep::fmt::Display (t)
+        "#]],
+        );
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("fmt".to_string())
+                .search_mode(SearchMode::Fuzzy)
+                .assoc_items_only()
+                .exclude_import_kind(ImportKind::AssociatedItem),
+            expect![[r#""#]],
         );
     }
 
@@ -834,12 +962,13 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string()).fuzzy(),
+            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
             expect![[r#"
                 dep::Fmt (m)
                 dep::Fmt (t)
                 dep::Fmt (v)
                 dep::fmt (t)
+                dep::fmt::Display (t)
                 dep::fmt::Display::fmt (a)
                 dep::format (f)
             "#]],
@@ -848,12 +977,26 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string()),
+            Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
+            expect![[r#"
+                dep::Fmt (m)
+                dep::Fmt (t)
+                dep::Fmt (v)
+                dep::fmt (t)
+                dep::fmt::Display::fmt (a)
+            "#]],
+        );
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
             expect![[r#"
                 dep::Fmt (m)
                 dep::Fmt (t)
                 dep::Fmt (v)
                 dep::fmt (t)
+                dep::fmt::Display (t)
                 dep::fmt::Display::fmt (a)
             "#]],
         );
@@ -894,6 +1037,7 @@ mod tests {
                 dep::Fmt (t)
                 dep::Fmt (v)
                 dep::fmt (t)
+                dep::fmt::Display (t)
                 dep::fmt::Display::fmt (a)
             "#]],
         );
@@ -901,7 +1045,7 @@ mod tests {
         check_search(
             ra_fixture,
             "main",
-            Query::new("fmt".to_string()),
+            Query::new("fmt".to_string()).name_only(),
             expect![[r#"
                 dep::Fmt (m)
                 dep::Fmt (t)
@@ -966,10 +1110,43 @@ mod tests {
         pub fn no() {}
     "#,
             "main",
-            Query::new("".to_string()).fuzzy().limit(1),
+            Query::new("".to_string()).limit(2),
             expect![[r#"
-                dep::fmt::Display (t)
+                dep::Fmt (m)
+                dep::Fmt (t)
+                dep::Fmt (v)
+                dep::fmt (t)
             "#]],
         );
     }
+
+    #[test]
+    fn search_exclusions() {
+        let ra_fixture = r#"
+            //- /main.rs crate:main deps:dep
+            //- /dep.rs crate:dep
+
+            pub struct fmt;
+            pub struct FMT;
+        "#;
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("FMT".to_string()),
+            expect![[r#"
+                dep::FMT (t)
+                dep::FMT (v)
+                dep::fmt (t)
+                dep::fmt (v)
+            "#]],
+        );
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
+            expect![[r#""#]],
+        );
+    }
 }
diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs
index 4625a3b01a..3860bccec8 100644
--- a/crates/hir-ty/src/autoderef.rs
+++ b/crates/hir-ty/src/autoderef.rs
@@ -36,7 +36,7 @@ pub fn autoderef(
 ) -> impl Iterator<Item = Ty> {
     let mut table = InferenceTable::new(db, env);
     let ty = table.instantiate_canonical(ty);
-    let mut autoderef = Autoderef::new(&mut table, ty, false);
+    let mut autoderef = Autoderef::new(&mut table, ty);
     let mut v = Vec::new();
     while let Some((ty, _steps)) = autoderef.next() {
         // `ty` may contain unresolved inference variables. Since there's no chance they would be
@@ -63,13 +63,12 @@ pub(crate) struct Autoderef<'a, 'db> {
     ty: Ty,
     at_start: bool,
     steps: Vec<(AutoderefKind, Ty)>,
-    explicit: bool,
 }
 
 impl<'a, 'db> Autoderef<'a, 'db> {
-    pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self {
+    pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty) -> Self {
         let ty = table.resolve_ty_shallow(&ty);
-        Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit }
+        Autoderef { table, ty, at_start: true, steps: Vec::new() }
     }
 
     pub(crate) fn step_count(&self) -> usize {
@@ -98,7 +97,7 @@ impl Iterator for Autoderef<'_, '_> {
             return None;
         }
 
-        let (kind, new_ty) = autoderef_step(self.table, self.ty.clone(), self.explicit)?;
+        let (kind, new_ty) = autoderef_step(self.table, self.ty.clone())?;
 
         self.steps.push((kind, self.ty.clone()));
         self.ty = new_ty;
@@ -110,9 +109,8 @@ impl Iterator for Autoderef<'_, '_> {
 pub(crate) fn autoderef_step(
     table: &mut InferenceTable<'_>,
     ty: Ty,
-    explicit: bool,
 ) -> Option<(AutoderefKind, Ty)> {
-    if let Some(derefed) = builtin_deref(table, &ty, explicit) {
+    if let Some(derefed) = builtin_deref(table, &ty, false) {
         Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
     } else {
         Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
@@ -126,6 +124,7 @@ pub(crate) fn builtin_deref<'ty>(
 ) -> Option<&'ty Ty> {
     match ty.kind(Interner) {
         TyKind::Ref(.., ty) => Some(ty),
+        // FIXME: Maybe accept this but diagnose if its not explicit?
         TyKind::Raw(.., ty) if explicit => Some(ty),
         &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => {
             if crate::lang_items::is_box(table.db, adt) {
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 8e7e62c496..7c5fe22bfa 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -377,7 +377,7 @@ impl InferenceTable<'_> {
 
         let snapshot = self.snapshot();
 
-        let mut autoderef = Autoderef::new(self, from_ty.clone(), false);
+        let mut autoderef = Autoderef::new(self, from_ty.clone());
         let mut first_error = None;
         let mut found = None;
 
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 8377eb2349..6c91f8426e 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -316,7 +316,7 @@ impl InferenceContext<'_> {
             }
             Expr::Call { callee, args, .. } => {
                 let callee_ty = self.infer_expr(*callee, &Expectation::none());
-                let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false);
+                let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
                 let (res, derefed_callee) = 'b: {
                     // manual loop to be able to access `derefs.table`
                     while let Some((callee_deref_ty, _)) = derefs.next() {
@@ -1385,7 +1385,7 @@ impl InferenceContext<'_> {
         receiver_ty: &Ty,
         name: &Name,
     ) -> Option<(Ty, Option<FieldId>, Vec<Adjustment>, bool)> {
-        let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone(), false);
+        let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone());
         let mut private_field = None;
         let res = autoderef.by_ref().find_map(|(derefed_ty, _)| {
             let (field_id, parameters) = match derefed_ty.kind(Interner) {
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index 38959b18bf..ab6430e8f1 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -534,7 +534,7 @@ impl ReceiverAdjustments {
         let mut ty = table.resolve_ty_shallow(&ty);
         let mut adjust = Vec::new();
         for _ in 0..self.autoderefs {
-            match autoderef::autoderef_step(table, ty.clone(), true) {
+            match autoderef::autoderef_step(table, ty.clone()) {
                 None => {
                     never!("autoderef not possible for {:?}", ty);
                     ty = TyKind::Error.intern(Interner);
@@ -1012,8 +1012,8 @@ fn iterate_method_candidates_by_receiver(
     let snapshot = table.snapshot();
     // We're looking for methods with *receiver* type receiver_ty. These could
     // be found in any of the derefs of receiver_ty, so we have to go through
-    // that, including raw derefs.
-    let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
+    // that.
+    let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
     while let Some((self_ty, _)) = autoderef.next() {
         iterate_inherent_methods(
             &self_ty,
@@ -1028,7 +1028,7 @@ fn iterate_method_candidates_by_receiver(
 
     table.rollback_to(snapshot);
 
-    let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
+    let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
     while let Some((self_ty, _)) = autoderef.next() {
         iterate_trait_method_candidates(
             &self_ty,
@@ -1504,7 +1504,7 @@ fn autoderef_method_receiver(
     ty: Ty,
 ) -> Vec<(Canonical<Ty>, ReceiverAdjustments)> {
     let mut deref_chain: Vec<_> = Vec::new();
-    let mut autoderef = autoderef::Autoderef::new(table, ty, true);
+    let mut autoderef = autoderef::Autoderef::new(table, ty);
     while let Some((ty, derefs)) = autoderef.next() {
         deref_chain.push((
             autoderef.table.canonicalize(ty).value,
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index c03bc2f41d..3bdd795bea 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -73,7 +73,7 @@ pub(crate) fn replace_derive_with_manual_impl(
         &ctx.sema,
         current_crate,
         NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
-        items_locator::AssocSearchMode::Exclude,
+        items_locator::AssocItemSearch::Exclude,
         Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
     )
     .filter_map(|item| match item.as_module_def()? {
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 2eaa42040a..106d4e1e52 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -231,7 +231,7 @@ pub fn resolve_completion_edits(
             &sema,
             current_crate,
             NameToImport::exact_case_sensitive(imported_name),
-            items_locator::AssocSearchMode::Include,
+            items_locator::AssocItemSearch::Include,
             Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
         );
         let import = items_with_name
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index e52dc35677..901d592c69 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -13,7 +13,7 @@ use syntax::{
 
 use crate::{
     helpers::item_name,
-    items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
+    items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
     RootDatabase,
 };
 
@@ -317,7 +317,7 @@ fn path_applicable_imports(
                 // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
                 //
                 // see also an ignored test under FIXME comment in the qualify_path.rs module
-                AssocSearchMode::Exclude,
+                AssocItemSearch::Exclude,
                 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
             )
             .filter_map(|item| {
@@ -334,7 +334,7 @@ fn path_applicable_imports(
                 sema,
                 current_crate,
                 path_candidate.name.clone(),
-                AssocSearchMode::Include,
+                AssocItemSearch::Include,
                 Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
             )
             .filter_map(|item| {
@@ -483,7 +483,7 @@ fn trait_applicable_items(
         sema,
         current_crate,
         trait_candidate.assoc_item_name.clone(),
-        AssocSearchMode::AssocItemsOnly,
+        AssocItemSearch::AssocItemsOnly,
         Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
     )
     .filter_map(|input| item_as_assoc(db, input))
diff --git a/crates/ide-db/src/items_locator.rs b/crates/ide-db/src/items_locator.rs
index 3f7a3ec2d0..46f1353e2e 100644
--- a/crates/ide-db/src/items_locator.rs
+++ b/crates/ide-db/src/items_locator.rs
@@ -3,7 +3,10 @@
 //! The main reason for this module to exist is the fact that project's items and dependencies' items
 //! are located in different caches, with different APIs.
 use either::Either;
-use hir::{import_map, AsAssocItem, Crate, ItemInNs, Semantics};
+use hir::{
+    import_map::{self, ImportKind},
+    AsAssocItem, Crate, ItemInNs, Semantics,
+};
 use limit::Limit;
 
 use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
@@ -11,14 +14,23 @@ use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
 /// A value to use, when uncertain which limit to pick.
 pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(40);
 
-pub use import_map::AssocSearchMode;
+/// Three possible ways to search for the name in associated and/or other items.
+#[derive(Debug, Clone, Copy)]
+pub enum AssocItemSearch {
+    /// Search for the name in both associated and other items.
+    Include,
+    /// Search for the name in other items only.
+    Exclude,
+    /// Search for the name in the associated items only.
+    AssocItemsOnly,
+}
 
 /// Searches for importable items with the given name in the crate and its dependencies.
 pub fn items_with_name<'a>(
     sema: &'a Semantics<'_, RootDatabase>,
     krate: Crate,
     name: NameToImport,
-    assoc_item_search: AssocSearchMode,
+    assoc_item_search: AssocItemSearch,
     limit: Option<usize>,
 ) -> impl Iterator<Item = ItemInNs> + 'a {
     let _p = profile::span("items_with_name").detail(|| {
@@ -36,7 +48,9 @@ pub fn items_with_name<'a>(
             let mut local_query = symbol_index::Query::new(exact_name.clone());
             local_query.exact();
 
-            let external_query = import_map::Query::new(exact_name);
+            let external_query = import_map::Query::new(exact_name)
+                .name_only()
+                .search_mode(import_map::SearchMode::Equals);
 
             (
                 local_query,
@@ -47,8 +61,17 @@ pub fn items_with_name<'a>(
             let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
 
             let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
-                .fuzzy()
-                .assoc_search_mode(assoc_item_search);
+                .search_mode(import_map::SearchMode::Fuzzy)
+                .name_only();
+            match assoc_item_search {
+                AssocItemSearch::Include => {}
+                AssocItemSearch::Exclude => {
+                    external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
+                }
+                AssocItemSearch::AssocItemsOnly => {
+                    external_query = external_query.assoc_items_only();
+                }
+            }
 
             if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
                 local_query.case_sensitive();
@@ -70,15 +93,13 @@ pub fn items_with_name<'a>(
 fn find_items<'a>(
     sema: &'a Semantics<'_, RootDatabase>,
     krate: Crate,
-    assoc_item_search: AssocSearchMode,
+    assoc_item_search: AssocItemSearch,
     local_query: symbol_index::Query,
     external_query: import_map::Query,
 ) -> impl Iterator<Item = ItemInNs> + 'a {
     let _p = profile::span("find_items");
     let db = sema.db;
 
-    // NOTE: `external_query` includes `assoc_item_search`, so we don't need to
-    // filter on our own.
     let external_importables =
         krate.query_external_importables(db, external_query).map(|external_importable| {
             match external_importable {
@@ -91,15 +112,18 @@ fn find_items<'a>(
     let local_results = local_query
         .search(&symbol_index::crate_symbols(db, krate))
         .into_iter()
-        .filter(move |candidate| match assoc_item_search {
-            AssocSearchMode::Include => true,
-            AssocSearchMode::Exclude => candidate.def.as_assoc_item(db).is_none(),
-            AssocSearchMode::AssocItemsOnly => candidate.def.as_assoc_item(db).is_some(),
-        })
-        .map(|local_candidate| match local_candidate.def {
-            hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
-            def => ItemInNs::from(def),
+        .filter_map(|local_candidate| match local_candidate.def {
+            hir::ModuleDef::Macro(macro_def) => Some(ItemInNs::Macros(macro_def)),
+            def => Some(ItemInNs::from(def)),
         });
 
-    external_importables.chain(local_results)
+    external_importables.chain(local_results).filter(move |&item| match assoc_item_search {
+        AssocItemSearch::Include => true,
+        AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
+        AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
+    })
+}
+
+fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
+    item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)).is_some()
 }
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 3d6cfddb3b..f883ea8d4f 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -155,8 +155,6 @@ impl Default for ExprFillDefaultMode {
 
 #[derive(Debug, Clone)]
 pub struct DiagnosticsConfig {
-    /// Whether native diagnostics are enabled.
-    pub enabled: bool,
     pub proc_macros_enabled: bool,
     pub proc_attr_macros_enabled: bool,
     pub disable_experimental: bool,
@@ -173,7 +171,6 @@ impl DiagnosticsConfig {
         use ide_db::imports::insert_use::ImportGranularity;
 
         Self {
-            enabled: true,
             proc_macros_enabled: Default::default(),
             proc_attr_macros_enabled: Default::default(),
             disable_experimental: Default::default(),
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index cb2a1140ba..f195f78b3a 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -642,7 +642,7 @@ impl Analysis {
         };
 
         self.with_db(|db| {
-            let diagnostic_assists = if diagnostics_config.enabled && include_fixes {
+            let diagnostic_assists = if include_fixes {
                 ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
                     .into_iter()
                     .flat_map(|it| it.fixes.unwrap_or_default())
diff --git a/crates/load-cargo/Cargo.toml b/crates/load-cargo/Cargo.toml
new file mode 100644
index 0000000000..4a6946d7f6
--- /dev/null
+++ b/crates/load-cargo/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "load-cargo"
+version = "0.0.0"
+rust-version.workspace = true
+edition.workspace = true
+license.workspace = true
+authors.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.62"
+crossbeam-channel = "0.5.5"
+itertools = "0.10.5"
+tracing = "0.1.35"
+
+ide.workspace = true
+ide-db.workspace =true
+proc-macro-api.workspace = true
+project-model.workspace = true
+tt.workspace = true
+vfs.workspace = true
+vfs-notify.workspace = true
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
new file mode 100644
index 0000000000..7a795dd62a
--- /dev/null
+++ b/crates/load-cargo/src/lib.rs
@@ -0,0 +1,441 @@
+//! Loads a Cargo project into a static instance of analysis, without support
+//! for incorporating changes.
+// Note, don't remove any public api from this. This API is consumed by external tools
+// to run rust-analyzer as a library.
+use std::{collections::hash_map::Entry, mem, path::Path, sync};
+
+use ::tt::token_id as tt;
+use crossbeam_channel::{unbounded, Receiver};
+use ide::{AnalysisHost, Change, SourceRoot};
+use ide_db::{
+    base_db::{
+        CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
+        ProcMacroLoadResult, ProcMacros,
+    },
+    FxHashMap,
+};
+use itertools::Itertools;
+use proc_macro_api::{MacroDylib, ProcMacroServer};
+use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
+use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
+
+pub struct LoadCargoConfig {
+    pub load_out_dirs_from_check: bool,
+    pub with_proc_macro_server: ProcMacroServerChoice,
+    pub prefill_caches: bool,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ProcMacroServerChoice {
+    Sysroot,
+    Explicit(AbsPathBuf),
+    None,
+}
+
+pub fn load_workspace_at(
+    root: &Path,
+    cargo_config: &CargoConfig,
+    load_config: &LoadCargoConfig,
+    progress: &dyn Fn(String),
+) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
+    let root = ProjectManifest::discover_single(&root)?;
+    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
+
+    if load_config.load_out_dirs_from_check {
+        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
+        workspace.set_build_scripts(build_scripts)
+    }
+
+    load_workspace(workspace, &cargo_config.extra_env, load_config)
+}
+
+pub fn load_workspace(
+    ws: ProjectWorkspace,
+    extra_env: &FxHashMap<String, String>,
+    load_config: &LoadCargoConfig,
+) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+    let (sender, receiver) = unbounded();
+    let mut vfs = vfs::Vfs::default();
+    let mut loader = {
+        let loader =
+            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
+        Box::new(loader)
+    };
+
+    let proc_macro_server = match &load_config.with_proc_macro_server {
+        ProcMacroServerChoice::Sysroot => ws
+            .find_sysroot_proc_macro_srv()
+            .and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
+        ProcMacroServerChoice::Explicit(path) => {
+            ProcMacroServer::spawn(path.clone()).map_err(Into::into)
+        }
+        ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")),
+    };
+
+    let (crate_graph, proc_macros) = ws.to_crate_graph(
+        &mut |path: &AbsPath| {
+            let contents = loader.load_sync(path);
+            let path = vfs::VfsPath::from(path.to_path_buf());
+            vfs.set_file_contents(path.clone(), contents);
+            vfs.file_id(&path)
+        },
+        extra_env,
+    );
+    let proc_macros = {
+        let proc_macro_server = match &proc_macro_server {
+            Ok(it) => Ok(it),
+            Err(e) => Err(e.to_string()),
+        };
+        proc_macros
+            .into_iter()
+            .map(|(crate_id, path)| {
+                (
+                    crate_id,
+                    path.map_or_else(
+                        |_| Err("proc macro crate is missing dylib".to_owned()),
+                        |(_, path)| {
+                            proc_macro_server.as_ref().map_err(Clone::clone).and_then(
+                                |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
+                            )
+                        },
+                    ),
+                )
+            })
+            .collect()
+    };
+
+    let project_folders = ProjectFolders::new(&[ws], &[]);
+    loader.set_config(vfs::loader::Config {
+        load: project_folders.load,
+        watch: vec![],
+        version: 0,
+    });
+
+    let host = load_crate_graph(
+        crate_graph,
+        proc_macros,
+        project_folders.source_root_config,
+        &mut vfs,
+        &receiver,
+    );
+
+    if load_config.prefill_caches {
+        host.analysis().parallel_prime_caches(1, |_| {})?;
+    }
+    Ok((host, vfs, proc_macro_server.ok()))
+}
+
+#[derive(Default)]
+pub struct ProjectFolders {
+    pub load: Vec<vfs::loader::Entry>,
+    pub watch: Vec<usize>,
+    pub source_root_config: SourceRootConfig,
+}
+
+impl ProjectFolders {
+    pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> ProjectFolders {
+        let mut res = ProjectFolders::default();
+        let mut fsc = FileSetConfig::builder();
+        let mut local_filesets = vec![];
+
+        // Dedup source roots
+        // Depending on the project setup, we can have duplicated source roots, or for example in
+        // the case of the rustc workspace, we can end up with two source roots that are almost the
+        // same but not quite, like:
+        // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
+        // PackageRoot {
+        //     is_local: true,
+        //     include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
+        //     exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
+        // }
+        //
+        // The first one comes from the explicit rustc workspace which points to the rustc workspace itself
+        // The second comes from the rustc workspace that we load as the actual project workspace
+        // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
+        // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
+        // so we need to also coalesce the includes if they overlap.
+
+        let mut roots: Vec<_> = workspaces
+            .iter()
+            .flat_map(|ws| ws.to_roots())
+            .update(|root| root.include.sort())
+            .sorted_by(|a, b| a.include.cmp(&b.include))
+            .collect();
+
+        // map that tracks indices of overlapping roots
+        let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
+        let mut done = false;
+
+        while !mem::replace(&mut done, true) {
+            // maps include paths to indices of the corresponding root
+            let mut include_to_idx = FxHashMap::default();
+            // Find and note down the indices of overlapping roots
+            for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
+                for include in &root.include {
+                    match include_to_idx.entry(include) {
+                        Entry::Occupied(e) => {
+                            overlap_map.entry(*e.get()).or_default().push(idx);
+                        }
+                        Entry::Vacant(e) => {
+                            e.insert(idx);
+                        }
+                    }
+                }
+            }
+            for (k, v) in overlap_map.drain() {
+                done = false;
+                for v in v {
+                    let r = mem::replace(
+                        &mut roots[v],
+                        PackageRoot { is_local: false, include: vec![], exclude: vec![] },
+                    );
+                    roots[k].is_local |= r.is_local;
+                    roots[k].include.extend(r.include);
+                    roots[k].exclude.extend(r.exclude);
+                }
+                roots[k].include.sort();
+                roots[k].exclude.sort();
+                roots[k].include.dedup();
+                roots[k].exclude.dedup();
+            }
+        }
+
+        for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
+            let file_set_roots: Vec<VfsPath> =
+                root.include.iter().cloned().map(VfsPath::from).collect();
+
+            let entry = {
+                let mut dirs = vfs::loader::Directories::default();
+                dirs.extensions.push("rs".into());
+                dirs.include.extend(root.include);
+                dirs.exclude.extend(root.exclude);
+                for excl in global_excludes {
+                    if dirs
+                        .include
+                        .iter()
+                        .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
+                    {
+                        dirs.exclude.push(excl.clone());
+                    }
+                }
+
+                vfs::loader::Entry::Directories(dirs)
+            };
+
+            if root.is_local {
+                res.watch.push(res.load.len());
+            }
+            res.load.push(entry);
+
+            if root.is_local {
+                local_filesets.push(fsc.len());
+            }
+            fsc.add_file_set(file_set_roots)
+        }
+
+        let fsc = fsc.build();
+        res.source_root_config = SourceRootConfig { fsc, local_filesets };
+
+        res
+    }
+}
+
+#[derive(Default, Debug)]
+pub struct SourceRootConfig {
+    pub fsc: FileSetConfig,
+    pub local_filesets: Vec<usize>,
+}
+
+impl SourceRootConfig {
+    pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
+        self.fsc
+            .partition(vfs)
+            .into_iter()
+            .enumerate()
+            .map(|(idx, file_set)| {
+                let is_local = self.local_filesets.contains(&idx);
+                if is_local {
+                    SourceRoot::new_local(file_set)
+                } else {
+                    SourceRoot::new_library(file_set)
+                }
+            })
+            .collect()
+    }
+}
+
+/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
+/// with an identity dummy expander.
+pub fn load_proc_macro(
+    server: &ProcMacroServer,
+    path: &AbsPath,
+    dummy_replace: &[Box<str>],
+) -> ProcMacroLoadResult {
+    let res: Result<Vec<_>, String> = (|| {
+        let dylib = MacroDylib::new(path.to_path_buf());
+        let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
+        if vec.is_empty() {
+            return Err("proc macro library returned no proc macros".to_string());
+        }
+        Ok(vec
+            .into_iter()
+            .map(|expander| expander_to_proc_macro(expander, dummy_replace))
+            .collect())
+    })();
+    match res {
+        Ok(proc_macros) => {
+            tracing::info!(
+                "Loaded proc-macros for {path}: {:?}",
+                proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
+            );
+            Ok(proc_macros)
+        }
+        Err(e) => {
+            tracing::warn!("proc-macro loading for {path} failed: {e}");
+            Err(e)
+        }
+    }
+}
+
+fn load_crate_graph(
+    crate_graph: CrateGraph,
+    proc_macros: ProcMacros,
+    source_root_config: SourceRootConfig,
+    vfs: &mut vfs::Vfs,
+    receiver: &Receiver<vfs::loader::Message>,
+) -> AnalysisHost {
+    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
+    let mut host = AnalysisHost::new(lru_cap);
+    let mut analysis_change = Change::new();
+
+    host.raw_database_mut().enable_proc_attr_macros();
+
+    // wait until Vfs has loaded all roots
+    for task in receiver {
+        match task {
+            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
+                if n_done == n_total {
+                    break;
+                }
+            }
+            vfs::loader::Message::Loaded { files } => {
+                for (path, contents) in files {
+                    vfs.set_file_contents(path.into(), contents);
+                }
+            }
+        }
+    }
+    let changes = vfs.take_changes();
+    for file in changes {
+        if file.exists() {
+            let contents = vfs.file_contents(file.file_id);
+            if let Ok(text) = std::str::from_utf8(contents) {
+                analysis_change.change_file(file.file_id, Some(text.into()))
+            }
+        }
+    }
+    let source_roots = source_root_config.partition(vfs);
+    analysis_change.set_roots(source_roots);
+
+    analysis_change.set_crate_graph(crate_graph);
+    analysis_change.set_proc_macros(proc_macros);
+
+    host.apply_change(analysis_change);
+    host
+}
+
+fn expander_to_proc_macro(
+    expander: proc_macro_api::ProcMacro,
+    dummy_replace: &[Box<str>],
+) -> ProcMacro {
+    let name = From::from(expander.name());
+    let kind = match expander.kind() {
+        proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
+        proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
+        proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
+    };
+    let expander: sync::Arc<dyn ProcMacroExpander> =
+        if dummy_replace.iter().any(|replace| &**replace == name) {
+            match kind {
+                ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
+                _ => sync::Arc::new(EmptyExpander),
+            }
+        } else {
+            sync::Arc::new(Expander(expander))
+        };
+    ProcMacro { name, kind, expander }
+}
+
+#[derive(Debug)]
+struct Expander(proc_macro_api::ProcMacro);
+
+impl ProcMacroExpander for Expander {
+    fn expand(
+        &self,
+        subtree: &tt::Subtree,
+        attrs: Option<&tt::Subtree>,
+        env: &Env,
+    ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+        let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
+        match self.0.expand(subtree, attrs, env) {
+            Ok(Ok(subtree)) => Ok(subtree),
+            Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
+            Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
+        }
+    }
+}
+
+/// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
+#[derive(Debug)]
+struct IdentityExpander;
+
+impl ProcMacroExpander for IdentityExpander {
+    fn expand(
+        &self,
+        subtree: &tt::Subtree,
+        _: Option<&tt::Subtree>,
+        _: &Env,
+    ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+        Ok(subtree.clone())
+    }
+}
+
+/// Empty expander, used for proc-macros that are deliberately ignored by the user.
+#[derive(Debug)]
+struct EmptyExpander;
+
+impl ProcMacroExpander for EmptyExpander {
+    fn expand(
+        &self,
+        _: &tt::Subtree,
+        _: Option<&tt::Subtree>,
+        _: &Env,
+    ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+        Ok(tt::Subtree::empty())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use ide_db::base_db::SourceDatabase;
+
+    use super::*;
+
+    #[test]
+    fn test_loading_rust_analyzer() {
+        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
+        let cargo_config = CargoConfig::default();
+        let load_cargo_config = LoadCargoConfig {
+            load_out_dirs_from_check: false,
+            with_proc_macro_server: ProcMacroServerChoice::None,
+            prefill_caches: false,
+        };
+        let (host, _vfs, _proc_macro) =
+            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
+
+        let n_crates = host.raw_database().crate_graph().iter().count();
+        // RA has quite a few crates, but the exact count doesn't matter
+        assert!(n_crates > 20);
+    }
+}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 14104b0955..5bfac7ee45 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -62,13 +62,13 @@ ide-db.workspace = true
 # This should only be used in CLI
 ide-ssr.workspace = true
 ide.workspace = true
+load-cargo.workspace = true
 proc-macro-api.workspace = true
 profile.workspace = true
 project-model.workspace = true
 stdx.workspace = true
 syntax.workspace = true
 toolchain.workspace = true
-tt.workspace = true
 vfs-notify.workspace = true
 vfs.workspace = true
 
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index 893eadf3fa..64646b33ad 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -1,7 +1,6 @@
 //! Various batch processing tasks, intended primarily for debugging.
 
 pub mod flags;
-pub mod load_cargo;
 mod parse;
 mod symbols;
 mod highlight;
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 1ceda8d824..1c09af9abd 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -15,7 +15,7 @@ use hir_def::{
     hir::{ExprId, PatId},
 };
 use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
-use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase};
+use ide::{LineCol, RootDatabase};
 use ide_db::{
     base_db::{
         salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@@ -24,19 +24,18 @@ use ide_db::{
     LineIndexDatabase,
 };
 use itertools::Itertools;
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
 use oorandom::Rand32;
 use profile::{Bytes, StopWatch};
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
 use rayon::prelude::*;
 use rustc_hash::FxHashSet;
 use syntax::{AstNode, SyntaxNode};
-use vfs::{AbsPathBuf, FileId, Vfs, VfsPath};
+use vfs::{AbsPathBuf, Vfs, VfsPath};
 
 use crate::cli::{
     flags::{self, OutputFormat},
-    full_name_of_item,
-    load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
-    print_memory_usage,
+    full_name_of_item, print_memory_usage,
     progress_report::ProgressReport,
     report_metric, Verbosity,
 };
@@ -149,10 +148,8 @@ impl flags::AnalysisStats {
         let mut bodies = Vec::new();
         let mut adts = Vec::new();
         let mut consts = Vec::new();
-        let mut file_ids = Vec::new();
         while let Some(module) = visit_queue.pop() {
             if visited_modules.insert(module) {
-                file_ids.extend(module.as_source_file_id(db));
                 visit_queue.extend(module.children(db));
 
                 for decl in module.declarations(db) {
@@ -226,10 +223,6 @@ impl flags::AnalysisStats {
             self.run_const_eval(db, &consts, verbosity);
         }
 
-        if self.run_all_ide_things {
-            self.run_ide_things(host.analysis(), file_ids);
-        }
-
         let total_span = analysis_sw.elapsed();
         eprintln!("{:<20} {total_span}", "Total:");
         report_metric("total time", total_span.time.as_millis() as u64, "ms");
@@ -735,83 +728,6 @@ impl flags::AnalysisStats {
         report_metric("body lowering time", body_lowering_time.time.as_millis() as u64, "ms");
     }
 
-    fn run_ide_things(&self, analysis: Analysis, mut file_ids: Vec<FileId>) {
-        file_ids.sort();
-        file_ids.dedup();
-        let mut sw = self.stop_watch();
-
-        for &file_id in &file_ids {
-            _ = analysis.diagnostics(
-                &DiagnosticsConfig {
-                    enabled: true,
-                    proc_macros_enabled: true,
-                    proc_attr_macros_enabled: true,
-                    disable_experimental: false,
-                    disabled: Default::default(),
-                    expr_fill_default: Default::default(),
-                    insert_use: ide_db::imports::insert_use::InsertUseConfig {
-                        granularity: ide_db::imports::insert_use::ImportGranularity::Crate,
-                        enforce_granularity: true,
-                        prefix_kind: hir::PrefixKind::ByCrate,
-                        group: true,
-                        skip_glob_imports: true,
-                    },
-                    prefer_no_std: Default::default(),
-                },
-                ide::AssistResolveStrategy::All,
-                file_id,
-            );
-        }
-        for &file_id in &file_ids {
-            _ = analysis.inlay_hints(
-                &InlayHintsConfig {
-                    render_colons: false,
-                    type_hints: true,
-                    discriminant_hints: ide::DiscriminantHints::Always,
-                    parameter_hints: true,
-                    chaining_hints: true,
-                    adjustment_hints: ide::AdjustmentHints::Always,
-                    adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix,
-                    adjustment_hints_hide_outside_unsafe: false,
-                    closure_return_type_hints: ide::ClosureReturnTypeHints::Always,
-                    closure_capture_hints: true,
-                    binding_mode_hints: true,
-                    lifetime_elision_hints: ide::LifetimeElisionHints::Always,
-                    param_names_for_lifetime_elision_hints: true,
-                    hide_named_constructor_hints: false,
-                    hide_closure_initialization_hints: false,
-                    closure_style: hir::ClosureStyle::ImplFn,
-                    max_length: Some(25),
-                    closing_brace_hints_min_lines: Some(20),
-                },
-                file_id,
-                None,
-            );
-        }
-        for &file_id in &file_ids {
-            analysis
-                .annotations(
-                    &AnnotationConfig {
-                        binary_target: true,
-                        annotate_runnables: true,
-                        annotate_impls: true,
-                        annotate_references: false,
-                        annotate_method_references: false,
-                        annotate_enum_variant_references: false,
-                        location: ide::AnnotationLocation::AboveName,
-                    },
-                    file_id,
-                )
-                .unwrap()
-                .into_iter()
-                .for_each(|annotation| {
-                    _ = analysis.resolve_annotation(annotation);
-                });
-        }
-        let ide_time = sw.elapsed();
-        eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
-    }
-
     fn stop_watch(&self) -> StopWatch {
         StopWatch::start().memory(self.memory_usage)
     }
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 0b46f35074..0db5fb4740 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -7,11 +7,9 @@ use rustc_hash::FxHashSet;
 use hir::{db::HirDatabase, Crate, Module};
 use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity};
 use ide_db::base_db::SourceDatabaseExt;
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
 
-use crate::cli::{
-    flags,
-    load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
-};
+use crate::cli::flags;
 
 impl flags::Diagnostics {
     pub fn run(self) -> anyhow::Result<()> {
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index 31be535fc2..9848739504 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -88,10 +88,6 @@ xflags::xflags! {
             optional --skip-data-layout
             /// Skip const evaluation
             optional --skip-const-eval
-            /// Runs several IDE features after analysis, including semantics highlighting, diagnostics
-            /// and annotations. This is useful for benchmarking the memory usage on a project that has
-            /// been worked on for a bit in a longer running session.
-            optional --run-all-ide-things
         }
 
         /// Run unit tests of the project using mir interpreter
@@ -203,7 +199,6 @@ pub struct AnalysisStats {
     pub skip_mir_stats: bool,
     pub skip_data_layout: bool,
     pub skip_const_eval: bool,
-    pub run_all_ide_things: bool,
 }
 
 #[derive(Debug)]
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
deleted file mode 100644
index 1b413029e0..0000000000
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ /dev/null
@@ -1,204 +0,0 @@
-//! Loads a Cargo project into a static instance of analysis, without support
-//! for incorporating changes.
-use std::path::Path;
-
-use crossbeam_channel::{unbounded, Receiver};
-use ide::{AnalysisHost, Change};
-use ide_db::{
-    base_db::{CrateGraph, ProcMacros},
-    FxHashMap,
-};
-use proc_macro_api::ProcMacroServer;
-use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
-use triomphe::Arc;
-use vfs::{loader::Handle, AbsPath, AbsPathBuf};
-
-use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};
-
-// Note: Since this type is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-pub struct LoadCargoConfig {
-    pub load_out_dirs_from_check: bool,
-    pub with_proc_macro_server: ProcMacroServerChoice,
-    pub prefill_caches: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ProcMacroServerChoice {
-    Sysroot,
-    Explicit(AbsPathBuf),
-    None,
-}
-
-// Note: Since this function is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-pub fn load_workspace_at(
-    root: &Path,
-    cargo_config: &CargoConfig,
-    load_config: &LoadCargoConfig,
-    progress: &dyn Fn(String),
-) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
-    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
-    let root = ProjectManifest::discover_single(&root)?;
-    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
-
-    if load_config.load_out_dirs_from_check {
-        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
-        workspace.set_build_scripts(build_scripts)
-    }
-
-    load_workspace(workspace, &cargo_config.extra_env, load_config)
-}
-
-// Note: Since this function is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-//
-// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
-// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
-pub fn load_workspace(
-    ws: ProjectWorkspace,
-    extra_env: &FxHashMap<String, String>,
-    load_config: &LoadCargoConfig,
-) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
-    let (sender, receiver) = unbounded();
-    let mut vfs = vfs::Vfs::default();
-    let mut loader = {
-        let loader =
-            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
-        Box::new(loader)
-    };
-
-    let proc_macro_server = match &load_config.with_proc_macro_server {
-        ProcMacroServerChoice::Sysroot => ws
-            .find_sysroot_proc_macro_srv()
-            .and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
-        ProcMacroServerChoice::Explicit(path) => {
-            ProcMacroServer::spawn(path.clone()).map_err(Into::into)
-        }
-        ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")),
-    };
-
-    let (crate_graph, proc_macros) = ws.to_crate_graph(
-        &mut |path: &AbsPath| {
-            let contents = loader.load_sync(path);
-            let path = vfs::VfsPath::from(path.to_path_buf());
-            vfs.set_file_contents(path.clone(), contents);
-            vfs.file_id(&path)
-        },
-        extra_env,
-    );
-    let proc_macros = {
-        let proc_macro_server = match &proc_macro_server {
-            Ok(it) => Ok(it),
-            Err(e) => Err(e.to_string()),
-        };
-        proc_macros
-            .into_iter()
-            .map(|(crate_id, path)| {
-                (
-                    crate_id,
-                    path.map_or_else(
-                        |_| Err("proc macro crate is missing dylib".to_owned()),
-                        |(_, path)| {
-                            proc_macro_server.as_ref().map_err(Clone::clone).and_then(
-                                |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
-                            )
-                        },
-                    ),
-                )
-            })
-            .collect()
-    };
-
-    let project_folders = ProjectFolders::new(&[ws], &[]);
-    loader.set_config(vfs::loader::Config {
-        load: project_folders.load,
-        watch: vec![],
-        version: 0,
-    });
-
-    tracing::debug!("crate graph: {:?}", crate_graph);
-    let host = load_crate_graph(
-        crate_graph,
-        proc_macros,
-        project_folders.source_root_config,
-        &mut vfs,
-        &receiver,
-    );
-
-    if load_config.prefill_caches {
-        host.analysis().parallel_prime_caches(1, |_| {})?;
-    }
-    Ok((host, vfs, proc_macro_server.ok()))
-}
-
-fn load_crate_graph(
-    crate_graph: CrateGraph,
-    proc_macros: ProcMacros,
-    source_root_config: SourceRootConfig,
-    vfs: &mut vfs::Vfs,
-    receiver: &Receiver<vfs::loader::Message>,
-) -> AnalysisHost {
-    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
-    let mut host = AnalysisHost::new(lru_cap);
-    let mut analysis_change = Change::new();
-
-    host.raw_database_mut().enable_proc_attr_macros();
-
-    // wait until Vfs has loaded all roots
-    for task in receiver {
-        match task {
-            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
-                if n_done == n_total {
-                    break;
-                }
-            }
-            vfs::loader::Message::Loaded { files } => {
-                for (path, contents) in files {
-                    vfs.set_file_contents(path.into(), contents);
-                }
-            }
-        }
-    }
-    let changes = vfs.take_changes();
-    for file in changes {
-        if file.exists() {
-            let contents = vfs.file_contents(file.file_id);
-            if let Ok(text) = std::str::from_utf8(contents) {
-                analysis_change.change_file(file.file_id, Some(Arc::from(text)))
-            }
-        }
-    }
-    let source_roots = source_root_config.partition(vfs);
-    analysis_change.set_roots(source_roots);
-
-    analysis_change.set_crate_graph(crate_graph);
-    analysis_change.set_proc_macros(proc_macros);
-
-    host.apply_change(analysis_change);
-    host
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use hir::Crate;
-
-    #[test]
-    fn test_loading_rust_analyzer() {
-        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
-        let cargo_config = CargoConfig::default();
-        let load_cargo_config = LoadCargoConfig {
-            load_out_dirs_from_check: false,
-            with_proc_macro_server: ProcMacroServerChoice::None,
-            prefill_caches: false,
-        };
-        let (host, _vfs, _proc_macro) =
-            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
-
-        let n_crates = Crate::all(host.raw_database()).len();
-        // RA has quite a few crates, but the exact count doesn't matter
-        assert!(n_crates > 20);
-    }
-}
diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs
index 6506f4cc4f..bb5016349a 100644
--- a/crates/rust-analyzer/src/cli/lsif.rs
+++ b/crates/rust-analyzer/src/cli/lsif.rs
@@ -8,22 +8,22 @@ use ide::{
     Analysis, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, StaticIndex,
     StaticIndexedFile, TokenId, TokenStaticData,
 };
-use ide_db::LineIndexDatabase;
-
-use ide_db::base_db::salsa::{self, ParallelDatabase};
-use ide_db::line_index::WideEncoding;
+use ide_db::{
+    base_db::salsa::{self, ParallelDatabase},
+    line_index::WideEncoding,
+    LineIndexDatabase,
+};
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
 use lsp_types::{self, lsif};
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
 use vfs::{AbsPathBuf, Vfs};
 
-use crate::cli::load_cargo::ProcMacroServerChoice;
-use crate::cli::{
-    flags,
-    load_cargo::{load_workspace, LoadCargoConfig},
+use crate::{
+    cli::flags,
+    line_index::{LineEndings, LineIndex, PositionEncoding},
+    to_proto,
+    version::version,
 };
-use crate::line_index::{LineEndings, LineIndex, PositionEncoding};
-use crate::to_proto;
-use crate::version::version;
 
 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
 struct Snap<DB>(DB);
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 3dd2dc69a1..4579aca302 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -6,22 +6,19 @@ use std::{
     time::Instant,
 };
 
-use crate::{
-    cli::load_cargo::ProcMacroServerChoice,
-    line_index::{LineEndings, LineIndex, PositionEncoding},
-};
 use ide::{
     LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
     TokenStaticData,
 };
 use ide_db::LineIndexDatabase;
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
 use scip::types as scip_types;
 use std::env;
 
-use crate::cli::{
-    flags,
-    load_cargo::{load_workspace, LoadCargoConfig},
+use crate::{
+    cli::flags,
+    line_index::{LineEndings, LineIndex, PositionEncoding},
 };
 
 impl flags::Scip {
@@ -275,7 +272,7 @@ mod test {
         let change_fixture = ChangeFixture::parse(ra_fixture);
         host.raw_database_mut().apply_change(change_fixture.change);
         let (file_id, range_or_offset) =
-            change_fixture.file_position.expect("expected a marker ($0)");
+            change_fixture.file_position.expect("expected a marker ()");
         let offset = range_or_offset.expect_offset();
         (host, FilePosition { file_id, offset })
     }
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 7ccb3e216a..f87dcb889a 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -2,12 +2,10 @@
 
 use anyhow::Context;
 use ide_ssr::MatchFinder;
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
 use project_model::{CargoConfig, RustLibSource};
 
-use crate::cli::{
-    flags,
-    load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
-};
+use crate::cli::flags;
 
 impl flags::Ssr {
     pub fn run(self) -> anyhow::Result<()> {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index fa20c796ec..6355c620f7 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1079,7 +1079,6 @@ impl Config {
 
     pub fn diagnostics(&self) -> DiagnosticsConfig {
         DiagnosticsConfig {
-            enabled: self.data.diagnostics_enable,
             proc_attr_macros_enabled: self.expand_proc_attr_macros(),
             proc_macros_enabled: self.data.procMacro_enable,
             disable_experimental: !self.data.diagnostics_experimental_enable,
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 19a931f65b..ea8a697519 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -9,6 +9,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
 use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
 use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase};
+use load_cargo::SourceRootConfig;
 use lsp_types::{SemanticTokens, Url};
 use nohash_hasher::IntMap;
 use parking_lot::{Mutex, RwLock};
@@ -27,7 +28,7 @@ use crate::{
     main_loop::Task,
     mem_docs::MemDocs,
     op_queue::OpQueue,
-    reload::{self, SourceRootConfig},
+    reload,
     task_pool::TaskPool,
     to_proto::url_from_abs_path,
 };
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index bd9f471a46..5a11012b93 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -20,7 +20,7 @@ use test_utils::project_root;
 use triomphe::Arc;
 use vfs::{AbsPathBuf, VfsPath};
 
-use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
 
 #[test]
 fn integrated_highlighting_benchmark() {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index f9e6152f5b..dd5517e697 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -12,26 +12,22 @@
 //! correct. Instead, we try to provide a best-effort service. Even if the
 //! project is currently loading and we don't have a full project model, we
 //! still want to respond to various  requests.
-use std::{collections::hash_map::Entry, iter, mem, sync};
+use std::{iter, mem};
 
 use flycheck::{FlycheckConfig, FlycheckHandle};
 use hir::db::DefDatabase;
 use ide::Change;
 use ide_db::{
-    base_db::{
-        salsa::Durability, CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
-        ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, SourceRoot, VfsPath,
-    },
+    base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, ProcMacros},
     FxHashMap,
 };
-use itertools::Itertools;
-use proc_macro_api::{MacroDylib, ProcMacroServer};
-use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts};
+use load_cargo::{load_proc_macro, ProjectFolders};
+use proc_macro_api::ProcMacroServer;
+use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
 use rustc_hash::FxHashSet;
 use stdx::{format_to, thread::ThreadIntent};
-use syntax::SmolStr;
 use triomphe::Arc;
-use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
+use vfs::{AbsPath, ChangeKind};
 
 use crate::{
     config::{Config, FilesWatcher, LinkedProject},
@@ -41,8 +37,6 @@ use crate::{
     op_queue::Cause,
 };
 
-use ::tt::token_id as tt;
-
 #[derive(Debug)]
 pub(crate) enum ProjectWorkspaceProgress {
     Begin,
@@ -619,253 +613,6 @@ impl GlobalState {
     }
 }
 
-#[derive(Default)]
-pub(crate) struct ProjectFolders {
-    pub(crate) load: Vec<vfs::loader::Entry>,
-    pub(crate) watch: Vec<usize>,
-    pub(crate) source_root_config: SourceRootConfig,
-}
-
-impl ProjectFolders {
-    pub(crate) fn new(
-        workspaces: &[ProjectWorkspace],
-        global_excludes: &[AbsPathBuf],
-    ) -> ProjectFolders {
-        let mut res = ProjectFolders::default();
-        let mut fsc = FileSetConfig::builder();
-        let mut local_filesets = vec![];
-
-        // Dedup source roots
-        // Depending on the project setup, we can have duplicated source roots, or for example in
-        // the case of the rustc workspace, we can end up with two source roots that are almost the
-        // same but not quite, like:
-        // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
-        // PackageRoot {
-        //     is_local: true,
-        //     include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
-        //     exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
-        // }
-        //
-        // The first one comes from the explicit rustc workspace which points to the rustc workspace itself
-        // The second comes from the rustc workspace that we load as the actual project workspace
-        // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
-        // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
-        // so we need to also coalesce the includes if they overlap.
-
-        let mut roots: Vec<_> = workspaces
-            .iter()
-            .flat_map(|ws| ws.to_roots())
-            .update(|root| root.include.sort())
-            .sorted_by(|a, b| a.include.cmp(&b.include))
-            .collect();
-
-        // map that tracks indices of overlapping roots
-        let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
-        let mut done = false;
-
-        while !mem::replace(&mut done, true) {
-            // maps include paths to indices of the corresponding root
-            let mut include_to_idx = FxHashMap::default();
-            // Find and note down the indices of overlapping roots
-            for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
-                for include in &root.include {
-                    match include_to_idx.entry(include) {
-                        Entry::Occupied(e) => {
-                            overlap_map.entry(*e.get()).or_default().push(idx);
-                        }
-                        Entry::Vacant(e) => {
-                            e.insert(idx);
-                        }
-                    }
-                }
-            }
-            for (k, v) in overlap_map.drain() {
-                done = false;
-                for v in v {
-                    let r = mem::replace(
-                        &mut roots[v],
-                        PackageRoot { is_local: false, include: vec![], exclude: vec![] },
-                    );
-                    roots[k].is_local |= r.is_local;
-                    roots[k].include.extend(r.include);
-                    roots[k].exclude.extend(r.exclude);
-                }
-                roots[k].include.sort();
-                roots[k].exclude.sort();
-                roots[k].include.dedup();
-                roots[k].exclude.dedup();
-            }
-        }
-
-        for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
-            let file_set_roots: Vec<VfsPath> =
-                root.include.iter().cloned().map(VfsPath::from).collect();
-
-            let entry = {
-                let mut dirs = vfs::loader::Directories::default();
-                dirs.extensions.push("rs".into());
-                dirs.include.extend(root.include);
-                dirs.exclude.extend(root.exclude);
-                for excl in global_excludes {
-                    if dirs
-                        .include
-                        .iter()
-                        .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
-                    {
-                        dirs.exclude.push(excl.clone());
-                    }
-                }
-
-                vfs::loader::Entry::Directories(dirs)
-            };
-
-            if root.is_local {
-                res.watch.push(res.load.len());
-            }
-            res.load.push(entry);
-
-            if root.is_local {
-                local_filesets.push(fsc.len());
-            }
-            fsc.add_file_set(file_set_roots)
-        }
-
-        let fsc = fsc.build();
-        res.source_root_config = SourceRootConfig { fsc, local_filesets };
-
-        res
-    }
-}
-
-#[derive(Default, Debug)]
-pub(crate) struct SourceRootConfig {
-    pub(crate) fsc: FileSetConfig,
-    pub(crate) local_filesets: Vec<usize>,
-}
-
-impl SourceRootConfig {
-    pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
-        let _p = profile::span("SourceRootConfig::partition");
-        self.fsc
-            .partition(vfs)
-            .into_iter()
-            .enumerate()
-            .map(|(idx, file_set)| {
-                let is_local = self.local_filesets.contains(&idx);
-                if is_local {
-                    SourceRoot::new_local(file_set)
-                } else {
-                    SourceRoot::new_library(file_set)
-                }
-            })
-            .collect()
-    }
-}
-
-/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
-/// with an identity dummy expander.
-pub(crate) fn load_proc_macro(
-    server: &ProcMacroServer,
-    path: &AbsPath,
-    dummy_replace: &[Box<str>],
-) -> ProcMacroLoadResult {
-    let res: Result<Vec<_>, String> = (|| {
-        let dylib = MacroDylib::new(path.to_path_buf());
-        let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
-        if vec.is_empty() {
-            return Err("proc macro library returned no proc macros".to_string());
-        }
-        Ok(vec
-            .into_iter()
-            .map(|expander| expander_to_proc_macro(expander, dummy_replace))
-            .collect())
-    })();
-    return match res {
-        Ok(proc_macros) => {
-            tracing::info!(
-                "Loaded proc-macros for {path}: {:?}",
-                proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
-            );
-            Ok(proc_macros)
-        }
-        Err(e) => {
-            tracing::warn!("proc-macro loading for {path} failed: {e}");
-            Err(e)
-        }
-    };
-
-    fn expander_to_proc_macro(
-        expander: proc_macro_api::ProcMacro,
-        dummy_replace: &[Box<str>],
-    ) -> ProcMacro {
-        let name = SmolStr::from(expander.name());
-        let kind = match expander.kind() {
-            proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
-            proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
-            proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
-        };
-        let expander: sync::Arc<dyn ProcMacroExpander> =
-            if dummy_replace.iter().any(|replace| &**replace == name) {
-                match kind {
-                    ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
-                    _ => sync::Arc::new(EmptyExpander),
-                }
-            } else {
-                sync::Arc::new(Expander(expander))
-            };
-        ProcMacro { name, kind, expander }
-    }
-
-    #[derive(Debug)]
-    struct Expander(proc_macro_api::ProcMacro);
-
-    impl ProcMacroExpander for Expander {
-        fn expand(
-            &self,
-            subtree: &tt::Subtree,
-            attrs: Option<&tt::Subtree>,
-            env: &Env,
-        ) -> Result<tt::Subtree, ProcMacroExpansionError> {
-            let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
-            match self.0.expand(subtree, attrs, env) {
-                Ok(Ok(subtree)) => Ok(subtree),
-                Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
-                Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
-            }
-        }
-    }
-
-    /// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
-    #[derive(Debug)]
-    struct IdentityExpander;
-
-    impl ProcMacroExpander for IdentityExpander {
-        fn expand(
-            &self,
-            subtree: &tt::Subtree,
-            _: Option<&tt::Subtree>,
-            _: &Env,
-        ) -> Result<tt::Subtree, ProcMacroExpansionError> {
-            Ok(subtree.clone())
-        }
-    }
-
-    /// Empty expander, used for proc-macros that are deliberately ignored by the user.
-    #[derive(Debug)]
-    struct EmptyExpander;
-
-    impl ProcMacroExpander for EmptyExpander {
-        fn expand(
-            &self,
-            _: &tt::Subtree,
-            _: Option<&tt::Subtree>,
-            _: &Env,
-        ) -> Result<tt::Subtree, ProcMacroExpansionError> {
-            Ok(tt::Subtree::empty())
-        }
-    }
-}
-
 pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
     const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
     const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
diff --git a/lib/line-index/Cargo.toml b/lib/line-index/Cargo.toml
index 6c0d06f471..b267daa205 100644
--- a/lib/line-index/Cargo.toml
+++ b/lib/line-index/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "line-index"
-version = "0.1.0"
+version = "0.1.0-pre.1"
 description = "Maps flat `TextSize` offsets to/from `(line, column)` representation."
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/line-index"
diff --git a/lib/line-index/src/lib.rs b/lib/line-index/src/lib.rs
index 58db93a589..ad67d3f246 100644
--- a/lib/line-index/src/lib.rs
+++ b/lib/line-index/src/lib.rs
@@ -94,7 +94,44 @@ pub struct LineIndex {
 impl LineIndex {
     /// Returns a `LineIndex` for the `text`.
     pub fn new(text: &str) -> LineIndex {
-        let (newlines, line_wide_chars) = analyze_source_file(text);
+        let mut newlines = Vec::<TextSize>::with_capacity(16);
+        let mut line_wide_chars = IntMap::<u32, Box<[WideChar]>>::default();
+
+        let mut wide_chars = Vec::<WideChar>::new();
+        let mut cur_row = TextSize::from(0);
+        let mut cur_col = TextSize::from(0);
+        let mut line = 0u32;
+
+        for c in text.chars() {
+            let c_len = TextSize::of(c);
+            cur_row += c_len;
+            if c == '\n' {
+                newlines.push(cur_row);
+
+                // Save any wide characters seen in the previous line
+                if !wide_chars.is_empty() {
+                    let cs = std::mem::take(&mut wide_chars).into_boxed_slice();
+                    line_wide_chars.insert(line, cs);
+                }
+
+                // Prepare for processing the next line
+                cur_col = TextSize::from(0);
+                line += 1;
+                continue;
+            }
+
+            if !c.is_ascii() {
+                wide_chars.push(WideChar { start: cur_col, end: cur_col + c_len });
+            }
+
+            cur_col += c_len;
+        }
+
+        // Save any wide characters seen in the last line
+        if !wide_chars.is_empty() {
+            line_wide_chars.insert(line, wide_chars.into_boxed_slice());
+        }
+
         LineIndex {
             newlines: newlines.into_boxed_slice(),
             line_wide_chars,
@@ -198,182 +235,3 @@ impl LineIndex {
         self.len
     }
 }
-
-/// This is adapted from the rustc_span crate, https://github.com/rust-lang/rust/blob/master/compiler/rustc_span/src/analyze_source_file.rs
-fn analyze_source_file(src: &str) -> (Vec<TextSize>, IntMap<u32, Box<[WideChar]>>) {
-    assert!(src.len() < !0u32 as usize);
-    let mut lines = vec![];
-    let mut line_wide_chars = IntMap::<u32, Vec<WideChar>>::default();
-
-    // Calls the right implementation, depending on hardware support available.
-    analyze_source_file_dispatch(src, &mut lines, &mut line_wide_chars);
-
-    (lines, line_wide_chars.into_iter().map(|(k, v)| (k, v.into_boxed_slice())).collect())
-}
-
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-fn analyze_source_file_dispatch(
-    src: &str,
-    lines: &mut Vec<TextSize>,
-    multi_byte_chars: &mut IntMap<u32, Vec<WideChar>>,
-) {
-    if is_x86_feature_detected!("sse2") {
-        // SAFETY: SSE2 support was checked
-        unsafe {
-            analyze_source_file_sse2(src, lines, multi_byte_chars);
-        }
-    } else {
-        analyze_source_file_generic(src, src.len(), TextSize::from(0), lines, multi_byte_chars);
-    }
-}
-
-/// Checks 16 byte chunks of text at a time. If the chunk contains
-/// something other than printable ASCII characters and newlines, the
-/// function falls back to the generic implementation. Otherwise it uses
-/// SSE2 intrinsics to quickly find all newlines.
-#[target_feature(enable = "sse2")]
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-unsafe fn analyze_source_file_sse2(
-    src: &str,
-    lines: &mut Vec<TextSize>,
-    multi_byte_chars: &mut IntMap<u32, Vec<WideChar>>,
-) {
-    #[cfg(target_arch = "x86")]
-    use std::arch::x86::*;
-    #[cfg(target_arch = "x86_64")]
-    use std::arch::x86_64::*;
-
-    const CHUNK_SIZE: usize = 16;
-
-    let src_bytes = src.as_bytes();
-
-    let chunk_count = src.len() / CHUNK_SIZE;
-
-    // This variable keeps track of where we should start decoding a
-    // chunk. If a multi-byte character spans across chunk boundaries,
-    // we need to skip that part in the next chunk because we already
-    // handled it.
-    let mut intra_chunk_offset = 0;
-
-    for chunk_index in 0..chunk_count {
-        let ptr = src_bytes.as_ptr() as *const __m128i;
-        // We don't know if the pointer is aligned to 16 bytes, so we
-        // use `loadu`, which supports unaligned loading.
-        let chunk = _mm_loadu_si128(ptr.add(chunk_index));
-
-        // For character in the chunk, see if its byte value is < 0, which
-        // indicates that it's part of a UTF-8 char.
-        let multibyte_test = _mm_cmplt_epi8(chunk, _mm_set1_epi8(0));
-        // Create a bit mask from the comparison results.
-        let multibyte_mask = _mm_movemask_epi8(multibyte_test);
-
-        // If the bit mask is all zero, we only have ASCII chars here:
-        if multibyte_mask == 0 {
-            assert!(intra_chunk_offset == 0);
-
-            // Check for newlines in the chunk
-            let newlines_test = _mm_cmpeq_epi8(chunk, _mm_set1_epi8(b'\n' as i8));
-            let newlines_mask = _mm_movemask_epi8(newlines_test);
-
-            if newlines_mask != 0 {
-                // All control characters are newlines, record them
-                let mut newlines_mask = 0xFFFF0000 | newlines_mask as u32;
-                let output_offset = TextSize::from((chunk_index * CHUNK_SIZE + 1) as u32);
-
-                loop {
-                    let index = newlines_mask.trailing_zeros();
-
-                    if index >= CHUNK_SIZE as u32 {
-                        // We have arrived at the end of the chunk.
-                        break;
-                    }
-
-                    lines.push(TextSize::from(index) + output_offset);
-
-                    // Clear the bit, so we can find the next one.
-                    newlines_mask &= (!1) << index;
-                }
-            }
-            continue;
-        }
-
-        // The slow path.
-        // There are control chars in here, fallback to generic decoding.
-        let scan_start = chunk_index * CHUNK_SIZE + intra_chunk_offset;
-        intra_chunk_offset = analyze_source_file_generic(
-            &src[scan_start..],
-            CHUNK_SIZE - intra_chunk_offset,
-            TextSize::from(scan_start as u32),
-            lines,
-            multi_byte_chars,
-        );
-    }
-
-    // There might still be a tail left to analyze
-    let tail_start = chunk_count * CHUNK_SIZE + intra_chunk_offset;
-    if tail_start < src.len() {
-        analyze_source_file_generic(
-            &src[tail_start..],
-            src.len() - tail_start,
-            TextSize::from(tail_start as u32),
-            lines,
-            multi_byte_chars,
-        );
-    }
-}
-
-#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
-// The target (or compiler version) does not support SSE2 ...
-fn analyze_source_file_dispatch(
-    src: &str,
-    lines: &mut Vec<TextSize>,
-    multi_byte_chars: &mut IntMap<u32, Vec<WideChar>>,
-) {
-    analyze_source_file_generic(src, src.len(), TextSize::from(0), lines, multi_byte_chars);
-}
-
-// `scan_len` determines the number of bytes in `src` to scan. Note that the
-// function can read past `scan_len` if a multi-byte character start within the
-// range but extends past it. The overflow is returned by the function.
-fn analyze_source_file_generic(
-    src: &str,
-    scan_len: usize,
-    output_offset: TextSize,
-    lines: &mut Vec<TextSize>,
-    multi_byte_chars: &mut IntMap<u32, Vec<WideChar>>,
-) -> usize {
-    assert!(src.len() >= scan_len);
-    let mut i = 0;
-    let src_bytes = src.as_bytes();
-
-    while i < scan_len {
-        let byte = unsafe {
-            // We verified that i < scan_len <= src.len()
-            *src_bytes.get_unchecked(i)
-        };
-
-        // How much to advance in order to get to the next UTF-8 char in the
-        // string.
-        let mut char_len = 1;
-
-        if byte == b'\n' {
-            lines.push(TextSize::from(i as u32 + 1) + output_offset);
-        } else if byte >= 127 {
-            // The slow path: Just decode to `char`.
-            let c = src[i..].chars().next().unwrap();
-            char_len = c.len_utf8();
-
-            let pos = TextSize::from(i as u32) + output_offset;
-
-            if char_len > 1 {
-                assert!((2..=4).contains(&char_len));
-                let mbc = WideChar { start: pos, end: pos + TextSize::from(char_len as u32) };
-                multi_byte_chars.entry(lines.len() as u32).or_default().push(mbc);
-            }
-        }
-
-        i += char_len;
-    }
-
-    i - scan_len
-}


Test patch:
diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs
index a8e146b096..1e57a4ae29 100644
--- a/crates/hir-ty/src/tests/method_resolution.rs
+++ b/crates/hir-ty/src/tests/method_resolution.rs
@@ -1215,52 +1215,6 @@ fn main() {
     );
 }
 
-#[test]
-fn inherent_method_deref_raw() {
-    check_types(
-        r#"
-struct Val;
-
-impl Val {
-    pub fn method(self: *const Val) -> u32 {
-        0
-    }
-}
-
-fn main() {
-    let foo: *const Val;
-    foo.method();
- // ^^^^^^^^^^^^ u32
-}
-"#,
-    );
-}
-
-#[test]
-fn trait_method_deref_raw() {
-    check_types(
-        r#"
-trait Trait {
-    fn method(self: *const Self) -> u32;
-}
-
-struct Val;
-
-impl Trait for Val {
-    fn method(self: *const Self) -> u32 {
-        0
-    }
-}
-
-fn main() {
-    let foo: *const Val;
-    foo.method();
- // ^^^^^^^^^^^^ u32
-}
-"#,
-    );
-}
-
 #[test]
 fn method_on_dyn_impl() {
     check_types(
diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs
index bebbf26b54..b63a266a57 100644
--- a/crates/rust-analyzer/src/cli/run_tests.rs
+++ b/crates/rust-analyzer/src/cli/run_tests.rs
@@ -7,12 +7,9 @@ use profile::StopWatch;
 use project_model::{CargoConfig, RustLibSource};
 use syntax::TextRange;
 
-use crate::cli::{
-    flags, full_name_of_item,
-    load_cargo::load_workspace_at,
-    load_cargo::{LoadCargoConfig, ProcMacroServerChoice},
-    Result,
-};
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
+
+use crate::cli::{flags, full_name_of_item, Result};
 
 impl flags::RunTests {
     pub fn run(self) -> Result<()> {
diff --git a/lib/line-index/src/tests.rs b/lib/line-index/src/tests.rs
index 8f3762d191..31c01c20ee 100644
--- a/lib/line-index/src/tests.rs
+++ b/lib/line-index/src/tests.rs
@@ -1,120 +1,11 @@
-use crate::{LineIndex, TextSize, WideChar};
-
-macro_rules! test {
-    (
-        case: $test_name:ident,
-     text: $text:expr,
-     lines: $lines:expr,
-     multi_byte_chars: $multi_byte_chars:expr,
-    ) => {
-        #[test]
-        fn $test_name() {
-            let line_index = LineIndex::new($text);
-
-            let expected_lines: Vec<TextSize> =
-                $lines.into_iter().map(<TextSize as From<u32>>::from).collect();
-
-            assert_eq!(&*line_index.newlines, &*expected_lines);
-
-            let expected_mbcs: Vec<_> = $multi_byte_chars
-                .into_iter()
-                .map(|(line, (pos, end)): (u32, (u32, u32))| {
-                    (line, WideChar { start: TextSize::from(pos), end: TextSize::from(end) })
-                })
-                .collect();
-
-            assert_eq!(
-                line_index
-                    .line_wide_chars
-                    .iter()
-                    .flat_map(|(line, val)| std::iter::repeat(*line).zip(val.iter().copied()))
-                    .collect::<Vec<_>>(),
-                expected_mbcs
-            );
-        }
-    };
+use super::LineIndex;
+
+#[test]
+fn test_empty_index() {
+    let col_index = LineIndex::new(
+        "
+const C: char = 'x';
+",
+    );
+    assert_eq!(col_index.line_wide_chars.len(), 0);
 }
-
-test!(
-    case: empty_text,
-    text: "",
-    lines: vec![],
-    multi_byte_chars: vec![],
-);
-
-test!(
-    case: newlines_short,
-    text: "a\nc",
-    lines: vec![2],
-    multi_byte_chars: vec![],
-);
-
-test!(
-    case: newlines_long,
-    text: "012345678\nabcdef012345678\na",
-    lines: vec![10, 26],
-    multi_byte_chars: vec![],
-);
-
-test!(
-    case: newline_and_multi_byte_char_in_same_chunk,
-    text: "01234β789\nbcdef0123456789abcdef",
-    lines: vec![11],
-    multi_byte_chars: vec![(0, (5, 7))],
-);
-
-test!(
-    case: newline_and_control_char_in_same_chunk,
-    text: "01234\u{07}6789\nbcdef0123456789abcdef",
-    lines: vec![11],
-    multi_byte_chars: vec![],
-);
-
-test!(
-    case: multi_byte_char_short,
-    text: "aβc",
-    lines: vec![],
-    multi_byte_chars: vec![(0, (1, 3))],
-);
-
-test!(
-    case: multi_byte_char_long,
-    text: "0123456789abcΔf012345β",
-    lines: vec![],
-    multi_byte_chars: vec![(0, (13, 15)), (0, (22, 24))],
-);
-
-test!(
-    case: multi_byte_char_across_chunk_boundary,
-    text: "0123456789abcdeΔ123456789abcdef01234",
-    lines: vec![],
-    multi_byte_chars: vec![(0, (15, 17))],
-);
-
-test!(
-    case: multi_byte_char_across_chunk_boundary_tail,
-    text: "0123456789abcdeΔ....",
-    lines: vec![],
-    multi_byte_chars: vec![(0, (15, 17))],
-);
-
-test!(
-    case: multi_byte_with_new_lines,
-    text: "01\t345\n789abcΔf01234567\u{07}9\nbcΔf",
-    lines: vec![7, 27],
-    multi_byte_chars: vec![(1, (13, 15)), (2, (29, 31))],
-);
-
-test!(
-    case: trailing_newline,
-    text: "0123456789\n",
-    lines: vec![11],
-    multi_byte_chars: vec![],
-);
-
-test!(
-    case: trailing_newline_chunk_boundary,
-    text: "0123456789abcde\n",
-    lines: vec![16],
-    multi_byte_chars: vec![],
-);
diff --git a/lib/line-index/tests/it.rs b/lib/line-index/tests/it.rs
new file mode 100644
index 0000000000..ce1c0bc6f1
--- /dev/null
+++ b/lib/line-index/tests/it.rs
@@ -0,0 +1,62 @@
+use line_index::{LineCol, LineIndex, TextRange};
+
+#[test]
+fn test_line_index() {
+    let text = "hello\nworld";
+    let table = [
+        (00, 0, 0),
+        (01, 0, 1),
+        (05, 0, 5),
+        (06, 1, 0),
+        (07, 1, 1),
+        (08, 1, 2),
+        (10, 1, 4),
+        (11, 1, 5),
+    ];
+
+    let index = LineIndex::new(text);
+    for (offset, line, col) in table {
+        assert_eq!(index.line_col(offset.into()), LineCol { line, col });
+    }
+
+    let text = "\nhello\nworld";
+    let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)];
+    let index = LineIndex::new(text);
+    for (offset, line, col) in table {
+        assert_eq!(index.line_col(offset.into()), LineCol { line, col });
+    }
+}
+
+#[test]
+fn test_char_len() {
+    assert_eq!('メ'.len_utf8(), 3);
+    assert_eq!('メ'.len_utf16(), 1);
+}
+
+#[test]
+fn test_splitlines() {
+    fn r(lo: u32, hi: u32) -> TextRange {
+        TextRange::new(lo.into(), hi.into())
+    }
+
+    let text = "a\nbb\nccc\n";
+    let line_index = LineIndex::new(text);
+
+    let actual = line_index.lines(r(0, 9)).collect::<Vec<_>>();
+    let expected = vec![r(0, 2), r(2, 5), r(5, 9)];
+    assert_eq!(actual, expected);
+
+    let text = "";
+    let line_index = LineIndex::new(text);
+
+    let actual = line_index.lines(r(0, 0)).collect::<Vec<_>>();
+    let expected = vec![];
+    assert_eq!(actual, expected);
+
+    let text = "\n";
+    let line_index = LineIndex::new(text);
+
+    let actual = line_index.lines(r(0, 1)).collect::<Vec<_>>();
+    let expected = vec![r(0, 1)];
+    assert_eq!(actual, expected)
+}