1use 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};
47use 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#[derive(Debug)]
61pub struct CompilerUniverse<F: CompilerFeat> {
62 entry: EntryState,
65 inputs: Arc<LazyHash<Dict>>,
67 pub features: Features,
69
70 pub font_resolver: Arc<F::FontResolver>,
72 pub registry: Arc<F::Registry>,
74 vfs: Vfs<F::AccessModel>,
76
77 pub revision: NonZeroUsize,
81
82 pub creation_timestamp: Option<i64>,
84}
85
86impl<F: CompilerFeat> CompilerUniverse<F> {
88 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 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 pub fn entry_file(&self) -> Option<PathResolution> {
125 self.path_for_id(self.main_id()?).ok()
126 }
127
128 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
130 self.inputs.clone()
131 }
132
133 pub fn snapshot(&self) -> CompilerWorld<F> {
135 self.snapshot_with(None)
136 }
137
138 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 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 pub fn snapshot_with_entry_content(
159 &self,
160 content: Bytes,
161 inputs: Option<TaskInputs>,
162 ) -> Arc<WorldComputeGraph<F>> {
163 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 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 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 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 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 pub fn vfs(&self) -> &Vfs<F::AccessModel> {
242 &self.vfs
243 }
244}
245
246impl<F: CompilerFeat> CompilerUniverse<F> {
247 pub fn reset(&mut self) {
249 self.vfs.reset_all();
250 }
252
253 pub fn evict(&mut self, vfs_threshold: usize) {
255 self.vfs.reset_access_model();
256 self.vfs.evict(vfs_threshold);
257 }
258
259 pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
261 self.vfs.file_path(id)
262 }
263
264 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 pub fn get_semantic_token_legend(&self) -> Arc<SemanticTokensLegend> {
275 Arc::new(get_semantic_tokens_legend())
276 }
277
278 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
357pub struct RevisingUniverse<'a, F: CompilerFeat> {
359 view_changed: bool,
361 vfs_revision: NonZeroUsize,
363 font_changed: bool,
365 creation_timestamp_changed: bool,
367 font_revision: Option<NonZeroUsize>,
369 registry_changed: bool,
371 registry_revision: Option<NonZeroUsize>,
373 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 self.font_changed() {
397 view_changed = true;
398 }
399 if self.registry_changed() {
402 view_changed = true;
403
404 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 pub fn vfs(&mut self) -> RevisingVfs<'_, F::AccessModel> {
421 self.vfs.revise()
422 }
423
424 pub fn set_fonts(&mut self, fonts: Arc<F::FontResolver>) {
426 self.font_changed = true;
427 self.inner.font_resolver = fonts;
428 }
429
430 pub fn set_package(&mut self, packages: Arc<F::Registry>) {
432 self.registry_changed = true;
433 self.inner.registry = packages;
434 }
435
436 pub fn set_inputs(&mut self, inputs: Arc<LazyHash<Dict>>) {
438 self.view_changed = true;
439 self.inner.inputs = inputs;
440 }
441
442 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 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 pub fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
456 self.view_changed = true;
457
458 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 pub fn flush(&mut self) {
470 self.view_changed = true;
471 }
472
473 pub fn font_changed(&self) -> bool {
475 self.font_changed && is_revision_changed(self.font_revision, self.font_resolver.revision())
476 }
477
478 pub fn creation_timestamp_changed(&self) -> bool {
480 self.creation_timestamp_changed
481 }
482
483 pub fn registry_changed(&self) -> bool {
485 self.registry_changed
486 && is_revision_changed(self.registry_revision, self.registry.revision())
487 }
488
489 pub fn vfs_changed(&self) -> bool {
491 self.vfs_revision != self.vfs.revision()
492 }
493}
494
495fn 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
514pub struct CompilerWorld<F: CompilerFeat> {
516 entry: EntryState,
519 inputs: Arc<LazyHash<Dict>>,
521 features: Features,
523
524 pub library: Arc<LazyHash<Library>>,
526 pub font_resolver: Arc<F::FontResolver>,
528 pub registry: Arc<F::Registry>,
530 vfs: Vfs<F::AccessModel>,
532
533 revision: NonZeroUsize,
534 source_db: SourceDb,
536 now: OnceLock<NowStorage>,
539 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#[derive(Debug, Default)]
551pub struct TaskInputs {
552 pub entry: Option<EntryState>,
554 pub inputs: Option<Arc<LazyHash<Dict>>>,
556}
557
558impl<F: CompilerFeat> CompilerWorld<F> {
559 pub fn task(&self, mutant: TaskInputs) -> CompilerWorld<F> {
561 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 pub fn reset_read(&mut self) {
598 self.vfs.reset_read();
599 }
600
601 pub fn take_source_cache(&mut self) -> SourceCache {
603 self.vfs.take_source_cache()
604 }
605
606 pub fn clone_source_cache(&mut self) -> SourceCache {
608 self.vfs.clone_source_cache()
609 }
610
611 pub fn take_db(&mut self) -> SourceDb {
613 self.source_db.take()
614 }
615
616 pub fn vfs(&self) -> &Vfs<F::AccessModel> {
618 &self.vfs
619 }
620
621 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
623 self.inputs.clone()
624 }
625
626 pub fn set_is_compiling(&mut self, is_compiling: bool) {
630 self.source_db.is_compiling = is_compiling;
631 }
632
633 pub fn revision(&self) -> NonZeroUsize {
635 self.revision
636 }
637
638 pub fn evict_vfs(&mut self, threshold: usize) {
640 self.vfs.evict(threshold);
641 }
642
643 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 pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
652 self.vfs.file_path(id)
653 }
654
655 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 pub fn file_id_by_path(&self, path: &Path) -> FileResult<FileId> {
666 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 pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
678 self.source(self.file_id_by_path(path)?)
679 }
680
681 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 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 pub fn packages(&self) -> &[PackageIndexEntry] {
708 self.registry.packages()
709 }
710
711 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 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 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 fn library(&self) -> &LazyHash<Library> {
802 self.library.as_ref()
803 }
804
805 fn main(&self) -> FileId {
807 self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
808 }
809
810 fn font(&self, id: usize) -> Option<Font> {
812 self.font_resolver.font(id)
813 }
814
815 fn book(&self) -> &LazyHash<FontBook> {
817 self.font_resolver.font_book()
818 }
819
820 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 fn file(&self, id: FileId) -> FileResult<Bytes> {
839 self.source_db.file(id, self)
840 }
841
842 #[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 #[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
923pub fn with_main(world: &dyn World, id: FileId) -> WorldWithMain<'_> {
925 WorldWithMain { world, main: id }
926}
927
928pub 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
964pub trait SourceWorld: World {
966 fn as_world(&self) -> &dyn World;
968
969 fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError>;
971
972 fn lookup(&self, id: FileId) -> Source {
974 self.source(id)
975 .expect("file id does not point to any source file")
976 }
977
978 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 fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
991 self.path_for_id(id)
992 }
993}
994
995pub struct CodeSpanReportWorld<'a> {
997 pub world: &'a dyn SourceWorld,
999}
1000
1001impl<'a> CodeSpanReportWorld<'a> {
1002 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 type FileId = FileId;
1012
1013 type Name = String;
1015
1016 type Source = Source;
1018
1019 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 fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
1029 Ok(self.world.lookup(id))
1030 }
1031
1032 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 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 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
1074impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
1076 type FileId = FileId;
1079
1080 type Name = String;
1082
1083 type Source = Source;
1085
1086 fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
1088 CodeSpanReportWorld::new(self).name(id)
1089 }
1090
1091 fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
1093 CodeSpanReportWorld::new(self).source(id)
1094 }
1095
1096 fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
1098 CodeSpanReportWorld::new(self).line_index(id, given)
1099 }
1100
1101 fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
1103 CodeSpanReportWorld::new(self).column_number(id, 0, given)
1104 }
1105
1106 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}