]> git.lizzy.rs Git - rust.git/blob - crates/vfs/src/lib.rs
Merge #9453
[rust.git] / crates / vfs / src / lib.rs
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 //! 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.
26 //!
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.
34 //!
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
40 mod anchored_path;
41 pub mod file_set;
42 pub mod loader;
43 mod path_interner;
44 mod vfs_path;
45
46 use std::{fmt, mem};
47
48 use crate::path_interner::PathInterner;
49
50 pub use crate::{
51     anchored_path::{AnchoredPath, AnchoredPathBuf},
52     vfs_path::VfsPath,
53 };
54 pub use paths::{AbsPath, AbsPathBuf};
55
56 /// Handle to a file in [`Vfs`]
57 ///
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);
61
62 /// Storage for all files read by rust-analyzer.
63 ///
64 /// For more informations see the [crate-level](crate) documentation.
65 #[derive(Default)]
66 pub struct Vfs {
67     interner: PathInterner,
68     data: Vec<Option<Vec<u8>>>,
69     changes: Vec<ChangedFile>,
70 }
71
72 /// Changed file in the [`Vfs`].
73 pub struct ChangedFile {
74     /// Id of the changed file
75     pub file_id: FileId,
76     /// Kind of change
77     pub change_kind: ChangeKind,
78 }
79
80 impl ChangedFile {
81     /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
82     pub fn exists(&self) -> bool {
83         self.change_kind != ChangeKind::Delete
84     }
85
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)
90     }
91 }
92
93 /// Kind of [file change](ChangedFile).
94 #[derive(Eq, PartialEq, Copy, Clone, Debug)]
95 pub enum ChangeKind {
96     /// The file was (re-)created
97     Create,
98     /// The file was modified
99     Modify,
100     /// The file was deleted
101     Delete,
102 }
103
104 impl Vfs {
105     /// Amount of files currently stored.
106     ///
107     /// Note that this includes deleted files.
108     pub fn len(&self) -> usize {
109         self.data.len()
110     }
111
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())
115     }
116
117     /// File path corresponding to the given `file_id`.
118     ///
119     /// # Panics
120     ///
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()
124     }
125
126     /// File content corresponding to the given `file_id`.
127     ///
128     /// # Panics
129     ///
130     /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
131     /// deleted.
132     pub fn file_contents(&self, file_id: FileId) -> &[u8] {
133         self.get(file_id).as_deref().unwrap()
134     }
135
136     /// Returns an iterator over the stored ids and their corresponding paths.
137     ///
138     /// This will skip deleted files.
139     pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
140         (0..self.data.len())
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);
145                 (file_id, path)
146             })
147     }
148
149     /// Update the `path` with the given `contents`. `None` means the file was deleted.
150     ///
151     /// Returns `true` if the file was modified, and saves the [change](ChangedFile).
152     ///
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,
163         };
164
165         *self.get_mut(file_id) = contents;
166         self.changes.push(ChangedFile { file_id, change_kind });
167         true
168     }
169
170     /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
171     pub fn has_changes(&self) -> bool {
172         !self.changes.is_empty()
173     }
174
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)
178     }
179
180     /// Returns the id associated with `path`
181     ///
182     /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a
183     /// deleted file;
184     /// - Else, returns `path`'s id.
185     ///
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);
192         file_id
193     }
194
195     /// Returns the content associated with the given `file_id`.
196     ///
197     /// # Panics
198     ///
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]
202     }
203
204     /// Mutably returns the content associated with the given `file_id`.
205     ///
206     /// # Panics
207     ///
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]
211     }
212 }
213
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()
217     }
218 }