1use std::{
2 borrow::Cow,
3 num::NonZeroUsize,
4 ops::Deref,
5 path::{Path, PathBuf},
6 sync::{Arc, LazyLock, OnceLock},
7};
8
9use chrono::{DateTime, Datelike, Local};
10use tinymist_std::error::prelude::*;
11use tinymist_vfs::{
12 FsProvider, PathResolution, RevisingVfs, SourceCache, TypstFileId, Vfs, WorkspaceResolver,
13};
14use typst::{
15 diag::{eco_format, At, EcoString, FileError, FileResult, SourceResult},
16 foundations::{Bytes, Datetime, Dict},
17 syntax::{FileId, Source, Span, VirtualPath},
18 text::{Font, FontBook},
19 utils::LazyHash,
20 Library, World,
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};
35use 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#[derive(Debug)]
49pub struct CompilerUniverse<F: CompilerFeat> {
50 entry: EntryState,
53 enable_html: bool,
55 inputs: Arc<LazyHash<Dict>>,
57
58 pub font_resolver: Arc<F::FontResolver>,
60 pub registry: Arc<F::Registry>,
62 vfs: Vfs<F::AccessModel>,
64
65 pub revision: NonZeroUsize,
67}
68
69impl<F: CompilerFeat> CompilerUniverse<F> {
71 pub fn new_raw(
78 entry: EntryState,
79 enable_html: bool,
80 inputs: Option<Arc<LazyHash<Dict>>>,
81 vfs: Vfs<F::AccessModel>,
82 registry: Arc<F::Registry>,
83 font_resolver: Arc<F::FontResolver>,
84 ) -> Self {
85 Self {
86 entry,
87 enable_html,
88 inputs: inputs.unwrap_or_default(),
89
90 revision: NonZeroUsize::new(1).expect("initial revision is 1"),
91
92 font_resolver,
93 registry,
94 vfs,
95 }
96 }
97
98 pub fn with_entry_file(mut self, entry_file: PathBuf) -> Self {
100 let _ = self.increment_revision(|this| this.set_entry_file_(entry_file.as_path().into()));
101 self
102 }
103
104 pub fn entry_file(&self) -> Option<PathResolution> {
105 self.path_for_id(self.main_id()?).ok()
106 }
107
108 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
109 self.inputs.clone()
110 }
111
112 pub fn snapshot(&self) -> CompilerWorld<F> {
113 self.snapshot_with(None)
114 }
115
116 pub fn computation(&self) -> Arc<WorldComputeGraph<F>> {
118 let world = self.snapshot();
119 let snap = CompileSnapshot::from_world(world);
120 WorldComputeGraph::new(snap)
121 }
122
123 pub fn computation_with(&self, mutant: TaskInputs) -> Arc<WorldComputeGraph<F>> {
124 let world = self.snapshot_with(Some(mutant));
125 let snap = CompileSnapshot::from_world(world);
126 WorldComputeGraph::new(snap)
127 }
128
129 pub fn snapshot_with_entry_content(
130 &self,
131 content: Bytes,
132 inputs: Option<TaskInputs>,
133 ) -> Arc<WorldComputeGraph<F>> {
134 let mut world = if self.main_id().is_some() {
137 self.snapshot_with(inputs)
138 } else {
139 let world = self.snapshot_with(Some(TaskInputs {
140 entry: Some(
141 self.entry_state()
142 .select_in_workspace(MEMORY_MAIN_ENTRY.vpath().as_rooted_path()),
143 ),
144 inputs: inputs.and_then(|i| i.inputs),
145 }));
146
147 world
148 };
149
150 world.map_shadow_by_id(world.main(), content).unwrap();
151
152 let snap = CompileSnapshot::from_world(world);
153 WorldComputeGraph::new(snap)
154 }
155
156 pub fn snapshot_with(&self, mutant: Option<TaskInputs>) -> CompilerWorld<F> {
157 let w = CompilerWorld {
158 entry: self.entry.clone(),
159 enable_html: self.enable_html,
160 inputs: self.inputs.clone(),
161 library: create_library(self.inputs.clone(), self.enable_html),
162 font_resolver: self.font_resolver.clone(),
163 registry: self.registry.clone(),
164 vfs: self.vfs.snapshot(),
165 revision: self.revision,
166 source_db: SourceDb {
167 is_compiling: true,
168 slots: Default::default(),
169 },
170 now: OnceLock::new(),
171 };
172
173 mutant.map(|m| w.task(m)).unwrap_or(w)
174 }
175
176 pub fn increment_revision<T>(&mut self, f: impl FnOnce(&mut RevisingUniverse<F>) -> T) -> T {
178 f(&mut RevisingUniverse {
179 vfs_revision: self.vfs.revision(),
180 font_changed: false,
181 font_revision: self.font_resolver.revision(),
182 registry_changed: false,
183 registry_revision: self.registry.revision(),
184 view_changed: false,
185 inner: self,
186 })
187 }
188
189 fn mutate_entry_(&mut self, mut state: EntryState) -> SourceResult<EntryState> {
191 std::mem::swap(&mut self.entry, &mut state);
192 Ok(state)
193 }
194
195 fn set_entry_file_(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
197 let state = self.entry_state();
198 let state = state
199 .try_select_path_in_workspace(&entry_file)
200 .map_err(|e| eco_format!("cannot select entry file out of workspace: {e}"))
201 .at(Span::detached())?
202 .ok_or_else(|| eco_format!("failed to determine root"))
203 .at(Span::detached())?;
204
205 self.mutate_entry_(state).map(|_| ())?;
206 Ok(())
207 }
208
209 pub fn vfs(&self) -> &Vfs<F::AccessModel> {
210 &self.vfs
211 }
212}
213
214impl<F: CompilerFeat> CompilerUniverse<F> {
215 pub fn reset(&mut self) {
217 self.vfs.reset_all();
218 }
220
221 pub fn evict(&mut self, vfs_threshold: usize) {
223 self.vfs.reset_access_model();
224 self.vfs.evict(vfs_threshold);
225 }
226
227 pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
229 self.vfs.file_path(id)
230 }
231
232 pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
234 let root = self.entry.workspace_root()?;
235 Some(WorkspaceResolver::workspace_file(
236 Some(&root),
237 VirtualPath::new(path.strip_prefix(&root).ok()?),
238 ))
239 }
240
241 pub fn get_semantic_token_legend(&self) -> Arc<SemanticTokensLegend> {
242 Arc::new(get_semantic_tokens_legend())
243 }
244
245 pub fn get_semantic_tokens(
246 &self,
247 file_path: Option<String>,
248 encoding: OffsetEncoding,
249 ) -> Result<Arc<Vec<SemanticToken>>> {
250 let world = match file_path {
251 Some(e) => {
252 let path = Path::new(&e);
253 let s = self
254 .entry_state()
255 .try_select_path_in_workspace(path)?
256 .ok_or_else(|| error_once!("cannot select file", path: e))?;
257
258 self.snapshot_with(Some(TaskInputs {
259 entry: Some(s),
260 inputs: None,
261 }))
262 }
263 None => self.snapshot(),
264 };
265
266 let src = world
267 .source(world.main())
268 .map_err(|e| error_once!("cannot access source file", err: e))?;
269 Ok(Arc::new(get_semantic_tokens_full(&src, encoding)))
270 }
271}
272
273impl<F: CompilerFeat> ShadowApi for CompilerUniverse<F> {
274 #[inline]
275 fn reset_shadow(&mut self) {
276 self.increment_revision(|this| this.vfs.revise().reset_shadow())
277 }
278
279 fn shadow_paths(&self) -> Vec<Arc<Path>> {
280 self.vfs.shadow_paths()
281 }
282
283 fn shadow_ids(&self) -> Vec<TypstFileId> {
284 self.vfs.shadow_ids()
285 }
286
287 #[inline]
288 fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
289 self.increment_revision(|this| this.vfs().map_shadow(path, Ok(content).into()))
290 }
291
292 #[inline]
293 fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
294 self.increment_revision(|this| this.vfs().unmap_shadow(path))
295 }
296
297 #[inline]
298 fn map_shadow_by_id(&mut self, file_id: FileId, content: Bytes) -> FileResult<()> {
299 self.increment_revision(|this| this.vfs().map_shadow_by_id(file_id, Ok(content).into()))
300 }
301
302 #[inline]
303 fn unmap_shadow_by_id(&mut self, file_id: FileId) -> FileResult<()> {
304 self.increment_revision(|this| {
305 this.vfs().remove_shadow_by_id(file_id);
306 Ok(())
307 })
308 }
309}
310
311impl<F: CompilerFeat> EntryReader for CompilerUniverse<F> {
312 fn entry_state(&self) -> EntryState {
313 self.entry.clone()
314 }
315}
316
317impl<F: CompilerFeat> EntryManager for CompilerUniverse<F> {
318 fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
319 self.increment_revision(|this| this.mutate_entry_(state))
320 }
321}
322
323pub struct RevisingUniverse<'a, F: CompilerFeat> {
324 view_changed: bool,
325 vfs_revision: NonZeroUsize,
326 font_changed: bool,
327 font_revision: Option<NonZeroUsize>,
328 registry_changed: bool,
329 registry_revision: Option<NonZeroUsize>,
330 pub inner: &'a mut CompilerUniverse<F>,
331}
332
333impl<F: CompilerFeat> std::ops::Deref for RevisingUniverse<'_, F> {
334 type Target = CompilerUniverse<F>;
335
336 fn deref(&self) -> &Self::Target {
337 self.inner
338 }
339}
340
341impl<F: CompilerFeat> std::ops::DerefMut for RevisingUniverse<'_, F> {
342 fn deref_mut(&mut self) -> &mut Self::Target {
343 self.inner
344 }
345}
346
347impl<F: CompilerFeat> Drop for RevisingUniverse<'_, F> {
348 fn drop(&mut self) {
349 let mut view_changed = self.view_changed;
350 if self.font_changed() {
353 view_changed = true;
354 }
355 if self.registry_changed() {
358 view_changed = true;
359
360 log::info!("resetting shadow registry_changed");
362 self.vfs().reset_cache();
363 }
364 let view_changed = view_changed || self.vfs_changed();
365
366 if view_changed {
367 self.vfs.reset_access_model();
368 let revision = &mut self.revision;
369 *revision = revision.checked_add(1).unwrap();
370 }
371 }
372}
373
374impl<F: CompilerFeat> RevisingUniverse<'_, F> {
375 pub fn vfs(&mut self) -> RevisingVfs<'_, F::AccessModel> {
376 self.vfs.revise()
377 }
378
379 pub fn set_fonts(&mut self, fonts: Arc<F::FontResolver>) {
380 self.font_changed = true;
381 self.inner.font_resolver = fonts;
382 }
383
384 pub fn set_package(&mut self, packages: Arc<F::Registry>) {
385 self.registry_changed = true;
386 self.inner.registry = packages;
387 }
388
389 pub fn set_inputs(&mut self, inputs: Arc<LazyHash<Dict>>) {
391 self.view_changed = true;
392 self.inner.inputs = inputs;
393 }
394
395 pub fn set_entry_file(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
396 self.view_changed = true;
397 self.inner.set_entry_file_(entry_file)
398 }
399
400 pub fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
401 self.view_changed = true;
402
403 let root_changed = self.inner.entry.workspace_root() == state.workspace_root();
405 if root_changed {
406 log::info!("resetting shadow root_changed");
407 self.vfs().reset_cache();
408 }
409
410 self.inner.mutate_entry_(state)
411 }
412
413 pub fn flush(&mut self) {
414 self.view_changed = true;
415 }
416
417 pub fn font_changed(&self) -> bool {
418 self.font_changed && is_revision_changed(self.font_revision, self.font_resolver.revision())
419 }
420
421 pub fn registry_changed(&self) -> bool {
422 self.registry_changed
423 && is_revision_changed(self.registry_revision, self.registry.revision())
424 }
425
426 pub fn vfs_changed(&self) -> bool {
427 self.vfs_revision != self.vfs.revision()
428 }
429}
430
431fn is_revision_changed(a: Option<NonZeroUsize>, b: Option<NonZeroUsize>) -> bool {
432 a.is_none() || b.is_none() || a != b
433}
434
435pub struct CompilerWorld<F: CompilerFeat> {
436 entry: EntryState,
439 enable_html: bool,
441 inputs: Arc<LazyHash<Dict>>,
443
444 pub library: Arc<LazyHash<Library>>,
446 pub font_resolver: Arc<F::FontResolver>,
448 pub registry: Arc<F::Registry>,
450 vfs: Vfs<F::AccessModel>,
452
453 revision: NonZeroUsize,
454 source_db: SourceDb,
456 now: OnceLock<DateTime<Local>>,
459}
460
461impl<F: CompilerFeat> Clone for CompilerWorld<F> {
462 fn clone(&self) -> Self {
463 self.task(TaskInputs::default())
464 }
465}
466
467#[derive(Debug, Default)]
468pub struct TaskInputs {
469 pub entry: Option<EntryState>,
470 pub inputs: Option<Arc<LazyHash<Dict>>>,
471}
472
473impl<F: CompilerFeat> CompilerWorld<F> {
474 pub fn task(&self, mutant: TaskInputs) -> CompilerWorld<F> {
475 let _ = self.today(None);
477
478 let library = mutant
479 .inputs
480 .clone()
481 .map(|inputs| create_library(inputs, self.enable_html));
482
483 let root_changed = if let Some(e) = mutant.entry.as_ref() {
484 self.entry.workspace_root() != e.workspace_root()
485 } else {
486 false
487 };
488
489 let mut world = CompilerWorld {
490 enable_html: self.enable_html,
491 inputs: mutant.inputs.unwrap_or_else(|| self.inputs.clone()),
492 library: library.unwrap_or_else(|| self.library.clone()),
493 entry: mutant.entry.unwrap_or_else(|| self.entry.clone()),
494 font_resolver: self.font_resolver.clone(),
495 registry: self.registry.clone(),
496 vfs: self.vfs.snapshot(),
497 revision: self.revision,
498 source_db: self.source_db.clone(),
499 now: self.now.clone(),
500 };
501
502 if root_changed {
503 world.vfs.revise().reset_cache();
504 }
505
506 world
507 }
508
509 pub fn take_cache(&mut self) -> SourceCache {
510 self.vfs.take_source_cache()
511 }
512
513 pub fn clone_cache(&mut self) -> SourceCache {
514 self.vfs.clone_source_cache()
515 }
516
517 pub fn take_db(&mut self) -> SourceDb {
518 self.source_db.take_state()
519 }
520
521 pub fn vfs(&self) -> &Vfs<F::AccessModel> {
522 &self.vfs
523 }
524
525 pub fn set_is_compiling(&mut self, is_compiling: bool) {
529 self.source_db.is_compiling = is_compiling;
530 }
531
532 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
533 self.inputs.clone()
534 }
535
536 pub fn path_for_id(&self, id: FileId) -> Result<PathResolution, FileError> {
538 self.vfs.file_path(id)
539 }
540
541 pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
543 let root = self.entry.workspace_root()?;
544 Some(WorkspaceResolver::workspace_file(
545 Some(&root),
546 VirtualPath::new(path.strip_prefix(&root).ok()?),
547 ))
548 }
549
550 #[track_caller]
552 fn lookup(&self, id: FileId) -> Source {
553 self.source(id)
554 .expect("file id does not point to any source file")
555 }
556
557 fn map_source_or_default<T>(
558 &self,
559 id: FileId,
560 default_v: T,
561 f: impl FnOnce(Source) -> CodespanResult<T>,
562 ) -> CodespanResult<T> {
563 match World::source(self, id).ok() {
564 Some(source) => f(source),
565 None => Ok(default_v),
566 }
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 pub fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
590 self.registry.packages()
591 }
592
593 pub fn paged_task(&self) -> Cow<'_, CompilerWorld<F>> {
594 let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html);
595
596 if enabled_paged {
597 return Cow::Borrowed(self);
598 }
599
600 let mut world = self.clone();
601 world.library = create_library(world.inputs.clone(), false);
602
603 Cow::Owned(world)
604 }
605
606 pub fn html_task(&self) -> Cow<'_, CompilerWorld<F>> {
607 let enabled_html = self.library.features.is_enabled(typst::Feature::Html);
608
609 if enabled_html {
610 return Cow::Borrowed(self);
611 }
612
613 let mut world = self.clone();
614 world.library = create_library(world.inputs.clone(), true);
615
616 Cow::Owned(world)
617 }
618}
619
620impl<F: CompilerFeat> ShadowApi for CompilerWorld<F> {
621 #[inline]
622 fn shadow_ids(&self) -> Vec<TypstFileId> {
623 self.vfs.shadow_ids()
624 }
625
626 #[inline]
627 fn shadow_paths(&self) -> Vec<Arc<Path>> {
628 self.vfs.shadow_paths()
629 }
630
631 #[inline]
632 fn reset_shadow(&mut self) {
633 self.vfs.revise().reset_shadow()
634 }
635
636 #[inline]
637 fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
638 self.vfs.revise().map_shadow(path, Ok(content).into())
639 }
640
641 #[inline]
642 fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
643 self.vfs.revise().unmap_shadow(path)
644 }
645
646 #[inline]
647 fn map_shadow_by_id(&mut self, file_id: TypstFileId, content: Bytes) -> FileResult<()> {
648 self.vfs
649 .revise()
650 .map_shadow_by_id(file_id, Ok(content).into())
651 }
652
653 #[inline]
654 fn unmap_shadow_by_id(&mut self, file_id: TypstFileId) -> FileResult<()> {
655 self.vfs.revise().remove_shadow_by_id(file_id);
656 Ok(())
657 }
658}
659
660impl<F: CompilerFeat> FsProvider for CompilerWorld<F> {
661 fn file_path(&self, file_id: TypstFileId) -> FileResult<PathResolution> {
662 self.vfs.file_path(file_id)
663 }
664
665 fn read(&self, file_id: TypstFileId) -> FileResult<Bytes> {
666 self.vfs.read(file_id)
667 }
668
669 fn read_source(&self, file_id: TypstFileId) -> FileResult<Source> {
670 self.vfs.source(file_id)
671 }
672}
673
674impl<F: CompilerFeat> World for CompilerWorld<F> {
675 fn library(&self) -> &LazyHash<Library> {
677 self.library.as_ref()
678 }
679
680 fn main(&self) -> FileId {
682 self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
683 }
684
685 fn font(&self, id: usize) -> Option<Font> {
687 self.font_resolver.font(id)
688 }
689
690 fn book(&self) -> &LazyHash<FontBook> {
692 self.font_resolver.font_book()
693 }
694
695 fn source(&self, id: FileId) -> FileResult<Source> {
702 static DETACH_SOURCE: LazyLock<Source> =
703 LazyLock::new(|| Source::new(*DETACHED_ENTRY, String::new()));
704
705 if id == *DETACHED_ENTRY {
706 return Ok(DETACH_SOURCE.clone());
707 }
708
709 self.source_db.source(id, self)
710 }
711
712 fn file(&self, id: FileId) -> FileResult<Bytes> {
714 self.source_db.file(id, self)
715 }
716
717 fn today(&self, offset: Option<i64>) -> Option<Datetime> {
725 let now = self.now.get_or_init(|| tinymist_std::time::now().into());
726
727 let naive = match offset {
728 None => now.naive_local(),
729 Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
730 };
731
732 Datetime::from_ymd(
733 naive.year(),
734 naive.month().try_into().ok()?,
735 naive.day().try_into().ok()?,
736 )
737 }
738}
739
740impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
741 fn entry_state(&self) -> EntryState {
742 self.entry.clone()
743 }
744}
745
746impl<F: CompilerFeat> WorldDeps for CompilerWorld<F> {
747 #[inline]
748 fn iter_dependencies(&self, f: &mut dyn FnMut(TypstFileId)) {
749 self.source_db.iter_dependencies_dyn(f)
750 }
751}
752
753impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
754 type FileId = FileId;
757
758 type Name = String;
760
761 type Source = Source;
763
764 fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
766 Ok(match self.path_for_id(id) {
767 Ok(path) => path.as_path().display().to_string(),
768 Err(_) => format!("{id:?}"),
769 })
770 }
771
772 fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
774 Ok(self.lookup(id))
775 }
776
777 fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
779 let source = self.lookup(id);
780 source
781 .byte_to_line(given)
782 .ok_or_else(|| CodespanError::IndexTooLarge {
783 given,
784 max: source.len_bytes(),
785 })
786 }
787
788 fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
790 let source = self.lookup(id);
791 source.byte_to_column(given).ok_or_else(|| {
792 let max = source.len_bytes();
793 if given <= max {
794 CodespanError::InvalidCharBoundary { given }
795 } else {
796 CodespanError::IndexTooLarge { given, max }
797 }
798 })
799 }
800
801 fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
803 self.map_source_or_default(id, 0..0, |source| {
804 source
805 .line_to_range(given)
806 .ok_or_else(|| CodespanError::LineTooLarge {
807 given,
808 max: source.len_lines(),
809 })
810 })
811 }
812}
813
814#[comemo::memoize]
815fn create_library(inputs: Arc<LazyHash<Dict>>, enable_html: bool) -> Arc<LazyHash<Library>> {
816 let features = if enable_html {
817 typst::Features::from_iter([typst::Feature::Html])
818 } else {
819 typst::Features::default()
820 };
821
822 let lib = typst::Library::builder()
823 .with_inputs(inputs.deref().deref().clone())
824 .with_features(features)
825 .build();
826
827 Arc::new(LazyHash::new(lib))
828}