diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-05 12:25:37 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-05 12:25:37 +0100 |
| commit | c2e458a133772a94009733040b39d58e781af977 (patch) | |
| tree | 9404ef8f4982a068f046cb772166fdb8af7c3a0b | |
| parent | 1d324235bd80fe8d1fb21b1e73ae9e3dfe918078 (diff) | |
Symbol notation
| -rw-r--r-- | Cargo.lock | 6 | ||||
| -rw-r--r-- | library/Cargo.toml | 1 | ||||
| -rw-r--r-- | library/src/lib.rs | 2 | ||||
| -rw-r--r-- | library/src/text/mod.rs | 2 | ||||
| -rw-r--r-- | library/src/text/symbol.rs | 35 | ||||
| -rw-r--r-- | src/model/eval.rs | 9 | ||||
| -rw-r--r-- | src/model/library.rs | 2 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 19 | ||||
| -rw-r--r-- | src/syntax/highlight.rs | 4 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 5 | ||||
| -rw-r--r-- | src/syntax/parsing.rs | 1 | ||||
| -rw-r--r-- | src/syntax/tokens.rs | 21 | ||||
| -rw-r--r-- | tests/ref/text/symbol.png | bin | 0 -> 14112 bytes | |||
| -rw-r--r-- | tests/typ/text/symbol.typ | 27 | ||||
| -rw-r--r-- | tools/support/typst.tmLanguage.json | 4 |
15 files changed, 137 insertions, 1 deletions
@@ -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 Binary files differnew file mode 100644 index 00000000..4806683b --- /dev/null +++ b/tests/ref/text/symbol.png 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|(?=\\])", |
