summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/layout/mod.rs14
-rw-r--r--library/src/meta/heading.rs73
-rw-r--r--library/src/meta/numbering.rs17
-rw-r--r--library/src/meta/outline.rs62
-rw-r--r--library/src/meta/reference.rs141
-rw-r--r--library/src/prelude.rs4
-rw-r--r--library/src/text/raw.rs10
-rw-r--r--macros/src/node.rs24
-rw-r--r--src/eval/library.rs4
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/ide/complete.rs1
-rw-r--r--src/model/content.rs33
-rw-r--r--src/model/realize.rs20
-rw-r--r--src/model/typeset.rs31
-rw-r--r--src/syntax/parser.rs38
-rw-r--r--tests/ref/meta/ref.pngbin0 -> 11693 bytes
-rw-r--r--tests/typ/compiler/set.typ4
-rw-r--r--tests/typ/meta/ref.typ21
18 files changed, 363 insertions, 136 deletions
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
index dc373ff5..eb440b7f 100644
--- a/library/src/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -232,20 +232,6 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.scratch.content.alloc(FormulaNode::new(content.clone()).pack());
}
- // Prepare only if this is the first application for this node.
- if content.can::<dyn Prepare>() {
- if !content.is_prepared() {
- let prepared = content
- .clone()
- .prepared()
- .with::<dyn Prepare>()
- .unwrap()
- .prepare(self.vt, styles)?;
- let stored = self.scratch.content.alloc(prepared);
- return self.accept(stored, styles);
- }
- }
-
if let Some(styled) = content.to::<StyledNode>() {
return self.styled(styled, styles);
}
diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs
index 8677aa55..1bff3af4 100644
--- a/library/src/meta/heading.rs
+++ b/library/src/meta/heading.rs
@@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize};
///
/// Display: Heading
/// Category: meta
-#[node(Prepare, Show, Finalize)]
+#[node(Synthesize, Show, Finalize)]
pub struct HeadingNode {
/// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::new(1).unwrap())]
@@ -76,44 +76,61 @@ pub struct HeadingNode {
/// The heading's title.
#[required]
pub body: Content,
+
+ /// The heading's numbering numbers.
+ ///
+ /// ```example
+ /// #show heading: it => it.numbers
+ ///
+ /// = First
+ /// == Second
+ /// = Third
+ /// ```
+ #[synthesized]
+ pub numbers: Option<Vec<NonZeroUsize>>,
}
-impl Prepare for HeadingNode {
- fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
+impl Synthesize for HeadingNode {
+ fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content {
let my_id = vt.identify(self);
+ let numbered = self.numbering(styles).is_some();
let mut counter = HeadingCounter::new();
- for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) {
- if node_id == my_id {
- break;
+ if numbered {
+ // Advance passed existing headings.
+ for (_, node) in vt
+ .locate(Selector::node::<HeadingNode>())
+ .into_iter()
+ .take_while(|&(id, _)| id != my_id)
+ {
+ let heading = node.to::<HeadingNode>().unwrap();
+ if heading.numbering(StyleChain::default()).is_some() {
+ counter.advance(heading);
+ }
}
- let numbers = node.field("numbers").unwrap();
- if *numbers != Value::None {
- let heading = node.to::<Self>().unwrap();
- counter.advance(heading);
- }
+ // Advance passed self.
+ counter.advance(self);
}
- let mut numbers = Value::None;
- if let Some(numbering) = self.numbering(styles) {
- numbers = numbering.apply(vt.world(), counter.advance(self))?;
- }
+ let node = self
+ .clone()
+ .with_outlined(self.outlined(styles))
+ .with_numbering(self.numbering(styles))
+ .with_numbers(numbered.then(|| counter.take()))
+ .pack();
- let mut node = self.clone().pack();
- node.push_field("outlined", Value::Bool(self.outlined(styles)));
- node.push_field("numbers", numbers);
let meta = Meta::Node(my_id, node.clone());
- Ok(node.styled(MetaNode::set_data(vec![meta])))
+ node.styled(MetaNode::set_data(vec![meta]))
}
}
impl Show for HeadingNode {
- fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
+ fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body();
- let numbers = self.0.field("numbers").unwrap();
- if *numbers != Value::None {
- realized = numbers.clone().display()
+ if let Some(numbering) = self.numbering(styles) {
+ let numbers = self.numbers().unwrap();
+ realized = numbering.apply(vt.world(), &numbers)?.display()
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
+ realized;
}
@@ -168,4 +185,14 @@ impl HeadingCounter {
&self.0
}
+
+ /// Take out the current counts.
+ pub fn take(self) -> Vec<NonZeroUsize> {
+ self.0
+ }
+}
+
+cast_from_value! {
+ HeadingNode,
+ v: Content => v.to::<Self>().ok_or("expected heading")?.clone(),
}
diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs
index 4e6e1aed..d71fb233 100644
--- a/library/src/meta/numbering.rs
+++ b/library/src/meta/numbering.rs
@@ -82,7 +82,7 @@ impl Numbering {
numbers: &[NonZeroUsize],
) -> SourceResult<Value> {
Ok(match self {
- Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
+ Self::Pattern(pattern) => Value::Str(pattern.apply(numbers, false).into()),
Self::Func(func) => {
let args = Args::new(
func.span(),
@@ -124,12 +124,16 @@ pub struct NumberingPattern {
impl NumberingPattern {
/// Apply the pattern to the given number.
- pub fn apply(&self, numbers: &[NonZeroUsize]) -> EcoString {
+ pub fn apply(&self, numbers: &[NonZeroUsize], trimmed: bool) -> EcoString {
let mut fmt = EcoString::new();
let mut numbers = numbers.into_iter();
- for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) {
- fmt.push_str(prefix);
+ for (i, ((prefix, kind, case), &n)) in
+ self.pieces.iter().zip(&mut numbers).enumerate()
+ {
+ if i > 0 || !trimmed {
+ fmt.push_str(prefix);
+ }
fmt.push_str(&kind.apply(n, *case));
}
@@ -144,7 +148,10 @@ impl NumberingPattern {
fmt.push_str(&kind.apply(n, *case));
}
- fmt.push_str(&self.suffix);
+ if !trimmed {
+ fmt.push_str(&self.suffix);
+ }
+
fmt
}
diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs
index d66a573d..a2b12511 100644
--- a/library/src/meta/outline.rs
+++ b/library/src/meta/outline.rs
@@ -22,7 +22,7 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
///
/// Display: Outline
/// Category: meta
-#[node(Prepare, Show)]
+#[node(Synthesize, Show)]
pub struct OutlineNode {
/// The title of the outline.
///
@@ -67,21 +67,22 @@ pub struct OutlineNode {
/// ```
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))]
pub fill: Option<Content>,
+
+ /// All outlined headings in the document.
+ #[synthesized]
+ pub headings: Vec<HeadingNode>,
}
-impl Prepare for OutlineNode {
- fn prepare(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
+impl Synthesize for OutlineNode {
+ fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content {
let headings = vt
.locate(Selector::node::<HeadingNode>())
.into_iter()
- .map(|(_, node)| node)
- .filter(|node| *node.field("outlined").unwrap() == Value::Bool(true))
- .map(|node| Value::Content(node.clone()))
+ .map(|(_, node)| node.to::<HeadingNode>().unwrap().clone())
+ .filter(|node| node.outlined(StyleChain::default()))
.collect();
- let mut node = self.clone().pack();
- node.push_field("headings", Value::Array(Array::from_vec(headings)));
- Ok(node)
+ self.clone().with_headings(headings).pack()
}
}
@@ -108,13 +109,12 @@ impl Show for OutlineNode {
let indent = self.indent(styles);
let depth = self.depth(styles);
- let mut ancestors: Vec<&Content> = vec![];
- for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
- if *node.field("outlined").unwrap() != Value::Bool(true) {
+ let mut ancestors: Vec<&HeadingNode> = vec![];
+ for heading in self.headings().iter() {
+ if !heading.outlined(StyleChain::default()) {
continue;
}
- let heading = node.to::<HeadingNode>().unwrap();
if let Some(depth) = depth {
if depth < heading.level(StyleChain::default()) {
continue;
@@ -122,37 +122,40 @@ impl Show for OutlineNode {
}
while ancestors.last().map_or(false, |last| {
- last.to::<HeadingNode>().unwrap().level(StyleChain::default())
- >= heading.level(StyleChain::default())
+ last.level(StyleChain::default()) >= heading.level(StyleChain::default())
}) {
ancestors.pop();
}
// Adjust the link destination a bit to the topleft so that the
// heading is fully visible.
- let mut loc = node.field("loc").unwrap().clone().cast::<Location>().unwrap();
+ let mut loc = heading.0.expect_field::<Location>("location");
loc.pos -= Point::splat(Abs::pt(10.0));
// Add hidden ancestors numberings to realize the indent.
if indent {
- let hidden: Vec<_> = ancestors
- .iter()
- .map(|node| node.field("numbers").unwrap())
- .filter(|&numbers| *numbers != Value::None)
- .map(|numbers| numbers.clone().display() + SpaceNode::new().pack())
- .collect();
-
- if !hidden.is_empty() {
- seq.push(HideNode::new(Content::sequence(hidden)).pack());
+ let mut hidden = Content::empty();
+ for ancestor in &ancestors {
+ if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
+ let numbers = ancestor.numbers().unwrap();
+ hidden += numbering.apply(vt.world(), &numbers)?.display()
+ + SpaceNode::new().pack();
+ };
+ }
+
+ if !ancestors.is_empty() {
+ seq.push(HideNode::new(hidden).pack());
seq.push(SpaceNode::new().pack());
}
}
// Format the numbering.
let mut start = heading.body();
- let numbers = node.field("numbers").unwrap();
- if *numbers != Value::None {
- start = numbers.clone().display() + SpaceNode::new().pack() + start;
+ if let Some(numbering) = heading.numbering(StyleChain::default()) {
+ let numbers = heading.numbers().unwrap();
+ start = numbering.apply(vt.world(), &numbers)?.display()
+ + SpaceNode::new().pack()
+ + start;
};
// Add the numbering and section name.
@@ -176,8 +179,7 @@ impl Show for OutlineNode {
let end = TextNode::packed(eco_format!("{}", loc.page));
seq.push(end.linked(Destination::Internal(loc)));
seq.push(LinebreakNode::new().pack());
-
- ancestors.push(node);
+ ancestors.push(heading);
}
seq.push(ParbreakNode::new().pack());
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
index bfc31785..a46198bd 100644
--- a/library/src/meta/reference.rs
+++ b/library/src/meta/reference.rs
@@ -1,31 +1,152 @@
+use super::{HeadingNode, Numbering};
use crate::prelude::*;
use crate::text::TextNode;
/// A reference to a label.
///
-/// *Note: This function is currently unimplemented.*
-///
/// The reference function produces a textual reference to a label. For example,
/// a reference to a heading will yield an appropriate string such as "Section
-/// 1" for a reference to the first heading's label. The references are also
-/// links to the respective labels.
+/// 1" for a reference to the first heading. The references are also links to
+/// the respective element.
+///
+/// # Example
+/// ```example
+/// #set heading(numbering: "1.")
+///
+/// = Introduction <intro>
+/// Recent developments in typesetting
+/// software have rekindled hope in
+/// previously frustrated researchers.
+/// As shown in @results, we ...
+///
+/// = Results <results>
+/// We evaluate our method in a
+/// series of tests. @perf discusses
+/// the performance aspects of ...
+///
+/// == Performance <perf>
+/// As described in @intro, we ...
+/// ```
///
/// ## Syntax
/// This function also has dedicated syntax: A reference to a label can be
-/// created by typing an `@` followed by the name of the label (e.g. `[=
-/// Introduction <intro>]` can be referenced by typing `[@intro]`).
+/// created by typing an `@` followed by the name of the label (e.g.
+/// `[= Introduction <intro>]` can be referenced by typing `[@intro]`).
///
/// Display: Reference
/// Category: meta
-#[node(Show)]
+#[node(Synthesize, Show)]
pub struct RefNode {
/// The label that should be referenced.
#[required]
- pub target: EcoString,
+ pub label: Label,
+
+ /// The prefix before the referenced number.
+ ///
+ /// ```example
+ /// #set heading(numbering: "1.")
+ /// #set ref(prefix: it => {
+ /// if it.func() == heading {
+ /// "Chapter"
+ /// } else {
+ /// "Thing"
+ /// }
+ /// })
+ ///
+ /// = Introduction <intro>
+ /// In @intro, we see how to turn
+ /// Sections into Chapters.
+ /// ```
+ pub prefix: Smart<Option<Func>>,
+
+ /// All elements with the `target` label in the document.
+ #[synthesized]
+ pub matches: Vec<Content>,
+}
+
+impl Synthesize for RefNode {
+ fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content {
+ let matches = vt
+ .locate(Selector::Label(self.label()))
+ .into_iter()
+ .map(|(_, node)| node.clone())
+ .collect();
+
+ self.clone().with_matches(matches).pack()
+ }
}
impl Show for RefNode {
- fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(TextNode::packed(eco_format!("@{}", self.target())))
+ fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
+ let matches = self.matches();
+ let [target] = matches.as_slice() else {
+ if vt.locatable() {
+ bail!(self.span(), if matches.is_empty() {
+ "label does not exist in the document"
+ } else {
+ "label occurs multiple times in the document"
+ });
+ } else {
+ return Ok(Content::empty());
+ }
+ };
+
+ let mut prefix = match self.prefix(styles) {
+ Smart::Auto => prefix(target, TextNode::lang_in(styles))
+ .map(TextNode::packed)
+ .unwrap_or_default(),
+ Smart::Custom(None) => Content::empty(),
+ Smart::Custom(Some(func)) => {
+ let args = Args::new(func.span(), [target.clone().into()]);
+ func.call_detached(vt.world(), args)?.display()
+ }
+ };
+
+ if !prefix.is_empty() {
+ prefix += TextNode::packed('\u{a0}');
+ }
+
+ let formatted = if let Some(heading) = target.to::<HeadingNode>() {
+ if let Some(numbering) = heading.numbering(StyleChain::default()) {
+ let numbers = heading.numbers().unwrap();
+ numbered(vt, prefix, &numbering, &numbers)?
+ } else {
+ bail!(self.span(), "cannot reference unnumbered heading");
+ }
+ } else {
+ bail!(self.span(), "cannot reference {}", target.id().name);
+ };
+
+ let loc = target.expect_field::<Location>("location");
+ Ok(formatted.linked(Destination::Internal(loc)))
+ }
+}
+
+/// Generate a numbered reference like "Section 1.1".
+fn numbered(
+ vt: &Vt,
+ prefix: Content,
+ numbering: &Numbering,
+ numbers: &[NonZeroUsize],
+) -> SourceResult<Content> {
+ Ok(prefix
+ + match numbering {
+ Numbering::Pattern(pattern) => {
+ TextNode::packed(pattern.apply(&numbers, true))
+ }
+ Numbering::Func(_) => numbering.apply(vt.world(), &numbers)?.display(),
+ })
+}
+
+/// The default prefix.
+fn prefix(node: &Content, lang: Lang) -> Option<&str> {
+ if node.is::<HeadingNode>() {
+ match lang {
+ Lang::ENGLISH => Some("Section"),
+ Lang::GERMAN => Some("Abschnitt"),
+ _ => None,
+ }
+ } else {
+ None
}
}
diff --git a/library/src/prelude.rs b/library/src/prelude.rs
index 49afc9ca..a9b19f58 100644
--- a/library/src/prelude.rs
+++ b/library/src/prelude.rs
@@ -22,8 +22,8 @@ pub use typst::eval::{
pub use typst::geom::*;
#[doc(no_inline)]
pub use typst::model::{
- node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Prepare,
- Resolve, Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec,
+ node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve,
+ Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize,
Unlabellable, Vt,
};
#[doc(no_inline)]
diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs
index 99fb89d2..3f03ba8e 100644
--- a/library/src/text/raw.rs
+++ b/library/src/text/raw.rs
@@ -35,7 +35,7 @@ use crate::prelude::*;
///
/// Display: Raw Text / Code
/// Category: text
-#[node(Prepare, Show, Finalize)]
+#[node(Synthesize, Show, Finalize)]
pub struct RawNode {
/// The raw text.
///
@@ -120,11 +120,9 @@ impl RawNode {
}
}
-impl Prepare for RawNode {
- fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut node = self.clone().pack();
- node.push_field("lang", self.lang(styles).clone());
- Ok(node)
+impl Synthesize for RawNode {
+ fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content {
+ self.clone().with_lang(self.lang(styles)).pack()
}
}
diff --git a/macros/src/node.rs b/macros/src/node.rs
index f89ee8df..678a154d 100644
--- a/macros/src/node.rs
+++ b/macros/src/node.rs
@@ -25,6 +25,7 @@ struct Field {
positional: bool,
required: bool,
variadic: bool,
+ synthesized: bool,
fold: bool,
resolve: bool,
parse: Option<FieldParser>,
@@ -88,6 +89,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
positional,
required,
variadic,
+ synthesized: has_attr(&mut attrs, "synthesized"),
fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"),
parse: parse_attr(&mut attrs, "parse")?.flatten(),
@@ -154,7 +156,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
fn create(node: &Node) -> TokenStream {
let Node { vis, ident, docs, .. } = node;
let all = node.fields.iter().filter(|field| !field.external);
- let settable = all.clone().filter(|field| field.settable());
+ let settable = all.clone().filter(|field| !field.synthesized && field.settable());
// Inherent methods and functions.
let new = create_new_func(node);
@@ -176,7 +178,7 @@ fn create(node: &Node) -> TokenStream {
#[doc = #docs]
#[derive(Debug, Clone, Hash)]
#[repr(transparent)]
- #vis struct #ident(::typst::model::Content);
+ #vis struct #ident(pub ::typst::model::Content);
impl #ident {
#new
@@ -205,7 +207,10 @@ fn create(node: &Node) -> TokenStream {
/// Create the `new` function for the node.
fn create_new_func(node: &Node) -> TokenStream {
- let relevant = node.fields.iter().filter(|field| !field.external && field.inherent());
+ let relevant = node
+ .fields
+ .iter()
+ .filter(|field| !field.external && !field.synthesized && field.inherent());
let params = relevant.clone().map(|Field { ident, ty, .. }| {
quote! { #ident: #ty }
});
@@ -224,11 +229,11 @@ fn create_new_func(node: &Node) -> TokenStream {
/// Create an accessor methods for a field.
fn create_field_method(field: &Field) -> TokenStream {
let Field { vis, docs, ident, name, output, .. } = field;
- if field.inherent() {
+ if field.inherent() || field.synthesized {
quote! {
#[doc = #docs]
#vis fn #ident(&self) -> #output {
- self.0.field(#name).unwrap().clone().cast().unwrap()
+ self.0.expect_field(#name)
}
}
} else {
@@ -311,7 +316,7 @@ fn create_node_impl(node: &Node) -> TokenStream {
let infos = node
.fields
.iter()
- .filter(|field| !field.internal)
+ .filter(|field| !field.internal && !field.synthesized)
.map(create_param_info);
quote! {
impl ::typst::model::Node for #ident {
@@ -395,7 +400,11 @@ fn create_construct_impl(node: &Node) -> TokenStream {
let handlers = node
.fields
.iter()
- .filter(|field| !field.external && (!field.internal || field.parse.is_some()))
+ .filter(|field| {
+ !field.external
+ && !field.synthesized
+ && (!field.internal || field.parse.is_some())
+ })
.map(|field| {
let with_ident = &field.with_ident;
let (prefix, value) = create_field_parser(field);
@@ -436,6 +445,7 @@ fn create_set_impl(node: &Node) -> TokenStream {
.iter()
.filter(|field| {
!field.external
+ && !field.synthesized
&& field.settable()
&& (!field.internal || field.parse.is_some())
})
diff --git a/src/eval/library.rs b/src/eval/library.rs
index c37c16fd..14f02d98 100644
--- a/src/eval/library.rs
+++ b/src/eval/library.rs
@@ -9,7 +9,7 @@ use super::Module;
use crate::diag::SourceResult;
use crate::doc::Document;
use crate::geom::{Abs, Dir};
-use crate::model::{Content, NodeId, StyleChain, StyleMap, Vt};
+use crate::model::{Content, Label, NodeId, StyleChain, StyleMap, Vt};
use crate::util::hash128;
/// Definition of Typst's standard library.
@@ -60,7 +60,7 @@ pub struct LangItems {
/// A hyperlink: `https://typst.org`.
pub link: fn(url: EcoString) -> Content,
/// A reference: `@target`.
- pub ref_: fn(target: EcoString) -> Content,
+ pub ref_: fn(target: Label) -> Content,
/// A section heading: `= Introduction`.
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
/// An item in a bullet list: `- ...`.
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 145f961a..fe56c060 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -561,7 +561,7 @@ impl Eval for ast::Ref {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.ref_)(self.get().into()))
+ Ok((vm.items.ref_)(Label(self.get().into())))
}
}
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index a0d5e9a4..a7b001ae 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -974,7 +974,6 @@ impl<'a> CompletionContext<'a> {
let detail = docs.map(Into::into).or_else(|| match value {
Value::Symbol(_) => None,
- Value::Content(_) => None,
Value::Func(func) => {
func.info().map(|info| plain_docs_sentence(info.docs).into())
}
diff --git a/src/model/content.rs b/src/model/content.rs
index 17fa786b..be737331 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -10,7 +10,7 @@ use once_cell::sync::Lazy;
use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult};
-use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm};
+use crate::eval::{cast_from_value, Args, Cast, FuncInfo, Str, Value, Vm};
use crate::syntax::Span;
use crate::util::pretty_array_like;
use crate::World;
@@ -27,7 +27,7 @@ pub struct Content {
/// Modifiers that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)]
enum Modifier {
- Prepared,
+ Synthesized,
Guard(Guard),
}
@@ -59,6 +59,12 @@ impl Content {
self.id
}
+ /// Whether the content is empty.
+ pub fn is_empty(&self) -> bool {
+ self.to::<SequenceNode>()
+ .map_or(false, |seq| seq.children().is_empty())
+ }
+
/// Whether the contained node is of type `T`.
pub fn is<T>(&self) -> bool
where
@@ -112,6 +118,21 @@ impl Content {
.map(|(_, value)| value)
}
+ /// Access a field on the content as a specified type.
+ #[track_caller]
+ pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> {
+ match self.field(name) {
+ Some(value) => Some(value.clone().cast().unwrap()),
+ None => None,
+ }
+ }
+
+ /// Expect a field on the content to exist as a specified type.
+ #[track_caller]
+ pub fn expect_field<T: Cast>(&self, name: &str) -> T {
+ self.cast_field(name).unwrap()
+ }
+
/// List all fields on the content.
pub fn fields(&self) -> &[(EcoString, Value)] {
&self.fields
@@ -209,14 +230,14 @@ impl Content {
}
/// Mark this content as prepared.
- pub fn prepared(mut self) -> Self {
- self.modifiers.push(Modifier::Prepared);
+ pub fn synthesized(mut self) -> Self {
+ self.modifiers.push(Modifier::Synthesized);
self
}
/// Whether this node was prepared.
- pub fn is_prepared(&self) -> bool {
- self.modifiers.contains(&Modifier::Prepared)
+ pub fn is_synthesized(&self) -> bool {
+ self.modifiers.contains(&Modifier::Synthesized)
}
/// Whether no show rule was executed for this node so far.
diff --git a/src/model/realize.rs b/src/model/realize.rs
index c4c67a4f..4685a605 100644
--- a/src/model/realize.rs
+++ b/src/model/realize.rs
@@ -3,7 +3,7 @@ use crate::diag::SourceResult;
/// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
- if target.can::<dyn Prepare>() && !target.is_prepared() {
+ if target.can::<dyn Synthesize>() && !target.is_synthesized() {
return true;
}
@@ -34,6 +34,18 @@ pub fn realize(
// Find out how many recipes there are.
let mut n = styles.recipes().count();
+ // Synthesize if not already happened for this node.
+ if target.can::<dyn Synthesize>() && !target.is_synthesized() {
+ return Ok(Some(
+ target
+ .clone()
+ .synthesized()
+ .with::<dyn Synthesize>()
+ .unwrap()
+ .synthesize(vt, styles),
+ ));
+ }
+
// Find an applicable recipe.
let mut realized = None;
for recipe in styles.recipes() {
@@ -132,10 +144,10 @@ fn try_apply(
}
}
-/// Preparations before execution of any show rule.
-pub trait Prepare {
+/// Synthesize fields on a node. This happens before execution of any show rule.
+pub trait Synthesize {
/// Prepare the node for show rule application.
- fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>;
+ fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content;
}
/// The base recipe for a node.
diff --git a/src/model/typeset.rs b/src/model/typeset.rs
index 6361e6ce..377c7c76 100644
--- a/src/model/typeset.rs
+++ b/src/model/typeset.rs
@@ -77,6 +77,11 @@ impl<'a> Vt<'a> {
self.provider.identify(hash128(key))
}
+ /// Whether things are locatable already.
+ pub fn locatable(&self) -> bool {
+ self.introspector.init()
+ }
+
/// Locate all metadata matches for the given selector.
pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
self.introspector.locate(selector)
@@ -115,6 +120,7 @@ impl StabilityProvider {
/// Provides access to information about the document.
#[doc(hidden)]
pub struct Introspector {
+ init: bool,
nodes: Vec<(StableId, Content)>,
queries: RefCell<Vec<(Selector, u128)>>,
}
@@ -122,7 +128,11 @@ pub struct Introspector {
impl Introspector {
/// Create a new introspector.
fn new() -> Self {
- Self { nodes: vec![], queries: RefCell::new(vec![]) }
+ Self {
+ init: false,
+ nodes: vec![],
+ queries: RefCell::new(vec![]),
+ }
}
/// Update the information given new frames and return whether we can stop
@@ -135,14 +145,20 @@ impl Introspector {
self.extract(frame, page, Transform::identity());
}
+ let was_init = std::mem::replace(&mut self.init, true);
let queries = std::mem::take(&mut self.queries).into_inner();
- for (selector, hash) in queries {
- let nodes = self.locate_impl(&selector);
- if hash128(&nodes) != hash {
+
+ for (selector, hash) in &queries {
+ let nodes = self.locate_impl(selector);
+ if hash128(&nodes) != *hash {
return false;
}
}
+ if !was_init && !queries.is_empty() {
+ return false;
+ }
+
true
}
@@ -161,7 +177,7 @@ impl Introspector {
let pos = pos.transform(ts);
let mut node = content.clone();
let loc = Location { page, pos };
- node.push_field("loc", loc);
+ node.push_field("location", loc);
self.nodes.push((id, node));
}
}
@@ -173,6 +189,11 @@ impl Introspector {
#[comemo::track]
impl Introspector {
+ /// Whether this introspector is not yet initialized.
+ fn init(&self) -> bool {
+ self.init
+ }
+
/// Locate all metadata matches for the given selector.
fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
let nodes = self.locate_impl(&selector);
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index b4321dbe..201d78fa 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -26,7 +26,7 @@ fn markup(
p: &mut Parser,
mut at_start: bool,
min_indent: usize,
- mut stop: impl FnMut(SyntaxKind) -> bool,
+ mut stop: impl FnMut(&Parser) -> bool,
) {
let m = p.marker();
let mut nesting: usize = 0;
@@ -34,7 +34,7 @@ fn markup(
match p.current() {
SyntaxKind::LeftBracket => nesting += 1,
SyntaxKind::RightBracket if nesting > 0 => nesting -= 1,
- _ if stop(p.current) => break,
+ _ if stop(p) => break,
_ => {}
}
@@ -133,10 +133,10 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
fn strong(p: &mut Parser) {
let m = p.marker();
p.assert(SyntaxKind::Star);
- markup(p, false, 0, |kind| {
- kind == SyntaxKind::Star
- || kind == SyntaxKind::Parbreak
- || kind == SyntaxKind::RightBracket
+ markup(p, false, 0, |p| {
+ p.at(SyntaxKind::Star)
+ || p.at(SyntaxKind::Parbreak)
+ || p.at(SyntaxKind::RightBracket)
});
p.expect(SyntaxKind::Star);
p.wrap(m, SyntaxKind::Strong);
@@ -145,10 +145,10 @@ fn strong(p: &mut Parser) {
fn emph(p: &mut Parser) {
let m = p.marker();
p.assert(SyntaxKind::Underscore);
- markup(p, false, 0, |kind| {
- kind == SyntaxKind::Underscore
- || kind == SyntaxKind::Parbreak
- || kind == SyntaxKind::RightBracket
+ markup(p, false, 0, |p| {
+ p.at(SyntaxKind::Underscore)
+ || p.at(SyntaxKind::Parbreak)
+ || p.at(SyntaxKind::RightBracket)
});
p.expect(SyntaxKind::Underscore);
p.wrap(m, SyntaxKind::Emph);
@@ -158,8 +158,10 @@ fn heading(p: &mut Parser) {
let m = p.marker();
p.assert(SyntaxKind::HeadingMarker);
whitespace_line(p);
- markup(p, false, usize::MAX, |kind| {
- kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket
+ markup(p, false, usize::MAX, |p| {
+ p.at(SyntaxKind::Label)
+ || p.at(SyntaxKind::RightBracket)
+ || (p.at(SyntaxKind::Space) && p.lexer.clone().next() == SyntaxKind::Label)
});
p.wrap(m, SyntaxKind::Heading);
}
@@ -169,7 +171,7 @@ fn list_item(p: &mut Parser) {
p.assert(SyntaxKind::ListMarker);
let min_indent = p.column(p.prev_end());
whitespace_line(p);
- markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
+ markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket));
p.wrap(m, SyntaxKind::ListItem);
}
@@ -178,7 +180,7 @@ fn enum_item(p: &mut Parser) {
p.assert(SyntaxKind::EnumMarker);
let min_indent = p.column(p.prev_end());
whitespace_line(p);
- markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
+ markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket));
p.wrap(m, SyntaxKind::EnumItem);
}
@@ -187,12 +189,12 @@ fn term_item(p: &mut Parser) {
p.assert(SyntaxKind::TermMarker);
let min_indent = p.column(p.prev_end());
whitespace_line(p);
- markup(p, false, usize::MAX, |kind| {
- kind == SyntaxKind::Colon || kind == SyntaxKind::RightBracket
+ markup(p, false, usize::MAX, |p| {
+ p.at(SyntaxKind::Colon) || p.at(SyntaxKind::RightBracket)
});
p.expect(SyntaxKind::Colon);
whitespace_line(p);
- markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
+ markup(p, false, min_indent, |p| p.at(SyntaxKind::RightBracket));
p.wrap(m, SyntaxKind::TermItem);
}
@@ -679,7 +681,7 @@ fn content_block(p: &mut Parser) {
let m = p.marker();
p.enter(LexMode::Markup);
p.assert(SyntaxKind::LeftBracket);
- markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket);
+ markup(p, true, 0, |p| p.at(SyntaxKind::RightBracket));
p.expect(SyntaxKind::RightBracket);
p.exit();
p.wrap(m, SyntaxKind::ContentBlock);
diff --git a/tests/ref/meta/ref.png b/tests/ref/meta/ref.png
new file mode 100644
index 00000000..13e4db33
--- /dev/null
+++ b/tests/ref/meta/ref.png
Binary files differ
diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ
index 36a42745..a4482e6d 100644
--- a/tests/typ/compiler/set.typ
+++ b/tests/typ/compiler/set.typ
@@ -51,8 +51,8 @@ Hello *#x*
---
// Test conditional set.
#show ref: it => {
- set text(red) if it.target == "unknown"
- it
+ set text(red) if it.label == <unknown>
+ "@" + str(it.label)
}
@hello from the @unknown
diff --git a/tests/typ/meta/ref.typ b/tests/typ/meta/ref.typ
new file mode 100644
index 00000000..85750712
--- /dev/null
+++ b/tests/typ/meta/ref.typ
@@ -0,0 +1,21 @@
+// Test references.
+
+---
+#set heading(numbering: "1.")
+
+= Introduction <intro>
+See @setup.
+
+== Setup <setup>
+As seen in @intro, we proceed.
+
+---
+// Error: 1-5 label does not exist in the document
+@foo
+
+---
+= First <foo>
+= Second <foo>
+
+// Error: 1-5 label occurs multiple times in the document
+@foo