|
| 1 | +//! # Virtual File System |
| 2 | +//! |
| 3 | +//! VFS stores all files read by rust-analyzer. Reading file contents from VFS |
| 4 | +//! always returns the same contents, unless VFS was explicitly modified with |
| 5 | +//! `set_file_contents`. All changes to VFS are logged, and can be retrieved via |
| 6 | +//! `take_changes` method. The pack of changes is then pushed to `salsa` and |
| 7 | +//! triggers incremental recomputation. |
| 8 | +//! |
| 9 | +//! Files in VFS are identified with `FileId`s -- interned paths. The notion of |
| 10 | +//! the path, `VfsPath` is somewhat abstract: at the moment, it is represented |
| 11 | +//! as an `std::path::PathBuf` internally, but this is an implementation detail. |
| 12 | +//! |
| 13 | +//! VFS doesn't do IO or file watching itself. For that, see the `loader` |
| 14 | +//! module. `loader::Handle` is an object-safe trait which abstracts both file |
| 15 | +//! loading and file watching. `Handle` is dynamically configured with a set of |
| 16 | +//! directory entries which should be scanned and watched. `Handle` then |
| 17 | +//! asynchronously pushes file changes. Directory entries are configured in |
| 18 | +//! free-form via list of globs, it's up to the `Handle` to interpret the globs |
| 19 | +//! in any specific way. |
| 20 | +//! |
| 21 | +//! A simple `WalkdirLoaderHandle` is provided, which doesn't implement watching |
| 22 | +//! and just scans the directory using walkdir. |
| 23 | +//! |
| 24 | +//! VFS stores a flat list of files. `FileSet` can partition this list of files |
| 25 | +//! into disjoint sets of files. Traversal-like operations (including getting |
| 26 | +//! the neighbor file by the relative path) are handled by the `FileSet`. |
| 27 | +//! `FileSet`s are also pushed to salsa and cause it to re-check `mod foo;` |
| 28 | +//! declarations when files are created or deleted. |
| 29 | +//! |
| 30 | +//! `file_set::FileSet` and `loader::Entry` play similar, but different roles. |
| 31 | +//! Both specify the "set of paths/files", one is geared towards file watching, |
| 32 | +//! the other towards salsa changes. In particular, single `file_set::FileSet` |
| 33 | +//! may correspond to several `loader::Entry`. For example, a crate from |
| 34 | +//! crates.io which uses code generation would have two `Entries` -- for sources |
| 35 | +//! in `~/.cargo`, and for generated code in `./target/debug/build`. It will |
| 36 | +//! have a single `FileSet` which unions the two sources. |
| 37 | +mod vfs_path; |
| 38 | +mod path_interner; |
| 39 | +pub mod file_set; |
| 40 | +pub mod loader; |
| 41 | +pub mod walkdir_loader; |
| 42 | + |
| 43 | +use std::{fmt, mem}; |
| 44 | + |
| 45 | +use crate::path_interner::PathInterner; |
| 46 | + |
| 47 | +pub use crate::vfs_path::VfsPath; |
| 48 | +pub use paths::{AbsPath, AbsPathBuf}; |
| 49 | + |
| 50 | +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] |
| 51 | +pub struct FileId(pub u32); |
| 52 | + |
| 53 | +#[derive(Default)] |
| 54 | +pub struct Vfs { |
| 55 | + interner: PathInterner, |
| 56 | + data: Vec<Option<Vec<u8>>>, |
| 57 | + changes: Vec<ChangedFile>, |
| 58 | +} |
| 59 | + |
| 60 | +pub struct ChangedFile { |
| 61 | + pub file_id: FileId, |
| 62 | + pub change_kind: ChangeKind, |
| 63 | +} |
| 64 | + |
| 65 | +impl ChangedFile { |
| 66 | + pub fn exists(&self) -> bool { |
| 67 | + self.change_kind != ChangeKind::Delete |
| 68 | + } |
| 69 | + pub fn is_created_or_deleted(&self) -> bool { |
| 70 | + matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete) |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +#[derive(Eq, PartialEq)] |
| 75 | +pub enum ChangeKind { |
| 76 | + Create, |
| 77 | + Modify, |
| 78 | + Delete, |
| 79 | +} |
| 80 | + |
| 81 | +impl Vfs { |
| 82 | + pub fn file_id(&self, path: &VfsPath) -> Option<FileId> { |
| 83 | + self.interner.get(path).filter(|&it| self.get(it).is_some()) |
| 84 | + } |
| 85 | + pub fn file_path(&self, file_id: FileId) -> VfsPath { |
| 86 | + self.interner.lookup(file_id).clone() |
| 87 | + } |
| 88 | + pub fn file_contents(&self, file_id: FileId) -> &[u8] { |
| 89 | + self.get(file_id).as_deref().unwrap() |
| 90 | + } |
| 91 | + pub fn iter(&self) -> impl Iterator<Item = (FileId, VfsPath)> + '_ { |
| 92 | + (0..self.data.len()) |
| 93 | + .map(|it| FileId(it as u32)) |
| 94 | + .filter(move |&file_id| self.get(file_id).is_some()) |
| 95 | + .map(move |file_id| { |
| 96 | + let path = self.interner.lookup(file_id).clone(); |
| 97 | + (file_id, path) |
| 98 | + }) |
| 99 | + } |
| 100 | + pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) { |
| 101 | + let file_id = self.alloc_file_id(path); |
| 102 | + let change_kind = match (&self.get(file_id), &contents) { |
| 103 | + (None, None) => return, |
| 104 | + (None, Some(_)) => ChangeKind::Create, |
| 105 | + (Some(_), None) => ChangeKind::Delete, |
| 106 | + (Some(old), Some(new)) if old == new => return, |
| 107 | + (Some(_), Some(_)) => ChangeKind::Modify, |
| 108 | + }; |
| 109 | + |
| 110 | + *self.get_mut(file_id) = contents; |
| 111 | + self.changes.push(ChangedFile { file_id, change_kind }) |
| 112 | + } |
| 113 | + pub fn has_changes(&self) -> bool { |
| 114 | + !self.changes.is_empty() |
| 115 | + } |
| 116 | + pub fn take_changes(&mut self) -> Vec<ChangedFile> { |
| 117 | + mem::take(&mut self.changes) |
| 118 | + } |
| 119 | + fn alloc_file_id(&mut self, path: VfsPath) -> FileId { |
| 120 | + let file_id = self.interner.intern(path); |
| 121 | + let idx = file_id.0 as usize; |
| 122 | + let len = self.data.len().max(idx + 1); |
| 123 | + self.data.resize_with(len, || None); |
| 124 | + file_id |
| 125 | + } |
| 126 | + fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { |
| 127 | + &self.data[file_id.0 as usize] |
| 128 | + } |
| 129 | + fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { |
| 130 | + &mut self.data[file_id.0 as usize] |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +impl fmt::Debug for Vfs { |
| 135 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 136 | + f.debug_struct("Vfs").field("n_files", &self.data.len()).finish() |
| 137 | + } |
| 138 | +} |
0 commit comments