aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs/src/lib.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-06-15 12:29:07 +0100
committerAleksey Kladov <[email protected]>2020-06-16 12:42:29 +0100
commitc002322bde06a73c8cfa02cd1cbe33cf225da47b (patch)
tree4eac72ea31ec4420f184d42592e616cee82563dd /crates/vfs/src/lib.rs
parentdb6100dbaa4f09543c7e6f2ab9986daa55919ad5 (diff)
New VFS API
Diffstat (limited to 'crates/vfs/src/lib.rs')
-rw-r--r--crates/vfs/src/lib.rs138
1 files changed, 138 insertions, 0 deletions
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
new file mode 100644
index 000000000..75ce61cf9
--- /dev/null
+++ b/crates/vfs/src/lib.rs
@@ -0,0 +1,138 @@
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 file_id(&self, path: &VfsPath) -> Option<FileId> {
83 self.interner.get(path).filter(|&it| self.get(it).is_some())
84 }
85 pub fn file_path(&self, file_id: FileId) -> VfsPath {
86 self.interner.lookup(file_id).clone()
87 }
88 pub fn file_contents(&self, file_id: FileId) -> &[u8] {
89 self.get(file_id).as_deref().unwrap()
90 }
91 pub fn iter(&self) -> impl Iterator<Item = (FileId, VfsPath)> + '_ {
92 (0..self.data.len())
93 .map(|it| FileId(it as u32))
94 .filter(move |&file_id| self.get(file_id).is_some())
95 .map(move |file_id| {
96 let path = self.interner.lookup(file_id).clone();
97 (file_id, path)
98 })
99 }
100 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) {
101 let file_id = self.alloc_file_id(path);
102 let change_kind = match (&self.get(file_id), &contents) {
103 (None, None) => return,
104 (None, Some(_)) => ChangeKind::Create,
105 (Some(_), None) => ChangeKind::Delete,
106 (Some(old), Some(new)) if old == new => return,
107 (Some(_), Some(_)) => ChangeKind::Modify,
108 };
109
110 *self.get_mut(file_id) = contents;
111 self.changes.push(ChangedFile { file_id, change_kind })
112 }
113 pub fn has_changes(&self) -> bool {
114 !self.changes.is_empty()
115 }
116 pub fn take_changes(&mut self) -> Vec<ChangedFile> {
117 mem::take(&mut self.changes)
118 }
119 fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
120 let file_id = self.interner.intern(path);
121 let idx = file_id.0 as usize;
122 let len = self.data.len().max(idx + 1);
123 self.data.resize_with(len, || None);
124 file_id
125 }
126 fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
127 &self.data[file_id.0 as usize]
128 }
129 fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
130 &mut self.data[file_id.0 as usize]
131 }
132}
133
134impl fmt::Debug for Vfs {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()
137 }
138}