typst_library/introspection/
location.rs1use std::fmt::{self, Debug, Formatter};
2use std::num::NonZeroUsize;
3
4use comemo::Tracked;
5use ecow::{EcoString, eco_format};
6use typst_syntax::{Span, VirtualPath};
7use typst_utils::NonZeroExt;
8
9use crate::diag::{SourceDiagnostic, warning};
10use crate::engine::Engine;
11use crate::foundations::{Content, IntoValue, Repr, Selector, func, repr, scope, ty};
12use crate::introspection::{
13 DocumentPosition, History, Introspect, Introspector, PagedPosition,
14};
15use crate::layout::Abs;
16use crate::model::Numbering;
17
18pub trait Locatable {}
20
21pub trait Unqueriable: Locatable {}
23
24pub trait Tagged {}
26
27#[ty(scope)]
58#[derive(Copy, Clone, Eq, PartialEq, Hash)]
59pub struct Location(u128);
60
61impl Location {
62 pub fn new(hash: u128) -> Self {
64 Self(hash)
65 }
66
67 pub fn hash(self) -> u128 {
69 self.0
70 }
71
72 pub fn variant(self, n: usize) -> Self {
78 Self(typst_utils::hash128(&(self.0, n)))
79 }
80}
81
82#[scope]
83impl Location {
84 #[func]
102 pub fn page(self, engine: &mut Engine, span: Span) -> NonZeroUsize {
103 engine.introspect(PageIntrospection(self, span))
104 }
105
106 #[func]
113 pub fn position(self, engine: &mut Engine, span: Span) -> PagedPosition {
114 engine.introspect(PositionIntrospection(self, span))
115 }
116
117 #[func]
125 pub fn page_numbering(self, engine: &mut Engine, span: Span) -> Option<Numbering> {
126 engine.introspect(PageNumberingIntrospection(self, span))
127 }
128}
129
130impl Debug for Location {
131 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
132 if f.alternate() {
133 write!(f, "Location({})", self.0)
134 } else {
135 let truncated = self.0 as u16;
137 write!(f, "Location({truncated})")
138 }
139 }
140}
141
142impl Repr for Location {
143 fn repr(&self) -> EcoString {
144 "location(..)".into()
145 }
146}
147
148#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
157pub struct LocationKey(u128);
158
159impl LocationKey {
160 pub fn new(location: Location) -> Self {
162 Self(location.0)
163 }
164}
165
166impl From<Location> for LocationKey {
167 fn from(location: Location) -> Self {
168 Self::new(location)
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Hash)]
174pub struct PositionIntrospection(pub Location, pub Span);
175
176impl Introspect for PositionIntrospection {
177 type Output = PagedPosition;
178
179 fn introspect(
180 &self,
181 _: &mut Engine,
182 introspector: Tracked<dyn Introspector + '_>,
183 ) -> Self::Output {
184 match introspector.position(self.0) {
185 Some(DocumentPosition::Paged(pos)) => pos,
186 Some(DocumentPosition::Html(_)) | None => PagedPosition::ORIGIN,
188 }
189 }
190
191 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
192 format_convergence_warning(
193 self.0,
194 self.1,
195 history,
196 "positions",
197 |element| eco_format!("{element} position"),
198 |pos| {
199 let coord = |v: Abs| repr::format_float(v.to_pt(), Some(0), false, "pt");
200 eco_format!(
201 "page {} at ({}, {})",
202 pos.page,
203 coord(pos.point.x),
204 coord(pos.point.y)
205 )
206 },
207 )
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Hash)]
213pub struct PageIntrospection(pub Location, pub Span);
214
215impl Introspect for PageIntrospection {
216 type Output = NonZeroUsize;
217
218 fn introspect(
219 &self,
220 _: &mut Engine,
221 introspector: Tracked<dyn Introspector + '_>,
222 ) -> Self::Output {
223 introspector.page(self.0).unwrap_or(NonZeroUsize::ONE)
225 }
226
227 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
228 format_convergence_warning(
229 self.0,
230 self.1,
231 history,
232 "page numbers",
233 |element| eco_format!("page number of the {element}"),
234 |n| eco_format!("page {n}"),
235 )
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Hash)]
241pub struct PageNumberingIntrospection(pub Location, pub Span);
242
243impl Introspect for PageNumberingIntrospection {
244 type Output = Option<Numbering>;
245
246 fn introspect(
247 &self,
248 _: &mut Engine,
249 introspector: Tracked<dyn Introspector + '_>,
250 ) -> Self::Output {
251 introspector.page_numbering(self.0).cloned()
252 }
253
254 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
255 format_convergence_warning(
256 self.0,
257 self.1,
258 history,
259 "numberings",
260 |element| {
261 eco_format!("numbering of the page on which the {element} is located")
262 },
263 |numbering| eco_format!("`{}`", numbering.clone().into_value().repr()),
264 )
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Hash)]
270pub struct PageSupplementIntrospection(pub Location, pub Span);
271
272impl Introspect for PageSupplementIntrospection {
273 type Output = Content;
274
275 fn introspect(
276 &self,
277 _: &mut Engine,
278 introspector: Tracked<dyn Introspector + '_>,
279 ) -> Self::Output {
280 introspector.page_supplement(self.0).cloned().unwrap_or_default()
283 }
284
285 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
286 format_convergence_warning(
287 self.0,
288 self.1,
289 history,
290 "supplements",
291 |element| {
292 eco_format!("supplement of the page on which the {element} is located")
293 },
294 |supplement| eco_format!("`{}`", supplement.repr()),
295 )
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Hash)]
302pub struct PathIntrospection(pub Location, pub Span);
303
304impl Introspect for PathIntrospection {
305 type Output = Option<VirtualPath>;
306
307 fn introspect(
308 &self,
309 _: &mut Engine,
310 introspector: Tracked<dyn Introspector + '_>,
311 ) -> Self::Output {
312 introspector.path(self.0).cloned()
313 }
314
315 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
316 format_convergence_warning(
317 self.0,
318 self.1,
319 history,
320 "path",
321 |element| {
322 eco_format!("path of the document in which the {element} is located")
323 },
324 |path| {
325 eco_format!(
326 "`{}`",
327 path.as_ref().map(|p| p.get_with_slash()).into_value().repr()
328 )
329 },
330 )
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Hash)]
336pub struct DocumentIntrospection(pub Location, pub Span);
337
338impl Introspect for DocumentIntrospection {
339 type Output = Option<Location>;
340
341 fn introspect(
342 &self,
343 _: &mut Engine,
344 introspector: Tracked<dyn Introspector + '_>,
345 ) -> Self::Output {
346 introspector.document(self.0)
347 }
348
349 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
350 format_convergence_warning(
351 self.0,
352 self.1,
353 history,
354 "path",
355 |element| eco_format!("document in which the {element} is located"),
356 |_loc| eco_format!("TODO"),
357 )
358 }
359}
360
361fn format_convergence_warning<T>(
363 loc: Location,
364 span: Span,
365 history: &History<T>,
366 output_kind_plural: &str,
367 format_output_kind: impl FnOnce(&str) -> EcoString,
368 format_output: impl FnMut(&T) -> EcoString,
369) -> SourceDiagnostic {
370 let elem = history.final_introspector().query_first(&Selector::Location(loc));
371 let kind = match &elem {
372 Some(content) => content.elem().name(),
373 None => "element",
374 };
375
376 let what = format_output_kind(kind);
377 let mut diag = warning!(span, "{what} did not stabilize");
378
379 if let Some(elem) = elem
380 && !elem.span().is_detached()
381 {
382 diag.spanned_hint(eco_format!("{kind} was created here"), elem.span());
383 }
384
385 diag.with_hint(history.hint(output_kind_plural, format_output))
386}