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
21 ops::{Deref, DerefMut},
22 path::{Path, PathBuf},
27 use crossbeam_channel::Receiver;
28 use ra_arena::{impl_arena_id, Arena, RawId};
29 use relative_path::{Component, RelativePath, RelativePathBuf};
30 use rustc_hash::{FxHashMap, FxHashSet};
31 use walkdir::DirEntry;
33 pub use crate::io::TaskResult as VfsTask;
34 use io::{TaskResult, Worker};
36 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
37 /// several filters match a file (nested dirs), the most nested one wins.
38 pub(crate) struct RootFilter {
40 filter: fn(&Path, &RelativePath) -> bool,
41 excluded_dirs: Vec<PathBuf>,
45 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter {
48 filter: default_filter,
52 /// Check if this root can contain `path`. NB: even if this returns
53 /// true, the `path` might actually be conained in some nested root.
54 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55 let rel_path = path.strip_prefix(&self.root).ok()?;
56 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
57 if !(self.filter)(path, rel_path.as_relative_path()) {
63 pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a {
64 move |entry: &DirEntry| {
65 if entry.file_type().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path())
67 // do not walk nested roots
70 self.can_contain(entry.path()).is_some()
76 pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
78 for (i, c) in rel_path.components().enumerate() {
79 if let Component::Normal(c) = c {
80 // TODO hardcoded for now
81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
88 rel_path.extension() == Some("rs")
92 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
93 pub struct VfsRoot(pub RawId);
94 impl_arena_id!(VfsRoot);
96 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97 pub struct VfsFile(pub RawId);
98 impl_arena_id!(VfsFile);
102 path: RelativePathBuf,
107 pub(crate) struct Roots {
108 roots: Arena<VfsRoot, Arc<RootFilter>>,
112 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
113 let mut roots = Arena::default();
114 // A hack to make nesting work.
115 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
116 for (i, path) in paths.iter().enumerate() {
117 let nested_roots = paths[..i]
119 .filter(|it| it.starts_with(path))
120 .map(|it| it.clone())
121 .collect::<Vec<_>>();
123 let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots));
125 roots.alloc(root_filter.clone());
129 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
132 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))
136 impl Deref for Roots {
137 type Target = Arena<VfsRoot, Arc<RootFilter>>;
138 fn deref(&self) -> &Self::Target {
143 impl DerefMut for Roots {
144 fn deref_mut(&mut self) -> &mut Self::Target {
151 files: Arena<VfsFile, VfsFileData>,
152 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
153 pending_changes: Vec<VfsChange>,
157 impl fmt::Debug for Vfs {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 f.write_str("Vfs { ... }")
164 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
165 let roots = Arc::new(Roots::new(roots));
166 let worker = io::Worker::start(roots.clone());
167 let mut root2files = FxHashMap::default();
169 for (root, filter) in roots.iter() {
170 root2files.insert(root, Default::default());
173 .send(io::Task::AddRoot {
175 filter: filter.clone(),
181 files: Arena::default(),
184 pending_changes: Vec::new(),
186 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
190 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
191 self.roots[root].root.clone()
194 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
195 if let Some((_root, _path, Some(file))) = self.find_root(path) {
201 pub fn file2path(&self, file: VfsFile) -> PathBuf {
202 let rel_path = &self.files[file].path;
203 let root_path = &self.roots[self.files[file].root].root;
204 rel_path.to_path(root_path)
207 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
208 if let Some((_root, _path, Some(file))) = self.find_root(path) {
214 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
215 if let Some((root, rel_path, file)) = self.find_root(path) {
216 return if let Some(file) = file {
219 let text = fs::read_to_string(path).unwrap_or_default();
220 let text = Arc::new(text);
221 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
222 let change = VfsChange::AddFile {
228 self.pending_changes.push(change);
235 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
236 self.worker.receiver()
239 pub fn handle_task(&mut self, task: io::TaskResult) {
241 TaskResult::BulkLoadRoot { root, files } => {
242 let mut cur_files = Vec::new();
243 // While we were scanning the root in the backgound, a file might have
244 // been open in the editor, so we need to account for that.
245 let exising = self.root2files[&root]
247 .map(|&file| (self.files[file].path.clone(), file))
248 .collect::<FxHashMap<_, _>>();
249 for (path, text) in files {
250 if let Some(&file) = exising.get(&path) {
251 let text = Arc::clone(&self.files[file].text);
252 cur_files.push((file, path, text));
255 let text = Arc::new(text);
256 let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
257 cur_files.push((file, path, text));
260 let change = VfsChange::AddRoot {
264 self.pending_changes.push(change);
266 TaskResult::AddSingleFile { root, path, text } => {
267 self.do_add_file(root, path, text, false);
269 TaskResult::ChangeSingleFile { root, path, text } => {
270 if let Some(file) = self.find_file(root, &path) {
271 self.do_change_file(file, text, false);
273 self.do_add_file(root, path, text, false);
276 TaskResult::RemoveSingleFile { root, path } => {
277 if let Some(file) = self.find_file(root, &path) {
278 self.do_remove_file(root, path, file, false);
287 path: RelativePathBuf,
290 ) -> Option<VfsFile> {
291 let text = Arc::new(text);
292 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
293 self.pending_changes.push(VfsChange::AddFile {
302 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
303 if !is_overlay && self.files[file].is_overlayed {
306 let text = Arc::new(text);
307 self.change_file(file, text.clone(), is_overlay);
309 .push(VfsChange::ChangeFile { file, text });
315 path: RelativePathBuf,
319 if !is_overlay && self.files[file].is_overlayed {
322 self.remove_file(file);
324 .push(VfsChange::RemoveFile { root, path, file });
327 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
328 if let Some((root, rel_path, file)) = self.find_root(path) {
329 if let Some(file) = file {
330 self.do_change_file(file, text, true);
333 self.do_add_file(root, rel_path, text, true)
340 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
341 if let Some((_root, _path, file)) = self.find_root(path) {
342 let file = file.expect("can't change a file which wasn't added");
343 self.do_change_file(file, new_text, true);
347 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
348 if let Some((root, path, file)) = self.find_root(path) {
349 let file = file.expect("can't remove a file which wasn't added");
350 let full_path = path.to_path(&self.roots[root].root);
351 if let Ok(text) = fs::read_to_string(&full_path) {
352 self.do_change_file(file, text, true);
354 self.do_remove_file(root, path, file, true);
362 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
363 mem::replace(&mut self.pending_changes, Vec::new())
366 /// Sutdown the VFS and terminate the background watching thread.
367 pub fn shutdown(self) -> thread::Result<()> {
368 self.worker.shutdown()
374 path: RelativePathBuf,
378 let data = VfsFileData {
384 let file = self.files.alloc(data);
385 self.root2files.get_mut(&root).unwrap().insert(file);
389 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
390 let mut file_data = &mut self.files[file];
391 file_data.text = new_text;
392 file_data.is_overlayed = is_overlayed;
395 fn remove_file(&mut self, file: VfsFile) {
396 //FIXME: use arena with removal
397 self.files[file].text = Default::default();
398 self.files[file].path = Default::default();
399 let root = self.files[file].root;
400 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
404 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
405 let (root, path) = self.roots.find(&path)?;
406 let file = self.find_file(root, &path);
407 Some((root, path, file))
410 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
411 self.root2files[&root]
414 .find(|&file| self.files[file].path == path)
418 #[derive(Debug, Clone)]
422 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
427 path: RelativePathBuf,
433 path: RelativePathBuf,