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