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::{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 thread_worker::WorkerHandle;
33 use walkdir::DirEntry;
35 pub use crate::io::TaskResult as VfsTask;
36 pub use crate::watcher::{Watcher, WatcherChange};
38 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
39 /// several filters match a file (nested dirs), the most nested one wins.
42 file_filter: fn(&Path) -> bool,
46 fn new(root: PathBuf) -> RootFilter {
49 file_filter: has_rs_extension,
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 fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55 if !(self.file_filter)(path) {
58 let path = path.strip_prefix(&self.root).ok()?;
59 RelativePathBuf::from_path(path).ok()
63 fn has_rs_extension(p: &Path) -> bool {
64 p.extension() == Some(OsStr::new("rs"))
67 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68 pub struct VfsRoot(pub RawId);
69 impl_arena_id!(VfsRoot);
71 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72 pub struct VfsFile(pub RawId);
73 impl_arena_id!(VfsFile);
77 path: RelativePathBuf,
82 roots: Arena<VfsRoot, RootFilter>,
83 files: Arena<VfsFile, VfsFileData>,
84 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
85 pending_changes: Vec<VfsChange>,
87 worker_handle: WorkerHandle,
91 impl fmt::Debug for Vfs {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 f.write_str("Vfs { ... }")
98 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
99 let (worker, worker_handle) = io::start();
101 let watcher = Watcher::new().unwrap(); // TODO return Result?
104 roots: Arena::default(),
105 files: Arena::default(),
106 root2files: FxHashMap::default(),
110 pending_changes: Vec::new(),
113 // A hack to make nesting work.
114 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
115 for (i, path) in roots.iter().enumerate() {
116 let root = res.roots.alloc(RootFilter::new(path.clone()));
117 res.root2files.insert(root, Default::default());
118 let nested = roots[..i]
120 .filter(|it| it.starts_with(path))
121 .map(|it| it.clone())
122 .collect::<Vec<_>>();
123 let filter = move |entry: &DirEntry| {
124 if entry.file_type().is_file() {
125 has_rs_extension(entry.path())
127 nested.iter().all(|it| it != entry.path())
130 let task = io::Task {
133 filter: Box::new(filter),
135 res.worker.inp.send(task).unwrap();
136 res.watcher.watch(path).unwrap();
138 let roots = res.roots.iter().map(|(id, _)| id).collect();
142 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
143 self.roots[root].root.clone()
146 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
147 if let Some((_root, _path, Some(file))) = self.find_root(path) {
153 pub fn file2path(&self, file: VfsFile) -> PathBuf {
154 let rel_path = &self.files[file].path;
155 let root_path = &self.roots[self.files[file].root].root;
156 rel_path.to_path(root_path)
159 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
160 if let Some((_root, _path, Some(file))) = self.find_root(path) {
166 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
167 if let Some((root, rel_path, file)) = self.find_root(path) {
168 return if let Some(file) = file {
171 let text = fs::read_to_string(path).unwrap_or_default();
172 let text = Arc::new(text);
173 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
174 let change = VfsChange::AddFile {
180 self.pending_changes.push(change);
187 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
191 pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
192 &self.watcher.change_receiver()
195 pub fn handle_task(&mut self, task: io::TaskResult) {
196 let mut files = Vec::new();
197 // While we were scanning the root in the backgound, a file might have
198 // been open in the editor, so we need to account for that.
199 let exising = self.root2files[&task.root]
201 .map(|&file| (self.files[file].path.clone(), file))
202 .collect::<FxHashMap<_, _>>();
203 for (path, text) in task.files {
204 if let Some(&file) = exising.get(&path) {
205 let text = Arc::clone(&self.files[file].text);
206 files.push((file, path, text));
209 let text = Arc::new(text);
210 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
211 files.push((file, path, text));
214 let change = VfsChange::AddRoot {
218 self.pending_changes.push(change);
221 pub fn handle_change(&mut self, change: WatcherChange) {
223 WatcherChange::Create(path) => {
224 self.add_file_overlay(&path, None);
226 WatcherChange::Remove(path) => {
227 self.remove_file_overlay(&path);
229 WatcherChange::Rename(src, dst) => {
230 self.remove_file_overlay(&src);
231 self.add_file_overlay(&dst, None);
233 WatcherChange::Write(path) => {
234 self.change_file_overlay(&path, None);
239 pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> {
241 if let Some((root, rel_path, file)) = self.find_root(path) {
242 let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
243 let text = Arc::new(text);
244 let change = if let Some(file) = file {
246 self.change_file(file, Arc::clone(&text));
247 VfsChange::ChangeFile { file, text }
249 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
258 self.pending_changes.push(change);
263 pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) {
264 if let Some((_root, _path, file)) = self.find_root(path) {
266 new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
267 let file = file.expect("can't change a file which wasn't added");
268 let text = Arc::new(new_text);
269 self.change_file(file, Arc::clone(&text));
270 let change = VfsChange::ChangeFile { file, text };
271 self.pending_changes.push(change);
275 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
277 if let Some((root, path, file)) = self.find_root(path) {
278 let file = file.expect("can't remove a file which wasn't added");
280 let full_path = path.to_path(&self.roots[root].root);
281 let change = if let Ok(text) = fs::read_to_string(&full_path) {
282 let text = Arc::new(text);
283 self.change_file(file, Arc::clone(&text));
284 VfsChange::ChangeFile { file, text }
286 self.remove_file(file);
287 VfsChange::RemoveFile { root, file, path }
289 self.pending_changes.push(change);
294 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
295 mem::replace(&mut self.pending_changes, Vec::new())
298 /// Sutdown the VFS and terminate the background watching thread.
299 pub fn shutdown(self) -> thread::Result<()> {
300 let _ = self.watcher.shutdown();
301 let _ = self.worker.shutdown();
302 self.worker_handle.shutdown()
305 fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
306 let data = VfsFileData { root, path, text };
307 let file = self.files.alloc(data);
308 self.root2files.get_mut(&root).unwrap().insert(file);
312 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
313 self.files[file].text = new_text;
316 fn remove_file(&mut self, file: VfsFile) {
317 //FIXME: use arena with removal
318 self.files[file].text = Default::default();
319 self.files[file].path = Default::default();
320 let root = self.files[file].root;
321 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
325 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
326 let (root, path) = self
329 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
330 let file = self.root2files[&root]
333 .find(|&file| self.files[file].path == path);
334 Some((root, path, file))
338 #[derive(Debug, Clone)]
342 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
347 path: RelativePathBuf,
353 path: RelativePathBuf,