tinymist_world/
world.rs

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