summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-14 22:33:22 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-14 22:34:43 +0100
commit2a86e4db0bb3894d1cc3b94e1a1af31a6cd87b80 (patch)
treed5c0954ef779689c40eb9b2f58d477869546ea89
parente50189cfa75d83ea1b74b1dc2cf1fc9c01f8c825 (diff)
Reference supplements
-rw-r--r--library/src/lib.rs18
-rw-r--r--library/src/meta/mod.rs8
-rw-r--r--library/src/meta/reference.rs46
-rw-r--r--macros/src/node.rs6
-rw-r--r--src/eval/library.rs6
-rw-r--r--src/eval/mod.rs4
-rw-r--r--src/ide/highlight.rs1
-rw-r--r--src/syntax/ast.rs17
-rw-r--r--src/syntax/kind.rs7
-rw-r--r--src/syntax/lexer.rs12
-rw-r--r--src/syntax/parser.rs13
11 files changed, 98 insertions, 40 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 19539ccf..5b114d9b 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -11,7 +11,7 @@ pub mod text;
pub mod visualize;
use typst::eval::{LangItems, Library, Module, Scope};
-use typst::geom::{Align, Color, Dir, GenAlign};
+use typst::geom::{Align, Color, Dir, GenAlign, Smart};
use typst::model::{Node, NodeId, StyleMap};
use self::layout::LayoutRoot;
@@ -185,13 +185,21 @@ fn items() -> LangItems {
},
raw_languages: text::RawNode::languages,
link: |url| meta::LinkNode::from_url(url).pack(),
- ref_: |target| meta::RefNode::new(target).pack(),
+ reference: |target, supplement| {
+ let mut node = meta::RefNode::new(target);
+ if let Some(supplement) = supplement {
+ node.push_supplement(Smart::Custom(Some(meta::Supplement::Content(
+ supplement,
+ ))));
+ }
+ node.pack()
+ },
heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(),
list_item: |body| layout::ListItem::new(body).pack(),
enum_item: |number, body| {
let mut node = layout::EnumItem::new(body);
if let Some(number) = number {
- node = node.with_number(Some(number));
+ node.push_number(Some(number));
}
node.pack()
},
@@ -202,10 +210,10 @@ fn items() -> LangItems {
math_attach: |base, bottom, top| {
let mut node = math::AttachNode::new(base);
if let Some(bottom) = bottom {
- node = node.with_bottom(Some(bottom));
+ node.push_bottom(Some(bottom));
}
if let Some(top) = top {
- node = node.with_top(Some(top));
+ node.push_top(Some(top));
}
node.pack()
},
diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs
index f5b9bf2f..3cde2b8e 100644
--- a/library/src/meta/mod.rs
+++ b/library/src/meta/mod.rs
@@ -15,3 +15,11 @@ pub use self::link::*;
pub use self::numbering::*;
pub use self::outline::*;
pub use self::reference::*;
+
+use typst::doc::Lang;
+
+/// The named with which an element is referenced.
+pub trait LocalName {
+ /// Get the name in the given language.
+ fn local_name(&self, lang: Lang) -> &'static str;
+}
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
index 18f0aa3f..f63c7e4c 100644
--- a/library/src/meta/reference.rs
+++ b/library/src/meta/reference.rs
@@ -1,4 +1,4 @@
-use super::{FigureNode, HeadingNode, Numbering};
+use super::{FigureNode, HeadingNode, LocalName, Numbering};
use crate::prelude::*;
use crate::text::TextNode;
@@ -41,11 +41,14 @@ pub struct RefNode {
#[required]
pub target: Label,
- /// The prefix before the referenced number.
+ /// A supplement for the reference.
+ ///
+ /// For references to headings or figures, this is added before the
+ /// referenced number. For citations, this can be used to add a page number.
///
/// ```example
/// #set heading(numbering: "1.")
- /// #set ref(prefix: it => {
+ /// #set ref(supplement: it => {
/// if it.func() == heading {
/// "Chapter"
/// } else {
@@ -57,11 +60,11 @@ pub struct RefNode {
/// 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>,
+ pub supplement: Smart<Option<Supplement>>,
}
impl Synthesize for RefNode {
@@ -90,34 +93,36 @@ impl Show for RefNode {
}
};
- let mut prefix = match self.prefix(styles) {
+ let supplement = self.supplement(styles);
+ let mut supplement = match supplement {
Smart::Auto => target
.with::<dyn LocalName>()
.map(|node| node.local_name(TextNode::lang_in(styles)))
.map(TextNode::packed)
.unwrap_or_default(),
Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(func)) => {
+ Smart::Custom(Some(Supplement::Content(content))) => content.clone(),
+ Smart::Custom(Some(Supplement::Func(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}');
+ if !supplement.is_empty() {
+ supplement += 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)?
+ numbered(vt, supplement, &numbering, &numbers)?
} else {
bail!(self.span(), "cannot reference unnumbered heading");
}
} else if let Some(figure) = target.to::<FigureNode>() {
if let Some(numbering) = figure.numbering(StyleChain::default()) {
let number = figure.number().unwrap();
- numbered(vt, prefix, &numbering, &[number])?
+ numbered(vt, supplement, &numbering, &[number])?
} else {
bail!(self.span(), "cannot reference unnumbered figure");
}
@@ -146,8 +151,21 @@ fn numbered(
})
}
-/// The named with which an element is referenced.
-pub trait LocalName {
- /// Get the name in the given language.
- fn local_name(&self, lang: Lang) -> &'static str;
+/// Additional content for a reference.
+pub enum Supplement {
+ Content(Content),
+ Func(Func),
+}
+
+cast_from_value! {
+ Supplement,
+ v: Content => Self::Content(v),
+ v: Func => Self::Func(v),
+}
+
+cast_to_value! {
+ v: Supplement => match v {
+ Supplement::Content(v) => v.into(),
+ Supplement::Func(v) => v.into(),
+ }
}
diff --git a/macros/src/node.rs b/macros/src/node.rs
index 14856074..dfbd9078 100644
--- a/macros/src/node.rs
+++ b/macros/src/node.rs
@@ -433,19 +433,19 @@ fn create_construct_impl(node: &Node) -> TokenStream {
&& (!field.internal || field.parse.is_some())
})
.map(|field| {
- let with_ident = &field.with_ident;
+ let push_ident = &field.push_ident;
let (prefix, value) = create_field_parser(field);
if field.settable() {
quote! {
#prefix
if let Some(value) = #value {
- node = node.#with_ident(value);
+ node.#push_ident(value);
}
}
} else {
quote! {
#prefix
- node = node.#with_ident(#value);
+ node.#push_ident(#value);
}
}
});
diff --git a/src/eval/library.rs b/src/eval/library.rs
index 14f02d98..d3f7547d 100644
--- a/src/eval/library.rs
+++ b/src/eval/library.rs
@@ -59,8 +59,8 @@ pub struct LangItems {
pub raw_languages: fn() -> Vec<(&'static str, Vec<&'static str>)>,
/// A hyperlink: `https://typst.org`.
pub link: fn(url: EcoString) -> Content,
- /// A reference: `@target`.
- pub ref_: fn(target: Label) -> Content,
+ /// A reference: `@target`, `@target[..]`.
+ pub reference: fn(target: Label, supplement: Option<Content>) -> Content,
/// A section heading: `= Introduction`.
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
/// An item in a bullet list: `- ...`.
@@ -106,7 +106,7 @@ impl Hash for LangItems {
self.emph.hash(state);
self.raw.hash(state);
self.link.hash(state);
- self.ref_.hash(state);
+ self.reference.hash(state);
self.heading.hash(state);
self.list_item.hash(state);
self.enum_item.hash(state);
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index ae5f668a..127c930f 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -567,7 +567,9 @@ impl Eval for ast::Ref {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.ref_)(Label(self.get().into())))
+ let label = Label(self.target().into());
+ let supplement = self.supplement().map(|block| block.eval(vm)).transpose()?;
+ Ok((vm.items.reference)(label, supplement))
}
}
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index 7827b2c9..6214328b 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -129,6 +129,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> {
SyntaxKind::Link => Some(Tag::Link),
SyntaxKind::Label => Some(Tag::Label),
SyntaxKind::Ref => Some(Tag::Ref),
+ SyntaxKind::RefMarker => None,
SyntaxKind::Heading => Some(Tag::Heading),
SyntaxKind::HeadingMarker => None,
SyntaxKind::ListItem => None,
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 2fdedbcf..8e48358d 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -101,7 +101,7 @@ pub enum Expr {
Link(Link),
/// A label: `<intro>`.
Label(Label),
- /// A reference: `@target`.
+ /// A reference: `@target`, `@target[..]`.
Ref(Ref),
/// A section heading: `= Introduction`.
Heading(Heading),
@@ -604,14 +604,23 @@ impl Label {
}
node! {
- /// A reference: `@target`.
+ /// A reference: `@target`, `@target[..]`.
Ref
}
impl Ref {
/// Get the target.
- pub fn get(&self) -> &str {
- self.0.text().trim_start_matches('@')
+ pub fn target(&self) -> &str {
+ self.0
+ .children()
+ .find(|node| node.kind() == SyntaxKind::RefMarker)
+ .map(|node| node.text().trim_start_matches('@'))
+ .unwrap_or_default()
+ }
+
+ /// Get the supplement.
+ pub fn supplement(&self) -> Option<ContentBlock> {
+ self.0.cast_last_match()
}
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index 47b5da31..ce3ae744 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -36,8 +36,10 @@ pub enum SyntaxKind {
Link,
/// A label: `<intro>`.
Label,
- /// A reference: `@target`.
+ /// A reference: `@target`, `@target[..]`.
Ref,
+ /// Introduces a reference: `@target`.
+ RefMarker,
/// A section heading: `= Introduction`.
Heading,
/// Introduces a section heading: `=`, `==`, ...
@@ -324,12 +326,14 @@ impl SyntaxKind {
Self::Parbreak => "paragraph break",
Self::Escape => "escape sequence",
Self::Shorthand => "shorthand",
+ Self::SmartQuote => "smart quote",
Self::Strong => "strong content",
Self::Emph => "emphasized content",
Self::Raw => "raw block",
Self::Link => "link",
Self::Label => "label",
Self::Ref => "reference",
+ Self::RefMarker => "reference marker",
Self::Heading => "heading",
Self::HeadingMarker => "heading marker",
Self::ListItem => "list item",
@@ -358,7 +362,6 @@ impl SyntaxKind {
Self::Star => "star",
Self::Underscore => "underscore",
Self::Dollar => "dollar sign",
- Self::SmartQuote => "smart quote",
Self::Plus => "plus",
Self::Minus => "minus",
Self::Slash => "slash",
diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs
index 919cce69..8e27d98d 100644
--- a/src/syntax/lexer.rs
+++ b/src/syntax/lexer.rs
@@ -172,7 +172,7 @@ impl Lexer<'_> {
'h' if self.s.eat_if("ttps://") => self.link(),
'0'..='9' => self.numbering(start),
'<' if self.s.at(is_id_continue) => self.label(),
- '@' if self.s.at(is_id_continue) => self.reference(),
+ '@' => self.ref_marker(),
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'-' if self.s.eat_if("--") => SyntaxKind::Shorthand,
@@ -297,6 +297,11 @@ impl Lexer<'_> {
self.text()
}
+ fn ref_marker(&mut self) -> SyntaxKind {
+ self.s.eat_while(is_id_continue);
+ SyntaxKind::RefMarker
+ }
+
fn label(&mut self) -> SyntaxKind {
let label = self.s.eat_while(is_id_continue);
if label.is_empty() {
@@ -310,11 +315,6 @@ impl Lexer<'_> {
SyntaxKind::Label
}
- fn reference(&mut self) -> SyntaxKind {
- self.s.eat_while(is_id_continue);
- SyntaxKind::Ref
- }
-
fn text(&mut self) -> SyntaxKind {
macro_rules! table {
($(|$c:literal)*) => {
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 201d78fa..127a89d5 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -104,8 +104,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
| SyntaxKind::SmartQuote
| SyntaxKind::Raw
| SyntaxKind::Link
- | SyntaxKind::Label
- | SyntaxKind::Ref => p.eat(),
+ | SyntaxKind::Label => p.eat(),
SyntaxKind::Hashtag => embedded_code_expr(p),
SyntaxKind::Star => strong(p),
@@ -114,6 +113,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
SyntaxKind::ListMarker if *at_start => list_item(p),
SyntaxKind::EnumMarker if *at_start => enum_item(p),
SyntaxKind::TermMarker if *at_start => term_item(p),
+ SyntaxKind::RefMarker => reference(p),
SyntaxKind::Dollar => formula(p),
SyntaxKind::LeftBracket
@@ -198,6 +198,15 @@ fn term_item(p: &mut Parser) {
p.wrap(m, SyntaxKind::TermItem);
}
+fn reference(p: &mut Parser) {
+ let m = p.marker();
+ p.assert(SyntaxKind::RefMarker);
+ if p.directly_at(SyntaxKind::LeftBracket) {
+ content_block(p);
+ }
+ p.wrap(m, SyntaxKind::Ref);
+}
+
fn whitespace_line(p: &mut Parser) {
while !p.newline() && p.current().is_trivia() {
p.eat();