1 //! VFS stands for Virtual File System.
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.
8 //! It is also responsible for watching the disk for changes, and for merging
9 //! editor state (modified, unsaved files) with disk state.
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>)
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
23 path::{Path, PathBuf},
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};
33 io::{TaskResult, Worker},
34 roots::{RootConfig, Roots},
38 io::TaskResult as VfsTask,
42 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43 pub struct VfsFile(pub RawId);
44 impl_arena_id!(VfsFile);
48 path: RelativePathBuf,
55 files: Arena<VfsFile, VfsFileData>,
56 root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
57 pending_changes: Vec<VfsChange>,
61 impl fmt::Debug for Vfs {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 .field("n_roots", &self.roots.len())
65 .field("n_files", &self.files.len())
66 .field("n_pending_changes", &self.pending_changes.len())
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();
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();
82 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
83 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
87 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
88 self.roots[root].root.clone()
91 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
92 if let Some((_root, _path, Some(file))) = self.find_root(path) {
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)
104 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
105 if let Some((_root, _path, Some(file))) = self.find_root(path) {
111 pub fn num_roots(&self) -> usize {
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 {
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);
131 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
132 self.worker.receiver()
135 pub fn handle_task(&mut self, task: io::TaskResult) {
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]
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));
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));
156 let change = VfsChange::AddRoot { root, files: cur_files };
157 self.pending_changes.push(change);
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);
164 (None, Some(text)) => {
165 self.do_add_file(root, path, text, false);
167 (Some(file), Some(text)) => {
168 self.do_change_file(file, text, false);
179 path: RelativePathBuf,
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 });
189 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
190 if !is_overlay && self.files[file].is_overlayed {
193 let text = Arc::new(text);
194 self.change_file(file, text.clone(), is_overlay);
195 self.pending_changes.push(VfsChange::ChangeFile { file, text });
201 path: RelativePathBuf,
205 if !is_overlay && self.files[file].is_overlayed {
208 self.remove_file(file);
209 self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
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);
218 self.do_add_file(root, rel_path, text, true)
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);
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);
239 self.do_remove_file(root, path, file, true);
247 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
248 mem::replace(&mut self.pending_changes, Vec::new())
254 path: RelativePathBuf,
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);
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;
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);
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))
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)
290 #[derive(Debug, Clone)]
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> },