From 072543fc59582eacfd9446055639c4427e707941 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Tue, 12 Apr 2022 13:00:34 +0200 Subject: [PATCH] Introduce `NodeKind::Quote` --- src/eval/content.rs | 8 +++++++- src/eval/mod.rs | 1 + src/library/text/par.rs | 8 ++++++++ src/parse/mod.rs | 1 + src/parse/tokens.rs | 15 +++++++++------ src/syntax/ast.rs | 3 +++ src/syntax/highlight.rs | 1 + src/syntax/mod.rs | 6 ++++++ 8 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/eval/content.rs b/src/eval/content.rs index d9685891a..274b64b0c 100644 --- a/src/eval/content.rs +++ b/src/eval/content.rs @@ -45,6 +45,8 @@ pub enum Content { Horizontal(Spacing), /// Plain text. Text(EcoString), + /// A smart quote, may be single (`false`) or double (`true`). + Quote(bool), /// An inline-level node. Inline(LayoutNode), /// A paragraph break. @@ -214,6 +216,7 @@ impl Debug for Content { Self::Linebreak => f.pad("Linebreak"), Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), Self::Text(text) => write!(f, "Text({text:?})"), + Self::Quote(double) => write!(f, "Quote({double:?})"), Self::Inline(node) => { f.write_str("Inline(")?; node.fmt(f)?; @@ -384,6 +387,9 @@ impl<'a> Builder<'a> { self.par.ignorant(child, styles); } } + Content::Quote(double) => { + self.par.supportive(ParChild::Quote(*double), styles); + } Content::Text(text) => { self.par.supportive(ParChild::Text(text.clone()), styles); } @@ -496,7 +502,7 @@ impl<'a> Builder<'a> { .items() .find_map(|child| match child { ParChild::Spacing(_) => None, - ParChild::Text(_) => Some(true), + ParChild::Text(_) | ParChild::Quote(_) => Some(true), ParChild::Node(_) => Some(false), }) .unwrap_or_default() diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 9e5a85550..f2c03c0f3 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -111,6 +111,7 @@ impl Eval for MarkupNode { Self::Linebreak => Content::Linebreak, Self::Parbreak => Content::Parbreak, Self::Text(text) => Content::Text(text.clone()), + Self::Quote(double) => Content::Quote(*double), Self::Strong(strong) => strong.eval(ctx, scp)?, Self::Emph(emph) => emph.eval(ctx, scp)?, Self::Raw(raw) => raw.eval(ctx, scp)?, diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 6eb3da66d..65bc52243 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -19,6 +19,8 @@ pub struct ParNode(pub StyleVec); pub enum ParChild { /// A chunk of text. Text(EcoString), + /// A smart quote, may be single (`false`) or double (`true`). + Quote(bool), /// Horizontal spacing between other children. Spacing(Spacing), /// An arbitrary inline-level node. @@ -89,6 +91,7 @@ impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Text(text) => write!(f, "Text({:?})", text), + Self::Quote(double) => write!(f, "Quote({})", double), Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } @@ -397,6 +400,11 @@ fn collect<'a>( } Segment::Text(full.len() - prev) } + ParChild::Quote(double) => { + let prev = full.len(); + full.push(if *double { '"' } else { '\'' }); + Segment::Text(full.len() - prev) + } ParChild::Spacing(spacing) => { full.push(SPACING_REPLACE); Segment::Spacing(*spacing) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6d1985e03..92e864507 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -215,6 +215,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | NodeKind::NonBreakingSpace | NodeKind::EnDash | NodeKind::EmDash + | NodeKind::Quote(_) | NodeKind::Linebreak | NodeKind::Raw(_) | NodeKind::Math(_) diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 40ea134e5..a98ef2649 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -143,11 +143,13 @@ impl<'s> Tokens<'s> { // Markup. '~' => NodeKind::NonBreakingSpace, + '-' => self.hyph(), + '\'' => NodeKind::Quote(false), + '"' => NodeKind::Quote(true), '*' if !self.in_word() => NodeKind::Star, '_' if !self.in_word() => NodeKind::Underscore, '`' => self.raw(), '$' => self.math(), - '-' => self.hyph(), '=' => NodeKind::Eq, c if c == '.' || c.is_ascii_digit() => self.numbering(start, c), @@ -220,7 +222,7 @@ impl<'s> Tokens<'s> { // Comments, parentheses, code. '/' | '[' | ']' | '{' | '}' | '#' | // Markup - '~' | '*' | '_' | '`' | '$' | '-' | '\\' + '~' | '\'' | '"' | '*' | '_' | '`' | '$' | '-' | '\\' }; loop { @@ -269,8 +271,8 @@ impl<'s> Tokens<'s> { // Parenthesis and hashtag. '[' | ']' | '{' | '}' | '#' | // Markup. - '~' | '*' | '_' | '`' | '$' | '=' | '-' | '.' => { - self.s.eat_assert(c); + '~' | '\'' | '"' | '*' | '_' | '`' | '$' | '=' | '-' | '.' => { + self.s.eat_assert(c) ; NodeKind::Escape(c) } 'u' if self.s.rest().starts_with("u{") => { @@ -789,7 +791,7 @@ mod tests { t!(Markup[" /"]: "hello-world" => Text("hello"), Minus, Text("world")); // Test code symbols in text. - t!(Markup[" /"]: "a():\"b" => Text("a():\"b")); + t!(Markup[" /"]: "a():\"b" => Text("a():"), Quote(true), Text("b")); t!(Markup[" /"]: ";:,|/+" => Text(";:,|"), Text("/+")); t!(Markup[" /"]: "=-a" => Eq, Minus, Text("a")); t!(Markup[" "]: "#123" => Text("#"), Text("123")); @@ -812,6 +814,8 @@ mod tests { t!(Markup: r"\_" => Escape('_')); t!(Markup: r"\=" => Escape('=')); t!(Markup: r"\~" => Escape('~')); + t!(Markup: r"\'" => Escape('\'')); + t!(Markup: r#"\""# => Escape('"')); t!(Markup: r"\`" => Escape('`')); t!(Markup: r"\$" => Escape('$')); t!(Markup: r"\#" => Escape('#')); @@ -820,7 +824,6 @@ mod tests { t!(Markup[" /"]: r"\a" => Text(r"\"), Text("a")); t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u")); t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1")); - t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\"")); // Test basic unicode escapes. t!(Markup: r"\u{}" => Error(Full, "invalid unicode escape sequence")); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 1318852df..d629b1fdc 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -68,6 +68,7 @@ impl Markup { NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())), NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())), NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())), + NodeKind::Quote(d) => Some(MarkupNode::Quote(*d)), NodeKind::Strong => node.cast().map(MarkupNode::Strong), NodeKind::Emph => node.cast().map(MarkupNode::Emph), NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())), @@ -91,6 +92,8 @@ pub enum MarkupNode { Parbreak, /// Plain text. Text(EcoString), + /// A smart quote: `'` (`false`) or `"` (true). + Quote(bool), /// Strong content: `*Strong*`. Strong(StrongNode), /// Emphasized content: `_Emphasized_`. diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 25b458efb..1b884c6e4 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -130,6 +130,7 @@ impl Category { NodeKind::NonBreakingSpace => Some(Category::Shortcut), NodeKind::EnDash => Some(Category::Shortcut), NodeKind::EmDash => Some(Category::Shortcut), + NodeKind::Quote(_) => Some(Category::Shortcut), NodeKind::Escape(_) => Some(Category::Escape), NodeKind::Not => Some(Category::Keyword), NodeKind::And => Some(Category::Keyword), diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 5857940c7..f0d3cdd4a 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -596,6 +596,8 @@ pub enum NodeKind { EnDash, /// An em-dash: `---`. EmDash, + /// A smart quote: `'` (`false`) or `"` (true). + Quote(bool), /// A slash and the letter "u" followed by a hexadecimal unicode entity /// enclosed in curly braces: `\u{1F5FA}`. Escape(char), @@ -769,6 +771,7 @@ impl NodeKind { | Self::NonBreakingSpace | Self::EnDash | Self::EmDash + | Self::Quote(_) | Self::Escape(_) | Self::Strong | Self::Emph @@ -861,6 +864,8 @@ impl NodeKind { Self::NonBreakingSpace => "non-breaking space", Self::EnDash => "en dash", Self::EmDash => "em dash", + Self::Quote(false) => "single quote", + Self::Quote(true) => "double quote", Self::Escape(_) => "escape sequence", Self::Strong => "strong content", Self::Emph => "emphasized content", @@ -981,6 +986,7 @@ impl Hash for NodeKind { Self::NonBreakingSpace => {} Self::EnDash => {} Self::EmDash => {} + Self::Quote(d) => d.hash(state), Self::Escape(c) => c.hash(state), Self::Strong => {} Self::Emph => {}