]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs
:arrow_up: rust-analyzer
[rust.git] / src / tools / rust-analyzer / crates / vfs-notify / src / lib.rs
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.
9
10 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
11
12 use std::fs;
13
14 use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
15 use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
16 use paths::{AbsPath, AbsPathBuf};
17 use vfs::loader;
18 use walkdir::WalkDir;
19
20 #[derive(Debug)]
21 pub struct NotifyHandle {
22     // Relative order of fields below is significant.
23     sender: Sender<Message>,
24     _thread: jod_thread::JoinHandle,
25 }
26
27 #[derive(Debug)]
28 enum Message {
29     Config(loader::Config),
30     Invalidate(AbsPathBuf),
31 }
32
33 impl loader::Handle for NotifyHandle {
34     fn spawn(sender: loader::Sender) -> NotifyHandle {
35         let actor = NotifyActor::new(sender);
36         let (sender, receiver) = unbounded::<Message>();
37         let thread = jod_thread::Builder::new()
38             .name("VfsLoader".to_owned())
39             .spawn(move || actor.run(receiver))
40             .expect("failed to spawn thread");
41         NotifyHandle { sender, _thread: thread }
42     }
43
44     fn set_config(&mut self, config: loader::Config) {
45         self.sender.send(Message::Config(config)).unwrap();
46     }
47
48     fn invalidate(&mut self, path: AbsPathBuf) {
49         self.sender.send(Message::Invalidate(path)).unwrap();
50     }
51
52     fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>> {
53         read(path)
54     }
55 }
56
57 type NotifyEvent = notify::Result<notify::Event>;
58
59 struct NotifyActor {
60     sender: loader::Sender,
61     watched_entries: Vec<loader::Entry>,
62     // Drop order is significant.
63     watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
64 }
65
66 #[derive(Debug)]
67 enum Event {
68     Message(Message),
69     NotifyEvent(NotifyEvent),
70 }
71
72 impl NotifyActor {
73     fn new(sender: loader::Sender) -> NotifyActor {
74         NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
75     }
76
77     fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
78         let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
79         select! {
80             recv(receiver) -> it => it.ok().map(Event::Message),
81             recv(watcher_receiver.unwrap_or(&never())) -> it => Some(Event::NotifyEvent(it.unwrap())),
82         }
83     }
84
85     fn run(mut self, inbox: Receiver<Message>) {
86         while let Some(event) = self.next_event(&inbox) {
87             tracing::debug!(?event, "vfs-notify event");
88             match event {
89                 Event::Message(msg) => match msg {
90                     Message::Config(config) => {
91                         self.watcher = None;
92                         if !config.watch.is_empty() {
93                             let (watcher_sender, watcher_receiver) = unbounded();
94                             let watcher = log_notify_error(RecommendedWatcher::new(
95                                 move |event| {
96                                     watcher_sender.send(event).unwrap();
97                                 },
98                                 Config::default(),
99                             ));
100                             self.watcher = watcher.map(|it| (it, watcher_receiver));
101                         }
102
103                         let config_version = config.version;
104
105                         let n_total = config.load.len();
106                         self.send(loader::Message::Progress { n_total, n_done: 0, config_version });
107
108                         self.watched_entries.clear();
109
110                         for (i, entry) in config.load.into_iter().enumerate() {
111                             let watch = config.watch.contains(&i);
112                             if watch {
113                                 self.watched_entries.push(entry.clone());
114                             }
115                             let files = self.load_entry(entry, watch);
116                             self.send(loader::Message::Loaded { files });
117                             self.send(loader::Message::Progress {
118                                 n_total,
119                                 n_done: i + 1,
120                                 config_version,
121                             });
122                         }
123                     }
124                     Message::Invalidate(path) => {
125                         let contents = read(path.as_path());
126                         let files = vec![(path, contents)];
127                         self.send(loader::Message::Loaded { files });
128                     }
129                 },
130                 Event::NotifyEvent(event) => {
131                     if let Some(event) = log_notify_error(event) {
132                         let files = event
133                             .paths
134                             .into_iter()
135                             .map(|path| AbsPathBuf::try_from(path).unwrap())
136                             .filter_map(|path| {
137                                 let meta = fs::metadata(&path).ok()?;
138                                 if meta.file_type().is_dir()
139                                     && self
140                                         .watched_entries
141                                         .iter()
142                                         .any(|entry| entry.contains_dir(&path))
143                                 {
144                                     self.watch(path);
145                                     return None;
146                                 }
147
148                                 if !meta.file_type().is_file() {
149                                     return None;
150                                 }
151                                 if !self
152                                     .watched_entries
153                                     .iter()
154                                     .any(|entry| entry.contains_file(&path))
155                                 {
156                                     return None;
157                                 }
158
159                                 let contents = read(&path);
160                                 Some((path, contents))
161                             })
162                             .collect();
163                         self.send(loader::Message::Loaded { files });
164                     }
165                 }
166             }
167         }
168     }
169     fn load_entry(
170         &mut self,
171         entry: loader::Entry,
172         watch: bool,
173     ) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
174         match entry {
175             loader::Entry::Files(files) => files
176                 .into_iter()
177                 .map(|file| {
178                     if watch {
179                         self.watch(file.clone());
180                     }
181                     let contents = read(file.as_path());
182                     (file, contents)
183                 })
184                 .collect::<Vec<_>>(),
185             loader::Entry::Directories(dirs) => {
186                 let mut res = Vec::new();
187
188                 for root in &dirs.include {
189                     let walkdir =
190                         WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
191                             if !entry.file_type().is_dir() {
192                                 return true;
193                             }
194                             let path = AbsPath::assert(entry.path());
195                             root == path
196                                 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
197                         });
198
199                     let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
200                         let is_dir = entry.file_type().is_dir();
201                         let is_file = entry.file_type().is_file();
202                         let abs_path = AbsPathBuf::assert(entry.into_path());
203                         if is_dir && watch {
204                             self.watch(abs_path.clone());
205                         }
206                         if !is_file {
207                             return None;
208                         }
209                         let ext = abs_path.extension().unwrap_or_default();
210                         if dirs.extensions.iter().all(|it| it.as_str() != ext) {
211                             return None;
212                         }
213                         Some(abs_path)
214                     });
215
216                     res.extend(files.map(|file| {
217                         let contents = read(file.as_path());
218                         (file, contents)
219                     }));
220                 }
221                 res
222             }
223         }
224     }
225
226     fn watch(&mut self, path: AbsPathBuf) {
227         if let Some((watcher, _)) = &mut self.watcher {
228             log_notify_error(watcher.watch(path.as_ref(), RecursiveMode::NonRecursive));
229         }
230     }
231     fn send(&mut self, msg: loader::Message) {
232         (self.sender)(msg);
233     }
234 }
235
236 fn read(path: &AbsPath) -> Option<Vec<u8>> {
237     std::fs::read(path).ok()
238 }
239
240 fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
241     res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
242 }