tinymist_world/
world.rs

1use std::{
2    borrow::Cow,
3    num::NonZeroUsize,
4    ops::Deref,
5    path::{Path, PathBuf},
6    sync::{Arc, LazyLock, OnceLock},
7};
8
9use chrono::{DateTime, Datelike, Local};
10use tinymist_std::error::prelude::*;
11use tinymist_vfs::{
12    FsProvider, PathResolution, RevisingVfs, SourceCache, TypstFileId, Vfs, WorkspaceResolver,
13};
14use typst::{
15    diag::{eco_format, At, EcoString, FileError, FileResult, SourceResult},
16    foundations::{Bytes, Datetime, Dict},
17    syntax::{FileId, Source, Span, VirtualPath},
18    text::{Font, FontBook},
19    utils::LazyHash,
20    Library, World,
21};
22
23use crate::{
24    package::{PackageRegistry, PackageSpec},
25    source::SourceDb,
26    CompileSnapshot, MEMORY_MAIN_ENTRY,
27};
28use crate::{
29    parser::{
30        get_semantic_tokens_full, get_semantic_tokens_legend, OffsetEncoding, SemanticToken,
31        SemanticTokensLegend,
32    },
33    WorldComputeGraph,
34};
35// use crate::source::{SharedState, SourceCache, SourceDb};
36use crate::entry::{EntryManager, EntryReader, EntryState, DETACHED_ENTRY};
37use crate::{font::FontResolver, CompilerFeat, ShadowApi, WorldDeps};
38
39type CodespanResult<T> = Result<T, CodespanError>;
40type CodespanError = codespan_reporting::files::Error;
41
42/// A universe that provides access to the operating system.
43///
44/// Use [`CompilerUniverse::new_raw`] to create a new universe. The concrete
45/// implementation usually wraps this function with a more user-friendly `new`
46/// function.
47/// Use [`CompilerUniverse::snapshot`] to create a new world.
48#[derive(Debug)]
49pub struct CompilerUniverse<F: CompilerFeat> {
50    /// State for the *root & entry* of compilation.
51    /// The world forbids direct access to files outside this directory.
52    entry: EntryState,
53    /// Whether to enable HTML features.
54    enable_html: bool,
55    /// Additional input arguments to compile the entry file.
56    inputs: Arc<LazyHash<Dict>>,
57
58    /// Provides font management for typst compiler.
59    pub font_resolver: Arc<F::FontResolver>,
60    /// Provides package management for typst compiler.
61    pub registry: Arc<F::Registry>,
62    /// Provides path-based data access for typst compiler.
63    vfs: Vfs<F::AccessModel>,
64
65    /// The current revision of the universe.
66    pub revision: NonZeroUsize,
67}
68
69/// Creates, snapshots, and manages the compiler universe.
70impl<F: CompilerFeat> CompilerUniverse<F> {
71    /// Create a [`CompilerUniverse`] with feature implementation.
72    ///
73    /// Although this function is public, it is always unstable and not intended
74    /// to be used directly.
75    /// + See [`crate::TypstSystemUniverse::new`] for system environment.
76    /// + See [`crate::TypstBrowserUniverse::new`] for browser environment.
77    pub fn new_raw(
78        entry: EntryState,
79        enable_html: bool,
80        inputs: Option<Arc<LazyHash<Dict>>>,
81        vfs: Vfs<F::AccessModel>,
82        registry: Arc<F::Registry>,
83        font_resolver: Arc<F::FontResolver>,
84    ) -> Self {
85        Self {
86            entry,
87            enable_html,
88            inputs: inputs.unwrap_or_default(),
89
90            revision: NonZeroUsize::new(1).expect("initial revision is 1"),
91
92            font_resolver,
93            registry,
94            vfs,
95        }
96    }
97
98    /// Wrap driver with a given entry file.
99    pub fn with_entry_file(mut self, entry_file: PathBuf) -> Self {
100        let _ = self.increment_revision(|this| this.set_entry_file_(entry_file.as_path().into()));
101        self
102    }
103
104    pub fn entry_file(&self) -> Option<PathResolution> {
105        self.path_for_id(self.main_id()?).ok()
106    }
107
108    pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
109        self.inputs.clone()
110    }
111
112    pub fn snapshot(&self) -> CompilerWorld<F> {
113        self.snapshot_with(None)
114    }
115
116    // todo: remove me.
117    pub fn computation(&self) -> Arc<WorldComputeGraph<F>> {
118        let world = self.snapshot();
119        let snap = CompileSnapshot::from_world(world);
120        WorldComputeGraph::new(snap)
121    }
122
123    pub fn computation_with(&self, mutant: TaskInputs) -> Arc<WorldComputeGraph<F>> {
124        let world = self.snapshot_with(Some(mutant));
125        let snap = CompileSnapshot::from_world(world);
126        WorldComputeGraph::new(snap)
127    }
128
129    pub fn snapshot_with_entry_content(
130        &self,
131        content: Bytes,
132        inputs: Option<TaskInputs>,
133    ) -> Arc<WorldComputeGraph<F>> {
134        // checkout the entry file
135
136        let mut world = if self.main_id().is_some() {
137            self.snapshot_with(inputs)
138        } else {
139            let world = self.snapshot_with(Some(TaskInputs {
140                entry: Some(
141                    self.entry_state()
142                        .select_in_workspace(MEMORY_MAIN_ENTRY.vpath().as_rooted_path()),
143                ),
144                inputs: inputs.and_then(|i| i.inputs),
145            }));
146
147            world
148        };
149
150        world.map_shadow_by_id(world.main(), content).unwrap();
151
152        let snap = CompileSnapshot::from_world(world);
153        WorldComputeGraph::new(snap)
154    }
155
156    pub fn snapshot_with(&self, mutant: Option<TaskInputs>) -> CompilerWorld<F> {
157        let w = CompilerWorld {
158            entry: self.entry.clone(),
159            enable_html: self.enable_html,
160            inputs: self.inputs.clone(),
161            library: create_library(self.inputs.clone(), self.enable_html),
162            font_resolver: self.font_resolver.clone(),
163            registry: self.registry.clone(),
164            vfs: self.vfs.snapshot(),
165            revision: self.revision,
166            source_db: SourceDb {
167                is_compiling: true,
168                slots: Default::default(),
169            },
170            now: OnceLock::new(),
171        };
172
173        mutant.map(|m| w.task(m)).unwrap_or(w)
174    }
175
176    /// Increment revision with actions.
177    pub fn increment_revision<T>(&mut self, f: impl FnOnce(&mut RevisingUniverse<F>) -> T) -> T {
178        f(&mut RevisingUniverse {
179            vfs_revision: self.vfs.revision(),
180            font_changed: false,
181            font_revision: self.font_resolver.revision(),
182            registry_changed: false,
183            registry_revision: self.registry.revision(),
184            view_changed: false,
185            inner: self,
186        })
187    }
188
189    /// Mutate the entry state and return the old state.
190    fn mutate_entry_(&mut self, mut state: EntryState) -> SourceResult<EntryState> {
191        std::mem::swap(&mut self.entry, &mut state);
192        Ok(state)
193    }
194
195    /// set an entry file.
196    fn set_entry_file_(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
197        let state = self.entry_state();
198        let state = state
199            .try_select_path_in_workspace(&entry_file)
200            .map_err(|e| eco_format!("cannot select entry file out of workspace: {e}"))
201            .at(Span::detached())?
202            .ok_or_else(|| eco_format!("failed to determine root"))
203            .at(Span::detached())?;
204
205        self.mutate_entry_(state).map(|_| ())?;
206        Ok(())
207    }
208
209    pub fn vfs(&self) -> &Vfs<F::AccessModel> {
210        &self.vfs
211    }
212}
213
214impl<F: CompilerFeat> CompilerUniverse<F> {
215    /// Reset the world for a new lifecycle (of garbage collection).
216    pub fn reset(&mut self) {
217        self.vfs.reset_all();
218        // todo: shared state
219    }
220
221    /// Clear the vfs cache that is not touched for a long time.
222    pub fn evict(&mut self, vfs_threshold: usize) {
223        self.vfs.reset_access_model();
224        self.vfs.evict(vfs_threshold);
225    }
226
227    /// Resolve the real path for a file id.
228    pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
229        self.vfs.file_path(id)
230    }
231
232    /// Resolve the root of the workspace.
233    pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
234        let root = self.entry.workspace_root()?;
235        Some(WorkspaceResolver::workspace_file(
236            Some(&root),
237            VirtualPath::new(path.strip_prefix(&root).ok()?),
238        ))
239    }
240
241    pub fn get_semantic_token_legend(&self) -> Arc<SemanticTokensLegend> {
242        Arc::new(get_semantic_tokens_legend())
243    }
244
245    pub fn get_semantic_tokens(
246        &self,
247        file_path: Option<String>,
248        encoding: OffsetEncoding,
249    ) -> Result<Arc<Vec<SemanticToken>>> {
250        let world = match file_path {
251            Some(e) => {
252                let path = Path::new(&e);
253                let s = self
254                    .entry_state()
255                    .try_select_path_in_workspace(path)?
256                    .ok_or_else(|| error_once!("cannot select file", path: e))?;
257
258                self.snapshot_with(Some(TaskInputs {
259                    entry: Some(s),
260                    inputs: None,
261                }))
262            }
263            None => self.snapshot(),
264        };
265
266        let src = world
267            .source(world.main())
268            .map_err(|e| error_once!("cannot access source file", err: e))?;
269        Ok(Arc::new(get_semantic_tokens_full(&src, encoding)))
270    }
271}
272
273impl<F: CompilerFeat> ShadowApi for CompilerUniverse<F> {
274    #[inline]
275    fn reset_shadow(&mut self) {
276        self.increment_revision(|this| this.vfs.revise().reset_shadow())
277    }
278
279    fn shadow_paths(&self) -> Vec<Arc<Path>> {
280        self.vfs.shadow_paths()
281    }
282
283    fn shadow_ids(&self) -> Vec<TypstFileId> {
284        self.vfs.shadow_ids()
285    }
286
287    #[inline]
288    fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
289        self.increment_revision(|this| this.vfs().map_shadow(path, Ok(content).into()))
290    }
291
292    #[inline]
293    fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
294        self.increment_revision(|this| this.vfs().unmap_shadow(path))
295    }
296
297    #[inline]
298    fn map_shadow_by_id(&mut self, file_id: FileId, content: Bytes) -> FileResult<()> {
299        self.increment_revision(|this| this.vfs().map_shadow_by_id(file_id, Ok(content).into()))
300    }
301
302    #[inline]
303    fn unmap_shadow_by_id(&mut self, file_id: FileId) -> FileResult<()> {
304        self.increment_revision(|this| {
305            this.vfs().remove_shadow_by_id(file_id);
306            Ok(())
307        })
308    }
309}
310
311impl<F: CompilerFeat> EntryReader for CompilerUniverse<F> {
312    fn entry_state(&self) -> EntryState {
313        self.entry.clone()
314    }
315}
316
317impl<F: CompilerFeat> EntryManager for CompilerUniverse<F> {
318    fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
319        self.increment_revision(|this| this.mutate_entry_(state))
320    }
321}
322
323pub struct RevisingUniverse<'a, F: CompilerFeat> {
324    view_changed: bool,
325    vfs_revision: NonZeroUsize,
326    font_changed: bool,
327    font_revision: Option<NonZeroUsize>,
328    registry_changed: bool,
329    registry_revision: Option<NonZeroUsize>,
330    pub inner: &'a mut CompilerUniverse<F>,
331}
332
333impl<F: CompilerFeat> std::ops::Deref for RevisingUniverse<'_, F> {
334    type Target = CompilerUniverse<F>;
335
336    fn deref(&self) -> &Self::Target {
337        self.inner
338    }
339}
340
341impl<F: CompilerFeat> std::ops::DerefMut for RevisingUniverse<'_, F> {
342    fn deref_mut(&mut self) -> &mut Self::Target {
343        self.inner
344    }
345}
346
347impl<F: CompilerFeat> Drop for RevisingUniverse<'_, F> {
348    fn drop(&mut self) {
349        let mut view_changed = self.view_changed;
350        // If the revision is none, it means the fonts should be viewed as
351        // changed unconditionally.
352        if self.font_changed() {
353            view_changed = true;
354        }
355        // If the revision is none, it means the packages should be viewed as
356        // changed unconditionally.
357        if self.registry_changed() {
358            view_changed = true;
359
360            // The registry has changed affects the vfs cache.
361            log::info!("resetting shadow registry_changed");
362            self.vfs().reset_cache();
363        }
364        let view_changed = view_changed || self.vfs_changed();
365
366        if view_changed {
367            self.vfs.reset_access_model();
368            let revision = &mut self.revision;
369            *revision = revision.checked_add(1).unwrap();
370        }
371    }
372}
373
374impl<F: CompilerFeat> RevisingUniverse<'_, F> {
375    pub fn vfs(&mut self) -> RevisingVfs<'_, F::AccessModel> {
376        self.vfs.revise()
377    }
378
379    pub fn set_fonts(&mut self, fonts: Arc<F::FontResolver>) {
380        self.font_changed = true;
381        self.inner.font_resolver = fonts;
382    }
383
384    pub fn set_package(&mut self, packages: Arc<F::Registry>) {
385        self.registry_changed = true;
386        self.inner.registry = packages;
387    }
388
389    /// Set the inputs for the compiler.
390    pub fn set_inputs(&mut self, inputs: Arc<LazyHash<Dict>>) {
391        self.view_changed = true;
392        self.inner.inputs = inputs;
393    }
394
395    pub fn set_entry_file(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
396        self.view_changed = true;
397        self.inner.set_entry_file_(entry_file)
398    }
399
400    pub fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
401        self.view_changed = true;
402
403        // Resets the cache if the workspace root has changed.
404        let root_changed = self.inner.entry.workspace_root() == state.workspace_root();
405        if root_changed {
406            log::info!("resetting shadow root_changed");
407            self.vfs().reset_cache();
408        }
409
410        self.inner.mutate_entry_(state)
411    }
412
413    pub fn flush(&mut self) {
414        self.view_changed = true;
415    }
416
417    pub fn font_changed(&self) -> bool {
418        self.font_changed && is_revision_changed(self.font_revision, self.font_resolver.revision())
419    }
420
421    pub fn registry_changed(&self) -> bool {
422        self.registry_changed
423            && is_revision_changed(self.registry_revision, self.registry.revision())
424    }
425
426    pub fn vfs_changed(&self) -> bool {
427        self.vfs_revision != self.vfs.revision()
428    }
429}
430
431fn is_revision_changed(a: Option<NonZeroUsize>, b: Option<NonZeroUsize>) -> bool {
432    a.is_none() || b.is_none() || a != b
433}
434
435pub struct CompilerWorld<F: CompilerFeat> {
436    /// State for the *root & entry* of compilation.
437    /// The world forbids direct access to files outside this directory.
438    entry: EntryState,
439    /// Whether to enable HTML features.
440    enable_html: bool,
441    /// Additional input arguments to compile the entry file.
442    inputs: Arc<LazyHash<Dict>>,
443
444    /// Provides library for typst compiler.
445    pub library: Arc<LazyHash<Library>>,
446    /// Provides font management for typst compiler.
447    pub font_resolver: Arc<F::FontResolver>,
448    /// Provides package management for typst compiler.
449    pub registry: Arc<F::Registry>,
450    /// Provides path-based data access for typst compiler.
451    vfs: Vfs<F::AccessModel>,
452
453    revision: NonZeroUsize,
454    /// Provides source database for typst compiler.
455    source_db: SourceDb,
456    /// The current datetime if requested. This is stored here to ensure it is
457    /// always the same within one compilation. Reset between compilations.
458    now: OnceLock<DateTime<Local>>,
459}
460
461impl<F: CompilerFeat> Clone for CompilerWorld<F> {
462    fn clone(&self) -> Self {
463        self.task(TaskInputs::default())
464    }
465}
466
467#[derive(Debug, Default)]
468pub struct TaskInputs {
469    pub entry: Option<EntryState>,
470    pub inputs: Option<Arc<LazyHash<Dict>>>,
471}
472
473impl<F: CompilerFeat> CompilerWorld<F> {
474    pub fn task(&self, mutant: TaskInputs) -> CompilerWorld<F> {
475        // Fetch to avoid inconsistent state.
476        let _ = self.today(None);
477
478        let library = mutant
479            .inputs
480            .clone()
481            .map(|inputs| create_library(inputs, self.enable_html));
482
483        let root_changed = if let Some(e) = mutant.entry.as_ref() {
484            self.entry.workspace_root() != e.workspace_root()
485        } else {
486            false
487        };
488
489        let mut world = CompilerWorld {
490            enable_html: self.enable_html,
491            inputs: mutant.inputs.unwrap_or_else(|| self.inputs.clone()),
492            library: library.unwrap_or_else(|| self.library.clone()),
493            entry: mutant.entry.unwrap_or_else(|| self.entry.clone()),
494            font_resolver: self.font_resolver.clone(),
495            registry: self.registry.clone(),
496            vfs: self.vfs.snapshot(),
497            revision: self.revision,
498            source_db: self.source_db.clone(),
499            now: self.now.clone(),
500        };
501
502        if root_changed {
503            world.vfs.revise().reset_cache();
504        }
505
506        world
507    }
508
509    pub fn take_cache(&mut self) -> SourceCache {
510        self.vfs.take_source_cache()
511    }
512
513    pub fn clone_cache(&mut self) -> SourceCache {
514        self.vfs.clone_source_cache()
515    }
516
517    pub fn take_db(&mut self) -> SourceDb {
518        self.source_db.take_state()
519    }
520
521    pub fn vfs(&self) -> &Vfs<F::AccessModel> {
522        &self.vfs
523    }
524
525    /// Sets flag to indicate whether the compiler is currently compiling.
526    /// Note: Since `CompilerWorld` can be cloned, you can clone the world and
527    /// set the flag then to avoid affecting the original world.
528    pub fn set_is_compiling(&mut self, is_compiling: bool) {
529        self.source_db.is_compiling = is_compiling;
530    }
531
532    pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
533        self.inputs.clone()
534    }
535
536    /// Resolve the real path for a file id.
537    pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
538        self.vfs.file_path(id)
539    }
540
541    /// Resolve the root of the workspace.
542    pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
543        let root = self.entry.workspace_root()?;
544        Some(WorkspaceResolver::workspace_file(
545            Some(&root),
546            VirtualPath::new(path.strip_prefix(&root).ok()?),
547        ))
548    }
549
550    /// Lookup a source file by id.
551    #[track_caller]
552    fn lookup(&self, id: FileId) -> Source {
553        self.source(id)
554            .expect("file id does not point to any source file")
555    }
556
557    fn map_source_or_default<T>(
558        &self,
559        id: FileId,
560        default_v: T,
561        f: impl FnOnce(Source) -> CodespanResult<T>,
562    ) -> CodespanResult<T> {
563        match World::source(self, id).ok() {
564            Some(source) => f(source),
565            None => Ok(default_v),
566        }
567    }
568
569    pub fn revision(&self) -> NonZeroUsize {
570        self.revision
571    }
572
573    pub fn evict_vfs(&mut self, threshold: usize) {
574        self.vfs.evict(threshold);
575    }
576
577    pub fn evict_source_cache(&mut self, threshold: usize) {
578        self.vfs
579            .clone_source_cache()
580            .evict(self.vfs.revision(), threshold);
581    }
582
583    /// A list of all available packages and optionally descriptions for them.
584    ///
585    /// This function is optional to implement. It enhances the user experience
586    /// by enabling autocompletion for packages. Details about packages from the
587    /// `@preview` namespace are available from
588    /// `https://packages.typst.org/preview/index.json`.
589    pub fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
590        self.registry.packages()
591    }
592
593    pub fn paged_task(&self) -> Cow<'_, CompilerWorld<F>> {
594        let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html);
595
596        if enabled_paged {
597            return Cow::Borrowed(self);
598        }
599
600        let mut world = self.clone();
601        world.library = create_library(world.inputs.clone(), false);
602
603        Cow::Owned(world)
604    }
605
606    pub fn html_task(&self) -> Cow<'_, CompilerWorld<F>> {
607        let enabled_html = self.library.features.is_enabled(typst::Feature::Html);
608
609        if enabled_html {
610            return Cow::Borrowed(self);
611        }
612
613        let mut world = self.clone();
614        world.library = create_library(world.inputs.clone(), true);
615
616        Cow::Owned(world)
617    }
618}
619
620impl<F: CompilerFeat> ShadowApi for CompilerWorld<F> {
621    #[inline]
622    fn shadow_ids(&self) -> Vec<TypstFileId> {
623        self.vfs.shadow_ids()
624    }
625
626    #[inline]
627    fn shadow_paths(&self) -> Vec<Arc<Path>> {
628        self.vfs.shadow_paths()
629    }
630
631    #[inline]
632    fn reset_shadow(&mut self) {
633        self.vfs.revise().reset_shadow()
634    }
635
636    #[inline]
637    fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
638        self.vfs.revise().map_shadow(path, Ok(content).into())
639    }
640
641    #[inline]
642    fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
643        self.vfs.revise().unmap_shadow(path)
644    }
645
646    #[inline]
647    fn map_shadow_by_id(&mut self, file_id: TypstFileId, content: Bytes) -> FileResult<()> {
648        self.vfs
649            .revise()
650            .map_shadow_by_id(file_id, Ok(content).into())
651    }
652
653    #[inline]
654    fn unmap_shadow_by_id(&mut self, file_id: TypstFileId) -> FileResult<()> {
655        self.vfs.revise().remove_shadow_by_id(file_id);
656        Ok(())
657    }
658}
659
660impl<F: CompilerFeat> FsProvider for CompilerWorld<F> {
661    fn file_path(&self, file_id: TypstFileId) -> FileResult<PathResolution> {
662        self.vfs.file_path(file_id)
663    }
664
665    fn read(&self, file_id: TypstFileId) -> FileResult<Bytes> {
666        self.vfs.read(file_id)
667    }
668
669    fn read_source(&self, file_id: TypstFileId) -> FileResult<Source> {
670        self.vfs.source(file_id)
671    }
672}
673
674impl<F: CompilerFeat> World for CompilerWorld<F> {
675    /// The standard library.
676    fn library(&self) -> &LazyHash<Library> {
677        self.library.as_ref()
678    }
679
680    /// Access the main source file.
681    fn main(&self) -> FileId {
682        self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
683    }
684
685    /// Metadata about all known fonts.
686    fn font(&self, id: usize) -> Option<Font> {
687        self.font_resolver.font(id)
688    }
689
690    /// Try to access the specified file.
691    fn book(&self) -> &LazyHash<FontBook> {
692        self.font_resolver.font_book()
693    }
694
695    /// Try to access the specified source file.
696    ///
697    /// The returned `Source` file's [id](Source::id) does not have to match the
698    /// given `id`. Due to symlinks, two different file id's can point to the
699    /// same on-disk file. Implementers can deduplicate and return the same
700    /// `Source` if they want to, but do not have to.
701    fn source(&self, id: FileId) -> FileResult<Source> {
702        static DETACH_SOURCE: LazyLock<Source> =
703            LazyLock::new(|| Source::new(*DETACHED_ENTRY, String::new()));
704
705        if id == *DETACHED_ENTRY {
706            return Ok(DETACH_SOURCE.clone());
707        }
708
709        self.source_db.source(id, self)
710    }
711
712    /// Try to access the specified file.
713    fn file(&self, id: FileId) -> FileResult<Bytes> {
714        self.source_db.file(id, self)
715    }
716
717    /// Get the current date.
718    ///
719    /// If no offset is specified, the local date should be chosen. Otherwise,
720    /// the UTC date should be chosen with the corresponding offset in hours.
721    ///
722    /// If this function returns `None`, Typst's `datetime` function will
723    /// return an error.
724    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
725        let now = self.now.get_or_init(|| tinymist_std::time::now().into());
726
727        let naive = match offset {
728            None => now.naive_local(),
729            Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
730        };
731
732        Datetime::from_ymd(
733            naive.year(),
734            naive.month().try_into().ok()?,
735            naive.day().try_into().ok()?,
736        )
737    }
738}
739
740impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
741    fn entry_state(&self) -> EntryState {
742        self.entry.clone()
743    }
744}
745
746impl<F: CompilerFeat> WorldDeps for CompilerWorld<F> {
747    #[inline]
748    fn iter_dependencies(&self, f: &mut dyn FnMut(TypstFileId)) {
749        self.source_db.iter_dependencies_dyn(f)
750    }
751}
752
753impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
754    /// A unique identifier for files in the file provider. This will be used
755    /// for rendering `diagnostic::Label`s in the corresponding source files.
756    type FileId = FileId;
757
758    /// The user-facing name of a file, to be displayed in diagnostics.
759    type Name = String;
760
761    /// The source code of a file.
762    type Source = Source;
763
764    /// The user-facing name of a file.
765    fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
766        Ok(match self.path_for_id(id) {
767            Ok(path) => path.as_path().display().to_string(),
768            Err(_) => format!("{id:?}"),
769        })
770    }
771
772    /// The source code of a file.
773    fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
774        Ok(self.lookup(id))
775    }
776
777    /// See [`codespan_reporting::files::Files::line_index`].
778    fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
779        let source = self.lookup(id);
780        source
781            .byte_to_line(given)
782            .ok_or_else(|| CodespanError::IndexTooLarge {
783                given,
784                max: source.len_bytes(),
785            })
786    }
787
788    /// See [`codespan_reporting::files::Files::column_number`].
789    fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
790        let source = self.lookup(id);
791        source.byte_to_column(given).ok_or_else(|| {
792            let max = source.len_bytes();
793            if given <= max {
794                CodespanError::InvalidCharBoundary { given }
795            } else {
796                CodespanError::IndexTooLarge { given, max }
797            }
798        })
799    }
800
801    /// See [`codespan_reporting::files::Files::line_range`].
802    fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
803        self.map_source_or_default(id, 0..0, |source| {
804            source
805                .line_to_range(given)
806                .ok_or_else(|| CodespanError::LineTooLarge {
807                    given,
808                    max: source.len_lines(),
809                })
810        })
811    }
812}
813
814#[comemo::memoize]
815fn create_library(inputs: Arc<LazyHash<Dict>>, enable_html: bool) -> Arc<LazyHash<Library>> {
816    let features = if enable_html {
817        typst::Features::from_iter([typst::Feature::Html])
818    } else {
819        typst::Features::default()
820    };
821
822    let lib = typst::Library::builder()
823        .with_inputs(inputs.deref().deref().clone())
824        .with_features(features)
825        .build();
826
827    Arc::new(LazyHash::new(lib))
828}