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