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
22 path::{Component, Path, PathBuf},
27 use crossbeam_channel::Receiver;
28 use ra_arena::{impl_arena_id, Arena, RawId};
29 use relative_path::RelativePathBuf;
30 use rustc_hash::{FxHashMap, FxHashSet};
32 pub use crate::io::TaskResult as VfsTask;
33 use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker};
35 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
36 /// several filters match a file (nested dirs), the most nested one wins.
37 pub(crate) struct RootFilter {
39 filter: fn(RootEntry) -> bool,
42 pub(crate) struct RootEntry<'a, 'b> {
48 fn new(root: PathBuf) -> RootFilter {
51 filter: default_filter,
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 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
57 if !(self.filter)(RootEntry {
63 let path = path.strip_prefix(&self.root).ok()?;
64 RelativePathBuf::from_path(path).ok()
68 pub(crate) fn default_filter(entry: RootEntry) -> bool {
69 if entry.path.is_dir() {
70 // first component relative to root is "target"
73 .strip_prefix(entry.root)
74 .map(|p| p.components().next() != Some(Component::Normal(OsStr::new("target"))))
77 entry.path.extension() == Some(OsStr::new("rs"))
81 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82 pub struct VfsRoot(pub RawId);
83 impl_arena_id!(VfsRoot);
85 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
86 pub struct VfsFile(pub RawId);
87 impl_arena_id!(VfsFile);
91 path: RelativePathBuf,
97 roots: Arena<VfsRoot, Arc<RootFilter>>,
98 files: Arena<VfsFile, VfsFileData>,
99 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
100 pending_changes: Vec<VfsChange>,
104 impl fmt::Debug for Vfs {
105 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106 f.write_str("Vfs { ... }")
111 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
112 let worker = io::Worker::start();
115 roots: Arena::default(),
116 files: Arena::default(),
117 root2files: FxHashMap::default(),
119 pending_changes: Vec::new(),
122 // A hack to make nesting work.
123 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
124 for (i, path) in roots.iter().enumerate() {
125 let root_filter = Arc::new(RootFilter::new(path.clone()));
127 let root = res.roots.alloc(root_filter.clone());
128 res.root2files.insert(root, Default::default());
130 let nested_roots = roots[..i]
132 .filter(|it| it.starts_with(path))
133 .map(|it| it.clone())
134 .collect::<Vec<_>>();
136 let task = io::Task::AddRoot {
142 res.worker.sender().send(task).unwrap();
144 let roots = res.roots.iter().map(|(id, _)| id).collect();
148 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
149 self.roots[root].root.clone()
152 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
153 if let Some((_root, _path, Some(file))) = self.find_root(path) {
159 pub fn file2path(&self, file: VfsFile) -> PathBuf {
160 let rel_path = &self.files[file].path;
161 let root_path = &self.roots[self.files[file].root].root;
162 rel_path.to_path(root_path)
165 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
166 if let Some((_root, _path, Some(file))) = self.find_root(path) {
172 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
173 if let Some((root, rel_path, file)) = self.find_root(path) {
174 return if let Some(file) = file {
177 let text = fs::read_to_string(path).unwrap_or_default();
178 let text = Arc::new(text);
179 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
180 let change = VfsChange::AddFile {
186 self.pending_changes.push(change);
193 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
194 self.worker.receiver()
197 pub fn handle_task(&mut self, task: io::TaskResult) {
199 TaskResult::AddRoot(task) => {
200 let mut files = Vec::new();
201 // While we were scanning the root in the backgound, a file might have
202 // been open in the editor, so we need to account for that.
203 let exising = self.root2files[&task.root]
205 .map(|&file| (self.files[file].path.clone(), file))
206 .collect::<FxHashMap<_, _>>();
207 for (path, text) in task.files {
208 if let Some(&file) = exising.get(&path) {
209 let text = Arc::clone(&self.files[file].text);
210 files.push((file, path, text));
213 let text = Arc::new(text);
214 let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
215 files.push((file, path, text));
218 let change = VfsChange::AddRoot {
222 self.pending_changes.push(change);
224 TaskResult::HandleChange(change) => match &change {
225 WatcherChange::Create(path) if path.is_dir() => {
226 if let Some((root, _path, _file)) = self.find_root(&path) {
227 let root_filter = self.roots[root].clone();
231 dir: path.to_path_buf(),
237 WatcherChange::Create(path)
238 | WatcherChange::Remove(path)
239 | WatcherChange::Write(path) => {
240 if self.should_handle_change(&path) {
241 self.worker.sender().send(Task::LoadChange(change)).unwrap()
244 WatcherChange::Rescan => {
245 // TODO we should reload all files
248 TaskResult::LoadChange(change) => match change {
249 WatcherChangeData::Create { path, text }
250 | WatcherChangeData::Write { path, text } => {
251 if let Some((root, path, file)) = self.find_root(&path) {
252 if let Some(file) = file {
253 self.do_change_file(file, text, false);
255 self.do_add_file(root, path, text, false);
259 WatcherChangeData::Remove { path } => {
260 if let Some((root, path, file)) = self.find_root(&path) {
261 if let Some(file) = file {
262 self.do_remove_file(root, path, file, false);
267 TaskResult::NoOp => {}
271 fn should_handle_change(&self, path: &Path) -> bool {
272 if let Some((_root, _rel_path, file)) = self.find_root(&path) {
273 if let Some(file) = file {
274 if self.files[file].is_overlayed {
276 log::debug!("skipping overlayed \"{}\"", path.display());
282 // file doesn't belong to any root
290 path: RelativePathBuf,
293 ) -> Option<VfsFile> {
294 let text = Arc::new(text);
295 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
296 self.pending_changes.push(VfsChange::AddFile {
305 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
306 if !is_overlay && self.files[file].is_overlayed {
309 let text = Arc::new(text);
310 self.change_file(file, text.clone(), is_overlay);
312 .push(VfsChange::ChangeFile { file, text });
318 path: RelativePathBuf,
322 if !is_overlay && self.files[file].is_overlayed {
325 self.remove_file(file);
327 .push(VfsChange::RemoveFile { root, path, file });
330 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
331 if let Some((root, rel_path, file)) = self.find_root(path) {
332 if let Some(file) = file {
333 self.do_change_file(file, text, true);
336 self.do_add_file(root, rel_path, text, true)
343 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
344 if let Some((_root, _path, file)) = self.find_root(path) {
345 let file = file.expect("can't change a file which wasn't added");
346 self.do_change_file(file, new_text, true);
350 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
351 if let Some((root, path, file)) = self.find_root(path) {
352 let file = file.expect("can't remove a file which wasn't added");
353 let full_path = path.to_path(&self.roots[root].root);
354 if let Ok(text) = fs::read_to_string(&full_path) {
355 self.do_change_file(file, text, true);
357 self.do_remove_file(root, path, file, true);
365 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
366 mem::replace(&mut self.pending_changes, Vec::new())
369 /// Sutdown the VFS and terminate the background watching thread.
370 pub fn shutdown(self) -> thread::Result<()> {
371 self.worker.shutdown()
377 path: RelativePathBuf,
381 let data = VfsFileData {
387 let file = self.files.alloc(data);
388 self.root2files.get_mut(&root).unwrap().insert(file);
392 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
393 let mut file_data = &mut self.files[file];
394 file_data.text = new_text;
395 file_data.is_overlayed = is_overlayed;
398 fn remove_file(&mut self, file: VfsFile) {
399 //FIXME: use arena with removal
400 self.files[file].text = Default::default();
401 self.files[file].path = Default::default();
402 let root = self.files[file].root;
403 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
407 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
408 let (root, path) = self
411 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
412 let file = self.root2files[&root]
415 .find(|&file| self.files[file].path == path);
416 Some((root, path, file))
420 #[derive(Debug, Clone)]
424 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
429 path: RelativePathBuf,
435 path: RelativePathBuf,