mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Section references
This commit is contained in:
parent
8e5f446544
commit
529d3e10c6
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())
|
||||||
})
|
})
|
||||||
|
@ -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: `- ...`.
|
||||||
|
@ -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())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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
BIN
tests/ref/meta/ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -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
21
tests/typ/meta/ref.typ
Normal 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
|
Loading…
x
Reference in New Issue
Block a user