tinymist_world/
world.rs

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