aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs-notify/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs-notify/src/lib.rs')
-rw-r--r--crates/vfs-notify/src/lib.rs230
1 files changed, 230 insertions, 0 deletions
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
new file mode 100644
index 000000000..6aaa53f63
--- /dev/null
+++ b/crates/vfs-notify/src/lib.rs
@@ -0,0 +1,230 @@
1//! An implementation of `loader::Handle`, based on `walkdir` and `notify`.
2//!
3//! The file watching bits here are untested and quite probably buggy. For this
4//! reason, by default we don't watch files and rely on editor's file watching
5//! capabilities.
6//!
7//! Hopefully, one day a reliable file watching/walking crate appears on
8//! crates.io, and we can reduce this to trivial glue code.
9mod include;
10
11use std::convert::{TryFrom, TryInto};
12
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
14use notify::{RecommendedWatcher, RecursiveMode, Watcher};
15use paths::{AbsPath, AbsPathBuf};
16use vfs::loader;
17use walkdir::WalkDir;
18
19use crate::include::Include;
20
21#[derive(Debug)]
22pub struct NotifyHandle {
23 // Relative order of fields below is significant.
24 sender: Sender<Message>,
25 thread: jod_thread::JoinHandle,
26}
27
28#[derive(Debug)]
29enum Message {
30 Config(loader::Config),
31 Invalidate(AbsPathBuf),
32}
33
34impl loader::Handle for NotifyHandle {
35 fn spawn(sender: loader::Sender) -> NotifyHandle {
36 let actor = NotifyActor::new(sender);
37 let (sender, receiver) = unbounded::<Message>();
38 let thread = jod_thread::spawn(move || actor.run(receiver));
39 NotifyHandle { sender, thread }
40 }
41 fn set_config(&mut self, config: loader::Config) {
42 self.sender.send(Message::Config(config)).unwrap()
43 }
44 fn invalidate(&mut self, path: AbsPathBuf) {
45 self.sender.send(Message::Invalidate(path)).unwrap();
46 }
47 fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>> {
48 read(path)
49 }
50}
51
52type NotifyEvent = notify::Result<notify::Event>;
53
54struct NotifyActor {
55 sender: loader::Sender,
56 config: Vec<(AbsPathBuf, Include, bool)>,
57 // Drop order is significant.
58 watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
59}
60
61#[derive(Debug)]
62enum Event {
63 Message(Message),
64 NotifyEvent(NotifyEvent),
65}
66
67impl NotifyActor {
68 fn new(sender: loader::Sender) -> NotifyActor {
69 NotifyActor { sender, config: Vec::new(), watcher: None }
70 }
71 fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
72 let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
73 select! {
74 recv(receiver) -> it => it.ok().map(Event::Message),
75 recv(watcher_receiver.unwrap_or(&never())) -> it => Some(Event::NotifyEvent(it.unwrap())),
76 }
77 }
78 fn run(mut self, inbox: Receiver<Message>) {
79 while let Some(event) = self.next_event(&inbox) {
80 log::debug!("vfs-notify event: {:?}", event);
81 match event {
82 Event::Message(msg) => match msg {
83 Message::Config(config) => {
84 self.watcher = None;
85 if !config.watch.is_empty() {
86 let (watcher_sender, watcher_receiver) = unbounded();
87 let watcher = log_notify_error(Watcher::new_immediate(move |event| {
88 watcher_sender.send(event).unwrap()
89 }));
90 self.watcher = watcher.map(|it| (it, watcher_receiver));
91 }
92
93 let n_total = config.load.len();
94 self.send(loader::Message::Progress { n_total, n_done: 0 });
95
96 self.config.clear();
97
98 for (i, entry) in config.load.into_iter().enumerate() {
99 let watch = config.watch.contains(&i);
100 let files = self.load_entry(entry, watch);
101 self.send(loader::Message::Loaded { files });
102 self.send(loader::Message::Progress { n_total, n_done: i + 1 });
103 }
104 self.config.sort_by(|x, y| x.0.cmp(&y.0));
105 }
106 Message::Invalidate(path) => {
107 let contents = read(path.as_path());
108 let files = vec![(path, contents)];
109 self.send(loader::Message::Loaded { files });
110 }
111 },
112 Event::NotifyEvent(event) => {
113 if let Some(event) = log_notify_error(event) {
114 let files = event
115 .paths
116 .into_iter()
117 .map(|path| AbsPathBuf::try_from(path).unwrap())
118 .filter_map(|path| {
119 let is_dir = path.is_dir();
120 let is_file = path.is_file();
121
122 let config_idx =
123 match self.config.binary_search_by(|it| it.0.cmp(&path)) {
124 Ok(it) => it,
125 Err(it) => it.saturating_sub(1),
126 };
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 }
139
140 if is_dir {
141 self.watch(path);
142 return None;
143 }
144 if !is_file {
145 return None;
146 }
147 let contents = read(&path);
148 Some((path, contents))
149 })
150 .collect();
151 self.send(loader::Message::Loaded { files })
152 }
153 }
154 }
155 }
156 }
157 fn load_entry(
158 &mut self,
159 entry: loader::Entry,
160 watch: bool,
161 ) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
162 match entry {
163 loader::Entry::Files(files) => files
164 .into_iter()
165 .map(|file| {
166 if watch {
167 self.watch(file.clone())
168 }
169 let contents = read(file.as_path());
170 (file, contents)
171 })
172 .collect::<Vec<_>>(),
173 loader::Entry::Directory { path, include } => {
174 let include = Include::new(include);
175 self.config.push((path.clone(), include.clone(), watch));
176
177 let files = WalkDir::new(&path)
178 .into_iter()
179 .filter_entry(|entry| {
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 }
187 })
188 .filter_map(|entry| entry.ok())
189 .filter_map(|entry| {
190 let is_dir = entry.file_type().is_dir();
191 let is_file = entry.file_type().is_file();
192 let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap();
193 if is_dir && watch {
194 self.watch(abs_path.clone());
195 }
196 let rel_path = abs_path.strip_prefix(&path)?;
197 if is_file && include.include_file(&rel_path) {
198 Some(abs_path)
199 } else {
200 None
201 }
202 });
203
204 files
205 .map(|file| {
206 let contents = read(file.as_path());
207 (file, contents)
208 })
209 .collect()
210 }
211 }
212 }
213
214 fn watch(&mut self, path: AbsPathBuf) {
215 if let Some((watcher, _)) = &mut self.watcher {
216 log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive));
217 }
218 }
219 fn send(&mut self, msg: loader::Message) {
220 (self.sender)(msg)
221 }
222}
223
224fn read(path: &AbsPath) -> Option<Vec<u8>> {
225 std::fs::read(path).ok()
226}
227
228fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
229 res.map_err(|err| log::warn!("notify error: {}", err)).ok()
230}