#![feature(hashmap_internals)] use std::cell::{OnceCell, RefCell, RefMut}; use std::collections::HashMap; use std::fs::{self}; use std::hash::Hash; use std::path::{Path, PathBuf}; use comemo::Prehashed; use elsa::FrozenVec; use same_file::Handle; use siphasher::sip128::{Hasher128, SipHasher13}; use typst::diag::{FileError, FileResult}; use typst::eval::Library; use typst::font::{Font, FontBook}; use typst::syntax::{Source, SourceId}; use typst::util::{Buffer, PathExt}; use typst::World; /// Holds canonical data for all paths pointing to the same entity. #[derive(Default)] struct PathSlot { source: OnceCell>, buffer: OnceCell>, } /// A hash that is the same for all paths pointing to the same entity. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] struct PathHash(u128); impl PathHash { fn new(path: &Path) -> FileResult { let f = |e| FileError::from_io(e, path); let handle = Handle::from_path(path).map_err(f)?; let mut state = SipHasher13::new(); handle.hash(&mut state); Ok(Self(state.finish128().as_u128())) } } /// Read a file. fn read(path: &Path) -> FileResult> { let f = |e| FileError::from_io(e, path); if fs::metadata(path).map_err(f)?.is_dir() { Err(FileError::IsDirectory) } else { fs::read(path).map_err(f) } } pub struct WebWorld { root: PathBuf, library: Prehashed, book: Prehashed, // unused hashes: RefCell>>, paths: RefCell>, sources: FrozenVec>, main: SourceId, } impl WebWorld { fn new(root: PathBuf) -> Self { WebWorld { root, library: Prehashed::new(typst_library::build()), book: Prehashed::new(Default::default()), hashes: RefCell::default(), paths: RefCell::default(), sources: FrozenVec::new(), main: SourceId::detached(), } } fn slot(&self, path: &Path) -> FileResult> { let mut hashes = self.hashes.borrow_mut(); let hash = match hashes.get(path).cloned() { Some(hash) => hash, None => { let hash = PathHash::new(path); if let Ok(canon) = path.canonicalize() { hashes.insert(canon.normalize(), hash.clone()); } hashes.insert(path.into(), hash.clone()); hash } }?; Ok(RefMut::map(self.paths.borrow_mut(), |paths| { paths.entry(hash).or_default() })) } /// Add a source file to the world and return its ID fn insert(&self, path: &Path, text: String) -> SourceId { let id = SourceId::from_u16(self.sources.len() as u16); let source = Source::new(id, path, text); self.sources.push(Box::new(source)); id } fn relevant(&mut self, event: ¬ify::Event) -> bool { match &event.kind { notify::EventKind::Any => {} notify::EventKind::Access(_) => return false, notify::EventKind::Create(_) => return true, notify::EventKind::Modify(kind) => match kind { notify::event::ModifyKind::Any => {} notify::event::ModifyKind::Data(_) => {} notify::event::ModifyKind::Metadata(_) => return false, notify::event::ModifyKind::Name(_) => return true, notify::event::ModifyKind::Other => return false, }, notify::EventKind::Remove(_) => {} notify::EventKind::Other => return false, } event.paths.iter().any(|path| self.dependant(path)) } fn dependant(&self, path: &Path) -> bool { self.hashes.borrow().contains_key(&path.normalize()) || PathHash::new(path) .map_or(false, |hash| self.paths.borrow().contains_key(&hash)) } fn reset(&mut self) { self.sources.as_mut().clear(); self.hashes.borrow_mut().clear(); self.paths.borrow_mut().clear(); } } impl World for WebWorld { fn root(&self) -> &Path { &self.root } fn library(&self) -> &Prehashed { &self.library } fn main(&self) -> &Source { self.source(self.main) } fn resolve(&self, path: &Path) -> FileResult { self.slot(path)? .source .get_or_init(|| { let buf = read(path)?; let text = String::from_utf8(buf)?; Ok(self.insert(path, text)) }) .clone() } fn source(&self, id: SourceId) -> &Source { &self.sources[id.into_u16() as usize] } fn book(&self) -> &Prehashed { &self.book // always empty } fn font(&self, _: usize) -> Option { None } fn file(&self, path: &Path) -> FileResult { self.slot(path)? .buffer .get_or_init(|| read(path).map(Buffer::from)) .clone() } } fn main() { println!("Hello, world!"); }