Introduce NodeKind::Quote

This commit is contained in:
Martin Haug 2022-04-12 13:00:34 +02:00 committed by Laurenz
parent c3a387b8f7
commit 072543fc59
8 changed files with 36 additions and 7 deletions

View File

@ -45,6 +45,8 @@ pub enum Content {
Horizontal(Spacing), Horizontal(Spacing),
/// Plain text. /// Plain text.
Text(EcoString), Text(EcoString),
/// A smart quote, may be single (`false`) or double (`true`).
Quote(bool),
/// An inline-level node. /// An inline-level node.
Inline(LayoutNode), Inline(LayoutNode),
/// A paragraph break. /// A paragraph break.
@ -214,6 +216,7 @@ impl Debug for Content {
Self::Linebreak => f.pad("Linebreak"), Self::Linebreak => f.pad("Linebreak"),
Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"),
Self::Text(text) => write!(f, "Text({text:?})"), Self::Text(text) => write!(f, "Text({text:?})"),
Self::Quote(double) => write!(f, "Quote({double:?})"),
Self::Inline(node) => { Self::Inline(node) => {
f.write_str("Inline(")?; f.write_str("Inline(")?;
node.fmt(f)?; node.fmt(f)?;
@ -384,6 +387,9 @@ impl<'a> Builder<'a> {
self.par.ignorant(child, styles); self.par.ignorant(child, styles);
} }
} }
Content::Quote(double) => {
self.par.supportive(ParChild::Quote(*double), styles);
}
Content::Text(text) => { Content::Text(text) => {
self.par.supportive(ParChild::Text(text.clone()), styles); self.par.supportive(ParChild::Text(text.clone()), styles);
} }
@ -496,7 +502,7 @@ impl<'a> Builder<'a> {
.items() .items()
.find_map(|child| match child { .find_map(|child| match child {
ParChild::Spacing(_) => None, ParChild::Spacing(_) => None,
ParChild::Text(_) => Some(true), ParChild::Text(_) | ParChild::Quote(_) => Some(true),
ParChild::Node(_) => Some(false), ParChild::Node(_) => Some(false),
}) })
.unwrap_or_default() .unwrap_or_default()

View File

@ -111,6 +111,7 @@ impl Eval for MarkupNode {
Self::Linebreak => Content::Linebreak, Self::Linebreak => Content::Linebreak,
Self::Parbreak => Content::Parbreak, Self::Parbreak => Content::Parbreak,
Self::Text(text) => Content::Text(text.clone()), Self::Text(text) => Content::Text(text.clone()),
Self::Quote(double) => Content::Quote(*double),
Self::Strong(strong) => strong.eval(ctx, scp)?, Self::Strong(strong) => strong.eval(ctx, scp)?,
Self::Emph(emph) => emph.eval(ctx, scp)?, Self::Emph(emph) => emph.eval(ctx, scp)?,
Self::Raw(raw) => raw.eval(ctx, scp)?, Self::Raw(raw) => raw.eval(ctx, scp)?,

View File

@ -19,6 +19,8 @@ pub struct ParNode(pub StyleVec<ParChild>);
pub enum ParChild { pub enum ParChild {
/// A chunk of text. /// A chunk of text.
Text(EcoString), Text(EcoString),
/// A smart quote, may be single (`false`) or double (`true`).
Quote(bool),
/// Horizontal spacing between other children. /// Horizontal spacing between other children.
Spacing(Spacing), Spacing(Spacing),
/// An arbitrary inline-level node. /// An arbitrary inline-level node.
@ -89,6 +91,7 @@ impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Text(text) => write!(f, "Text({:?})", text), Self::Text(text) => write!(f, "Text({:?})", text),
Self::Quote(double) => write!(f, "Quote({})", double),
Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f), Self::Node(node) => node.fmt(f),
} }
@ -397,6 +400,11 @@ fn collect<'a>(
} }
Segment::Text(full.len() - prev) 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) => { ParChild::Spacing(spacing) => {
full.push(SPACING_REPLACE); full.push(SPACING_REPLACE);
Segment::Spacing(*spacing) Segment::Spacing(*spacing)

View File

@ -215,6 +215,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
| NodeKind::NonBreakingSpace | NodeKind::NonBreakingSpace
| NodeKind::EnDash | NodeKind::EnDash
| NodeKind::EmDash | NodeKind::EmDash
| NodeKind::Quote(_)
| NodeKind::Linebreak | NodeKind::Linebreak
| NodeKind::Raw(_) | NodeKind::Raw(_)
| NodeKind::Math(_) | NodeKind::Math(_)

View File

@ -143,11 +143,13 @@ impl<'s> Tokens<'s> {
// Markup. // Markup.
'~' => NodeKind::NonBreakingSpace, '~' => NodeKind::NonBreakingSpace,
'-' => self.hyph(),
'\'' => NodeKind::Quote(false),
'"' => NodeKind::Quote(true),
'*' if !self.in_word() => NodeKind::Star, '*' if !self.in_word() => NodeKind::Star,
'_' if !self.in_word() => NodeKind::Underscore, '_' if !self.in_word() => NodeKind::Underscore,
'`' => self.raw(), '`' => self.raw(),
'$' => self.math(), '$' => self.math(),
'-' => self.hyph(),
'=' => NodeKind::Eq, '=' => NodeKind::Eq,
c if c == '.' || c.is_ascii_digit() => self.numbering(start, c), c if c == '.' || c.is_ascii_digit() => self.numbering(start, c),
@ -220,7 +222,7 @@ impl<'s> Tokens<'s> {
// Comments, parentheses, code. // Comments, parentheses, code.
'/' | '[' | ']' | '{' | '}' | '#' | '/' | '[' | ']' | '{' | '}' | '#' |
// Markup // Markup
'~' | '*' | '_' | '`' | '$' | '-' | '\\' '~' | '\'' | '"' | '*' | '_' | '`' | '$' | '-' | '\\'
}; };
loop { loop {
@ -269,8 +271,8 @@ impl<'s> Tokens<'s> {
// Parenthesis and hashtag. // Parenthesis and hashtag.
'[' | ']' | '{' | '}' | '#' | '[' | ']' | '{' | '}' | '#' |
// Markup. // Markup.
'~' | '*' | '_' | '`' | '$' | '=' | '-' | '.' => { '~' | '\'' | '"' | '*' | '_' | '`' | '$' | '=' | '-' | '.' => {
self.s.eat_assert(c); self.s.eat_assert(c) ;
NodeKind::Escape(c) NodeKind::Escape(c)
} }
'u' if self.s.rest().starts_with("u{") => { 'u' if self.s.rest().starts_with("u{") => {
@ -789,7 +791,7 @@ mod tests {
t!(Markup[" /"]: "hello-world" => Text("hello"), Minus, Text("world")); t!(Markup[" /"]: "hello-world" => Text("hello"), Minus, Text("world"));
// Test code symbols in text. // 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[" /"]: ";:,|/+" => Text(";:,|"), Text("/+"));
t!(Markup[" /"]: "=-a" => Eq, Minus, Text("a")); t!(Markup[" /"]: "=-a" => Eq, Minus, Text("a"));
t!(Markup[" "]: "#123" => Text("#"), Text("123")); 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('"'));
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"\a" => Text(r"\"), Text("a"));
t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u")); t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u"));
t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1")); t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1"));
t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\""));
// Test basic unicode escapes. // Test basic unicode escapes.
t!(Markup: r"\u{}" => Error(Full, "invalid unicode escape sequence")); t!(Markup: r"\u{}" => Error(Full, "invalid unicode escape sequence"));

View File

@ -68,6 +68,7 @@ impl Markup {
NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())), NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())),
NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())), NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())),
NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())), NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())),
NodeKind::Quote(d) => Some(MarkupNode::Quote(*d)),
NodeKind::Strong => node.cast().map(MarkupNode::Strong), NodeKind::Strong => node.cast().map(MarkupNode::Strong),
NodeKind::Emph => node.cast().map(MarkupNode::Emph), NodeKind::Emph => node.cast().map(MarkupNode::Emph),
NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())), NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())),
@ -91,6 +92,8 @@ pub enum MarkupNode {
Parbreak, Parbreak,
/// Plain text. /// Plain text.
Text(EcoString), Text(EcoString),
/// A smart quote: `'` (`false`) or `"` (true).
Quote(bool),
/// Strong content: `*Strong*`. /// Strong content: `*Strong*`.
Strong(StrongNode), Strong(StrongNode),
/// Emphasized content: `_Emphasized_`. /// Emphasized content: `_Emphasized_`.

View File

@ -130,6 +130,7 @@ impl Category {
NodeKind::NonBreakingSpace => Some(Category::Shortcut), NodeKind::NonBreakingSpace => Some(Category::Shortcut),
NodeKind::EnDash => Some(Category::Shortcut), NodeKind::EnDash => Some(Category::Shortcut),
NodeKind::EmDash => Some(Category::Shortcut), NodeKind::EmDash => Some(Category::Shortcut),
NodeKind::Quote(_) => Some(Category::Shortcut),
NodeKind::Escape(_) => Some(Category::Escape), NodeKind::Escape(_) => Some(Category::Escape),
NodeKind::Not => Some(Category::Keyword), NodeKind::Not => Some(Category::Keyword),
NodeKind::And => Some(Category::Keyword), NodeKind::And => Some(Category::Keyword),

View File

@ -596,6 +596,8 @@ pub enum NodeKind {
EnDash, EnDash,
/// An em-dash: `---`. /// An em-dash: `---`.
EmDash, EmDash,
/// A smart quote: `'` (`false`) or `"` (true).
Quote(bool),
/// A slash and the letter "u" followed by a hexadecimal unicode entity /// A slash and the letter "u" followed by a hexadecimal unicode entity
/// enclosed in curly braces: `\u{1F5FA}`. /// enclosed in curly braces: `\u{1F5FA}`.
Escape(char), Escape(char),
@ -769,6 +771,7 @@ impl NodeKind {
| Self::NonBreakingSpace | Self::NonBreakingSpace
| Self::EnDash | Self::EnDash
| Self::EmDash | Self::EmDash
| Self::Quote(_)
| Self::Escape(_) | Self::Escape(_)
| Self::Strong | Self::Strong
| Self::Emph | Self::Emph
@ -861,6 +864,8 @@ impl NodeKind {
Self::NonBreakingSpace => "non-breaking space", Self::NonBreakingSpace => "non-breaking space",
Self::EnDash => "en dash", Self::EnDash => "en dash",
Self::EmDash => "em dash", Self::EmDash => "em dash",
Self::Quote(false) => "single quote",
Self::Quote(true) => "double quote",
Self::Escape(_) => "escape sequence", Self::Escape(_) => "escape sequence",
Self::Strong => "strong content", Self::Strong => "strong content",
Self::Emph => "emphasized content", Self::Emph => "emphasized content",
@ -981,6 +986,7 @@ impl Hash for NodeKind {
Self::NonBreakingSpace => {} Self::NonBreakingSpace => {}
Self::EnDash => {} Self::EnDash => {}
Self::EmDash => {} Self::EmDash => {}
Self::Quote(d) => d.hash(state),
Self::Escape(c) => c.hash(state), Self::Escape(c) => c.hash(state),
Self::Strong => {} Self::Strong => {}
Self::Emph => {} Self::Emph => {}