aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs-notify
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
committerZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
commitf05d7b41a719d848844b054a16477b29d0f063c6 (patch)
tree0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /crates/vfs-notify
parent73ff610e41959e3e7c78a2b4b25b086883132956 (diff)
parent6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Hasn't fixed tests yet.
Diffstat (limited to 'crates/vfs-notify')
-rw-r--r--crates/vfs-notify/Cargo.toml5
-rw-r--r--crates/vfs-notify/src/include.rs43
-rw-r--r--crates/vfs-notify/src/lib.rs147
3 files changed, 66 insertions, 129 deletions
diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml
index 4737a52a7..fce7bae3a 100644
--- a/crates/vfs-notify/Cargo.toml
+++ b/crates/vfs-notify/Cargo.toml
@@ -3,13 +3,16 @@ name = "vfs-notify"
3version = "0.1.0" 3version = "0.1.0"
4authors = ["rust-analyzer developers"] 4authors = ["rust-analyzer developers"]
5edition = "2018" 5edition = "2018"
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
6 10
7[dependencies] 11[dependencies]
8log = "0.4.8" 12log = "0.4.8"
9rustc-hash = "1.0" 13rustc-hash = "1.0"
10jod-thread = "0.1.0" 14jod-thread = "0.1.0"
11walkdir = "2.3.1" 15walkdir = "2.3.1"
12globset = "0.4.5"
13crossbeam-channel = "0.4.0" 16crossbeam-channel = "0.4.0"
14notify = "5.0.0-pre.3" 17notify = "5.0.0-pre.3"
15 18
diff --git a/crates/vfs-notify/src/include.rs b/crates/vfs-notify/src/include.rs
deleted file mode 100644
index 7378766f5..000000000
--- a/crates/vfs-notify/src/include.rs
+++ /dev/null
@@ -1,43 +0,0 @@
1//! See `Include`.
2
3use std::convert::TryFrom;
4
5use globset::{Glob, GlobSet, GlobSetBuilder};
6use paths::{RelPath, RelPathBuf};
7
8/// `Include` is the opposite of .gitignore.
9///
10/// It describes the set of files inside some directory.
11///
12/// The current implementation is very limited, it allows white-listing file
13/// globs and black-listing directories.
14#[derive(Debug, Clone)]
15pub(crate) struct Include {
16 include_files: GlobSet,
17 exclude_dirs: Vec<RelPathBuf>,
18}
19
20impl Include {
21 pub(crate) fn new(include: Vec<String>) -> Include {
22 let mut include_files = GlobSetBuilder::new();
23 let mut exclude_dirs = Vec::new();
24
25 for glob in include {
26 if glob.starts_with("!/") {
27 if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) {
28 exclude_dirs.push(path)
29 }
30 } else {
31 include_files.add(Glob::new(&glob).unwrap());
32 }
33 }
34 let include_files = include_files.build().unwrap();
35 Include { include_files, exclude_dirs }
36 }
37 pub(crate) fn include_file(&self, path: &RelPath) -> bool {
38 self.include_files.is_match(path)
39 }
40 pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool {
41 self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded))
42 }
43}
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index b1ea298ae..e1e36612a 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -6,19 +6,14 @@
6//! 6//!
7//! Hopefully, one day a reliable file watching/walking crate appears on 7//! Hopefully, one day a reliable file watching/walking crate appears on
8//! crates.io, and we can reduce this to trivial glue code. 8//! crates.io, and we can reduce this to trivial glue code.
9mod include; 9use std::convert::TryFrom;
10 10
11use std::convert::{TryFrom, TryInto}; 11use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
12
13use crossbeam_channel::{select, unbounded, Receiver, Sender};
14use notify::{RecommendedWatcher, RecursiveMode, Watcher}; 12use notify::{RecommendedWatcher, RecursiveMode, Watcher};
15use paths::{AbsPath, AbsPathBuf}; 13use paths::{AbsPath, AbsPathBuf};
16use rustc_hash::FxHashSet;
17use vfs::loader; 14use vfs::loader;
18use walkdir::WalkDir; 15use walkdir::WalkDir;
19 16
20use crate::include::Include;
21
22#[derive(Debug)] 17#[derive(Debug)]
23pub struct NotifyHandle { 18pub struct NotifyHandle {
24 // Relative order of fields below is significant. 19 // Relative order of fields below is significant.
@@ -54,11 +49,9 @@ type NotifyEvent = notify::Result<notify::Event>;
54 49
55struct NotifyActor { 50struct NotifyActor {
56 sender: loader::Sender, 51 sender: loader::Sender,
57 config: Vec<(AbsPathBuf, Include, bool)>, 52 watched_entries: Vec<loader::Entry>,
58 watched_paths: FxHashSet<AbsPathBuf>, 53 // Drop order is significant.
59 // Drop order of fields bellow is significant, 54 watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
60 watcher: Option<RecommendedWatcher>,
61 watcher_receiver: Receiver<NotifyEvent>,
62} 55}
63 56
64#[derive(Debug)] 57#[derive(Debug)]
@@ -69,23 +62,13 @@ enum Event {
69 62
70impl NotifyActor { 63impl NotifyActor {
71 fn new(sender: loader::Sender) -> NotifyActor { 64 fn new(sender: loader::Sender) -> NotifyActor {
72 let (watcher_sender, watcher_receiver) = unbounded(); 65 NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
73 let watcher = log_notify_error(Watcher::new_immediate(move |event| {
74 watcher_sender.send(event).unwrap()
75 }));
76
77 NotifyActor {
78 sender,
79 config: Vec::new(),
80 watched_paths: FxHashSet::default(),
81 watcher,
82 watcher_receiver,
83 }
84 } 66 }
85 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> { 67 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
68 let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
86 select! { 69 select! {
87 recv(receiver) -> it => it.ok().map(Event::Message), 70 recv(receiver) -> it => it.ok().map(Event::Message),
88 recv(&self.watcher_receiver) -> it => Some(Event::NotifyEvent(it.unwrap())), 71 recv(watcher_receiver.unwrap_or(&never())) -> it => Some(Event::NotifyEvent(it.unwrap())),
89 } 72 }
90 } 73 }
91 fn run(mut self, inbox: Receiver<Message>) { 74 fn run(mut self, inbox: Receiver<Message>) {
@@ -94,19 +77,29 @@ impl NotifyActor {
94 match event { 77 match event {
95 Event::Message(msg) => match msg { 78 Event::Message(msg) => match msg {
96 Message::Config(config) => { 79 Message::Config(config) => {
80 self.watcher = None;
81 if !config.watch.is_empty() {
82 let (watcher_sender, watcher_receiver) = unbounded();
83 let watcher = log_notify_error(Watcher::new_immediate(move |event| {
84 watcher_sender.send(event).unwrap()
85 }));
86 self.watcher = watcher.map(|it| (it, watcher_receiver));
87 }
88
97 let n_total = config.load.len(); 89 let n_total = config.load.len();
98 self.send(loader::Message::Progress { n_total, n_done: 0 }); 90 self.send(loader::Message::Progress { n_total, n_done: 0 });
99 91
100 self.unwatch_all(); 92 self.watched_entries.clear();
101 self.config.clear();
102 93
103 for (i, entry) in config.load.into_iter().enumerate() { 94 for (i, entry) in config.load.into_iter().enumerate() {
104 let watch = config.watch.contains(&i); 95 let watch = config.watch.contains(&i);
96 if watch {
97 self.watched_entries.push(entry.clone())
98 }
105 let files = self.load_entry(entry, watch); 99 let files = self.load_entry(entry, watch);
106 self.send(loader::Message::Loaded { files }); 100 self.send(loader::Message::Loaded { files });
107 self.send(loader::Message::Progress { n_total, n_done: i + 1 }); 101 self.send(loader::Message::Progress { n_total, n_done: i + 1 });
108 } 102 }
109 self.config.sort_by(|x, y| x.0.cmp(&y.0));
110 } 103 }
111 Message::Invalidate(path) => { 104 Message::Invalidate(path) => {
112 let contents = read(path.as_path()); 105 let contents = read(path.as_path());
@@ -121,34 +114,27 @@ impl NotifyActor {
121 .into_iter() 114 .into_iter()
122 .map(|path| AbsPathBuf::try_from(path).unwrap()) 115 .map(|path| AbsPathBuf::try_from(path).unwrap())
123 .filter_map(|path| { 116 .filter_map(|path| {
124 let is_dir = path.is_dir(); 117 if path.is_dir()
125 let is_file = path.is_file(); 118 && self
126 119 .watched_entries
127 let config_idx = 120 .iter()
128 match self.config.binary_search_by(|it| it.0.cmp(&path)) { 121 .any(|entry| entry.contains_dir(&path))
129 Ok(it) => it, 122 {
130 Err(it) => it.saturating_sub(1), 123 self.watch(path);
131 }; 124 return None;
132 let include = self.config.get(config_idx).and_then(|it| {
133 let rel_path = path.strip_prefix(&it.0)?;
134 Some((rel_path, &it.1))
135 });
136
137 if let Some((rel_path, include)) = include {
138 if is_dir && include.exclude_dir(&rel_path)
139 || is_file && !include.include_file(&rel_path)
140 {
141 return None;
142 }
143 } 125 }
144 126
145 if is_dir { 127 if !path.is_file() {
146 self.watch(path);
147 return None; 128 return None;
148 } 129 }
149 if !is_file { 130 if !self
131 .watched_entries
132 .iter()
133 .any(|entry| entry.contains_file(&path))
134 {
150 return None; 135 return None;
151 } 136 }
137
152 let contents = read(&path); 138 let contents = read(&path);
153 Some((path, contents)) 139 Some((path, contents))
154 }) 140 })
@@ -175,58 +161,49 @@ impl NotifyActor {
175 (file, contents) 161 (file, contents)
176 }) 162 })
177 .collect::<Vec<_>>(), 163 .collect::<Vec<_>>(),
178 loader::Entry::Directory { path, include } => { 164 loader::Entry::Directories(dirs) => {
179 let include = Include::new(include); 165 let mut res = Vec::new();
180 self.config.push((path.clone(), include.clone(), watch)); 166
181 167 for root in dirs.include.iter() {
182 let files = WalkDir::new(&path) 168 let walkdir = WalkDir::new(root).into_iter().filter_entry(|entry| {
183 .into_iter() 169 if !entry.file_type().is_dir() {
184 .filter_entry(|entry| { 170 return true;
185 let abs_path: &AbsPath = entry.path().try_into().unwrap();
186 match abs_path.strip_prefix(&path) {
187 Some(rel_path) => {
188 !(entry.file_type().is_dir() && include.exclude_dir(rel_path))
189 }
190 None => false,
191 } 171 }
192 }) 172 let path = AbsPath::assert(entry.path());
193 .filter_map(|entry| entry.ok()) 173 root == path
194 .filter_map(|entry| { 174 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
175 });
176
177 let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
195 let is_dir = entry.file_type().is_dir(); 178 let is_dir = entry.file_type().is_dir();
196 let is_file = entry.file_type().is_file(); 179 let is_file = entry.file_type().is_file();
197 let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); 180 let abs_path = AbsPathBuf::assert(entry.into_path());
198 if is_dir && watch { 181 if is_dir && watch {
199 self.watch(abs_path.clone()); 182 self.watch(abs_path.clone());
200 } 183 }
201 let rel_path = abs_path.strip_prefix(&path)?; 184 if !is_file {
202 if is_file && include.include_file(&rel_path) { 185 return None;
203 Some(abs_path) 186 }
204 } else { 187 let ext = abs_path.extension().unwrap_or_default();
205 None 188 if dirs.extensions.iter().all(|it| it.as_str() != ext) {
189 return None;
206 } 190 }
191 Some(abs_path)
207 }); 192 });
208 193
209 files 194 res.extend(files.map(|file| {
210 .map(|file| {
211 let contents = read(file.as_path()); 195 let contents = read(file.as_path());
212 (file, contents) 196 (file, contents)
213 }) 197 }));
214 .collect() 198 }
199 res
215 } 200 }
216 } 201 }
217 } 202 }
218 203
219 fn watch(&mut self, path: AbsPathBuf) { 204 fn watch(&mut self, path: AbsPathBuf) {
220 if let Some(watcher) = &mut self.watcher { 205 if let Some((watcher, _)) = &mut self.watcher {
221 log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive)); 206 log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive));
222 self.watched_paths.insert(path);
223 }
224 }
225 fn unwatch_all(&mut self) {
226 if let Some(watcher) = &mut self.watcher {
227 for path in self.watched_paths.drain() {
228 log_notify_error(watcher.unwatch(path));
229 }
230 } 207 }
231 } 208 }
232 fn send(&mut self, msg: loader::Message) { 209 fn send(&mut self, msg: loader::Message) {