1#[cfg(feature = "browser")]
7pub mod browser;
8
9#[cfg(feature = "system")]
12pub mod system;
13
14pub mod dummy;
20
21pub mod snapshot;
23pub use snapshot::*;
24use tinymist_std::hash::{FxDashMap, FxHashMap};
25
26pub mod notify;
29pub use notify::{FilesystemEvent, MemoryEvent};
30pub mod overlay;
33pub mod resolve;
35pub mod trace;
37mod utils;
38
39mod path_mapper;
40pub use path_mapper::{PathResolution, RootResolver, WorkspaceResolution, WorkspaceResolver};
41
42use core::fmt;
43use std::num::NonZeroUsize;
44use std::sync::OnceLock;
45use std::{path::Path, sync::Arc};
46
47use ecow::EcoVec;
48use parking_lot::Mutex;
49use rpds::RedBlackTreeMapSync;
50use typst::diag::{FileError, FileResult};
51use typst::foundations::Dict;
52use typst::syntax::Source;
53use typst::utils::LazyHash;
54
55use crate::notify::NotifyAccessModel;
56use crate::overlay::OverlayAccessModel;
57use crate::resolve::ResolveAccessModel;
58
59pub use tinymist_std::ImmutPath;
60pub use tinymist_std::time::Time;
61pub use typst::foundations::Bytes;
62pub use typst::syntax::FileId;
63
64pub type ImmutDict = Arc<LazyHash<Dict>>;
66
67pub trait PathAccessModel {
72 fn reset(&mut self) {}
77
78 fn content(&self, src: &Path) -> FileResult<Bytes>;
80}
81
82pub trait AccessModel {
87 fn reset(&mut self) {}
92
93 fn content(&self, src: FileId) -> (Option<ImmutPath>, FileResult<Bytes>);
95}
96
97type VfsPathAccessModel<M> = OverlayAccessModel<ImmutPath, NotifyAccessModel<M>>;
98type VfsAccessModel<M> = OverlayAccessModel<FileId, ResolveAccessModel<VfsPathAccessModel<M>>>;
101
102pub trait FsProvider {
104 fn file_path(&self, id: FileId) -> FileResult<PathResolution>;
106 fn read(&self, id: FileId) -> FileResult<Bytes>;
108 fn read_source(&self, id: FileId) -> FileResult<Source>;
111}
112
113struct SourceEntry {
114 last_accessed_rev: NonZeroUsize,
115 source: FileResult<Source>,
116}
117
118#[derive(Default)]
119struct SourceIdShard {
120 last_accessed_rev: usize,
121 recent_source: Option<Source>,
122 sources: FxHashMap<Bytes, SourceEntry>,
123}
124
125#[derive(Default, Clone)]
127pub struct SourceCache {
128 cache_entries: Arc<FxDashMap<FileId, SourceIdShard>>,
130}
131
132impl SourceCache {
133 pub fn evict(&self, curr: NonZeroUsize, threshold: usize) {
136 self.cache_entries.retain(|_, shard| {
137 let diff = curr.get().saturating_sub(shard.last_accessed_rev);
138 if diff > threshold {
139 return false;
140 }
141
142 shard.sources.retain(|_, entry| {
143 let diff = curr.get().saturating_sub(entry.last_accessed_rev.get());
144 diff <= threshold
145 });
146
147 true
148 });
149 }
150}
151
152pub struct Vfs<M: PathAccessModel + Sized> {
156 source_cache: SourceCache,
157 managed: Arc<Mutex<EntryMap>>,
158 paths: Arc<Mutex<PathMap>>,
159 revision: NonZeroUsize,
160 access_model: VfsAccessModel<M>,
163}
164
165impl<M: PathAccessModel + Sized> fmt::Debug for Vfs<M> {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.debug_struct("Vfs")
168 .field("revision", &self.revision)
169 .field("managed", &self.managed.lock().entries.size())
170 .field("paths", &self.paths.lock().paths.len())
171 .finish()
172 }
173}
174
175impl<M: PathAccessModel + Clone + Sized> Vfs<M> {
176 pub fn revision(&self) -> NonZeroUsize {
178 self.revision
179 }
180
181 pub fn snapshot(&self) -> Self {
183 Self {
184 revision: self.revision,
185 source_cache: self.source_cache.clone(),
186 managed: self.managed.clone(),
187 paths: self.paths.clone(),
188 access_model: self.access_model.clone(),
189 }
190 }
191
192 pub fn fork(&self) -> Self {
194 Self {
195 source_cache: self.source_cache.clone(),
197 managed: Arc::new(Mutex::new(EntryMap::default())),
198 paths: Arc::new(Mutex::new(PathMap::default())),
199 revision: NonZeroUsize::new(2).expect("initial revision is 2"),
200 access_model: self.access_model.clone(),
201 }
202 }
203
204 pub fn is_clean_compile(&self, rev: usize, file_ids: &[FileId]) -> bool {
207 let mut m = self.managed.lock();
208 for id in file_ids {
209 let Some(entry) = m.entries.get_mut(id) else {
210 log::debug!("Vfs(dirty, {id:?}): file id not found");
211 return false;
212 };
213 if entry.changed_at > rev {
214 log::debug!("Vfs(dirty, {id:?}): rev {rev:?} => {:?}", entry.changed_at);
215 return false;
216 }
217 log::debug!(
218 "Vfs(clean, {id:?}, rev={rev}, changed_at={})",
219 entry.changed_at
220 );
221 }
222 true
223 }
224}
225
226impl<M: PathAccessModel + Sized> Vfs<M> {
227 pub fn new(resolver: Arc<dyn RootResolver + Send + Sync>, access_model: M) -> Self {
240 let access_model = NotifyAccessModel::new(access_model);
241 let access_model = OverlayAccessModel::new(access_model);
242 let access_model = ResolveAccessModel {
243 resolver,
244 inner: access_model,
245 };
246 let access_model = OverlayAccessModel::new(access_model);
247
248 Self {
252 source_cache: SourceCache::default(),
253 managed: Arc::default(),
254 paths: Arc::default(),
255 revision: NonZeroUsize::new(2).expect("initial revision is 2"),
256 access_model,
257 }
258 }
259
260 pub fn reset_all(&mut self) {
262 self.reset_access_model();
263 self.reset_read();
264 self.take_source_cache();
265 }
266
267 pub fn reset_access_model(&mut self) {
269 self.access_model.reset();
270 }
271
272 pub fn reset_read(&mut self) {
276 self.managed = Arc::default();
277 self.paths = Arc::default();
278 }
279
280 pub fn evict(&mut self, threshold: usize) {
282 let mut m = self.managed.lock();
283 let rev = self.revision.get();
284 for (id, entry) in m.entries.clone().iter() {
285 let entry_rev = entry.bytes.get().map(|e| e.1).unwrap_or_default();
286 if entry_rev + threshold < rev {
287 m.entries.remove_mut(id);
288 }
289 }
290 }
291
292 pub fn take_source_cache(&mut self) -> SourceCache {
294 std::mem::take(&mut self.source_cache)
295 }
296
297 pub fn clone_source_cache(&self) -> SourceCache {
299 self.source_cache.clone()
300 }
301
302 pub fn file_path(&self, id: FileId) -> Result<PathResolution, FileError> {
304 self.access_model.inner.resolver.path_for_id(id)
305 }
306
307 pub fn shadow_paths(&self) -> Vec<ImmutPath> {
309 self.access_model.inner.inner.file_paths()
310 }
311
312 pub fn shadow_ids(&self) -> Vec<FileId> {
317 self.access_model.file_paths()
318 }
319
320 pub fn memory_usage(&self) -> usize {
322 0
323 }
324
325 pub fn revise(&mut self) -> RevisingVfs<'_, M> {
328 let managed = self.managed.lock().clone();
329 let paths = self.paths.lock().clone();
330 let goal_revision = self.revision.checked_add(1).expect("revision overflowed");
331
332 RevisingVfs {
333 managed,
334 paths,
335 inner: self,
336 goal_revision,
337 view_changed: false,
338 }
339 }
340
341 pub fn display(&self) -> DisplayVfs<'_, M> {
343 DisplayVfs { inner: self }
344 }
345
346 pub fn read(&self, fid: FileId) -> FileResult<Bytes> {
348 let bytes = self.managed.lock().slot(fid, |entry| entry.bytes.clone());
349
350 self.read_content(&bytes, fid).clone()
351 }
352
353 pub fn source(&self, file_id: FileId) -> FileResult<Source> {
356 let (bytes, source) = self
357 .managed
358 .lock()
359 .slot(file_id, |entry| (entry.bytes.clone(), entry.source.clone()));
360
361 let source = source.get_or_init(|| {
362 let content = self
363 .read_content(&bytes, file_id)
364 .as_ref()
365 .map_err(Clone::clone)?;
366
367 let mut cache_entry = self.source_cache.cache_entries.entry(file_id).or_default();
368 if let Some(source) = cache_entry.sources.get(content) {
369 return source.source.clone();
370 }
371
372 let source = (|| {
373 let prev = cache_entry.recent_source.clone();
374 let content = from_utf8_or_bom(content).map_err(|_| FileError::InvalidUtf8)?;
375
376 let next = match prev {
377 Some(mut prev) => {
378 prev.replace(content);
379 prev
380 }
381 None => Source::new(file_id, content.to_owned()),
382 };
383
384 let should_update = cache_entry.recent_source.is_none()
385 || cache_entry.last_accessed_rev < self.revision.get();
386 if should_update {
387 cache_entry.recent_source = Some(next.clone());
388 }
389
390 Ok(next)
391 })();
392
393 let entry = cache_entry
394 .sources
395 .entry(content.clone())
396 .or_insert_with(|| SourceEntry {
397 last_accessed_rev: self.revision,
398 source: source.clone(),
399 });
400
401 if entry.last_accessed_rev < self.revision {
402 entry.last_accessed_rev = self.revision;
403 }
404
405 source
406 });
407
408 source.clone()
409 }
410
411 fn read_content<'a>(&self, bytes: &'a BytesQuery, fid: FileId) -> &'a FileResult<Bytes> {
413 &bytes
414 .get_or_init(|| {
415 let (path, content) = self.access_model.content(fid);
416 if let Some(path) = path.as_ref() {
417 self.paths.lock().insert(path, fid, self.revision);
418 }
419
420 (path, self.revision.get(), content)
421 })
422 .2
423 }
424}
425
426pub struct DisplayVfs<'a, M: PathAccessModel + Sized> {
428 inner: &'a Vfs<M>,
429}
430
431impl<M: PathAccessModel + Sized> fmt::Debug for DisplayVfs<'_, M> {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 f.debug_struct("Vfs")
434 .field("revision", &self.inner.revision)
435 .field("managed", &self.inner.managed.lock().display())
436 .field("paths", &self.inner.paths.lock().display())
437 .finish()
438 }
439}
440
441pub struct RevisingVfs<'a, M: PathAccessModel + Sized> {
443 inner: &'a mut Vfs<M>,
444 managed: EntryMap,
445 paths: PathMap,
446 goal_revision: NonZeroUsize,
447 view_changed: bool,
448}
449
450impl<M: PathAccessModel + Sized> Drop for RevisingVfs<'_, M> {
451 fn drop(&mut self) {
452 if self.view_changed {
453 self.inner.managed = Arc::new(Mutex::new(std::mem::take(&mut self.managed)));
454 self.inner.paths = Arc::new(Mutex::new(std::mem::take(&mut self.paths)));
455 let revision = &mut self.inner.revision;
456 *revision = self.goal_revision;
457 }
458 }
459}
460
461impl<M: PathAccessModel + Sized> RevisingVfs<'_, M> {
462 pub fn vfs(&mut self) -> &mut Vfs<M> {
464 self.inner
465 }
466
467 fn am(&mut self) -> &mut VfsAccessModel<M> {
468 &mut self.inner.access_model
469 }
470
471 fn invalidate_path(&mut self, path: &Path, snap: Option<&FileSnapshot>) {
472 if let Some(fids) = self.paths.get(path) {
473 if fids.is_empty() {
474 return;
475 }
476
477 self.view_changed = snap.is_none();
479 for fid in fids.clone() {
480 self.invalidate_file_id(fid, snap);
481 }
482 }
483 }
484
485 fn invalidate_file_id(&mut self, file_id: FileId, snap: Option<&FileSnapshot>) {
486 let mut changed = false;
487 self.managed.slot(file_id, |e| {
488 if let Some(snap) = snap {
489 let may_read_bytes = e.bytes.get().map(|b| &b.2);
490 match (snap, may_read_bytes) {
491 (FileSnapshot(Ok(snap)), Some(Ok(read))) if snap == read => {
492 return;
493 }
494 (FileSnapshot(Err(snap)), Some(Err(read))) if snap.as_ref() == read => {
495 return;
496 }
497 _ => {}
498 }
499 }
500
501 e.changed_at = self.goal_revision.get();
502 e.bytes = Arc::default();
503 e.source = Arc::default();
504 changed = true;
505 });
506 self.view_changed = changed;
507 }
508
509 pub fn reset_shadow(&mut self) {
511 for path in self.am().inner.inner.file_paths() {
512 self.invalidate_path(&path, None);
513 }
514 for fid in self.am().file_paths() {
515 self.invalidate_file_id(fid, None);
516 }
517
518 self.am().clear_shadow();
519 self.am().inner.inner.clear_shadow();
520 }
521
522 pub fn change_view(&mut self) -> FileResult<()> {
524 self.view_changed = true;
525 Ok(())
526 }
527
528 pub fn map_shadow(&mut self, path: &Path, snap: FileSnapshot) -> FileResult<()> {
530 self.invalidate_path(path, Some(&snap));
531 self.am().inner.inner.add_file(path, snap, |c| c.into());
532
533 Ok(())
534 }
535
536 pub fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
538 self.invalidate_path(path, None);
539 self.am().inner.inner.remove_file(path);
540
541 Ok(())
542 }
543
544 pub fn map_shadow_by_id(&mut self, file_id: FileId, snap: FileSnapshot) -> FileResult<()> {
546 self.invalidate_file_id(file_id, Some(&snap));
547 self.am().add_file(&file_id, snap, |c| *c);
548
549 Ok(())
550 }
551
552 pub fn remove_shadow_by_id(&mut self, file_id: FileId) {
554 self.invalidate_file_id(file_id, None);
555 self.am().remove_file(&file_id);
556 }
557
558 pub fn notify_fs_event(&mut self, event: FilesystemEvent) {
562 self.notify_fs_changes(event.split().0);
563 }
564 pub fn notify_fs_changes(&mut self, event: FileChangeSet) {
568 for path in &event.removes {
569 self.invalidate_path(path, None);
570 }
571 for (path, snap) in &event.inserts {
572 self.invalidate_path(path, Some(snap));
573 }
574
575 self.am().inner.inner.inner.notify(event);
576 }
577}
578
579type BytesQuery = Arc<OnceLock<(Option<ImmutPath>, usize, FileResult<Bytes>)>>;
580
581#[derive(Debug, Clone, Default)]
582struct VfsEntry {
583 changed_at: usize,
584 bytes: BytesQuery,
585 source: Arc<OnceLock<FileResult<Source>>>,
586}
587
588#[derive(Debug, Clone, Default)]
589struct EntryMap {
590 entries: RedBlackTreeMapSync<FileId, VfsEntry>,
591}
592
593impl EntryMap {
594 #[inline(always)]
596 fn slot<T>(&mut self, path: FileId, f: impl FnOnce(&mut VfsEntry) -> T) -> T {
597 if let Some(entry) = self.entries.get_mut(&path) {
598 f(entry)
599 } else {
600 let mut entry = VfsEntry::default();
601 let res = f(&mut entry);
602 self.entries.insert_mut(path, entry);
603 res
604 }
605 }
606
607 fn display(&self) -> DisplayEntryMap<'_> {
608 DisplayEntryMap { map: self }
609 }
610}
611
612pub struct DisplayEntryMap<'a> {
614 map: &'a EntryMap,
615}
616
617impl fmt::Debug for DisplayEntryMap<'_> {
618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619 f.debug_map().entries(self.map.entries.iter()).finish()
620 }
621}
622
623#[derive(Debug, Clone, Default)]
624struct PathMap {
625 paths: FxHashMap<ImmutPath, EcoVec<FileId>>,
626 file_ids: FxHashMap<FileId, (ImmutPath, NonZeroUsize)>,
627}
628
629impl PathMap {
630 fn insert(&mut self, next: &ImmutPath, fid: FileId, rev: NonZeroUsize) {
631 use std::collections::hash_map::Entry;
632 let rev_entry = self.file_ids.entry(fid);
633
634 match rev_entry {
635 Entry::Occupied(mut entry) => {
636 let (prev, prev_rev) = entry.get_mut();
637 if prev != next {
638 if *prev_rev == rev {
639 log::warn!("Vfs: {fid:?} is changed in rev({rev:?}), {prev:?} -> {next:?}");
640 }
641
642 if let Some(fids) = self.paths.get_mut(prev) {
643 fids.retain(|f| *f != fid);
644 }
645
646 *prev = next.clone();
647 *prev_rev = rev;
648
649 self.paths.entry(next.clone()).or_default().push(fid);
650 }
651 }
652 Entry::Vacant(entry) => {
653 entry.insert((next.clone(), rev));
654 self.paths.entry(next.clone()).or_default().push(fid);
655 }
656 }
657 }
658
659 fn get(&mut self, path: &Path) -> Option<&EcoVec<FileId>> {
660 self.paths.get(path)
661 }
662
663 fn display(&self) -> DisplayPathMap<'_> {
664 DisplayPathMap { map: self }
665 }
666}
667
668pub struct DisplayPathMap<'a> {
670 map: &'a PathMap,
671}
672
673impl fmt::Debug for DisplayPathMap<'_> {
674 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675 f.debug_map().entries(self.map.paths.iter()).finish()
676 }
677}
678
679fn from_utf8_or_bom(buf: &[u8]) -> FileResult<&str> {
681 Ok(std::str::from_utf8(if buf.starts_with(b"\xef\xbb\xbf") {
682 &buf[3..]
684 } else {
685 buf
687 })?)
688}
689
690#[cfg(test)]
691mod tests {
692 fn is_send<T: Send>() {}
693 fn is_sync<T: Sync>() {}
694
695 #[test]
696 fn test_vfs_send_sync() {
697 is_send::<super::Vfs<super::dummy::DummyAccessModel>>();
698 is_sync::<super::Vfs<super::dummy::DummyAccessModel>>();
699 }
700}