summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-01-30 12:50:58 +0100
committerLaurenz <laurmaedje@gmail.com>2022-01-30 22:46:59 +0100
commit8d1ce390e21ce0a5812a4211c893ec359906d6f1 (patch)
tree5dbe1ad96af8ce8f9f01887340fe06025462e959
parentd7072f378fef733ae994fd9a1e767df4e4dd878e (diff)
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
-rw-r--r--src/eval/mod.rs26
-rw-r--r--src/eval/styles.rs13
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/text.rs20
-rw-r--r--src/parse/incremental.rs8
-rw-r--r--src/parse/mod.rs39
-rw-r--r--src/parse/parser.rs84
-rw-r--r--src/parse/tokens.rs17
-rw-r--r--src/syntax/ast.rs40
-rw-r--r--src/syntax/highlight.rs12
-rw-r--r--src/syntax/mod.rs41
-rw-r--r--src/syntax/pretty.rs24
-rw-r--r--tests/ref/markup/emph-strong.pngbin0 -> 6988 bytes
-rw-r--r--tests/ref/markup/emph.pngbin3772 -> 0 bytes
-rw-r--r--tests/ref/markup/strong.pngbin3481 -> 0 bytes
-rw-r--r--tests/ref/style/set-toggle.pngbin1188 -> 0 bytes
-rw-r--r--tests/typ/code/ops.typ2
-rw-r--r--tests/typ/markup/emph-strong.typ33
-rw-r--r--tests/typ/markup/emph.typ11
-rw-r--r--tests/typ/markup/escape.typ2
-rw-r--r--tests/typ/markup/strong.typ11
-rw-r--r--tests/typ/style/set-toggle.typ10
-rw-r--r--tests/typ/style/wrap.typ2
-rw-r--r--tests/typ/text/bidi.typ4
-rw-r--r--tests/typ/text/linebreaks.typ2
-rw-r--r--tests/typeset.rs5
-rw-r--r--tools/support/typst.tmLanguage.json8
27 files changed, 268 insertions, 148 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 2fa07d49..a453a357 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<Self::Output> {
+ Ok(self.body().eval(ctx)?.styled(TextNode::STRONG, true))
+ }
+}
+
+impl Eval for EmphNode {
+ type Output = Node;
+
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ 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 bdc01f1f..508996a1 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<P: Property<Value = bool>>(&mut self, key: P) {
- for (i, entry) in self.0.iter_mut().enumerate() {
- if entry.is::<P>() {
- 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 44f8f947..3115cc7a 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -93,6 +93,8 @@ pub fn new() -> Scope {
std.def_class::<ParbreakNode>("parbreak");
std.def_class::<LinebreakNode>("linebreak");
std.def_class::<TextNode>("text");
+ std.def_class::<StrongNode>("strong");
+ std.def_class::<EmphNode>("emph");
std.def_class::<DecoNode<Underline>>("underline");
std.def_class::<DecoNode<Strikethrough>>("strike");
std.def_class::<DecoNode<Overline>>("overline");
diff --git a/src/library/text.rs b/src/library/text.rs
index b8810ac6..6d7be323 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<Node> {
+ Ok(args.expect::<Node>("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<Node> {
+ Ok(args.expect::<Node>("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 9dd5bec1..fb927c24 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 a9839ed6..b8ef3066 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<GreenNode> {
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<Green>, 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 0184c198..db003e72 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<T = ()> = Result<T, ParseError>;
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index eef7a72d..d741dea1 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<char>| 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 13c639f9..560d7c30 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 315e8f17..b806b4e4 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 9b606e0e..fdd50a7a 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<RawNode>),
+ /// Dollar signs surrounding inner contents.
+ Math(Rc<MathNode>),
/// 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<usize>),
- /// 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<RawNode>),
- /// Dollar signs surrounding inner contents.
- Math(Rc<MathNode>),
/// 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<TokenMode> {
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 e8110262..07ab979b 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 00000000..cce98812
--- /dev/null
+++ b/tests/ref/markup/emph-strong.png
Binary files differ
diff --git a/tests/ref/markup/emph.png b/tests/ref/markup/emph.png
deleted file mode 100644
index 6b3bfb2d..00000000
--- a/tests/ref/markup/emph.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png
deleted file mode 100644
index 53062e9a..00000000
--- a/tests/ref/markup/strong.png
+++ /dev/null
Binary files differ
diff --git a/tests/ref/style/set-toggle.png b/tests/ref/style/set-toggle.png
deleted file mode 100644
index daaa3d6c..00000000
--- a/tests/ref/style/set-toggle.png
+++ /dev/null
Binary files differ
diff --git a/tests/typ/code/ops.typ b/tests/typ/code/ops.typ
index be2cdb48..e3e2e855 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 00000000..9bdb5059
--- /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 77ac2c72..00000000
--- 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 c039e0b7..87d2b83a 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 d396cc2d..00000000
--- 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 9f26bdf7..00000000
--- 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 1ff3cc95..2fa7716e 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 658c7fa6..09e23558 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 5f886381..de99f5ed 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 5437860b..64749975 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -259,6 +259,8 @@ fn test_part(
debug: bool,
rng: &mut LinearShift,
) -> (bool, bool, Vec<Rc<Frame>>) {
+ 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 78809b4e..577804fc 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" }]
},