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