diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index c117ffc6..ca46c311 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -23,7 +23,15 @@ //! - [`service::CompileDriver`]: A driver for the compiler. Examples: //! - Single thread (Sync): //! - Multiple thread (Async): + // #![warn(missing_docs)] +// #![warn(clippy::missing_docs_in_private_items)] +// #![warn(clippy::missing_errors_doc)] +// #![warn(clippy::missing_panics_doc)] +// #![warn(clippy::missing_safety_doc)] +// #![warn(clippy::undocumented_unsafe_blocks)] +// #![warn(missing_crate_level_docs)] +// #![warn(clippy::host_endian_bytes)] // #![warn(missing_debug_implementations)] // #![warn(missing_copy_implementations)] diff --git a/compiler/src/vfs/browser.rs b/compiler/src/vfs/browser.rs index 87a09c54..f0ea24fd 100644 --- a/compiler/src/vfs/browser.rs +++ b/compiler/src/vfs/browser.rs @@ -9,12 +9,20 @@ use crate::time::SystemTime; use super::AccessModel; +/// Provides proxy access model from typst compiler to some JavaScript +/// implementation. #[derive(Debug)] pub struct ProxyAccessModel { + /// The `this` value when calling the JavaScript functions pub context: JsValue, + /// The JavaScript function to get the mtime of a file pub mtime_fn: js_sys::Function, + /// The JavaScript function to check if a path corresponds to a file or a + /// directory pub is_file_fn: js_sys::Function, + /// The JavaScript function to get the real path of a file pub real_path_fn: js_sys::Function, + /// The JavaScript function to get the content of a file pub read_all_fn: js_sys::Function, } diff --git a/compiler/src/vfs/cached.rs b/compiler/src/vfs/cached.rs index 5910a9a2..5bf2154d 100644 --- a/compiler/src/vfs/cached.rs +++ b/compiler/src/vfs/cached.rs @@ -12,35 +12,51 @@ use super::AccessModel; /// incrementally query a value from a self holding state type IncrQueryRef = QueryRef>; +/// Holds the cached data of a single file #[derive(Debug)] -pub struct FileCache { - lifetime_cnt: usize, +pub struct CacheEntry { + /// The last lifetime count when the cache is updated + last_access_lifetime: usize, + /// The cached mtime of the file mtime: SystemTime, + /// Whether the file is a file, lazily triggered when mtime is changed is_file: QueryRef, + /// The content of the file, lazily triggered when mtime is changed read_all: QueryRef, + /// The incremental state of the source, lazily triggered when mtime is + /// changed source_state: IncrQueryRef, } +/// Provides general cache to file access. #[derive(Debug)] pub struct CachedAccessModel { + /// The underlying access model for real file access inner: Inner, + /// The lifetime count which resembles [`crate::vfs::Vfs::lifetime_cnt`] + /// + /// Note: The lifetime counter is incremented on resetting vfs. lifetime_cnt: usize, - path_results: RwLock, FileCache>>, + /// The cache entries for each paths + cache_entries: RwLock, CacheEntry>>, } impl CachedAccessModel { + /// Create a new [`CachedAccessModel`] with the given inner access model pub fn new(inner: Inner) -> Self { CachedAccessModel { inner, - lifetime_cnt: 0, - path_results: RwLock::new(HashMap::new()), + lifetime_cnt: 1, + cache_entries: RwLock::new(HashMap::new()), } } + /// Get the inner access model pub fn inner(&self) -> &Inner { &self.inner } + /// Get the mutable reference to the inner access model pub fn inner_mut(&mut self) -> &mut Inner { &mut self.inner } @@ -54,13 +70,13 @@ impl CachedAccessModel { fn cache_entry( &self, src: &Path, - cb: impl FnOnce(&FileCache) -> FileResult, + cb: impl FnOnce(&CacheEntry) -> FileResult, ) -> FileResult { let path_key = src.as_os_str(); - let path_results = self.path_results.upgradable_read(); + let path_results = self.cache_entries.upgradable_read(); let entry = path_results.get(path_key); let (new_mtime, prev_to_diff) = if let Some(entry) = entry { - if entry.lifetime_cnt == self.lifetime_cnt { + if entry.last_access_lifetime == self.lifetime_cnt { return cb(entry); } @@ -85,8 +101,8 @@ impl CachedAccessModel { path_results.insert( path_key.into(), - FileCache { - lifetime_cnt: self.lifetime_cnt, + CacheEntry { + last_access_lifetime: self.lifetime_cnt, mtime: new_mtime, is_file: QueryRef::default(), read_all: QueryRef::default(), @@ -95,12 +111,14 @@ impl CachedAccessModel { ); drop(path_results); - let path_results = self.path_results.read(); + let path_results = self.cache_entries.read(); cb(path_results.get(path_key).unwrap()) } } impl CachedAccessModel { + /// This is not a common interface for access model, but it is used for vfs + /// incremental parsing. pub fn read_all_diff( &self, src: &Path, @@ -127,9 +145,9 @@ impl AccessModel for CachedAccessModel { fn clear(&mut self) { self.lifetime_cnt += 1; - let mut path_results = self.path_results.write(); + let mut path_results = self.cache_entries.write(); let new_lifetime = self.lifetime_cnt; - path_results.retain(|_, v| new_lifetime - v.lifetime_cnt <= 30); + path_results.retain(|_, v| new_lifetime - v.last_access_lifetime <= 30); } fn mtime(&self, src: &Path) -> FileResult { diff --git a/compiler/src/vfs/dummy.rs b/compiler/src/vfs/dummy.rs index d4f4f597..45c88beb 100644 --- a/compiler/src/vfs/dummy.rs +++ b/compiler/src/vfs/dummy.rs @@ -8,6 +8,11 @@ use crate::time::SystemTime; use super::AccessModel; +/// Provides dummy access model. +/// +/// Note: we can still perform compilation with dummy access model, since +/// [`Vfs`] will make a overlay access model over the provided dummy access +/// model. #[derive(Default, Debug, Clone, Copy)] pub struct DummyAccessModel; diff --git a/compiler/src/vfs/mod.rs b/compiler/src/vfs/mod.rs index 46b8affb..02dfeb8c 100644 --- a/compiler/src/vfs/mod.rs +++ b/compiler/src/vfs/mod.rs @@ -1,19 +1,31 @@ //! upstream of following files //! ::path_interner.rs -> path_interner.rs -//! ::paths.rs -> abs_path.rs -//! ::anchored_path.rs -> path_anchored.rs -//! ::vfs_path.rs -> path_vfs.rs +/// Provides ProxyAccessModel that makes access to JavaScript objects for +/// browser compilation. #[cfg(feature = "browser-compile")] pub mod browser; +/// Provides SystemAccessModel that makes access to the local file system for +/// system compilation. #[cfg(feature = "system-compile")] pub mod system; +/// Provides general cache to file access. pub mod cached; +/// Provides dummy access model. +/// +/// Note: we can still perform compilation with dummy access model, since +/// [`Vfs`] will make a overlay access model over the provided dummy access +/// model. pub mod dummy; +/// Provides notify access model which retrieves file system events and changes +/// from some notify backend. pub mod notify; +/// Provides overlay access model which allows to shadow the underlying access +/// model with memory contents. pub mod overlay; +/// Provides trace access model which traces the underlying access model. pub mod trace; mod path_interner; @@ -42,24 +54,44 @@ use self::{ /// Handle to a file in [`Vfs`] /// -/// Most functions in rust-analyzer use this when they need to refer to a file. +/// Most functions in typst-ts use this when they need to refer to a file. #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FileId(pub u32); /// safe because `FileId` is a newtype of `u32` impl nohash_hasher::IsEnabled for FileId {} +/// A trait for accessing underlying file system. +/// +/// This trait is simplified by [`Vfs`] and requires a minimal method set for +/// typst compilation. pub trait AccessModel { + /// The real path type for the underlying file system. + /// This type is used for canonicalizing paths. type RealPath: Hash + Eq + PartialEq + for<'a> From<&'a Path>; + /// Clear the cache of the access model. + /// + /// This is called when the vfs is reset. See [`Vfs`]'s reset method for + /// more information. fn clear(&mut self) {} + /// Return a mtime corresponding to the path. + /// + /// Note: vfs won't touch the file entry if mtime is same between vfs reset + /// lifecycles for performance design. fn mtime(&self, src: &Path) -> FileResult; + /// Return whether a path is corresponding to a file. fn is_file(&self, src: &Path) -> FileResult; + /// Return the real path before creating a vfs file entry. + /// + /// Note: vfs will fetch the file entry once if multiple paths shares a same + /// real path. fn real_path(&self, src: &Path) -> FileResult; + /// Return the content of a file entry. fn content(&self, src: &Path) -> FileResult; } @@ -76,7 +108,8 @@ pub struct PathSlot { } impl PathSlot { - pub fn new(idx: FileId) -> Self { + /// Create a new slot with a given local file id from [`PathInterner`]. + fn new(idx: FileId) -> Self { PathSlot { idx, sampled_path: once_cell::sync::OnceCell::new(), @@ -87,18 +120,36 @@ impl PathSlot { } } +/// we add notify access model here since notify access model doesn't introduce +/// overheads by our observation type VfsAccessModel = CachedAccessModel>, Source>; +/// Create a new `Vfs` harnessing over the given `access_model` specific for +/// [`crate::world::CompilerWorld`]. With vfs, we can minimize the +/// implementation overhead for [`AccessModel`] trait. pub struct Vfs { + /// The number of lifecycles since the creation of the `Vfs`. + /// + /// Note: The lifetime counter is incremented on resetting vfs. lifetime_cnt: u64, + // access_model: TraceAccessModel>, - // we add notify access model here since notify access model doesn't introduce overheads + /// The wrapped access model. access_model: VfsAccessModel, + /// The path interner for canonical paths. path_interner: Mutex::RealPath, u64>>, + /// Map from path to slot index. + /// + /// Note: we use a owned [`FileId`] here, which is resultant from + /// [`PathInterner`] path2slot: RwLock, FileId>>, + /// Map from typst global file id to a local file id. src2file_id: RwLock>, + /// The slots for all the files during a single lifecycle. pub slots: AppendOnlyVec, + /// Whether to reparse the file when it is changed. + /// Default to `true`. pub do_reparse: bool, } @@ -115,10 +166,26 @@ impl fmt::Debug for Vfs { } impl Vfs { + /// Create a new `Vfs` with a given `access_model`. + /// + /// Retrieving an [`AccessModel`], it will further wrap the access model + /// with [`CachedAccessModel`], [`OverlayAccessModel`], and + /// [`NotifyAccessModel`]. This means that you don't need to implement: + /// + cache: caches underlying access result for a single vfs lifecycle, + /// typically also corresponds to a single compilation. + /// + overlay: allowing to shadow the underlying access model with memory + /// contents, which is useful for a limited execution environment and + /// instrumenting or overriding source files or packages. + /// + notify: regards problems of synchronizing with the file system when + /// the vfs is watching the file system. + /// + /// See [`AccessModel`] for more information. pub fn new(access_model: M) -> Self { let access_model = NotifyAccessModel::new(access_model); let access_model = OverlayAccessModel::new(access_model); let access_model = CachedAccessModel::new(access_model); + + // If you want to trace the access model, uncomment the following line // let access_model = TraceAccessModel::new(access_model); Self { @@ -132,28 +199,65 @@ impl Vfs { } } - /// Reset the source manager. + /// Reset the source file and path references. + /// + /// It performs a rolling reset, with discard some cache file entry when it + /// is unused in recent 30 lifecycles. + /// + /// Note: The lifetime counter is incremented every time this function is + /// called. pub fn reset(&mut self) { self.lifetime_cnt += 1; let new_lifetime_cnt = self.lifetime_cnt; + self.slots = AppendOnlyVec::new(); self.path2slot.get_mut().clear(); self.src2file_id.get_mut().clear(); + self.path_interner .get_mut() .retain(|_, lifetime| new_lifetime_cnt - *lifetime <= 30); + self.access_model.clear(); } + /// Reset the shadowing files in [`OverlayAccessModel`]. + /// + /// Note: This function is independent from [`Vfs::reset`]. pub fn reset_shadow(&mut self) { self.access_model.inner().clear_shadow(); } + /// Get paths to all the shadowing files in [`OverlayAccessModel`]. pub fn shadow_paths(&self) -> Vec> { self.access_model.inner().file_paths() } - /// Set the `do_reparse` flag. + /// Add a shadowing file to the [`OverlayAccessModel`]. + pub fn map_shadow(&self, path: &Path, content: Bytes) -> FileResult<()> { + self.access_model.inner().add_file(path.into(), content); + + Ok(()) + } + + /// Remove a shadowing file from the [`OverlayAccessModel`]. + pub fn remove_shadow(&self, path: &Path) { + self.access_model.inner().remove_file(path); + } + + /// Let the vfs notify the access model with a filesystem event. + /// + /// See [`NotifyAccessModel`] for more information. + pub fn notify_fs_event(&mut self, event: FilesystemEvent) { + self.access_model.inner_mut().inner_mut().notify(event); + } + + /// Set the `do_reparse` flag that indicates whether to reparsing the file + /// instead of creating a new [`Source`] when the file is changed. + /// Default to `true`. + /// + /// You usually want to set this flag to `true` for better performance. + /// However, one could disable this flag for debugging purpose. pub fn set_do_reparse(&mut self, do_reparse: bool) { self.do_reparse = do_reparse; } @@ -169,15 +273,22 @@ impl Vfs { self.path2slot.read().get(path.as_os_str()).copied() } - /// Check whether a path is related to a source. - /// Note: this does not check the canonical path, but only the normalized - /// one. - pub fn dependant(&self, path: &Path) -> bool { - let path = path.clean(); - self.path2slot.read().contains_key(path.as_os_str()) + /// File path corresponding to the given `file_id`. + /// + /// # Panics + /// + /// Panics if the id is not present in the `Vfs`. + pub fn file_path(&self, file_id: FileId) -> &Path { + self.slots[file_id.0 as usize].sampled_path.get().unwrap() } - /// Get all the files in the VFS. + /// Get all the files that are currently in the VFS. + /// + /// This is typically corresponds to the file dependencies of a single + /// compilation. + /// + /// When you don't reset the vfs for each compilation, this function will + /// still return remaining files from the previous compilation. pub fn iter_dependencies(&self) -> impl Iterator { self.slots.iter().map(|slot| { let dep_path = slot.sampled_path.get().unwrap(); @@ -190,7 +301,8 @@ impl Vfs { }) } - /// Get all the files in the VFS. + /// Get all the files that are currently in the VFS. This function is + /// similar to [`Vfs::iter_dependencies`], but it is for trait objects. pub fn iter_dependencies_dyn<'a>( &'a self, f: &mut dyn FnMut(&'a ImmutPath, instant::SystemTime), @@ -210,15 +322,6 @@ impl Vfs { } } - /// File path corresponding to the given `file_id`. - /// - /// # Panics - /// - /// Panics if the id is not present in the `Vfs`. - pub fn file_path(&self, file_id: FileId) -> &Path { - self.slots[file_id.0 as usize].sampled_path.get().unwrap() - } - /// Read a file. fn read(&self, path: &Path) -> FileResult { if self.access_model.is_file(path)? { @@ -228,12 +331,45 @@ impl Vfs { } } + /// Get file content by path. + pub fn file(&self, path: &Path) -> FileResult { + let slot = self.slot(path)?; + + let buffer = slot.buffer.compute(|| self.read(path))?; + Ok(buffer.clone()) + } + + /// Get source content by path and assign the source with a given typst + /// global file id. + /// + /// See [`Vfs::resolve_with_f`] for more information. + pub fn resolve(&self, path: &Path, source_id: TypstFileId) -> FileResult { + self.resolve_with_f(path, source_id, || { + // Return a new source if we don't have a reparse feature + if !self.do_reparse { + let content = self.read(path)?; + let content = from_utf8_or_bom(&content)?.to_owned(); + let res = Ok(Source::new(source_id, content)); + + return res; + } + + // otherwise reparse the source + if self.access_model.is_file(path)? { + Ok(self + .access_model + .read_all_diff(path, |x, y| reparse(source_id, x, y))?) + } else { + Err(FileError::IsDirectory) + } + }) + } + /// Get or insert a slot for a path. All paths pointing to the same entity /// will share the same slot. /// /// - If `path` does not exists in the `Vfs`, allocate a new id for it, - /// associated with a - /// deleted file; + /// associated with a deleted file; /// - Else, returns `path`'s id. /// /// Does not record a change. @@ -252,7 +388,7 @@ impl Vfs { Ok(&self.slots[idx]) } - /// Insert a new source into the source manager. + /// Insert a new slot into the vfs. fn slot(&self, origin_path: &Path) -> FileResult<&PathSlot> { // fast path for already inserted paths let path2slot = self.path2slot.upgradable_read(); @@ -282,29 +418,11 @@ impl Vfs { Ok(slot) } - /// Get source by id. - pub fn source(&self, file_id: TypstFileId) -> FileResult { - let f = *self.src2file_id.read().get(&file_id).ok_or_else(|| { - FileError::NotFound({ - // Path with package name - let path_repr = file_id - .package() - .and_then(|pkg| file_id.vpath().resolve(Path::new(&pkg.to_string()))); - - // Path without package name - path_repr.unwrap_or_else(|| file_id.vpath().as_rootless_path().to_owned()) - }) - })?; - - self.slots[f.0 as usize] - .source - // the value should be computed - .compute_ref(|| Err(other_reason("vfs: not computed source"))) - .cloned() - } - - /// Get source id by path. - /// This function will not check whether the path exists. + /// Get source content by path with a read content implmentation. + /// + /// Note: This function will also do eager check that whether the path + /// exists in the underlying access model. So the read content function + /// won't be triggered if the path doesn't exist. fn resolve_with_f FileResult>( &self, path: &Path, @@ -320,52 +438,9 @@ impl Vfs { }) .map(|e| e.clone()) } - - /// Get source id by path with filesystem content. - pub fn resolve(&self, path: &Path, source_id: TypstFileId) -> FileResult { - self.resolve_with_f(path, source_id, || { - if !self.do_reparse { - let instant = instant::Instant::now(); - - let content = self.read(path)?; - let content = from_utf8_or_bom(&content)?.to_owned(); - let res = Ok(Source::new(source_id, content)); - - println!("parse: {:?} {:?}", path, instant.elapsed()); - return res; - } - if self.access_model.is_file(path)? { - Ok(self - .access_model - .read_all_diff(path, |x, y| reparse(source_id, x, y))?) - } else { - Err(FileError::IsDirectory) - } - }) - } - - pub fn map_shadow(&self, path: &Path, content: Bytes) -> FileResult<()> { - self.access_model.inner().add_file(path.into(), content); - - Ok(()) - } - - pub fn remove_shadow(&self, path: &Path) { - self.access_model.inner().remove_file(path); - } - - pub fn notify_fs_event(&mut self, event: FilesystemEvent) { - self.access_model.inner_mut().inner_mut().notify(event); - } - - pub fn file(&self, path: &Path) -> FileResult { - let slot = self.slot(path)?; - - let buffer = slot.buffer.compute(|| self.read(path))?; - Ok(buffer.clone()) - } } +/// Convert a byte slice to a string, removing UTF-8 BOM if present. fn from_utf8_or_bom(buf: &[u8]) -> FileResult<&str> { Ok(std::str::from_utf8(if buf.starts_with(b"\xef\xbb\xbf") { // remove UTF-8 BOM @@ -376,6 +451,7 @@ fn from_utf8_or_bom(buf: &[u8]) -> FileResult<&str> { })?) } +/// Create a [`FileError`] with a given error message. fn other_reason(err: &str) -> FileError { FileError::Other(Some(err.into())) } diff --git a/compiler/src/vfs/notify.rs b/compiler/src/vfs/notify.rs index 2867cc8d..81545f98 100644 --- a/compiler/src/vfs/notify.rs +++ b/compiler/src/vfs/notify.rs @@ -162,6 +162,7 @@ pub enum FilesystemEvent { UpstreamUpdate { /// New changeset produced by invalidation changeset: FileChangeSet, + /// The upstream event that causes the invalidation upstream_event: Option, }, } @@ -196,11 +197,16 @@ pub enum NotifyMessage { UpstreamUpdate(UpstreamUpdateEvent), } -/// Notify shadowing access model, which the typical underlying access model is +/// Provides notify access model which retrieves file system events and changes +/// from some notify backend. +/// +/// It simply hold notified filesystem data in memory, but still have a fallback +/// access model, whose the typical underlying access model is /// [`crate::vfs::system::SystemAccessModel`] #[derive(Debug)] pub struct NotifyAccessModel { files: HashMap, + /// The fallback access model when the file is not notified ever. pub inner: M, } diff --git a/compiler/src/vfs/overlay.rs b/compiler/src/vfs/overlay.rs index c2b7c211..fd55d9f6 100644 --- a/compiler/src/vfs/overlay.rs +++ b/compiler/src/vfs/overlay.rs @@ -16,36 +16,45 @@ struct OverlayFileMeta { content: Bytes, } +/// Provides overlay access model which allows to shadow the underlying access +/// model with memory contents. #[derive(Default, Debug)] pub struct OverlayAccessModel { files: RwLock, OverlayFileMeta>>, - pub model: M, + /// The underlying access model + pub inner: M, } impl OverlayAccessModel { - pub fn new(model: M) -> Self { + /// Create a new [`OverlayAccessModel`] with the given inner access model + pub fn new(inner: M) -> Self { Self { files: RwLock::new(HashMap::new()), - model, + inner, } } + /// Get the inner access model pub fn inner(&self) -> &M { - &self.model + &self.inner } + /// Get the mutable reference to the inner access model pub fn inner_mut(&mut self) -> &mut M { - &mut self.model + &mut self.inner } + /// Clear the shadowed files pub fn clear_shadow(&self) { self.files.write().clear(); } + /// Get the shadowed file paths pub fn file_paths(&self) -> Vec> { self.files.read().keys().cloned().collect() } + /// Add a shadow file to the [`OverlayAccessModel`] pub fn add_file(&self, path: Arc, content: Bytes) { // we change mt every time, since content almost changes every time // Note: we can still benefit from cache, since we incrementally parse source @@ -75,6 +84,7 @@ impl OverlayAccessModel { .or_insert(meta); } + /// Remove a shadow file from the [`OverlayAccessModel`] pub fn remove_file(&self, path: &Path) { self.files.write().remove(path); } @@ -88,7 +98,7 @@ impl AccessModel for OverlayAccessModel { return Ok(meta.mt); } - self.model.mtime(src) + self.inner.mtime(src) } fn is_file(&self, src: &Path) -> FileResult { @@ -96,7 +106,7 @@ impl AccessModel for OverlayAccessModel { return Ok(true); } - self.model.is_file(src) + self.inner.is_file(src) } fn real_path(&self, src: &Path) -> FileResult { @@ -104,7 +114,7 @@ impl AccessModel for OverlayAccessModel { return Ok(src.into()); } - self.model.real_path(src) + self.inner.real_path(src) } fn content(&self, src: &Path) -> FileResult { @@ -112,6 +122,6 @@ impl AccessModel for OverlayAccessModel { return Ok(meta.content.clone()); } - self.model.content(src) + self.inner.content(src) } } diff --git a/compiler/src/vfs/system.rs b/compiler/src/vfs/system.rs index 46060485..8d1fa2d4 100644 --- a/compiler/src/vfs/system.rs +++ b/compiler/src/vfs/system.rs @@ -10,37 +10,8 @@ use typst_ts_core::{Bytes, ReadAllOnce}; use super::AccessModel; -#[derive(Debug)] -pub struct LazyFile { - path: std::path::PathBuf, - file: Option>, -} - -impl LazyFile { - pub fn new(path: std::path::PathBuf) -> Self { - Self { path, file: None } - } -} - -impl ReadAllOnce for LazyFile { - fn read_all(mut self, buf: &mut Vec) -> std::io::Result { - let file = self.file.get_or_insert_with(|| File::open(&self.path)); - let Ok(ref mut file) = file else { - let err = file.as_ref().unwrap_err(); - // todo: clone error or hide error - return Err(std::io::Error::new(err.kind(), err.to_string())); - }; - - file.read_to_end(buf) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct SystemFileMeta { - mt: std::time::SystemTime, - is_file: bool, -} - +/// Provides SystemAccessModel that makes access to the local file system for +/// system compilation. #[derive(Debug, Clone, Copy)] pub struct SystemAccessModel; @@ -81,3 +52,39 @@ impl AccessModel for SystemAccessModel { Ok(buf.into()) } } + +/// Lazily opened file entry corresponding to a file in the local file system. +/// +/// This is used by font loading instead of the [`SystemAccessModel`]. +#[derive(Debug)] +pub struct LazyFile { + path: std::path::PathBuf, + file: Option>, +} + +impl LazyFile { + /// Create a new [`LazyFile`] with the given path. + pub fn new(path: std::path::PathBuf) -> Self { + Self { path, file: None } + } +} + +impl ReadAllOnce for LazyFile { + fn read_all(mut self, buf: &mut Vec) -> std::io::Result { + let file = self.file.get_or_insert_with(|| File::open(&self.path)); + let Ok(ref mut file) = file else { + let err = file.as_ref().unwrap_err(); + // todo: clone error or hide error + return Err(std::io::Error::new(err.kind(), err.to_string())); + }; + + file.read_to_end(buf) + } +} + +/// Meta data of a file in the local file system. +#[derive(Debug, Clone, Copy)] +pub struct SystemFileMeta { + mt: std::time::SystemTime, + is_file: bool, +} diff --git a/compiler/src/vfs/trace.rs b/compiler/src/vfs/trace.rs index ffb8f782..2a87405e 100644 --- a/compiler/src/vfs/trace.rs +++ b/compiler/src/vfs/trace.rs @@ -8,6 +8,10 @@ use crate::time::SystemTime; use super::{cached::CachedAccessModel, AccessModel}; +/// Provides trace access model which traces the underlying access model. +/// +/// It simply wraps the underlying access model and prints all the access to the +/// stdout or the browser console. #[derive(Debug)] pub struct TraceAccessModel { inner: M, @@ -15,6 +19,7 @@ pub struct TraceAccessModel { } impl TraceAccessModel> { + /// Create a new [`TraceAccessModel`] with the given inner access model pub fn new(inner: CachedAccessModel) -> Self { Self { inner, @@ -22,14 +27,18 @@ impl TraceAccessModel> } } + /// Get the inner access model pub fn inner(&self) -> &M { self.inner.inner() } + /// Get the mutable reference to the inner access model pub fn inner_mut(&mut self) -> &mut M { self.inner.inner_mut() } + /// This is not a common interface for access model, but it is used for vfs + /// incremental parsing. pub fn read_all_diff( &self, src: &Path,