diff options
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 108 | ||||
-rw-r--r-- | crates/ra_vfs/src/roots.rs | 102 |
2 files changed, 112 insertions, 98 deletions
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index cfdc1275f..a740e82ef 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -15,10 +15,10 @@ | |||
15 | //! VFS is based on a concept of roots: a set of directories on the file system | 15 | //! VFS is based on a concept of roots: a set of directories on the file system |
16 | //! which are watched for changes. Typically, there will be a root for each | 16 | //! which are watched for changes. Typically, there will be a root for each |
17 | //! Cargo package. | 17 | //! Cargo package. |
18 | mod roots; | ||
18 | mod io; | 19 | mod io; |
19 | 20 | ||
20 | use std::{ | 21 | use std::{ |
21 | cmp::Reverse, | ||
22 | fmt, fs, mem, | 22 | fmt, fs, mem, |
23 | path::{Path, PathBuf}, | 23 | path::{Path, PathBuf}, |
24 | sync::Arc, | 24 | sync::Arc, |
@@ -26,106 +26,18 @@ use std::{ | |||
26 | 26 | ||
27 | use crossbeam_channel::Receiver; | 27 | use crossbeam_channel::Receiver; |
28 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; | 28 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; |
29 | use relative_path::{Component, RelativePath, RelativePathBuf}; | 29 | use relative_path::{RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 30 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | 31 | ||
32 | pub use crate::io::TaskResult as VfsTask; | 32 | use crate::{ |
33 | use io::{TaskResult, Worker}; | 33 | io::{TaskResult, Worker}, |
34 | 34 | roots::{RootConfig, Roots}, | |
35 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 35 | }; |
36 | pub struct VfsRoot(pub RawId); | ||
37 | impl_arena_id!(VfsRoot); | ||
38 | |||
39 | /// Describes the contents of a single source root. | ||
40 | /// | ||
41 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which | ||
42 | /// specifies the source root or as a function which takes a `PathBuf` and | ||
43 | /// returns `true` iff path belongs to the source root | ||
44 | pub(crate) struct RootConfig { | ||
45 | root: PathBuf, | ||
46 | // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. | ||
47 | canonical_root: Option<PathBuf>, | ||
48 | excluded_dirs: Vec<PathBuf>, | ||
49 | } | ||
50 | |||
51 | pub(crate) struct Roots { | ||
52 | roots: Arena<VfsRoot, Arc<RootConfig>>, | ||
53 | } | ||
54 | |||
55 | impl std::ops::Deref for Roots { | ||
56 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | ||
57 | fn deref(&self) -> &Self::Target { | ||
58 | &self.roots | ||
59 | } | ||
60 | } | ||
61 | |||
62 | impl RootConfig { | ||
63 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig { | ||
64 | let mut canonical_root = root.canonicalize().ok(); | ||
65 | if Some(&root) == canonical_root.as_ref() { | ||
66 | canonical_root = None; | ||
67 | } | ||
68 | RootConfig { root, canonical_root, excluded_dirs } | ||
69 | } | ||
70 | /// Checks if root contains a path and returns a root-relative path. | ||
71 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { | ||
72 | // First, check excluded dirs | ||
73 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { | ||
74 | return None; | ||
75 | } | ||
76 | let rel_path = path | ||
77 | .strip_prefix(&self.root) | ||
78 | .or_else(|err_payload| { | ||
79 | self.canonical_root | ||
80 | .as_ref() | ||
81 | .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root)) | ||
82 | }) | ||
83 | .ok()?; | ||
84 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
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 rel_path.components().enumerate() { | ||
91 | if let Component::Normal(c) = c { | ||
92 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | ||
93 | return None; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if path.is_file() && rel_path.extension() != Some("rs") { | ||
99 | return None; | ||
100 | } | ||
101 | |||
102 | Some(rel_path) | ||
103 | } | ||
104 | } | ||
105 | 36 | ||
106 | impl Roots { | 37 | pub use crate::{ |
107 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { | 38 | io::TaskResult as VfsTask, |
108 | let mut roots = Arena::default(); | 39 | roots::VfsRoot, |
109 | // A hack to make nesting work. | 40 | }; |
110 | paths.sort_by_key(|it| Reverse(it.as_os_str().len())); | ||
111 | paths.dedup(); | ||
112 | for (i, path) in paths.iter().enumerate() { | ||
113 | let nested_roots = paths[..i] | ||
114 | .iter() | ||
115 | .filter(|it| it.starts_with(path)) | ||
116 | .map(|it| it.clone()) | ||
117 | .collect::<Vec<_>>(); | ||
118 | |||
119 | let config = Arc::new(RootConfig::new(path.clone(), nested_roots)); | ||
120 | |||
121 | roots.alloc(config.clone()); | ||
122 | } | ||
123 | Roots { roots } | ||
124 | } | ||
125 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | ||
126 | self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it))) | ||
127 | } | ||
128 | } | ||
129 | 41 | ||
130 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
131 | pub struct VfsFile(pub RawId); | 43 | pub struct VfsFile(pub RawId); |
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs new file mode 100644 index 000000000..564e12239 --- /dev/null +++ b/crates/ra_vfs/src/roots.rs | |||
@@ -0,0 +1,102 @@ | |||
1 | use std::{ | ||
2 | sync::Arc, | ||
3 | path::{Path, PathBuf}, | ||
4 | }; | ||
5 | |||
6 | use relative_path::RelativePathBuf; | ||
7 | use ra_arena::{impl_arena_id, Arena, RawId}; | ||
8 | |||
9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
10 | pub struct VfsRoot(pub RawId); | ||
11 | impl_arena_id!(VfsRoot); | ||
12 | |||
13 | /// Describes the contents of a single source root. | ||
14 | /// | ||
15 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which | ||
16 | /// specifies the source root or as a function which takes a `PathBuf` and | ||
17 | /// returns `true` iff path belongs to the source root | ||
18 | pub(crate) struct RootConfig { | ||
19 | pub(crate) root: PathBuf, | ||
20 | // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. | ||
21 | canonical_root: Option<PathBuf>, | ||
22 | excluded_dirs: Vec<PathBuf>, | ||
23 | } | ||
24 | |||
25 | pub(crate) struct Roots { | ||
26 | roots: Arena<VfsRoot, Arc<RootConfig>>, | ||
27 | } | ||
28 | |||
29 | impl std::ops::Deref for Roots { | ||
30 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | ||
31 | fn deref(&self) -> &Self::Target { | ||
32 | &self.roots | ||
33 | } | ||
34 | } | ||
35 | |||
36 | impl RootConfig { | ||
37 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig { | ||
38 | let mut canonical_root = root.canonicalize().ok(); | ||
39 | if Some(&root) == canonical_root.as_ref() { | ||
40 | canonical_root = None; | ||
41 | } | ||
42 | RootConfig { root, canonical_root, excluded_dirs } | ||
43 | } | ||
44 | /// Checks if root contains a path and returns a root-relative path. | ||
45 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { | ||
46 | // First, check excluded dirs | ||
47 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { | ||
48 | return None; | ||
49 | } | ||
50 | let rel_path = path | ||
51 | .strip_prefix(&self.root) | ||
52 | .or_else(|err_payload| { | ||
53 | self.canonical_root | ||
54 | .as_ref() | ||
55 | .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root)) | ||
56 | }) | ||
57 | .ok()?; | ||
58 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
59 | |||
60 | // Ignore some common directories. | ||
61 | // | ||
62 | // FIXME: don't hard-code, specify at source-root creation time using | ||
63 | // gitignore | ||
64 | for (i, c) in rel_path.components().enumerate() { | ||
65 | if let relative_path::Component::Normal(c) = c { | ||
66 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | ||
67 | return None; | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | if path.is_file() && rel_path.extension() != Some("rs") { | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | Some(rel_path) | ||
77 | } | ||
78 | } | ||
79 | |||
80 | impl Roots { | ||
81 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { | ||
82 | let mut roots = Arena::default(); | ||
83 | // A hack to make nesting work. | ||
84 | paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len())); | ||
85 | paths.dedup(); | ||
86 | for (i, path) in paths.iter().enumerate() { | ||
87 | let nested_roots = paths[..i] | ||
88 | .iter() | ||
89 | .filter(|it| it.starts_with(path)) | ||
90 | .map(|it| it.clone()) | ||
91 | .collect::<Vec<_>>(); | ||
92 | |||
93 | let config = Arc::new(RootConfig::new(path.clone(), nested_roots)); | ||
94 | |||
95 | roots.alloc(config.clone()); | ||
96 | } | ||
97 | Roots { roots } | ||
98 | } | ||
99 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | ||
100 | self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it))) | ||
101 | } | ||
102 | } | ||