aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs
diff options
context:
space:
mode:
authorBernardo <[email protected]>2019-01-06 14:05:12 +0000
committerAleksey Kladov <[email protected]>2019-01-26 08:46:16 +0000
commit1d5eaefe8a8e4f8b267d51ee8ece866741586ada (patch)
tree04c740f694ecacdd84a635256b9a3d483f45f593 /crates/ra_vfs
parentac757e114e9dcbc70600803dd4adc4f99ecde78e (diff)
initial Watcher impl
Diffstat (limited to 'crates/ra_vfs')
-rw-r--r--crates/ra_vfs/Cargo.toml2
-rw-r--r--crates/ra_vfs/src/lib.rs59
-rw-r--r--crates/ra_vfs/src/watcher.rs96
3 files changed, 143 insertions, 14 deletions
diff --git a/crates/ra_vfs/Cargo.toml b/crates/ra_vfs/Cargo.toml
index e637063c9..f7a972e91 100644
--- a/crates/ra_vfs/Cargo.toml
+++ b/crates/ra_vfs/Cargo.toml
@@ -10,6 +10,8 @@ relative-path = "0.4.0"
10rustc-hash = "1.0" 10rustc-hash = "1.0"
11crossbeam-channel = "0.3.5" 11crossbeam-channel = "0.3.5"
12log = "0.4.6" 12log = "0.4.6"
13notify = "4"
14drop_bomb = "0.1.0"
13 15
14thread_worker = { path = "../thread_worker" } 16thread_worker = { path = "../thread_worker" }
15ra_arena = { path = "../ra_arena" } 17ra_arena = { path = "../ra_arena" }
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index cdea18d73..5336822b3 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -14,26 +14,26 @@
14//! which are watched for changes. Typically, there will be a root for each 14//! which are watched for changes. Typically, there will be a root for each
15//! Cargo package. 15//! Cargo package.
16mod io; 16mod io;
17mod watcher;
17 18
18use std::{ 19use std::{
19 fmt,
20 mem,
21 thread,
22 cmp::Reverse, 20 cmp::Reverse,
23 path::{Path, PathBuf},
24 ffi::OsStr, 21 ffi::OsStr,
22 fmt, fs, mem,
23 path::{Path, PathBuf},
25 sync::Arc, 24 sync::Arc,
26 fs, 25 thread,
27}; 26};
28 27
29use rustc_hash::{FxHashMap, FxHashSet};
30use relative_path::RelativePathBuf;
31use crossbeam_channel::Receiver; 28use crossbeam_channel::Receiver;
32use walkdir::DirEntry; 29use ra_arena::{impl_arena_id, Arena, RawId};
30use relative_path::RelativePathBuf;
31use rustc_hash::{FxHashMap, FxHashSet};
33use thread_worker::WorkerHandle; 32use thread_worker::WorkerHandle;
34use ra_arena::{Arena, RawId, impl_arena_id}; 33use walkdir::DirEntry;
35 34
36pub use crate::io::TaskResult as VfsTask; 35pub use crate::io::TaskResult as VfsTask;
36pub use crate::watcher::{Watcher, WatcherChange};
37 37
38/// `RootFilter` is a predicate that checks if a file can belong to a root. If 38/// `RootFilter` is a predicate that checks if a file can belong to a root. If
39/// several filters match a file (nested dirs), the most nested one wins. 39/// several filters match a file (nested dirs), the most nested one wins.
@@ -85,6 +85,7 @@ pub struct Vfs {
85 pending_changes: Vec<VfsChange>, 85 pending_changes: Vec<VfsChange>,
86 worker: io::Worker, 86 worker: io::Worker,
87 worker_handle: WorkerHandle, 87 worker_handle: WorkerHandle,
88 watcher: Watcher,
88} 89}
89 90
90impl fmt::Debug for Vfs { 91impl fmt::Debug for Vfs {
@@ -97,12 +98,15 @@ impl Vfs {
97 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 98 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
98 let (worker, worker_handle) = io::start(); 99 let (worker, worker_handle) = io::start();
99 100
101 let watcher = Watcher::new().unwrap(); // TODO return Result?
102
100 let mut res = Vfs { 103 let mut res = Vfs {
101 roots: Arena::default(), 104 roots: Arena::default(),
102 files: Arena::default(), 105 files: Arena::default(),
103 root2files: FxHashMap::default(), 106 root2files: FxHashMap::default(),
104 worker, 107 worker,
105 worker_handle, 108 worker_handle,
109 watcher,
106 pending_changes: Vec::new(), 110 pending_changes: Vec::new(),
107 }; 111 };
108 112
@@ -129,6 +133,7 @@ impl Vfs {
129 filter: Box::new(filter), 133 filter: Box::new(filter),
130 }; 134 };
131 res.worker.inp.send(task).unwrap(); 135 res.worker.inp.send(task).unwrap();
136 res.watcher.watch(path).unwrap();
132 } 137 }
133 let roots = res.roots.iter().map(|(id, _)| id).collect(); 138 let roots = res.roots.iter().map(|(id, _)| id).collect();
134 (res, roots) 139 (res, roots)
@@ -183,6 +188,10 @@ impl Vfs {
183 &self.worker.out 188 &self.worker.out
184 } 189 }
185 190
191 pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
192 &self.watcher.change_receiver()
193 }
194
186 pub fn handle_task(&mut self, task: io::TaskResult) { 195 pub fn handle_task(&mut self, task: io::TaskResult) {
187 let mut files = Vec::new(); 196 let mut files = Vec::new();
188 // While we were scanning the root in the backgound, a file might have 197 // While we were scanning the root in the backgound, a file might have
@@ -209,22 +218,41 @@ impl Vfs {
209 self.pending_changes.push(change); 218 self.pending_changes.push(change);
210 } 219 }
211 220
212 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { 221 pub fn handle_change(&mut self, change: WatcherChange) {
222 match change {
223 WatcherChange::Create(path) => {
224 self.add_file_overlay(&path, None);
225 }
226 WatcherChange::Remove(path) => {
227 self.remove_file_overlay(&path);
228 }
229 WatcherChange::Rename(src, dst) => {
230 self.remove_file_overlay(&src);
231 self.add_file_overlay(&dst, None);
232 }
233 WatcherChange::Write(path) => {
234 self.change_file_overlay(&path, None);
235 }
236 }
237 }
238
239 pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> {
213 let mut res = None; 240 let mut res = None;
214 if let Some((root, path, file)) = self.find_root(path) { 241 if let Some((root, rel_path, file)) = self.find_root(path) {
242 let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
215 let text = Arc::new(text); 243 let text = Arc::new(text);
216 let change = if let Some(file) = file { 244 let change = if let Some(file) = file {
217 res = Some(file); 245 res = Some(file);
218 self.change_file(file, Arc::clone(&text)); 246 self.change_file(file, Arc::clone(&text));
219 VfsChange::ChangeFile { file, text } 247 VfsChange::ChangeFile { file, text }
220 } else { 248 } else {
221 let file = self.add_file(root, path.clone(), Arc::clone(&text)); 249 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
222 res = Some(file); 250 res = Some(file);
223 VfsChange::AddFile { 251 VfsChange::AddFile {
224 file, 252 file,
225 text, 253 text,
226 root, 254 root,
227 path, 255 path: rel_path,
228 } 256 }
229 }; 257 };
230 self.pending_changes.push(change); 258 self.pending_changes.push(change);
@@ -232,8 +260,10 @@ impl Vfs {
232 res 260 res
233 } 261 }
234 262
235 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { 263 pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) {
236 if let Some((_root, _path, file)) = self.find_root(path) { 264 if let Some((_root, _path, file)) = self.find_root(path) {
265 let new_text =
266 new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
237 let file = file.expect("can't change a file which wasn't added"); 267 let file = file.expect("can't change a file which wasn't added");
238 let text = Arc::new(new_text); 268 let text = Arc::new(new_text);
239 self.change_file(file, Arc::clone(&text)); 269 self.change_file(file, Arc::clone(&text));
@@ -267,6 +297,7 @@ impl Vfs {
267 297
268 /// Sutdown the VFS and terminate the background watching thread. 298 /// Sutdown the VFS and terminate the background watching thread.
269 pub fn shutdown(self) -> thread::Result<()> { 299 pub fn shutdown(self) -> thread::Result<()> {
300 let _ = self.watcher.shutdown();
270 let _ = self.worker.shutdown(); 301 let _ = self.worker.shutdown();
271 self.worker_handle.shutdown() 302 self.worker_handle.shutdown()
272 } 303 }
diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs
new file mode 100644
index 000000000..cc05f949e
--- /dev/null
+++ b/crates/ra_vfs/src/watcher.rs
@@ -0,0 +1,96 @@
1use std::{
2 path::{Path, PathBuf},
3 sync::mpsc,
4 thread,
5 time::Duration,
6};
7
8use crossbeam_channel::Receiver;
9use drop_bomb::DropBomb;
10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
11
12pub struct Watcher {
13 receiver: Receiver<WatcherChange>,
14 watcher: RecommendedWatcher,
15 thread: thread::JoinHandle<()>,
16 bomb: DropBomb,
17}
18
19#[derive(Debug)]
20pub enum WatcherChange {
21 Create(PathBuf),
22 Write(PathBuf),
23 Remove(PathBuf),
24 Rename(PathBuf, PathBuf),
25}
26
27impl WatcherChange {
28 fn from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> {
29 match ev {
30 DebouncedEvent::NoticeWrite(_)
31 | DebouncedEvent::NoticeRemove(_)
32 | DebouncedEvent::Chmod(_)
33 | DebouncedEvent::Rescan => {
34 // ignore
35 None
36 }
37 DebouncedEvent::Create(path) => Some(WatcherChange::Create(path)),
38 DebouncedEvent::Write(path) => Some(WatcherChange::Write(path)),
39 DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)),
40 DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)),
41 DebouncedEvent::Error(err, path) => {
42 // TODO
43 log::warn!("watch error {}, {:?}", err, path);
44 None
45 }
46 }
47 }
48}
49
50impl Watcher {
51 pub fn new() -> Result<Watcher, Box<std::error::Error>> {
52 let (input_sender, input_receiver) = mpsc::channel();
53 let watcher = notify::watcher(input_sender, Duration::from_millis(250))?;
54 let (output_sender, output_receiver) = crossbeam_channel::unbounded();
55 let thread = thread::spawn(move || loop {
56 match input_receiver.recv() {
57 Ok(ev) => {
58 // forward relevant events only
59 if let Some(change) = WatcherChange::from_debounced_event(ev) {
60 output_sender.send(change).unwrap();
61 }
62 }
63 Err(err) => {
64 log::debug!("Watcher stopped ({})", err);
65 break;
66 }
67 }
68 });
69 Ok(Watcher {
70 receiver: output_receiver,
71 watcher,
72 thread,
73 bomb: DropBomb::new(format!("Watcher was not shutdown")),
74 })
75 }
76
77 pub fn watch(&mut self, root: impl AsRef<Path>) -> Result<(), Box<std::error::Error>> {
78 self.watcher.watch(root, RecursiveMode::Recursive)?;
79 Ok(())
80 }
81
82 pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
83 &self.receiver
84 }
85
86 pub fn shutdown(mut self) -> thread::Result<()> {
87 self.bomb.defuse();
88 drop(self.watcher);
89 let res = self.thread.join();
90 match &res {
91 Ok(()) => log::info!("... Watcher terminated with ok"),
92 Err(_) => log::error!("... Watcher terminated with err"),
93 }
94 res
95 }
96}