diff options
-rw-r--r-- | crates/ra_vfs/src/io.rs | 31 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 14 | ||||
-rw-r--r-- | crates/ra_vfs/src/roots.rs | 125 |
3 files changed, 89 insertions, 81 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index f64b4c532..0cffc03f3 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs | |||
@@ -9,10 +9,10 @@ use relative_path::RelativePathBuf; | |||
9 | use walkdir::WalkDir; | 9 | use walkdir::WalkDir; |
10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; | 10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; |
11 | 11 | ||
12 | use crate::{RootConfig, Roots, VfsRoot}; | 12 | use crate::{Roots, VfsRoot}; |
13 | 13 | ||
14 | pub(crate) enum Task { | 14 | pub(crate) enum Task { |
15 | AddRoot { root: VfsRoot, config: Arc<RootConfig> }, | 15 | AddRoot { root: VfsRoot }, |
16 | } | 16 | } |
17 | 17 | ||
18 | /// `TaskResult` transfers files read on the IO thread to the VFS on the main | 18 | /// `TaskResult` transfers files read on the IO thread to the VFS on the main |
@@ -98,8 +98,8 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker { | |||
98 | drop(input_receiver); | 98 | drop(input_receiver); |
99 | break | 99 | break |
100 | }, | 100 | }, |
101 | Ok(Task::AddRoot { root, config }) => { | 101 | Ok(Task::AddRoot { root }) => { |
102 | watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config)); | 102 | watch_root(watcher.as_mut(), &output_sender, &*roots, root); |
103 | } | 103 | } |
104 | }, | 104 | }, |
105 | // Watcher send us changes. If **this** channel is | 105 | // Watcher send us changes. If **this** channel is |
@@ -123,20 +123,21 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker { | |||
123 | fn watch_root( | 123 | fn watch_root( |
124 | watcher: Option<&mut RecommendedWatcher>, | 124 | watcher: Option<&mut RecommendedWatcher>, |
125 | sender: &Sender<TaskResult>, | 125 | sender: &Sender<TaskResult>, |
126 | roots: &Roots, | ||
126 | root: VfsRoot, | 127 | root: VfsRoot, |
127 | config: Arc<RootConfig>, | ||
128 | ) { | 128 | ) { |
129 | log::debug!("loading {} ...", config.root.as_path().display()); | 129 | let root_path = roots.path(root); |
130 | let files = watch_recursive(watcher, config.root.as_path(), &*config) | 130 | log::debug!("loading {} ...", root_path.display()); |
131 | let files = watch_recursive(watcher, root_path, roots, root) | ||
131 | .into_iter() | 132 | .into_iter() |
132 | .filter_map(|path| { | 133 | .filter_map(|path| { |
133 | let abs_path = path.to_path(&config.root); | 134 | let abs_path = path.to_path(&root_path); |
134 | let text = read_to_string(&abs_path)?; | 135 | let text = read_to_string(&abs_path)?; |
135 | Some((path, text)) | 136 | Some((path, text)) |
136 | }) | 137 | }) |
137 | .collect(); | 138 | .collect(); |
138 | sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap(); | 139 | sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap(); |
139 | log::debug!("... loaded {}", config.root.as_path().display()); | 140 | log::debug!("... loaded {}", root_path.display()); |
140 | } | 141 | } |
141 | 142 | ||
142 | fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) { | 143 | fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) { |
@@ -181,19 +182,18 @@ fn handle_change( | |||
181 | None => return, | 182 | None => return, |
182 | Some(it) => it, | 183 | Some(it) => it, |
183 | }; | 184 | }; |
184 | let config = &roots[root]; | ||
185 | match kind { | 185 | match kind { |
186 | ChangeKind::Create => { | 186 | ChangeKind::Create => { |
187 | let mut paths = Vec::new(); | 187 | let mut paths = Vec::new(); |
188 | if path.is_dir() { | 188 | if path.is_dir() { |
189 | paths.extend(watch_recursive(watcher, &path, &config)); | 189 | paths.extend(watch_recursive(watcher, &path, roots, root)); |
190 | } else { | 190 | } else { |
191 | paths.push(rel_path); | 191 | paths.push(rel_path); |
192 | } | 192 | } |
193 | paths | 193 | paths |
194 | .into_iter() | 194 | .into_iter() |
195 | .try_for_each(|rel_path| { | 195 | .try_for_each(|rel_path| { |
196 | let abs_path = rel_path.to_path(&config.root); | 196 | let abs_path = rel_path.to_path(&roots.path(root)); |
197 | let text = read_to_string(&abs_path); | 197 | let text = read_to_string(&abs_path); |
198 | sender.send(TaskResult::SingleFile { root, path: rel_path, text }) | 198 | sender.send(TaskResult::SingleFile { root, path: rel_path, text }) |
199 | }) | 199 | }) |
@@ -209,12 +209,13 @@ fn handle_change( | |||
209 | fn watch_recursive( | 209 | fn watch_recursive( |
210 | mut watcher: Option<&mut RecommendedWatcher>, | 210 | mut watcher: Option<&mut RecommendedWatcher>, |
211 | dir: &Path, | 211 | dir: &Path, |
212 | config: &RootConfig, | 212 | roots: &Roots, |
213 | root: VfsRoot, | ||
213 | ) -> Vec<RelativePathBuf> { | 214 | ) -> Vec<RelativePathBuf> { |
214 | let mut files = Vec::new(); | 215 | let mut files = Vec::new(); |
215 | for entry in WalkDir::new(dir) | 216 | for entry in WalkDir::new(dir) |
216 | .into_iter() | 217 | .into_iter() |
217 | .filter_entry(|it| config.contains(it.path()).is_some()) | 218 | .filter_entry(|it| roots.contains(root, it.path()).is_some()) |
218 | .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) | 219 | .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) |
219 | { | 220 | { |
220 | if entry.file_type().is_dir() { | 221 | if entry.file_type().is_dir() { |
@@ -222,7 +223,7 @@ fn watch_recursive( | |||
222 | watch_one(watcher, entry.path()); | 223 | watch_one(watcher, entry.path()); |
223 | } | 224 | } |
224 | } else { | 225 | } else { |
225 | let path = config.contains(entry.path()).unwrap(); | 226 | let path = roots.contains(root, entry.path()).unwrap(); |
226 | files.push(path.to_owned()); | 227 | files.push(path.to_owned()); |
227 | } | 228 | } |
228 | } | 229 | } |
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index a740e82ef..c78096ee1 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -31,7 +31,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
31 | 31 | ||
32 | use crate::{ | 32 | use crate::{ |
33 | io::{TaskResult, Worker}, | 33 | io::{TaskResult, Worker}, |
34 | roots::{RootConfig, Roots}, | 34 | roots::Roots, |
35 | }; | 35 | }; |
36 | 36 | ||
37 | pub use crate::{ | 37 | pub use crate::{ |
@@ -74,18 +74,18 @@ impl Vfs { | |||
74 | let worker = io::start(Arc::clone(&roots)); | 74 | let worker = io::start(Arc::clone(&roots)); |
75 | let mut root2files = ArenaMap::default(); | 75 | let mut root2files = ArenaMap::default(); |
76 | 76 | ||
77 | for (root, config) in roots.iter() { | 77 | for root in roots.iter() { |
78 | root2files.insert(root, Default::default()); | 78 | root2files.insert(root, Default::default()); |
79 | worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap(); | 79 | worker.sender().send(io::Task::AddRoot { root }).unwrap(); |
80 | } | 80 | } |
81 | let res = | 81 | let res = |
82 | Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; | 82 | Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; |
83 | let vfs_roots = res.roots.iter().map(|(id, _)| id).collect(); | 83 | let vfs_roots = res.roots.iter().collect(); |
84 | (res, vfs_roots) | 84 | (res, vfs_roots) |
85 | } | 85 | } |
86 | 86 | ||
87 | pub fn root2path(&self, root: VfsRoot) -> PathBuf { | 87 | pub fn root2path(&self, root: VfsRoot) -> PathBuf { |
88 | self.roots[root].root.clone() | 88 | self.roots.path(root).to_path_buf() |
89 | } | 89 | } |
90 | 90 | ||
91 | pub fn path2file(&self, path: &Path) -> Option<VfsFile> { | 91 | pub fn path2file(&self, path: &Path) -> Option<VfsFile> { |
@@ -97,7 +97,7 @@ impl Vfs { | |||
97 | 97 | ||
98 | pub fn file2path(&self, file: VfsFile) -> PathBuf { | 98 | pub fn file2path(&self, file: VfsFile) -> PathBuf { |
99 | let rel_path = &self.files[file].path; | 99 | let rel_path = &self.files[file].path; |
100 | let root_path = &self.roots[self.files[file].root].root; | 100 | let root_path = &self.roots.path(self.files[file].root); |
101 | rel_path.to_path(root_path) | 101 | rel_path.to_path(root_path) |
102 | } | 102 | } |
103 | 103 | ||
@@ -232,7 +232,7 @@ impl Vfs { | |||
232 | pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { | 232 | pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { |
233 | if let Some((root, path, file)) = self.find_root(path) { | 233 | if let Some((root, path, file)) = self.find_root(path) { |
234 | let file = file.expect("can't remove a file which wasn't added"); | 234 | let file = file.expect("can't remove a file which wasn't added"); |
235 | let full_path = path.to_path(&self.roots[root].root); | 235 | let full_path = path.to_path(&self.roots.path(root)); |
236 | if let Ok(text) = fs::read_to_string(&full_path) { | 236 | if let Ok(text) = fs::read_to_string(&full_path) { |
237 | self.do_change_file(file, text, true); | 237 | self.do_change_file(file, text, true); |
238 | } else { | 238 | } else { |
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs index 564e12239..5e2776a35 100644 --- a/crates/ra_vfs/src/roots.rs +++ b/crates/ra_vfs/src/roots.rs | |||
@@ -1,11 +1,13 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | iter, | ||
2 | sync::Arc, | 3 | sync::Arc, |
3 | path::{Path, PathBuf}, | 4 | path::{Path, PathBuf}, |
4 | }; | 5 | }; |
5 | 6 | ||
6 | use relative_path::RelativePathBuf; | 7 | use relative_path::{ RelativePath, RelativePathBuf}; |
7 | use ra_arena::{impl_arena_id, Arena, RawId}; | 8 | use ra_arena::{impl_arena_id, Arena, RawId}; |
8 | 9 | ||
10 | /// VfsRoot identifies a watched directory on the file system. | ||
9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
10 | pub struct VfsRoot(pub RawId); | 12 | pub struct VfsRoot(pub RawId); |
11 | impl_arena_id!(VfsRoot); | 13 | impl_arena_id!(VfsRoot); |
@@ -15,88 +17,93 @@ impl_arena_id!(VfsRoot); | |||
15 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which | 17 | /// `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 | 18 | /// specifies the source root or as a function which takes a `PathBuf` and |
17 | /// returns `true` iff path belongs to the source root | 19 | /// returns `true` iff path belongs to the source root |
18 | pub(crate) struct RootConfig { | 20 | struct RootData { |
19 | pub(crate) root: PathBuf, | 21 | path: PathBuf, |
20 | // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. | 22 | // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. |
21 | canonical_root: Option<PathBuf>, | 23 | canonical_path: Option<PathBuf>, |
22 | excluded_dirs: Vec<PathBuf>, | 24 | excluded_dirs: Vec<RelativePathBuf>, |
23 | } | 25 | } |
24 | 26 | ||
25 | pub(crate) struct Roots { | 27 | pub(crate) struct Roots { |
26 | roots: Arena<VfsRoot, Arc<RootConfig>>, | 28 | roots: Arena<VfsRoot, Arc<RootData>>, |
27 | } | 29 | } |
28 | 30 | ||
29 | impl std::ops::Deref for Roots { | 31 | impl Roots { |
30 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | 32 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { |
31 | fn deref(&self) -> &Self::Target { | 33 | let mut roots = Arena::default(); |
32 | &self.roots | 34 | // A hack to make nesting work. |
33 | } | 35 | paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len())); |
34 | } | 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)); | ||
35 | 42 | ||
36 | impl RootConfig { | 43 | roots.alloc(config.clone()); |
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 | } | 44 | } |
42 | RootConfig { root, canonical_root, excluded_dirs } | 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() | ||
43 | } | 61 | } |
44 | /// Checks if root contains a path and returns a root-relative path. | 62 | /// Checks if root contains a path and returns a root-relative path. |
45 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { | 63 | pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> { |
46 | // First, check excluded dirs | 64 | let data = &self.roots[root]; |
47 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { | 65 | iter::once(&data.path) |
48 | return None; | 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; | ||
49 | } | 78 | } |
50 | let rel_path = path | 79 | RootData { path, canonical_path, excluded_dirs } |
51 | .strip_prefix(&self.root) | 80 | } |
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 | 81 | ||
82 | fn is_excluded(&self, path: &RelativePath) -> bool { | ||
83 | if self.excluded_dirs.iter().any(|it| it == path) { | ||
84 | return true; | ||
85 | } | ||
60 | // Ignore some common directories. | 86 | // Ignore some common directories. |
61 | // | 87 | // |
62 | // FIXME: don't hard-code, specify at source-root creation time using | 88 | // FIXME: don't hard-code, specify at source-root creation time using |
63 | // gitignore | 89 | // gitignore |
64 | for (i, c) in rel_path.components().enumerate() { | 90 | for (i, c) in path.components().enumerate() { |
65 | if let relative_path::Component::Normal(c) = c { | 91 | if let relative_path::Component::Normal(c) = c { |
66 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | 92 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { |
67 | return None; | 93 | return true; |
68 | } | 94 | } |
69 | } | 95 | } |
70 | } | 96 | } |
71 | 97 | ||
72 | if path.is_file() && rel_path.extension() != Some("rs") { | 98 | match path.extension() { |
73 | return None; | 99 | None | Some("rs") => false, |
100 | _ => true, | ||
74 | } | 101 | } |
75 | |||
76 | Some(rel_path) | ||
77 | } | 102 | } |
78 | } | 103 | } |
79 | 104 | ||
80 | impl Roots { | 105 | fn rel_path(base: &Path, path: &Path) -> Option<RelativePathBuf> { |
81 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { | 106 | let path = path.strip_prefix(base).ok()?; |
82 | let mut roots = Arena::default(); | 107 | let path = RelativePathBuf::from_path(path).unwrap(); |
83 | // A hack to make nesting work. | 108 | Some(path) |
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 | } | 109 | } |