From 04fb8b288aa7c80607da79db7d085a4820b95a9d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 23 Apr 2022 21:55:58 +0200 Subject: [PATCH] Show rules with type ascribed object --- src/eval/capture.rs | 13 +++++++++ src/eval/content.rs | 23 ++++++++------- src/eval/mod.rs | 25 +++++++++++++--- src/eval/raw.rs | 1 - src/eval/show.rs | 28 ++++++++++++++---- src/eval/styles.rs | 21 +++++++------- src/library/layout/grid.rs | 6 ++-- src/library/math/mod.rs | 20 +++++++++---- src/library/structure/heading.rs | 23 +++++++++------ src/library/structure/list.rs | 49 +++++++++++++++++++++----------- src/library/structure/table.rs | 31 ++++++++++++++------ src/library/text/deco.rs | 31 ++++++++++++-------- src/library/text/link.rs | 41 +++++++++++++++----------- src/library/text/mod.rs | 30 +++++++++++++------ src/library/text/raw.rs | 25 +++++++++------- src/parse/mod.rs | 17 +++-------- src/syntax/ast.rs | 25 ++++++++++------ src/syntax/highlight.rs | 18 ++++++++---- src/syntax/mod.rs | 2 +- tests/typ/style/show.typ | 30 ++++++++----------- 20 files changed, 294 insertions(+), 165 deletions(-) diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 4e8d76046..8e54122fa 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -88,6 +88,14 @@ impl<'a> CapturesVisitor<'a> { self.bind(expr.binding()); } + // A show rule contains a binding, but that binding is only active + // after the target has been evaluated. + Some(Expr::Show(show)) => { + self.visit(show.target().as_red()); + self.bind(show.binding()); + self.visit(show.body().as_red()); + } + // A for loop contains one or two bindings in its pattern. These are // active after the iterable is evaluated but before the body is // evaluated. @@ -162,6 +170,11 @@ mod tests { test("{(..x) => x + y}", &["y"]); test("{(x, y: x + z) => x + y}", &["x", "z"]); + // Show rule. + test("#show x: y as x", &["y"]); + test("#show x: y as x + z", &["y", "z"]); + test("#show x: x as x", &["x"]); + // For loop. test("#for x in y { x + z }", &["y", "z"]); test("#for x, y in y { x + y }", &["y"]); diff --git a/src/eval/content.rs b/src/eval/content.rs index 605abe516..098bfbfc5 100644 --- a/src/eval/content.rs +++ b/src/eval/content.rs @@ -320,7 +320,7 @@ struct ListBuilder<'a> { styles: StyleChain<'a>, kind: ListKind, items: Vec, - wide: bool, + tight: bool, staged: Vec<(&'a Content, StyleChain<'a>)>, } @@ -356,15 +356,15 @@ impl<'a> Builder<'a> { return Ok(()); } Content::List(item) if builder.kind == UNORDERED => { - builder.wide |= - builder.staged.iter().any(|&(t, _)| *t == Content::Parbreak); + builder.tight &= + builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); builder.staged.clear(); builder.items.push(item.clone()); return Ok(()); } Content::Enum(item) if builder.kind == ORDERED => { - builder.wide |= - builder.staged.iter().any(|&(t, _)| *t == Content::Parbreak); + builder.tight &= + builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); builder.staged.clear(); builder.items.push(item.clone()); return Ok(()); @@ -430,7 +430,7 @@ impl<'a> Builder<'a> { styles, kind: UNORDERED, items: vec![item.clone()], - wide: false, + tight: true, staged: vec![], }); } @@ -439,7 +439,7 @@ impl<'a> Builder<'a> { styles, kind: ORDERED, items: vec![item.clone()], - wide: false, + tight: true, staged: vec![], }); } @@ -454,7 +454,8 @@ impl<'a> Builder<'a> { } Content::Show(node) => { let id = node.id(); - let content = node.show(ctx, styles)?; + let realized = styles.realize(ctx, node)?; + let content = node.show(ctx, styles, realized)?; let stored = self.tpa.alloc(content); self.process(ctx, stored, styles.unscoped(id))?; } @@ -532,14 +533,14 @@ impl<'a> Builder<'a> { /// Finish the currently built list. fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> { - let ListBuilder { styles, kind, items, wide, staged } = match self.list.take() { + let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() { Some(list) => list, None => return Ok(()), }; let content = match kind { - UNORDERED => Content::show(ListNode:: { start: 1, wide, items }), - ORDERED | _ => Content::show(ListNode:: { start: 1, wide, items }), + UNORDERED => Content::show(ListNode:: { start: 1, tight, items }), + ORDERED | _ => Content::show(ListNode:: { start: 1, tight, items }), }; let stored = self.tpa.alloc(content); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d9651cced..7e0d3b15d 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -624,13 +624,30 @@ impl Eval for ShowExpr { type Output = StyleMap; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { + // Evaluate the target function. let target = self.target(); let target_span = target.span(); let target = target.eval(ctx, scp)?.cast::().at(target_span)?; - let recipe = self.recipe(); - let recipe_span = recipe.span(); - let recipe = recipe.eval(ctx, scp)?.cast::().at(recipe_span)?; - Ok(target.show(recipe, recipe_span).at(target_span)?) + + // Collect captured variables. + let captured = { + let mut visitor = CapturesVisitor::new(scp); + visitor.visit(self.as_red()); + visitor.finish() + }; + + // Define the recipe function. + let body = self.body(); + let body_span = body.span(); + let recipe = Func::from_closure(Closure { + name: None, + captured, + params: vec![(self.binding().take(), None)], + sink: None, + body, + }); + + Ok(target.show(recipe, body_span).at(target_span)?) } } diff --git a/src/eval/raw.rs b/src/eval/raw.rs index a83c363f7..6792c4915 100644 --- a/src/eval/raw.rs +++ b/src/eval/raw.rs @@ -110,7 +110,6 @@ impl Resolve for RawStroke { } } -// This faciliates RawStroke => Stroke. impl Fold for RawStroke { type Output = Self; diff --git a/src/eval/show.rs b/src/eval/show.rs index 383497ba0..f8d98d52e 100644 --- a/src/eval/show.rs +++ b/src/eval/show.rs @@ -3,15 +3,24 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::sync::Arc; -use super::{Content, StyleChain}; +use super::{Content, Dict, StyleChain}; use crate::diag::TypResult; use crate::util::Prehashed; use crate::Context; /// A node that can be realized given some styles. pub trait Show: 'static { - /// Realize this node in the given styles. - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult; + /// Encode this node into a dictionary. + fn encode(&self) -> Dict; + + /// Show this node in the given styles and optionally given the realization + /// of a show rule. + fn show( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult; /// Convert to a packed show node. fn pack(self) -> ShowNode @@ -42,8 +51,17 @@ impl ShowNode { } impl Show for ShowNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - self.0.show(ctx, styles) + fn encode(&self) -> Dict { + self.0.encode() + } + + fn show( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + self.0.show(ctx, styles, realized) } fn pack(self) -> ShowNode { diff --git a/src/eval/styles.rs b/src/eval/styles.rs index f147d8cf6..b666c85dd 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -4,7 +4,7 @@ use std::hash::Hash; use std::marker::PhantomData; use std::sync::Arc; -use super::{Args, Content, Func, Layout, Node, Smart, Span, Value}; +use super::{Args, Content, Func, Layout, Node, Show, ShowNode, Smart, Span, Value}; use crate::diag::{At, TypResult}; use crate::geom::{Numeric, Relative, Sides, Spec}; use crate::library::layout::PageNode; @@ -510,19 +510,20 @@ impl<'a> StyleChain<'a> { K::get(self, self.values(key)) } - /// Execute and return the result of a user recipe for a node if there is - /// any. - pub fn show(self, ctx: &mut Context, values: I) -> TypResult> - where - T: Node, - I: IntoIterator, - { + /// Realize a node with a user recipe. + pub fn realize( + self, + ctx: &mut Context, + node: &ShowNode, + ) -> TypResult> { + let id = node.id(); if let Some(recipe) = self .entries() .filter_map(Entry::recipe) - .find(|recipe| recipe.node == TypeId::of::()) + .find(|recipe| recipe.node == id) { - let args = Args::from_values(recipe.span, values); + let dict = node.encode(); + let args = Args::from_values(recipe.span, [Value::Dict(dict)]); Ok(Some(recipe.func.call(ctx, args)?.cast().at(recipe.span)?)) } else { Ok(None) diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 4908d4d8b..afd08aeed 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -8,7 +8,7 @@ pub struct GridNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Spec>, /// The nodes to be arranged in a grid. - pub children: Vec, + pub cells: Vec, } #[node] @@ -25,7 +25,7 @@ impl GridNode { column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), - children: args.all()?, + cells: args.all()?, })) } } @@ -41,7 +41,7 @@ impl Layout for GridNode { let layouter = GridLayouter::new( self.tracks.as_deref(), self.gutter.as_deref(), - &self.children, + &self.cells, regions, styles, ); diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index e6548438f..587949d74 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -28,11 +28,21 @@ impl MathNode { } impl Show for MathNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - let args = [Value::Str(self.formula.clone()), Value::Bool(self.display)]; - let mut content = styles - .show::(ctx, args)? - .unwrap_or_else(|| Content::Text(self.formula.trim().into())); + fn encode(&self) -> Dict { + dict! { + "formula" => Value::Str(self.formula.clone()), + "display" => Value::Bool(self.display) + } + } + + fn show( + &self, + _: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + let mut content = + realized.unwrap_or_else(|| Content::Text(self.formula.trim().into())); let mut map = StyleMap::new(); if let Smart::Custom(family) = styles.get(Self::FAMILY) { diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index 07e5e6628..a352cc92b 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -56,21 +56,26 @@ impl HeadingNode { } impl Show for HeadingNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + fn encode(&self) -> Dict { + dict! { + "level" => Value::Int(self.level as i64), + "body" => Value::Content(self.body.clone()), + } + } + + fn show( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { macro_rules! resolve { ($key:expr) => { styles.get($key).resolve(ctx, self.level)? }; } - let args = [ - Value::Int(self.level as i64), - Value::Content(self.body.clone()), - ]; - - let mut body = styles - .show::(ctx, args)? - .unwrap_or_else(|| self.body.clone()); + let mut body = realized.unwrap_or_else(|| self.body.clone()); let mut map = StyleMap::new(); map.set(TextNode::SIZE, resolve!(Self::SIZE)); diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 10dcfb7b9..c59b443dc 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -10,9 +10,9 @@ use crate::library::utility::Numbering; pub struct ListNode { /// Where the list starts. pub start: usize, - /// If true, there is paragraph spacing between the items, if false + /// If false, there is paragraph spacing between the items, if true /// there is list spacing between the items. - pub wide: bool, + pub tight: bool, /// The individual bulleted or numbered items. pub items: Vec, } @@ -55,7 +55,7 @@ impl ListNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::show(Self { start: args.named("start")?.unwrap_or(1), - wide: args.named("wide")?.unwrap_or(false), + tight: args.named("tight")?.unwrap_or(true), items: args .all()? .into_iter() @@ -66,30 +66,47 @@ impl ListNode { } impl Show for ListNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - let args = self.items.iter().map(|item| Value::Content((*item.body).clone())); - let content = if let Some(content) = styles.show::(ctx, args)? { + fn encode(&self) -> Dict { + dict! { + "start" => Value::Int(self.start as i64), + "tight" => Value::Bool(self.tight), + "items" => Value::Array( + self.items + .iter() + .map(|item| Value::Content((*item.body).clone())) + .collect() + ), + } + } + + fn show( + &self, + ctx: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + let content = if let Some(content) = realized { content } else { - let mut children = vec![]; + let mut cells = vec![]; let mut number = self.start; let label = styles.get(Self::LABEL); for item in &self.items { number = item.number.unwrap_or(number); - children.push(LayoutNode::default()); - children.push(label.resolve(ctx, L, number)?.pack()); - children.push(LayoutNode::default()); - children.push((*item.body).clone().pack()); + cells.push(LayoutNode::default()); + cells.push(label.resolve(ctx, L, number)?.pack()); + cells.push(LayoutNode::default()); + cells.push((*item.body).clone().pack()); number += 1; } let leading = styles.get(ParNode::LEADING); - let spacing = if self.wide { - styles.get(ParNode::SPACING) - } else { + let spacing = if self.tight { styles.get(Self::SPACING) + } else { + styles.get(ParNode::SPACING) }; let gutter = leading + spacing; @@ -104,7 +121,7 @@ impl Show for ListNode { TrackSizing::Auto, ]), gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]), - children, + cells, }) }; @@ -127,7 +144,7 @@ impl Show for ListNode { impl From for ListNode { fn from(item: ListItem) -> Self { - Self { items: vec![item], wide: false, start: 1 } + Self { items: vec![item], tight: true, start: 1 } } } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 96d3bd5b5..aefd01b54 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -9,7 +9,7 @@ pub struct TableNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Spec>, /// The nodes to be arranged in the table. - pub children: Vec, + pub cells: Vec, } #[node(showable)] @@ -37,7 +37,7 @@ impl TableNode { column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), - children: args.all()?, + cells: args.all()?, })) } @@ -53,9 +53,24 @@ impl TableNode { } impl Show for TableNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - let args = self.children.iter().map(|child| Value::Content(child.clone())); - if let Some(content) = styles.show::(ctx, args)? { + fn encode(&self) -> Dict { + dict! { + "cells" => Value::Array( + self.cells + .iter() + .map(|cell| Value::Content(cell.clone())) + .collect() + ), + } + } + + fn show( + &self, + _: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + if let Some(content) = realized { return Ok(content); } @@ -65,8 +80,8 @@ impl Show for TableNode { let padding = styles.get(Self::PADDING); let cols = self.tracks.x.len().max(1); - let children = self - .children + let cells = self + .cells .iter() .cloned() .enumerate() @@ -90,7 +105,7 @@ impl Show for TableNode { Ok(Content::block(GridNode { tracks: self.tracks.clone(), gutter: self.gutter.clone(), - children, + cells, })) } } diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index 7481b836e..9fe4e65a6 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -43,18 +43,25 @@ impl DecoNode { } impl Show for DecoNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - Ok(styles - .show::(ctx, [Value::Content(self.0.clone())])? - .unwrap_or_else(|| { - self.0.clone().styled(TextNode::DECO, Decoration { - line: L, - stroke: styles.get(Self::STROKE).unwrap_or_default(), - offset: styles.get(Self::OFFSET), - extent: styles.get(Self::EXTENT), - evade: styles.get(Self::EVADE), - }) - })) + fn encode(&self) -> Dict { + dict! { "body" => Value::Content(self.0.clone()) } + } + + fn show( + &self, + _: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + Ok(realized.unwrap_or_else(|| { + self.0.clone().styled(TextNode::DECO, Decoration { + line: L, + stroke: styles.get(Self::STROKE).unwrap_or_default(), + offset: styles.get(Self::OFFSET), + extent: styles.get(Self::EXTENT), + evade: styles.get(Self::EVADE), + }) + })) } } diff --git a/src/library/text/link.rs b/src/library/text/link.rs index 3ef7011d1..d1e5eb8e3 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -28,24 +28,31 @@ impl LinkNode { } impl Show for LinkNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - let args = [Value::Str(self.url.clone()), match &self.body { - Some(body) => Value::Content(body.clone()), - None => Value::None, - }]; + fn encode(&self) -> Dict { + dict! { + "url" => Value::Str(self.url.clone()), + "body" => match &self.body { + Some(body) => Value::Content(body.clone()), + None => Value::None, + }, + } + } - let mut body = styles - .show::(ctx, args)? - .or_else(|| self.body.clone()) - .unwrap_or_else(|| { - let url = &self.url; - let mut text = url.as_str(); - for prefix in ["mailto:", "tel:"] { - text = text.trim_start_matches(prefix); - } - let shorter = text.len() < url.len(); - Content::Text(if shorter { text.into() } else { url.clone() }) - }); + fn show( + &self, + _: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { + let mut body = realized.or_else(|| self.body.clone()).unwrap_or_else(|| { + let url = &self.url; + let mut text = url.as_str(); + for prefix in ["mailto:", "tel:"] { + text = text.trim_start_matches(prefix); + } + let shorter = text.len() < url.len(); + Content::Text(if shorter { text.into() } else { url.clone() }) + }); let mut map = StyleMap::new(); map.set(TextNode::LINK, Some(self.url.clone())); diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index bde553e23..e477e76d1 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -471,10 +471,17 @@ impl StrongNode { } impl Show for StrongNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - Ok(styles - .show::(ctx, [Value::Content(self.0.clone())])? - .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle))) + fn encode(&self) -> Dict { + dict! { "body" => Value::Content(self.0.clone()) } + } + + fn show( + &self, + _: &mut Context, + _: StyleChain, + realized: Option, + ) -> TypResult { + Ok(realized.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle))) } } @@ -490,9 +497,16 @@ impl EmphNode { } impl Show for EmphNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - Ok(styles - .show::(ctx, [Value::Content(self.0.clone())])? - .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle))) + fn encode(&self) -> Dict { + dict! { "body" => Value::Content(self.0.clone()) } + } + + fn show( + &self, + _: &mut Context, + _: StyleChain, + realized: Option, + ) -> TypResult { + Ok(realized.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle))) } } diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index db97da075..cc225bedb 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -43,7 +43,19 @@ impl RawNode { } impl Show for RawNode { - fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + fn encode(&self) -> Dict { + dict! { + "text" => Value::Str(self.text.clone()), + "block" => Value::Bool(self.block) + } + } + + fn show( + &self, + _: &mut Context, + styles: StyleChain, + realized: Option, + ) -> TypResult { let lang = styles.get(Self::LANG).as_ref(); let foreground = THEME .settings @@ -52,16 +64,7 @@ impl Show for RawNode { .unwrap_or(Color::BLACK) .into(); - let args = [ - Value::Str(self.text.clone()), - match lang { - Some(lang) => Value::Str(lang.clone()), - None => Value::None, - }, - Value::Bool(self.block), - ]; - - let mut content = if let Some(content) = styles.show::(ctx, args)? { + let mut content = if let Some(content) = realized { content } else if matches!( lang.map(|s| s.to_lowercase()).as_deref(), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index c387de0c2..b27af08fe 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -809,19 +809,10 @@ fn show_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ShowExpr, |p| { p.eat_assert(&NodeKind::Show); ident(p)?; - if !p.at(&NodeKind::LeftParen) { - p.expected_found("parameter list"); - return Err(ParseError); - } - p.perform(NodeKind::ClosureExpr, |p| { - let marker = p.marker(); - p.start_group(Group::Paren); - collection(p); - p.end_group(); - params(p, marker); - p.eat_expect(&NodeKind::As)?; - expr(p) - }) + p.eat_expect(&NodeKind::Colon)?; + ident(p)?; + p.eat_expect(&NodeKind::As)?; + expr(p) }) } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 8af359bf2..82bb7d563 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -249,7 +249,7 @@ pub enum Expr { Let(LetExpr), /// A set expression: `set text(...)`. Set(SetExpr), - /// A show expression: `show heading(body) as [*{body}*]`. + /// A show expression: `show node: heading as [*{nody.body}*]`. Show(ShowExpr), /// A wrap expression: `wrap body in columns(2, body)`. Wrap(WrapExpr), @@ -999,19 +999,28 @@ impl SetExpr { } node! { - /// A show expression: `show heading(body) as [*{body}*]`. + /// A show expression: `show node: heading as [*{nody.body}*]`. ShowExpr } impl ShowExpr { - /// The function to customize with this show rule. - pub fn target(&self) -> Ident { - self.0.cast_first_child().expect("show rule is missing target") + /// The binding to assign to. + pub fn binding(&self) -> Ident { + self.0.cast_first_child().expect("show rule is missing binding") } - /// The closure that defines the rule. - pub fn recipe(&self) -> ClosureExpr { - self.0.cast_last_child().expect("show rule is missing closure") + /// The function to customize with this show rule. + pub fn target(&self) -> Ident { + self.0 + .children() + .filter_map(RedRef::cast) + .nth(1) + .expect("show rule is missing target") + } + + /// The expression that realizes the node. + pub fn body(&self) -> Expr { + self.0.cast_last_child().expect("show rule is missing body") } } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 10dfce69f..9bee73ae3 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -178,13 +178,21 @@ impl Category { NodeKind::None => Some(Category::None), NodeKind::Auto => Some(Category::Auto), NodeKind::Ident(_) => match parent.kind() { - NodeKind::Named => None, - NodeKind::ClosureExpr if i == 0 => Some(Category::Function), - NodeKind::SetExpr => Some(Category::Function), - NodeKind::ShowExpr => Some(Category::Function), + NodeKind::Markup(_) => Some(Category::Variable), NodeKind::FuncCall => Some(Category::Function), NodeKind::MethodCall if i > 0 => Some(Category::Function), - NodeKind::Markup(_) => Some(Category::Variable), + NodeKind::ClosureExpr if i == 0 => Some(Category::Function), + NodeKind::SetExpr => Some(Category::Function), + NodeKind::ShowExpr + if parent + .children() + .filter(|c| matches!(c.kind(), NodeKind::Ident(_))) + .map(RedRef::span) + .nth(1) + .map_or(false, |span| span == child.span()) => + { + Some(Category::Function) + } _ => None, }, NodeKind::Bool(_) => Some(Category::Bool), diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 71646cb28..00bcb376c 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -671,7 +671,7 @@ pub enum NodeKind { LetExpr, /// A set expression: `set text(...)`. SetExpr, - /// A show expression: `show heading(body) as [*{body}*]`. + /// A show expression: `show node: heading as [*{nody.body}*]`. ShowExpr, /// A wrap expression: `wrap body in columns(2, body)`. WrapExpr, diff --git a/tests/typ/style/show.typ b/tests/typ/style/show.typ index 7a5aba8f1..9aabfb34b 100644 --- a/tests/typ/style/show.typ +++ b/tests/typ/style/show.typ @@ -4,13 +4,13 @@ #let i = 1 #set heading(size: 1em) -#show heading(level, body) as { - if level == 1 { +#show node: heading as { + if node.level == 1 { v(10pt) - underline(text(1.5em, blue)[{i}. #body]) + underline(text(1.5em, blue)[{i}. {node.body}]) i += 1 } else { - text(red, body) + text(red, node.body) } } @@ -30,29 +30,23 @@ Another text. --- #set heading(size: 1em, strong: false, block: false) -#show heading(a, b) as [B] +#show _: heading as [B] A [= Heading] C --- -// Error: 14-22 unexpected argument -#show heading() as [] +// Error: 21-25 expected content, found string +#show _: heading as "hi" = Heading --- -// Error: 14-28 expected content, found string -#show heading(_, _) as "hi" +// Error: 22-29 dictionary does not contain key: "page" +#show it: heading as it.page = Heading --- -// Error: 7-12 this function cannot be customized with show -#show upper() as {} - ---- -// Ref: false -// // Error: 1-29 show rule is recursive -// #show strong(x) as strong(x) -// *Hi* +// Error: 10-15 this function cannot be customized with show +#show _: upper as {} --- // Error: 2-19 set, show and wrap are only allowed directly in markup -{show list(a) as b} +{show a: list as a}