1use std::{
2 num::NonZeroUsize,
3 ops::Deref,
4 path::{Path, PathBuf},
5 sync::{Arc, LazyLock, OnceLock},
6};
7
8use chrono::{DateTime, Datelike, Local};
9use parking_lot::RwLock;
10use reflexo::error::prelude::*;
11use reflexo::ImmutPath;
12use reflexo_vfs::{notify::FilesystemEvent, Vfs};
13use typst::{
14 diag::{eco_format, At, EcoString, FileError, FileResult, SourceResult},
15 foundations::{Bytes, Datetime, Dict},
16 syntax::{FileId, Source, Span},
17 text::{Font, FontBook},
18 utils::LazyHash,
19 Library, World,
20};
21
22use crate::{
23 entry::{EntryManager, EntryReader, EntryState, DETACHED_ENTRY},
24 font::FontResolver,
25 package::{PackageRegistry, PackageSpec},
26 parser::{
27 get_semantic_tokens_full, get_semantic_tokens_legend, OffsetEncoding, SemanticToken,
28 SemanticTokensLegend,
29 },
30 source::{SharedState, SourceCache, SourceDb},
31 CodespanError, CodespanResult, CompilerFeat, ShadowApi, WorldDeps,
32};
33
34pub struct Revising<'a, T> {
35 pub revision: NonZeroUsize,
36 pub inner: &'a mut T,
37}
38
39impl<T> std::ops::Deref for Revising<'_, T> {
40 type Target = T;
41
42 fn deref(&self) -> &Self::Target {
43 self.inner
44 }
45}
46
47impl<T> std::ops::DerefMut for Revising<'_, T> {
48 fn deref_mut(&mut self) -> &mut Self::Target {
49 self.inner
50 }
51}
52
53impl<F: CompilerFeat> Revising<'_, CompilerUniverse<F>> {
54 pub fn vfs(&mut self) -> &mut Vfs<F::AccessModel> {
55 &mut self.inner.vfs
56 }
57
58 pub fn notify_fs_event(&mut self, event: FilesystemEvent) {
62 self.inner.vfs.notify_fs_event(event);
63 }
64
65 pub fn reset_shadow(&mut self) {
66 self.inner.vfs.reset_shadow()
67 }
68
69 pub fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
70 self.inner.vfs.map_shadow(path, content)
71 }
72
73 pub fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
74 self.inner.vfs.remove_shadow(path);
75 Ok(())
76 }
77
78 pub fn set_do_reparse(&mut self, do_reparse: bool) {
80 self.inner.do_reparse = do_reparse;
81 }
82
83 pub fn set_inputs(&mut self, inputs: Arc<LazyHash<Dict>>) {
85 self.inner.inputs = inputs;
86 }
87
88 pub fn set_entry_file(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
89 self.inner.set_entry_file_(entry_file)
90 }
91
92 pub fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
93 self.inner.mutate_entry_(state)
94 }
95}
96
97#[derive(Debug)]
102pub struct CompilerUniverse<F: CompilerFeat> {
103 entry: EntryState,
106 inputs: Arc<LazyHash<Dict>>,
108 do_reparse: bool,
110
111 pub font_resolver: Arc<F::FontResolver>,
113 pub registry: Arc<F::Registry>,
115 vfs: Vfs<F::AccessModel>,
117
118 pub revision: RwLock<NonZeroUsize>,
120 pub shared: Arc<RwLock<SharedState<SourceCache>>>,
122}
123
124impl<F: CompilerFeat> CompilerUniverse<F> {
126 pub fn new_raw(
133 entry: EntryState,
134 inputs: Option<Arc<LazyHash<Dict>>>,
135 vfs: Vfs<F::AccessModel>,
136 registry: F::Registry,
137 font_resolver: Arc<F::FontResolver>,
138 ) -> Self {
139 Self {
140 entry,
141 inputs: inputs.unwrap_or_default(),
142 do_reparse: true,
143
144 revision: RwLock::new(NonZeroUsize::new(1).expect("initial revision is 1")),
145 shared: Arc::new(RwLock::new(SharedState::default())),
146
147 font_resolver,
148 registry: Arc::new(registry),
149 vfs,
150 }
151 }
152
153 pub fn with_entry_file(mut self, entry_file: PathBuf) -> Self {
155 let _ = self.increment_revision(|this| this.set_entry_file_(entry_file.as_path().into()));
156 self
157 }
158
159 pub fn do_reparse(&self) -> bool {
160 self.do_reparse
161 }
162
163 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
164 self.inputs.clone()
165 }
166
167 pub fn snapshot(&self) -> CompilerWorld<F> {
168 self.snapshot_with(None)
169 }
170
171 pub fn snapshot_with(&self, mutant: Option<TaskInputs>) -> CompilerWorld<F> {
172 let rev_lock = self.revision.read();
173
174 let w = CompilerWorld {
175 entry: self.entry.clone(),
176 inputs: self.inputs.clone(),
177 library: create_library(self.inputs.clone()),
178 font_resolver: self.font_resolver.clone(),
179 registry: self.registry.clone(),
180 vfs: self.vfs.snapshot(),
181 source_db: SourceDb {
182 revision: *rev_lock,
183 do_reparse: self.do_reparse,
184 shared: self.shared.clone(),
185 slots: Default::default(),
186 },
187 now: OnceLock::new(),
188 };
189
190 mutant.map(|m| w.task(m)).unwrap_or(w)
191 }
192
193 pub fn increment_revision<T>(&mut self, f: impl FnOnce(&mut Revising<Self>) -> T) -> T {
195 let rev_lock = self.revision.get_mut();
196 *rev_lock = rev_lock.checked_add(1).unwrap();
197 let revision = *rev_lock;
198 f(&mut Revising {
199 inner: self,
200 revision,
201 })
202 }
203
204 fn mutate_entry_(&mut self, mut state: EntryState) -> SourceResult<EntryState> {
206 self.reset();
207 std::mem::swap(&mut self.entry, &mut state);
208 Ok(state)
209 }
210
211 fn set_entry_file_(&mut self, entry_file: Arc<Path>) -> SourceResult<()> {
213 let state = self.entry_state();
214 let state = state
215 .try_select_path_in_workspace(&entry_file, true)
216 .map_err(|e| eco_format!("cannot select entry file out of workspace: {e}"))
217 .at(Span::detached())?
218 .ok_or_else(|| eco_format!("failed to determine root"))
219 .at(Span::detached())?;
220
221 self.mutate_entry_(state).map(|_| ())?;
222 Ok(())
223 }
224}
225
226impl<F: CompilerFeat> CompilerUniverse<F> {
227 pub fn reset(&mut self) {
229 self.vfs.reset();
230 }
232
233 pub fn path_for_id(&self, id: FileId) -> Result<PathBuf, FileError> {
235 if id == *DETACHED_ENTRY {
236 return Ok(DETACHED_ENTRY.vpath().as_rooted_path().to_owned());
237 }
238
239 let root = match id.package() {
242 Some(spec) => self.registry.resolve(spec)?,
243 None => self.entry.root().ok_or(FileError::Other(Some(eco_format!(
244 "cannot access directory without root: state: {:?}",
245 self.entry
246 ))))?,
247 };
248
249 id.vpath().resolve(&root).ok_or(FileError::AccessDenied)
252 }
253
254 pub fn get_semantic_token_legend(&self) -> Arc<SemanticTokensLegend> {
255 Arc::new(get_semantic_tokens_legend())
256 }
257
258 pub fn get_semantic_tokens(
259 &self,
260 file_path: Option<String>,
261 encoding: OffsetEncoding,
262 ) -> ZResult<Arc<Vec<SemanticToken>>> {
263 let world = match file_path {
264 Some(e) => {
265 let path = Path::new(&e);
266 let s = self
267 .entry_state()
268 .try_select_path_in_workspace(path, true)?
269 .ok_or_else(|| error_once!("cannot select file", path: e))?;
270
271 self.snapshot_with(Some(TaskInputs {
272 entry: Some(s),
273 inputs: None,
274 }))
275 }
276 None => self.snapshot(),
277 };
278
279 let src = world
280 .source(world.main())
281 .map_err(|e| error_once!("cannot access source file", err: e))?;
282 Ok(Arc::new(get_semantic_tokens_full(&src, encoding)))
283 }
284}
285
286impl<F: CompilerFeat> ShadowApi for CompilerUniverse<F> {
287 #[inline]
288 fn _shadow_map_id(&self, file_id: FileId) -> FileResult<PathBuf> {
289 self.path_for_id(file_id)
290 }
291
292 #[inline]
293 fn shadow_paths(&self) -> Vec<Arc<Path>> {
294 self.vfs.shadow_paths()
295 }
296
297 #[inline]
298 fn reset_shadow(&mut self) {
299 self.increment_revision(|this| this.vfs.reset_shadow())
300 }
301
302 #[inline]
303 fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
304 self.increment_revision(|this| this.vfs().map_shadow(path, content))
305 }
306
307 #[inline]
308 fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
309 self.increment_revision(|this| {
310 this.vfs().remove_shadow(path);
311 Ok(())
312 })
313 }
314}
315
316impl<F: CompilerFeat> EntryReader for CompilerUniverse<F> {
317 fn entry_state(&self) -> EntryState {
318 self.entry.clone()
319 }
320}
321
322impl<F: CompilerFeat> EntryManager for CompilerUniverse<F> {
323 fn reset(&mut self) -> SourceResult<()> {
324 self.reset();
325 Ok(())
326 }
327
328 fn mutate_entry(&mut self, state: EntryState) -> SourceResult<EntryState> {
329 self.increment_revision(|this| this.mutate_entry_(state))
330 }
331}
332
333pub struct CompilerWorld<F: CompilerFeat> {
334 entry: EntryState,
337 inputs: Arc<LazyHash<Dict>>,
339
340 pub library: Arc<LazyHash<Library>>,
342 pub font_resolver: Arc<F::FontResolver>,
344 pub registry: Arc<F::Registry>,
346 vfs: Vfs<F::AccessModel>,
348
349 pub source_db: SourceDb,
351 now: OnceLock<DateTime<Local>>,
354}
355
356impl<F: CompilerFeat> Clone for CompilerWorld<F> {
357 fn clone(&self) -> Self {
358 self.task(TaskInputs::default())
359 }
360}
361
362impl<F: CompilerFeat> Drop for CompilerWorld<F> {
363 fn drop(&mut self) {
364 let state = self.source_db.shared.clone();
365 let source_state = self.source_db.take_state();
366 let mut state = state.write();
367 source_state.commit_impl(&mut state);
368 }
369}
370
371#[derive(Default)]
372pub struct TaskInputs {
373 pub entry: Option<EntryState>,
374 pub inputs: Option<Arc<LazyHash<Dict>>>,
375}
376
377impl<F: CompilerFeat> CompilerWorld<F> {
378 pub fn task(&self, mutant: TaskInputs) -> CompilerWorld<F> {
379 let _ = self.today(None);
381
382 let library = mutant.inputs.clone().map(create_library);
383
384 CompilerWorld {
385 inputs: mutant.inputs.unwrap_or_else(|| self.inputs.clone()),
386 library: library.unwrap_or_else(|| self.library.clone()),
387 entry: mutant.entry.unwrap_or_else(|| self.entry.clone()),
388 font_resolver: self.font_resolver.clone(),
389 registry: self.registry.clone(),
390 vfs: self.vfs.snapshot(),
391 source_db: self.source_db.clone(),
392 now: self.now.clone(),
393 }
394 }
395
396 pub fn inputs(&self) -> Arc<LazyHash<Dict>> {
397 self.inputs.clone()
398 }
399
400 pub fn path_for_id(&self, id: FileId) -> Result<PathBuf, FileError> {
402 if id == *DETACHED_ENTRY {
403 return Ok(DETACHED_ENTRY.vpath().as_rooted_path().to_owned());
404 }
405
406 let root = match id.package() {
409 Some(spec) => self.registry.resolve(spec)?,
410 None => self.entry.root().ok_or(FileError::Other(Some(eco_format!(
411 "cannot access directory without root: state: {:?}",
412 self.entry
413 ))))?,
414 };
415
416 id.vpath().resolve(&root).ok_or(FileError::AccessDenied)
419 }
420 #[track_caller]
422 fn lookup(&self, id: FileId) -> Source {
423 self.source(id)
424 .expect("file id does not point to any source file")
425 }
426
427 fn map_source_or_default<T>(
428 &self,
429 id: FileId,
430 default_v: T,
431 f: impl FnOnce(Source) -> CodespanResult<T>,
432 ) -> CodespanResult<T> {
433 match World::source(self, id).ok() {
434 Some(source) => f(source),
435 None => Ok(default_v),
436 }
437 }
438
439 pub fn revision(&self) -> NonZeroUsize {
440 self.source_db.revision
441 }
442}
443
444impl<F: CompilerFeat> ShadowApi for CompilerWorld<F> {
445 #[inline]
446 fn _shadow_map_id(&self, file_id: FileId) -> FileResult<PathBuf> {
447 self.path_for_id(file_id)
448 }
449
450 #[inline]
451 fn shadow_paths(&self) -> Vec<Arc<Path>> {
452 self.vfs.shadow_paths()
453 }
454
455 #[inline]
456 fn reset_shadow(&mut self) {
457 self.vfs.reset_shadow()
458 }
459
460 #[inline]
461 fn map_shadow(&mut self, path: &Path, content: Bytes) -> FileResult<()> {
462 self.vfs.map_shadow(path, content)
463 }
464
465 #[inline]
466 fn unmap_shadow(&mut self, path: &Path) -> FileResult<()> {
467 self.vfs.remove_shadow(path);
468 Ok(())
469 }
470}
471
472impl<F: CompilerFeat> World for CompilerWorld<F> {
473 fn library(&self) -> &LazyHash<Library> {
475 self.library.as_ref()
476 }
477
478 fn main(&self) -> FileId {
480 self.entry.main().unwrap_or_else(|| *DETACHED_ENTRY)
481 }
482
483 fn font(&self, id: usize) -> Option<Font> {
485 self.font_resolver.font(id)
486 }
487
488 fn book(&self) -> &LazyHash<FontBook> {
490 self.font_resolver.font_book()
491 }
492
493 fn source(&self, id: FileId) -> FileResult<Source> {
500 static DETACH_SOURCE: LazyLock<Source> =
501 LazyLock::new(|| Source::new(*DETACHED_ENTRY, String::new()));
502
503 if id == *DETACHED_ENTRY {
504 return Ok(DETACH_SOURCE.clone());
505 }
506
507 let fid = self.vfs.file_id(&self.path_for_id(id)?);
508 self.source_db.source(id, fid, &self.vfs)
509 }
510
511 fn file(&self, id: FileId) -> FileResult<Bytes> {
513 let fid = self.vfs.file_id(&self.path_for_id(id)?);
514 self.source_db.file(id, fid, &self.vfs)
515 }
516
517 fn today(&self, offset: Option<i64>) -> Option<Datetime> {
525 let now = self.now.get_or_init(|| reflexo::time::now().into());
526
527 let naive = match offset {
528 None => now.naive_local(),
529 Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
530 };
531
532 Datetime::from_ymd(
533 naive.year(),
534 naive.month().try_into().ok()?,
535 naive.day().try_into().ok()?,
536 )
537 }
538
539 fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
546 self.registry.packages()
547 }
548}
549
550impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
551 fn entry_state(&self) -> EntryState {
552 self.entry.clone()
553 }
554}
555
556impl<F: CompilerFeat> WorldDeps for CompilerWorld<F> {
557 #[inline]
558 fn iter_dependencies(&self, f: &mut dyn FnMut(ImmutPath)) {
559 self.source_db.iter_dependencies_dyn(&self.vfs, f)
560 }
561}
562
563impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld<F> {
564 type FileId = FileId;
567
568 type Name = String;
570
571 type Source = Source;
573
574 fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
576 let vpath = id.vpath();
577 Ok(if let Some(package) = id.package() {
578 format!("{package}{}", vpath.as_rooted_path().display())
579 } else {
580 match self.entry.root() {
581 Some(root) => {
582 vpath
584 .resolve(&root)
585 .as_deref()
588 .unwrap_or_else(|| vpath.as_rootless_path())
589 .to_string_lossy()
590 .into()
591 }
592 None => vpath.as_rooted_path().display().to_string(),
593 }
594 })
595 }
596
597 fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
599 Ok(self.lookup(id))
600 }
601
602 fn line_index(&'a self, id: FileId, given: usize) -> CodespanResult<usize> {
604 let source = self.lookup(id);
605 source
606 .byte_to_line(given)
607 .ok_or_else(|| CodespanError::IndexTooLarge {
608 given,
609 max: source.len_bytes(),
610 })
611 }
612
613 fn column_number(&'a self, id: FileId, _: usize, given: usize) -> CodespanResult<usize> {
615 let source = self.lookup(id);
616 source.byte_to_column(given).ok_or_else(|| {
617 let max = source.len_bytes();
618 if given <= max {
619 CodespanError::InvalidCharBoundary { given }
620 } else {
621 CodespanError::IndexTooLarge { given, max }
622 }
623 })
624 }
625
626 fn line_range(&'a self, id: FileId, given: usize) -> CodespanResult<std::ops::Range<usize>> {
628 self.map_source_or_default(id, 0..0, |source| {
629 source
630 .line_to_range(given)
631 .ok_or_else(|| CodespanError::LineTooLarge {
632 given,
633 max: source.len_lines(),
634 })
635 })
636 }
637}
638
639#[comemo::memoize]
640fn create_library(inputs: Arc<LazyHash<Dict>>) -> Arc<LazyHash<Library>> {
641 let lib = typst::Library::builder()
642 .with_inputs(inputs.deref().deref().clone())
643 .build();
644
645 Arc::new(LazyHash::new(lib))
646}