tinymist_world/
world.rs

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