From 8d1ce390e21ce0a5812a4211c893ec359906d6f1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 30 Jan 2022 12:50:58 +0100 Subject: [PATCH] Rework strong and emph - Star and underscore not parsed as strong/emph inside of words - Stars/underscores must be balanced and they cannot go over paragraph break - New `strong` and `emph` classes --- src/eval/mod.rs | 26 ++++++--- src/eval/styles.rs | 13 ----- src/library/mod.rs | 2 + src/library/text.rs | 20 +++++++ src/parse/incremental.rs | 8 ++- src/parse/mod.rs | 39 +++++++++---- src/parse/parser.rs | 84 ++++++++++++++++++---------- src/parse/tokens.rs | 17 ++++-- src/syntax/ast.rs | 40 ++++++++++--- src/syntax/highlight.rs | 12 ++-- src/syntax/mod.rs | 41 +++++++------- src/syntax/pretty.rs | 24 ++++++-- tests/ref/markup/emph-strong.png | Bin 0 -> 6988 bytes tests/ref/markup/emph.png | Bin 3772 -> 0 bytes tests/ref/markup/strong.png | Bin 3481 -> 0 bytes tests/ref/style/set-toggle.png | Bin 1188 -> 0 bytes tests/typ/code/ops.typ | 2 +- tests/typ/markup/emph-strong.typ | 33 +++++++++++ tests/typ/markup/emph.typ | 11 ---- tests/typ/markup/escape.typ | 2 +- tests/typ/markup/strong.typ | 11 ---- tests/typ/style/set-toggle.typ | 10 ---- tests/typ/style/wrap.typ | 2 +- tests/typ/text/bidi.typ | 4 +- tests/typ/text/linebreaks.typ | 2 +- tests/typeset.rs | 5 +- tools/support/typst.tmLanguage.json | 8 +-- 27 files changed, 268 insertions(+), 148 deletions(-) create mode 100644 tests/ref/markup/emph-strong.png delete mode 100644 tests/ref/markup/emph.png delete mode 100644 tests/ref/markup/strong.png delete mode 100644 tests/ref/style/set-toggle.png create mode 100644 tests/typ/markup/emph-strong.typ delete mode 100644 tests/typ/markup/emph.typ delete mode 100644 tests/typ/markup/strong.typ delete mode 100644 tests/typ/style/set-toggle.typ diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2fa07d49e..a453a3570 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -213,15 +213,9 @@ impl Eval for MarkupNode { Self::Space => Node::Space, Self::Linebreak => Node::Linebreak, Self::Parbreak => Node::Parbreak, - Self::Strong => { - ctx.styles.toggle(TextNode::STRONG); - Node::new() - } - Self::Emph => { - ctx.styles.toggle(TextNode::EMPH); - Node::new() - } Self::Text(text) => Node::Text(text.clone()), + Self::Strong(strong) => strong.eval(ctx)?, + Self::Emph(emph) => emph.eval(ctx)?, Self::Raw(raw) => raw.eval(ctx)?, Self::Math(math) => math.eval(ctx)?, Self::Heading(heading) => heading.eval(ctx)?, @@ -232,6 +226,22 @@ impl Eval for MarkupNode { } } +impl Eval for StrongNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(self.body().eval(ctx)?.styled(TextNode::STRONG, true)) + } +} + +impl Eval for EmphNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(self.body().eval(ctx)?.styled(TextNode::EMPH, true)) + } +} + impl Eval for RawNode { type Output = Node; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index bdc01f1f0..508996a1c 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -87,19 +87,6 @@ impl StyleMap { } } - /// Toggle a boolean style property, removing it if it exists and inserting - /// it with `true` if it doesn't. - pub fn toggle>(&mut self, key: P) { - for (i, entry) in self.0.iter_mut().enumerate() { - if entry.is::

() { - self.0.swap_remove(i); - return; - } - } - - self.0.push(Entry::new(key, true)); - } - /// Mark all contained properties as _scoped_. This means that they only /// apply to the first descendant node (of their type) in the hierarchy and /// not its children, too. This is used by class constructors. diff --git a/src/library/mod.rs b/src/library/mod.rs index 44f8f947f..3115cc7ae 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -93,6 +93,8 @@ pub fn new() -> Scope { std.def_class::("parbreak"); std.def_class::("linebreak"); std.def_class::("text"); + std.def_class::("strong"); + std.def_class::("emph"); std.def_class::>("underline"); std.def_class::>("strike"); std.def_class::>("overline"); diff --git a/src/library/text.rs b/src/library/text.rs index b8810ac62..6d7be323f 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -150,6 +150,26 @@ impl Debug for TextNode { } } +/// Strong text, rendered in boldface. +pub struct StrongNode; + +#[class] +impl StrongNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { + Ok(args.expect::("body")?.styled(TextNode::STRONG, true)) + } +} + +/// Emphasized text, rendered with an italic face. +pub struct EmphNode; + +#[class] +impl EmphNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { + Ok(args.expect::("body")?.styled(TextNode::EMPH, true)) + } +} + /// A generic or named font family. #[derive(Clone, Eq, PartialEq, Hash)] pub enum FontFamily { diff --git a/src/parse/incremental.rs b/src/parse/incremental.rs index 9dd5bec19..fb927c247 100644 --- a/src/parse/incremental.rs +++ b/src/parse/incremental.rs @@ -435,10 +435,12 @@ impl NodeKind { | Self::LeftParen | Self::RightParen => SuccessionRule::Unsafe, + // These work similar to parentheses. + Self::Star | Self::Underscore => SuccessionRule::Unsafe, + // Replacing an operator can change whether the parent is an - // operation which makes it unsafe. The star can appear in markup. - Self::Star - | Self::Comma + // operation which makes it unsafe. + Self::Comma | Self::Semicolon | Self::Colon | Self::Plus diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a9839ed67..b8ef30661 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -21,7 +21,7 @@ use crate::util::EcoString; /// Parse a source file. pub fn parse(src: &str) -> Rc { let mut p = Parser::new(src, TokenMode::Markup); - markup(&mut p); + markup(&mut p, true); match p.finish().into_iter().next() { Some(Green::Node(node)) => node, _ => unreachable!(), @@ -61,7 +61,7 @@ pub fn parse_markup( ) -> Option<(Vec, bool)> { let mut p = Parser::with_prefix(prefix, src, TokenMode::Markup); if min_column == 0 { - markup(&mut p); + markup(&mut p, true); } else { markup_indented(&mut p, min_column); } @@ -128,8 +128,8 @@ pub fn parse_comment( } /// Parse markup. -fn markup(p: &mut Parser) { - markup_while(p, true, 0, &mut |_| true) +fn markup(p: &mut Parser, at_start: bool) { + markup_while(p, at_start, 0, &mut |_| true) } /// Parse markup that stays right of the given column. @@ -191,8 +191,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | NodeKind::EnDash | NodeKind::EmDash | NodeKind::NonBreakingSpace - | NodeKind::Emph - | NodeKind::Strong | NodeKind::Linebreak | NodeKind::Raw(_) | NodeKind::Math(_) @@ -200,6 +198,9 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { p.eat(); } + // Grouping markup. + NodeKind::Star => strong(p), + NodeKind::Underscore => emph(p), NodeKind::Eq => heading(p, *at_start), NodeKind::Minus => list_node(p, *at_start), NodeKind::EnumNumbering(_) => enum_node(p, *at_start), @@ -227,6 +228,24 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { *at_start = false; } +/// Parse strong content. +fn strong(p: &mut Parser) { + p.perform(NodeKind::Strong, |p| { + p.start_group(Group::Strong); + markup(p, false); + p.end_group(); + }) +} + +/// Parse emphasized content. +fn emph(p: &mut Parser) { + p.perform(NodeKind::Emph, |p| { + p.start_group(Group::Emph); + markup(p, false); + p.end_group(); + }) +} + /// Parse a heading. fn heading(p: &mut Parser, at_start: bool) { let marker = p.marker(); @@ -234,7 +253,7 @@ fn heading(p: &mut Parser, at_start: bool) { p.eat_assert(&NodeKind::Eq); while p.eat_if(&NodeKind::Eq) {} - if at_start && p.peek().map_or(true, |kind| kind.is_whitespace()) { + if at_start && p.peek().map_or(true, |kind| kind.is_space()) { let column = p.column(p.prev_end()); markup_indented(p, column); marker.end(p, NodeKind::Heading); @@ -250,7 +269,7 @@ fn list_node(p: &mut Parser, at_start: bool) { let text: EcoString = p.peek_src().into(); p.eat_assert(&NodeKind::Minus); - if at_start && p.peek().map_or(true, |kind| kind.is_whitespace()) { + if at_start && p.peek().map_or(true, |kind| kind.is_space()) { let column = p.column(p.prev_end()); markup_indented(p, column); marker.end(p, NodeKind::List); @@ -265,7 +284,7 @@ fn enum_node(p: &mut Parser, at_start: bool) { let text: EcoString = p.peek_src().into(); p.eat(); - if at_start && p.peek().map_or(true, |kind| kind.is_whitespace()) { + if at_start && p.peek().map_or(true, |kind| kind.is_space()) { let column = p.column(p.prev_end()); markup_indented(p, column); marker.end(p, NodeKind::Enum); @@ -620,7 +639,7 @@ fn params(p: &mut Parser, marker: Marker) { fn template(p: &mut Parser) { p.perform(NodeKind::Template, |p| { p.start_group(Group::Bracket); - markup(p); + markup(p, true); p.end_group(); }); } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 0184c1983..db003e726 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -239,17 +239,18 @@ impl<'s> Parser<'s> { pub fn start_group(&mut self, kind: Group) { self.groups.push(GroupEntry { kind, prev_mode: self.tokens.mode() }); self.tokens.set_mode(match kind { - Group::Bracket => TokenMode::Markup, - _ => TokenMode::Code, + Group::Bracket | Group::Strong | Group::Emph => TokenMode::Markup, + Group::Paren | Group::Brace | Group::Expr | Group::Imports => TokenMode::Code, }); - self.repeek(); match kind { Group::Paren => self.eat_assert(&NodeKind::LeftParen), Group::Bracket => self.eat_assert(&NodeKind::LeftBracket), Group::Brace => self.eat_assert(&NodeKind::LeftBrace), - Group::Expr => {} - Group::Imports => {} + Group::Strong => self.eat_assert(&NodeKind::Star), + Group::Emph => self.eat_assert(&NodeKind::Underscore), + Group::Expr => self.repeek(), + Group::Imports => self.repeek(), } } @@ -273,6 +274,8 @@ impl<'s> Parser<'s> { Group::Paren => Some((NodeKind::RightParen, true)), Group::Bracket => Some((NodeKind::RightBracket, true)), Group::Brace => Some((NodeKind::RightBrace, true)), + Group::Strong => Some((NodeKind::Star, true)), + Group::Emph => Some((NodeKind::Underscore, true)), Group::Expr => Some((NodeKind::Semicolon, false)), Group::Imports => None, } { @@ -322,9 +325,11 @@ impl<'s> Parser<'s> { Some(NodeKind::RightParen) => self.inside(Group::Paren), Some(NodeKind::RightBracket) => self.inside(Group::Bracket), Some(NodeKind::RightBrace) => self.inside(Group::Brace), + Some(NodeKind::Star) => self.inside(Group::Strong), + Some(NodeKind::Underscore) => self.inside(Group::Emph), Some(NodeKind::Semicolon) => self.inside(Group::Expr), Some(NodeKind::From) => self.inside(Group::Imports), - Some(NodeKind::Space(n)) => *n >= 1 && self.stop_at_newline(), + Some(NodeKind::Space(n)) => self.space_ends_group(*n), Some(_) => false, None => true, }; @@ -332,31 +337,34 @@ impl<'s> Parser<'s> { /// Returns whether the given type can be skipped over. fn is_trivia(&self, token: &NodeKind) -> bool { - Self::is_trivia_ext(token, self.stop_at_newline()) - } - - /// Returns whether the given type can be skipped over given the current - /// newline mode. - fn is_trivia_ext(token: &NodeKind, stop_at_newline: bool) -> bool { match token { - NodeKind::Space(n) => *n == 0 || !stop_at_newline, + NodeKind::Space(n) => !self.space_ends_group(*n), NodeKind::LineComment => true, NodeKind::BlockComment => true, _ => false, } } - /// Whether the active group must end at a newline. - fn stop_at_newline(&self) -> bool { - matches!( - self.groups.last().map(|group| group.kind), - Some(Group::Expr | Group::Imports) - ) + /// Whether a space with the given number of newlines ends the current group. + fn space_ends_group(&self, n: usize) -> bool { + if n == 0 { + return false; + } + + match self.groups.last().map(|group| group.kind) { + Some(Group::Strong | Group::Emph) => n >= 2, + Some(Group::Expr | Group::Imports) => n >= 1, + _ => false, + } } - /// Whether we are inside the given group. + /// Whether we are inside the given group (can be nested). fn inside(&self, kind: Group) -> bool { - self.groups.iter().any(|g| g.kind == kind) + self.groups + .iter() + .rev() + .take_while(|g| !kind.is_weak() || g.kind.is_weak()) + .any(|g| g.kind == kind) } } @@ -431,15 +439,20 @@ impl Marker { F: Fn(&Green) -> Result<(), &'static str>, { for child in &mut p.children[self.0 ..] { - if (p.tokens.mode() == TokenMode::Markup - || !Parser::is_trivia_ext(child.kind(), false)) - && !child.kind().is_error() - { - if let Err(msg) = f(child) { - let error = NodeKind::Error(ErrorPos::Full, msg.into()); - let inner = mem::take(child); - *child = GreenNode::with_child(error, inner).into(); - } + // Don't expose errors. + if child.kind().is_error() { + continue; + } + + // Don't expose trivia in code. + if p.tokens.mode() == TokenMode::Code && child.kind().is_trivia() { + continue; + } + + if let Err(msg) = f(child) { + let error = NodeKind::Error(ErrorPos::Full, msg.into()); + let inner = mem::take(child); + *child = GreenNode::with_child(error, inner).into(); } } } @@ -485,12 +498,23 @@ pub enum Group { Brace, /// A parenthesized group: `(...)`. Paren, + /// A group surrounded with stars: `*...*`. + Strong, + /// A group surrounded with underscore: `_..._`. + Emph, /// A group ended by a semicolon or a line break: `;`, `\n`. Expr, /// A group for import items, ended by a semicolon, line break or `from`. Imports, } +impl Group { + /// Whether the group can only force other weak groups to end. + fn is_weak(self) -> bool { + matches!(self, Group::Strong | Group::Emph) + } +} + /// Allows parser methods to use the try operator. Never returned top-level /// because the parser recovers from all errors. pub type ParseResult = Result; diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index eef7a72d8..d741dea1a 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -123,8 +123,8 @@ impl<'s> Tokens<'s> { // Markup. '~' => NodeKind::NonBreakingSpace, - '*' => NodeKind::Strong, - '_' => NodeKind::Emph, + '*' if !self.in_word() => NodeKind::Star, + '_' if !self.in_word() => NodeKind::Underscore, '`' => self.raw(), '$' => self.math(), '-' => self.hyph(), @@ -527,6 +527,13 @@ impl<'s> Tokens<'s> { NodeKind::BlockComment } + fn in_word(&self) -> bool { + let alphanumeric = |c: Option| c.map_or(false, |c| c.is_alphanumeric()); + let prev = self.s.get(.. self.s.last_index()).chars().next_back(); + let next = self.s.peek(); + alphanumeric(prev) && alphanumeric(next) + } + fn maybe_in_url(&self) -> bool { self.mode == TokenMode::Markup && self.s.eaten().ends_with(":/") } @@ -651,7 +658,7 @@ mod tests { ('/', None, "[", LeftBracket), ('/', None, "//", LineComment), ('/', None, "/**/", BlockComment), - ('/', Some(Markup), "*", Strong), + ('/', Some(Markup), "*", Star), ('/', Some(Markup), "$ $", Math(" ", false)), ('/', Some(Markup), r"\\", Escape('\\')), ('/', Some(Markup), "#let", Let), @@ -790,8 +797,8 @@ mod tests { #[test] fn test_tokenize_markup_symbols() { // Test markup tokens. - t!(Markup[" a1"]: "*" => Strong); - t!(Markup: "_" => Emph); + t!(Markup[" a1"]: "*" => Star); + t!(Markup: "_" => Underscore); t!(Markup[""]: "===" => Eq, Eq, Eq); t!(Markup["a1/"]: "= " => Eq, Space(0)); t!(Markup: "~" => NonBreakingSpace); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 13c639f98..560d7c30f 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -63,8 +63,6 @@ impl Markup { NodeKind::Space(_) => Some(MarkupNode::Space), NodeKind::Linebreak => Some(MarkupNode::Linebreak), NodeKind::Parbreak => Some(MarkupNode::Parbreak), - NodeKind::Strong => Some(MarkupNode::Strong), - NodeKind::Emph => Some(MarkupNode::Emph), NodeKind::Text(s) | NodeKind::TextInLine(s) => { Some(MarkupNode::Text(s.clone())) } @@ -72,8 +70,10 @@ impl Markup { NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())), NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())), NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())), - NodeKind::Math(math) => Some(MarkupNode::Math(math.as_ref().clone())), + NodeKind::Strong => node.cast().map(MarkupNode::Strong), + NodeKind::Emph => node.cast().map(MarkupNode::Emph), NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())), + NodeKind::Math(math) => Some(MarkupNode::Math(math.as_ref().clone())), NodeKind::Heading => node.cast().map(MarkupNode::Heading), NodeKind::List => node.cast().map(MarkupNode::List), NodeKind::Enum => node.cast().map(MarkupNode::Enum), @@ -91,12 +91,12 @@ pub enum MarkupNode { Linebreak, /// A paragraph break: Two or more newlines. Parbreak, - /// Strong text was enabled / disabled: `*`. - Strong, - /// Emphasized text was enabled / disabled: `_`. - Emph, /// Plain text. Text(EcoString), + /// Strong content: `*Strong*`. + Strong(StrongNode), + /// Emphasized content: `_Emphasized_`. + Emph(EmphNode), /// A raw block with optional syntax highlighting: `` `...` ``. Raw(RawNode), /// A math formula: `$a^2 = b^2 + c^2$`. @@ -111,6 +111,32 @@ pub enum MarkupNode { Expr(Expr), } +node! { + /// Strong content: `*Strong*`. + StrongNode: Strong +} + +impl StrongNode { + /// The contents of the strong node. + pub fn body(&self) -> Markup { + self.0.cast_first_child().expect("strong node is missing markup body") + } +} + +node! { + /// Emphasized content: `_Emphasized_`. + EmphNode: Emph +} + +impl EmphNode { + /// The contents of the emphasis node. + pub fn body(&self) -> Markup { + self.0 + .cast_first_child() + .expect("emphasis node is missing markup body") + } +} + /// A raw block with optional syntax highlighting: `` `...` ``. #[derive(Debug, Clone, PartialEq)] pub struct RawNode { diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 315e8f17e..b806b4e43 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -151,7 +151,10 @@ impl Category { NodeKind::From => Some(Category::Keyword), NodeKind::Include => Some(Category::Keyword), NodeKind::Plus => Some(Category::Operator), - NodeKind::Star => Some(Category::Operator), + NodeKind::Star => match parent.kind() { + NodeKind::Strong => None, + _ => Some(Category::Operator), + }, NodeKind::Slash => Some(Category::Operator), NodeKind::PlusEq => Some(Category::Operator), NodeKind::HyphEq => Some(Category::Operator), @@ -191,6 +194,7 @@ impl Category { NodeKind::Str(_) => Some(Category::String), NodeKind::Error(_, _) => Some(Category::Invalid), NodeKind::Unknown(_) => Some(Category::Invalid), + NodeKind::Underscore => None, NodeKind::Markup(_) => None, NodeKind::Space(_) => None, NodeKind::Parbreak => None, @@ -276,11 +280,7 @@ mod tests { assert_eq!(vec, goal); } - test("= *AB*", &[ - (0 .. 6, Heading), - (2 .. 3, Strong), - (5 .. 6, Strong), - ]); + test("= *AB*", &[(0 .. 6, Heading), (2 .. 6, Strong)]); test("#f(x + 1)", &[ (0 .. 2, Function), diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 9b606e0e4..fdd50a7a6 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -496,6 +496,8 @@ pub enum NodeKind { RightParen, /// An asterisk: `*`. Star, + /// An underscore: `_`. + Underscore, /// A comma: `,`. Comma, /// A semicolon: `;`. @@ -599,25 +601,25 @@ pub enum NodeKind { /// A slash and the letter "u" followed by a hexadecimal unicode entity /// enclosed in curly braces: `\u{1F5FA}`. Escape(char), - /// Strong text was enabled / disabled: `*`. + /// Strong content: `*Strong*`. Strong, - /// Emphasized text was enabled / disabled: `_`. + /// Emphasized content: `_Emphasized_`. Emph, + /// An arbitrary number of backticks followed by inner contents, terminated + /// with the same number of backticks: `` `...` ``. + Raw(Rc), + /// Dollar signs surrounding inner contents. + Math(Rc), /// A section heading: `= Introduction`. Heading, + /// An item in an unordered list: `- ...`. + List, /// An item in an enumeration (ordered list): `1. ...`. Enum, /// A numbering: `23.`. /// /// Can also exist without the number: `.`. EnumNumbering(Option), - /// An item in an unordered list: `- ...`. - List, - /// An arbitrary number of backticks followed by inner contents, terminated - /// with the same number of backticks: `` `...` ``. - Raw(Rc), - /// Dollar signs surrounding inner contents. - Math(Rc), /// An identifier: `center`. Ident(EcoString), /// A boolean: `true`, `false`. @@ -736,14 +738,14 @@ impl NodeKind { matches!(self, Self::LeftParen | Self::RightParen) } - /// Whether this is whitespace. - pub fn is_whitespace(&self) -> bool { - matches!(self, Self::Space(_) | Self::Parbreak) + /// Whether this is a space. + pub fn is_space(&self) -> bool { + matches!(self, Self::Space(_)) } /// Whether this is trivia. pub fn is_trivia(&self) -> bool { - self.is_whitespace() || matches!(self, Self::LineComment | Self::BlockComment) + self.is_space() || matches!(self, Self::LineComment | Self::BlockComment) } /// Whether this is some kind of error. @@ -761,7 +763,7 @@ impl NodeKind { } } - /// Which mode this token can appear in, in both if `None`. + /// Which mode this node can appear in, in both if `None`. pub fn mode(&self) -> Option { match self { Self::Markup(_) @@ -814,6 +816,7 @@ impl NodeKind { Self::LeftParen => "opening paren", Self::RightParen => "closing paren", Self::Star => "star", + Self::Underscore => "underscore", Self::Comma => "comma", Self::Semicolon => "semicolon", Self::Colon => "colon", @@ -864,14 +867,14 @@ impl NodeKind { Self::EnDash => "en dash", Self::EmDash => "em dash", Self::Escape(_) => "escape sequence", - Self::Strong => "strong", - Self::Emph => "emphasis", + Self::Strong => "strong content", + Self::Emph => "emphasized content", + Self::Raw(_) => "raw block", + Self::Math(_) => "math formula", + Self::List => "list item", Self::Heading => "heading", Self::Enum => "enumeration item", Self::EnumNumbering(_) => "enumeration item numbering", - Self::List => "list item", - Self::Raw(_) => "raw block", - Self::Math(_) => "math formula", Self::Ident(_) => "identifier", Self::Bool(_) => "boolean", Self::Int(_) => "integer", diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index e8110262f..07ab979b5 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -95,8 +95,8 @@ impl Pretty for MarkupNode { Self::Space => p.push(' '), Self::Linebreak => p.push_str(r"\"), Self::Parbreak => p.push_str("\n\n"), - Self::Strong => p.push('*'), - Self::Emph => p.push('_'), + Self::Strong(strong) => strong.pretty(p), + Self::Emph(emph) => emph.pretty(p), Self::Text(text) => p.push_str(text), Self::Raw(raw) => raw.pretty(p), Self::Math(math) => math.pretty(p), @@ -113,6 +113,22 @@ impl Pretty for MarkupNode { } } +impl Pretty for StrongNode { + fn pretty(&self, p: &mut Printer) { + p.push('*'); + self.body().pretty(p); + p.push('*'); + } +} + +impl Pretty for EmphNode { + fn pretty(&self, p: &mut Printer) { + p.push('_'); + self.body().pretty(p); + p.push('_'); + } +} + impl Pretty for RawNode { fn pretty(&self, p: &mut Printer) { // Find out how many backticks we need. @@ -604,12 +620,12 @@ mod tests { #[test] fn test_pretty_print_markup() { // Basic stuff. - roundtrip("*"); - roundtrip("_"); roundtrip(" "); + roundtrip("*ab*"); roundtrip("\\ "); roundtrip("\n\n"); roundtrip("hi"); + roundtrip("_ab_"); roundtrip("= *Ok*"); roundtrip("- Ok"); diff --git a/tests/ref/markup/emph-strong.png b/tests/ref/markup/emph-strong.png new file mode 100644 index 0000000000000000000000000000000000000000..cce98812ca2f0918d179ec7e17592ab0711a6294 GIT binary patch literal 6988 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 AK>z>% literal 0 HcmV?d00001 diff --git a/tests/ref/markup/emph.png b/tests/ref/markup/emph.png deleted file mode 100644 index 6b3bfb2dc385f372588d83fa6483e87a4d8126f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3772 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 diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png deleted file mode 100644 index 53062e9a1fee439372343eaa48f9e4eff00608bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3481 zcmaKvXHXMrv&Tb-Aq2#L5I}lpB2_WyL6KrYks>YhDkw_P0D^RaMhsYJQUsJPB!Li+ zP$dLV0Z}PR@5nhwsFDOYAo6nFd*3hj-nqXI&pf-c^X%-*fA-l;vbHig3OxY@002kL zOi{J~02k=+o&`BP08-7wQ-^*pGnAoy#Q5rDw37&j-t}{x<<0=2+9x_%oNhps=C0@Y z&hIR2yjZ*4YOB1~luUD?9hn8n8ZKanv8$+evn+F{^ z-NXxAXLVhBWfD=ka-Xn&DSkia#rmhp5p7nU^P9LQgrxt_b2L%kiT}Q#76jZbD+YSt z1^b^yqd3HGOGfjzkG!uHkw^;jp#X_VNq+GI;b%|^)y?t-aoD9vt86(N-{q&b5%KRs zRd0*pvr)gw`vkTJMH3XyB?Hvpylw6>A=O|9L05GbnGjU2I%OSrkR{Z z;OufmMc50wsFX1t>x~aTUZqS*T=W+dN2GgB2^KamD+g~R2h<=@$!h*ZmrqKUAP}oo z2DRPYzY+6IG8$9r2*ei6dS0DIj_!h3I1Ux^_CDh-?_fTz1cnNuLRsN+Zk$u zuW_A-Dp`xwCM`FP4QvrfP39+PhhUCyv8HBLIXd%(|CaZSOZyXy7$&&?iVnnk{Ohe^9 zIk;Pc6UuZjoF|{aU-Y)una=aFGVUv5(QK z*C45)ucH=!{@=1qgpXV5)w26GLi@KLb-a7Sn34)PDxlJN{zVSQ4 zT*7`!`@^rVCRWp=6v5kYBV^M5Bhd;s{M&yZ>$f)RaplFW>w$K8K>536$c%9e*eI7k zXr$#w$3Ci?0!%0_mDks6?`OxRuLcLB4XCE$nrJbPmJnFf`wt)Jn^cRpYbzBW^dA96 zMJIp6xkp^@(8V?zM^9aW*Z}yk7vfvT?BWEtP6oHxfL;kE;oBQd809v@{$eU)mpJ9G z-(n!tu%nCV;bm$;&KKn7geDY|?0Cz$Mz2M>ISK!?u;r~RKwLh4iFyUs@F85Pl?|oJ zp9!%WKVDQq2X^P{Rj?TFDa7!q^qiuQ;0rX>)Wx=KZGa-P8U8ts{Z}N;oj34l{%%o# ze0i4M{zpSc*5m8~bGmI4B$TZk*Vr3=HMPD6*dejb&LXrCN&e8o^cK;=2K041N=J3f zG9H=wNaJ`#u`c73Tua-be6K!oN>fQd%x8d!VQAF5XjNWHtC_NsLc0=zJAr)l7I}>( z5eKyBKS~dp3?_RTyR+EuG1Gi-K<(LQp-fAbEKXw{=9O7tbp&&$;r9p zt^jR>z=War=;k<|H?VZC))*+izq8Be?Lr!qubpKg+m^Vqc$u%JOh|Q5w4T7e54Z)L z)bv2lQg=ble4QwVP}*-(+=E(>(oHzkX1IN)dCZ_^sz_^ic?Qs`O7vlG{QOCbo{<;^ zbpWf5dBW$CJwa}DeH!k8m9ZW!&iD@iPx5p|e*UDTuY3(+0BcmL&W3M)UHF!k==L}j zs*Q$+Sa|gH_V%`{tl@c~SbMmUY0AIFT6C#jrLneJpseTROijYnZ89Jsm_re&CYJGe zuBhAI+n{ek@+zy$)M^l?Q#FJjy=dm$Alc0ras1gF`C6R1gUwYwIZL=f)!=|^l(fmH zr4eNNRYTDFbZRmzynFBN8!#29l9VCQ9PcdPU0}N0_-{0OWnona+k-#nmk}#zj&`>H zV3lcjbG#RcEF)yO-x27DdktFZ|O8Ovh zLSceg3!#-d#$1~xrM$s~BYu)U%Jx?~>rgMS?~3Cyn?<^L_jum*fMv^X1Jw80bb?p3lIl5=|ID>n0 zaQ!iM@brj($O_jGqtx!D6#9vp9;^)-ejZozRF@myZ5-en(iM?ZKh3Z8Cdo(1fp;FOqmdh_r%xsTsp{j^D>ODQpp-iD*mbKSN*&oM5BK_udeP} zt2+7_#@arDv=Noj=T`6XP~2e|k(spdRV4)VYkV+*n9VE95pIyYZF}m7+}16PR(~vffZ*yu^q}{oj(sd{PuV4Sa<^lXi zV?gb(pfOf}43~EQ+|JTL&ZRX%^-VY|?m#^OyUTk{@aL26{V-Rk$RD=jZ>dXB&sBt6 zZK{w0g(Sh9pOnF-D$N52d{w%wrQnUEvz}s$?4j#z98)j5Fmz#cxBqO*0oUw=Uv!;J z&2ic2wa(Rp@9O~7mtVMLwwBJXxdNtTCW!HDGr)Z3Wu+TVkYA1GEq<$RX<_G{wpp0d z;d7G`U;gfLj}Iz9-uTJu$SSEYTdqz#5)#%^4v-{#t~!N}Lu6EK-=tC!NhCUWW?BV- z;HKd0e2eo4J5m6IJ*srNi-8hM(+{hGS?!FzpHSusM^XlenU{R`tzIB(j=DB*Drn*- za|=JOvB;SqE$7$^I+DV0h`SNs$8md%39ygsUm~Zuq5?h>K+gNRM1HmSgQ$x&PG!bm zlOw`k&T4ML0)=^9PWQRy!Z%)8mhR5faIt6=+PPFXryNW}Y3vHO6rXhvjOQMx+rqKogv^Jr#YR7<%jL`{fytWkYDT5`pjliw0W%`(~P zs0W;6V(%n9?3b7opYV>TGlWykMmhqgxbiu?pizo>m^Ss^<={+7-rl!v+Cc#cM2hNr z)6qat!89d(oLy-ffj~d{@THsixjktk3`XLJD}-u;xMs!pscBk z!F7dHhwAsmVRo&%!0Z#zS6%mK_8)L_UCAA0Yu;ByE|id#jtd;+2yzZsa)4>*mi2)% znjtzB;Qzqp1U457NM!?XI(3n3Rp{O&J9JHRVJOSoTW65eO-=&L4(TBVPqVYbzbhZuP39 zzr!qpVIh@*fzR#FCabml%8?|WX!J#%to0ZIk=dcyJ{WgIyhfShqbr{aKki6lT4B@A zw%j5f%l|?a+WuA@glZ9KtdHu?tlfS7*QWB{J!>Y}>)L+^+5e?Kfap{lr;e;2u3P{! Lv=xeCZf2$vuBh8=O^Q66|nbQ4(n6a8W>E{>yKP`10XEiNg;!mm>E!&v-vh z-5mb)2tTj+vo4R5ikoZab}WgP{drbyg~y(>&DtFNGn?i6-2_hlx@hxPQ>6Y`DbvO? zm!?g7Hp}6mX7Q6puDaV+XmfBM72bK4E9wHXtgOC?c>NN5 zka{)spVEU{KR+JVy~I7`xqn$>v2(|&HHTk@Xv+LOwquX<0ohgGf3Frey~B22;M(+t zgtQY|&Wg6UyT&nkx$bhSw-PB~db%qA*Xj$({>RO^o=NVw#Jj9^oq~?gOi7+)ohA}2 zSLepY&gES-o$2VRz5lfKsLa1{+%7itp1Ad#T{6F}*w?tq9Q;$Q9JwlRP3Yf4Iye7o zH|#p_zU(jGPlgrOC;KVB5zypoKm0If;`&+^o@FcL4%e>oeRfX4;B{ew=LtRe8PQA$ zl?PMUEzDac9cfQqd!np6ATql8dFrZSw+q4-uI!$7d&rDmwbBhA;jXdzRsbLKJ=6WOHUT@ z9FTP8E0RCE6Kf*taA^52k+|abZTn@+PMdymK6}17J1BIsl>3FWvh07p{>RR|6aMG@ zu8dXj<{uUY_OxfV-yTMxWyt%aN5y&&TmV#IZi%PXRv4Q_Mg3X`x3c);hEu~ zTP=K_RCNm!zA5rBvuE6K{PpJQ{=K%B)Pk57nENle>)Mj?S=ivk+RxV&8XMAGv|?RA z%64z5)nVf6c(SX_;N72=j&J?04%1J#vMJs^=g+aTX+lmNQ-G=TNdXm+Z4h3;)kzyU z4%AJwxvtk<_*{KWgn<}ig;&bfY>vn0{8yarINw`QSAE9)#XgrOvDXET&7EHmAboaX z^Gx&G#){h)Jv!Cku`}D;TlkFsii@}7Zbwhwm%#8Zce6+4epijmEu~hCAXnUIcDyP% zyY0_U&U;U@%Of|+|6$}>n+J@5E9H;gq}adKH#u~La0L7 (bool, bool, Vec>) { + let mut ok = true; + let id = ctx.sources.provide(src_path, src); let source = ctx.sources.get(id); if debug { @@ -267,7 +269,8 @@ fn test_part( let (local_compare_ref, mut ref_errors) = parse_metadata(&source); let compare_ref = local_compare_ref.unwrap_or(compare_ref); - let mut ok = test_reparse(ctx.sources.get(id).src(), i, rng); + + ok &= test_reparse(ctx.sources.get(id).src(), i, rng); let (frames, mut errors) = match ctx.evaluate(id) { Ok(module) => { diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 78809b4e4..577804fc2 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -44,15 +44,15 @@ }, { "name": "markup.bold.typst", - "begin": "\\*", - "end": "\\*|(?=\\])", + "begin": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))", + "end": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))|\n|(?=\\])", "captures": { "0": { "name": "punctuation.definition.bold.typst" } }, "patterns": [{ "include": "#markup" }] }, { "name": "markup.italic.typst", - "begin": "_", - "end": "_|(?=\\])", + "begin": "(^_|_$|((?<=\\W|_)_)|(_(?=\\W|_)))", + "end": "(^_|_$|((?<=\\W|_)_)|(_(?=\\W|_)))|\n|(?=\\])", "captures": { "0": { "name": "punctuation.definition.italic.typst" } }, "patterns": [{ "include": "#markup" }] },