Skip to main content

typst_library/foundations/
selector.rs

1use std::any::{Any, TypeId};
2use std::sync::Arc;
3
4use comemo::Tracked;
5use ecow::{EcoString, EcoVec, eco_format};
6use smallvec::SmallVec;
7use typst_syntax::Span;
8
9use crate::diag::{At, HintedStrResult, SourceResult, StrResult, bail};
10use crate::engine::Engine;
11use crate::foundations::{
12    CastInfo, Content, Context, Dict, Element, FromValue, Func, Label, Reflect, Regex,
13    Repr, Str, StyleChain, Symbol, Type, Value, cast, func, repr, scope, ty,
14};
15use crate::introspection::{Locatable, Location, QueryUniqueIntrospection, Unqueriable};
16
17/// A helper macro to create a field selector used in [`Selector::Elem`]
18#[macro_export]
19#[doc(hidden)]
20macro_rules! __select_where {
21    ($ty:ty $(, $field:ident => $value:expr)* $(,)?) => {{
22        #[allow(unused_mut)]
23        let mut fields = $crate::foundations::SmallVec::new();
24        $(
25            fields.push((
26                <$ty>::$field.index(),
27                $crate::foundations::IntoValue::into_value($value),
28            ));
29        )*
30        $crate::foundations::Selector::Elem(
31            <$ty as $crate::foundations::NativeElement>::ELEM,
32            Some(fields),
33        )
34    }};
35}
36
37#[doc(inline)]
38pub use crate::__select_where as select_where;
39
40/// A filter for selecting elements within the document.
41///
42/// To construct a selector you can:
43/// - use an @function:element-functions[element function]
44/// - filter for an element function with @function.where[specific fields]
45/// - use a @str[string] or @regex[regular expression]
46/// - use a @label[`{<label>}`]
47/// - use a @location
48/// - call the @selector constructor to convert any of the above types into a
49///   selector value and use the methods below to refine it
50///
51/// Selectors are used to @reference:styling:show-rules[apply styling rules] to
52/// elements. You can also use selectors to @query[query] the document for
53/// certain types of elements.
54///
55/// Furthermore, you can pass a selector to several of Typst's built-in
56/// functions to configure their behaviour. One such example is the
57/// @outline[outline] where it can be used to change which elements are listed
58/// within the outline.
59///
60/// Multiple selectors can be combined using the methods shown below. However,
61/// not all kinds of selectors are supported in all places, at the moment.
62///
63/// = Example <example>
64/// ```example
65/// #context query(
66///   heading.where(level: 1)
67///     .or(heading.where(level: 2))
68/// )
69///
70/// = This will be found
71/// == So will this
72/// === But this will not.
73/// ```
74#[ty(scope, cast)]
75#[derive(Debug, Clone, PartialEq, Hash)]
76pub enum Selector {
77    /// Matches a specific type of element.
78    ///
79    /// If there is a dictionary, only elements with the fields from the
80    /// dictionary match.
81    Elem(Element, Option<SmallVec<[(u8, Value); 1]>>),
82    /// Matches the element at the specified location.
83    Location(Location),
84    /// Matches elements with a specific label.
85    Label(Label),
86    /// Matches text elements through a regular expression.
87    Regex(Regex),
88    /// Matches elements with a specific capability.
89    ///
90    /// This is not exposed, but used internally.
91    Can(TypeId),
92    /// Matches if any of the subselectors match.
93    Or(EcoVec<Self>),
94    /// Matches if all of the subselectors match.
95    And(EcoVec<Self>),
96    /// Matches all matches of `selector` before `end`.
97    Before { selector: Arc<Self>, end: Arc<Self>, inclusive: bool },
98    /// Matches all matches of `selector` after `start`.
99    After { selector: Arc<Self>, start: Arc<Self>, inclusive: bool },
100    /// Matches all matches of `selector` that are strictly within `ancestor`.
101    ///
102    /// This is not yet exposed, but used internally.
103    Within { selector: Arc<Self>, ancestor: Arc<Self> },
104}
105
106impl Selector {
107    /// Define a simple text selector.
108    pub fn text(text: &str) -> StrResult<Self> {
109        if text.is_empty() {
110            bail!("text selector is empty");
111        }
112        Ok(Self::Regex(Regex::new(&regex::escape(text)).unwrap()))
113    }
114
115    /// Define a regex selector.
116    pub fn regex(regex: Regex) -> StrResult<Self> {
117        if regex.as_str().is_empty() {
118            bail!("regex selector is empty");
119        }
120        if regex.is_match("") {
121            bail!("regex matches empty text");
122        }
123        Ok(Self::Regex(regex))
124    }
125
126    /// Define a simple [`Selector::Can`] selector.
127    pub fn can<T: ?Sized + Any>() -> Self {
128        Self::Can(TypeId::of::<T>())
129    }
130
131    /// Whether the selector matches for the target.
132    pub fn matches(&self, target: &Content, styles: Option<StyleChain>) -> bool {
133        match self {
134            Self::Elem(element, dict) => {
135                target.elem() == *element
136                    && dict.iter().flat_map(|dict| dict.iter()).all(|(id, value)| {
137                        target.get(*id, styles).as_ref().ok() == Some(value)
138                    })
139            }
140            Self::Label(label) => target.label() == Some(*label),
141            Self::Can(cap) => target.func().can_type_id(*cap),
142            Self::Or(selectors) => {
143                selectors.iter().any(move |sel| sel.matches(target, styles))
144            }
145            Self::And(selectors) => {
146                selectors.iter().all(move |sel| sel.matches(target, styles))
147            }
148            Self::Location(location) => target.location() == Some(*location),
149            // Not supported here.
150            Self::Regex(_)
151            | Self::Before { .. }
152            | Self::After { .. }
153            | Self::Within { .. } => false,
154        }
155    }
156}
157
158#[scope]
159impl Selector {
160    /// Turns a value into a selector. The following values are accepted:
161    /// - An element function like a `heading` or `figure`.
162    /// - A @str[string] or @regex[regular expression].
163    /// - A `{<label>}`.
164    /// - A @location.
165    /// - A more complex selector like `{heading.where(level: 1)}`.
166    #[func(constructor)]
167    pub fn construct(
168        /// Can be an element function like a `heading` or `figure`, a
169        /// `{<label>}` or a more complex selector like
170        /// `{heading.where(level: 1)}`.
171        target: Selector,
172    ) -> Selector {
173        target
174    }
175
176    /// Selects all elements that match this or any of the other selectors.
177    #[func]
178    pub fn or(
179        self,
180        /// The other selectors to match on.
181        #[variadic]
182        others: Vec<Selector>,
183    ) -> Selector {
184        Self::Or(others.into_iter().chain(Some(self)).collect())
185    }
186
187    /// Selects all elements that match this and all of the other selectors.
188    #[func]
189    pub fn and(
190        self,
191        /// The other selectors to match on.
192        #[variadic]
193        others: Vec<Selector>,
194    ) -> Selector {
195        Self::And(others.into_iter().chain(Some(self)).collect())
196    }
197
198    /// Returns a modified selector that will only match elements that occur
199    /// before the first match of `end`.
200    ///
201    /// _Note:_ This selector is currently only supported with introspection
202    /// functions, not in show rules.
203    #[func]
204    pub fn before(
205        self,
206        /// The original selection will end at the first match of `end`.
207        end: LocatableSelector,
208        /// Whether `end` itself should match or not. This is only relevant if
209        /// both selectors match the same type of element. Defaults to `{true}`.
210        #[named]
211        #[default(true)]
212        inclusive: bool,
213    ) -> Selector {
214        Self::Before {
215            selector: Arc::new(self),
216            end: Arc::new(end.0),
217            inclusive,
218        }
219    }
220
221    /// Returns a modified selector that will only match elements that occur
222    /// after the first match of `start`.
223    ///
224    /// _Note:_ This selector is currently only supported with introspection
225    /// functions, not in show rules.
226    #[func]
227    pub fn after(
228        self,
229        /// The original selection will start at the first match of `start`.
230        start: LocatableSelector,
231        /// Whether `start` itself should match or not. This is only relevant if
232        /// both selectors match the same type of element. Defaults to `{true}`.
233        #[named]
234        #[default(true)]
235        inclusive: bool,
236    ) -> Selector {
237        Self::After {
238            selector: Arc::new(self),
239            start: Arc::new(start.0),
240            inclusive,
241        }
242    }
243
244    /// Returns a modified selector that will only match elements that are
245    /// contained within any elements matching the `ancestor` selector.
246    ///
247    /// #example(
248    ///   title: "Finding strong elements in lists",
249    ///   ```
250    ///   *Strong emphasis* that does not count.
251    ///
252    ///   - An *important* word
253    ///   - Another *key* word
254    ///
255    ///   Strong elements in lists:
256    ///   #context {
257    ///     query(selector(strong).within(list))
258    ///       .map(it => it.body)
259    ///       .join[, ]
260    ///   }
261    ///   ```
262    /// )
263    ///
264    /// This can also be used in combination with @here to find all matches of a
265    /// selector within a @reference:context[context] expression. This can be
266    /// quite useful to have an introspection return results local to some
267    /// component you are building.
268    ///
269    /// #example(
270    ///   title: "Counting elements locally in a context block",
271    ///   ```
272    ///   #let count(sel, body) = context {
273    ///     let n = query(selector(sel).within(here())).len()
274    ///     [#body (#n matches)]
275    ///   }
276    ///
277    ///   - #count(emph)[Has _two_ matching _elements_]
278    ///   - #count(strong)[Has *one* matching element]
279    ///   ```
280    /// )
281    ///
282    /// _Note:_ This selector is currently only supported with introspection
283    /// functions, not in show rules.
284    #[func]
285    pub fn within(
286        self,
287        /// Only matches of `self` that are descendants of any element matching
288        /// this selector will be included in the output.
289        ///
290        /// An element is not considered its own ancestor.
291        ancestor: LocatableSelector,
292    ) -> Selector {
293        Selector::Within {
294            selector: Arc::new(self),
295            ancestor: Arc::new(ancestor.0),
296        }
297    }
298}
299
300impl From<Location> for Selector {
301    fn from(value: Location) -> Self {
302        Self::Location(value)
303    }
304}
305
306impl Repr for Selector {
307    fn repr(&self) -> EcoString {
308        match self {
309            Self::Elem(elem, dict) => {
310                if let Some(dict) = dict {
311                    let dict = dict
312                        .iter()
313                        .map(|(id, value)| (elem.field_name(*id).unwrap(), value.clone()))
314                        .map(|(name, value)| (EcoString::from(name).into(), value))
315                        .collect::<Dict>();
316                    eco_format!("{}.where{}", elem.name(), dict.repr())
317                } else {
318                    elem.name().into()
319                }
320            }
321            Self::Label(label) => label.repr(),
322            Self::Regex(regex) => regex.repr(),
323            Self::Can(_) => eco_format!("selector(..)"),
324            Self::Or(selectors) | Self::And(selectors) => {
325                let function = if matches!(self, Self::Or(_)) { "or" } else { "and" };
326                let pieces: Vec<_> = selectors.iter().map(Selector::repr).collect();
327                eco_format!(
328                    "selector.{}{}",
329                    function,
330                    repr::pretty_array_like(&pieces, false)
331                )
332            }
333            Self::Location(loc) => loc.repr(),
334            Self::Before { selector, end: split, inclusive }
335            | Self::After { selector, start: split, inclusive } => {
336                let method =
337                    if matches!(self, Self::Before { .. }) { "before" } else { "after" };
338                let inclusive_arg = if !*inclusive { ", inclusive: false" } else { "" };
339                eco_format!(
340                    "{}.{}({}{})",
341                    selector.repr(),
342                    method,
343                    split.repr(),
344                    inclusive_arg
345                )
346            }
347            Self::Within { selector, ancestor } => {
348                eco_format!("{}.within({})", selector.repr(), ancestor.repr(),)
349            }
350        }
351    }
352}
353
354cast! {
355    type Selector,
356    text: EcoString => Self::text(&text)?,
357    func: Func => func
358        .to_element()
359        .ok_or("only element functions can be used as selectors")?
360        .select(),
361    label: Label => Self::Label(label),
362    regex: Regex => Self::regex(regex)?,
363    location: Location => Self::Location(location),
364}
365
366/// A selector that can be used with `query`.
367///
368/// Hopefully, this is made obsolete by a more powerful query mechanism in the
369/// future.
370#[derive(Debug, Clone, PartialEq, Hash)]
371pub struct LocatableSelector(pub Selector);
372
373impl LocatableSelector {
374    /// Resolve this selector into a location that is guaranteed to be unique.
375    pub fn resolve_unique(
376        &self,
377        engine: &mut Engine,
378        context: Tracked<Context>,
379        span: Span,
380    ) -> SourceResult<Location> {
381        match self.0.clone() {
382            Selector::Location(loc) => Ok(loc),
383            other => {
384                context.introspect().at(span)?;
385                engine
386                    .introspect(QueryUniqueIntrospection(other, span))
387                    .map(|c| c.location().unwrap())
388                    .at(span)
389            }
390        }
391    }
392}
393
394impl Reflect for LocatableSelector {
395    fn input() -> CastInfo {
396        CastInfo::Union(vec![
397            CastInfo::Type(Type::of::<Label>()),
398            CastInfo::Type(Type::of::<Func>()),
399            CastInfo::Type(Type::of::<Location>()),
400            CastInfo::Type(Type::of::<Selector>()),
401        ])
402    }
403
404    fn output() -> CastInfo {
405        CastInfo::Type(Type::of::<Selector>())
406    }
407
408    fn castable(value: &Value) -> bool {
409        Label::castable(value)
410            || Func::castable(value)
411            || Location::castable(value)
412            || Selector::castable(value)
413    }
414}
415
416cast! {
417    LocatableSelector,
418    self => self.0.into_value(),
419}
420
421impl FromValue for LocatableSelector {
422    fn from_value(value: Value) -> HintedStrResult<Self> {
423        fn validate(selector: &Selector) -> StrResult<()> {
424            match selector {
425                Selector::Elem(elem, _) => {
426                    if !elem.can::<dyn Locatable>() || elem.can::<dyn Unqueriable>() {
427                        Err(eco_format!("{} is not locatable", elem.name()))?
428                    }
429                }
430                Selector::Location(_) => {}
431                Selector::Label(_) => {}
432                Selector::Regex(_) => bail!("text is not locatable"),
433                Selector::Can(_) => bail!("capability is not locatable"),
434                Selector::Or(list) | Selector::And(list) => {
435                    for selector in list {
436                        validate(selector)?;
437                    }
438                }
439                Selector::Before { selector, end: split, .. }
440                | Selector::After { selector, start: split, .. } => {
441                    for selector in [selector, split] {
442                        validate(selector)?;
443                    }
444                }
445                Selector::Within { selector, ancestor } => {
446                    for selector in [selector, ancestor] {
447                        validate(selector)?;
448                    }
449                }
450            }
451            Ok(())
452        }
453
454        if !Self::castable(&value) {
455            return Err(Self::error(&value));
456        }
457
458        let selector = Selector::from_value(value)?;
459        validate(&selector)?;
460        Ok(Self(selector))
461    }
462}
463
464impl From<Location> for LocatableSelector {
465    fn from(loc: Location) -> Self {
466        Self(Selector::Location(loc))
467    }
468}
469
470/// A selector that can be used with show rules.
471///
472/// Hopefully, this is made obsolete by a more powerful showing mechanism in the
473/// future.
474#[derive(Clone, PartialEq, Hash)]
475pub struct ShowableSelector(pub Selector);
476
477impl Reflect for ShowableSelector {
478    fn input() -> CastInfo {
479        CastInfo::Union(vec![
480            CastInfo::Type(Type::of::<Symbol>()),
481            CastInfo::Type(Type::of::<Str>()),
482            CastInfo::Type(Type::of::<Label>()),
483            CastInfo::Type(Type::of::<Func>()),
484            CastInfo::Type(Type::of::<Regex>()),
485            CastInfo::Type(Type::of::<Selector>()),
486        ])
487    }
488
489    fn output() -> CastInfo {
490        CastInfo::Type(Type::of::<Selector>())
491    }
492
493    fn castable(value: &Value) -> bool {
494        Symbol::castable(value)
495            || Str::castable(value)
496            || Label::castable(value)
497            || Func::castable(value)
498            || Regex::castable(value)
499            || Selector::castable(value)
500    }
501}
502
503cast! {
504    ShowableSelector,
505    self => self.0.into_value(),
506}
507
508impl FromValue for ShowableSelector {
509    fn from_value(value: Value) -> HintedStrResult<Self> {
510        fn validate(selector: &Selector, nested: bool) -> HintedStrResult<()> {
511            match selector {
512                Selector::Elem(_, _) => {}
513                Selector::Label(_) => {}
514                Selector::Regex(_) if !nested => {}
515                Selector::Or(list) | Selector::And(list) => {
516                    for selector in list {
517                        validate(selector, true)?;
518                    }
519                }
520                Selector::Regex(_)
521                | Selector::Location(_)
522                | Selector::Can(_)
523                | Selector::Before { .. }
524                | Selector::After { .. } => {
525                    bail!("this selector cannot be used with show")
526                }
527                Selector::Within { .. } => {
528                    bail!(
529                        "this selector cannot currently be used with show";
530                        hint: "support for this is planned for the future";
531                    )
532                }
533            }
534            Ok(())
535        }
536
537        if !Self::castable(&value) {
538            return Err(Self::error(&value));
539        }
540
541        let selector = Selector::from_value(value)?;
542        validate(&selector, false)?;
543        Ok(Self(selector))
544    }
545}