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());
}
// 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);
}

View File

@ -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;
}
let numbers = node.field("numbers").unwrap();
if *numbers != Value::None {
let heading = node.to::<Self>().unwrap();
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 mut numbers = Value::None;
if let Some(numbering) = self.numbering(styles) {
numbers = numbering.apply(vt.world(), counter.advance(self))?;
// Advance passed self.
counter.advance(self);
}
let mut node = self.clone().pack();
node.push_field("outlined", Value::Bool(self.outlined(styles)));
node.push_field("numbers", numbers);
let node = self
.clone()
.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());
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(),
}

View File

@ -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) {
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));
}
if !trimmed {
fmt.push_str(&self.suffix);
}
fmt
}

View File

@ -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();
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 !hidden.is_empty() {
seq.push(HideNode::new(Content::sequence(hidden)).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());

View File

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

View File

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

View File

@ -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()
}
}

View File

@ -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())
})

View File

@ -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: `- ...`.

View File

@ -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())))
}
}

View File

@ -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())
}

View File

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

View File

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

View File

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

View File

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

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.
#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

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