1#[cfg(feature = "browser")]
7pub mod browser;
8
9#[cfg(feature = "system")]
12pub mod system;
13
14pub mod dummy;
20pub mod notify;
23pub mod overlay;
26pub mod trace;
28mod utils;
29
30mod path_interner;
31
32pub use typst::foundations::Bytes;
33pub use typst::syntax::FileId as TypstFileId;
34
35pub use reflexo::time::Time;
36pub use reflexo::ImmutPath;
37
38pub(crate) use path_interner::PathInterner;
39
40use core::fmt;
41use std::{
42 collections::HashMap,
43 hash::Hash,
44 path::Path,
45 sync::{
46 atomic::{AtomicU64, Ordering},
47 Arc,
48 },
49};
50
51use parking_lot::{Mutex, RwLock};
52use reflexo::path::PathClean;
53use typst::diag::{FileError, FileResult};
54
55use self::{
56 notify::{FilesystemEvent, NotifyAccessModel},
57 overlay::OverlayAccessModel,
58};
59
60#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
64pub struct FileId(pub u32);
65
66impl nohash_hasher::IsEnabled for FileId {}
68
69pub trait AccessModel {
74 fn clear(&mut self) {}
79 fn mtime(&self, src: &Path) -> FileResult<Time>;
84
85 fn is_file(&self, src: &Path) -> FileResult<bool>;
87
88 fn real_path(&self, src: &Path) -> FileResult<ImmutPath>;
93
94 fn content(&self, src: &Path) -> FileResult<Bytes>;
96}
97
98#[derive(Clone)]
99pub struct SharedAccessModel<M> {
100 pub inner: Arc<RwLock<M>>,
101}
102
103impl<M> SharedAccessModel<M> {
104 pub fn new(inner: M) -> Self {
105 Self {
106 inner: Arc::new(RwLock::new(inner)),
107 }
108 }
109}
110
111impl<M> AccessModel for SharedAccessModel<M>
112where
113 M: AccessModel,
114{
115 fn clear(&mut self) {
116 self.inner.write().clear();
117 }
118
119 fn mtime(&self, src: &Path) -> FileResult<Time> {
120 self.inner.read().mtime(src)
121 }
122
123 fn is_file(&self, src: &Path) -> FileResult<bool> {
124 self.inner.read().is_file(src)
125 }
126
127 fn real_path(&self, src: &Path) -> FileResult<ImmutPath> {
128 self.inner.read().real_path(src)
129 }
130
131 fn content(&self, src: &Path) -> FileResult<Bytes> {
132 self.inner.read().content(src)
133 }
134}
135
136type VfsAccessModel<M> = OverlayAccessModel<NotifyAccessModel<SharedAccessModel<M>>>;
139
140pub trait FsProvider {
141 fn file_path(&self, id: FileId) -> ImmutPath;
143
144 fn mtime(&self, id: FileId) -> FileResult<Time>;
145
146 fn read(&self, id: FileId) -> FileResult<Bytes>;
147
148 fn is_file(&self, id: FileId) -> FileResult<bool>;
149}
150
151#[derive(Default)]
152struct PathMapper {
153 clock: AtomicU64,
157 id_cache: RwLock<HashMap<ImmutPath, FileId>>,
162 intern: Mutex<PathInterner<ImmutPath, u64>>,
164}
165
166impl PathMapper {
167 pub fn reset(&self) {
175 self.clock.fetch_add(1, Ordering::SeqCst);
176
177 }
184
185 pub fn file_id(&self, path: &Path) -> FileId {
187 let quick_id = self.id_cache.read().get(path).copied();
188 if let Some(id) = quick_id {
189 return id;
190 }
191
192 let path: ImmutPath = path.clean().as_path().into();
193
194 let mut path_interner = self.intern.lock();
195 let lifetime_cnt = self.clock.load(Ordering::SeqCst);
196 let id = path_interner.intern(path.clone(), lifetime_cnt).0;
197
198 let mut path2slot = self.id_cache.write();
199 path2slot.insert(path.clone(), id);
200
201 id
202 }
203
204 pub fn file_path(&self, file_id: FileId) -> ImmutPath {
206 let path_interner = self.intern.lock();
207 path_interner.lookup(file_id).clone()
208 }
209}
210
211pub struct Vfs<M: AccessModel + Sized> {
215 paths: Arc<PathMapper>,
216
217 access_model: VfsAccessModel<M>,
220}
221
222impl<M: AccessModel + Sized> fmt::Debug for Vfs<M> {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.debug_struct("Vfs").finish()
225 }
226}
227
228impl<M: AccessModel + Clone + Sized> Vfs<M> {
229 pub fn snapshot(&self) -> Self {
230 Self {
231 paths: self.paths.clone(),
232 access_model: self.access_model.clone(),
233 }
234 }
235}
236
237impl<M: AccessModel + Sized> Vfs<M> {
238 pub fn new(access_model: M) -> Self {
251 let access_model = SharedAccessModel::new(access_model);
252 let access_model = NotifyAccessModel::new(access_model);
253 let access_model = OverlayAccessModel::new(access_model);
254
255 Self {
259 paths: Default::default(),
260 access_model,
261 }
262 }
263
264 pub fn reset(&mut self) {
272 self.paths.reset();
273 self.access_model.clear();
274 }
275
276 pub fn reset_shadow(&mut self) {
280 self.access_model.clear_shadow();
281 }
282
283 pub fn shadow_paths(&self) -> Vec<Arc<Path>> {
285 self.access_model.file_paths()
286 }
287
288 pub fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
290 self.access_model.add_file(path.into(), content);
291
292 Ok(())
293 }
294
295 pub fn remove_shadow(&mut self, path: &Path) {
297 self.access_model.remove_file(path);
298 }
299
300 pub fn notify_fs_event(&mut self, event: FilesystemEvent) {
304 self.access_model.inner.notify(event);
305 }
306
307 pub fn memory_usage(&self) -> usize {
309 0
310 }
311
312 pub fn file_id(&self, path: &Path) -> FileId {
314 self.paths.file_id(path)
315 }
316
317 pub fn read(&self, path: &Path) -> FileResult<Bytes> {
319 if self.access_model.is_file(path)? {
320 self.access_model.content(path)
321 } else {
322 Err(FileError::IsDirectory)
323 }
324 }
325}
326
327impl<M: AccessModel> FsProvider for Vfs<M> {
328 fn file_path(&self, id: FileId) -> ImmutPath {
329 self.paths.file_path(id)
330 }
331
332 fn mtime(&self, src: FileId) -> FileResult<Time> {
333 self.access_model.mtime(&self.file_path(src))
334 }
335
336 fn read(&self, src: FileId) -> FileResult<Bytes> {
337 self.access_model.content(&self.file_path(src))
338 }
339
340 fn is_file(&self, src: FileId) -> FileResult<bool> {
341 self.access_model.is_file(&self.file_path(src))
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 fn is_send<T: Send>() {}
348 fn is_sync<T: Sync>() {}
349
350 #[test]
351 fn test_vfs_send_sync() {
352 is_send::<super::Vfs<super::dummy::DummyAccessModel>>();
353 is_sync::<super::Vfs<super::dummy::DummyAccessModel>>();
354 }
355}