diff options
Diffstat (limited to 'crates/ra_vfs/src/roots.rs')
-rw-r--r-- | crates/ra_vfs/src/roots.rs | 109 |
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 @@ | |||
1 | use std::{ | ||
2 | iter, | ||
3 | sync::Arc, | ||
4 | path::{Path, PathBuf}, | ||
5 | }; | ||
6 | |||
7 | use relative_path::{ RelativePath, RelativePathBuf}; | ||
8 | use 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)] | ||
12 | pub struct VfsRoot(pub RawId); | ||
13 | impl_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 | ||
20 | struct 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 | |||
27 | pub(crate) struct Roots { | ||
28 | roots: Arena<VfsRoot, Arc<RootData>>, | ||
29 | } | ||
30 | |||
31 | impl 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 | |||
73 | impl 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 | |||
105 | fn 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 | } | ||