]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
handle recursive watching ourselves
[rust.git] / crates / ra_vfs / src / lib.rs
1 //! VFS stands for Virtual File System.
2 //!
3 //! When doing analysis, we don't want to do any IO, we want to keep all source
4 //! code in memory. However, the actual source code is stored on disk, so you
5 //! need to get it into the memory in the first place somehow. VFS is the
6 //! component which does this.
7 //!
8 //! It is also responsible for watching the disk for changes, and for merging
9 //! editor state (modified, unsaved files) with disk state.
10 //! TODO: Some LSP clients support watching the disk, so this crate should
11 //! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
12 //!
13 //! VFS is based on a concept of roots: a set of directories on the file system
14 //! which are watched for changes. Typically, there will be a root for each
15 //! Cargo package.
16 mod io;
17 mod watcher;
18
19 use std::{
20     cmp::Reverse,
21     ffi::OsStr,
22     fmt, fs, mem,
23     path::{Path, PathBuf},
24     sync::Arc,
25     thread,
26 };
27
28 use crossbeam_channel::Receiver;
29 use ra_arena::{impl_arena_id, Arena, RawId};
30 use relative_path::RelativePathBuf;
31 use rustc_hash::{FxHashMap, FxHashSet};
32 use thread_worker::WorkerHandle;
33 use walkdir::DirEntry;
34
35 pub use crate::io::TaskResult as VfsTask;
36 pub use crate::watcher::{Watcher, WatcherChange};
37
38 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
39 /// several filters match a file (nested dirs), the most nested one wins.
40 struct RootFilter {
41     root: PathBuf,
42     file_filter: fn(&Path) -> bool,
43 }
44
45 impl RootFilter {
46     fn new(root: PathBuf) -> RootFilter {
47         RootFilter {
48             root,
49             file_filter: has_rs_extension,
50         }
51     }
52     /// Check if this root can contain `path`. NB: even if this returns
53     /// true, the `path` might actually be conained in some nested root.
54     fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55         if !(self.file_filter)(path) {
56             return None;
57         }
58         let path = path.strip_prefix(&self.root).ok()?;
59         RelativePathBuf::from_path(path).ok()
60     }
61 }
62
63 pub(crate) fn has_rs_extension(p: &Path) -> bool {
64     p.extension() == Some(OsStr::new("rs"))
65 }
66
67 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68 pub struct VfsRoot(pub RawId);
69 impl_arena_id!(VfsRoot);
70
71 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72 pub struct VfsFile(pub RawId);
73 impl_arena_id!(VfsFile);
74
75 struct VfsFileData {
76     root: VfsRoot,
77     path: RelativePathBuf,
78     is_overlayed: bool,
79     text: Arc<String>,
80 }
81
82 pub struct Vfs {
83     roots: Arena<VfsRoot, RootFilter>,
84     files: Arena<VfsFile, VfsFileData>,
85     root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
86     pending_changes: Vec<VfsChange>,
87     worker: io::Worker,
88     worker_handle: WorkerHandle,
89     watcher: Option<Watcher>,
90 }
91
92 impl fmt::Debug for Vfs {
93     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94         f.write_str("Vfs { ... }")
95     }
96 }
97
98 impl Vfs {
99     pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
100         let (worker, worker_handle) = io::start();
101
102         let watcher = match Watcher::start(worker.inp.clone()) {
103             Ok(watcher) => Some(watcher),
104             Err(e) => {
105                 log::error!("could not start watcher: {}", e);
106                 None
107             }
108         };
109
110         let mut res = Vfs {
111             roots: Arena::default(),
112             files: Arena::default(),
113             root2files: FxHashMap::default(),
114             worker,
115             worker_handle,
116             watcher,
117             pending_changes: Vec::new(),
118         };
119
120         // A hack to make nesting work.
121         roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
122         for (i, path) in roots.iter().enumerate() {
123             let root = res.roots.alloc(RootFilter::new(path.clone()));
124             res.root2files.insert(root, Default::default());
125             let nested = roots[..i]
126                 .iter()
127                 .filter(|it| it.starts_with(path))
128                 .map(|it| it.clone())
129                 .collect::<Vec<_>>();
130             let filter = move |entry: &DirEntry| {
131                 if entry.file_type().is_file() {
132                     has_rs_extension(entry.path())
133                 } else {
134                     nested.iter().all(|it| it != entry.path())
135                 }
136             };
137             let task = io::Task::AddRoot {
138                 root,
139                 path: path.clone(),
140                 filter: Box::new(filter),
141             };
142             res.worker.inp.send(task).unwrap();
143             if let Some(ref mut watcher) = res.watcher {
144                 watcher.watch(path);
145             }
146         }
147         let roots = res.roots.iter().map(|(id, _)| id).collect();
148         (res, roots)
149     }
150
151     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
152         self.roots[root].root.clone()
153     }
154
155     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
156         if let Some((_root, _path, Some(file))) = self.find_root(path) {
157             return Some(file);
158         }
159         None
160     }
161
162     pub fn file2path(&self, file: VfsFile) -> PathBuf {
163         let rel_path = &self.files[file].path;
164         let root_path = &self.roots[self.files[file].root].root;
165         rel_path.to_path(root_path)
166     }
167
168     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
169         if let Some((_root, _path, Some(file))) = self.find_root(path) {
170             return Some(file);
171         }
172         None
173     }
174
175     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
176         if let Some((root, rel_path, file)) = self.find_root(path) {
177             return if let Some(file) = file {
178                 Some(file)
179             } else {
180                 let text = fs::read_to_string(path).unwrap_or_default();
181                 let text = Arc::new(text);
182                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
183                 let change = VfsChange::AddFile {
184                     file,
185                     text,
186                     root,
187                     path: rel_path,
188                 };
189                 self.pending_changes.push(change);
190                 Some(file)
191             };
192         }
193         None
194     }
195
196     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
197         &self.worker.out
198     }
199
200     pub fn handle_task(&mut self, task: io::TaskResult) {
201         match task {
202             io::TaskResult::AddRoot(task) => {
203                 let mut files = Vec::new();
204                 // While we were scanning the root in the backgound, a file might have
205                 // been open in the editor, so we need to account for that.
206                 let exising = self.root2files[&task.root]
207                     .iter()
208                     .map(|&file| (self.files[file].path.clone(), file))
209                     .collect::<FxHashMap<_, _>>();
210                 for (path, text) in task.files {
211                     if let Some(&file) = exising.get(&path) {
212                         let text = Arc::clone(&self.files[file].text);
213                         files.push((file, path, text));
214                         continue;
215                     }
216                     let text = Arc::new(text);
217                     let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
218                     files.push((file, path, text));
219                 }
220
221                 let change = VfsChange::AddRoot {
222                     root: task.root,
223                     files,
224                 };
225                 self.pending_changes.push(change);
226             }
227             io::TaskResult::HandleChange(change) => match &change {
228                 watcher::WatcherChange::Create(path)
229                 | watcher::WatcherChange::Remove(path)
230                 | watcher::WatcherChange::Write(path) => {
231                     if self.should_handle_change(&path) {
232                         self.worker.inp.send(io::Task::LoadChange(change)).unwrap()
233                     }
234                 }
235                 watcher::WatcherChange::Rescan => {
236                     // TODO we should reload all files
237                 }
238             },
239             io::TaskResult::LoadChange(None) => {}
240             io::TaskResult::LoadChange(Some(change)) => match change {
241                 io::WatcherChangeData::Create { path, text }
242                 | io::WatcherChangeData::Write { path, text } => {
243                     if let Some((root, path, file)) = self.find_root(&path) {
244                         if let Some(file) = file {
245                             self.do_change_file(file, text, false);
246                         } else {
247                             self.do_add_file(root, path, text, false);
248                         }
249                     }
250                 }
251                 io::WatcherChangeData::Remove { path } => {
252                     if let Some((root, path, file)) = self.find_root(&path) {
253                         if let Some(file) = file {
254                             self.do_remove_file(root, path, file, false);
255                         }
256                     }
257                 }
258             },
259         }
260     }
261
262     fn should_handle_change(&self, path: &Path) -> bool {
263         if let Some((_root, _rel_path, file)) = self.find_root(&path) {
264             if let Some(file) = file {
265                 if self.files[file].is_overlayed {
266                     // file is overlayed
267                     log::debug!("skipping overlayed \"{}\"", path.display());
268                     return false;
269                 }
270             }
271             true
272         } else {
273             // file doesn't belong to any root
274             false
275         }
276     }
277
278     fn do_add_file(
279         &mut self,
280         root: VfsRoot,
281         path: RelativePathBuf,
282         text: String,
283         is_overlay: bool,
284     ) -> Option<VfsFile> {
285         let text = Arc::new(text);
286         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
287         self.pending_changes.push(VfsChange::AddFile {
288             file,
289             root,
290             path,
291             text,
292         });
293         Some(file)
294     }
295
296     fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
297         if !is_overlay && self.files[file].is_overlayed {
298             return;
299         }
300         let text = Arc::new(text);
301         self.change_file(file, text.clone(), is_overlay);
302         self.pending_changes
303             .push(VfsChange::ChangeFile { file, text });
304     }
305
306     fn do_remove_file(
307         &mut self,
308         root: VfsRoot,
309         path: RelativePathBuf,
310         file: VfsFile,
311         is_overlay: bool,
312     ) {
313         if !is_overlay && self.files[file].is_overlayed {
314             return;
315         }
316         self.remove_file(file);
317         self.pending_changes
318             .push(VfsChange::RemoveFile { root, path, file });
319     }
320
321     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
322         if let Some((root, rel_path, file)) = self.find_root(path) {
323             if let Some(file) = file {
324                 self.do_change_file(file, text, true);
325                 Some(file)
326             } else {
327                 self.do_add_file(root, rel_path, text, true)
328             }
329         } else {
330             None
331         }
332     }
333
334     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
335         if let Some((_root, _path, file)) = self.find_root(path) {
336             let file = file.expect("can't change a file which wasn't added");
337             self.do_change_file(file, new_text, true);
338         }
339     }
340
341     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
342         if let Some((root, path, file)) = self.find_root(path) {
343             let file = file.expect("can't remove a file which wasn't added");
344             let full_path = path.to_path(&self.roots[root].root);
345             if let Ok(text) = fs::read_to_string(&full_path) {
346                 self.do_change_file(file, text, true);
347             } else {
348                 self.do_remove_file(root, path, file, true);
349             }
350             Some(file)
351         } else {
352             None
353         }
354     }
355
356     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
357         mem::replace(&mut self.pending_changes, Vec::new())
358     }
359
360     /// Sutdown the VFS and terminate the background watching thread.
361     pub fn shutdown(self) -> thread::Result<()> {
362         if let Some(watcher) = self.watcher {
363             let _ = watcher.shutdown();
364         }
365         let _ = self.worker.shutdown();
366         self.worker_handle.shutdown()
367     }
368
369     fn add_file(
370         &mut self,
371         root: VfsRoot,
372         path: RelativePathBuf,
373         text: Arc<String>,
374         is_overlayed: bool,
375     ) -> VfsFile {
376         let data = VfsFileData {
377             root,
378             path,
379             text,
380             is_overlayed,
381         };
382         let file = self.files.alloc(data);
383         self.root2files.get_mut(&root).unwrap().insert(file);
384         file
385     }
386
387     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
388         let mut file_data = &mut self.files[file];
389         file_data.text = new_text;
390         file_data.is_overlayed = is_overlayed;
391     }
392
393     fn remove_file(&mut self, file: VfsFile) {
394         //FIXME: use arena with removal
395         self.files[file].text = Default::default();
396         self.files[file].path = Default::default();
397         let root = self.files[file].root;
398         let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
399         assert!(removed);
400     }
401
402     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
403         let (root, path) = self
404             .roots
405             .iter()
406             .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
407         let file = self.root2files[&root]
408             .iter()
409             .map(|&it| it)
410             .find(|&file| self.files[file].path == path);
411         Some((root, path, file))
412     }
413 }
414
415 #[derive(Debug, Clone)]
416 pub enum VfsChange {
417     AddRoot {
418         root: VfsRoot,
419         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
420     },
421     AddFile {
422         root: VfsRoot,
423         file: VfsFile,
424         path: RelativePathBuf,
425         text: Arc<String>,
426     },
427     RemoveFile {
428         root: VfsRoot,
429         file: VfsFile,
430         path: RelativePathBuf,
431     },
432     ChangeFile {
433         file: VfsFile,
434         text: Arc<String>,
435     },
436 }