aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs-notify/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs-notify/src')
-rw-r--r--crates/vfs-notify/src/include.rs42
-rw-r--r--crates/vfs-notify/src/lib.rs104
2 files changed, 47 insertions, 99 deletions
diff --git a/crates/vfs-notify/src/include.rs b/crates/vfs-notify/src/include.rs
deleted file mode 100644
index 59da3d77a..000000000
--- a/crates/vfs-notify/src/include.rs
+++ /dev/null
@@ -1,42 +0,0 @@
1//! See `Include`.
2use std::convert::TryFrom;
3
4use globset::{Glob, GlobSet, GlobSetBuilder};
5use paths::{RelPath, RelPathBuf};
6
7/// `Include` is the opposite of .gitignore.
8///
9/// It describes the set of files inside some directory.
10///
11/// The current implementation is very limited, it allows including file globs
12/// and recursively excluding directories.
13#[derive(Debug, Clone)]
14pub(crate) struct Include {
15 include_files: GlobSet,
16 exclude_dirs: Vec<RelPathBuf>,
17}
18
19impl Include {
20 pub(crate) fn new(include: Vec<String>) -> Include {
21 let mut include_files = GlobSetBuilder::new();
22 let mut exclude_dirs = Vec::new();
23
24 for glob in include {
25 if glob.starts_with("!/") {
26 if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) {
27 exclude_dirs.push(path)
28 }
29 } else {
30 include_files.add(Glob::new(&glob).unwrap());
31 }
32 }
33 let include_files = include_files.build().unwrap();
34 Include { include_files, exclude_dirs }
35 }
36 pub(crate) fn include_file(&self, path: &RelPath) -> bool {
37 self.include_files.is_match(path)
38 }
39 pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool {
40 self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded))
41 }
42}
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index 6aaa53f63..e1e36612a 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -6,9 +6,7 @@
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
11use std::convert::{TryFrom, TryInto};
12 10
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 11use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
14use notify::{RecommendedWatcher, RecursiveMode, Watcher}; 12use notify::{RecommendedWatcher, RecursiveMode, Watcher};
@@ -16,8 +14,6 @@ use paths::{AbsPath, AbsPathBuf};
16use vfs::loader; 14use vfs::loader;
17use walkdir::WalkDir; 15use walkdir::WalkDir;
18 16
19use crate::include::Include;
20
21#[derive(Debug)] 17#[derive(Debug)]
22pub struct NotifyHandle { 18pub struct NotifyHandle {
23 // Relative order of fields below is significant. 19 // Relative order of fields below is significant.
@@ -53,7 +49,7 @@ type NotifyEvent = notify::Result<notify::Event>;
53 49
54struct NotifyActor { 50struct NotifyActor {
55 sender: loader::Sender, 51 sender: loader::Sender,
56 config: Vec<(AbsPathBuf, Include, bool)>, 52 watched_entries: Vec<loader::Entry>,
57 // Drop order is significant. 53 // Drop order is significant.
58 watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>, 54 watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
59} 55}
@@ -66,7 +62,7 @@ enum Event {
66 62
67impl NotifyActor { 63impl NotifyActor {
68 fn new(sender: loader::Sender) -> NotifyActor { 64 fn new(sender: loader::Sender) -> NotifyActor {
69 NotifyActor { sender, config: Vec::new(), watcher: None } 65 NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
70 } 66 }
71 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> { 67 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
72 let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver); 68 let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
@@ -93,15 +89,17 @@ impl NotifyActor {
93 let n_total = config.load.len(); 89 let n_total = config.load.len();
94 self.send(loader::Message::Progress { n_total, n_done: 0 }); 90 self.send(loader::Message::Progress { n_total, n_done: 0 });
95 91
96 self.config.clear(); 92 self.watched_entries.clear();
97 93
98 for (i, entry) in config.load.into_iter().enumerate() { 94 for (i, entry) in config.load.into_iter().enumerate() {
99 let watch = config.watch.contains(&i); 95 let watch = config.watch.contains(&i);
96 if watch {
97 self.watched_entries.push(entry.clone())
98 }
100 let files = self.load_entry(entry, watch); 99 let files = self.load_entry(entry, watch);
101 self.send(loader::Message::Loaded { files }); 100 self.send(loader::Message::Loaded { files });
102 self.send(loader::Message::Progress { n_total, n_done: i + 1 }); 101 self.send(loader::Message::Progress { n_total, n_done: i + 1 });
103 } 102 }
104 self.config.sort_by(|x, y| x.0.cmp(&y.0));
105 } 103 }
106 Message::Invalidate(path) => { 104 Message::Invalidate(path) => {
107 let contents = read(path.as_path()); 105 let contents = read(path.as_path());
@@ -116,34 +114,27 @@ impl NotifyActor {
116 .into_iter() 114 .into_iter()
117 .map(|path| AbsPathBuf::try_from(path).unwrap()) 115 .map(|path| AbsPathBuf::try_from(path).unwrap())
118 .filter_map(|path| { 116 .filter_map(|path| {
119 let is_dir = path.is_dir(); 117 if path.is_dir()
120 let is_file = path.is_file(); 118 && self
121 119 .watched_entries
122 let config_idx = 120 .iter()
123 match self.config.binary_search_by(|it| it.0.cmp(&path)) { 121 .any(|entry| entry.contains_dir(&path))
124 Ok(it) => it, 122 {
125 Err(it) => it.saturating_sub(1), 123 self.watch(path);
126 }; 124 return None;
127 let include = self.config.get(config_idx).and_then(|it| {
128 let rel_path = path.strip_prefix(&it.0)?;
129 Some((rel_path, &it.1))
130 });
131
132 if let Some((rel_path, include)) = include {
133 if is_dir && include.exclude_dir(&rel_path)
134 || is_file && !include.include_file(&rel_path)
135 {
136 return None;
137 }
138 } 125 }
139 126
140 if is_dir { 127 if !path.is_file() {
141 self.watch(path);
142 return None; 128 return None;
143 } 129 }
144 if !is_file { 130 if !self
131 .watched_entries
132 .iter()
133 .any(|entry| entry.contains_file(&path))
134 {
145 return None; 135 return None;
146 } 136 }
137
147 let contents = read(&path); 138 let contents = read(&path);
148 Some((path, contents)) 139 Some((path, contents))
149 }) 140 })
@@ -170,43 +161,42 @@ impl NotifyActor {
170 (file, contents) 161 (file, contents)
171 }) 162 })
172 .collect::<Vec<_>>(), 163 .collect::<Vec<_>>(),
173 loader::Entry::Directory { path, include } => { 164 loader::Entry::Directories(dirs) => {
174 let include = Include::new(include); 165 let mut res = Vec::new();
175 self.config.push((path.clone(), include.clone(), watch)); 166
176 167 for root in dirs.include.iter() {
177 let files = WalkDir::new(&path) 168 let walkdir = WalkDir::new(root).into_iter().filter_entry(|entry| {
178 .into_iter() 169 if !entry.file_type().is_dir() {
179 .filter_entry(|entry| { 170 return true;
180 let abs_path: &AbsPath = entry.path().try_into().unwrap();
181 match abs_path.strip_prefix(&path) {
182 Some(rel_path) => {
183 !(entry.file_type().is_dir() && include.exclude_dir(rel_path))
184 }
185 None => false,
186 } 171 }
187 }) 172 let path = AbsPath::assert(entry.path());
188 .filter_map(|entry| entry.ok()) 173 root == path
189 .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| {
190 let is_dir = entry.file_type().is_dir(); 178 let is_dir = entry.file_type().is_dir();
191 let is_file = entry.file_type().is_file(); 179 let is_file = entry.file_type().is_file();
192 let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); 180 let abs_path = AbsPathBuf::assert(entry.into_path());
193 if is_dir && watch { 181 if is_dir && watch {
194 self.watch(abs_path.clone()); 182 self.watch(abs_path.clone());
195 } 183 }
196 let rel_path = abs_path.strip_prefix(&path)?; 184 if !is_file {
197 if is_file && include.include_file(&rel_path) { 185 return None;
198 Some(abs_path) 186 }
199 } else { 187 let ext = abs_path.extension().unwrap_or_default();
200 None 188 if dirs.extensions.iter().all(|it| it.as_str() != ext) {
189 return None;
201 } 190 }
191 Some(abs_path)
202 }); 192 });
203 193
204 files 194 res.extend(files.map(|file| {
205 .map(|file| {
206 let contents = read(file.as_path()); 195 let contents = read(file.as_path());
207 (file, contents) 196 (file, contents)
208 }) 197 }));
209 .collect() 198 }
199 res
210 } 200 }
211 } 201 }
212 } 202 }