diff options
Diffstat (limited to 'crates/vfs/src/lib.rs')
-rw-r--r-- | crates/vfs/src/lib.rs | 121 |
1 files changed, 97 insertions, 24 deletions
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 9cf2afd33..e075d752b 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs | |||
@@ -2,43 +2,46 @@ | |||
2 | //! | 2 | //! |
3 | //! VFS stores all files read by rust-analyzer. Reading file contents from VFS | 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 | 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 | 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 | 6 | //! [`take_changes`] method. The pack of changes is then pushed to `salsa` and |
7 | //! triggers incremental recomputation. | 7 | //! triggers incremental recomputation. |
8 | //! | 8 | //! |
9 | //! Files in VFS are identified with `FileId`s -- interned paths. The notion of | 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 | 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. | 11 | //! as an [`std::path::PathBuf`] internally, but this is an implementation detail. |
12 | //! | 12 | //! |
13 | //! VFS doesn't do IO or file watching itself. For that, see the `loader` | 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 | 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 | 15 | //! loading and file watching. [`Handle`] is dynamically configured with a set of |
16 | //! directory entries which should be scanned and watched. `Handle` then | 16 | //! directory entries which should be scanned and watched. [`Handle`] then |
17 | //! asynchronously pushes file changes. Directory entries are configured in | 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 | 18 | //! free-form via list of globs, it's up to the [`Handle`] to interpret the globs |
19 | //! in any specific way. | 19 | //! in any specific way. |
20 | //! | 20 | //! |
21 | //! A simple `WalkdirLoaderHandle` is provided, which doesn't implement watching | 21 | //! VFS stores a flat list of files. [`file_set::FileSet`] can partition this list |
22 | //! and just scans the directory using walkdir. | 22 | //! of files into disjoint sets of files. Traversal-like operations (including |
23 | //! | 23 | //! getting the neighbor file by the relative path) are handled by the [`FileSet`]. |
24 | //! VFS stores a flat list of files. `FileSet` can partition this list of files | 24 | //! [`FileSet`]s are also pushed to salsa and cause it to re-check `mod foo;` |
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. | 25 | //! declarations when files are created or deleted. |
29 | //! | 26 | //! |
30 | //! `file_set::FileSet` and `loader::Entry` play similar, but different roles. | 27 | //! [`FileSet`] and [`loader::Entry`] play similar, but different roles. |
31 | //! Both specify the "set of paths/files", one is geared towards file watching, | 28 | //! 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` | 29 | //! the other towards salsa changes. In particular, single [`FileSet`] |
33 | //! may correspond to several `loader::Entry`. For example, a crate from | 30 | //! may correspond to several [`loader::Entry`]. For example, a crate from |
34 | //! crates.io which uses code generation would have two `Entries` -- for sources | 31 | //! 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 | 32 | //! in `~/.cargo`, and for generated code in `./target/debug/build`. It will |
36 | //! have a single `FileSet` which unions the two sources. | 33 | //! have a single [`FileSet`] which unions the two sources. |
37 | mod vfs_path; | 34 | //! |
38 | mod path_interner; | 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 | ||
39 | mod anchored_path; | 40 | mod anchored_path; |
40 | pub mod file_set; | 41 | pub mod file_set; |
41 | pub mod loader; | 42 | pub mod loader; |
43 | mod path_interner; | ||
44 | mod vfs_path; | ||
42 | 45 | ||
43 | use std::{fmt, mem}; | 46 | use std::{fmt, mem}; |
44 | 47 | ||
@@ -50,9 +53,15 @@ pub use crate::{ | |||
50 | }; | 53 | }; |
51 | pub use paths::{AbsPath, AbsPathBuf}; | 54 | pub use paths::{AbsPath, AbsPathBuf}; |
52 | 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. | ||
53 | #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | 59 | #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] |
54 | pub struct FileId(pub u32); | 60 | pub struct FileId(pub u32); |
55 | 61 | ||
62 | /// Storage for all files read by rust-analyzer. | ||
63 | /// | ||
64 | /// For more informations see the [crate-level](crate) documentation. | ||
56 | #[derive(Default)] | 65 | #[derive(Default)] |
57 | pub struct Vfs { | 66 | pub struct Vfs { |
58 | interner: PathInterner, | 67 | interner: PathInterner, |
@@ -60,40 +69,73 @@ pub struct Vfs { | |||
60 | changes: Vec<ChangedFile>, | 69 | changes: Vec<ChangedFile>, |
61 | } | 70 | } |
62 | 71 | ||
72 | /// Changed file in the [`Vfs`]. | ||
63 | pub struct ChangedFile { | 73 | pub struct ChangedFile { |
74 | /// Id of the changed file | ||
64 | pub file_id: FileId, | 75 | pub file_id: FileId, |
76 | /// Kind of change | ||
65 | pub change_kind: ChangeKind, | 77 | pub change_kind: ChangeKind, |
66 | } | 78 | } |
67 | 79 | ||
68 | impl ChangedFile { | 80 | impl ChangedFile { |
81 | /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete). | ||
69 | pub fn exists(&self) -> bool { | 82 | pub fn exists(&self) -> bool { |
70 | self.change_kind != ChangeKind::Delete | 83 | self.change_kind != ChangeKind::Delete |
71 | } | 84 | } |
85 | |||
86 | /// Returns `true` if the change is [`Create`](ChangeKind::Create) or | ||
87 | /// [`Delete`](ChangeKind::Delete). | ||
72 | pub fn is_created_or_deleted(&self) -> bool { | 88 | pub fn is_created_or_deleted(&self) -> bool { |
73 | matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete) | 89 | matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete) |
74 | } | 90 | } |
75 | } | 91 | } |
76 | 92 | ||
93 | /// Kind of [file change](ChangedFile). | ||
77 | #[derive(Eq, PartialEq, Copy, Clone, Debug)] | 94 | #[derive(Eq, PartialEq, Copy, Clone, Debug)] |
78 | pub enum ChangeKind { | 95 | pub enum ChangeKind { |
96 | /// The file was (re-)created | ||
79 | Create, | 97 | Create, |
98 | /// The file was modified | ||
80 | Modify, | 99 | Modify, |
100 | /// The file was deleted | ||
81 | Delete, | 101 | Delete, |
82 | } | 102 | } |
83 | 103 | ||
84 | impl Vfs { | 104 | impl Vfs { |
105 | /// Amount of files currently stored. | ||
106 | /// | ||
107 | /// Note that this includes deleted files. | ||
85 | pub fn len(&self) -> usize { | 108 | pub fn len(&self) -> usize { |
86 | self.data.len() | 109 | self.data.len() |
87 | } | 110 | } |
111 | |||
112 | /// Id of the given path if it exists in the `Vfs` and is not deleted. | ||
88 | pub fn file_id(&self, path: &VfsPath) -> Option<FileId> { | 113 | pub fn file_id(&self, path: &VfsPath) -> Option<FileId> { |
89 | self.interner.get(path).filter(|&it| self.get(it).is_some()) | 114 | self.interner.get(path).filter(|&it| self.get(it).is_some()) |
90 | } | 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`. | ||
91 | pub fn file_path(&self, file_id: FileId) -> VfsPath { | 122 | pub fn file_path(&self, file_id: FileId) -> VfsPath { |
92 | self.interner.lookup(file_id).clone() | 123 | self.interner.lookup(file_id).clone() |
93 | } | 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. | ||
94 | pub fn file_contents(&self, file_id: FileId) -> &[u8] { | 132 | pub fn file_contents(&self, file_id: FileId) -> &[u8] { |
95 | self.get(file_id).as_deref().unwrap() | 133 | self.get(file_id).as_deref().unwrap() |
96 | } | 134 | } |
135 | |||
136 | /// Returns an iterator over the stored ids and their corresponding paths. | ||
137 | /// | ||
138 | /// This will skip deleted files. | ||
97 | pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ { | 139 | pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ { |
98 | (0..self.data.len()) | 140 | (0..self.data.len()) |
99 | .map(|it| FileId(it as u32)) | 141 | .map(|it| FileId(it as u32)) |
@@ -103,6 +145,13 @@ impl Vfs { | |||
103 | (file_id, path) | 145 | (file_id, path) |
104 | }) | 146 | }) |
105 | } | 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. | ||
106 | pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool { | 155 | pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool { |
107 | let file_id = self.alloc_file_id(path); | 156 | let file_id = self.alloc_file_id(path); |
108 | let change_kind = match (&self.get(file_id), &contents) { | 157 | let change_kind = match (&self.get(file_id), &contents) { |
@@ -117,12 +166,24 @@ impl Vfs { | |||
117 | self.changes.push(ChangedFile { file_id, change_kind }); | 166 | self.changes.push(ChangedFile { file_id, change_kind }); |
118 | true | 167 | true |
119 | } | 168 | } |
169 | |||
170 | /// Returns `true` if the `Vfs` contains [changes](ChangedFile). | ||
120 | pub fn has_changes(&self) -> bool { | 171 | pub fn has_changes(&self) -> bool { |
121 | !self.changes.is_empty() | 172 | !self.changes.is_empty() |
122 | } | 173 | } |
174 | |||
175 | /// Drain and returns all the changes in the `Vfs`. | ||
123 | pub fn take_changes(&mut self) -> Vec<ChangedFile> { | 176 | pub fn take_changes(&mut self) -> Vec<ChangedFile> { |
124 | mem::take(&mut self.changes) | 177 | mem::take(&mut self.changes) |
125 | } | 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. | ||
126 | fn alloc_file_id(&mut self, path: VfsPath) -> FileId { | 187 | fn alloc_file_id(&mut self, path: VfsPath) -> FileId { |
127 | let file_id = self.interner.intern(path); | 188 | let file_id = self.interner.intern(path); |
128 | let idx = file_id.0 as usize; | 189 | let idx = file_id.0 as usize; |
@@ -130,9 +191,21 @@ impl Vfs { | |||
130 | self.data.resize_with(len, || None); | 191 | self.data.resize_with(len, || None); |
131 | file_id | 192 | file_id |
132 | } | 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. | ||
133 | fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { | 200 | fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { |
134 | &self.data[file_id.0 as usize] | 201 | &self.data[file_id.0 as usize] |
135 | } | 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. | ||
136 | fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { | 209 | fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { |
137 | &mut self.data[file_id.0 as usize] | 210 | &mut self.data[file_id.0 as usize] |
138 | } | 211 | } |