diff options
-rw-r--r-- | crates/ra_vfs/src/io.rs | 271 | ||||
-rw-r--r-- | crates/ra_vfs/src/io/watcher.rs | 200 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 146 |
3 files changed, 281 insertions, 336 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 7ca1e9835..d764c534a 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs | |||
@@ -1,19 +1,22 @@ | |||
1 | use std::{fs, sync::Arc, thread}; | 1 | use std::{ |
2 | 2 | fs, | |
3 | use crossbeam_channel::{Receiver, Sender}; | 3 | thread, |
4 | path::{Path, PathBuf}, | ||
5 | sync::{mpsc, Arc}, | ||
6 | time::Duration, | ||
7 | }; | ||
8 | use crossbeam_channel::{Receiver, Sender, unbounded, RecvError, select}; | ||
4 | use relative_path::RelativePathBuf; | 9 | use relative_path::RelativePathBuf; |
5 | use thread_worker::WorkerHandle; | 10 | use thread_worker::WorkerHandle; |
6 | use walkdir::WalkDir; | 11 | use walkdir::WalkDir; |
12 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; | ||
7 | 13 | ||
8 | mod watcher; | 14 | use crate::{RootConfig, Roots, VfsRoot}; |
9 | use watcher::Watcher; | ||
10 | |||
11 | use crate::{RootFilter, Roots, VfsRoot}; | ||
12 | 15 | ||
13 | pub(crate) enum Task { | 16 | pub(crate) enum Task { |
14 | AddRoot { | 17 | AddRoot { |
15 | root: VfsRoot, | 18 | root: VfsRoot, |
16 | filter: Arc<RootFilter>, | 19 | config: Arc<RootConfig>, |
17 | }, | 20 | }, |
18 | } | 21 | } |
19 | 22 | ||
@@ -39,6 +42,15 @@ pub enum TaskResult { | |||
39 | }, | 42 | }, |
40 | } | 43 | } |
41 | 44 | ||
45 | #[derive(Debug)] | ||
46 | enum ChangeKind { | ||
47 | Create, | ||
48 | Write, | ||
49 | Remove, | ||
50 | } | ||
51 | |||
52 | const WATCHER_DELAY: Duration = Duration::from_millis(250); | ||
53 | |||
42 | pub(crate) struct Worker { | 54 | pub(crate) struct Worker { |
43 | worker: thread_worker::Worker<Task, TaskResult>, | 55 | worker: thread_worker::Worker<Task, TaskResult>, |
44 | worker_handle: WorkerHandle, | 56 | worker_handle: WorkerHandle, |
@@ -46,24 +58,75 @@ pub(crate) struct Worker { | |||
46 | 58 | ||
47 | impl Worker { | 59 | impl Worker { |
48 | pub(crate) fn start(roots: Arc<Roots>) -> Worker { | 60 | pub(crate) fn start(roots: Arc<Roots>) -> Worker { |
49 | let (worker, worker_handle) = | 61 | // This is a pretty elaborate setup of threads & channels! It is |
50 | thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { | 62 | // explained by the following concerns: |
51 | let mut watcher = match Watcher::start(roots, output_sender.clone()) { | 63 | // * we need to burn a thread translating from notify's mpsc to |
52 | Ok(w) => Some(w), | 64 | // crossbeam_channel. |
53 | Err(e) => { | 65 | // * we want to read all files from a single thread, to gurantee that |
54 | log::error!("could not start watcher: {}", e); | 66 | // we always get fresher versions and never go back in time. |
55 | None | 67 | // * we want to tear down everything neatly during shutdown. |
68 | let (worker, worker_handle) = thread_worker::spawn( | ||
69 | "vfs", | ||
70 | 128, | ||
71 | // This are the channels we use to communicate with outside world. | ||
72 | // If `input_receiver` is closed we need to tear ourselves down. | ||
73 | // `output_sender` should not be closed unless the parent died. | ||
74 | move |input_receiver, output_sender| { | ||
75 | // These are `std` channels notify will send events to | ||
76 | let (notify_sender, notify_receiver) = mpsc::channel(); | ||
77 | // These are the corresponding crossbeam channels | ||
78 | let (watcher_sender, watcher_receiver) = unbounded(); | ||
79 | |||
80 | let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY) | ||
81 | .map_err(|e| log::error!("failed to spawn notify {}", e)) | ||
82 | .ok(); | ||
83 | // Start a silly thread to tranform between two channels | ||
84 | let thread = thread::spawn(move || { | ||
85 | notify_receiver | ||
86 | .into_iter() | ||
87 | .for_each(|event| convert_notify_event(event, &watcher_sender)) | ||
88 | }); | ||
89 | |||
90 | // Process requests from the called or notifications from | ||
91 | // watcher until the caller says stop. | ||
92 | loop { | ||
93 | select! { | ||
94 | // Received request from the caller. If this channel is | ||
95 | // closed, we should shutdown everything. | ||
96 | recv(input_receiver) -> t => match t { | ||
97 | Err(RecvError) => { | ||
98 | drop(input_receiver); | ||
99 | break | ||
100 | }, | ||
101 | Ok(Task::AddRoot { root, config }) => { | ||
102 | watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config)); | ||
103 | } | ||
104 | }, | ||
105 | // Watcher send us changes. If **this** channel is | ||
106 | // closed, the watcher has died, which indicates a bug | ||
107 | // -- escalate! | ||
108 | recv(watcher_receiver) -> event => match event { | ||
109 | Err(RecvError) => panic!("watcher is dead"), | ||
110 | Ok((path, change)) => { | ||
111 | handle_change(watcher.as_mut(), &output_sender, &*roots, path, change); | ||
112 | } | ||
113 | }, | ||
56 | } | 114 | } |
57 | }; | ||
58 | let res = input_receiver | ||
59 | .into_iter() | ||
60 | .filter_map(|t| handle_task(t, &mut watcher)) | ||
61 | .try_for_each(|it| output_sender.send(it)); | ||
62 | if let Some(watcher) = watcher { | ||
63 | let _ = watcher.shutdown(); | ||
64 | } | 115 | } |
65 | res.unwrap() | 116 | // Stopped the watcher |
66 | }); | 117 | drop(watcher.take()); |
118 | // Drain pending events: we are not inrerested in them anyways! | ||
119 | watcher_receiver.into_iter().for_each(|_| ()); | ||
120 | |||
121 | let res = thread.join(); | ||
122 | match &res { | ||
123 | Ok(()) => log::info!("... Watcher terminated with ok"), | ||
124 | Err(_) => log::error!("... Watcher terminated with err"), | ||
125 | } | ||
126 | res.unwrap(); | ||
127 | }, | ||
128 | ); | ||
129 | |||
67 | Worker { | 130 | Worker { |
68 | worker, | 131 | worker, |
69 | worker_handle, | 132 | worker_handle, |
@@ -84,46 +147,142 @@ impl Worker { | |||
84 | } | 147 | } |
85 | } | 148 | } |
86 | 149 | ||
87 | fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> { | 150 | fn watch_root( |
88 | match task { | 151 | watcher: Option<&mut RecommendedWatcher>, |
89 | Task::AddRoot { root, filter } => { | 152 | sender: &Sender<TaskResult>, |
90 | if let Some(watcher) = watcher { | 153 | root: VfsRoot, |
91 | watcher.watch_root(&filter) | 154 | config: Arc<RootConfig>, |
155 | ) { | ||
156 | log::debug!("loading {} ...", config.root.as_path().display()); | ||
157 | let files = watch_recursive(watcher, config.root.as_path(), &*config) | ||
158 | .into_iter() | ||
159 | .filter_map(|path| { | ||
160 | let abs_path = path.to_path(&config.root); | ||
161 | let text = read_to_string(&abs_path)?; | ||
162 | Some((path, text)) | ||
163 | }) | ||
164 | .collect(); | ||
165 | sender | ||
166 | .send(TaskResult::BulkLoadRoot { root, files }) | ||
167 | .unwrap(); | ||
168 | log::debug!("... loaded {}", config.root.as_path().display()); | ||
169 | } | ||
170 | |||
171 | fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) { | ||
172 | // forward relevant events only | ||
173 | match event { | ||
174 | DebouncedEvent::NoticeWrite(_) | ||
175 | | DebouncedEvent::NoticeRemove(_) | ||
176 | | DebouncedEvent::Chmod(_) => { | ||
177 | // ignore | ||
178 | } | ||
179 | DebouncedEvent::Rescan => { | ||
180 | // TODO rescan all roots | ||
181 | } | ||
182 | DebouncedEvent::Create(path) => { | ||
183 | sender.send((path, ChangeKind::Create)).unwrap(); | ||
184 | } | ||
185 | DebouncedEvent::Write(path) => { | ||
186 | sender.send((path, ChangeKind::Write)).unwrap(); | ||
187 | } | ||
188 | DebouncedEvent::Remove(path) => { | ||
189 | sender.send((path, ChangeKind::Remove)).unwrap(); | ||
190 | } | ||
191 | DebouncedEvent::Rename(src, dst) => { | ||
192 | sender.send((src, ChangeKind::Remove)).unwrap(); | ||
193 | sender.send((dst, ChangeKind::Create)).unwrap(); | ||
194 | } | ||
195 | DebouncedEvent::Error(err, path) => { | ||
196 | // TODO should we reload the file contents? | ||
197 | log::warn!("watcher error \"{}\", {:?}", err, path); | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | |||
202 | fn handle_change( | ||
203 | watcher: Option<&mut RecommendedWatcher>, | ||
204 | sender: &Sender<TaskResult>, | ||
205 | roots: &Roots, | ||
206 | path: PathBuf, | ||
207 | kind: ChangeKind, | ||
208 | ) { | ||
209 | let (root, rel_path) = match roots.find(&path) { | ||
210 | None => return, | ||
211 | Some(it) => it, | ||
212 | }; | ||
213 | let config = &roots[root]; | ||
214 | match kind { | ||
215 | ChangeKind::Create => { | ||
216 | let mut paths = Vec::new(); | ||
217 | if path.is_dir() { | ||
218 | paths.extend(watch_recursive(watcher, &path, &config)); | ||
219 | } else { | ||
220 | paths.push(rel_path); | ||
92 | } | 221 | } |
93 | log::debug!("loading {} ...", filter.root.as_path().display()); | 222 | paths |
94 | let files = load_root(filter.as_ref()); | 223 | .into_iter() |
95 | log::debug!("... loaded {}", filter.root.as_path().display()); | 224 | .filter_map(|rel_path| { |
96 | Some(TaskResult::BulkLoadRoot { root, files }) | 225 | let abs_path = rel_path.to_path(&config.root); |
226 | let text = read_to_string(&abs_path)?; | ||
227 | Some((rel_path, text)) | ||
228 | }) | ||
229 | .try_for_each(|(path, text)| { | ||
230 | sender.send(TaskResult::AddSingleFile { root, path, text }) | ||
231 | }) | ||
232 | .unwrap() | ||
97 | } | 233 | } |
234 | ChangeKind::Write => { | ||
235 | if let Some(text) = read_to_string(&path) { | ||
236 | sender | ||
237 | .send(TaskResult::ChangeSingleFile { | ||
238 | root, | ||
239 | path: rel_path, | ||
240 | text, | ||
241 | }) | ||
242 | .unwrap(); | ||
243 | } | ||
244 | } | ||
245 | ChangeKind::Remove => sender | ||
246 | .send(TaskResult::RemoveSingleFile { | ||
247 | root, | ||
248 | path: rel_path, | ||
249 | }) | ||
250 | .unwrap(), | ||
98 | } | 251 | } |
99 | } | 252 | } |
100 | 253 | ||
101 | fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> { | 254 | fn watch_recursive( |
102 | let mut res = Vec::new(); | 255 | mut watcher: Option<&mut RecommendedWatcher>, |
103 | for entry in WalkDir::new(&filter.root) | 256 | dir: &Path, |
257 | config: &RootConfig, | ||
258 | ) -> Vec<RelativePathBuf> { | ||
259 | let mut files = Vec::new(); | ||
260 | for entry in WalkDir::new(dir) | ||
104 | .into_iter() | 261 | .into_iter() |
105 | .filter_entry(filter.entry_filter()) | 262 | .filter_entry(|it| config.contains(it.path()).is_some()) |
263 | .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) | ||
106 | { | 264 | { |
107 | let entry = match entry { | 265 | if entry.file_type().is_dir() { |
108 | Ok(entry) => entry, | 266 | if let Some(watcher) = &mut watcher { |
109 | Err(e) => { | 267 | watch_one(watcher, entry.path()); |
110 | log::warn!("watcher error: {}", e); | ||
111 | continue; | ||
112 | } | 268 | } |
113 | }; | 269 | } else { |
114 | if !entry.file_type().is_file() { | 270 | let path = config.contains(entry.path()).unwrap(); |
115 | continue; | 271 | files.push(path.to_owned()); |
116 | } | 272 | } |
117 | let path = entry.path(); | ||
118 | let text = match fs::read_to_string(path) { | ||
119 | Ok(text) => text, | ||
120 | Err(e) => { | ||
121 | log::warn!("watcher error: {}", e); | ||
122 | continue; | ||
123 | } | ||
124 | }; | ||
125 | let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap(); | ||
126 | res.push((path.to_owned(), text)) | ||
127 | } | 273 | } |
128 | res | 274 | files |
275 | } | ||
276 | |||
277 | fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) { | ||
278 | match watcher.watch(dir, RecursiveMode::NonRecursive) { | ||
279 | Ok(()) => log::debug!("watching \"{}\"", dir.display()), | ||
280 | Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e), | ||
281 | } | ||
282 | } | ||
283 | |||
284 | fn read_to_string(path: &Path) -> Option<String> { | ||
285 | fs::read_to_string(&path) | ||
286 | .map_err(|e| log::warn!("failed to read file {}", e)) | ||
287 | .ok() | ||
129 | } | 288 | } |
diff --git a/crates/ra_vfs/src/io/watcher.rs b/crates/ra_vfs/src/io/watcher.rs deleted file mode 100644 index ff6775f59..000000000 --- a/crates/ra_vfs/src/io/watcher.rs +++ /dev/null | |||
@@ -1,200 +0,0 @@ | |||
1 | use crate::{io, RootFilter, Roots, VfsRoot}; | ||
2 | use crossbeam_channel::Sender; | ||
3 | use drop_bomb::DropBomb; | ||
4 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; | ||
5 | use parking_lot::Mutex; | ||
6 | use std::{ | ||
7 | fs, | ||
8 | path::{Path, PathBuf}, | ||
9 | sync::{mpsc, Arc}, | ||
10 | thread, | ||
11 | time::Duration, | ||
12 | }; | ||
13 | use walkdir::WalkDir; | ||
14 | |||
15 | #[derive(Debug)] | ||
16 | enum ChangeKind { | ||
17 | Create, | ||
18 | Write, | ||
19 | Remove, | ||
20 | } | ||
21 | |||
22 | const WATCHER_DELAY: Duration = Duration::from_millis(250); | ||
23 | |||
24 | pub(crate) struct Watcher { | ||
25 | thread: thread::JoinHandle<()>, | ||
26 | bomb: DropBomb, | ||
27 | watcher: Arc<Mutex<Option<RecommendedWatcher>>>, | ||
28 | } | ||
29 | |||
30 | impl Watcher { | ||
31 | pub(crate) fn start( | ||
32 | roots: Arc<Roots>, | ||
33 | output_sender: Sender<io::TaskResult>, | ||
34 | ) -> Result<Watcher, Box<std::error::Error>> { | ||
35 | let (input_sender, input_receiver) = mpsc::channel(); | ||
36 | let watcher = Arc::new(Mutex::new(Some(notify::watcher( | ||
37 | input_sender, | ||
38 | WATCHER_DELAY, | ||
39 | )?))); | ||
40 | let sender = output_sender.clone(); | ||
41 | let watcher_clone = watcher.clone(); | ||
42 | let thread = thread::spawn(move || { | ||
43 | let worker = WatcherWorker { | ||
44 | roots, | ||
45 | watcher: watcher_clone, | ||
46 | sender, | ||
47 | }; | ||
48 | input_receiver | ||
49 | .into_iter() | ||
50 | // forward relevant events only | ||
51 | .try_for_each(|change| worker.handle_debounced_event(change)) | ||
52 | .unwrap() | ||
53 | }); | ||
54 | Ok(Watcher { | ||
55 | thread, | ||
56 | watcher, | ||
57 | bomb: DropBomb::new(format!("Watcher was not shutdown")), | ||
58 | }) | ||
59 | } | ||
60 | |||
61 | pub fn watch_root(&mut self, filter: &RootFilter) { | ||
62 | for res in WalkDir::new(&filter.root) | ||
63 | .into_iter() | ||
64 | .filter_entry(filter.entry_filter()) | ||
65 | { | ||
66 | match res { | ||
67 | Ok(entry) => { | ||
68 | if entry.file_type().is_dir() { | ||
69 | watch_one(self.watcher.as_ref(), entry.path()); | ||
70 | } | ||
71 | } | ||
72 | Err(e) => log::warn!("watcher error: {}", e), | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub fn shutdown(mut self) -> thread::Result<()> { | ||
78 | self.bomb.defuse(); | ||
79 | drop(self.watcher.lock().take()); | ||
80 | let res = self.thread.join(); | ||
81 | match &res { | ||
82 | Ok(()) => log::info!("... Watcher terminated with ok"), | ||
83 | Err(_) => log::error!("... Watcher terminated with err"), | ||
84 | } | ||
85 | res | ||
86 | } | ||
87 | } | ||
88 | |||
89 | struct WatcherWorker { | ||
90 | watcher: Arc<Mutex<Option<RecommendedWatcher>>>, | ||
91 | roots: Arc<Roots>, | ||
92 | sender: Sender<io::TaskResult>, | ||
93 | } | ||
94 | |||
95 | impl WatcherWorker { | ||
96 | fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<(), Box<std::error::Error>> { | ||
97 | match ev { | ||
98 | DebouncedEvent::NoticeWrite(_) | ||
99 | | DebouncedEvent::NoticeRemove(_) | ||
100 | | DebouncedEvent::Chmod(_) => { | ||
101 | // ignore | ||
102 | } | ||
103 | DebouncedEvent::Rescan => { | ||
104 | // TODO rescan all roots | ||
105 | } | ||
106 | DebouncedEvent::Create(path) => { | ||
107 | self.handle_change(path, ChangeKind::Create); | ||
108 | } | ||
109 | DebouncedEvent::Write(path) => { | ||
110 | self.handle_change(path, ChangeKind::Write); | ||
111 | } | ||
112 | DebouncedEvent::Remove(path) => { | ||
113 | self.handle_change(path, ChangeKind::Remove); | ||
114 | } | ||
115 | DebouncedEvent::Rename(src, dst) => { | ||
116 | self.handle_change(src, ChangeKind::Remove); | ||
117 | self.handle_change(dst, ChangeKind::Create); | ||
118 | } | ||
119 | DebouncedEvent::Error(err, path) => { | ||
120 | // TODO should we reload the file contents? | ||
121 | log::warn!("watcher error \"{}\", {:?}", err, path); | ||
122 | } | ||
123 | } | ||
124 | Ok(()) | ||
125 | } | ||
126 | |||
127 | fn handle_change(&self, path: PathBuf, kind: ChangeKind) { | ||
128 | if let Err(e) = self.try_handle_change(path, kind) { | ||
129 | log::warn!("watcher error: {}", e) | ||
130 | } | ||
131 | } | ||
132 | |||
133 | fn try_handle_change( | ||
134 | &self, | ||
135 | path: PathBuf, | ||
136 | kind: ChangeKind, | ||
137 | ) -> Result<(), Box<std::error::Error>> { | ||
138 | let (root, rel_path) = match self.roots.find(&path) { | ||
139 | Some(x) => x, | ||
140 | None => return Ok(()), | ||
141 | }; | ||
142 | match kind { | ||
143 | ChangeKind::Create => { | ||
144 | if path.is_dir() { | ||
145 | self.watch_recursive(&path, root); | ||
146 | } else { | ||
147 | let text = fs::read_to_string(&path)?; | ||
148 | self.sender.send(io::TaskResult::AddSingleFile { | ||
149 | root, | ||
150 | path: rel_path, | ||
151 | text, | ||
152 | })? | ||
153 | } | ||
154 | } | ||
155 | ChangeKind::Write => { | ||
156 | let text = fs::read_to_string(&path)?; | ||
157 | self.sender.send(io::TaskResult::ChangeSingleFile { | ||
158 | root, | ||
159 | path: rel_path, | ||
160 | text, | ||
161 | })? | ||
162 | } | ||
163 | ChangeKind::Remove => self.sender.send(io::TaskResult::RemoveSingleFile { | ||
164 | root, | ||
165 | path: rel_path, | ||
166 | })?, | ||
167 | } | ||
168 | Ok(()) | ||
169 | } | ||
170 | |||
171 | fn watch_recursive(&self, dir: &Path, root: VfsRoot) { | ||
172 | let filter = &self.roots[root]; | ||
173 | for res in WalkDir::new(dir) | ||
174 | .into_iter() | ||
175 | .filter_entry(filter.entry_filter()) | ||
176 | { | ||
177 | match res { | ||
178 | Ok(entry) => { | ||
179 | if entry.file_type().is_dir() { | ||
180 | watch_one(self.watcher.as_ref(), entry.path()); | ||
181 | } else { | ||
182 | // emit only for files otherwise we will cause watch_recursive to be called again with a dir that we are already watching | ||
183 | // emit as create because we haven't seen it yet | ||
184 | self.handle_change(entry.path().to_path_buf(), ChangeKind::Create); | ||
185 | } | ||
186 | } | ||
187 | Err(e) => log::warn!("watcher error: {}", e), | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | } | ||
192 | |||
193 | fn watch_one(watcher: &Mutex<Option<RecommendedWatcher>>, dir: &Path) { | ||
194 | if let Some(watcher) = watcher.lock().as_mut() { | ||
195 | match watcher.watch(dir, RecursiveMode::NonRecursive) { | ||
196 | Ok(()) => log::debug!("watching \"{}\"", dir.display()), | ||
197 | Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e), | ||
198 | } | ||
199 | } | ||
200 | } | ||
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 70a13f765..71a3f807d 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -18,94 +18,78 @@ mod io; | |||
18 | use std::{ | 18 | use std::{ |
19 | cmp::Reverse, | 19 | cmp::Reverse, |
20 | fmt, fs, mem, | 20 | fmt, fs, mem, |
21 | ops::{Deref, DerefMut}, | ||
22 | path::{Path, PathBuf}, | 21 | path::{Path, PathBuf}, |
23 | sync::Arc, | 22 | sync::Arc, |
24 | thread, | 23 | thread, |
25 | }; | 24 | }; |
26 | 25 | ||
27 | use crossbeam_channel::Receiver; | 26 | use crossbeam_channel::Receiver; |
28 | use ra_arena::{impl_arena_id, Arena, RawId}; | 27 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; |
29 | use relative_path::{Component, RelativePath, RelativePathBuf}; | 28 | use relative_path::{Component, RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 29 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | use walkdir::DirEntry; | ||
32 | 30 | ||
33 | pub use crate::io::TaskResult as VfsTask; | 31 | pub use crate::io::TaskResult as VfsTask; |
34 | use io::{TaskResult, Worker}; | 32 | use io::{TaskResult, Worker}; |
35 | 33 | ||
36 | /// `RootFilter` is a predicate that checks if a file can belong to a root. If | 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
37 | /// several filters match a file (nested dirs), the most nested one wins. | 35 | pub struct VfsRoot(pub RawId); |
38 | pub(crate) struct RootFilter { | 36 | impl_arena_id!(VfsRoot); |
37 | |||
38 | /// Describes the contents of a single source root. | ||
39 | /// | ||
40 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` whihc | ||
41 | /// specifes the source root or as a function whihc takes a `PathBuf` and | ||
42 | /// returns `true` iff path belongs to the source root | ||
43 | pub(crate) struct RootConfig { | ||
39 | root: PathBuf, | 44 | root: PathBuf, |
40 | filter: fn(&Path, &RelativePath) -> bool, | ||
41 | excluded_dirs: Vec<PathBuf>, | 45 | excluded_dirs: Vec<PathBuf>, |
42 | } | 46 | } |
43 | 47 | ||
44 | impl RootFilter { | 48 | pub(crate) struct Roots { |
45 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter { | 49 | roots: Arena<VfsRoot, Arc<RootConfig>>, |
46 | RootFilter { | 50 | } |
51 | |||
52 | impl std::ops::Deref for Roots { | ||
53 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | ||
54 | fn deref(&self) -> &Self::Target { | ||
55 | &self.roots | ||
56 | } | ||
57 | } | ||
58 | |||
59 | impl RootConfig { | ||
60 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig { | ||
61 | RootConfig { | ||
47 | root, | 62 | root, |
48 | filter: default_filter, | ||
49 | excluded_dirs, | 63 | excluded_dirs, |
50 | } | 64 | } |
51 | } | 65 | } |
52 | /// Check if this root can contain `path`. NB: even if this returns | 66 | /// Cheks if root contains a path and returns a root-relative path. |
53 | /// true, the `path` might actually be conained in some nested root. | 67 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { |
54 | pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { | 68 | // First, check excluded dirs |
55 | let rel_path = path.strip_prefix(&self.root).ok()?; | 69 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { |
56 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
57 | if !(self.filter)(path, rel_path.as_relative_path()) { | ||
58 | return None; | 70 | return None; |
59 | } | 71 | } |
60 | Some(rel_path) | 72 | let rel_path = path.strip_prefix(&self.root).ok()?; |
61 | } | 73 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; |
62 | |||
63 | pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a { | ||
64 | move |entry: &DirEntry| { | ||
65 | if entry.file_type().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path()) | ||
66 | { | ||
67 | // do not walk nested roots | ||
68 | false | ||
69 | } else { | ||
70 | self.can_contain(entry.path()).is_some() | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | 74 | ||
76 | pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { | 75 | // Ignore some common directories. |
77 | if path.is_dir() { | 76 | // |
77 | // FIXME: don't hard-code, specify at source-root creation time using | ||
78 | // gitignore | ||
78 | for (i, c) in rel_path.components().enumerate() { | 79 | for (i, c) in rel_path.components().enumerate() { |
79 | if let Component::Normal(c) = c { | 80 | if let Component::Normal(c) = c { |
80 | // TODO hardcoded for now | ||
81 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | 81 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { |
82 | return false; | 82 | return None; |
83 | } | 83 | } |
84 | } | 84 | } |
85 | } | 85 | } |
86 | true | ||
87 | } else { | ||
88 | rel_path.extension() == Some("rs") | ||
89 | } | ||
90 | } | ||
91 | |||
92 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
93 | pub struct VfsRoot(pub RawId); | ||
94 | impl_arena_id!(VfsRoot); | ||
95 | |||
96 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
97 | pub struct VfsFile(pub RawId); | ||
98 | impl_arena_id!(VfsFile); | ||
99 | 86 | ||
100 | struct VfsFileData { | 87 | if path.is_file() && rel_path.extension() != Some("rs") { |
101 | root: VfsRoot, | 88 | return None; |
102 | path: RelativePathBuf, | 89 | } |
103 | is_overlayed: bool, | ||
104 | text: Arc<String>, | ||
105 | } | ||
106 | 90 | ||
107 | pub(crate) struct Roots { | 91 | Some(rel_path) |
108 | roots: Arena<VfsRoot, Arc<RootFilter>>, | 92 | } |
109 | } | 93 | } |
110 | 94 | ||
111 | impl Roots { | 95 | impl Roots { |
@@ -120,59 +104,61 @@ impl Roots { | |||
120 | .map(|it| it.clone()) | 104 | .map(|it| it.clone()) |
121 | .collect::<Vec<_>>(); | 105 | .collect::<Vec<_>>(); |
122 | 106 | ||
123 | let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots)); | 107 | let config = Arc::new(RootConfig::new(path.clone(), nested_roots)); |
124 | 108 | ||
125 | roots.alloc(root_filter.clone()); | 109 | roots.alloc(config.clone()); |
126 | } | 110 | } |
127 | Roots { roots } | 111 | Roots { roots } |
128 | } | 112 | } |
129 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | 113 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { |
130 | self.roots | 114 | self.roots |
131 | .iter() | 115 | .iter() |
132 | .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it))) | 116 | .find_map(|(root, data)| data.contains(path).map(|it| (root, it))) |
133 | } | 117 | } |
134 | } | 118 | } |
135 | 119 | ||
136 | impl Deref for Roots { | 120 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
137 | type Target = Arena<VfsRoot, Arc<RootFilter>>; | 121 | pub struct VfsFile(pub RawId); |
138 | fn deref(&self) -> &Self::Target { | 122 | impl_arena_id!(VfsFile); |
139 | &self.roots | ||
140 | } | ||
141 | } | ||
142 | 123 | ||
143 | impl DerefMut for Roots { | 124 | struct VfsFileData { |
144 | fn deref_mut(&mut self) -> &mut Self::Target { | 125 | root: VfsRoot, |
145 | &mut self.roots | 126 | path: RelativePathBuf, |
146 | } | 127 | is_overlayed: bool, |
128 | text: Arc<String>, | ||
147 | } | 129 | } |
148 | 130 | ||
149 | pub struct Vfs { | 131 | pub struct Vfs { |
150 | roots: Arc<Roots>, | 132 | roots: Arc<Roots>, |
151 | files: Arena<VfsFile, VfsFileData>, | 133 | files: Arena<VfsFile, VfsFileData>, |
152 | root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, | 134 | root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>, |
153 | pending_changes: Vec<VfsChange>, | 135 | pending_changes: Vec<VfsChange>, |
154 | worker: Worker, | 136 | worker: Worker, |
155 | } | 137 | } |
156 | 138 | ||
157 | impl fmt::Debug for Vfs { | 139 | impl fmt::Debug for Vfs { |
158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 140 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
159 | f.write_str("Vfs { ... }") | 141 | f.debug_struct("Vfs") |
142 | .field("n_roots", &self.roots.len()) | ||
143 | .field("n_files", &self.files.len()) | ||
144 | .field("n_pending_changes", &self.pending_changes.len()) | ||
145 | .finish() | ||
160 | } | 146 | } |
161 | } | 147 | } |
162 | 148 | ||
163 | impl Vfs { | 149 | impl Vfs { |
164 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { | 150 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { |
165 | let roots = Arc::new(Roots::new(roots)); | 151 | let roots = Arc::new(Roots::new(roots)); |
166 | let worker = io::Worker::start(roots.clone()); | 152 | let worker = io::Worker::start(Arc::clone(&roots)); |
167 | let mut root2files = FxHashMap::default(); | 153 | let mut root2files = ArenaMap::default(); |
168 | 154 | ||
169 | for (root, filter) in roots.iter() { | 155 | for (root, config) in roots.iter() { |
170 | root2files.insert(root, Default::default()); | 156 | root2files.insert(root, Default::default()); |
171 | worker | 157 | worker |
172 | .sender() | 158 | .sender() |
173 | .send(io::Task::AddRoot { | 159 | .send(io::Task::AddRoot { |
174 | root, | 160 | root, |
175 | filter: filter.clone(), | 161 | config: Arc::clone(config), |
176 | }) | 162 | }) |
177 | .unwrap(); | 163 | .unwrap(); |
178 | } | 164 | } |
@@ -242,7 +228,7 @@ impl Vfs { | |||
242 | let mut cur_files = Vec::new(); | 228 | let mut cur_files = Vec::new(); |
243 | // While we were scanning the root in the backgound, a file might have | 229 | // While we were scanning the root in the backgound, a file might have |
244 | // been open in the editor, so we need to account for that. | 230 | // been open in the editor, so we need to account for that. |
245 | let exising = self.root2files[&root] | 231 | let exising = self.root2files[root] |
246 | .iter() | 232 | .iter() |
247 | .map(|&file| (self.files[file].path.clone(), file)) | 233 | .map(|&file| (self.files[file].path.clone(), file)) |
248 | .collect::<FxHashMap<_, _>>(); | 234 | .collect::<FxHashMap<_, _>>(); |
@@ -384,7 +370,7 @@ impl Vfs { | |||
384 | is_overlayed, | 370 | is_overlayed, |
385 | }; | 371 | }; |
386 | let file = self.files.alloc(data); | 372 | let file = self.files.alloc(data); |
387 | self.root2files.get_mut(&root).unwrap().insert(file); | 373 | self.root2files.get_mut(root).unwrap().insert(file); |
388 | file | 374 | file |
389 | } | 375 | } |
390 | 376 | ||
@@ -399,7 +385,7 @@ impl Vfs { | |||
399 | self.files[file].text = Default::default(); | 385 | self.files[file].text = Default::default(); |
400 | self.files[file].path = Default::default(); | 386 | self.files[file].path = Default::default(); |
401 | let root = self.files[file].root; | 387 | let root = self.files[file].root; |
402 | let removed = self.root2files.get_mut(&root).unwrap().remove(&file); | 388 | let removed = self.root2files.get_mut(root).unwrap().remove(&file); |
403 | assert!(removed); | 389 | assert!(removed); |
404 | } | 390 | } |
405 | 391 | ||
@@ -410,7 +396,7 @@ impl Vfs { | |||
410 | } | 396 | } |
411 | 397 | ||
412 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { | 398 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { |
413 | self.root2files[&root] | 399 | self.root2files[root] |
414 | .iter() | 400 | .iter() |
415 | .map(|&it| it) | 401 | .map(|&it| it) |
416 | .find(|&file| self.files[file].path == path) | 402 | .find(|&file| self.files[file].path == path) |