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.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
new file mode 100644
index 000000000..cdf6f1fd0
--- /dev/null
+++ b/crates/vfs/src/lib.rs
@@ -0,0 +1,140 @@
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.
37mod vfs_path;
38mod path_interner;
39pub mod file_set;
40pub mod loader;
41
42use std::{fmt, mem};
43
44use crate::path_interner::PathInterner;
45
46pub use crate::vfs_path::VfsPath;
47pub use paths::{AbsPath, AbsPathBuf};
48
49#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
50pub struct FileId(pub u32);
51
52#[derive(Default)]
53pub struct Vfs {
54 interner: PathInterner,
55 data: Vec<Option<Vec<u8>>>,
56 changes: Vec<ChangedFile>,
57}
58
59pub struct ChangedFile {
60 pub file_id: FileId,
61 pub change_kind: ChangeKind,
62}
63
64impl ChangedFile {
65 pub fn exists(&self) -> bool {
66 self.change_kind != ChangeKind::Delete
67 }
68 pub fn is_created_or_deleted(&self) -> bool {
69 matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
70 }
71}
72
73#[derive(Eq, PartialEq, Copy, Clone, Debug)]
74pub enum ChangeKind {
75 Create,
76 Modify,
77 Delete,
78}
79
80impl Vfs {
81 pub fn len(&self) -> usize {
82 self.data.len()
83 }
84 pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
85 self.interner.get(path).filter(|&it| self.get(it).is_some())
86 }
87 pub fn file_path(&self, file_id: FileId) -> VfsPath {
88 self.interner.lookup(file_id).clone()
89 }
90 pub fn file_contents(&self, file_id: FileId) -> &[u8] {
91 self.get(file_id).as_deref().unwrap()
92 }
93 pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
94 (0..self.data.len())
95 .map(|it| FileId(it as u32))
96 .filter(move |&file_id| self.get(file_id).is_some())
97 .map(move |file_id| {
98 let path = self.interner.lookup(file_id);
99 (file_id, path)
100 })
101 }
102 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) {
103 let file_id = self.alloc_file_id(path);
104 let change_kind = match (&self.get(file_id), &contents) {
105 (None, None) => return,
106 (None, Some(_)) => ChangeKind::Create,
107 (Some(_), None) => ChangeKind::Delete,
108 (Some(old), Some(new)) if old == new => return,
109 (Some(_), Some(_)) => ChangeKind::Modify,
110 };
111
112 *self.get_mut(file_id) = contents;
113 self.changes.push(ChangedFile { file_id, change_kind })
114 }
115 pub fn has_changes(&self) -> bool {
116 !self.changes.is_empty()
117 }
118 pub fn take_changes(&mut self) -> Vec<ChangedFile> {
119 mem::take(&mut self.changes)
120 }
121 fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
122 let file_id = self.interner.intern(path);
123 let idx = file_id.0 as usize;
124 let len = self.data.len().max(idx + 1);
125 self.data.resize_with(len, || None);
126 file_id
127 }
128 fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
129 &self.data[file_id.0 as usize]
130 }
131 fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
132 &mut self.data[file_id.0 as usize]
133 }
134}
135
136impl fmt::Debug for Vfs {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()
139 }
140}