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