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)]
62#[derive(Copy, Clone, Eq, PartialEq, Hash)]
63pub struct Location(u128);
64
65impl Location {
66 pub fn new(hash: u128) -> Self {
68 Self(hash)
69 }
70
71 pub fn hash(self) -> u128 {
73 self.0
74 }
75
76 pub fn variant(self, n: usize) -> Self {
82 Self(typst_utils::hash128(&(self.0, n)))
83 }
84}
85
86#[scope]
87impl Location {
88 #[func]
106 pub fn page(self, engine: &mut Engine, span: Span) -> NonZeroUsize {
107 engine.introspect(PageIntrospection(self, span))
108 }
109
110 #[func]
117 pub fn position(self, engine: &mut Engine, span: Span) -> PagedPosition {
118 engine.introspect(PositionIntrospection(self, span))
119 }
120
121 #[func]
129 pub fn page_numbering(self, engine: &mut Engine, span: Span) -> Option<Numbering> {
130 engine.introspect(PageNumberingIntrospection(self, span))
131 }
132}
133
134impl Debug for Location {
135 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
136 if f.alternate() {
137 write!(f, "Location({})", self.0)
138 } else {
139 let truncated = self.0 as u16;
141 write!(f, "Location({truncated})")
142 }
143 }
144}
145
146impl Repr for Location {
147 fn repr(&self) -> EcoString {
148 "location(..)".into()
149 }
150}
151
152#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
161pub struct LocationKey(u128);
162
163impl LocationKey {
164 pub fn new(location: Location) -> Self {
166 Self(location.0)
167 }
168}
169
170impl From<Location> for LocationKey {
171 fn from(location: Location) -> Self {
172 Self::new(location)
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Hash)]
178pub struct PositionIntrospection(pub Location, pub Span);
179
180impl Introspect for PositionIntrospection {
181 type Output = PagedPosition;
182
183 fn introspect(
184 &self,
185 _: &mut Engine,
186 introspector: Tracked<dyn Introspector + '_>,
187 ) -> Self::Output {
188 match introspector.position(self.0) {
189 Some(DocumentPosition::Paged(pos)) => pos,
190 Some(DocumentPosition::Html(_)) | None => PagedPosition::ORIGIN,
192 }
193 }
194
195 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
196 format_convergence_warning(
197 self.0,
198 self.1,
199 history,
200 "positions",
201 |element| eco_format!("{element} position"),
202 |pos| {
203 let coord = |v: Abs| repr::format_float(v.to_pt(), Some(0), false, "pt");
204 eco_format!(
205 "page {} at ({}, {})",
206 pos.page,
207 coord(pos.point.x),
208 coord(pos.point.y)
209 )
210 },
211 )
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Hash)]
217pub struct PageIntrospection(pub Location, pub Span);
218
219impl Introspect for PageIntrospection {
220 type Output = NonZeroUsize;
221
222 fn introspect(
223 &self,
224 _: &mut Engine,
225 introspector: Tracked<dyn Introspector + '_>,
226 ) -> Self::Output {
227 introspector.page(self.0).unwrap_or(NonZeroUsize::ONE)
229 }
230
231 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
232 format_convergence_warning(
233 self.0,
234 self.1,
235 history,
236 "page numbers",
237 |element| eco_format!("page number of the {element}"),
238 |n| eco_format!("page {n}"),
239 )
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Hash)]
245pub struct PageNumberingIntrospection(pub Location, pub Span);
246
247impl Introspect for PageNumberingIntrospection {
248 type Output = Option<Numbering>;
249
250 fn introspect(
251 &self,
252 _: &mut Engine,
253 introspector: Tracked<dyn Introspector + '_>,
254 ) -> Self::Output {
255 introspector.page_numbering(self.0).cloned()
256 }
257
258 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
259 format_convergence_warning(
260 self.0,
261 self.1,
262 history,
263 "numberings",
264 |element| {
265 eco_format!("numbering of the page on which the {element} is located")
266 },
267 |numbering| eco_format!("`{}`", numbering.clone().into_value().repr()),
268 )
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Hash)]
274pub struct PageSupplementIntrospection(pub Location, pub Span);
275
276impl Introspect for PageSupplementIntrospection {
277 type Output = Content;
278
279 fn introspect(
280 &self,
281 _: &mut Engine,
282 introspector: Tracked<dyn Introspector + '_>,
283 ) -> Self::Output {
284 introspector.page_supplement(self.0).cloned().unwrap_or_default()
287 }
288
289 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
290 format_convergence_warning(
291 self.0,
292 self.1,
293 history,
294 "supplements",
295 |element| {
296 eco_format!("supplement of the page on which the {element} is located")
297 },
298 |supplement| eco_format!("`{}`", supplement.repr()),
299 )
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Hash)]
306pub struct PathIntrospection(pub Location, pub Span);
307
308impl Introspect for PathIntrospection {
309 type Output = Option<VirtualPath>;
310
311 fn introspect(
312 &self,
313 _: &mut Engine,
314 introspector: Tracked<dyn Introspector + '_>,
315 ) -> Self::Output {
316 introspector.path(self.0).cloned()
317 }
318
319 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
320 format_convergence_warning(
321 self.0,
322 self.1,
323 history,
324 "path",
325 |element| {
326 eco_format!("path of the document in which the {element} is located")
327 },
328 |path| {
329 eco_format!(
330 "`{}`",
331 path.as_ref().map(|p| p.get_with_slash()).into_value().repr()
332 )
333 },
334 )
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Hash)]
340pub struct DocumentIntrospection(pub Location, pub Span);
341
342impl Introspect for DocumentIntrospection {
343 type Output = Option<Location>;
344
345 fn introspect(
346 &self,
347 _: &mut Engine,
348 introspector: Tracked<dyn Introspector + '_>,
349 ) -> Self::Output {
350 introspector.document(self.0)
351 }
352
353 fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
354 format_convergence_warning(
355 self.0,
356 self.1,
357 history,
358 "path",
359 |element| eco_format!("document in which the {element} is located"),
360 |_loc| eco_format!("TODO"),
361 )
362 }
363}
364
365fn format_convergence_warning<T>(
367 loc: Location,
368 span: Span,
369 history: &History<T>,
370 output_kind_plural: &str,
371 format_output_kind: impl FnOnce(&str) -> EcoString,
372 format_output: impl FnMut(&T) -> EcoString,
373) -> SourceDiagnostic {
374 let elem = history.final_introspector().query_first(&Selector::Location(loc));
375 let kind = match &elem {
376 Some(content) => content.elem().name(),
377 None => "element",
378 };
379
380 let what = format_output_kind(kind);
381 let mut diag = warning!(span, "{what} did not stabilize");
382
383 if let Some(elem) = elem
384 && !elem.span().is_detached()
385 {
386 diag.spanned_hint(eco_format!("{kind} was created here"), elem.span());
387 }
388
389 diag.with_hint(history.hint(output_kind_plural, format_output))
390}