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 000000000..cce98812c Binary files /dev/null and b/tests/ref/markup/emph-strong.png differ diff --git a/tests/ref/markup/emph.png b/tests/ref/markup/emph.png deleted file mode 100644 index 6b3bfb2dc..000000000 Binary files a/tests/ref/markup/emph.png and /dev/null differ diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png deleted file mode 100644 index 53062e9a1..000000000 Binary files a/tests/ref/markup/strong.png and /dev/null differ diff --git a/tests/ref/style/set-toggle.png b/tests/ref/style/set-toggle.png deleted file mode 100644 index daaa3d6c0..000000000 Binary files a/tests/ref/style/set-toggle.png and /dev/null differ diff --git a/tests/typ/code/ops.typ b/tests/typ/code/ops.typ index be2cdb488..e3e2e8556 100644 --- a/tests/typ/code/ops.typ +++ b/tests/typ/code/ops.typ @@ -4,7 +4,7 @@ --- // Test template addition. // Ref: true -{[*Hello ] + [world!]} +{[*Hello* ] + [world!]} --- // Test math operators. diff --git a/tests/typ/markup/emph-strong.typ b/tests/typ/markup/emph-strong.typ new file mode 100644 index 000000000..9bdb50590 --- /dev/null +++ b/tests/typ/markup/emph-strong.typ @@ -0,0 +1,33 @@ +// Test emph and strong. + +--- +// Basic. +_Emphasized and *strong* words!_ + +// Inside of a word it's a normal underscore or star. +hello_world Nutzer*innen + +// Can contain paragraph in child template. +_Still [ + +] emphasized._ + +--- +// Inside of words can still use the functions. +P#strong[art]ly em#emph[phas]ized. + +--- +// Error: 13 expected underscore +#box[_Scoped] to body. + +--- +// Ends at paragraph break. +// Error: 7 expected underscore +_Hello + +World + +--- +// Error: 1:12 expected star +// Error: 2:1 expected star +_Cannot *be_ interleaved* diff --git a/tests/typ/markup/emph.typ b/tests/typ/markup/emph.typ deleted file mode 100644 index 77ac2c72b..000000000 --- a/tests/typ/markup/emph.typ +++ /dev/null @@ -1,11 +0,0 @@ -// Test emphasis toggle. - ---- -// Basic. -_Emphasized!_ - -// Inside of words. -Partly em_phas_ized. - -// Scoped to body. -#box[_Scoped] to body. diff --git a/tests/typ/markup/escape.typ b/tests/typ/markup/escape.typ index c039e0b77..87d2b83ad 100644 --- a/tests/typ/markup/escape.typ +++ b/tests/typ/markup/escape.typ @@ -32,4 +32,4 @@ let f() , ; : | + - /= == 12 "string" --- // Unterminated. // Error: 6 expected closing brace -\u{41*Bold* +\u{41[*Bold*] diff --git a/tests/typ/markup/strong.typ b/tests/typ/markup/strong.typ deleted file mode 100644 index d396cc2d0..000000000 --- a/tests/typ/markup/strong.typ +++ /dev/null @@ -1,11 +0,0 @@ -// Test strong toggle. - ---- -// Basic. -*Strong!* - -// Inside of words. -Partly str*ength*ened. - -// Scoped to body. -#box[*Scoped] to body. diff --git a/tests/typ/style/set-toggle.typ b/tests/typ/style/set-toggle.typ deleted file mode 100644 index 9f26bdf7e..000000000 --- a/tests/typ/style/set-toggle.typ +++ /dev/null @@ -1,10 +0,0 @@ -// Test set rules for toggleable booleans. - ---- -// Test toggling and untoggling. -*AB_C*DE -*_* - ---- -// Test toggling and nested templates. -*A[B*[_C]]D*E diff --git a/tests/typ/style/wrap.typ b/tests/typ/style/wrap.typ index 1ff3cc95e..2fa7716e4 100644 --- a/tests/typ/style/wrap.typ +++ b/tests/typ/style/wrap.typ @@ -16,7 +16,7 @@ in booklovers and the great fulfiller of human need. --- // Test wrap in template. -A [_B #wrap c in [*#c*]; C] D +A [_B #wrap c in [*#c*]; C_] D --- // Test wrap style precedence. diff --git a/tests/typ/text/bidi.typ b/tests/typ/text/bidi.typ index 658c7fa6f..09e235586 100644 --- a/tests/typ/text/bidi.typ +++ b/tests/typ/text/bidi.typ @@ -10,7 +10,7 @@ --- // Test that consecutive, embedded LTR runs stay LTR. // Here, we have two runs: "A" and italic "B". -#let content = [أنت A_B_مطرC] +#let content = [أنت A#emph[B]مطرC] #set text(serif, "Noto Sans Arabic") #par(lang: "ar", content) #par(lang: "de", content) @@ -18,7 +18,7 @@ --- // Test that consecutive, embedded RTL runs stay RTL. // Here, we have three runs: "גֶ", bold "שֶׁ", and "ם". -#let content = [Aגֶ*שֶׁ*םB] +#let content = [Aגֶ#strong[שֶׁ]םB] #set text(serif, "Noto Serif Hebrew") #par(lang: "he", content) #par(lang: "de", content) diff --git a/tests/typ/text/linebreaks.typ b/tests/typ/text/linebreaks.typ index 5f886381c..de99f5ed1 100644 --- a/tests/typ/text/linebreaks.typ +++ b/tests/typ/text/linebreaks.typ @@ -10,7 +10,7 @@ Supercalifragilisticexpialidocious Expialigoricmetrioxidation. --- // Test that there are no unwanted line break opportunities on run change. -This is partly emp_has_ized. +This is partly emp#emph[has]ized. --- Hard \ break. diff --git a/tests/typeset.rs b/tests/typeset.rs index 5437860b2..647499750 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -259,6 +259,8 @@ fn test_part( debug: bool, rng: &mut LinearShift, ) -> (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" }] },