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