1 //! # Virtual File System
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.
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.
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.
21 //! VFS stores a flat list of files. [`file_set::FileSet`] can partition this list
22 //! of files into disjoint sets of files. Traversal-like operations (including
23 //! getting the neighbor file by the relative path) are handled by the [`FileSet`].
24 //! [`FileSet`]s are also pushed to salsa and cause it to re-check `mod foo;`
25 //! declarations when files are created or deleted.
27 //! [`FileSet`] and [`loader::Entry`] play similar, but different roles.
28 //! Both specify the "set of paths/files", one is geared towards file watching,
29 //! the other towards salsa changes. In particular, single [`FileSet`]
30 //! may correspond to several [`loader::Entry`]. For example, a crate from
31 //! crates.io which uses code generation would have two [`Entries`] -- for sources
32 //! in `~/.cargo`, and for generated code in `./target/debug/build`. It will
33 //! have a single [`FileSet`] which unions the two sources.
35 //! [`set_file_contents`]: Vfs::set_file_contents
36 //! [`take_changes`]: Vfs::take_changes
37 //! [`FileSet`]: file_set::FileSet
38 //! [`Handle`]: loader::Handle
39 //! [`Entries`]: loader::Entry
48 use crate::path_interner::PathInterner;
51 anchored_path::{AnchoredPath, AnchoredPathBuf},
54 pub use paths::{AbsPath, AbsPathBuf};
56 /// Handle to a file in [`Vfs`]
58 /// Most functions in rust-analyzer use this when they need to refer to a file.
59 #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
60 pub struct FileId(pub u32);
62 /// Storage for all files read by rust-analyzer.
64 /// For more informations see the [crate-level](crate) documentation.
67 interner: PathInterner,
68 data: Vec<Option<Vec<u8>>>,
69 changes: Vec<ChangedFile>,
72 /// Changed file in the [`Vfs`].
73 pub struct ChangedFile {
74 /// Id of the changed file
77 pub change_kind: ChangeKind,
81 /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
82 pub fn exists(&self) -> bool {
83 self.change_kind != ChangeKind::Delete
86 /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
87 /// [`Delete`](ChangeKind::Delete).
88 pub fn is_created_or_deleted(&self) -> bool {
89 matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
93 /// Kind of [file change](ChangedFile).
94 #[derive(Eq, PartialEq, Copy, Clone, Debug)]
96 /// The file was (re-)created
98 /// The file was modified
100 /// The file was deleted
105 /// Amount of files currently stored.
107 /// Note that this includes deleted files.
108 pub fn len(&self) -> usize {
112 /// Id of the given path if it exists in the `Vfs` and is not deleted.
113 pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
114 self.interner.get(path).filter(|&it| self.get(it).is_some())
117 /// File path corresponding to the given `file_id`.
121 /// Panics if the id is not present in the `Vfs`.
122 pub fn file_path(&self, file_id: FileId) -> VfsPath {
123 self.interner.lookup(file_id).clone()
126 /// File content corresponding to the given `file_id`.
130 /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
132 pub fn file_contents(&self, file_id: FileId) -> &[u8] {
133 self.get(file_id).as_deref().unwrap()
136 /// Returns an iterator over the stored ids and their corresponding paths.
138 /// This will skip deleted files.
139 pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
141 .map(|it| FileId(it as u32))
142 .filter(move |&file_id| self.get(file_id).is_some())
143 .map(move |file_id| {
144 let path = self.interner.lookup(file_id);
149 /// Update the `path` with the given `contents`. `None` means the file was deleted.
151 /// Returns `true` if the file was modified, and saves the [change](ChangedFile).
153 /// If the path does not currently exists in the `Vfs`, allocates a new
154 /// [`FileId`] for it.
155 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
156 let file_id = self.alloc_file_id(path);
157 let change_kind = match (&self.get(file_id), &contents) {
158 (None, None) => return false,
159 (None, Some(_)) => ChangeKind::Create,
160 (Some(_), None) => ChangeKind::Delete,
161 (Some(old), Some(new)) if old == new => return false,
162 (Some(_), Some(_)) => ChangeKind::Modify,
165 *self.get_mut(file_id) = contents;
166 self.changes.push(ChangedFile { file_id, change_kind });
170 /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
171 pub fn has_changes(&self) -> bool {
172 !self.changes.is_empty()
175 /// Drain and returns all the changes in the `Vfs`.
176 pub fn take_changes(&mut self) -> Vec<ChangedFile> {
177 mem::take(&mut self.changes)
180 /// Returns the id associated with `path`
182 /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a
184 /// - Else, returns `path`'s id.
186 /// Does not record a change.
187 fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
188 let file_id = self.interner.intern(path);
189 let idx = file_id.0 as usize;
190 let len = self.data.len().max(idx + 1);
191 self.data.resize_with(len, || None);
195 /// Returns the content associated with the given `file_id`.
199 /// Panics if no file is associated to that id.
200 fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
201 &self.data[file_id.0 as usize]
204 /// Mutably returns the content associated with the given `file_id`.
208 /// Panics if no file is associated to that id.
209 fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
210 &mut self.data[file_id.0 as usize]
214 impl fmt::Debug for Vfs {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()