aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs/src/lib.rs')
-rw-r--r--crates/vfs/src/lib.rs121
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.
37mod vfs_path; 34//!
38mod 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
39mod anchored_path; 40mod anchored_path;
40pub mod file_set; 41pub mod file_set;
41pub mod loader; 42pub mod loader;
43mod path_interner;
44mod vfs_path;
42 45
43use std::{fmt, mem}; 46use std::{fmt, mem};
44 47
@@ -50,9 +53,15 @@ pub use crate::{
50}; 53};
51pub use paths::{AbsPath, AbsPathBuf}; 54pub 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)]
54pub struct FileId(pub u32); 60pub 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)]
57pub struct Vfs { 66pub 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`].
63pub struct ChangedFile { 73pub 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
68impl ChangedFile { 80impl 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)]
78pub enum ChangeKind { 95pub 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
84impl Vfs { 104impl 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 }