summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-05 12:25:37 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-05 12:25:37 +0100
commitc2e458a133772a94009733040b39d58e781af977 (patch)
tree9404ef8f4982a068f046cb772166fdb8af7c3a0b
parent1d324235bd80fe8d1fb21b1e73ae9e3dfe918078 (diff)
Symbol notation
-rw-r--r--Cargo.lock6
-rw-r--r--library/Cargo.toml1
-rw-r--r--library/src/lib.rs2
-rw-r--r--library/src/text/mod.rs2
-rw-r--r--library/src/text/symbol.rs35
-rw-r--r--src/model/eval.rs9
-rw-r--r--src/model/library.rs2
-rw-r--r--src/syntax/ast.rs19
-rw-r--r--src/syntax/highlight.rs4
-rw-r--r--src/syntax/kind.rs5
-rw-r--r--src/syntax/parsing.rs1
-rw-r--r--src/syntax/tokens.rs21
-rw-r--r--tests/ref/text/symbol.pngbin0 -> 14112 bytes
-rw-r--r--tests/typ/text/symbol.typ27
-rw-r--r--tools/support/typst.tmLanguage.json4
15 files changed, 137 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 222e1c98..fe522d84 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1010,6 +1010,11 @@ dependencies = [
]
[[package]]
+name = "symmie"
+version = "0.1.0"
+source = "git+https://github.com/typst/symmie#8504bf7ec0d8996d160832c2724ba024ab6e988a"
+
+[[package]]
name = "syn"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1175,6 +1180,7 @@ dependencies = [
"roxmltree",
"rustybuzz",
"serde_json",
+ "symmie",
"syntect",
"ttf-parser 0.17.1",
"typed-arena",
diff --git a/library/Cargo.toml b/library/Cargo.toml
index 92bf84a2..2410cb0c 100644
--- a/library/Cargo.toml
+++ b/library/Cargo.toml
@@ -21,6 +21,7 @@ rex = { git = "https://github.com/laurmaedje/ReX" }
roxmltree = "0.14"
rustybuzz = "0.5"
serde_json = "1"
+symmie = { git = "https://github.com/typst/symmie" }
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
ttf-parser = "0.17"
typed-arena = "2"
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 3543a672..d549c1cd 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -34,6 +34,7 @@ fn scope() -> Scope {
// Text.
std.def_node::<text::TextNode>("text");
std.def_node::<text::LinebreakNode>("linebreak");
+ std.def_node::<text::SymbolNode>("symbol");
std.def_node::<text::SmartQuoteNode>("smartquote");
std.def_node::<text::StrongNode>("strong");
std.def_node::<text::EmphNode>("emph");
@@ -173,6 +174,7 @@ fn items() -> LangItems {
text: |text| text::TextNode(text).pack(),
text_id: NodeId::of::<text::TextNode>(),
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
+ symbol: |notation| text::SymbolNode(notation).pack(),
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
parbreak: || layout::ParbreakNode.pack(),
strong: |body| text::StrongNode(body).pack(),
diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs
index 47aaba36..5466637e 100644
--- a/library/src/text/mod.rs
+++ b/library/src/text/mod.rs
@@ -6,6 +6,7 @@ mod quotes;
mod raw;
mod shaping;
mod shift;
+mod symbol;
pub use self::deco::*;
pub use self::misc::*;
@@ -13,6 +14,7 @@ pub use self::quotes::*;
pub use self::raw::*;
pub use self::shaping::*;
pub use self::shift::*;
+pub use self::symbol::*;
use std::borrow::Cow;
diff --git a/library/src/text/symbol.rs b/library/src/text/symbol.rs
new file mode 100644
index 00000000..cc12afb9
--- /dev/null
+++ b/library/src/text/symbol.rs
@@ -0,0 +1,35 @@
+use crate::prelude::*;
+use crate::text::TextNode;
+
+/// A symbol identified by symmie notation.
+#[derive(Debug, Hash)]
+pub struct SymbolNode(pub EcoString);
+
+#[node(Show)]
+impl SymbolNode {
+ fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
+ Ok(Self(args.expect("notation")?).pack())
+ }
+
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "notation" => Some(Value::Str(self.0.clone().into())),
+ _ => None,
+ }
+ }
+}
+
+impl Show for SymbolNode {
+ fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
+ match symmie::get(&self.0) {
+ Some(c) => Ok(TextNode::packed(c)),
+ None => {
+ if let Some(span) = this.span() {
+ bail!(span, "unknown symbol");
+ }
+
+ Ok(Content::empty())
+ }
+ }
+ }
+}
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 43cdbbcc..1d942dd0 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -265,6 +265,7 @@ impl Eval for ast::MarkupNode {
Self::Text(v) => v.eval(vm)?,
Self::Escape(v) => (vm.items.text)(v.get().into()),
Self::Shorthand(v) => v.eval(vm)?,
+ Self::Symbol(v) => v.eval(vm)?,
Self::SmartQuote(v) => v.eval(vm)?,
Self::Strong(v) => v.eval(vm)?,
Self::Emph(v) => v.eval(vm)?,
@@ -306,6 +307,14 @@ impl Eval for ast::Shorthand {
}
}
+impl Eval for ast::Symbol {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok((vm.items.symbol)(self.get().clone()))
+ }
+}
+
impl Eval for ast::SmartQuote {
type Output = Content;
diff --git a/src/model/library.rs b/src/model/library.rs
index c890fef1..63bd5839 100644
--- a/src/model/library.rs
+++ b/src/model/library.rs
@@ -41,6 +41,8 @@ pub struct LangItems {
pub text_id: NodeId,
/// Get the string if this is a text node.
pub text_str: fn(&Content) -> Option<&str>,
+ /// Symbol notation: `:arrow:l:`.
+ pub symbol: fn(notation: EcoString) -> Content,
/// A smart quote: `'` or `"`.
pub smart_quote: fn(double: bool) -> Content,
/// A paragraph break.
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 3c60acbb..c44fa2a0 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -85,6 +85,8 @@ pub enum MarkupNode {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand(Shorthand),
+ /// Symbol notation: `:arrow:l:`.
+ Symbol(Symbol),
/// A smart quote: `'` or `"`.
SmartQuote(SmartQuote),
/// Strong content: `*Strong*`.
@@ -119,6 +121,7 @@ impl AstNode for MarkupNode {
SyntaxKind::Text(_) => node.cast().map(Self::Text),
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand),
+ SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol),
SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
SyntaxKind::Strong => node.cast().map(Self::Strong),
SyntaxKind::Emph => node.cast().map(Self::Emph),
@@ -141,6 +144,7 @@ impl AstNode for MarkupNode {
Self::Text(v) => v.as_untyped(),
Self::Escape(v) => v.as_untyped(),
Self::Shorthand(v) => v.as_untyped(),
+ Self::Symbol(v) => v.as_untyped(),
Self::SmartQuote(v) => v.as_untyped(),
Self::Strong(v) => v.as_untyped(),
Self::Emph(v) => v.as_untyped(),
@@ -224,6 +228,21 @@ impl Shorthand {
}
node! {
+ /// Symbol notation: `:arrow:l:`.
+ Symbol
+}
+
+impl Symbol {
+ /// Get the symbol's notation.
+ pub fn get(&self) -> &EcoString {
+ match self.0.kind() {
+ SyntaxKind::Symbol(v) => v,
+ _ => panic!("symbol is of wrong kind"),
+ }
+ }
+}
+
+node! {
/// A smart quote: `'` or `"`.
SmartQuote
}
diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs
index 526d1302..d4da7b3e 100644
--- a/src/syntax/highlight.rs
+++ b/src/syntax/highlight.rs
@@ -139,6 +139,8 @@ pub enum Category {
Escape,
/// An easily typable shortcut to a unicode codepoint.
Shorthand,
+ /// Symbol notation.
+ Symbol,
/// A smart quote.
SmartQuote,
/// Strong markup.
@@ -285,6 +287,7 @@ impl Category {
SyntaxKind::Linebreak => Some(Category::Escape),
SyntaxKind::Escape(_) => Some(Category::Escape),
SyntaxKind::Shorthand(_) => Some(Category::Shorthand),
+ SyntaxKind::Symbol(_) => Some(Category::Symbol),
SyntaxKind::SmartQuote { .. } => Some(Category::SmartQuote),
SyntaxKind::Strong => Some(Category::Strong),
SyntaxKind::Emph => Some(Category::Emph),
@@ -369,6 +372,7 @@ impl Category {
Self::Punctuation => "punctuation.typst",
Self::Escape => "constant.character.escape.typst",
Self::Shorthand => "constant.character.shorthand.typst",
+ Self::Symbol => "constant.symbol.typst",
Self::SmartQuote => "constant.character.quote.typst",
Self::Strong => "markup.bold.typst",
Self::Emph => "markup.italic.typst",
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index a7425d70..a4eb317b 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -144,6 +144,9 @@ pub enum SyntaxKind {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand(char),
+ /// Symbol notation: `:arrow:l:`. The string only contains the inner part
+ /// without leading and trailing dot.
+ Symbol(EcoString),
/// A smart quote: `'` or `"`.
SmartQuote { double: bool },
/// Strong content: `*Strong*`.
@@ -389,6 +392,7 @@ impl SyntaxKind {
Self::Linebreak => "linebreak",
Self::Escape(_) => "escape sequence",
Self::Shorthand(_) => "shorthand",
+ Self::Symbol(_) => "symbol notation",
Self::Strong => "strong content",
Self::Emph => "emphasized content",
Self::Raw(_) => "raw block",
@@ -507,6 +511,7 @@ impl Hash for SyntaxKind {
Self::Linebreak => {}
Self::Escape(c) => c.hash(state),
Self::Shorthand(c) => c.hash(state),
+ Self::Symbol(s) => s.hash(state),
Self::SmartQuote { double } => double.hash(state),
Self::Strong => {}
Self::Emph => {}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 1678eb01..59e066a6 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -230,6 +230,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
| SyntaxKind::SmartQuote { .. }
| SyntaxKind::Escape(_)
| SyntaxKind::Shorthand(_)
+ | SyntaxKind::Symbol(_)
| SyntaxKind::Link(_)
| SyntaxKind::Raw(_)
| SyntaxKind::Ref(_) => p.eat(),
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index e7015bb2..130ad668 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -203,6 +203,7 @@ impl<'s> Tokens<'s> {
'#' => self.hash(start),
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand('\u{2026}'),
'-' => self.hyph(),
+ ':' => self.colon(),
'h' if self.s.eat_if("ttp://") || self.s.eat_if("ttps://") => {
self.link(start)
}
@@ -224,7 +225,6 @@ impl<'s> Tokens<'s> {
'=' => SyntaxKind::Eq,
'+' => SyntaxKind::Plus,
'/' => SyntaxKind::Slash,
- ':' => SyntaxKind::Colon,
// Plain text.
_ => self.text(start),
@@ -328,6 +328,25 @@ impl<'s> Tokens<'s> {
}
}
+ fn colon(&mut self) -> SyntaxKind {
+ let start = self.s.cursor();
+ let mut end = start;
+ while !self.s.eat_while(char::is_ascii_alphanumeric).is_empty() && self.s.at(':')
+ {
+ end = self.s.cursor();
+ self.s.eat();
+ }
+
+ self.s.jump(end);
+
+ if start < end {
+ self.s.expect(':');
+ SyntaxKind::Symbol(self.s.get(start..end).into())
+ } else {
+ SyntaxKind::Colon
+ }
+ }
+
fn link(&mut self, start: usize) -> SyntaxKind {
#[rustfmt::skip]
self.s.eat_while(|c: char| matches!(c,
diff --git a/tests/ref/text/symbol.png b/tests/ref/text/symbol.png
new file mode 100644
index 00000000..4806683b
--- /dev/null
+++ b/tests/ref/text/symbol.png
Binary files differ
diff --git a/tests/typ/text/symbol.typ b/tests/typ/text/symbol.typ
new file mode 100644
index 00000000..4dcef58b
--- /dev/null
+++ b/tests/typ/text/symbol.typ
@@ -0,0 +1,27 @@
+// Test symbol notation.
+
+---
+:face:
+:face:unknown:
+:woman:old:
+:turtle:
+
+#set text("NewComputerModernMath")
+:arrow:
+:arrow:l:
+:arrow:r:squiggly:
+#symbol(("arrow", "tr", "hook").join(":"))
+
+---
+Just a: colon. \
+Still :not a symbol. \
+Also not:a symbol \
+:arrow:r:this and this:arrow:l: \
+
+---
+#show symbol.where(notation: "my:custom"): "MY"
+This is :my:custom: notation.
+
+---
+// Error: 1-14 unknown symbol
+:nonexisting:
diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json
index f8d66234..c849382e 100644
--- a/tools/support/typst.tmLanguage.json
+++ b/tools/support/typst.tmLanguage.json
@@ -73,6 +73,10 @@
"match": "\\.\\.\\."
},
{
+ "name": "constant.symbol.typst",
+ "match": ":([a-zA-Z0-9]+:)+"
+ },
+ {
"name": "markup.bold.typst",
"begin": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))",
"end": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))|\n|(?=\\])",