diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
commit | f05d7b41a719d848844b054a16477b29d0f063c6 (patch) | |
tree | 0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /crates/vfs-notify | |
parent | 73ff610e41959e3e7c78a2b4b25b086883132956 (diff) | |
parent | 6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (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.toml | 5 | ||||
-rw-r--r-- | crates/vfs-notify/src/include.rs | 43 | ||||
-rw-r--r-- | crates/vfs-notify/src/lib.rs | 147 |
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" | |||
3 | version = "0.1.0" | 3 | version = "0.1.0" |
4 | authors = ["rust-analyzer developers"] | 4 | authors = ["rust-analyzer developers"] |
5 | edition = "2018" | 5 | edition = "2018" |
6 | license = "MIT OR Apache-2.0" | ||
7 | |||
8 | [lib] | ||
9 | doctest = false | ||
6 | 10 | ||
7 | [dependencies] | 11 | [dependencies] |
8 | log = "0.4.8" | 12 | log = "0.4.8" |
9 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
10 | jod-thread = "0.1.0" | 14 | jod-thread = "0.1.0" |
11 | walkdir = "2.3.1" | 15 | walkdir = "2.3.1" |
12 | globset = "0.4.5" | ||
13 | crossbeam-channel = "0.4.0" | 16 | crossbeam-channel = "0.4.0" |
14 | notify = "5.0.0-pre.3" | 17 | notify = "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 | |||
3 | use std::convert::TryFrom; | ||
4 | |||
5 | use globset::{Glob, GlobSet, GlobSetBuilder}; | ||
6 | use 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)] | ||
15 | pub(crate) struct Include { | ||
16 | include_files: GlobSet, | ||
17 | exclude_dirs: Vec<RelPathBuf>, | ||
18 | } | ||
19 | |||
20 | impl 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. |
9 | mod include; | 9 | use std::convert::TryFrom; |
10 | 10 | ||
11 | use std::convert::{TryFrom, TryInto}; | 11 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; |
12 | |||
13 | use crossbeam_channel::{select, unbounded, Receiver, Sender}; | ||
14 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; | 12 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; |
15 | use paths::{AbsPath, AbsPathBuf}; | 13 | use paths::{AbsPath, AbsPathBuf}; |
16 | use rustc_hash::FxHashSet; | ||
17 | use vfs::loader; | 14 | use vfs::loader; |
18 | use walkdir::WalkDir; | 15 | use walkdir::WalkDir; |
19 | 16 | ||
20 | use crate::include::Include; | ||
21 | |||
22 | #[derive(Debug)] | 17 | #[derive(Debug)] |
23 | pub struct NotifyHandle { | 18 | pub 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 | ||
55 | struct NotifyActor { | 50 | struct 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 | ||
70 | impl NotifyActor { | 63 | impl 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) { |