aboutsummaryrefslogtreecommitdiff
path: root/crates
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
parentac757e114e9dcbc70600803dd4adc4f99ecde78e (diff)
initial Watcher impl
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs19
-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
4 files changed, 159 insertions, 17 deletions
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index ddd20a41f..e5a0603d1 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -11,7 +11,7 @@ use gen_lsp_server::{
11}; 11};
12use lsp_types::NumberOrString; 12use lsp_types::NumberOrString;
13use ra_ide_api::{Canceled, FileId, LibraryData}; 13use ra_ide_api::{Canceled, FileId, LibraryData};
14use ra_vfs::VfsTask; 14use ra_vfs::{VfsTask, WatcherChange};
15use rustc_hash::FxHashSet; 15use rustc_hash::FxHashSet;
16use serde::{de::DeserializeOwned, Serialize}; 16use serde::{de::DeserializeOwned, Serialize};
17use threadpool::ThreadPool; 17use threadpool::ThreadPool;
@@ -113,6 +113,7 @@ enum Event {
113 Msg(RawMessage), 113 Msg(RawMessage),
114 Task(Task), 114 Task(Task),
115 Vfs(VfsTask), 115 Vfs(VfsTask),
116 Watcher(WatcherChange),
116 Lib(LibraryData), 117 Lib(LibraryData),
117} 118}
118 119
@@ -149,6 +150,7 @@ impl fmt::Debug for Event {
149 Event::Task(it) => fmt::Debug::fmt(it, f), 150 Event::Task(it) => fmt::Debug::fmt(it, f),
150 Event::Vfs(it) => fmt::Debug::fmt(it, f), 151 Event::Vfs(it) => fmt::Debug::fmt(it, f),
151 Event::Lib(it) => fmt::Debug::fmt(it, f), 152 Event::Lib(it) => fmt::Debug::fmt(it, f),
153 Event::Watcher(it) => fmt::Debug::fmt(it, f),
152 } 154 }
153 } 155 }
154} 156}
@@ -183,6 +185,10 @@ fn main_loop_inner(
183 Ok(task) => Event::Vfs(task), 185 Ok(task) => Event::Vfs(task),
184 Err(RecvError) => bail!("vfs died"), 186 Err(RecvError) => bail!("vfs died"),
185 }, 187 },
188 recv(state.vfs.read().change_receiver()) -> change => match change {
189 Ok(change) => Event::Watcher(change),
190 Err(RecvError) => bail!("vfs watcher died"),
191 },
186 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) 192 recv(libdata_receiver) -> data => Event::Lib(data.unwrap())
187 }; 193 };
188 log::info!("loop_turn = {:?}", event); 194 log::info!("loop_turn = {:?}", event);
@@ -194,6 +200,10 @@ fn main_loop_inner(
194 state.vfs.write().handle_task(task); 200 state.vfs.write().handle_task(task);
195 state_changed = true; 201 state_changed = true;
196 } 202 }
203 Event::Watcher(change) => {
204 state.vfs.write().handle_change(change);
205 state_changed = true;
206 }
197 Event::Lib(lib) => { 207 Event::Lib(lib) => {
198 feedback(internal_mode, "library loaded", msg_sender); 208 feedback(internal_mode, "library loaded", msg_sender);
199 state.add_lib(lib); 209 state.add_lib(lib);
@@ -365,7 +375,7 @@ fn on_notification(
365 if let Some(file_id) = state 375 if let Some(file_id) = state
366 .vfs 376 .vfs
367 .write() 377 .write()
368 .add_file_overlay(&path, params.text_document.text) 378 .add_file_overlay(&path, Some(params.text_document.text))
369 { 379 {
370 subs.add_sub(FileId(file_id.0.into())); 380 subs.add_sub(FileId(file_id.0.into()));
371 } 381 }
@@ -384,7 +394,10 @@ fn on_notification(
384 .pop() 394 .pop()
385 .ok_or_else(|| format_err!("empty changes"))? 395 .ok_or_else(|| format_err!("empty changes"))?
386 .text; 396 .text;
387 state.vfs.write().change_file_overlay(path.as_path(), text); 397 state
398 .vfs
399 .write()
400 .change_file_overlay(path.as_path(), Some(text));
388 return Ok(()); 401 return Ok(());
389 } 402 }
390 Err(not) => not, 403 Err(not) => not,
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}