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},
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 in roots.iter() {
78 root2files.insert(root, Default::default());
79 worker.sender().send(io::Task::AddRoot { root }).unwrap();
82 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
83 let vfs_roots = res.roots.iter().collect();
87 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
88 self.roots.path(root).to_path_buf()
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.path(self.files[file].root);
101 rel_path.to_path(root_path)
104 pub fn n_roots(&self) -> usize {
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 {
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);
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);
130 self.add_file_event(root, rel_path, text, true)
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);
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);
148 self.remove_file_event(root, rel_path, file);
153 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
154 mem::replace(&mut self.pending_changes, Vec::new())
157 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
158 self.worker.receiver()
161 pub fn handle_task(&mut self, task: io::TaskResult) {
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]
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));
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));
182 let change = VfsChange::AddRoot { root, files: cur_files };
183 self.pending_changes.push(change);
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) {
190 match (existing_file, text) {
191 (Some(file), None) => {
192 self.remove_file_event(root, path, file);
194 (None, Some(text)) => {
195 self.add_file_event(root, path, text, false);
197 (Some(file), Some(text)) => {
198 self.change_file_event(file, text, false);
206 // *_event calls change the state of VFS and push a change onto pending
212 path: RelativePathBuf,
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 });
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 });
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 });
233 // raw_* calls change the state of VFS, but **do not** emit events.
238 path: RelativePathBuf,
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);
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;
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);
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))
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)
274 #[derive(Debug, Clone)]
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> },