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    Features, 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    /// Additional input arguments to compile the entry file.
54    inputs: Arc<LazyHash<Dict>>,
55    /// Features enabled for the compiler.
56    pub features: Features,
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        features: Features,
80        inputs: Option<Arc<LazyHash<Dict>>>,
81        vfs: Vfs<F::AccessModel>,
82        package_registry: Arc<F::Registry>,
83        font_resolver: Arc<F::FontResolver>,
84    ) -> Self {
85        Self {
86            entry,
87            inputs: inputs.unwrap_or_default(),
88            features,
89
90            revision: NonZeroUsize::new(1).expect("initial revision is 1"),
91
92            font_resolver,
93            registry: package_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            features: self.features.clone(),
160            inputs: self.inputs.clone(),
161            library: create_library(self.inputs.clone(), self.features.clone()),
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    /// Additional input arguments to compile the entry file.
440    inputs: Arc<LazyHash<Dict>>,
441    /// A selection of in-development features that should be enabled.
442    features: Features,
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.features.clone()));
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            features: self.features.clone(),
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    pub fn revision(&self) -> NonZeroUsize {
551        self.revision
552    }
553
554    pub fn evict_vfs(&mut self, threshold: usize) {
555        self.vfs.evict(threshold);
556    }
557
558    pub fn evict_source_cache(&mut self, threshold: usize) {
559        self.vfs
560            .clone_source_cache()
561            .evict(self.vfs.revision(), threshold);
562    }
563
564    /// A list of all available packages and optionally descriptions for them.
565    ///
566    /// This function is optional to implement. It enhances the user experience
567    /// by enabling autocompletion for packages. Details about packages from the
568    /// `@preview` namespace are available from
569    /// `https://packages.typst.org/preview/index.json`.
570    pub fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
571        self.registry.packages()
572    }
573
574    pub fn paged_task(&self) -> Cow<'_, CompilerWorld<F>> {
575        let force_html = self.features.is_enabled(typst::Feature::Html);
576        let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html) || force_html;
577
578        if enabled_paged {
579            return Cow::Borrowed(self);
580        }
581
582        let mut world = self.clone();
583        world.library = create_library(world.inputs.clone(), self.features.clone());
584
585        Cow::Owned(world)
586    }
587
588    pub fn html_task(&self) -> Cow<'_, CompilerWorld<F>> {
589        let enabled_html = self.library.features.is_enabled(typst::Feature::Html);
590
591        if enabled_html {
592            return Cow::Borrowed(self);
593        }
594
595        // todo: We need some way to enable html features based on the features but
596        // typst doesn't give one.
597        let features = typst::Features::from_iter([typst::Feature::Html]);
598
599        let mut world = self.clone();
600        world.library = create_library(world.inputs.clone(), features);
601
602        Cow::Owned(world)
603    }
604}
605
606impl<F: CompilerFeat> ShadowApi for CompilerWorld<F> {
607    #[inline]
608    fn shadow_ids(&self) -> Vec<TypstFileId> {
609        self.vfs.shadow_ids()
610    }
611
612    #[inline]
613    fn shadow_paths(&self) -> Vec<Arc<Path>> {
614        self.vfs.shadow_paths()
615    }
616
617    #[inline]
618    fn reset_shadow(&mut self) {
619        self.vfs.revise().reset_shadow()
620    }
621
622    #[inline]
623    fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
624        self.vfs.revise().map_shadow(path, Ok(content).into())
625    }
626
627    #[inline]
628    fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
629        self.vfs.revise().unmap_shadow(path)
630    }
631
632    #[inline]
633    fn map_shadow_by_id(&mut self, file_id: TypstFileId, content: Bytes) -> FileResult<()> {
634        self.vfs
635            .revise()
636            .map_shadow_by_id(file_id, Ok(content).into())
637    }
638
639    #[inline]
640    fn unmap_shadow_by_id(&mut self, file_id: TypstFileId) -> FileResult<()> {
641        self.vfs.revise().remove_shadow_by_id(file_id);
642        Ok(())
643    }
644}
645
646impl<F: CompilerFeat> FsProvider for CompilerWorld<F> {
647    fn file_path(&self, file_id: TypstFileId) -> FileResult<PathResolution> {
648        self.vfs.file_path(file_id)
649    }
650
651    fn read(&self, file_id: TypstFileId) -> FileResult<Bytes> {
652        self.vfs.read(file_id)
653    }
654
655    fn read_source(&self, file_id: TypstFileId) -> FileResult<Source> {
656        self.vfs.source(file_id)
657    }
658}
659
660impl<F: CompilerFeat> World for CompilerWorld<F> {
661    /// The standard library.
662    fn library(&self) -> &LazyHash<Library> {
663        self.library.as_ref()
664    }
665
666    /// Access the main source file.
667    fn main(&self) -> FileId {
668        self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
669    }
670
671    /// Metadata about all known fonts.
672    fn font(&self, id: usize) -> Option<Font> {
673        self.font_resolver.font(id)
674    }
675
676    /// Try to access the specified file.
677    fn book(&self) -> &LazyHash<FontBook> {
678        self.font_resolver.font_book()
679    }
680
681    /// Try to access the specified source file.
682    ///
683    /// The returned `Source` file's [id](Source::id) does not have to match the
684    /// given `id`. Due to symlinks, two different file id's can point to the
685    /// same on-disk file. Implementers can deduplicate and return the same
686    /// `Source` if they want to, but do not have to.
687    fn source(&self, id: FileId) -> FileResult<Source> {
688        static DETACH_SOURCE: LazyLock<Source> =
689            LazyLock::new(|| Source::new(*DETACHED_ENTRY, String::new()));
690
691        if id == *DETACHED_ENTRY {
692            return Ok(DETACH_SOURCE.clone());
693        }
694
695        self.source_db.source(id, self)
696    }
697
698    /// Try to access the specified file.
699    fn file(&self, id: FileId) -> FileResult<Bytes> {
700        self.source_db.file(id, self)
701    }
702
703    /// Get the current date.
704    ///
705    /// If no offset is specified, the local date should be chosen. Otherwise,
706    /// the UTC date should be chosen with the corresponding offset in hours.
707    ///
708    /// If this function returns `None`, Typst's `datetime` function will
709    /// return an error.
710    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
711        let now = self.now.get_or_init(|| tinymist_std::time::now().into());
712
713        let naive = match offset {
714            None => now.naive_local(),
715            Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
716        };
717
718        Datetime::from_ymd(
719            naive.year(),
720            naive.month().try_into().ok()?,
721            naive.day().try_into().ok()?,
722        )
723    }
724}
725
726impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
727    fn entry_state(&self) -> EntryState {
728        self.entry.clone()
729    }
730}
731
732impl<F: CompilerFeat> WorldDeps for CompilerWorld<F> {
733    #[inline]
734    fn iter_dependencies(&self, f: &mut dyn FnMut(TypstFileId)) {
735        self.source_db.iter_dependencies_dyn(f)
736    }
737}
738
739/// Runs a world with a main file.
740pub fn with_main(world: &dyn World, id: FileId) -> WorldWithMain<'_> {
741    WorldWithMain { world, main: id }
742}
743
744pub struct WorldWithMain<'a> {
745    world: &'a dyn World,
746    main: FileId,
747}
748
749impl typst::World for WorldWithMain<'_> {
750    fn main(&self) -> FileId {
751        self.main
752    }
753
754    fn source(&self, id: FileId) -> FileResult<Source> {
755        self.world.source(id)
756    }
757
758    fn library(&self) -> &LazyHash<Library> {
759        self.world.library()
760    }
761
762    fn book(&self) -> &LazyHash<FontBook> {
763        self.world.book()
764    }
765
766    fn file(&self, id: FileId) -> FileResult<Bytes> {
767        self.world.file(id)
768    }
769
770    fn font(&self, index: usize) -> Option<Font> {
771        self.world.font(index)
772    }
773
774    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
775        self.world.today(offset)
776    }
777}
778
779pub trait SourceWorld: World {
780    fn as_world(&self) -> &dyn World;
781
782    fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError>;
783    fn lookup(&self, id: FileId) -> Source {
784        self.source(id)
785            .expect("file id does not point to any source file")
786    }
787}
788
789impl<F: CompilerFeat> SourceWorld for CompilerWorld<F> {
790    fn as_world(&self) -> &dyn World {
791        self
792    }
793
794    /// Resolve the real path for a file id.
795    fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
796        self.path_for_id(id)
797    }
798}
799
800pub struct CodeSpanReportWorld<'a> {
801    pub world: &'a dyn SourceWorld,
802}
803
804impl<'a> CodeSpanReportWorld<'a> {
805    pub fn new(world: &'a dyn SourceWorld) -> Self {
806        Self { world }
807    }
808}
809
810impl<'a> codespan_reporting::files::Files<'a> for CodeSpanReportWorld<'a> {
811    /// A unique identifier for files in the file provider. This will be used
812    /// for rendering `diagnostic::Label`s in the corresponding source files.
813    type FileId = FileId;
814
815    /// The user-facing name of a file, to be displayed in diagnostics.
816    type Name = String;
817
818    /// The source code of a file.
819    type Source = Source;
820
821    /// The user-facing name of a file.
822    fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
823        Ok(match self.world.path_for_id(id) {
824            Ok(path) => path.as_path().display().to_string(),
825            Err(_) => format!("{id:?}"),
826        })
827    }
828
829    /// The source code of a file.
830    fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
831        Ok(self.world.lookup(id))
832    }
833
834    /// See [`codespan_reporting::files::Files::line_index`].
835    fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
836        let source = self.world.lookup(id);
837        source
838            .byte_to_line(given)
839            .ok_or_else(|| CodespanError::IndexTooLarge {
840                given,
841                max: source.len_bytes(),
842            })
843    }
844
845    /// See [`codespan_reporting::files::Files::column_number`].
846    fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
847        let source = self.world.lookup(id);
848        source.byte_to_column(given).ok_or_else(|| {
849            let max = source.len_bytes();
850            if given <= max {
851                CodespanError::InvalidCharBoundary { given }
852            } else {
853                CodespanError::IndexTooLarge { given, max }
854            }
855        })
856    }
857
858    /// See [`codespan_reporting::files::Files::line_range`].
859    fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
860        match self.world.source(id).ok() {
861            Some(source) => {
862                source
863                    .line_to_range(given)
864                    .ok_or_else(|| CodespanError::LineTooLarge {
865                        given,
866                        max: source.len_lines(),
867                    })
868            }
869            None => Ok(0..0),
870        }
871    }
872}
873
874// todo: remove me
875impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
876    /// A unique identifier for files in the file provider. This will be used
877    /// for rendering `diagnostic::Label`s in the corresponding source files.
878    type FileId = FileId;
879
880    /// The user-facing name of a file, to be displayed in diagnostics.
881    type Name = String;
882
883    /// The source code of a file.
884    type Source = Source;
885
886    /// The user-facing name of a file.
887    fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
888        CodeSpanReportWorld::new(self).name(id)
889    }
890
891    /// The source code of a file.
892    fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
893        CodeSpanReportWorld::new(self).source(id)
894    }
895
896    /// See [`codespan_reporting::files::Files::line_index`].
897    fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
898        CodeSpanReportWorld::new(self).line_index(id, given)
899    }
900
901    /// See [`codespan_reporting::files::Files::column_number`].
902    fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
903        CodeSpanReportWorld::new(self).column_number(id, 0, given)
904    }
905
906    /// See [`codespan_reporting::files::Files::line_range`].
907    fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
908        CodeSpanReportWorld::new(self).line_range(id, given)
909    }
910}
911
912#[comemo::memoize]
913fn create_library(inputs: Arc<LazyHash<Dict>>, features: Features) -> Arc<LazyHash<Library>> {
914    let lib = typst::Library::builder()
915        .with_inputs(inputs.deref().deref().clone())
916        .with_features(features)
917        .build();
918
919    Arc::new(LazyHash::new(lib))
920}