]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
move roots to a module
[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::{RootConfig, 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, config) in roots.iter() {
78             root2files.insert(root, Default::default());
79             worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap();
80         }
81         let res =
82             Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
83         let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
84         (res, vfs_roots)
85     }
86
87     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
88         self.roots[root].root.clone()
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[self.files[file].root].root;
101         rel_path.to_path(root_path)
102     }
103
104     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
105         if let Some((_root, _path, Some(file))) = self.find_root(path) {
106             return Some(file);
107         }
108         None
109     }
110
111     pub fn num_roots(&self) -> usize {
112         self.roots.len()
113     }
114
115     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
116         if let Some((root, rel_path, file)) = self.find_root(path) {
117             return if let Some(file) = file {
118                 Some(file)
119             } else {
120                 let text = fs::read_to_string(path).unwrap_or_default();
121                 let text = Arc::new(text);
122                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
123                 let change = VfsChange::AddFile { file, text, root, path: rel_path };
124                 self.pending_changes.push(change);
125                 Some(file)
126             };
127         }
128         None
129     }
130
131     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
132         self.worker.receiver()
133     }
134
135     pub fn handle_task(&mut self, task: io::TaskResult) {
136         match task {
137             TaskResult::BulkLoadRoot { root, files } => {
138                 let mut cur_files = Vec::new();
139                 // While we were scanning the root in the background, a file might have
140                 // been open in the editor, so we need to account for that.
141                 let existing = self.root2files[root]
142                     .iter()
143                     .map(|&file| (self.files[file].path.clone(), file))
144                     .collect::<FxHashMap<_, _>>();
145                 for (path, text) in files {
146                     if let Some(&file) = existing.get(&path) {
147                         let text = Arc::clone(&self.files[file].text);
148                         cur_files.push((file, path, text));
149                         continue;
150                     }
151                     let text = Arc::new(text);
152                     let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
153                     cur_files.push((file, path, text));
154                 }
155
156                 let change = VfsChange::AddRoot { root, files: cur_files };
157                 self.pending_changes.push(change);
158             }
159             TaskResult::SingleFile { root, path, text } => {
160                 match (self.find_file(root, &path), text) {
161                     (Some(file), None) => {
162                         self.do_remove_file(root, path, file, false);
163                     }
164                     (None, Some(text)) => {
165                         self.do_add_file(root, path, text, false);
166                     }
167                     (Some(file), Some(text)) => {
168                         self.do_change_file(file, text, false);
169                     }
170                     (None, None) => (),
171                 }
172             }
173         }
174     }
175
176     fn do_add_file(
177         &mut self,
178         root: VfsRoot,
179         path: RelativePathBuf,
180         text: String,
181         is_overlay: bool,
182     ) -> Option<VfsFile> {
183         let text = Arc::new(text);
184         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
185         self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
186         Some(file)
187     }
188
189     fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
190         if !is_overlay && self.files[file].is_overlayed {
191             return;
192         }
193         let text = Arc::new(text);
194         self.change_file(file, text.clone(), is_overlay);
195         self.pending_changes.push(VfsChange::ChangeFile { file, text });
196     }
197
198     fn do_remove_file(
199         &mut self,
200         root: VfsRoot,
201         path: RelativePathBuf,
202         file: VfsFile,
203         is_overlay: bool,
204     ) {
205         if !is_overlay && self.files[file].is_overlayed {
206             return;
207         }
208         self.remove_file(file);
209         self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
210     }
211
212     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
213         if let Some((root, rel_path, file)) = self.find_root(path) {
214             if let Some(file) = file {
215                 self.do_change_file(file, text, true);
216                 Some(file)
217             } else {
218                 self.do_add_file(root, rel_path, text, true)
219             }
220         } else {
221             None
222         }
223     }
224
225     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
226         if let Some((_root, _path, file)) = self.find_root(path) {
227             let file = file.expect("can't change a file which wasn't added");
228             self.do_change_file(file, new_text, true);
229         }
230     }
231
232     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
233         if let Some((root, path, file)) = self.find_root(path) {
234             let file = file.expect("can't remove a file which wasn't added");
235             let full_path = path.to_path(&self.roots[root].root);
236             if let Ok(text) = fs::read_to_string(&full_path) {
237                 self.do_change_file(file, text, true);
238             } else {
239                 self.do_remove_file(root, path, file, true);
240             }
241             Some(file)
242         } else {
243             None
244         }
245     }
246
247     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
248         mem::replace(&mut self.pending_changes, Vec::new())
249     }
250
251     fn add_file(
252         &mut self,
253         root: VfsRoot,
254         path: RelativePathBuf,
255         text: Arc<String>,
256         is_overlayed: bool,
257     ) -> VfsFile {
258         let data = VfsFileData { root, path, text, is_overlayed };
259         let file = self.files.alloc(data);
260         self.root2files.get_mut(root).unwrap().insert(file);
261         file
262     }
263
264     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
265         let mut file_data = &mut self.files[file];
266         file_data.text = new_text;
267         file_data.is_overlayed = is_overlayed;
268     }
269
270     fn remove_file(&mut self, file: VfsFile) {
271         // FIXME: use arena with removal
272         self.files[file].text = Default::default();
273         self.files[file].path = Default::default();
274         let root = self.files[file].root;
275         let removed = self.root2files.get_mut(root).unwrap().remove(&file);
276         assert!(removed);
277     }
278
279     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
280         let (root, path) = self.roots.find(&path)?;
281         let file = self.find_file(root, &path);
282         Some((root, path, file))
283     }
284
285     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
286         self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path)
287     }
288 }
289
290 #[derive(Debug, Clone)]
291 pub enum VfsChange {
292     AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
293     AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
294     RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
295     ChangeFile { file: VfsFile, text: Arc<String> },
296 }