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.
10 //! TODO: Some LSP clients support watching the disk, so this crate should
11 //! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
13 //! VFS is based on a concept of roots: a set of directories on the file system
14 //! which are watched for changes. Typically, there will be a root for each
24 path::{Path, PathBuf},
30 use rustc_hash::{FxHashMap, FxHashSet};
31 use relative_path::RelativePathBuf;
32 use crossbeam_channel::Receiver;
33 use walkdir::DirEntry;
34 use thread_worker::WorkerHandle;
37 arena::{ArenaId, Arena},
40 pub use crate::io::TaskResult as VfsTask;
42 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
43 /// several filters match a file (nested dirs), the most nested one wins.
46 file_filter: fn(&Path) -> bool,
50 fn new(root: PathBuf) -> RootFilter {
53 file_filter: has_rs_extension,
56 /// Check if this root can contain `path`. NB: even if this returns
57 /// true, the `path` might actually be conained in some nested root.
58 fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
59 if !(self.file_filter)(path) {
62 let path = path.strip_prefix(&self.root).ok()?;
63 RelativePathBuf::from_path(path).ok()
67 fn has_rs_extension(p: &Path) -> bool {
68 p.extension() == Some(OsStr::new("rs"))
71 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
72 pub struct VfsRoot(pub u32);
74 impl ArenaId for VfsRoot {
75 fn from_u32(idx: u32) -> VfsRoot {
78 fn to_u32(self) -> u32 {
83 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
84 pub struct VfsFile(pub u32);
86 impl ArenaId for VfsFile {
87 fn from_u32(idx: u32) -> VfsFile {
90 fn to_u32(self) -> u32 {
97 path: RelativePathBuf,
102 roots: Arena<VfsRoot, RootFilter>,
103 files: Arena<VfsFile, VfsFileData>,
104 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
105 pending_changes: Vec<VfsChange>,
107 worker_handle: WorkerHandle,
110 impl fmt::Debug for Vfs {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 f.write_str("Vfs { ... }")
117 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
118 let (worker, worker_handle) = io::start();
121 roots: Arena::default(),
122 files: Arena::default(),
123 root2files: FxHashMap::default(),
126 pending_changes: Vec::new(),
129 // A hack to make nesting work.
130 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
131 for (i, path) in roots.iter().enumerate() {
132 let root = res.roots.alloc(RootFilter::new(path.clone()));
133 res.root2files.insert(root, Default::default());
134 let nested = roots[..i]
136 .filter(|it| it.starts_with(path))
137 .map(|it| it.clone())
138 .collect::<Vec<_>>();
139 let filter = move |entry: &DirEntry| {
140 if entry.file_type().is_file() {
141 has_rs_extension(entry.path())
143 nested.iter().all(|it| it != entry.path())
146 let task = io::Task {
149 filter: Box::new(filter),
151 res.worker.inp.send(task).unwrap();
153 let roots = res.roots.iter().map(|(id, _)| id).collect();
157 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
158 self.roots[root].root.clone()
161 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
162 if let Some((_root, _path, Some(file))) = self.find_root(path) {
168 pub fn file2path(&self, file: VfsFile) -> PathBuf {
169 let rel_path = &self.files[file].path;
170 let root_path = &self.roots[self.files[file].root].root;
171 rel_path.to_path(root_path)
174 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
175 if let Some((_root, _path, Some(file))) = self.find_root(path) {
181 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
182 if let Some((root, rel_path, file)) = self.find_root(path) {
183 return if let Some(file) = file {
186 let text = fs::read_to_string(path).unwrap_or_default();
187 let text = Arc::new(text);
188 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
189 let change = VfsChange::AddFile {
195 self.pending_changes.push(change);
202 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
206 pub fn handle_task(&mut self, task: io::TaskResult) {
207 let mut files = Vec::new();
208 // While we were scanning the root in the backgound, a file might have
209 // been open in the editor, so we need to account for that.
210 let exising = self.root2files[&task.root]
212 .map(|&file| (self.files[file].path.clone(), file))
213 .collect::<FxHashMap<_, _>>();
214 for (path, text) in task.files {
215 if let Some(&file) = exising.get(&path) {
216 let text = Arc::clone(&self.files[file].text);
217 files.push((file, path, text));
220 let text = Arc::new(text);
221 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
222 files.push((file, path, text));
225 let change = VfsChange::AddRoot {
229 self.pending_changes.push(change);
232 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
234 if let Some((root, path, file)) = self.find_root(path) {
235 let text = Arc::new(text);
236 let change = if let Some(file) = file {
238 self.change_file(file, Arc::clone(&text));
239 VfsChange::ChangeFile { file, text }
241 let file = self.add_file(root, path.clone(), Arc::clone(&text));
250 self.pending_changes.push(change);
255 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
256 if let Some((_root, _path, file)) = self.find_root(path) {
257 let file = file.expect("can't change a file which wasn't added");
258 let text = Arc::new(new_text);
259 self.change_file(file, Arc::clone(&text));
260 let change = VfsChange::ChangeFile { file, text };
261 self.pending_changes.push(change);
265 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
267 if let Some((root, path, file)) = self.find_root(path) {
268 let file = file.expect("can't remove a file which wasn't added");
270 let full_path = path.to_path(&self.roots[root].root);
271 let change = if let Ok(text) = fs::read_to_string(&full_path) {
272 let text = Arc::new(text);
273 self.change_file(file, Arc::clone(&text));
274 VfsChange::ChangeFile { file, text }
276 self.remove_file(file);
277 VfsChange::RemoveFile { root, file, path }
279 self.pending_changes.push(change);
284 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
285 mem::replace(&mut self.pending_changes, Vec::new())
288 /// Sutdown the VFS and terminate the background watching thread.
289 pub fn shutdown(self) -> thread::Result<()> {
290 let _ = self.worker.shutdown();
291 self.worker_handle.shutdown()
294 fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
295 let data = VfsFileData { root, path, text };
296 let file = self.files.alloc(data);
297 self.root2files.get_mut(&root).unwrap().insert(file);
301 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
302 self.files[file].text = new_text;
305 fn remove_file(&mut self, file: VfsFile) {
306 //FIXME: use arena with removal
307 self.files[file].text = Default::default();
308 self.files[file].path = Default::default();
309 let root = self.files[file].root;
310 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
314 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
315 let (root, path) = self
318 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
319 let file = self.root2files[&root]
322 .find(|&file| self.files[file].path == path);
323 Some((root, path, file))
327 #[derive(Debug, Clone)]
331 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
336 path: RelativePathBuf,
342 path: RelativePathBuf,