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