summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/introspection/query.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/introspection/query.rs')
-rw-r--r--crates/typst-library/src/introspection/query.rs171
1 files changed, 171 insertions, 0 deletions
diff --git a/crates/typst-library/src/introspection/query.rs b/crates/typst-library/src/introspection/query.rs
new file mode 100644
index 00000000..7b106bf0
--- /dev/null
+++ b/crates/typst-library/src/introspection/query.rs
@@ -0,0 +1,171 @@
+use comemo::Tracked;
+use typst_syntax::Span;
+
+use crate::diag::{warning, HintedStrResult};
+use crate::engine::Engine;
+use crate::foundations::{func, Array, Context, LocatableSelector, Value};
+use crate::introspection::Location;
+
+/// Finds elements in the document.
+///
+/// The `query` functions lets you search your document for elements of a
+/// particular type or with a particular label. To use it, you first need to
+/// ensure that [context] is available.
+///
+
+/// # Finding elements
+/// In the example below, we manually create a table of contents instead of
+/// using the [`outline`] function.
+///
+/// To do this, we first query for all headings in the document at level 1 and
+/// where `outlined` is true. Querying only for headings at level 1 ensures
+/// that, for the purpose of this example, sub-headings are not included in the
+/// table of contents. The `outlined` field is used to exclude the "Table of
+/// Contents" heading itself.
+///
+/// Note that we open a `context` to be able to use the `query` function.
+///
+/// ```example
+/// >>> #set page(
+/// >>> width: 240pt,
+/// >>> height: 180pt,
+/// >>> margin: (top: 20pt, bottom: 35pt)
+/// >>> )
+/// #set page(numbering: "1")
+///
+/// #heading(outlined: false)[
+/// Table of Contents
+/// ]
+/// #context {
+/// let chapters = query(
+/// heading.where(
+/// level: 1,
+/// outlined: true,
+/// )
+/// )
+/// for chapter in chapters {
+/// let loc = chapter.location()
+/// let nr = numbering(
+/// loc.page-numbering(),
+/// ..counter(page).at(loc),
+/// )
+/// [#chapter.body #h(1fr) #nr \ ]
+/// }
+/// }
+///
+/// = Introduction
+/// #lorem(10)
+/// #pagebreak()
+///
+/// == Sub-Heading
+/// #lorem(8)
+///
+/// = Discussion
+/// #lorem(18)
+/// ```
+///
+/// To get the page numbers, we first get the location of the elements returned
+/// by `query` with [`location`]($content.location). We then also retrieve the
+/// [page numbering]($location.page-numbering) and [page
+/// counter]($counter/#page-counter) at that location and apply the numbering to
+/// the counter.
+///
+/// # A word of caution { #caution }
+/// To resolve all your queries, Typst evaluates and layouts parts of the
+/// document multiple times. However, there is no guarantee that your queries
+/// can actually be completely resolved. If you aren't careful a query can
+/// affect itself—leading to a result that never stabilizes.
+///
+/// In the example below, we query for all headings in the document. We then
+/// generate as many headings. In the beginning, there's just one heading,
+/// titled `Real`. Thus, `count` is `1` and one `Fake` heading is generated.
+/// Typst sees that the query's result has changed and processes it again. This
+/// time, `count` is `2` and two `Fake` headings are generated. This goes on and
+/// on. As we can see, the output has a finite amount of headings. This is
+/// because Typst simply gives up after a few attempts.
+///
+/// In general, you should try not to write queries that affect themselves. The
+/// same words of caution also apply to other introspection features like
+/// [counters]($counter) and [state].
+///
+/// ```example
+/// = Real
+/// #context {
+/// let elems = query(heading)
+/// let count = elems.len()
+/// count * [= Fake]
+/// }
+/// ```
+///
+/// # Command line queries
+/// You can also perform queries from the command line with the `typst query`
+/// command. This command executes an arbitrary query on the document and
+/// returns the resulting elements in serialized form. Consider the following
+/// `example.typ` file which contains some invisible [metadata]:
+///
+/// ```typ
+/// #metadata("This is a note") <note>
+/// ```
+///
+/// You can execute a query on it as follows using Typst's CLI:
+/// ```sh
+/// $ typst query example.typ "<note>"
+/// [
+/// {
+/// "func": "metadata",
+/// "value": "This is a note",
+/// "label": "<note>"
+/// }
+/// ]
+/// ```
+///
+/// Frequently, you're interested in only one specific field of the resulting
+/// elements. In the case of the `metadata` element, the `value` field is the
+/// interesting one. You can extract just this field with the `--field`
+/// argument.
+///
+/// ```sh
+/// $ typst query example.typ "<note>" --field value
+/// ["This is a note"]
+/// ```
+///
+/// If you are interested in just a single element, you can use the `--one`
+/// flag to extract just it.
+///
+/// ```sh
+/// $ typst query example.typ "<note>" --field value --one
+/// "This is a note"
+/// ```
+#[func(contextual)]
+pub fn query(
+ /// The engine.
+ engine: &mut Engine,
+ /// The callsite context.
+ context: Tracked<Context>,
+ /// The span of the `query` call.
+ span: Span,
+ /// Can be
+ /// - an element function like a `heading` or `figure`,
+ /// - a `{<label>}`,
+ /// - a more complex selector like `{heading.where(level: 1)}`,
+ /// - or `{selector(heading).before(here())}`.
+ ///
+ /// Only [locatable]($location/#locatable) element functions are supported.
+ target: LocatableSelector,
+ /// _Compatibility:_ This argument is deprecated. It only exists for
+ /// compatibility with Typst 0.10 and lower and shouldn't be used anymore.
+ #[default]
+ location: Option<Location>,
+) -> HintedStrResult<Array> {
+ if location.is_none() {
+ context.introspect()?;
+ } else {
+ engine.sink.warn(warning!(
+ span, "calling `query` with a location is deprecated";
+ hint: "try removing the location argument"
+ ));
+ }
+
+ let vec = engine.introspector.query(&target.0);
+ Ok(vec.into_iter().map(Value::Content).collect())
+}