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 path::{Path, PathBuf},
26 use crossbeam_channel::Receiver;
27 use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
28 use relative_path::{Component, RelativePath, RelativePathBuf};
29 use rustc_hash::{FxHashMap, FxHashSet};
31 pub use crate::io::TaskResult as VfsTask;
32 use io::{TaskResult, Worker};
34 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35 pub struct VfsRoot(pub RawId);
36 impl_arena_id!(VfsRoot);
38 /// Describes the contents of a single source root.
40 /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` whihc
41 /// specifes the source root or as a function whihc takes a `PathBuf` and
42 /// returns `true` iff path belongs to the source root
43 pub(crate) struct RootConfig {
45 excluded_dirs: Vec<PathBuf>,
48 pub(crate) struct Roots {
49 roots: Arena<VfsRoot, Arc<RootConfig>>,
52 impl std::ops::Deref for Roots {
53 type Target = Arena<VfsRoot, Arc<RootConfig>>;
54 fn deref(&self) -> &Self::Target {
60 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
66 /// Cheks if root contains a path and returns a root-relative path.
67 pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
68 // First, check excluded dirs
69 if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
72 let rel_path = path.strip_prefix(&self.root).ok()?;
73 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
75 // Ignore some common directories.
77 // FIXME: don't hard-code, specify at source-root creation time using
79 for (i, c) in rel_path.components().enumerate() {
80 if let Component::Normal(c) = c {
81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
87 if path.is_file() && rel_path.extension() != Some("rs") {
96 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
97 let mut roots = Arena::default();
98 // A hack to make nesting work.
99 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
100 for (i, path) in paths.iter().enumerate() {
101 let nested_roots = paths[..i]
103 .filter(|it| it.starts_with(path))
104 .map(|it| it.clone())
105 .collect::<Vec<_>>();
107 let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
109 roots.alloc(config.clone());
113 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
116 .find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
120 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
121 pub struct VfsFile(pub RawId);
122 impl_arena_id!(VfsFile);
126 path: RelativePathBuf,
133 files: Arena<VfsFile, VfsFileData>,
134 root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
135 pending_changes: Vec<VfsChange>,
139 impl fmt::Debug for Vfs {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 f.debug_struct("Vfs")
142 .field("n_roots", &self.roots.len())
143 .field("n_files", &self.files.len())
144 .field("n_pending_changes", &self.pending_changes.len())
150 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
151 let roots = Arc::new(Roots::new(roots));
152 let worker = io::Worker::start(Arc::clone(&roots));
153 let mut root2files = ArenaMap::default();
155 for (root, config) in roots.iter() {
156 root2files.insert(root, Default::default());
159 .send(io::Task::AddRoot {
161 config: Arc::clone(config),
167 files: Arena::default(),
170 pending_changes: Vec::new(),
172 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
176 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
177 self.roots[root].root.clone()
180 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
181 if let Some((_root, _path, Some(file))) = self.find_root(path) {
187 pub fn file2path(&self, file: VfsFile) -> PathBuf {
188 let rel_path = &self.files[file].path;
189 let root_path = &self.roots[self.files[file].root].root;
190 rel_path.to_path(root_path)
193 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
194 if let Some((_root, _path, Some(file))) = self.find_root(path) {
200 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
201 if let Some((root, rel_path, file)) = self.find_root(path) {
202 return if let Some(file) = file {
205 let text = fs::read_to_string(path).unwrap_or_default();
206 let text = Arc::new(text);
207 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
208 let change = VfsChange::AddFile {
214 self.pending_changes.push(change);
221 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
222 self.worker.receiver()
225 pub fn handle_task(&mut self, task: io::TaskResult) {
227 TaskResult::BulkLoadRoot { root, files } => {
228 let mut cur_files = Vec::new();
229 // While we were scanning the root in the backgound, a file might have
230 // been open in the editor, so we need to account for that.
231 let exising = self.root2files[root]
233 .map(|&file| (self.files[file].path.clone(), file))
234 .collect::<FxHashMap<_, _>>();
235 for (path, text) in files {
236 if let Some(&file) = exising.get(&path) {
237 let text = Arc::clone(&self.files[file].text);
238 cur_files.push((file, path, text));
241 let text = Arc::new(text);
242 let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
243 cur_files.push((file, path, text));
246 let change = VfsChange::AddRoot {
250 self.pending_changes.push(change);
252 TaskResult::AddSingleFile { root, path, text } => {
253 if self.find_file(root, &path).is_none() {
254 self.do_add_file(root, path, text, false);
257 TaskResult::ChangeSingleFile { root, path, text } => {
258 if let Some(file) = self.find_file(root, &path) {
259 self.do_change_file(file, text, false);
261 self.do_add_file(root, path, text, false);
264 TaskResult::RemoveSingleFile { root, path } => {
265 if let Some(file) = self.find_file(root, &path) {
266 self.do_remove_file(root, path, file, false);
275 path: RelativePathBuf,
278 ) -> Option<VfsFile> {
279 let text = Arc::new(text);
280 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
281 self.pending_changes.push(VfsChange::AddFile {
290 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
291 if !is_overlay && self.files[file].is_overlayed {
294 let text = Arc::new(text);
295 self.change_file(file, text.clone(), is_overlay);
297 .push(VfsChange::ChangeFile { file, text });
303 path: RelativePathBuf,
307 if !is_overlay && self.files[file].is_overlayed {
310 self.remove_file(file);
312 .push(VfsChange::RemoveFile { root, path, file });
315 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
316 if let Some((root, rel_path, file)) = self.find_root(path) {
317 if let Some(file) = file {
318 self.do_change_file(file, text, true);
321 self.do_add_file(root, rel_path, text, true)
328 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
329 if let Some((_root, _path, file)) = self.find_root(path) {
330 let file = file.expect("can't change a file which wasn't added");
331 self.do_change_file(file, new_text, true);
335 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
336 if let Some((root, path, file)) = self.find_root(path) {
337 let file = file.expect("can't remove a file which wasn't added");
338 let full_path = path.to_path(&self.roots[root].root);
339 if let Ok(text) = fs::read_to_string(&full_path) {
340 self.do_change_file(file, text, true);
342 self.do_remove_file(root, path, file, true);
350 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
351 mem::replace(&mut self.pending_changes, Vec::new())
354 /// Sutdown the VFS and terminate the background watching thread.
355 pub fn shutdown(self) -> thread::Result<()> {
356 self.worker.shutdown()
362 path: RelativePathBuf,
366 let data = VfsFileData {
372 let file = self.files.alloc(data);
373 self.root2files.get_mut(root).unwrap().insert(file);
377 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
378 let mut file_data = &mut self.files[file];
379 file_data.text = new_text;
380 file_data.is_overlayed = is_overlayed;
383 fn remove_file(&mut self, file: VfsFile) {
384 //FIXME: use arena with removal
385 self.files[file].text = Default::default();
386 self.files[file].path = Default::default();
387 let root = self.files[file].root;
388 let removed = self.root2files.get_mut(root).unwrap().remove(&file);
392 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
393 let (root, path) = self.roots.find(&path)?;
394 let file = self.find_file(root, &path);
395 Some((root, path, file))
398 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
399 self.root2files[root]
402 .find(|&file| self.files[file].path == path)
406 #[derive(Debug, Clone)]
410 files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
415 path: RelativePathBuf,
421 path: RelativePathBuf,