summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/math/mod.rs2
-rw-r--r--library/src/prelude.rs2
-rw-r--r--library/src/structure/heading.rs4
-rw-r--r--library/src/structure/list.rs6
-rw-r--r--library/src/structure/reference.rs2
-rw-r--r--library/src/structure/table.rs4
-rw-r--r--library/src/text/deco.rs4
-rw-r--r--library/src/text/link.rs4
-rw-r--r--library/src/text/mod.rs8
-rw-r--r--library/src/text/raw.rs2
-rw-r--r--library/src/text/shift.rs2
-rw-r--r--src/model/cast.rs9
-rw-r--r--src/model/content.rs10
-rw-r--r--src/model/eval.rs16
-rw-r--r--src/model/func.rs37
-rw-r--r--src/model/methods.rs1
-rw-r--r--src/model/str.rs64
-rw-r--r--src/model/styles.rs79
-rw-r--r--src/syntax/ast.rs4
-rw-r--r--tests/ref/style/show-selector.pngbin0 -> 12923 bytes
-rw-r--r--tests/typ/style/show-bare.typ2
-rw-r--r--tests/typ/style/show-node.typ2
-rw-r--r--tests/typ/style/show-selector.typ36
-rw-r--r--tools/support/typst.tmLanguage.json2
24 files changed, 185 insertions, 117 deletions
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index 0fad2939..a89b4953 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 f51b826f..f08604a8 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 46e98c18..f93be5d9 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<dyn World>, _: StyleChain) -> SourceResult<Content> {
diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs
index 499207a4..89dc0f35 100644
--- a/library/src/structure/list.rs
+++ b/library/src/structure/list.rs
@@ -94,9 +94,9 @@ impl<const L: ListKind> 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 {
- 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 18f4eecb..7004f49e 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 fbf1c7c0..8c6191be 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 fa0f05a7..bc7a312d 100644
--- a/library/src/text/deco.rs
+++ b/library/src/text/deco.rs
@@ -47,8 +47,8 @@ impl<const L: DecoLine> DecoNode<L> {
}
impl<const L: DecoLine> Show for DecoNode<L> {
- 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<dyn World>, styles: StyleChain) -> SourceResult<Content> {
diff --git a/library/src/text/link.rs b/library/src/text/link.rs
index 4312559e..b74ca530 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 86c6884a..a8164727 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<dyn World>, _: StyleChain) -> SourceResult<Content> {
@@ -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<dyn World>, _: StyleChain) -> SourceResult<Content> {
diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs
index 5a98cf3b..c6229d59 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 0f654b5a..a91285bf 100644
--- a/library/src/text/shift.rs
+++ b/library/src/text/shift.rs
@@ -43,7 +43,7 @@ impl<const S: ShiftKind> 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()
}
diff --git a/src/model/cast.rs b/src/model/cast.rs
index 7a466b72..d2e10a1f 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 0257f4da..bc25cd79 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<dyn World>,
recipe: Recipe,
) -> SourceResult<Self> {
- 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 fd43c4c3..2ed8c13b 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::<Func>().at(target.span())?;
+ let span = target.span();
+ let target = target.eval(vm)?.cast::<Func>().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<Self::Output> {
- let pattern = self
- .pattern()
- .map(|pattern| pattern.eval(vm)?.cast::<Pattern>().at(pattern.span()))
+ let selector = self
+ .selector()
+ .map(|selector| selector.eval(vm)?.cast::<Selector>().at(selector.span()))
.transpose()?;
let transform = self.transform();
let span = transform.span();
let transform = transform.eval(vm)?.cast::<Transform>().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 8cedb158..15434bbf 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<StyleMap> {
- 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<StyleMap> {
+ 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<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 {
diff --git a/src/model/methods.rs b/src/model/methods.rs
index 26d27dfa..5f879eeb 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 1fcf7075..454c561f 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<Self> {
+ pub fn find(&self, pattern: Pattern) -> Option<Self> {
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<i64> {
+ pub fn position(&self, pattern: Pattern) -> Option<i64> {
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<Dict> {
+ pub fn match_(&self, pattern: Pattern) -> Option<Dict> {
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<StrPattern>) -> Array {
+ pub fn split(&self, pattern: Option<Pattern>) -> 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<StrPattern>,
+ pattern: Option<Pattern>,
at: Option<StrSide>,
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<usize>) -> Self {
+ pub fn replace(&self, pattern: Pattern, with: Self, count: Option<usize>) -> 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 3800490b..8e731942 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<dyn World>,
target: Target,
) -> SourceResult<Option<Content>> {
- // 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<Abs> {
pub struct Recipe {
/// The span errors are reported with.
pub span: Span,
- /// The pattern that the rule applies to.
- pub pattern: Option<Pattern>,
+ /// Determines whether the recipe applies to a node.
+ pub selector: Option<Selector>,
/// 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<dyn World>,
- sel: Selector,
+ sel: RecipeId,
target: Target,
) -> SourceResult<Option<Content>> {
- 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::<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 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<Dict>),
+ /// 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(&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.
@@ -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 1b0e8985..547545c7 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<Expr> {
+ /// Defines which nodes the show rule applies to.
+ pub fn selector(&self) -> Option<Expr> {
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 00000000..1cdcfa3f
--- /dev/null
+++ b/tests/ref/style/show-selector.png
Binary files differ
diff --git a/tests/typ/style/show-bare.typ b/tests/typ/style/show-bare.typ
index 2dba742f..8b8d0852 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 56b7e34a..e552038d 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 00000000..dc88d395
--- /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 8e0907f6..f8d66234 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",