#[cfg(feature = "browser")]
pub mod browser;
#[cfg(feature = "system")]
pub mod system;
pub mod dummy;
pub mod notify;
pub mod overlay;
pub mod trace;
mod utils;
mod path_interner;
pub use typst::foundations::Bytes;
pub use typst::syntax::FileId as TypstFileId;
pub use reflexo::time::Time;
pub use reflexo::ImmutPath;
pub(crate) use path_interner::PathInterner;
use core::fmt;
use std::{
collections::HashMap,
hash::Hash,
path::Path,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use parking_lot::{Mutex, RwLock};
use reflexo::path::PathClean;
use typst::diag::{FileError, FileResult};
use self::{
notify::{FilesystemEvent, NotifyAccessModel},
overlay::OverlayAccessModel,
};
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct FileId(pub u32);
impl nohash_hasher::IsEnabled for FileId {}
pub trait AccessModel {
fn clear(&mut self) {}
fn mtime(&self, src: &Path) -> FileResult<Time>;
fn is_file(&self, src: &Path) -> FileResult<bool>;
fn real_path(&self, src: &Path) -> FileResult<ImmutPath>;
fn content(&self, src: &Path) -> FileResult<Bytes>;
}
#[derive(Clone)]
pub struct SharedAccessModel<M> {
pub inner: Arc<RwLock<M>>,
}
impl<M> SharedAccessModel<M> {
pub fn new(inner: M) -> Self {
Self {
inner: Arc::new(RwLock::new(inner)),
}
}
}
impl<M> AccessModel for SharedAccessModel<M>
where
M: AccessModel,
{
fn clear(&mut self) {
self.inner.write().clear();
}
fn mtime(&self, src: &Path) -> FileResult<Time> {
self.inner.read().mtime(src)
}
fn is_file(&self, src: &Path) -> FileResult<bool> {
self.inner.read().is_file(src)
}
fn real_path(&self, src: &Path) -> FileResult<ImmutPath> {
self.inner.read().real_path(src)
}
fn content(&self, src: &Path) -> FileResult<Bytes> {
self.inner.read().content(src)
}
}
type VfsAccessModel<M> = OverlayAccessModel<NotifyAccessModel<SharedAccessModel<M>>>;
pub trait FsProvider {
fn file_path(&self, id: FileId) -> ImmutPath;
fn mtime(&self, id: FileId) -> FileResult<Time>;
fn read(&self, id: FileId) -> FileResult<Bytes>;
fn is_file(&self, id: FileId) -> FileResult<bool>;
}
#[derive(Default)]
struct PathMapper {
clock: AtomicU64,
id_cache: RwLock<HashMap<ImmutPath, FileId>>,
intern: Mutex<PathInterner<ImmutPath, u64>>,
}
impl PathMapper {
pub fn reset(&self) {
self.clock.fetch_add(1, Ordering::SeqCst);
}
pub fn file_id(&self, path: &Path) -> FileId {
let quick_id = self.id_cache.read().get(path).copied();
if let Some(id) = quick_id {
return id;
}
let path: ImmutPath = path.clean().as_path().into();
let mut path_interner = self.intern.lock();
let lifetime_cnt = self.clock.load(Ordering::SeqCst);
let id = path_interner.intern(path.clone(), lifetime_cnt).0;
let mut path2slot = self.id_cache.write();
path2slot.insert(path.clone(), id);
id
}
pub fn file_path(&self, file_id: FileId) -> ImmutPath {
let path_interner = self.intern.lock();
path_interner.lookup(file_id).clone()
}
}
pub struct Vfs<M: AccessModel + Sized> {
paths: Arc<PathMapper>,
access_model: VfsAccessModel<M>,
}
impl<M: AccessModel + Sized> fmt::Debug for Vfs<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Vfs").finish()
}
}
impl<M: AccessModel + Clone + Sized> Vfs<M> {
pub fn snapshot(&self) -> Self {
Self {
paths: self.paths.clone(),
access_model: self.access_model.clone(),
}
}
}
impl<M: AccessModel + Sized> Vfs<M> {
pub fn new(access_model: M) -> Self {
let access_model = SharedAccessModel::new(access_model);
let access_model = NotifyAccessModel::new(access_model);
let access_model = OverlayAccessModel::new(access_model);
Self {
paths: Default::default(),
access_model,
}
}
pub fn reset(&mut self) {
self.paths.reset();
self.access_model.clear();
}
pub fn reset_shadow(&mut self) {
self.access_model.clear_shadow();
}
pub fn shadow_paths(&self) -> Vec<Arc<Path>> {
self.access_model.file_paths()
}
pub fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
self.access_model.add_file(path.into(), content);
Ok(())
}
pub fn remove_shadow(&mut self, path: &Path) {
self.access_model.remove_file(path);
}
pub fn notify_fs_event(&mut self, event: FilesystemEvent) {
self.access_model.inner.notify(event);
}
pub fn memory_usage(&self) -> usize {
0
}
pub fn file_id(&self, path: &Path) -> FileId {
self.paths.file_id(path)
}
pub fn read(&self, path: &Path) -> FileResult<Bytes> {
if self.access_model.is_file(path)? {
self.access_model.content(path)
} else {
Err(FileError::IsDirectory)
}
}
}
impl<M: AccessModel> FsProvider for Vfs<M> {
fn file_path(&self, id: FileId) -> ImmutPath {
self.paths.file_path(id)
}
fn mtime(&self, src: FileId) -> FileResult<Time> {
self.access_model.mtime(&self.file_path(src))
}
fn read(&self, src: FileId) -> FileResult<Bytes> {
self.access_model.content(&self.file_path(src))
}
fn is_file(&self, src: FileId) -> FileResult<bool> {
self.access_model.is_file(&self.file_path(src))
}
}
#[cfg(test)]
mod tests {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
#[test]
fn test_vfs_send_sync() {
is_send::<super::Vfs<super::dummy::DummyAccessModel>>();
is_sync::<super::Vfs<super::dummy::DummyAccessModel>>();
}
}