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
23 path::{Component, Path, PathBuf},
28 use crossbeam_channel::Receiver;
29 use ra_arena::{impl_arena_id, Arena, RawId};
30 use relative_path::RelativePathBuf;
31 use rustc_hash::{FxHashMap, FxHashSet};
32 use walkdir::DirEntry;
34 pub use crate::io::TaskResult as VfsTask;
35 pub use crate::watcher::WatcherChange;
37 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
38 /// several filters match a file (nested dirs), the most nested one wins.
39 pub(crate) struct RootFilter {
41 filter: fn(RootEntry) -> bool,
44 pub(crate) struct RootEntry<'a, 'b> {
50 fn new(root: PathBuf) -> RootFilter {
53 filter: default_filter,
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 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
59 if !(self.filter)(RootEntry {
65 let path = path.strip_prefix(&self.root).ok()?;
66 RelativePathBuf::from_path(path).ok()
70 pub(crate) fn default_filter(entry: RootEntry) -> bool {
71 if entry.path.is_dir() {
72 // first component relative to root is "target"
75 .strip_prefix(entry.root)
76 .map(|p| p.components().next() != Some(Component::Normal(OsStr::new("target"))))
79 entry.path.extension() == Some(OsStr::new("rs"))
83 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84 pub struct VfsRoot(pub RawId);
85 impl_arena_id!(VfsRoot);
87 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
88 pub struct VfsFile(pub RawId);
89 impl_arena_id!(VfsFile);
93 path: RelativePathBuf,
99 roots: Arena<VfsRoot, Arc<RootFilter>>,
100 files: Arena<VfsFile, VfsFileData>,
101 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
102 pending_changes: Vec<VfsChange>,
106 impl fmt::Debug for Vfs {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 f.write_str("Vfs { ... }")
113 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
114 let worker = io::Worker::start();
117 roots: Arena::default(),
118 files: Arena::default(),
119 root2files: FxHashMap::default(),
121 pending_changes: Vec::new(),
124 // A hack to make nesting work.
125 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
126 for (i, path) in roots.iter().enumerate() {
127 let root_filter = Arc::new(RootFilter::new(path.clone()));
129 let root = res.roots.alloc(root_filter.clone());
130 res.root2files.insert(root, Default::default());
132 let nested = roots[..i]
134 .filter(|it| it.starts_with(path))
135 .map(|it| it.clone())
136 .collect::<Vec<_>>();
138 let filter = move |entry: &DirEntry| {
139 if entry.file_type().is_dir() && nested.iter().any(|it| it == entry.path()) {
142 root_filter.can_contain(entry.path()).is_some()
145 let task = io::Task::AddRoot {
148 filter: Box::new(filter),
150 res.worker.sender().send(task).unwrap();
152 let roots = res.roots.iter().map(|(id, _)| id).collect();
156 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
157 self.roots[root].root.clone()
160 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
161 if let Some((_root, _path, Some(file))) = self.find_root(path) {
167 pub fn file2path(&self, file: VfsFile) -> PathBuf {
168 let rel_path = &self.files[file].path;
169 let root_path = &self.roots[self.files[file].root].root;
170 rel_path.to_path(root_path)
173 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
174 if let Some((_root, _path, Some(file))) = self.find_root(path) {
180 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
181 if let Some((root, rel_path, file)) = self.find_root(path) {
182 return if let Some(file) = file {
185 let text = fs::read_to_string(path).unwrap_or_default();
186 let text = Arc::new(text);
187 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
188 let change = VfsChange::AddFile {
194 self.pending_changes.push(change);
201 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
202 self.worker.receiver()
205 pub fn handle_task(&mut self, task: io::TaskResult) {
207 io::TaskResult::AddRoot(task) => {
208 let mut files = Vec::new();
209 // While we were scanning the root in the backgound, a file might have
210 // been open in the editor, so we need to account for that.
211 let exising = self.root2files[&task.root]
213 .map(|&file| (self.files[file].path.clone(), file))
214 .collect::<FxHashMap<_, _>>();
215 for (path, text) in task.files {
216 if let Some(&file) = exising.get(&path) {
217 let text = Arc::clone(&self.files[file].text);
218 files.push((file, path, text));
221 let text = Arc::new(text);
222 let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
223 files.push((file, path, text));
226 let change = VfsChange::AddRoot {
230 self.pending_changes.push(change);
232 io::TaskResult::HandleChange(change) => match &change {
233 watcher::WatcherChange::Create(path) if path.is_dir() => {
234 if let Some((root, _path, _file)) = self.find_root(&path) {
235 let root_filter = self.roots[root].clone();
237 move |entry: &DirEntry| root_filter.can_contain(entry.path()).is_some();
240 .send(io::Task::Watch {
241 dir: path.to_path_buf(),
242 filter: Box::new(filter),
247 watcher::WatcherChange::Create(path)
248 | watcher::WatcherChange::Remove(path)
249 | watcher::WatcherChange::Write(path) => {
250 if self.should_handle_change(&path) {
253 .send(io::Task::LoadChange(change))
257 watcher::WatcherChange::Rescan => {
258 // TODO we should reload all files
261 io::TaskResult::LoadChange(change) => match change {
262 io::WatcherChangeData::Create { path, text }
263 | io::WatcherChangeData::Write { path, text } => {
264 if let Some((root, path, file)) = self.find_root(&path) {
265 if let Some(file) = file {
266 self.do_change_file(file, text, false);
268 self.do_add_file(root, path, text, false);
272 io::WatcherChangeData::Remove { path } => {
273 if let Some((root, path, file)) = self.find_root(&path) {
274 if let Some(file) = file {
275 self.do_remove_file(root, path, file, false);
280 io::TaskResult::NoOp => {}
284 fn should_handle_change(&self, path: &Path) -> bool {
285 if let Some((_root, _rel_path, file)) = self.find_root(&path) {
286 if let Some(file) = file {
287 if self.files[file].is_overlayed {
289 log::debug!("skipping overlayed \"{}\"", path.display());
295 // file doesn't belong to any root
303 path: RelativePathBuf,
306 ) -> Option<VfsFile> {
307 let text = Arc::new(text);
308 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
309 self.pending_changes.push(VfsChange::AddFile {
318 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
319 if !is_overlay && self.files[file].is_overlayed {
322 let text = Arc::new(text);
323 self.change_file(file, text.clone(), is_overlay);
325 .push(VfsChange::ChangeFile { file, text });
331 path: RelativePathBuf,
335 if !is_overlay && self.files[file].is_overlayed {
338 self.remove_file(file);
340 .push(VfsChange::RemoveFile { root, path, file });
343 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
344 if let Some((root, rel_path, file)) = self.find_root(path) {
345 if let Some(file) = file {
346 self.do_change_file(file, text, true);
349 self.do_add_file(root, rel_path, text, true)
356 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
357 if let Some((_root, _path, file)) = self.find_root(path) {
358 let file = file.expect("can't change a file which wasn't added");
359 self.do_change_file(file, new_text, true);
363 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
364 if let Some((root, path, file)) = self.find_root(path) {
365 let file = file.expect("can't remove a file which wasn't added");
366 let full_path = path.to_path(&self.roots[root].root);
367 if let Ok(text) = fs::read_to_string(&full_path) {
368 self.do_change_file(file, text, true);
370 self.do_remove_file(root, path, file, true);
378 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
379 mem::replace(&mut self.pending_changes, Vec::new())
382 /// Sutdown the VFS and terminate the background watching thread.
383 pub fn shutdown(self) -> thread::Result<()> {
384 self.worker.shutdown()
390 path: RelativePathBuf,
394 let data = VfsFileData {
400 let file = self.files.alloc(data);
401 self.root2files.get_mut(&root).unwrap().insert(file);
405 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
406 let mut file_data = &mut self.files[file];
407 file_data.text = new_text;
408 file_data.is_overlayed = is_overlayed;
411 fn remove_file(&mut self, file: VfsFile) {
412 //FIXME: use arena with removal
413 self.files[file].text = Default::default();
414 self.files[file].path = Default::default();
415 let root = self.files[file].root;
416 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
420 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
421 let (root, path) = self
424 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
425 let file = self.root2files[&root]
428 .find(|&file| self.files[file].path == path);
429 Some((root, path, file))
433 #[derive(Debug, Clone)]
437 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
442 path: RelativePathBuf,
448 path: RelativePathBuf,