diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 0fad2939f..a89b4953f 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -39,7 +39,7 @@ impl MathNode { } impl Show for MathNode { - fn unguard_parts(&self, _: Selector) -> Content { + fn unguard_parts(&self, _: RecipeId) -> Content { self.clone().pack() } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index f51b826fd..f08604a8e 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -9,7 +9,7 @@ pub use typst::frame::*; pub use typst::geom::*; pub use typst::model::{ 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, }; pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index 46e98c186..f93be5d98 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -78,8 +78,8 @@ impl HeadingNode { } impl Show for HeadingNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self { body: self.body.unguard(sel), ..*self }.pack() + fn unguard_parts(&self, id: RecipeId) -> Content { + Self { body: self.body.unguard(id), ..*self }.pack() } fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 499207a4c..89dc0f355 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -94,9 +94,9 @@ impl ListNode { } impl Show for ListNode { - fn unguard_parts(&self, sel: Selector) -> Content { + fn unguard_parts(&self, id: RecipeId) -> Content { Self { - items: self.items.map(|item| item.unguard(sel)), + items: self.items.map(|item| item.unguard(id)), ..*self } .pack() @@ -208,7 +208,7 @@ impl ListItem { } } - fn unguard(&self, sel: Selector) -> Self { + fn unguard(&self, sel: RecipeId) -> Self { match self { Self::List(body) => Self::List(Box::new(body.unguard(sel))), Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))), diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs index 18f4eecb4..7004f49ea 100644 --- a/library/src/structure/reference.rs +++ b/library/src/structure/reference.rs @@ -20,7 +20,7 @@ impl RefNode { } impl Show for RefNode { - fn unguard_parts(&self, _: Selector) -> Content { + fn unguard_parts(&self, _: RecipeId) -> Content { Self(self.0.clone()).pack() } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index fbf1c7c0f..8c6191bed 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -58,11 +58,11 @@ impl TableNode { } impl Show for TableNode { - fn unguard_parts(&self, sel: Selector) -> Content { + fn unguard_parts(&self, id: RecipeId) -> Content { Self { tracks: self.tracks.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() } diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index fa0f05a70..bc7a312d9 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -47,8 +47,8 @@ impl DecoNode { } impl Show for DecoNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() + fn unguard_parts(&self, id: RecipeId) -> Content { + Self(self.0.unguard(id)).pack() } fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { diff --git a/library/src/text/link.rs b/library/src/text/link.rs index 4312559e9..b74ca530d 100644 --- a/library/src/text/link.rs +++ b/library/src/text/link.rs @@ -50,10 +50,10 @@ impl LinkNode { } impl Show for LinkNode { - fn unguard_parts(&self, sel: Selector) -> Content { + fn unguard_parts(&self, id: RecipeId) -> Content { Self { 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() } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 86c6884a2..a81647273 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -503,8 +503,8 @@ impl StrongNode { } impl Show for StrongNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() + fn unguard_parts(&self, id: RecipeId) -> Content { + Self(self.0.unguard(id)).pack() } fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { @@ -531,8 +531,8 @@ impl EmphNode { } impl Show for EmphNode { - fn unguard_parts(&self, sel: Selector) -> Content { - Self(self.0.unguard(sel)).pack() + fn unguard_parts(&self, id: RecipeId) -> Content { + Self(self.0.unguard(id)).pack() } fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 5a98cf3bd..c6229d599 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -52,7 +52,7 @@ impl RawNode { } impl Show for RawNode { - fn unguard_parts(&self, _: Selector) -> Content { + fn unguard_parts(&self, _: RecipeId) -> Content { Self { text: self.text.clone(), ..*self }.pack() } diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 0f654b5a9..a91285bf4 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -43,7 +43,7 @@ impl ShiftNode { } impl Show for ShiftNode { - fn unguard_parts(&self, _: Selector) -> Content { + fn unguard_parts(&self, _: RecipeId) -> Content { Self(self.0.clone()).pack() } diff --git a/src/model/cast.rs b/src/model/cast.rs index 7a466b72e..d2e10a1f1 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; 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::font::{FontStretch, FontStyle, FontWeight}; use crate::frame::{Destination, Lang, Location, Region}; @@ -181,10 +181,9 @@ dynamic! { Regex: "regular expression", } -castable! { - Pattern, - Expected: "function, string or regular expression", - Value::Func(func) => Self::Node(func.node()?), +dynamic! { + Selector: "selector", + Value::Func(func) => Self::Node(func.node()?, None), Value::Str(text) => Self::text(&text), @regex: Regex => Self::Regex(regex.clone()), } diff --git a/src/model/content.rs b/src/model/content.rs index 0257f4da5..bc25cd79c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,7 +9,7 @@ use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; 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::util::ReadableTypeId; use crate::World; @@ -104,7 +104,7 @@ impl Content { world: Tracked, recipe: Recipe, ) -> SourceResult { - if recipe.pattern.is_none() { + if recipe.selector.is_none() { recipe.transform.apply(world, recipe.span, || Value::Content(self)) } else { Ok(self.styled_with_entry(StyleEntry::Recipe(recipe))) @@ -135,9 +135,9 @@ impl Content { StyledNode { sub: self, map: styles }.pack() } - /// Reenable the show rule identified by the selector. - pub fn unguard(&self, sel: Selector) -> Self { - self.clone().styled_with_entry(StyleEntry::Unguard(sel)) + /// Reenable a specific show rule recipe. + pub fn unguard(&self, id: RecipeId) -> Self { + self.clone().styled_with_entry(StyleEntry::Unguard(id)) } } diff --git a/src/model/eval.rs b/src/model/eval.rs index fd43c4c31..2ed8c13b3 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -7,7 +7,7 @@ use unicode_segmentation::UnicodeSegmentation; use super::{ 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::geom::{Abs, Angle, Em, Fr, Ratio}; @@ -831,10 +831,12 @@ impl Eval for ast::SetRule { return Ok(StyleMap::new()); } } + let target = self.target(); - let target = target.eval(vm)?.cast::().at(target.span())?; + let span = target.span(); + let target = target.eval(vm)?.cast::().at(span)?; 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; fn eval(&self, vm: &mut Vm) -> SourceResult { - let pattern = self - .pattern() - .map(|pattern| pattern.eval(vm)?.cast::().at(pattern.span())) + let selector = self + .selector() + .map(|selector| selector.eval(vm)?.cast::().at(selector.span())) .transpose()?; let transform = self.transform(); let span = transform.span(); let transform = transform.eval(vm)?.cast::().at(span)?; - Ok(Recipe { span, pattern, transform }) + Ok(Recipe { span, selector, transform }) } } diff --git a/src/model/func.rs b/src/model/func.rs index 8cedb158f..15434bbfe 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -4,10 +4,12 @@ use std::sync::Arc; 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::syntax::ast::{self, Expr, TypedNode}; -use crate::syntax::{SourceId, SyntaxNode}; +use crate::syntax::{SourceId, Span, SyntaxNode}; use crate::util::EcoString; use crate::World; @@ -54,11 +56,6 @@ impl Func { 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. pub fn name(&self) -> Option<&str> { match self.0.as_ref() { @@ -106,12 +103,18 @@ impl Func { 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. - pub fn set(&self, mut args: Args) -> SourceResult { - let styles = match self.0.as_ref() { - Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, - _ => StyleMap::new(), + pub fn set(&self, mut args: Args, span: Span) -> SourceResult { + let Repr::Native(Native { set: Some(set), .. }) = self.0.as_ref() else { + bail!(span, "this function cannot be customized with set"); }; + + let styles = set(&mut args)?; args.finish()?; Ok(styles) } @@ -123,6 +126,18 @@ impl Func { _ => 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 { + 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 { diff --git a/src/model/methods.rs b/src/model/methods.rs index 26d27dfa4..5f879eeb0 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -98,6 +98,7 @@ pub fn call( Value::Func(func) => match method { "with" => Value::Func(func.with(args.take())), + "where" => Value::dynamic(func.where_(&mut args).at(span)?), _ => return missing(), }, diff --git a/src/model/str.rs b/src/model/str.rs index 1fcf7075d..454c561f6 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -77,69 +77,69 @@ impl Str { } /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: StrPattern) -> bool { + pub fn contains(&self, pattern: Pattern) -> bool { match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()), - StrPattern::Regex(re) => re.is_match(self), + Pattern::Str(pat) => self.0.contains(pat.as_str()), + Pattern::Regex(re) => re.is_match(self), } } /// 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 { - StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), - StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), + Pattern::Str(pat) => self.0.starts_with(pat.as_str()), + Pattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), } } /// 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 { - StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), - StrPattern::Regex(re) => { + Pattern::Str(pat) => self.0.ends_with(pat.as_str()), + Pattern::Regex(re) => { 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. - pub fn find(&self, pattern: StrPattern) -> Option { + pub fn find(&self, pattern: Pattern) -> Option { match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), - StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), + Pattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), + Pattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), } } /// The position of the pattern's first match in this string. - pub fn position(&self, pattern: StrPattern) -> Option { + pub fn position(&self, pattern: Pattern) -> Option { match pattern { - StrPattern::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::Str(pat) => self.0.find(pat.as_str()).map(|i| i 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 pattern in this string. - pub fn match_(&self, pattern: StrPattern) -> Option { + pub fn match_(&self, pattern: Pattern) -> Option { match pattern { - StrPattern::Str(pat) => { + Pattern::Str(pat) => { 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 /// pattern in this string. - pub fn matches(&self, pattern: StrPattern) -> Array { + pub fn matches(&self, pattern: Pattern) -> Array { match pattern { - StrPattern::Str(pat) => self + Pattern::Str(pat) => self .0 .match_indices(pat.as_str()) .map(match_to_dict) .map(Value::Dict) .collect(), - StrPattern::Regex(re) => re + Pattern::Regex(re) => re .captures_iter(self) .map(captures_to_dict) .map(Value::Dict) @@ -148,14 +148,14 @@ impl Str { } /// Split this string at whitespace or a specific pattern. - pub fn split(&self, pattern: Option) -> Array { + pub fn split(&self, pattern: Option) -> Array { let s = self.as_str(); match pattern { 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() } - Some(StrPattern::Regex(re)) => { + Some(Pattern::Regex(re)) => { re.split(s).map(|v| Value::Str(v.into())).collect() } } @@ -167,7 +167,7 @@ impl Str { /// pattern. pub fn trim( &self, - pattern: Option, + pattern: Option, at: Option, repeat: bool, ) -> Self { @@ -180,7 +180,7 @@ impl Str { Some(StrSide::Start) => self.0.trim_start(), Some(StrSide::End) => self.0.trim_end(), }, - Some(StrPattern::Str(pat)) => { + Some(Pattern::Str(pat)) => { let pat = pat.as_str(); let mut s = self.as_str(); if repeat { @@ -200,7 +200,7 @@ impl Str { } s } - Some(StrPattern::Regex(re)) => { + Some(Pattern::Regex(re)) => { let s = self.as_str(); let mut last = 0; let mut range = 0..s.len(); @@ -240,13 +240,13 @@ impl Str { /// Replace at most `count` occurances of the given pattern with a /// replacement string (beginning from the start). - pub fn replace(&self, pattern: StrPattern, with: Self, count: Option) -> Self { + pub fn replace(&self, pattern: Pattern, with: Self, count: Option) -> Self { match pattern { - StrPattern::Str(pat) => match count { + Pattern::Str(pat) => match count { Some(n) => self.0.replacen(pat.as_str(), &with, n).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(), 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. #[derive(Debug, Clone)] -pub enum StrPattern { +pub enum Pattern { /// Just a string. Str(Str), /// A regular expression. @@ -441,7 +441,7 @@ pub enum StrPattern { } castable! { - StrPattern, + Pattern, Expected: "string or regular expression", Value::Str(text) => Self::Str(text), @regex: Regex => Self::Regex(regex.clone()), diff --git a/src/model/styles.rs b/src/model/styles.rs index 3800490b8..8e7319420 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,7 +7,7 @@ use std::sync::Arc; 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::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, @@ -141,9 +141,9 @@ pub enum StyleEntry { /// A barrier for scoped styles. Barrier(Barrier), /// Guards against recursive show rules. - Guard(Selector), + Guard(RecipeId), /// Allows recursive show rules again. - Unguard(Selector), + Unguard(RecipeId), } impl StyleEntry { @@ -243,8 +243,7 @@ impl<'a> StyleChain<'a> { world: Tracked, target: Target, ) -> SourceResult> { - // Find out how many recipes there any and whether any of their patterns - // match. + // Find out how many recipes there any and whether any of them match. let mut n = 0; let mut any = true; for recipe in self.entries().filter_map(StyleEntry::recipe) { @@ -258,7 +257,7 @@ impl<'a> StyleChain<'a> { if any { for recipe in self.entries().filter_map(StyleEntry::recipe) { if recipe.applicable(target) { - let sel = Selector::Nth(n); + let sel = RecipeId::Nth(n); if self.guarded(sel) { guarded = true; } else if let Some(content) = recipe.apply(world, sel, target)? { @@ -273,7 +272,7 @@ impl<'a> StyleChain<'a> { if let Target::Node(node) = target { // Realize if there was no matching recipe. if realized.is_none() { - let sel = Selector::Base(node.id()); + let sel = RecipeId::Base(node.id()); if self.guarded(sel) { guarded = true; } else { @@ -302,7 +301,7 @@ impl<'a> StyleChain<'a> { } /// 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() { match *entry { StyleEntry::Guard(s) if s == sel => return true, @@ -976,8 +975,8 @@ impl Fold for PartialStroke { pub struct Recipe { /// The span errors are reported with. pub span: Span, - /// The pattern that the rule applies to. - pub pattern: Option, + /// Determines whether the recipe applies to a node. + pub selector: Option, /// The transformation to perform on the match. pub transform: Transform, } @@ -985,28 +984,26 @@ pub struct Recipe { impl Recipe { /// Whether the recipe is applicable to the target. pub fn applicable(&self, target: Target) -> bool { - match (&self.pattern, target) { - (Some(Pattern::Node(id)), Target::Node(node)) => *id == node.id(), - (Some(Pattern::Regex(_)), Target::Text(_)) => true, - _ => false, - } + self.selector + .as_ref() + .map_or(false, |selector| selector.matches(target)) } /// Try to apply the recipe to the target. pub fn apply( &self, world: Tracked, - sel: Selector, + sel: RecipeId, target: Target, ) -> SourceResult> { - let content = match (target, &self.pattern) { - (Target::Node(node), Some(Pattern::Node(id))) if node.id() == *id => { + let content = match (target, &self.selector) { + (Target::Node(node), Some(Selector::Node(id, _))) if node.id() == *id => { self.transform.apply(world, self.span, || { Value::Content(node.to::().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 mut result = vec![]; let mut cursor = 0; @@ -1043,8 +1040,8 @@ impl Recipe { /// Whether this recipe is for the given node. pub fn is_of(&self, node: NodeId) -> bool { - match self.pattern { - Some(Pattern::Node(id)) => id == node, + match self.selector { + Some(Selector::Node(id, _)) => id == node, _ => false, } } @@ -1052,24 +1049,42 @@ impl Recipe { impl Debug for Recipe { 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)] -pub enum Pattern { - /// Defines the appearence of some node. - Node(NodeId), - /// Defines text to be replaced. +pub enum Selector { + /// Matches a specific type of node. + /// + /// If there is a dictionary, only nodes with the fields from the + /// dictionary match. + Node(NodeId, Option), + /// Matches text through a regular expression. Regex(Regex), } -impl Pattern { - /// Define a simple text replacement pattern. +impl Selector { + /// Define a simple text selector. pub fn text(text: &str) -> Self { Self::Regex(Regex::new(®ex::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. @@ -1113,7 +1128,7 @@ pub enum Target<'a> { /// Identifies a show rule recipe. #[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum Selector { +pub enum RecipeId { /// The nth recipe from the top of the chain. Nth(usize), /// The base recipe for a kind of node. @@ -1123,8 +1138,8 @@ pub enum Selector { /// A node that can be realized given some styles. #[capability] pub trait Show: 'static + Sync + Send { - /// Unguard nested content against recursive show rules. - fn unguard_parts(&self, sel: Selector) -> Content; + /// Unguard nested content against a specific recipe. + fn unguard_parts(&self, id: RecipeId) -> Content; /// The base recipe for this node that is executed if there is no /// user-defined show rule. diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 1b0e8985c..547545c7c 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1358,8 +1358,8 @@ node! { } impl ShowRule { - /// The pattern that this rule matches. - pub fn pattern(&self) -> Option { + /// Defines which nodes the show rule applies to. + pub fn selector(&self) -> Option { self.0 .children() .rev() diff --git a/tests/ref/style/show-selector.png b/tests/ref/style/show-selector.png new file mode 100644 index 000000000..1cdcfa3f0 Binary files /dev/null and b/tests/ref/style/show-selector.png differ diff --git a/tests/typ/style/show-bare.typ b/tests/typ/style/show-bare.typ index 2dba742f0..8b8d0852a 100644 --- a/tests/typ/style/show-bare.typ +++ b/tests/typ/style/show-bare.typ @@ -1,4 +1,4 @@ -// Test bare show without pattern. +// Test bare show without selector. --- #set page(height: 130pt) diff --git a/tests/typ/style/show-node.typ b/tests/typ/style/show-node.typ index 56b7e34aa..e552038de 100644 --- a/tests/typ/style/show-node.typ +++ b/tests/typ/style/show-node.typ @@ -94,7 +94,7 @@ Another text. = Heading --- -// Error: 7-10 expected function, string or regular expression, found color +// Error: 7-10 expected selector, found color #show red: [] --- diff --git a/tests/typ/style/show-selector.typ b/tests/typ/style/show-selector.typ new file mode 100644 index 000000000..dc88d395b --- /dev/null +++ b/tests/typ/style/show-selector.typ @@ -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 diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 8e0907f6e..f8d66234c 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -280,7 +280,7 @@ { "comment": "Function name", "name": "entity.name.function.typst", - "match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*:)" + "match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*[:.])" }, { "comment": "Function arguments",