diff options
author | Bernardo <[email protected]> | 2019-01-06 14:05:12 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-01-26 08:46:16 +0000 |
commit | 1d5eaefe8a8e4f8b267d51ee8ece866741586ada (patch) | |
tree | 04c740f694ecacdd84a635256b9a3d483f45f593 /crates/ra_vfs | |
parent | ac757e114e9dcbc70600803dd4adc4f99ecde78e (diff) |
initial Watcher impl
Diffstat (limited to 'crates/ra_vfs')
-rw-r--r-- | crates/ra_vfs/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 59 | ||||
-rw-r--r-- | crates/ra_vfs/src/watcher.rs | 96 |
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" | |||
10 | rustc-hash = "1.0" | 10 | rustc-hash = "1.0" |
11 | crossbeam-channel = "0.3.5" | 11 | crossbeam-channel = "0.3.5" |
12 | log = "0.4.6" | 12 | log = "0.4.6" |
13 | notify = "4" | ||
14 | drop_bomb = "0.1.0" | ||
13 | 15 | ||
14 | thread_worker = { path = "../thread_worker" } | 16 | thread_worker = { path = "../thread_worker" } |
15 | ra_arena = { path = "../ra_arena" } | 17 | ra_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. |
16 | mod io; | 16 | mod io; |
17 | mod watcher; | ||
17 | 18 | ||
18 | use std::{ | 19 | use 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 | ||
29 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
30 | use relative_path::RelativePathBuf; | ||
31 | use crossbeam_channel::Receiver; | 28 | use crossbeam_channel::Receiver; |
32 | use walkdir::DirEntry; | 29 | use ra_arena::{impl_arena_id, Arena, RawId}; |
30 | use relative_path::RelativePathBuf; | ||
31 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
33 | use thread_worker::WorkerHandle; | 32 | use thread_worker::WorkerHandle; |
34 | use ra_arena::{Arena, RawId, impl_arena_id}; | 33 | use walkdir::DirEntry; |
35 | 34 | ||
36 | pub use crate::io::TaskResult as VfsTask; | 35 | pub use crate::io::TaskResult as VfsTask; |
36 | pub 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 | ||
90 | impl fmt::Debug for Vfs { | 91 | impl 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 @@ | |||
1 | use std::{ | ||
2 | path::{Path, PathBuf}, | ||
3 | sync::mpsc, | ||
4 | thread, | ||
5 | time::Duration, | ||
6 | }; | ||
7 | |||
8 | use crossbeam_channel::Receiver; | ||
9 | use drop_bomb::DropBomb; | ||
10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; | ||
11 | |||
12 | pub struct Watcher { | ||
13 | receiver: Receiver<WatcherChange>, | ||
14 | watcher: RecommendedWatcher, | ||
15 | thread: thread::JoinHandle<()>, | ||
16 | bomb: DropBomb, | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | pub enum WatcherChange { | ||
21 | Create(PathBuf), | ||
22 | Write(PathBuf), | ||
23 | Remove(PathBuf), | ||
24 | Rename(PathBuf, PathBuf), | ||
25 | } | ||
26 | |||
27 | impl 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 | |||
50 | impl 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 | } | ||