aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs/src/roots.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_vfs/src/roots.rs')
-rw-r--r--crates/ra_vfs/src/roots.rs109
1 files changed, 109 insertions, 0 deletions
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs
new file mode 100644
index 000000000..5e2776a35
--- /dev/null
+++ b/crates/ra_vfs/src/roots.rs
@@ -0,0 +1,109 @@
1use std::{
2 iter,
3 sync::Arc,
4 path::{Path, PathBuf},
5};
6
7use relative_path::{ RelativePath, RelativePathBuf};
8use ra_arena::{impl_arena_id, Arena, RawId};
9
10/// VfsRoot identifies a watched directory on the file system.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct VfsRoot(pub RawId);
13impl_arena_id!(VfsRoot);
14
15/// Describes the contents of a single source root.
16///
17/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
18/// specifies the source root or as a function which takes a `PathBuf` and
19/// returns `true` iff path belongs to the source root
20struct RootData {
21 path: PathBuf,
22 // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
23 canonical_path: Option<PathBuf>,
24 excluded_dirs: Vec<RelativePathBuf>,
25}
26
27pub(crate) struct Roots {
28 roots: Arena<VfsRoot, Arc<RootData>>,
29}
30
31impl Roots {
32 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
33 let mut roots = Arena::default();
34 // A hack to make nesting work.
35 paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len()));
36 paths.dedup();
37 for (i, path) in paths.iter().enumerate() {
38 let nested_roots =
39 paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>();
40
41 let config = Arc::new(RootData::new(path.clone(), nested_roots));
42
43 roots.alloc(config.clone());
44 }
45 Roots { roots }
46 }
47 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
48 self.iter().find_map(|root| {
49 let rel_path = self.contains(root, path)?;
50 Some((root, rel_path))
51 })
52 }
53 pub(crate) fn len(&self) -> usize {
54 self.roots.len()
55 }
56 pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a {
57 self.roots.iter().map(|(id, _)| id)
58 }
59 pub(crate) fn path(&self, root: VfsRoot) -> &Path {
60 self.roots[root].path.as_path()
61 }
62 /// Checks if root contains a path and returns a root-relative path.
63 pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> {
64 let data = &self.roots[root];
65 iter::once(&data.path)
66 .chain(data.canonical_path.as_ref().into_iter())
67 .find_map(|base| rel_path(base, path))
68 .filter(|path| !data.excluded_dirs.contains(path))
69 .filter(|path| !data.is_excluded(path))
70 }
71}
72
73impl RootData {
74 fn new(path: PathBuf, excluded_dirs: Vec<RelativePathBuf>) -> RootData {
75 let mut canonical_path = path.canonicalize().ok();
76 if Some(&path) == canonical_path.as_ref() {
77 canonical_path = None;
78 }
79 RootData { path, canonical_path, excluded_dirs }
80 }
81
82 fn is_excluded(&self, path: &RelativePath) -> bool {
83 if self.excluded_dirs.iter().any(|it| it == path) {
84 return true;
85 }
86 // Ignore some common directories.
87 //
88 // FIXME: don't hard-code, specify at source-root creation time using
89 // gitignore
90 for (i, c) in path.components().enumerate() {
91 if let relative_path::Component::Normal(c) = c {
92 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
93 return true;
94 }
95 }
96 }
97
98 match path.extension() {
99 None | Some("rs") => false,
100 _ => true,
101 }
102 }
103}
104
105fn rel_path(base: &Path, path: &Path) -> Option<RelativePathBuf> {
106 let path = path.strip_prefix(base).ok()?;
107 let path = RelativePathBuf::from_path(path).unwrap();
108 Some(path)
109}