1#![cfg(feature = "timer")]
7
8use std::fs::File;
9use std::io::BufWriter;
10use std::path::{Path, PathBuf};
11
12use typst_library::World;
13use typst_library::diag::{StrResult, bail};
14use typst_syntax::{Span, SpanKind};
15
16pub struct Timer {
18 path: Option<PathBuf>,
20 iter: usize,
22}
23
24impl Timer {
25 pub fn new(path: PathBuf) -> Self {
34 typst_timing::enable();
36 Self { path: Some(path), iter: 0 }
37 }
38
39 pub fn placeholder() -> Self {
43 Self { path: None, iter: 0 }
44 }
45
46 pub fn new_or_placeholder(path: Option<PathBuf>) -> Self {
49 match path {
50 Some(path) => Self::new(path),
51 None => Self::placeholder(),
52 }
53 }
54
55 pub fn record<W: World, T>(
58 &mut self,
59 world: &mut W,
60 f: impl FnOnce(&mut W) -> T,
61 ) -> StrResult<T> {
62 let Some(path) = &self.path else {
63 return Ok(f(world));
64 };
65
66 typst_timing::clear();
67
68 let string = path.to_str().unwrap_or_default();
69 let numbered = string.contains("{n}");
70 if !numbered && self.iter > 0 {
71 bail!("cannot export multiple recordings without `{{n}}` in path");
72 }
73
74 let storage;
75 let path = if numbered {
76 storage = string.replace("{n}", &self.iter.to_string());
77 Path::new(&storage)
78 } else {
79 path.as_path()
80 };
81
82 let output = f(world);
83 self.iter += 1;
84
85 let file =
86 File::create(path).map_err(|e| format!("failed to create file: {e}"))?;
87 let writer = BufWriter::with_capacity(1 << 20, file);
88
89 typst_timing::export_json(writer, |span| {
90 resolve_span(world, Span::from_raw(span))
91 .unwrap_or_else(|| ("unknown".to_string(), 0))
92 })?;
93
94 Ok(output)
95 }
96}
97
98fn resolve_span<W: World>(world: &W, span: Span) -> Option<(String, u32)> {
100 let (id, line) = match span.get() {
101 SpanKind::Detached => return None,
102 SpanKind::Number { id, num } => {
103 let source = world.source(id).ok()?;
104 let range = source.range(num, None)?;
105 let line = source.lines().byte_to_line(range.start)?;
106 (id, line)
107 }
108 SpanKind::Range { id, range } => {
109 let file = world.file(id).ok()?;
110 let lines = file.lines().ok()?;
111 let line = lines.byte_to_line(range.start)?;
112 (id, line)
113 }
114 };
115 Some((format!("{id:?}"), line as u32 + 1))
116}