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    FsProvider, PathResolution, RevisingVfs, SourceCache, TypstFileId, Vfs, WorkspaceResolver,
12};
13use typst::{
14    diag::{eco_format, At, EcoString, FileError, FileResult, SourceResult},
15    foundations::{Bytes, Datetime, Dict},
16    syntax::{FileId, 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<TypstFileId> {
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_cache();
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_cache();
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.revise().reset_cache();
508        }
509
510        world
511    }
512
513    pub fn take_cache(&mut self) -> SourceCache {
514        self.vfs.take_source_cache()
515    }
516
517    pub fn clone_cache(&mut self) -> SourceCache {
518        self.vfs.clone_source_cache()
519    }
520
521    pub fn take_db(&mut self) -> SourceDb {
522        self.source_db.take_state()
523    }
524
525    pub fn vfs(&self) -> &Vfs<F::AccessModel> {
526        &self.vfs
527    }
528
529    /// Sets flag to indicate whether the compiler is currently compiling.
530    /// Note: Since `CompilerWorld` can be cloned, you can clone the world and
531    /// set the flag then to avoid affecting the original world.
532    pub fn set_is_compiling(&mut self, is_compiling: bool) {
533        self.source_db.is_compiling = is_compiling;
534    }
535
536    pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
537        self.inputs.clone()
538    }
539
540    /// Resolve the real path for a file id.
541    pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
542        self.vfs.file_path(id)
543    }
544
545    /// Resolve the root of the workspace.
546    pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
547        let root = self.entry.workspace_root()?;
548        Some(WorkspaceResolver::workspace_file(
549            Some(&root),
550            VirtualPath::new(path.strip_prefix(&root).ok()?),
551        ))
552    }
553
554    pub fn revision(&self) -> NonZeroUsize {
555        self.revision
556    }
557
558    pub fn evict_vfs(&mut self, threshold: usize) {
559        self.vfs.evict(threshold);
560    }
561
562    pub fn evict_source_cache(&mut self, threshold: usize) {
563        self.vfs
564            .clone_source_cache()
565            .evict(self.vfs.revision(), threshold);
566    }
567
568    /// A list of all available packages and optionally descriptions for them.
569    ///
570    /// This function is optional to implement. It enhances the user experience
571    /// by enabling autocompletion for packages. Details about packages from the
572    /// `@preview` namespace are available from
573    /// `https://packages.typst.org/preview/index.json`.
574    pub fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
575        self.registry.packages()
576    }
577
578    pub fn paged_task(&self) -> Cow<'_, CompilerWorld<F>> {
579        let force_html = self.features.is_enabled(typst::Feature::Html);
580        let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html) || force_html;
581
582        if enabled_paged {
583            return Cow::Borrowed(self);
584        }
585
586        let mut world = self.clone();
587        world.library = create_library(world.inputs.clone(), self.features.clone());
588
589        Cow::Owned(world)
590    }
591
592    pub fn html_task(&self) -> Cow<'_, CompilerWorld<F>> {
593        let enabled_html = self.library.features.is_enabled(typst::Feature::Html);
594
595        if enabled_html {
596            return Cow::Borrowed(self);
597        }
598
599        // todo: We need some way to enable html features based on the features but
600        // typst doesn't give one.
601        let features = typst::Features::from_iter([typst::Feature::Html]);
602
603        let mut world = self.clone();
604        world.library = create_library(world.inputs.clone(), features);
605
606        Cow::Owned(world)
607    }
608}
609
610impl<F: CompilerFeat> ShadowApi for CompilerWorld<F> {
611    #[inline]
612    fn shadow_ids(&self) -> Vec<TypstFileId> {
613        self.vfs.shadow_ids()
614    }
615
616    #[inline]
617    fn shadow_paths(&self) -> Vec<Arc<Path>> {
618        self.vfs.shadow_paths()
619    }
620
621    #[inline]
622    fn reset_shadow(&mut self) {
623        self.vfs.revise().reset_shadow()
624    }
625
626    #[inline]
627    fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
628        self.vfs.revise().map_shadow(path, Ok(content).into())
629    }
630
631    #[inline]
632    fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
633        self.vfs.revise().unmap_shadow(path)
634    }
635
636    #[inline]
637    fn map_shadow_by_id(&mut self, file_id: TypstFileId, content: Bytes) -> FileResult<()> {
638        self.vfs
639            .revise()
640            .map_shadow_by_id(file_id, Ok(content).into())
641    }
642
643    #[inline]
644    fn unmap_shadow_by_id(&mut self, file_id: TypstFileId) -> FileResult<()> {
645        self.vfs.revise().remove_shadow_by_id(file_id);
646        Ok(())
647    }
648}
649
650impl<F: CompilerFeat> FsProvider for CompilerWorld<F> {
651    fn file_path(&self, file_id: TypstFileId) -> FileResult<PathResolution> {
652        self.vfs.file_path(file_id)
653    }
654
655    fn read(&self, file_id: TypstFileId) -> FileResult<Bytes> {
656        self.vfs.read(file_id)
657    }
658
659    fn read_source(&self, file_id: TypstFileId) -> FileResult<Source> {
660        self.vfs.source(file_id)
661    }
662}
663
664impl<F: CompilerFeat> World for CompilerWorld<F> {
665    /// The standard library.
666    fn library(&self) -> &LazyHash<Library> {
667        self.library.as_ref()
668    }
669
670    /// Access the main source file.
671    fn main(&self) -> FileId {
672        self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
673    }
674
675    /// Metadata about all known fonts.
676    fn font(&self, id: usize) -> Option<Font> {
677        self.font_resolver.font(id)
678    }
679
680    /// Try to access the specified file.
681    fn book(&self) -> &LazyHash<FontBook> {
682        self.font_resolver.font_book()
683    }
684
685    /// Try to access the specified source file.
686    ///
687    /// The returned `Source` file's [id](Source::id) does not have to match the
688    /// given `id`. Due to symlinks, two different file id's can point to the
689    /// same on-disk file. Implementers can deduplicate and return the same
690    /// `Source` if they want to, but do not have to.
691    fn source(&self, id: FileId) -> FileResult<Source> {
692        static DETACH_SOURCE: LazyLock<Source> =
693            LazyLock::new(|| Source::new(*DETACHED_ENTRY, String::new()));
694
695        if id == *DETACHED_ENTRY {
696            return Ok(DETACH_SOURCE.clone());
697        }
698
699        self.source_db.source(id, self)
700    }
701
702    /// Try to access the specified file.
703    fn file(&self, id: FileId) -> FileResult<Bytes> {
704        self.source_db.file(id, self)
705    }
706
707    /// Get the current date.
708    ///
709    /// If no offset is specified, the local date should be chosen. Otherwise,
710    /// the UTC date should be chosen with the corresponding offset in hours.
711    ///
712    /// If this function returns `None`, Typst's `datetime` function will
713    /// return an error.
714    #[cfg(any(feature = "web", feature = "system"))]
715    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
716        use chrono::{Datelike, Duration};
717        // todo: typst respects creation_timestamp, but we don't...
718        let now = self.now.get_or_init(|| tinymist_std::time::now().into());
719
720        let naive = match offset {
721            None => now.naive_local(),
722            Some(o) => now.naive_utc() + Duration::try_hours(o)?,
723        };
724
725        Datetime::from_ymd(
726            naive.year(),
727            naive.month().try_into().ok()?,
728            naive.day().try_into().ok()?,
729        )
730    }
731
732    /// Get the current date.
733    ///
734    /// If no offset is specified, the local date should be chosen. Otherwise,
735    /// the UTC date should be chosen with the corresponding offset in hours.
736    ///
737    /// If this function returns `None`, Typst's `datetime` function will
738    /// return an error.
739    #[cfg(not(any(feature = "web", feature = "system")))]
740    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
741        use tinymist_std::time::{now, to_typst_time, Duration};
742        // todo: typst respects creation_timestamp, but we don't...
743        let now = self.now.get_or_init(|| now().into());
744
745        let now = offset
746            .and_then(|offset| {
747                let dur = Duration::from_secs(offset.checked_mul(3600)? as u64)
748                    .try_into()
749                    .ok()?;
750                now.checked_add(dur)
751            })
752            .unwrap_or(*now);
753
754        Some(to_typst_time(now))
755    }
756}
757
758impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
759    fn entry_state(&self) -> EntryState {
760        self.entry.clone()
761    }
762}
763
764impl<F: CompilerFeat> WorldDeps for CompilerWorld<F> {
765    #[inline]
766    fn iter_dependencies(&self, f: &mut dyn FnMut(TypstFileId)) {
767        self.source_db.iter_dependencies_dyn(f)
768    }
769}
770
771/// Runs a world with a main file.
772pub fn with_main(world: &dyn World, id: FileId) -> WorldWithMain<'_> {
773    WorldWithMain { world, main: id }
774}
775
776pub struct WorldWithMain<'a> {
777    world: &'a dyn World,
778    main: FileId,
779}
780
781impl typst::World for WorldWithMain<'_> {
782    fn main(&self) -> FileId {
783        self.main
784    }
785
786    fn source(&self, id: FileId) -> FileResult<Source> {
787        self.world.source(id)
788    }
789
790    fn library(&self) -> &LazyHash<Library> {
791        self.world.library()
792    }
793
794    fn book(&self) -> &LazyHash<FontBook> {
795        self.world.book()
796    }
797
798    fn file(&self, id: FileId) -> FileResult<Bytes> {
799        self.world.file(id)
800    }
801
802    fn font(&self, index: usize) -> Option<Font> {
803        self.world.font(index)
804    }
805
806    fn today(&self, offset: Option<i64>) -> Option<Datetime> {
807        self.world.today(offset)
808    }
809}
810
811pub trait SourceWorld: World {
812    fn as_world(&self) -> &dyn World;
813
814    fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError>;
815    fn lookup(&self, id: FileId) -> Source {
816        self.source(id)
817            .expect("file id does not point to any source file")
818    }
819}
820
821impl<F: CompilerFeat> SourceWorld for CompilerWorld<F> {
822    fn as_world(&self) -> &dyn World {
823        self
824    }
825
826    /// Resolve the real path for a file id.
827    fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
828        self.path_for_id(id)
829    }
830}
831
832pub struct CodeSpanReportWorld<'a> {
833    pub world: &'a dyn SourceWorld,
834}
835
836impl<'a> CodeSpanReportWorld<'a> {
837    pub fn new(world: &'a dyn SourceWorld) -> Self {
838        Self { world }
839    }
840}
841
842impl<'a> codespan_reporting::files::Files<'a> for CodeSpanReportWorld<'a> {
843    /// A unique identifier for files in the file provider. This will be used
844    /// for rendering `diagnostic::Label`s in the corresponding source files.
845    type FileId = FileId;
846
847    /// The user-facing name of a file, to be displayed in diagnostics.
848    type Name = String;
849
850    /// The source code of a file.
851    type Source = Source;
852
853    /// The user-facing name of a file.
854    fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
855        Ok(match self.world.path_for_id(id) {
856            Ok(path) => path.as_path().display().to_string(),
857            Err(_) => format!("{id:?}"),
858        })
859    }
860
861    /// The source code of a file.
862    fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
863        Ok(self.world.lookup(id))
864    }
865
866    /// See [`codespan_reporting::files::Files::line_index`].
867    fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
868        let source = self.world.lookup(id);
869        source
870            .byte_to_line(given)
871            .ok_or_else(|| CodespanError::IndexTooLarge {
872                given,
873                max: source.len_bytes(),
874            })
875    }
876
877    /// See [`codespan_reporting::files::Files::column_number`].
878    fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
879        let source = self.world.lookup(id);
880        source.byte_to_column(given).ok_or_else(|| {
881            let max = source.len_bytes();
882            if given <= max {
883                CodespanError::InvalidCharBoundary { given }
884            } else {
885                CodespanError::IndexTooLarge { given, max }
886            }
887        })
888    }
889
890    /// See [`codespan_reporting::files::Files::line_range`].
891    fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
892        match self.world.source(id).ok() {
893            Some(source) => {
894                source
895                    .line_to_range(given)
896                    .ok_or_else(|| CodespanError::LineTooLarge {
897                        given,
898                        max: source.len_lines(),
899                    })
900            }
901            None => Ok(0..0),
902        }
903    }
904}
905
906// todo: remove me
907impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
908    /// A unique identifier for files in the file provider. This will be used
909    /// for rendering `diagnostic::Label`s in the corresponding source files.
910    type FileId = FileId;
911
912    /// The user-facing name of a file, to be displayed in diagnostics.
913    type Name = String;
914
915    /// The source code of a file.
916    type Source = Source;
917
918    /// The user-facing name of a file.
919    fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
920        CodeSpanReportWorld::new(self).name(id)
921    }
922
923    /// The source code of a file.
924    fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
925        CodeSpanReportWorld::new(self).source(id)
926    }
927
928    /// See [`codespan_reporting::files::Files::line_index`].
929    fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
930        CodeSpanReportWorld::new(self).line_index(id, given)
931    }
932
933    /// See [`codespan_reporting::files::Files::column_number`].
934    fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
935        CodeSpanReportWorld::new(self).column_number(id, 0, given)
936    }
937
938    /// See [`codespan_reporting::files::Files::line_range`].
939    fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
940        CodeSpanReportWorld::new(self).line_range(id, given)
941    }
942}
943
944#[comemo::memoize]
945fn create_library(inputs: Arc<LazyHash<Dict>>, features: Features) -> Arc<LazyHash<Library>> {
946    let lib = typst::Library::builder()
947        .with_inputs(inputs.deref().deref().clone())
948        .with_features(features)
949        .build();
950
951    Arc::new(LazyHash::new(lib))
952}