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