Selectors
This commit is contained in:
parent
efd1853d06
commit
0a41844cc4
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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> {
|
||||
|
@ -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))),
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()),
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
},
|
||||
|
||||
|
@ -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()),
|
||||
|
@ -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(®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.
|
||||
|
@ -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()
|
||||
|
BIN
tests/ref/style/show-selector.png
Normal file
BIN
tests/ref/style/show-selector.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -1,4 +1,4 @@
|
||||
// Test bare show without pattern.
|
||||
// Test bare show without selector.
|
||||
|
||||
---
|
||||
#set page(height: 130pt)
|
||||
|
@ -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: []
|
||||
|
||||
---
|
||||
|
36
tests/typ/style/show-selector.typ
Normal file
36
tests/typ/style/show-selector.typ
Normal 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
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user