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 //! component which does this.
6 //! need to get it into the memory in the first place somehow. VFS is the
8 //! It also is responsible for watching the disk for changes, and for merging
9 //! editor state (modified, unsaved files) with disk state.
11 //! VFS is based on a concept of roots: a set of directories on the file system
12 //! whihc are watched for changes. Typically, there will be a root for each
22 path::{Path, PathBuf},
28 use rustc_hash::{FxHashMap, FxHashSet};
29 use relative_path::RelativePathBuf;
30 use crossbeam_channel::Receiver;
31 use walkdir::DirEntry;
32 use thread_worker::{WorkerHandle};
35 arena::{ArenaId, Arena},
38 pub use crate::io::TaskResult as VfsTask;
40 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
41 /// several filters match a file (nested dirs), the most nested one wins.
44 file_filter: fn(&Path) -> bool,
48 fn new(root: PathBuf) -> RootFilter {
51 file_filter: has_rs_extension,
54 /// Check if this root can contain `path`. NB: even if this returns
55 /// true, the `path` might actually be conained in some nested root.
56 fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
57 if !(self.file_filter)(path) {
60 if !(path.starts_with(&self.root)) {
63 let path = path.strip_prefix(&self.root).unwrap();
64 let path = RelativePathBuf::from_path(path).unwrap();
69 fn has_rs_extension(p: &Path) -> bool {
70 p.extension() == Some(OsStr::new("rs"))
73 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
74 pub struct VfsRoot(pub u32);
76 impl ArenaId for VfsRoot {
77 fn from_u32(idx: u32) -> VfsRoot {
80 fn to_u32(self) -> u32 {
85 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
86 pub struct VfsFile(pub u32);
88 impl ArenaId for VfsFile {
89 fn from_u32(idx: u32) -> VfsFile {
92 fn to_u32(self) -> u32 {
99 path: RelativePathBuf,
104 roots: Arena<VfsRoot, RootFilter>,
105 files: Arena<VfsFile, VfsFileData>,
106 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
107 pending_changes: Vec<VfsChange>,
109 worker_handle: WorkerHandle,
112 impl fmt::Debug for Vfs {
113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 f.write_str("Vfs { ... }")
119 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
120 let (worker, worker_handle) = io::start();
123 roots: Arena::default(),
124 files: Arena::default(),
125 root2files: FxHashMap::default(),
128 pending_changes: Vec::new(),
131 // A hack to make nesting work.
132 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
133 for (i, path) in roots.iter().enumerate() {
134 let root = res.roots.alloc(RootFilter::new(path.clone()));
135 let nested = roots[..i]
137 .filter(|it| it.starts_with(path))
138 .map(|it| it.clone())
139 .collect::<Vec<_>>();
140 let filter = move |entry: &DirEntry| {
141 if entry.file_type().is_file() {
142 has_rs_extension(entry.path())
144 nested.iter().all(|it| it != entry.path())
147 let task = io::Task {
150 filter: Box::new(filter),
152 res.worker.inp.send(task);
154 let roots = res.roots.iter().map(|(id, _)| id).collect();
158 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
159 if let Some((_root, _path, Some(file))) = self.find_root(path) {
165 pub fn file2path(&self, file: VfsFile) -> PathBuf {
166 let rel_path = &self.files[file].path;
167 let root_path = &self.roots[self.files[file].root].root;
168 rel_path.to_path(root_path)
171 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
172 if let Some((_root, _path, Some(file))) = self.find_root(path) {
178 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
182 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
186 pub fn handle_task(&mut self, task: io::TaskResult) {
187 let mut files = Vec::new();
188 for (path, text) in task.files {
189 let text = Arc::new(text);
190 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
191 files.push((file, path, text));
193 let change = VfsChange::AddRoot {
197 self.pending_changes.push(change);
200 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
202 if let Some((root, path, file)) = self.find_root(path) {
203 let text = Arc::new(text);
204 let change = if let Some(file) = file {
206 self.change_file(file, Arc::clone(&text));
207 VfsChange::ChangeFile { file, text }
209 let file = self.add_file(root, path.clone(), Arc::clone(&text));
218 self.pending_changes.push(change);
223 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
224 if let Some((_root, _path, file)) = self.find_root(path) {
225 let file = file.expect("can't change a file which wasn't added");
226 let text = Arc::new(new_text);
227 self.change_file(file, Arc::clone(&text));
228 let change = VfsChange::ChangeFile { file, text };
229 self.pending_changes.push(change);
233 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
235 if let Some((root, path, file)) = self.find_root(path) {
236 let file = file.expect("can't remove a file which wasn't added");
238 let full_path = path.to_path(&self.roots[root].root);
239 let change = if let Ok(text) = fs::read_to_string(&full_path) {
240 let text = Arc::new(text);
241 self.change_file(file, Arc::clone(&text));
242 VfsChange::ChangeFile { file, text }
244 self.remove_file(file);
245 VfsChange::RemoveFile { root, file, path }
247 self.pending_changes.push(change);
252 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
253 mem::replace(&mut self.pending_changes, Vec::new())
256 /// Sutdown the VFS and terminate the background watching thread.
257 pub fn shutdown(self) -> thread::Result<()> {
258 let _ = self.worker.shutdown();
259 self.worker_handle.shutdown()
262 fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
263 let data = VfsFileData { root, path, text };
264 let file = self.files.alloc(data);
267 .or_insert_with(FxHashSet::default)
272 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
273 self.files[file].text = new_text;
276 fn remove_file(&mut self, file: VfsFile) {
277 //FIXME: use arena with removal
278 self.files[file].text = Default::default();
279 self.files[file].path = Default::default();
280 let root = self.files[file].root;
281 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
285 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
286 let (root, path) = self
289 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
290 let file = self.root2files[&root]
293 .find(|&file| self.files[file].path == path);
294 Some((root, path, file))
298 #[derive(Debug, Clone)]
302 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
307 path: RelativePathBuf,
313 path: RelativePathBuf,