Locatable selectors

This commit is contained in:
Laurenz 2023-04-04 23:27:51 +02:00
parent 23715e813e
commit 715f9fb0a5
5 changed files with 76 additions and 39 deletions

View File

@ -276,10 +276,11 @@ use crate::prelude::*;
pub fn counter( pub fn counter(
/// The key that identifies this counter. /// The key that identifies this counter.
/// ///
/// - If this is the [`page`]($func/page) function, counts through pages.
/// - If this is any other element function, counts through its elements.
/// - If it is a string, creates a custom counter that is only affected by /// - If it is a string, creates a custom counter that is only affected by
/// manual updates. /// manual updates,
/// - If this is a `{<label>}`, counts through all elements with that label,
/// - If this is an element function or selector, counts through its elements,
/// - If this is the [`page`]($func/page) function, counts through pages.
key: CounterKey, key: CounterKey,
) -> Value { ) -> Value {
Value::dynamic(Counter::new(key)) Value::dynamic(Counter::new(key))
@ -485,18 +486,14 @@ cast_from_value! {
CounterKey, CounterKey,
v: Str => Self::Str(v), v: Str => Self::Str(v),
label: Label => Self::Selector(Selector::Label(label)), label: Label => Self::Selector(Selector::Label(label)),
element: ElemFunc => { v: ElemFunc => {
if element == PageElem::func() { if v == PageElem::func() {
return Ok(Self::Page); Self::Page
} else {
Self::Selector(LocatableSelector::cast(Value::from(v))?.0)
} }
if !Content::new(element).can::<dyn Locatable>() {
Err(eco_format!("cannot count through {}s", element.name()))?;
}
Self::Selector(Selector::Elem(element, None))
}, },
selector: Selector => Self::Selector(selector), selector: LocatableSelector => Self::Selector(selector.0),
} }
impl Debug for CounterKey { impl Debug for CounterKey {

View File

@ -94,8 +94,8 @@ use crate::prelude::*;
/// Returns: content /// Returns: content
#[func] #[func]
pub fn query( pub fn query(
/// Can be an element function like a `heading` or `figure` or a /// Can be an element function like a `heading` or `figure`, a `{<label>}`
/// `{<label>}`. /// or a more complex selector like `{heading.where(level: 1)}`.
/// ///
/// Currently, only a subset of element functions is supported. Aside from /// Currently, only a subset of element functions is supported. Aside from
/// headings and figures, this includes equations, references and all /// headings and figures, this includes equations, references and all
@ -104,7 +104,7 @@ pub fn query(
/// have an explicit label attached to them. This limitation will be /// have an explicit label attached to them. This limitation will be
/// resolved /// resolved
/// in the future. /// in the future.
target: Target, target: LocatableSelector,
/// Can be any location. Why is it required then? As noted before, Typst has /// Can be any location. Why is it required then? As noted before, Typst has
/// to evaluate parts of your code multiple times to determine the values of /// to evaluate parts of your code multiple times to determine the values of
@ -151,18 +151,3 @@ pub fn query(
}; };
elements.into() elements.into()
} }
/// A query target.
struct Target(Selector);
cast_from_value! {
Target,
label: Label => Self(Selector::Label(label)),
element: ElemFunc => {
if !Content::new(element).can::<dyn Locatable>() {
Err(eco_format!("cannot query for {}s", element.name()))?;
}
Self(Selector::Elem(element, None))
}
}

View File

@ -23,8 +23,9 @@ pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold, element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold,
Introspector, Label, Locatable, Location, MetaElem, Resolve, Selector, Set, Show, Introspector, Label, Locatable, LocatableSelector, Location, MetaElem, Resolve,
StabilityProvider, StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt, Selector, Set, Show, StabilityProvider, StyleChain, StyleVec, Styles, Synthesize,
Unlabellable, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};

View File

@ -237,7 +237,7 @@ impl<T> Variadics for Vec<T> {
} }
/// Describes a possible value for a cast. /// Describes a possible value for a cast.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash, PartialEq, PartialOrd)]
pub enum CastInfo { pub enum CastInfo {
/// Any value is okay. /// Any value is okay.
Any, Any,
@ -302,15 +302,23 @@ impl Add for CastInfo {
fn add(self, rhs: Self) -> Self { fn add(self, rhs: Self) -> Self {
Self::Union(match (self, rhs) { Self::Union(match (self, rhs) {
(Self::Union(mut lhs), Self::Union(rhs)) => { (Self::Union(mut lhs), Self::Union(rhs)) => {
lhs.extend(rhs); for cast in rhs {
if !lhs.contains(&cast) {
lhs.push(cast);
}
}
lhs lhs
} }
(Self::Union(mut lhs), rhs) => { (Self::Union(mut lhs), rhs) => {
lhs.push(rhs); if !lhs.contains(&rhs) {
lhs.push(rhs);
}
lhs lhs
} }
(lhs, Self::Union(mut rhs)) => { (lhs, Self::Union(mut rhs)) => {
rhs.insert(0, lhs); if !rhs.contains(&lhs) {
rhs.insert(0, lhs);
}
rhs rhs
} }
(lhs, rhs) => vec![lhs, rhs], (lhs, rhs) => vec![lhs, rhs],

View File

@ -6,8 +6,9 @@ use std::mem;
use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use super::{Content, ElemFunc, Element, Label, Vt}; use super::{Content, ElemFunc, Element, Label, Vt};
use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::diag::{SourceResult, StrResult, Trace, Tracepoint};
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm}; use crate::eval::{cast_from_value, Args, Cast, CastInfo, Dict, Func, Regex, Value, Vm};
use crate::model::Locatable;
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
@ -337,6 +338,51 @@ cast_from_value! {
regex: Regex => Self::Regex(regex), regex: Regex => Self::Regex(regex),
} }
/// A selector that can be used with `query`. Hopefully, this is made obsolote
/// by a more powerful query mechanism in the future.
#[derive(Clone, PartialEq, Hash)]
pub struct LocatableSelector(pub Selector);
impl Cast for LocatableSelector {
fn is(value: &Value) -> bool {
matches!(value, Value::Label(_) | Value::Func(_))
|| value.type_name() == "selector"
}
fn cast(value: Value) -> StrResult<Self> {
fn validate(selector: &Selector) -> StrResult<()> {
match &selector {
Selector::Elem(elem, _) if !elem.can::<dyn Locatable>() => {
Err(eco_format!("{} is not locatable", elem.name()))?
}
Selector::Regex(_) => Err("text is not locatable")?,
Selector::Any(list) | Selector::All(list) => {
for selector in list {
validate(selector)?;
}
}
_ => {}
}
Ok(())
}
if !Self::is(&value) {
return <Self as Cast>::error(value);
}
let selector = Selector::cast(value)?;
validate(&selector)?;
Ok(Self(selector))
}
fn describe() -> CastInfo {
CastInfo::Union(vec![
CastInfo::Type("label"),
CastInfo::Type("function"),
CastInfo::Type("selector"),
])
}
}
/// A show rule transformation that can be applied to a match. /// A show rule transformation that can be applied to a match.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub enum Transform { pub enum Transform {