Section references

This commit is contained in:
Laurenz 2023-03-11 17:42:40 +01:00
parent 8e5f446544
commit 529d3e10c6
18 changed files with 362 additions and 135 deletions

View File

@ -232,20 +232,6 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.scratch.content.alloc(FormulaNode::new(content.clone()).pack()); 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>() { if let Some(styled) = content.to::<StyledNode>() {
return self.styled(styled, styles); return self.styled(styled, styles);
} }

View File

@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize};
/// ///
/// Display: Heading /// Display: Heading
/// Category: meta /// Category: meta
#[node(Prepare, Show, Finalize)] #[node(Synthesize, Show, Finalize)]
pub struct HeadingNode { pub struct HeadingNode {
/// The logical nesting depth of the heading, starting from one. /// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::new(1).unwrap())] #[default(NonZeroUsize::new(1).unwrap())]
@ -76,44 +76,61 @@ pub struct HeadingNode {
/// The heading's title. /// The heading's title.
#[required] #[required]
pub body: Content, 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 { impl Synthesize for HeadingNode {
fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content {
let my_id = vt.identify(self); let my_id = vt.identify(self);
let numbered = self.numbering(styles).is_some();
let mut counter = HeadingCounter::new(); let mut counter = HeadingCounter::new();
for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) { if numbered {
if node_id == my_id { // Advance passed existing headings.
break; for (_, node) in vt
} .locate(Selector::node::<HeadingNode>())
.into_iter()
let numbers = node.field("numbers").unwrap(); .take_while(|&(id, _)| id != my_id)
if *numbers != Value::None { {
let heading = node.to::<Self>().unwrap(); let heading = node.to::<HeadingNode>().unwrap();
if heading.numbering(StyleChain::default()).is_some() {
counter.advance(heading); counter.advance(heading);
} }
} }
let mut numbers = Value::None; // Advance passed self.
if let Some(numbering) = self.numbering(styles) { counter.advance(self);
numbers = numbering.apply(vt.world(), counter.advance(self))?;
} }
let mut node = self.clone().pack(); let node = self
node.push_field("outlined", Value::Bool(self.outlined(styles))); .clone()
node.push_field("numbers", numbers); .with_outlined(self.outlined(styles))
.with_numbering(self.numbering(styles))
.with_numbers(numbered.then(|| counter.take()))
.pack();
let meta = Meta::Node(my_id, node.clone()); 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 { 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 mut realized = self.body();
let numbers = self.0.field("numbers").unwrap(); if let Some(numbering) = self.numbering(styles) {
if *numbers != Value::None { let numbers = self.numbers().unwrap();
realized = numbers.clone().display() realized = numbering.apply(vt.world(), &numbers)?.display()
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack() + HNode::new(Em::new(0.3).into()).with_weak(true).pack()
+ realized; + realized;
} }
@ -168,4 +185,14 @@ impl HeadingCounter {
&self.0 &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(),
} }

View File

@ -82,7 +82,7 @@ impl Numbering {
numbers: &[NonZeroUsize], numbers: &[NonZeroUsize],
) -> SourceResult<Value> { ) -> SourceResult<Value> {
Ok(match self { 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) => { Self::Func(func) => {
let args = Args::new( let args = Args::new(
func.span(), func.span(),
@ -124,12 +124,16 @@ pub struct NumberingPattern {
impl NumberingPattern { impl NumberingPattern {
/// Apply the pattern to the given number. /// 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 fmt = EcoString::new();
let mut numbers = numbers.into_iter(); let mut numbers = numbers.into_iter();
for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) { 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(prefix);
}
fmt.push_str(&kind.apply(n, *case)); fmt.push_str(&kind.apply(n, *case));
} }
@ -144,7 +148,10 @@ impl NumberingPattern {
fmt.push_str(&kind.apply(n, *case)); fmt.push_str(&kind.apply(n, *case));
} }
if !trimmed {
fmt.push_str(&self.suffix); fmt.push_str(&self.suffix);
}
fmt fmt
} }

View File

@ -22,7 +22,7 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
/// ///
/// Display: Outline /// Display: Outline
/// Category: meta /// Category: meta
#[node(Prepare, Show)] #[node(Synthesize, Show)]
pub struct OutlineNode { pub struct OutlineNode {
/// The title of the outline. /// The title of the outline.
/// ///
@ -67,21 +67,22 @@ pub struct OutlineNode {
/// ``` /// ```
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))] #[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))]
pub fill: Option<Content>, pub fill: Option<Content>,
/// All outlined headings in the document.
#[synthesized]
pub headings: Vec<HeadingNode>,
} }
impl Prepare for OutlineNode { impl Synthesize for OutlineNode {
fn prepare(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content {
let headings = vt let headings = vt
.locate(Selector::node::<HeadingNode>()) .locate(Selector::node::<HeadingNode>())
.into_iter() .into_iter()
.map(|(_, node)| node) .map(|(_, node)| node.to::<HeadingNode>().unwrap().clone())
.filter(|node| *node.field("outlined").unwrap() == Value::Bool(true)) .filter(|node| node.outlined(StyleChain::default()))
.map(|node| Value::Content(node.clone()))
.collect(); .collect();
let mut node = self.clone().pack(); self.clone().with_headings(headings).pack()
node.push_field("headings", Value::Array(Array::from_vec(headings)));
Ok(node)
} }
} }
@ -108,13 +109,12 @@ impl Show for OutlineNode {
let indent = self.indent(styles); let indent = self.indent(styles);
let depth = self.depth(styles); let depth = self.depth(styles);
let mut ancestors: Vec<&Content> = vec![]; let mut ancestors: Vec<&HeadingNode> = vec![];
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) { for heading in self.headings().iter() {
if *node.field("outlined").unwrap() != Value::Bool(true) { if !heading.outlined(StyleChain::default()) {
continue; continue;
} }
let heading = node.to::<HeadingNode>().unwrap();
if let Some(depth) = depth { if let Some(depth) = depth {
if depth < heading.level(StyleChain::default()) { if depth < heading.level(StyleChain::default()) {
continue; continue;
@ -122,37 +122,40 @@ impl Show for OutlineNode {
} }
while ancestors.last().map_or(false, |last| { while ancestors.last().map_or(false, |last| {
last.to::<HeadingNode>().unwrap().level(StyleChain::default()) last.level(StyleChain::default()) >= heading.level(StyleChain::default())
>= heading.level(StyleChain::default())
}) { }) {
ancestors.pop(); ancestors.pop();
} }
// Adjust the link destination a bit to the topleft so that the // Adjust the link destination a bit to the topleft so that the
// heading is fully visible. // 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)); loc.pos -= Point::splat(Abs::pt(10.0));
// Add hidden ancestors numberings to realize the indent. // Add hidden ancestors numberings to realize the indent.
if indent { if indent {
let hidden: Vec<_> = ancestors let mut hidden = Content::empty();
.iter() for ancestor in &ancestors {
.map(|node| node.field("numbers").unwrap()) if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
.filter(|&numbers| *numbers != Value::None) let numbers = ancestor.numbers().unwrap();
.map(|numbers| numbers.clone().display() + SpaceNode::new().pack()) hidden += numbering.apply(vt.world(), &numbers)?.display()
.collect(); + SpaceNode::new().pack();
};
}
if !hidden.is_empty() { if !ancestors.is_empty() {
seq.push(HideNode::new(Content::sequence(hidden)).pack()); seq.push(HideNode::new(hidden).pack());
seq.push(SpaceNode::new().pack()); seq.push(SpaceNode::new().pack());
} }
} }
// Format the numbering. // Format the numbering.
let mut start = heading.body(); let mut start = heading.body();
let numbers = node.field("numbers").unwrap(); if let Some(numbering) = heading.numbering(StyleChain::default()) {
if *numbers != Value::None { let numbers = heading.numbers().unwrap();
start = numbers.clone().display() + SpaceNode::new().pack() + start; start = numbering.apply(vt.world(), &numbers)?.display()
+ SpaceNode::new().pack()
+ start;
}; };
// Add the numbering and section name. // Add the numbering and section name.
@ -176,8 +179,7 @@ impl Show for OutlineNode {
let end = TextNode::packed(eco_format!("{}", loc.page)); let end = TextNode::packed(eco_format!("{}", loc.page));
seq.push(end.linked(Destination::Internal(loc))); seq.push(end.linked(Destination::Internal(loc)));
seq.push(LinebreakNode::new().pack()); seq.push(LinebreakNode::new().pack());
ancestors.push(heading);
ancestors.push(node);
} }
seq.push(ParbreakNode::new().pack()); seq.push(ParbreakNode::new().pack());

View File

@ -1,31 +1,152 @@
use super::{HeadingNode, Numbering};
use crate::prelude::*; use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// A reference to a label. /// A reference to a label.
/// ///
/// *Note: This function is currently unimplemented.*
///
/// The reference function produces a textual reference to a label. For example, /// 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 /// 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 /// 1" for a reference to the first heading. The references are also links to
/// links to the respective labels. /// 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 /// ## Syntax
/// This function also has dedicated syntax: A reference to a label can be /// 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. `[= /// created by typing an `@` followed by the name of the label (e.g.
/// Introduction <intro>]` can be referenced by typing `[@intro]`). /// `[= Introduction <intro>]` can be referenced by typing `[@intro]`).
/// ///
/// Display: Reference /// Display: Reference
/// Category: meta /// Category: meta
#[node(Show)] #[node(Synthesize, Show)]
pub struct RefNode { pub struct RefNode {
/// The label that should be referenced. /// The label that should be referenced.
#[required] #[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 { impl Show for RefNode {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
Ok(TextNode::packed(eco_format!("@{}", self.target()))) 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
} }
} }

View File

@ -22,8 +22,8 @@ pub use typst::eval::{
pub use typst::geom::*; pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Prepare, node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve,
Resolve, Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize,
Unlabellable, Vt, Unlabellable, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]

View File

@ -35,7 +35,7 @@ use crate::prelude::*;
/// ///
/// Display: Raw Text / Code /// Display: Raw Text / Code
/// Category: text /// Category: text
#[node(Prepare, Show, Finalize)] #[node(Synthesize, Show, Finalize)]
pub struct RawNode { pub struct RawNode {
/// The raw text. /// The raw text.
/// ///
@ -120,11 +120,9 @@ impl RawNode {
} }
} }
impl Prepare for RawNode { impl Synthesize for RawNode {
fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content {
let mut node = self.clone().pack(); self.clone().with_lang(self.lang(styles)).pack()
node.push_field("lang", self.lang(styles).clone());
Ok(node)
} }
} }

View File

@ -25,6 +25,7 @@ struct Field {
positional: bool, positional: bool,
required: bool, required: bool,
variadic: bool, variadic: bool,
synthesized: bool,
fold: bool, fold: bool,
resolve: bool, resolve: bool,
parse: Option<FieldParser>, parse: Option<FieldParser>,
@ -88,6 +89,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
positional, positional,
required, required,
variadic, variadic,
synthesized: has_attr(&mut attrs, "synthesized"),
fold: has_attr(&mut attrs, "fold"), fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"), resolve: has_attr(&mut attrs, "resolve"),
parse: parse_attr(&mut attrs, "parse")?.flatten(), 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 { fn create(node: &Node) -> TokenStream {
let Node { vis, ident, docs, .. } = node; let Node { vis, ident, docs, .. } = node;
let all = node.fields.iter().filter(|field| !field.external); 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. // Inherent methods and functions.
let new = create_new_func(node); let new = create_new_func(node);
@ -176,7 +178,7 @@ fn create(node: &Node) -> TokenStream {
#[doc = #docs] #[doc = #docs]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
#[repr(transparent)] #[repr(transparent)]
#vis struct #ident(::typst::model::Content); #vis struct #ident(pub ::typst::model::Content);
impl #ident { impl #ident {
#new #new
@ -205,7 +207,10 @@ fn create(node: &Node) -> TokenStream {
/// Create the `new` function for the node. /// Create the `new` function for the node.
fn create_new_func(node: &Node) -> TokenStream { 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, .. }| { let params = relevant.clone().map(|Field { ident, ty, .. }| {
quote! { #ident: #ty } quote! { #ident: #ty }
}); });
@ -224,11 +229,11 @@ fn create_new_func(node: &Node) -> TokenStream {
/// Create an accessor methods for a field. /// Create an accessor methods for a field.
fn create_field_method(field: &Field) -> TokenStream { fn create_field_method(field: &Field) -> TokenStream {
let Field { vis, docs, ident, name, output, .. } = field; let Field { vis, docs, ident, name, output, .. } = field;
if field.inherent() { if field.inherent() || field.synthesized {
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#vis fn #ident(&self) -> #output { #vis fn #ident(&self) -> #output {
self.0.field(#name).unwrap().clone().cast().unwrap() self.0.expect_field(#name)
} }
} }
} else { } else {
@ -311,7 +316,7 @@ fn create_node_impl(node: &Node) -> TokenStream {
let infos = node let infos = node
.fields .fields
.iter() .iter()
.filter(|field| !field.internal) .filter(|field| !field.internal && !field.synthesized)
.map(create_param_info); .map(create_param_info);
quote! { quote! {
impl ::typst::model::Node for #ident { impl ::typst::model::Node for #ident {
@ -395,7 +400,11 @@ fn create_construct_impl(node: &Node) -> TokenStream {
let handlers = node let handlers = node
.fields .fields
.iter() .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| { .map(|field| {
let with_ident = &field.with_ident; let with_ident = &field.with_ident;
let (prefix, value) = create_field_parser(field); let (prefix, value) = create_field_parser(field);
@ -436,6 +445,7 @@ fn create_set_impl(node: &Node) -> TokenStream {
.iter() .iter()
.filter(|field| { .filter(|field| {
!field.external !field.external
&& !field.synthesized
&& field.settable() && field.settable()
&& (!field.internal || field.parse.is_some()) && (!field.internal || field.parse.is_some())
}) })

View File

@ -9,7 +9,7 @@ use super::Module;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::Document; use crate::doc::Document;
use crate::geom::{Abs, Dir}; 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; use crate::util::hash128;
/// Definition of Typst's standard library. /// Definition of Typst's standard library.
@ -60,7 +60,7 @@ pub struct LangItems {
/// A hyperlink: `https://typst.org`. /// A hyperlink: `https://typst.org`.
pub link: fn(url: EcoString) -> Content, pub link: fn(url: EcoString) -> Content,
/// A reference: `@target`. /// A reference: `@target`.
pub ref_: fn(target: EcoString) -> Content, pub ref_: fn(target: Label) -> Content,
/// A section heading: `= Introduction`. /// A section heading: `= Introduction`.
pub heading: fn(level: NonZeroUsize, body: Content) -> Content, pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
/// An item in a bullet list: `- ...`. /// An item in a bullet list: `- ...`.

View File

@ -561,7 +561,7 @@ impl Eval for ast::Ref {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.ref_)(self.get().into())) Ok((vm.items.ref_)(Label(self.get().into())))
} }
} }

View File

@ -974,7 +974,6 @@ impl<'a> CompletionContext<'a> {
let detail = docs.map(Into::into).or_else(|| match value { let detail = docs.map(Into::into).or_else(|| match value {
Value::Symbol(_) => None, Value::Symbol(_) => None,
Value::Content(_) => None,
Value::Func(func) => { Value::Func(func) => {
func.info().map(|info| plain_docs_sentence(info.docs).into()) func.info().map(|info| plain_docs_sentence(info.docs).into())
} }

View File

@ -10,7 +10,7 @@ use once_cell::sync::Lazy;
use super::{node, Guard, Recipe, Style, StyleMap}; use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult}; 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::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
use crate::World; use crate::World;
@ -27,7 +27,7 @@ pub struct Content {
/// Modifiers that can be attached to content. /// Modifiers that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
enum Modifier { enum Modifier {
Prepared, Synthesized,
Guard(Guard), Guard(Guard),
} }
@ -59,6 +59,12 @@ impl Content {
self.id 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`. /// Whether the contained node is of type `T`.
pub fn is<T>(&self) -> bool pub fn is<T>(&self) -> bool
where where
@ -112,6 +118,21 @@ impl Content {
.map(|(_, value)| value) .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. /// List all fields on the content.
pub fn fields(&self) -> &[(EcoString, Value)] { pub fn fields(&self) -> &[(EcoString, Value)] {
&self.fields &self.fields
@ -209,14 +230,14 @@ impl Content {
} }
/// Mark this content as prepared. /// Mark this content as prepared.
pub fn prepared(mut self) -> Self { pub fn synthesized(mut self) -> Self {
self.modifiers.push(Modifier::Prepared); self.modifiers.push(Modifier::Synthesized);
self self
} }
/// Whether this node was prepared. /// Whether this node was prepared.
pub fn is_prepared(&self) -> bool { pub fn is_synthesized(&self) -> bool {
self.modifiers.contains(&Modifier::Prepared) self.modifiers.contains(&Modifier::Synthesized)
} }
/// Whether no show rule was executed for this node so far. /// Whether no show rule was executed for this node so far.

View File

@ -3,7 +3,7 @@ use crate::diag::SourceResult;
/// Whether the target is affected by show rules in the given style chain. /// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool { 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; return true;
} }
@ -34,6 +34,18 @@ pub fn realize(
// Find out how many recipes there are. // Find out how many recipes there are.
let mut n = styles.recipes().count(); 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. // Find an applicable recipe.
let mut realized = None; let mut realized = None;
for recipe in styles.recipes() { for recipe in styles.recipes() {
@ -132,10 +144,10 @@ fn try_apply(
} }
} }
/// Preparations before execution of any show rule. /// Synthesize fields on a node. This happens before execution of any show rule.
pub trait Prepare { pub trait Synthesize {
/// Prepare the node for show rule application. /// 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. /// The base recipe for a node.

View File

@ -77,6 +77,11 @@ impl<'a> Vt<'a> {
self.provider.identify(hash128(key)) 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. /// Locate all metadata matches for the given selector.
pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> { pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
self.introspector.locate(selector) self.introspector.locate(selector)
@ -115,6 +120,7 @@ impl StabilityProvider {
/// Provides access to information about the document. /// Provides access to information about the document.
#[doc(hidden)] #[doc(hidden)]
pub struct Introspector { pub struct Introspector {
init: bool,
nodes: Vec<(StableId, Content)>, nodes: Vec<(StableId, Content)>,
queries: RefCell<Vec<(Selector, u128)>>, queries: RefCell<Vec<(Selector, u128)>>,
} }
@ -122,7 +128,11 @@ pub struct Introspector {
impl Introspector { impl Introspector {
/// Create a new introspector. /// Create a new introspector.
fn new() -> Self { 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 /// Update the information given new frames and return whether we can stop
@ -135,14 +145,20 @@ impl Introspector {
self.extract(frame, page, Transform::identity()); 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(); let queries = std::mem::take(&mut self.queries).into_inner();
for (selector, hash) in queries {
let nodes = self.locate_impl(&selector); for (selector, hash) in &queries {
if hash128(&nodes) != hash { let nodes = self.locate_impl(selector);
if hash128(&nodes) != *hash {
return false; return false;
} }
} }
if !was_init && !queries.is_empty() {
return false;
}
true true
} }
@ -161,7 +177,7 @@ impl Introspector {
let pos = pos.transform(ts); let pos = pos.transform(ts);
let mut node = content.clone(); let mut node = content.clone();
let loc = Location { page, pos }; let loc = Location { page, pos };
node.push_field("loc", loc); node.push_field("location", loc);
self.nodes.push((id, node)); self.nodes.push((id, node));
} }
} }
@ -173,6 +189,11 @@ impl Introspector {
#[comemo::track] #[comemo::track]
impl Introspector { impl Introspector {
/// Whether this introspector is not yet initialized.
fn init(&self) -> bool {
self.init
}
/// Locate all metadata matches for the given selector. /// Locate all metadata matches for the given selector.
fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> { fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
let nodes = self.locate_impl(&selector); let nodes = self.locate_impl(&selector);

View File

@ -26,7 +26,7 @@ fn markup(
p: &mut Parser, p: &mut Parser,
mut at_start: bool, mut at_start: bool,
min_indent: usize, min_indent: usize,
mut stop: impl FnMut(SyntaxKind) -> bool, mut stop: impl FnMut(&Parser) -> bool,
) { ) {
let m = p.marker(); let m = p.marker();
let mut nesting: usize = 0; let mut nesting: usize = 0;
@ -34,7 +34,7 @@ fn markup(
match p.current() { match p.current() {
SyntaxKind::LeftBracket => nesting += 1, SyntaxKind::LeftBracket => nesting += 1,
SyntaxKind::RightBracket if nesting > 0 => 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) { fn strong(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.assert(SyntaxKind::Star); p.assert(SyntaxKind::Star);
markup(p, false, 0, |kind| { markup(p, false, 0, |p| {
kind == SyntaxKind::Star p.at(SyntaxKind::Star)
|| kind == SyntaxKind::Parbreak || p.at(SyntaxKind::Parbreak)
|| kind == SyntaxKind::RightBracket || p.at(SyntaxKind::RightBracket)
}); });
p.expect(SyntaxKind::Star); p.expect(SyntaxKind::Star);
p.wrap(m, SyntaxKind::Strong); p.wrap(m, SyntaxKind::Strong);
@ -145,10 +145,10 @@ fn strong(p: &mut Parser) {
fn emph(p: &mut Parser) { fn emph(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.assert(SyntaxKind::Underscore); p.assert(SyntaxKind::Underscore);
markup(p, false, 0, |kind| { markup(p, false, 0, |p| {
kind == SyntaxKind::Underscore p.at(SyntaxKind::Underscore)
|| kind == SyntaxKind::Parbreak || p.at(SyntaxKind::Parbreak)
|| kind == SyntaxKind::RightBracket || p.at(SyntaxKind::RightBracket)
}); });
p.expect(SyntaxKind::Underscore); p.expect(SyntaxKind::Underscore);
p.wrap(m, SyntaxKind::Emph); p.wrap(m, SyntaxKind::Emph);
@ -158,8 +158,10 @@ fn heading(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.assert(SyntaxKind::HeadingMarker); p.assert(SyntaxKind::HeadingMarker);
whitespace_line(p); whitespace_line(p);
markup(p, false, usize::MAX, |kind| { markup(p, false, usize::MAX, |p| {
kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket p.at(SyntaxKind::Label)
|| p.at(SyntaxKind::RightBracket)
|| (p.at(SyntaxKind::Space) && p.lexer.clone().next() == SyntaxKind::Label)
}); });
p.wrap(m, SyntaxKind::Heading); p.wrap(m, SyntaxKind::Heading);
} }
@ -169,7 +171,7 @@ fn list_item(p: &mut Parser) {
p.assert(SyntaxKind::ListMarker); p.assert(SyntaxKind::ListMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace_line(p); 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); p.wrap(m, SyntaxKind::ListItem);
} }
@ -178,7 +180,7 @@ fn enum_item(p: &mut Parser) {
p.assert(SyntaxKind::EnumMarker); p.assert(SyntaxKind::EnumMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace_line(p); 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); p.wrap(m, SyntaxKind::EnumItem);
} }
@ -187,12 +189,12 @@ fn term_item(p: &mut Parser) {
p.assert(SyntaxKind::TermMarker); p.assert(SyntaxKind::TermMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace_line(p); whitespace_line(p);
markup(p, false, usize::MAX, |kind| { markup(p, false, usize::MAX, |p| {
kind == SyntaxKind::Colon || kind == SyntaxKind::RightBracket p.at(SyntaxKind::Colon) || p.at(SyntaxKind::RightBracket)
}); });
p.expect(SyntaxKind::Colon); p.expect(SyntaxKind::Colon);
whitespace_line(p); 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); p.wrap(m, SyntaxKind::TermItem);
} }
@ -679,7 +681,7 @@ fn content_block(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.enter(LexMode::Markup); p.enter(LexMode::Markup);
p.assert(SyntaxKind::LeftBracket); 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.expect(SyntaxKind::RightBracket);
p.exit(); p.exit();
p.wrap(m, SyntaxKind::ContentBlock); p.wrap(m, SyntaxKind::ContentBlock);

BIN
tests/ref/meta/ref.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -51,8 +51,8 @@ Hello *#x*
--- ---
// Test conditional set. // Test conditional set.
#show ref: it => { #show ref: it => {
set text(red) if it.target == "unknown" set text(red) if it.label == <unknown>
it "@" + str(it.label)
} }
@hello from the @unknown @hello from the @unknown

21
tests/typ/meta/ref.typ Normal file
View File

@ -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