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