]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
Merge #847
[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 //!
11 //! TODO: Some LSP clients support watching the disk, so this crate should to
12 //! support custom watcher events (related to
13 //! <https://github.com/rust-analyzer/rust-analyzer/issues/131>)
14 //!
15 //! VFS is based on a concept of roots: a set of directories on the file system
16 //! which are watched for changes. Typically, there will be a root for each
17 //! Cargo package.
18 mod roots;
19 mod io;
20
21 use std::{
22     fmt, fs, mem,
23     path::{Path, PathBuf},
24     sync::Arc,
25 };
26
27 use crossbeam_channel::Receiver;
28 use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29 use relative_path::{RelativePath, RelativePathBuf};
30 use rustc_hash::{FxHashMap, FxHashSet};
31
32 use crate::{
33     io::{TaskResult, Worker},
34     roots::Roots,
35 };
36
37 pub use crate::{
38     io::TaskResult as VfsTask,
39     roots::VfsRoot,
40 };
41
42 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43 pub struct VfsFile(pub RawId);
44 impl_arena_id!(VfsFile);
45
46 struct VfsFileData {
47     root: VfsRoot,
48     path: RelativePathBuf,
49     is_overlayed: bool,
50     text: Arc<String>,
51 }
52
53 pub struct Vfs {
54     roots: Arc<Roots>,
55     files: Arena<VfsFile, VfsFileData>,
56     root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
57     pending_changes: Vec<VfsChange>,
58     worker: Worker,
59 }
60
61 impl fmt::Debug for Vfs {
62     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63         f.debug_struct("Vfs")
64             .field("n_roots", &self.roots.len())
65             .field("n_files", &self.files.len())
66             .field("n_pending_changes", &self.pending_changes.len())
67             .finish()
68     }
69 }
70
71 impl Vfs {
72     pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
73         let roots = Arc::new(Roots::new(roots));
74         let worker = io::start(Arc::clone(&roots));
75         let mut root2files = ArenaMap::default();
76
77         for root in roots.iter() {
78             root2files.insert(root, Default::default());
79             worker.sender().send(io::Task::AddRoot { root }).unwrap();
80         }
81         let res =
82             Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
83         let vfs_roots = res.roots.iter().collect();
84         (res, vfs_roots)
85     }
86
87     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
88         self.roots.path(root).to_path_buf()
89     }
90
91     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
92         if let Some((_root, _path, Some(file))) = self.find_root(path) {
93             return Some(file);
94         }
95         None
96     }
97
98     pub fn file2path(&self, file: VfsFile) -> PathBuf {
99         let rel_path = &self.files[file].path;
100         let root_path = &self.roots.path(self.files[file].root);
101         rel_path.to_path(root_path)
102     }
103
104     pub fn n_roots(&self) -> usize {
105         self.roots.len()
106     }
107
108     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
109         if let Some((root, rel_path, file)) = self.find_root(path) {
110             return if let Some(file) = file {
111                 Some(file)
112             } else {
113                 let text = fs::read_to_string(path).unwrap_or_default();
114                 let text = Arc::new(text);
115                 let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false);
116                 let change = VfsChange::AddFile { file, text, root, path: rel_path };
117                 self.pending_changes.push(change);
118                 Some(file)
119             };
120         }
121         None
122     }
123
124     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
125         let (root, rel_path, file) = self.find_root(path)?;
126         if let Some(file) = file {
127             self.change_file_event(file, text, true);
128             Some(file)
129         } else {
130             self.add_file_event(root, rel_path, text, true)
131         }
132     }
133
134     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
135         if let Some((_root, _path, file)) = self.find_root(path) {
136             let file = file.expect("can't change a file which wasn't added");
137             self.change_file_event(file, new_text, true);
138         }
139     }
140
141     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
142         let (root, rel_path, file) = self.find_root(path)?;
143         let file = file.expect("can't remove a file which wasn't added");
144         let full_path = rel_path.to_path(&self.roots.path(root));
145         if let Ok(text) = fs::read_to_string(&full_path) {
146             self.change_file_event(file, text, false);
147         } else {
148             self.remove_file_event(root, rel_path, file);
149         }
150         Some(file)
151     }
152
153     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
154         mem::replace(&mut self.pending_changes, Vec::new())
155     }
156
157     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
158         self.worker.receiver()
159     }
160
161     pub fn handle_task(&mut self, task: io::TaskResult) {
162         match task {
163             TaskResult::BulkLoadRoot { root, files } => {
164                 let mut cur_files = Vec::new();
165                 // While we were scanning the root in the background, a file might have
166                 // been open in the editor, so we need to account for that.
167                 let existing = self.root2files[root]
168                     .iter()
169                     .map(|&file| (self.files[file].path.clone(), file))
170                     .collect::<FxHashMap<_, _>>();
171                 for (path, text) in files {
172                     if let Some(&file) = existing.get(&path) {
173                         let text = Arc::clone(&self.files[file].text);
174                         cur_files.push((file, path, text));
175                         continue;
176                     }
177                     let text = Arc::new(text);
178                     let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false);
179                     cur_files.push((file, path, text));
180                 }
181
182                 let change = VfsChange::AddRoot { root, files: cur_files };
183                 self.pending_changes.push(change);
184             }
185             TaskResult::SingleFile { root, path, text } => {
186                 let existing_file = self.find_file(root, &path);
187                 if existing_file.map(|file| self.files[file].is_overlayed) == Some(true) {
188                     return;
189                 }
190                 match (existing_file, text) {
191                     (Some(file), None) => {
192                         self.remove_file_event(root, path, file);
193                     }
194                     (None, Some(text)) => {
195                         self.add_file_event(root, path, text, false);
196                     }
197                     (Some(file), Some(text)) => {
198                         self.change_file_event(file, text, false);
199                     }
200                     (None, None) => (),
201                 }
202             }
203         }
204     }
205
206     // *_event calls change the state of VFS and push a change onto pending
207     // changes array.
208
209     fn add_file_event(
210         &mut self,
211         root: VfsRoot,
212         path: RelativePathBuf,
213         text: String,
214         is_overlay: bool,
215     ) -> Option<VfsFile> {
216         let text = Arc::new(text);
217         let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay);
218         self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
219         Some(file)
220     }
221
222     fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) {
223         let text = Arc::new(text);
224         self.raw_change_file(file, text.clone(), is_overlay);
225         self.pending_changes.push(VfsChange::ChangeFile { file, text });
226     }
227
228     fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) {
229         self.raw_remove_file(file);
230         self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
231     }
232
233     // raw_* calls change the state of VFS, but **do not** emit events.
234
235     fn raw_add_file(
236         &mut self,
237         root: VfsRoot,
238         path: RelativePathBuf,
239         text: Arc<String>,
240         is_overlayed: bool,
241     ) -> VfsFile {
242         let data = VfsFileData { root, path, text, is_overlayed };
243         let file = self.files.alloc(data);
244         self.root2files.get_mut(root).unwrap().insert(file);
245         file
246     }
247
248     fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
249         let mut file_data = &mut self.files[file];
250         file_data.text = new_text;
251         file_data.is_overlayed = is_overlayed;
252     }
253
254     fn raw_remove_file(&mut self, file: VfsFile) {
255         // FIXME: use arena with removal
256         self.files[file].text = Default::default();
257         self.files[file].path = Default::default();
258         let root = self.files[file].root;
259         let removed = self.root2files.get_mut(root).unwrap().remove(&file);
260         assert!(removed);
261     }
262
263     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
264         let (root, path) = self.roots.find(&path)?;
265         let file = self.find_file(root, &path);
266         Some((root, path, file))
267     }
268
269     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
270         self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path)
271     }
272 }
273
274 #[derive(Debug, Clone)]
275 pub enum VfsChange {
276     AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
277     AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
278     RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
279     ChangeFile { file: VfsFile, text: Arc<String> },
280 }