diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs index 1cd9cd429..3d3a85ad4 100644 --- a/crates/typst-syntax/src/ast.rs +++ b/crates/typst-syntax/src/ast.rs @@ -125,6 +125,8 @@ pub enum Expr<'a> { Math(Math<'a>), /// An identifier in math: `pi`. MathIdent(MathIdent<'a>), + /// A shorthand for a unicode codepoint in math: `a <= b`. + MathShorthand(MathShorthand<'a>), /// An alignment point in math: `&`. MathAlignPoint(MathAlignPoint<'a>), /// Matched delimiters in math: `[x + y]`. @@ -232,6 +234,7 @@ impl<'a> AstNode<'a> for Expr<'a> { SyntaxKind::Equation => node.cast().map(Self::Equation), SyntaxKind::Math => node.cast().map(Self::Math), SyntaxKind::MathIdent => node.cast().map(Self::MathIdent), + SyntaxKind::MathShorthand => node.cast().map(Self::MathShorthand), SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint), SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited), SyntaxKind::MathAttach => node.cast().map(Self::MathAttach), @@ -295,6 +298,7 @@ impl<'a> AstNode<'a> for Expr<'a> { Self::Equation(v) => v.to_untyped(), Self::Math(v) => v.to_untyped(), Self::MathIdent(v) => v.to_untyped(), + Self::MathShorthand(v) => v.to_untyped(), Self::MathAlignPoint(v) => v.to_untyped(), Self::MathDelimited(v) => v.to_untyped(), Self::MathAttach(v) => v.to_untyped(), @@ -450,7 +454,7 @@ node! { impl Shorthand<'_> { /// A list of all shorthands in markup mode. - pub const MARKUP_LIST: &'static [(&'static str, char)] = &[ + pub const LIST: &'static [(&'static str, char)] = &[ ("...", '…'), ("~", '\u{00A0}'), ("-", '\u{2212}'), // Only before a digit @@ -459,52 +463,11 @@ impl Shorthand<'_> { ("-?", '\u{00AD}'), ]; - /// A list of all shorthands in math mode. - pub const MATH_LIST: &'static [(&'static str, char)] = &[ - ("...", '…'), - ("-", '\u{2212}'), - ("'", '′'), - ("*", '∗'), - ("!=", '≠'), - (":=", '≔'), - ("::=", '⩴'), - ("=:", '≕'), - ("<<", '≪'), - ("<<<", '⋘'), - (">>", '≫'), - (">>>", '⋙'), - ("<=", '≤'), - (">=", '≥'), - ("->", '→'), - ("-->", '⟶'), - ("|->", '↦'), - (">->", '↣'), - ("->>", '↠'), - ("<-", '←'), - ("<--", '⟵'), - ("<-<", '↢'), - ("<<-", '↞'), - ("<->", '↔'), - ("<-->", '⟷'), - ("~>", '⇝'), - ("~~>", '⟿'), - ("<~", '⇜'), - ("<~~", '⬳'), - ("=>", '⇒'), - ("|=>", '⤇'), - ("==>", '⟹'), - ("<==", '⟸'), - ("<=>", '⇔'), - ("<==>", '⟺'), - ("[|", '⟦'), - ("|]", '⟧'), - ("||", '‖'), - ]; - /// Get the shorthanded character. pub fn get(self) -> char { let text = self.0.text(); - (Self::MARKUP_LIST.iter().chain(Self::MATH_LIST)) + Self::LIST + .iter() .find(|&&(s, _)| s == text) .map_or_else(char::default, |&(_, c)| c) } @@ -770,6 +733,65 @@ impl Deref for MathIdent<'_> { } } +node! { + /// A shorthand for a unicode codepoint in math: `a <= b`. + MathShorthand +} + +impl MathShorthand<'_> { + /// A list of all shorthands in math mode. + pub const LIST: &'static [(&'static str, char)] = &[ + ("...", '…'), + ("-", '−'), + ("'", '′'), + ("*", '∗'), + ("~", '∼'), + ("!=", '≠'), + (":=", '≔'), + ("::=", '⩴'), + ("=:", '≕'), + ("<<", '≪'), + ("<<<", '⋘'), + (">>", '≫'), + (">>>", '⋙'), + ("<=", '≤'), + (">=", '≥'), + ("->", '→'), + ("-->", '⟶'), + ("|->", '↦'), + (">->", '↣'), + ("->>", '↠'), + ("<-", '←'), + ("<--", '⟵'), + ("<-<", '↢'), + ("<<-", '↞'), + ("<->", '↔'), + ("<-->", '⟷'), + ("~>", '⇝'), + ("~~>", '⟿'), + ("<~", '⇜'), + ("<~~", '⬳'), + ("=>", '⇒'), + ("|=>", '⤇'), + ("==>", '⟹'), + ("<==", '⟸'), + ("<=>", '⇔'), + ("<==>", '⟺'), + ("[|", '⟦'), + ("|]", '⟧'), + ("||", '‖'), + ]; + + /// Get the shorthanded character. + pub fn get(self) -> char { + let text = self.0.text(); + Self::LIST + .iter() + .find(|&&(s, _)| s == text) + .map_or_else(char::default, |&(_, c)| c) + } +} + node! { /// An alignment point in math: `&`. MathAlignPoint diff --git a/crates/typst-syntax/src/highlight.rs b/crates/typst-syntax/src/highlight.rs index 0c1f3d5fd..ddd293260 100644 --- a/crates/typst-syntax/src/highlight.rs +++ b/crates/typst-syntax/src/highlight.rs @@ -172,6 +172,7 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::Math => None, SyntaxKind::MathIdent => highlight_ident(node), + SyntaxKind::MathShorthand => Some(Tag::Escape), SyntaxKind::MathAlignPoint => Some(Tag::MathOperator), SyntaxKind::MathDelimited => None, SyntaxKind::MathAttach => None, diff --git a/crates/typst-syntax/src/kind.rs b/crates/typst-syntax/src/kind.rs index 7505dbc61..a4456b9f4 100644 --- a/crates/typst-syntax/src/kind.rs +++ b/crates/typst-syntax/src/kind.rs @@ -75,6 +75,8 @@ pub enum SyntaxKind { Math, /// An identifier in math: `pi`. MathIdent, + /// A shorthand for a unicode codepoint in math: `a <= b`. + MathShorthand, /// An alignment point in math: `&`. MathAlignPoint, /// Matched delimiters in math: `[x + y]`. @@ -400,6 +402,7 @@ impl SyntaxKind { Self::Equation => "equation", Self::Math => "math", Self::MathIdent => "math identifier", + Self::MathShorthand => "math shorthand", Self::MathAlignPoint => "math alignment point", Self::MathDelimited => "delimited math", Self::MathAttach => "math attachments", diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index 993af0806..92e78b2d0 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -514,42 +514,42 @@ impl Lexer<'_> { '\\' => self.backslash(), '"' => self.string(), - '-' if self.s.eat_if(">>") => SyntaxKind::Shorthand, - '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '-' if self.s.eat_if("->") => SyntaxKind::Shorthand, - ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, - ':' if self.s.eat_if(":=") => SyntaxKind::Shorthand, - '!' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, - '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, - '<' if self.s.eat_if("==>") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("-->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("--") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("-<") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("<-") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("<<") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("==") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("~~") => SyntaxKind::Shorthand, - '<' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('<') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('-') => SyntaxKind::Shorthand, - '<' if self.s.eat_if('~') => SyntaxKind::Shorthand, - '>' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand, - '=' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '=' if self.s.eat_if(':') => SyntaxKind::Shorthand, - '>' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '>' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '|' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '|' if self.s.eat_if("=>") => SyntaxKind::Shorthand, - '|' if self.s.eat_if(']') => SyntaxKind::Shorthand, - '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, - '~' if self.s.eat_if("~>") => SyntaxKind::Shorthand, - '~' if self.s.eat_if('>') => SyntaxKind::Shorthand, - '*' | '-' => SyntaxKind::Shorthand, + '-' if self.s.eat_if(">>") => SyntaxKind::MathShorthand, + '-' if self.s.eat_if('>') => SyntaxKind::MathShorthand, + '-' if self.s.eat_if("->") => SyntaxKind::MathShorthand, + ':' if self.s.eat_if('=') => SyntaxKind::MathShorthand, + ':' if self.s.eat_if(":=") => SyntaxKind::MathShorthand, + '!' if self.s.eat_if('=') => SyntaxKind::MathShorthand, + '.' if self.s.eat_if("..") => SyntaxKind::MathShorthand, + '[' if self.s.eat_if('|') => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("==>") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("-->") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("--") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("-<") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("->") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("<-") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("<<") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("=>") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("==") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if("~~") => SyntaxKind::MathShorthand, + '<' if self.s.eat_if('=') => SyntaxKind::MathShorthand, + '<' if self.s.eat_if('<') => SyntaxKind::MathShorthand, + '<' if self.s.eat_if('-') => SyntaxKind::MathShorthand, + '<' if self.s.eat_if('~') => SyntaxKind::MathShorthand, + '>' if self.s.eat_if("->") => SyntaxKind::MathShorthand, + '>' if self.s.eat_if(">>") => SyntaxKind::MathShorthand, + '=' if self.s.eat_if("=>") => SyntaxKind::MathShorthand, + '=' if self.s.eat_if('>') => SyntaxKind::MathShorthand, + '=' if self.s.eat_if(':') => SyntaxKind::MathShorthand, + '>' if self.s.eat_if('=') => SyntaxKind::MathShorthand, + '>' if self.s.eat_if('>') => SyntaxKind::MathShorthand, + '|' if self.s.eat_if("->") => SyntaxKind::MathShorthand, + '|' if self.s.eat_if("=>") => SyntaxKind::MathShorthand, + '|' if self.s.eat_if(']') => SyntaxKind::MathShorthand, + '|' if self.s.eat_if('|') => SyntaxKind::MathShorthand, + '~' if self.s.eat_if("~>") => SyntaxKind::MathShorthand, + '~' if self.s.eat_if('>') => SyntaxKind::MathShorthand, + '*' | '-' | '~' => SyntaxKind::MathShorthand, '#' => SyntaxKind::Hash, '_' => SyntaxKind::Underscore, diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index 2cbce1d70..4ce3917e0 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -319,7 +319,7 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { } } - SyntaxKind::Text | SyntaxKind::Shorthand => { + SyntaxKind::Text | SyntaxKind::MathShorthand => { continuable = matches!( math_class(p.current_text()), None | Some(MathClass::Alphabetic) diff --git a/crates/typst-syntax/src/set.rs b/crates/typst-syntax/src/set.rs index 39e64651b..2297cacb1 100644 --- a/crates/typst-syntax/src/set.rs +++ b/crates/typst-syntax/src/set.rs @@ -80,7 +80,7 @@ pub const MATH_EXPR: SyntaxSet = SyntaxSet::new() .add(SyntaxKind::Hash) .add(SyntaxKind::MathIdent) .add(SyntaxKind::Text) - .add(SyntaxKind::Shorthand) + .add(SyntaxKind::MathShorthand) .add(SyntaxKind::Linebreak) .add(SyntaxKind::MathAlignPoint) .add(SyntaxKind::Escape) diff --git a/crates/typst/src/eval/code.rs b/crates/typst/src/eval/code.rs index dcbc5b69d..7cfb7f593 100644 --- a/crates/typst/src/eval/code.rs +++ b/crates/typst/src/eval/code.rs @@ -95,6 +95,7 @@ impl Eval for ast::Expr<'_> { Self::Equation(v) => v.eval(vm).map(Value::Content), Self::Math(v) => v.eval(vm).map(Value::Content), Self::MathIdent(v) => v.eval(vm), + Self::MathShorthand(v) => v.eval(vm), Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), Self::MathDelimited(v) => v.eval(vm).map(Value::Content), Self::MathAttach(v) => v.eval(vm).map(Value::Content), diff --git a/crates/typst/src/eval/math.rs b/crates/typst/src/eval/math.rs index 548c935dc..42528d618 100644 --- a/crates/typst/src/eval/math.rs +++ b/crates/typst/src/eval/math.rs @@ -4,6 +4,7 @@ use crate::diag::{At, SourceResult}; use crate::eval::{Eval, Vm}; use crate::foundations::{Content, NativeElement, Value}; use crate::math::{AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem}; +use crate::symbols::Symbol; use crate::syntax::ast::{self, AstNode}; use crate::text::TextElem; @@ -26,6 +27,14 @@ impl Eval for ast::MathIdent<'_> { } } +impl Eval for ast::MathShorthand<'_> { + type Output = Value; + + fn eval(self, _: &mut Vm) -> SourceResult { + Ok(Value::Symbol(Symbol::single(self.get().into()))) + } +} + impl Eval for ast::MathAlignPoint<'_> { type Output = Content; diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 335d01108..b28111e4e 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -664,8 +664,8 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { list.push(SymbolModel { name: complete(variant), - markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST), - math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST), + markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST), + math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST), codepoint: c.char() as _, accent: typst::math::Accent::combine(c.char()).is_some(), alternates: symbol diff --git a/tests/ref/math-class-chars.png b/tests/ref/math-class-chars.png index a4f7d29b1..6bcaaf408 100644 Binary files a/tests/ref/math-class-chars.png and b/tests/ref/math-class-chars.png differ diff --git a/tests/ref/shorthand-minus.png b/tests/ref/shorthand-minus.png new file mode 100644 index 000000000..e42498651 Binary files /dev/null and b/tests/ref/shorthand-minus.png differ diff --git a/tests/ref/shorthands-math.png b/tests/ref/shorthands-math.png index 0514fa622..ab6b73337 100644 Binary files a/tests/ref/shorthands-math.png and b/tests/ref/shorthands-math.png differ diff --git a/tests/suite/math/class.typ b/tests/suite/math/class.typ index 7aad04465..d25071dbd 100644 --- a/tests/suite/math/class.typ +++ b/tests/suite/math/class.typ @@ -8,7 +8,7 @@ $ a class("normal", +) b \ { x class("fence", \;) x > 0} \ a class("large", \/) b \ a class("punctuation", :) b \ - a class("relation", ~) b \ + a class("relation", !) b \ a + class("unary", times) b \ class("vary", :) a class("vary", :) b $ diff --git a/tests/suite/syntax/shorthand.typ b/tests/suite/syntax/shorthand.typ index 81aa6b7bd..7d1782b9f 100644 --- a/tests/suite/syntax/shorthand.typ +++ b/tests/suite/syntax/shorthand.typ @@ -19,12 +19,17 @@ a~b #set text(font: "Roboto") A... vs #"A..." +--- shorthand-minus --- +// Make sure shorthand is applied only before a digit. +-a -1 + --- shorthands-math --- -// Check all math shorthands +// Check all math shorthands. $...$\ $-$\ $'$\ $*$\ +$~$\ $!=$\ $:=$\ $::=$\