From a15cc86c640a3db70138b8cd067f9a36d6d39d41 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Mon, 26 Feb 2024 14:35:54 +0100 Subject: [PATCH] Implement parent-child relation for `SourceRoot`s This commit adds the said relation by keeping a map of type `FxHashMap>` inside the `GlobalState`. Its primary use case is reading the rust-analyzer.toml files that can be placed under every local source root. As a config will be found by traversing this "tree" we need the parent information for every local source root. This commit omits defining this relation for library source roots entirely. --- crates/load-cargo/src/lib.rs | 237 ++++++++++++++++++++++- crates/rust-analyzer/src/global_state.rs | 5 +- crates/rust-analyzer/src/reload.rs | 1 + crates/vfs/src/file_set.rs | 11 ++ 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 @@ 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 @@ pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> P 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 @@ pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> P #[derive(Default, Debug)] pub struct SourceRootConfig { pub fsc: FileSetConfig, - pub local_filesets: Vec, + pub local_filesets: Vec, } impl SourceRootConfig { @@ -256,7 +256,7 @@ pub fn partition(&self, vfs: &vfs::Vfs) -> Vec { .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 @@ pub fn partition(&self, vfs: &vfs::Vfs) -> Vec { }) .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> { + let roots = self.fsc.roots(); + let mut map = FxHashMap::>::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 @@ fn test_loading_rust_analyzer() { // 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::>(); + + 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::>(); + + 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::>(); + + 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::>(); + + 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::>(); + + 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::>(); + + 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::>(); + 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::>(); + 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::>(); + 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 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>, pub(crate) semantic_tokens_cache: Arc>>, // status @@ -204,6 +206,7 @@ pub(crate) fn new(sender: Sender, config: Config) -> Global 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 @@ pub(crate) fn switch_workspaces(&mut self, cause: Cause) { 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 @@ fn len(&self) -> usize { self.n_file_sets } + /// Get the lexicographically ordered vector of the underlying map. + pub fn roots(&self) -> Vec<(Vec, 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.