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