about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/load-cargo/src/lib.rs237
-rw-r--r--crates/rust-analyzer/src/global_state.rs5
-rw-r--r--crates/rust-analyzer/src/reload.rs1
-rw-r--r--crates/vfs/src/file_set.rs11
4 files changed, 249 insertions, 5 deletions
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 29c1251ccee..7b651257cdc 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -10,7 +10,7 @@ use hir_expand::proc_macro::{
     ProcMacros,
 };
 use ide_db::{
-    base_db::{CrateGraph, Env, SourceRoot},
+    base_db::{CrateGraph, Env, SourceRoot, SourceRootId},
     prime_caches, ChangeWithProcMacros, FxHashMap, RootDatabase,
 };
 use itertools::Itertools;
@@ -231,7 +231,7 @@ impl ProjectFolders {
             res.load.push(entry);
 
             if root.is_local {
-                local_filesets.push(fsc.len());
+                local_filesets.push(fsc.len() as u64);
             }
             fsc.add_file_set(file_set_roots)
         }
@@ -246,7 +246,7 @@ impl ProjectFolders {
 #[derive(Default, Debug)]
 pub struct SourceRootConfig {
     pub fsc: FileSetConfig,
-    pub local_filesets: Vec<usize>,
+    pub local_filesets: Vec<u64>,
 }
 
 impl SourceRootConfig {
@@ -256,7 +256,7 @@ impl SourceRootConfig {
             .into_iter()
             .enumerate()
             .map(|(idx, file_set)| {
-                let is_local = self.local_filesets.contains(&idx);
+                let is_local = self.local_filesets.contains(&(idx as u64));
                 if is_local {
                     SourceRoot::new_local(file_set)
                 } else {
@@ -265,6 +265,36 @@ impl SourceRootConfig {
             })
             .collect()
     }
+
+    /// Maps local source roots to their parent source roots by bytewise comparing of root paths .
+    /// If a source root doesn't have a parent then its parent is declared as None.
+    pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, Option<SourceRootId>> {
+        let roots = self.fsc.roots();
+        let mut map = FxHashMap::<SourceRootId, Option<SourceRootId>>::default();
+
+        'outer: for (idx, (root, root_id)) in roots.iter().enumerate() {
+            if !self.local_filesets.contains(root_id) {
+                continue;
+            }
+
+            for (_, (root2, root2_id)) in roots.iter().enumerate().take(idx).rev() {
+                if root2.iter().enumerate().all(|(i, c)| &root[i] == c) {
+                    // We are interested in parents if they are also local source roots.
+                    // So instead of a non-local parent we may take a local ancestor as a parent to a node.
+                    if self.local_filesets.contains(root2_id) {
+                        map.insert(
+                            SourceRootId(root_id.to_owned() as u32),
+                            Some(SourceRootId(root2_id.to_owned() as u32)),
+                        );
+                        continue 'outer;
+                    }
+                }
+            }
+            map.insert(SourceRootId(idx as u32), None);
+        }
+
+        map
+    }
 }
 
 /// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
@@ -413,4 +443,203 @@ mod tests {
         // RA has quite a few crates, but the exact count doesn't matter
         assert!(n_crates > 20);
     }
+
+    mod source_root_parent {
+        use ide_db::base_db::SourceRootId;
+        use vfs::{file_set::FileSetConfigBuilder, VfsPath};
+
+        use crate::SourceRootConfig;
+
+        macro_rules! virp {
+            ($s : literal) => {
+                VfsPath::new_virtual_path(format!($s))
+            };
+        }
+
+        #[test]
+        fn test1() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), None)])
+        }
+
+        #[test]
+        fn test2() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def/abc")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), None)])
+        }
+
+        #[test]
+        fn test3() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/abc/def")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), Some(SourceRootId(0)))])
+        }
+
+        #[test]
+        fn test4() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            let root3 = vec![virp!("/ROOT/def/abc")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(
+                vc,
+                vec![
+                    (SourceRootId(0), None),
+                    (SourceRootId(1), None),
+                    (SourceRootId(2), Some(SourceRootId(1)))
+                ]
+            )
+        }
+
+        #[test]
+        fn test5() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/ghi")];
+            let root3 = vec![virp!("/ROOT/def/abc")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(
+                vc,
+                vec![(SourceRootId(0), None), (SourceRootId(1), None), (SourceRootId(2), None)]
+            )
+        }
+
+        #[test]
+        fn test6() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+            let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+            assert_eq!(
+                vc,
+                vec![
+                    (SourceRootId(0), None),
+                    (SourceRootId(1), None),
+                    (SourceRootId(2), Some(SourceRootId(1)))
+                ]
+            )
+        }
+
+        #[test]
+        fn test7() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
+            let root4 = vec![virp!("/ROOT/def/ghi/klm")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            builder.add_file_set(root4);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
+            let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+            vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+            assert_eq!(
+                vc,
+                vec![
+                    (SourceRootId(0), None),
+                    (SourceRootId(1), None),
+                    (SourceRootId(2), Some(SourceRootId(1))),
+                    (SourceRootId(3), Some(SourceRootId(1)))
+                ]
+            )
+        }
+
+        #[test]
+        fn test8() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
+            let root4 = vec![virp!("/ROOT/def/klm")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            builder.add_file_set(root4);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
+            let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+            vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+            assert_eq!(
+                vc,
+                vec![
+                    (SourceRootId(0), None),
+                    (SourceRootId(1), None),
+                    (SourceRootId(3), Some(SourceRootId(1))),
+                ]
+            )
+        }
+
+        #[test]
+        fn test9() {
+            let mut builder = FileSetConfigBuilder::default();
+            let root = vec![virp!("/ROOT/abc")];
+            let root2 = vec![virp!("/ROOT/def")];
+            let root3 = vec![virp!("/ROOT/def/klm")];
+            let root4 = vec![virp!("/ROOT/def/klm/jkl")];
+            builder.add_file_set(root);
+            builder.add_file_set(root2);
+            builder.add_file_set(root3);
+            builder.add_file_set(root4);
+            let fsc = builder.build();
+            let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
+            let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+            vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+            assert_eq!(
+                vc,
+                vec![
+                    (SourceRootId(0), None),
+                    (SourceRootId(1), None),
+                    (SourceRootId(3), Some(SourceRootId(1))),
+                ]
+            )
+        }
+    }
 }
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 560410e3325..3d14b9198f8 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -8,7 +8,7 @@ use std::{collections::hash_map::Entry, time::Instant};
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
 use hir::ChangeWithProcMacros;
-use ide::{Analysis, AnalysisHost, Cancellable, FileId};
+use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
 use ide_db::base_db::{CrateId, ProcMacroPaths};
 use load_cargo::SourceRootConfig;
 use lsp_types::{SemanticTokens, Url};
@@ -66,6 +66,8 @@ pub(crate) struct GlobalState {
     pub(crate) diagnostics: DiagnosticCollection,
     pub(crate) mem_docs: MemDocs,
     pub(crate) source_root_config: SourceRootConfig,
+    /// A mapping that maps a local source root's `SourceRootId` to it parent's `SourceRootId`, if it has one.
+    pub(crate) local_roots_parent_map: FxHashMap<SourceRootId, Option<SourceRootId>>,
     pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
 
     // status
@@ -204,6 +206,7 @@ impl GlobalState {
             send_hint_refresh_query: false,
             last_reported_status: None,
             source_root_config: SourceRootConfig::default(),
+            local_roots_parent_map: FxHashMap::default(),
             config_errors: Default::default(),
 
             proc_macro_clients: Arc::from_iter([]),
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 95d51baab38..c2725e1fad9 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -515,6 +515,7 @@ impl GlobalState {
             version: self.vfs_config_version,
         });
         self.source_root_config = project_folders.source_root_config;
+        self.local_roots_parent_map = self.source_root_config.source_root_parent_map();
 
         self.recreate_crate_graph(cause);
 
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 0392ef3cebe..794f15577ef 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -123,6 +123,17 @@ impl FileSetConfig {
         self.n_file_sets
     }
 
+    /// Get the lexicographically ordered vector of the underlying map.
+    pub fn roots(&self) -> Vec<(Vec<u8>, u64)> {
+        let mut stream = self.map.stream();
+        let mut vc = vec![];
+        while let Some((pth, idx)) = stream.next() {
+            vc.push((pth.to_vec(), idx));
+        }
+
+        vc
+    }
+
     /// Returns the set index for the given `path`.
     ///
     /// `scratch_space` is used as a buffer and will be entirely replaced.