Selectors

This commit is contained in:
Laurenz 2022-11-07 14:30:50 +01:00
parent efd1853d06
commit 0a41844cc4
24 changed files with 185 additions and 117 deletions

View File

@ -39,7 +39,7 @@ impl MathNode {
} }
impl Show for MathNode { impl Show for MathNode {
fn unguard_parts(&self, _: Selector) -> Content { fn unguard_parts(&self, _: RecipeId) -> Content {
self.clone().pack() self.clone().pack()
} }

View File

@ -9,7 +9,7 @@ pub use typst::frame::*;
pub use typst::geom::*; pub use typst::geom::*;
pub use typst::model::{ pub use typst::model::{
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
Content, Dict, Finalize, Fold, Func, Key, Node, Resolve, Scope, Selector, Show, Content, Dict, Finalize, Fold, Func, Key, Node, RecipeId, Resolve, Scope, Show,
Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm, Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm,
}; };
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};

View File

@ -78,8 +78,8 @@ impl HeadingNode {
} }
impl Show for HeadingNode { impl Show for HeadingNode {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self { body: self.body.unguard(sel), ..*self }.pack() Self { body: self.body.unguard(id), ..*self }.pack()
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {

View File

@ -94,9 +94,9 @@ impl<const L: ListKind> ListNode<L> {
} }
impl<const L: ListKind> Show for ListNode<L> { impl<const L: ListKind> Show for ListNode<L> {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self { Self {
items: self.items.map(|item| item.unguard(sel)), items: self.items.map(|item| item.unguard(id)),
..*self ..*self
} }
.pack() .pack()
@ -208,7 +208,7 @@ impl ListItem {
} }
} }
fn unguard(&self, sel: Selector) -> Self { fn unguard(&self, sel: RecipeId) -> Self {
match self { match self {
Self::List(body) => Self::List(Box::new(body.unguard(sel))), Self::List(body) => Self::List(Box::new(body.unguard(sel))),
Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))), Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))),

View File

@ -20,7 +20,7 @@ impl RefNode {
} }
impl Show for RefNode { impl Show for RefNode {
fn unguard_parts(&self, _: Selector) -> Content { fn unguard_parts(&self, _: RecipeId) -> Content {
Self(self.0.clone()).pack() Self(self.0.clone()).pack()
} }

View File

@ -58,11 +58,11 @@ impl TableNode {
} }
impl Show for TableNode { impl Show for TableNode {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self { Self {
tracks: self.tracks.clone(), tracks: self.tracks.clone(),
gutter: self.gutter.clone(), gutter: self.gutter.clone(),
cells: self.cells.iter().map(|cell| cell.unguard(sel)).collect(), cells: self.cells.iter().map(|cell| cell.unguard(id)).collect(),
} }
.pack() .pack()
} }

View File

@ -47,8 +47,8 @@ impl<const L: DecoLine> DecoNode<L> {
} }
impl<const L: DecoLine> Show for DecoNode<L> { impl<const L: DecoLine> Show for DecoNode<L> {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self(self.0.unguard(sel)).pack() Self(self.0.unguard(id)).pack()
} }
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {

View File

@ -50,10 +50,10 @@ impl LinkNode {
} }
impl Show for LinkNode { impl Show for LinkNode {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self { Self {
dest: self.dest.clone(), dest: self.dest.clone(),
body: self.body.as_ref().map(|body| body.unguard(sel)), body: self.body.as_ref().map(|body| body.unguard(id)),
} }
.pack() .pack()
} }

View File

@ -503,8 +503,8 @@ impl StrongNode {
} }
impl Show for StrongNode { impl Show for StrongNode {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self(self.0.unguard(sel)).pack() Self(self.0.unguard(id)).pack()
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
@ -531,8 +531,8 @@ impl EmphNode {
} }
impl Show for EmphNode { impl Show for EmphNode {
fn unguard_parts(&self, sel: Selector) -> Content { fn unguard_parts(&self, id: RecipeId) -> Content {
Self(self.0.unguard(sel)).pack() Self(self.0.unguard(id)).pack()
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {

View File

@ -52,7 +52,7 @@ impl RawNode {
} }
impl Show for RawNode { impl Show for RawNode {
fn unguard_parts(&self, _: Selector) -> Content { fn unguard_parts(&self, _: RecipeId) -> Content {
Self { text: self.text.clone(), ..*self }.pack() Self { text: self.text.clone(), ..*self }.pack()
} }

View File

@ -43,7 +43,7 @@ impl<const S: ShiftKind> ShiftNode<S> {
} }
impl<const S: ShiftKind> Show for ShiftNode<S> { impl<const S: ShiftKind> Show for ShiftNode<S> {
fn unguard_parts(&self, _: Selector) -> Content { fn unguard_parts(&self, _: RecipeId) -> Content {
Self(self.0.clone()).pack() Self(self.0.clone()).pack()
} }

View File

@ -1,7 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::str::FromStr; use std::str::FromStr;
use super::{Content, Pattern, Regex, Transform, Value}; use super::{Content, Regex, Selector, Transform, Value};
use crate::diag::{with_alternative, StrResult}; use crate::diag::{with_alternative, StrResult};
use crate::font::{FontStretch, FontStyle, FontWeight}; use crate::font::{FontStretch, FontStyle, FontWeight};
use crate::frame::{Destination, Lang, Location, Region}; use crate::frame::{Destination, Lang, Location, Region};
@ -181,10 +181,9 @@ dynamic! {
Regex: "regular expression", Regex: "regular expression",
} }
castable! { dynamic! {
Pattern, Selector: "selector",
Expected: "function, string or regular expression", Value::Func(func) => Self::Node(func.node()?, None),
Value::Func(func) => Self::Node(func.node()?),
Value::Str(text) => Self::text(&text), Value::Str(text) => Self::text(&text),
@regex: Regex => Self::Regex(regex.clone()), @regex: Regex => Self::Regex(regex.clone()),
} }

View File

@ -9,7 +9,7 @@ use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher}; use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node; use typst_macros::node;
use super::{Args, Key, Property, Recipe, Selector, StyleEntry, StyleMap, Value, Vm}; use super::{Args, Key, Property, Recipe, RecipeId, StyleEntry, StyleMap, Value, Vm};
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::util::ReadableTypeId; use crate::util::ReadableTypeId;
use crate::World; use crate::World;
@ -104,7 +104,7 @@ impl Content {
world: Tracked<dyn World>, world: Tracked<dyn World>,
recipe: Recipe, recipe: Recipe,
) -> SourceResult<Self> { ) -> SourceResult<Self> {
if recipe.pattern.is_none() { if recipe.selector.is_none() {
recipe.transform.apply(world, recipe.span, || Value::Content(self)) recipe.transform.apply(world, recipe.span, || Value::Content(self))
} else { } else {
Ok(self.styled_with_entry(StyleEntry::Recipe(recipe))) Ok(self.styled_with_entry(StyleEntry::Recipe(recipe)))
@ -135,9 +135,9 @@ impl Content {
StyledNode { sub: self, map: styles }.pack() StyledNode { sub: self, map: styles }.pack()
} }
/// Reenable the show rule identified by the selector. /// Reenable a specific show rule recipe.
pub fn unguard(&self, sel: Selector) -> Self { pub fn unguard(&self, id: RecipeId) -> Self {
self.clone().styled_with_entry(StyleEntry::Unguard(sel)) self.clone().styled_with_entry(StyleEntry::Unguard(id))
} }
} }

View File

@ -7,7 +7,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{ use super::{
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
Pattern, Recipe, Scope, Scopes, StyleMap, Transform, Value, Vm, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, Vm,
}; };
use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::geom::{Abs, Angle, Em, Fr, Ratio};
@ -831,10 +831,12 @@ impl Eval for ast::SetRule {
return Ok(StyleMap::new()); return Ok(StyleMap::new());
} }
} }
let target = self.target(); let target = self.target();
let target = target.eval(vm)?.cast::<Func>().at(target.span())?; let span = target.span();
let target = target.eval(vm)?.cast::<Func>().at(span)?;
let args = self.args().eval(vm)?; let args = self.args().eval(vm)?;
target.set(args) target.set(args, span)
} }
} }
@ -842,16 +844,16 @@ impl Eval for ast::ShowRule {
type Output = Recipe; type Output = Recipe;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let pattern = self let selector = self
.pattern() .selector()
.map(|pattern| pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())) .map(|selector| selector.eval(vm)?.cast::<Selector>().at(selector.span()))
.transpose()?; .transpose()?;
let transform = self.transform(); let transform = self.transform();
let span = transform.span(); let span = transform.span();
let transform = transform.eval(vm)?.cast::<Transform>().at(span)?; let transform = transform.eval(vm)?.cast::<Transform>().at(span)?;
Ok(Recipe { span, pattern, transform }) Ok(Recipe { span, selector, transform })
} }
} }

View File

@ -4,10 +4,12 @@ use std::sync::Arc;
use comemo::{Track, Tracked}; use comemo::{Track, Tracked};
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm}; use super::{
Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, StyleMap, Value, Vm,
};
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, Expr, TypedNode}; use crate::syntax::ast::{self, Expr, TypedNode};
use crate::syntax::{SourceId, SyntaxNode}; use crate::syntax::{SourceId, Span, SyntaxNode};
use crate::util::EcoString; use crate::util::EcoString;
use crate::World; use crate::World;
@ -54,11 +56,6 @@ impl Func {
Self(Arc::new(Repr::Closure(closure))) Self(Arc::new(Repr::Closure(closure)))
} }
/// Apply the given arguments to the function.
pub fn with(self, args: Args) -> Self {
Self(Arc::new(Repr::With(self, args)))
}
/// The name of the function. /// The name of the function.
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match self.0.as_ref() { match self.0.as_ref() {
@ -106,12 +103,18 @@ impl Func {
self.call(&mut vm, args) self.call(&mut vm, args)
} }
/// Apply the given arguments to the function.
pub fn with(self, args: Args) -> Self {
Self(Arc::new(Repr::With(self, args)))
}
/// Execute the function's set rule and return the resulting style map. /// Execute the function's set rule and return the resulting style map.
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { pub fn set(&self, mut args: Args, span: Span) -> SourceResult<StyleMap> {
let styles = match self.0.as_ref() { let Repr::Native(Native { set: Some(set), .. }) = self.0.as_ref() else {
Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, bail!(span, "this function cannot be customized with set");
_ => StyleMap::new(),
}; };
let styles = set(&mut args)?;
args.finish()?; args.finish()?;
Ok(styles) Ok(styles)
} }
@ -123,6 +126,18 @@ impl Func {
_ => Err("this function cannot be customized with show")?, _ => Err("this function cannot be customized with show")?,
} }
} }
/// Create a selector from this node and the given arguments.
pub fn where_(self, args: &mut Args) -> StrResult<Selector> {
match self.0.as_ref() {
Repr::Native(Native { node: Some(id), .. }) => {
let named = args.to_named();
args.items.retain(|arg| arg.name.is_none());
Ok(Selector::Node(*id, Some(named)))
}
_ => Err("this function is not selectable")?,
}
}
} }
impl Debug for Func { impl Debug for Func {

View File

@ -98,6 +98,7 @@ pub fn call(
Value::Func(func) => match method { Value::Func(func) => match method {
"with" => Value::Func(func.with(args.take())), "with" => Value::Func(func.with(args.take())),
"where" => Value::dynamic(func.where_(&mut args).at(span)?),
_ => return missing(), _ => return missing(),
}, },

View File

@ -77,69 +77,69 @@ impl Str {
} }
/// Whether the given pattern exists in this string. /// Whether the given pattern exists in this string.
pub fn contains(&self, pattern: StrPattern) -> bool { pub fn contains(&self, pattern: Pattern) -> bool {
match pattern { match pattern {
StrPattern::Str(pat) => self.0.contains(pat.as_str()), Pattern::Str(pat) => self.0.contains(pat.as_str()),
StrPattern::Regex(re) => re.is_match(self), Pattern::Regex(re) => re.is_match(self),
} }
} }
/// Whether this string begins with the given pattern. /// Whether this string begins with the given pattern.
pub fn starts_with(&self, pattern: StrPattern) -> bool { pub fn starts_with(&self, pattern: Pattern) -> bool {
match pattern { match pattern {
StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), Pattern::Str(pat) => self.0.starts_with(pat.as_str()),
StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), Pattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0),
} }
} }
/// Whether this string ends with the given pattern. /// Whether this string ends with the given pattern.
pub fn ends_with(&self, pattern: StrPattern) -> bool { pub fn ends_with(&self, pattern: Pattern) -> bool {
match pattern { match pattern {
StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), Pattern::Str(pat) => self.0.ends_with(pat.as_str()),
StrPattern::Regex(re) => { Pattern::Regex(re) => {
re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len()) re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len())
} }
} }
} }
/// The text of the pattern's first match in this string. /// The text of the pattern's first match in this string.
pub fn find(&self, pattern: StrPattern) -> Option<Self> { pub fn find(&self, pattern: Pattern) -> Option<Self> {
match pattern { match pattern {
StrPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), Pattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat),
StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), Pattern::Regex(re) => re.find(self).map(|m| m.as_str().into()),
} }
} }
/// The position of the pattern's first match in this string. /// The position of the pattern's first match in this string.
pub fn position(&self, pattern: StrPattern) -> Option<i64> { pub fn position(&self, pattern: Pattern) -> Option<i64> {
match pattern { match pattern {
StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), Pattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64),
StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64), Pattern::Regex(re) => re.find(self).map(|m| m.start() as i64),
} }
} }
/// The start and, text and capture groups (if any) of the first match of /// The start and, text and capture groups (if any) of the first match of
/// the pattern in this string. /// the pattern in this string.
pub fn match_(&self, pattern: StrPattern) -> Option<Dict> { pub fn match_(&self, pattern: Pattern) -> Option<Dict> {
match pattern { match pattern {
StrPattern::Str(pat) => { Pattern::Str(pat) => {
self.0.match_indices(pat.as_str()).next().map(match_to_dict) self.0.match_indices(pat.as_str()).next().map(match_to_dict)
} }
StrPattern::Regex(re) => re.captures(self).map(captures_to_dict), Pattern::Regex(re) => re.captures(self).map(captures_to_dict),
} }
} }
/// The start, end, text and capture groups (if any) of all matches of the /// The start, end, text and capture groups (if any) of all matches of the
/// pattern in this string. /// pattern in this string.
pub fn matches(&self, pattern: StrPattern) -> Array { pub fn matches(&self, pattern: Pattern) -> Array {
match pattern { match pattern {
StrPattern::Str(pat) => self Pattern::Str(pat) => self
.0 .0
.match_indices(pat.as_str()) .match_indices(pat.as_str())
.map(match_to_dict) .map(match_to_dict)
.map(Value::Dict) .map(Value::Dict)
.collect(), .collect(),
StrPattern::Regex(re) => re Pattern::Regex(re) => re
.captures_iter(self) .captures_iter(self)
.map(captures_to_dict) .map(captures_to_dict)
.map(Value::Dict) .map(Value::Dict)
@ -148,14 +148,14 @@ impl Str {
} }
/// Split this string at whitespace or a specific pattern. /// Split this string at whitespace or a specific pattern.
pub fn split(&self, pattern: Option<StrPattern>) -> Array { pub fn split(&self, pattern: Option<Pattern>) -> Array {
let s = self.as_str(); let s = self.as_str();
match pattern { match pattern {
None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(),
Some(StrPattern::Str(pat)) => { Some(Pattern::Str(pat)) => {
s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect()
} }
Some(StrPattern::Regex(re)) => { Some(Pattern::Regex(re)) => {
re.split(s).map(|v| Value::Str(v.into())).collect() re.split(s).map(|v| Value::Str(v.into())).collect()
} }
} }
@ -167,7 +167,7 @@ impl Str {
/// pattern. /// pattern.
pub fn trim( pub fn trim(
&self, &self,
pattern: Option<StrPattern>, pattern: Option<Pattern>,
at: Option<StrSide>, at: Option<StrSide>,
repeat: bool, repeat: bool,
) -> Self { ) -> Self {
@ -180,7 +180,7 @@ impl Str {
Some(StrSide::Start) => self.0.trim_start(), Some(StrSide::Start) => self.0.trim_start(),
Some(StrSide::End) => self.0.trim_end(), Some(StrSide::End) => self.0.trim_end(),
}, },
Some(StrPattern::Str(pat)) => { Some(Pattern::Str(pat)) => {
let pat = pat.as_str(); let pat = pat.as_str();
let mut s = self.as_str(); let mut s = self.as_str();
if repeat { if repeat {
@ -200,7 +200,7 @@ impl Str {
} }
s s
} }
Some(StrPattern::Regex(re)) => { Some(Pattern::Regex(re)) => {
let s = self.as_str(); let s = self.as_str();
let mut last = 0; let mut last = 0;
let mut range = 0..s.len(); let mut range = 0..s.len();
@ -240,13 +240,13 @@ impl Str {
/// Replace at most `count` occurances of the given pattern with a /// Replace at most `count` occurances of the given pattern with a
/// replacement string (beginning from the start). /// replacement string (beginning from the start).
pub fn replace(&self, pattern: StrPattern, with: Self, count: Option<usize>) -> Self { pub fn replace(&self, pattern: Pattern, with: Self, count: Option<usize>) -> Self {
match pattern { match pattern {
StrPattern::Str(pat) => match count { Pattern::Str(pat) => match count {
Some(n) => self.0.replacen(pat.as_str(), &with, n).into(), Some(n) => self.0.replacen(pat.as_str(), &with, n).into(),
None => self.0.replace(pat.as_str(), &with).into(), None => self.0.replace(pat.as_str(), &with).into(),
}, },
StrPattern::Regex(re) => match count { Pattern::Regex(re) => match count {
Some(n) => re.replacen(self, n, with.as_str()).into(), Some(n) => re.replacen(self, n, with.as_str()).into(),
None => re.replace(self, with.as_str()).into(), None => re.replace(self, with.as_str()).into(),
}, },
@ -433,7 +433,7 @@ impl Hash for Regex {
/// A pattern which can be searched for in a string. /// A pattern which can be searched for in a string.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum StrPattern { pub enum Pattern {
/// Just a string. /// Just a string.
Str(Str), Str(Str),
/// A regular expression. /// A regular expression.
@ -441,7 +441,7 @@ pub enum StrPattern {
} }
castable! { castable! {
StrPattern, Pattern,
Expected: "string or regular expression", Expected: "string or regular expression",
Value::Str(text) => Self::Str(text), Value::Str(text) => Self::Str(text),
@regex: Regex => Self::Regex(regex.clone()), @regex: Regex => Self::Regex(regex.clone()),

View File

@ -7,7 +7,7 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked}; use comemo::{Prehashed, Tracked};
use super::{capability, Args, Content, Func, NodeId, Regex, Smart, Value}; use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::geom::{ use crate::geom::{
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
@ -141,9 +141,9 @@ pub enum StyleEntry {
/// A barrier for scoped styles. /// A barrier for scoped styles.
Barrier(Barrier), Barrier(Barrier),
/// Guards against recursive show rules. /// Guards against recursive show rules.
Guard(Selector), Guard(RecipeId),
/// Allows recursive show rules again. /// Allows recursive show rules again.
Unguard(Selector), Unguard(RecipeId),
} }
impl StyleEntry { impl StyleEntry {
@ -243,8 +243,7 @@ impl<'a> StyleChain<'a> {
world: Tracked<dyn World>, world: Tracked<dyn World>,
target: Target, target: Target,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
// Find out how many recipes there any and whether any of their patterns // Find out how many recipes there any and whether any of them match.
// match.
let mut n = 0; let mut n = 0;
let mut any = true; let mut any = true;
for recipe in self.entries().filter_map(StyleEntry::recipe) { for recipe in self.entries().filter_map(StyleEntry::recipe) {
@ -258,7 +257,7 @@ impl<'a> StyleChain<'a> {
if any { if any {
for recipe in self.entries().filter_map(StyleEntry::recipe) { for recipe in self.entries().filter_map(StyleEntry::recipe) {
if recipe.applicable(target) { if recipe.applicable(target) {
let sel = Selector::Nth(n); let sel = RecipeId::Nth(n);
if self.guarded(sel) { if self.guarded(sel) {
guarded = true; guarded = true;
} else if let Some(content) = recipe.apply(world, sel, target)? { } else if let Some(content) = recipe.apply(world, sel, target)? {
@ -273,7 +272,7 @@ impl<'a> StyleChain<'a> {
if let Target::Node(node) = target { if let Target::Node(node) = target {
// Realize if there was no matching recipe. // Realize if there was no matching recipe.
if realized.is_none() { if realized.is_none() {
let sel = Selector::Base(node.id()); let sel = RecipeId::Base(node.id());
if self.guarded(sel) { if self.guarded(sel) {
guarded = true; guarded = true;
} else { } else {
@ -302,7 +301,7 @@ impl<'a> StyleChain<'a> {
} }
/// Whether the recipe identified by the selector is guarded. /// Whether the recipe identified by the selector is guarded.
fn guarded(self, sel: Selector) -> bool { fn guarded(self, sel: RecipeId) -> bool {
for entry in self.entries() { for entry in self.entries() {
match *entry { match *entry {
StyleEntry::Guard(s) if s == sel => return true, StyleEntry::Guard(s) if s == sel => return true,
@ -976,8 +975,8 @@ impl Fold for PartialStroke<Abs> {
pub struct Recipe { pub struct Recipe {
/// The span errors are reported with. /// The span errors are reported with.
pub span: Span, pub span: Span,
/// The pattern that the rule applies to. /// Determines whether the recipe applies to a node.
pub pattern: Option<Pattern>, pub selector: Option<Selector>,
/// The transformation to perform on the match. /// The transformation to perform on the match.
pub transform: Transform, pub transform: Transform,
} }
@ -985,28 +984,26 @@ pub struct Recipe {
impl Recipe { impl Recipe {
/// Whether the recipe is applicable to the target. /// Whether the recipe is applicable to the target.
pub fn applicable(&self, target: Target) -> bool { pub fn applicable(&self, target: Target) -> bool {
match (&self.pattern, target) { self.selector
(Some(Pattern::Node(id)), Target::Node(node)) => *id == node.id(), .as_ref()
(Some(Pattern::Regex(_)), Target::Text(_)) => true, .map_or(false, |selector| selector.matches(target))
_ => false,
}
} }
/// Try to apply the recipe to the target. /// Try to apply the recipe to the target.
pub fn apply( pub fn apply(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
sel: Selector, sel: RecipeId,
target: Target, target: Target,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
let content = match (target, &self.pattern) { let content = match (target, &self.selector) {
(Target::Node(node), Some(Pattern::Node(id))) if node.id() == *id => { (Target::Node(node), Some(Selector::Node(id, _))) if node.id() == *id => {
self.transform.apply(world, self.span, || { self.transform.apply(world, self.span, || {
Value::Content(node.to::<dyn Show>().unwrap().unguard_parts(sel)) Value::Content(node.to::<dyn Show>().unwrap().unguard_parts(sel))
})? })?
} }
(Target::Text(text), Some(Pattern::Regex(regex))) => { (Target::Text(text), Some(Selector::Regex(regex))) => {
let make = world.config().items.text; let make = world.config().items.text;
let mut result = vec![]; let mut result = vec![];
let mut cursor = 0; let mut cursor = 0;
@ -1043,8 +1040,8 @@ impl Recipe {
/// Whether this recipe is for the given node. /// Whether this recipe is for the given node.
pub fn is_of(&self, node: NodeId) -> bool { pub fn is_of(&self, node: NodeId) -> bool {
match self.pattern { match self.selector {
Some(Pattern::Node(id)) => id == node, Some(Selector::Node(id, _)) => id == node,
_ => false, _ => false,
} }
} }
@ -1052,24 +1049,42 @@ impl Recipe {
impl Debug for Recipe { impl Debug for Recipe {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Recipe matching {:?}", self.pattern) write!(f, "Recipe matching {:?}", self.selector)
} }
} }
/// A show rule pattern that may match a target. /// A selector in a show rule.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum Pattern { pub enum Selector {
/// Defines the appearence of some node. /// Matches a specific type of node.
Node(NodeId), ///
/// Defines text to be replaced. /// If there is a dictionary, only nodes with the fields from the
/// dictionary match.
Node(NodeId, Option<Dict>),
/// Matches text through a regular expression.
Regex(Regex), Regex(Regex),
} }
impl Pattern { impl Selector {
/// Define a simple text replacement pattern. /// Define a simple text selector.
pub fn text(text: &str) -> Self { pub fn text(text: &str) -> Self {
Self::Regex(Regex::new(&regex::escape(text)).unwrap()) Self::Regex(Regex::new(&regex::escape(text)).unwrap())
} }
/// Whether the selector matches for the target.
pub fn matches(&self, target: Target) -> bool {
match (self, target) {
(Self::Node(id, dict), Target::Node(node)) => {
*id == node.id()
&& dict
.iter()
.flat_map(|dict| dict.iter())
.all(|(name, value)| node.field(name).as_ref() == Some(value))
}
(Self::Regex(_), Target::Text(_)) => true,
_ => false,
}
}
} }
/// A show rule transformation that can be applied to a match. /// A show rule transformation that can be applied to a match.
@ -1113,7 +1128,7 @@ pub enum Target<'a> {
/// Identifies a show rule recipe. /// Identifies a show rule recipe.
#[derive(Debug, Copy, Clone, PartialEq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub enum Selector { pub enum RecipeId {
/// The nth recipe from the top of the chain. /// The nth recipe from the top of the chain.
Nth(usize), Nth(usize),
/// The base recipe for a kind of node. /// The base recipe for a kind of node.
@ -1123,8 +1138,8 @@ pub enum Selector {
/// A node that can be realized given some styles. /// A node that can be realized given some styles.
#[capability] #[capability]
pub trait Show: 'static + Sync + Send { pub trait Show: 'static + Sync + Send {
/// Unguard nested content against recursive show rules. /// Unguard nested content against a specific recipe.
fn unguard_parts(&self, sel: Selector) -> Content; fn unguard_parts(&self, id: RecipeId) -> Content;
/// The base recipe for this node that is executed if there is no /// The base recipe for this node that is executed if there is no
/// user-defined show rule. /// user-defined show rule.

View File

@ -1358,8 +1358,8 @@ node! {
} }
impl ShowRule { impl ShowRule {
/// The pattern that this rule matches. /// Defines which nodes the show rule applies to.
pub fn pattern(&self) -> Option<Expr> { pub fn selector(&self) -> Option<Expr> {
self.0 self.0
.children() .children()
.rev() .rev()

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,4 +1,4 @@
// Test bare show without pattern. // Test bare show without selector.
--- ---
#set page(height: 130pt) #set page(height: 130pt)

View File

@ -94,7 +94,7 @@ Another text.
= Heading = Heading
--- ---
// Error: 7-10 expected function, string or regular expression, found color // Error: 7-10 expected selector, found color
#show red: [] #show red: []
--- ---

View File

@ -0,0 +1,36 @@
// Test show rule patterns.
---
// Inline code.
#show raw.where(block: false): rect.with(
radius: 2pt,
outset: (y: 3pt),
inset: (x: 3pt),
fill: luma(230),
)
// Code blocks.
#show raw.where(block: true): rect.with(
outset: -3pt,
inset: 11pt,
fill: luma(230),
stroke: (left: 1.5pt + luma(180)),
)
#set page(margins: (top: 12pt))
#set par(justify: true)
This code tests `code`
with selectors and justification.
```rs
code!("it");
```
---
#show heading.where(level: 1): text.with(red)
#show heading.where(level: 2): text.with(blue)
#show heading: text.with(green)
= Red
== Blue
=== Green

View File

@ -280,7 +280,7 @@
{ {
"comment": "Function name", "comment": "Function name",
"name": "entity.name.function.typst", "name": "entity.name.function.typst",
"match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*:)" "match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*[:.])"
}, },
{ {
"comment": "Function arguments", "comment": "Function arguments",