Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a multiline blockquote extension #359

Merged
merged 5 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions script/cibuild
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ if [ x"$SPEC" = "xtrue" ]; then
# python3 roundtrip_tests.py --spec extensions-table-prefer-style-attributes.txt "$PROGRAM_ARG --table-prefer-style-attributes" --extensions "table strikethrough autolink tagfilter footnotes tasklist" || failed=1
python3 roundtrip_tests.py --spec extensions-full-info-string.txt "$PROGRAM_ARG --full-info-string" \
|| failed=1
python3 spec_tests.py --no-normalize --spec ../../../src/tests/fixtures/multiline_blockquote.txt "$PROGRAM_ARG" \
|| failed=1

python3 spec_tests.py --no-normalize --spec regression.txt "$PROGRAM_ARG" \
|| failed=1
Expand Down
1 change: 1 addition & 0 deletions src/cm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
NodeValue::FootnoteReference(ref nfr) => {
self.format_footnote_reference(nfr.name.as_bytes(), entering)
}
NodeValue::MultilineBlockQuote(..) => self.format_block_quote(entering),
digitalmoksha marked this conversation as resolved.
Show resolved Hide resolved
};
true
}
Expand Down
11 changes: 11 additions & 0 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,17 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b"</li>\n")?;
}
}
NodeValue::MultilineBlockQuote(_) => {
if entering {
self.cr()?;
self.output.write_all(b"<blockquote")?;
self.render_sourcepos(node)?;
self.output.write_all(b">\n")?;
} else {
self.cr()?;
self.output.write_all(b"</blockquote>\n")?;
}
}
}
Ok(false)
}
Expand Down
21 changes: 21 additions & 0 deletions src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::convert::TryFrom;
#[cfg(feature = "shortcodes")]
use crate::parser::shortcodes::NodeShortCode;

use crate::parser::multiline_block_quote::NodeMultilineBlockQuote;

/// The core AST node enum.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeValue {
Expand Down Expand Up @@ -151,6 +153,19 @@ pub enum NodeValue {
#[cfg(feature = "shortcodes")]
/// **Inline**. An Emoji character generated from a shortcode. Enable with feature "shortcodes".
ShortCode(NodeShortCode),

/// **Block**. A [multiline block quote](https://github.github.com/gfm/#block-quotes). Spans multiple
/// lines and contains other **blocks**.
///
/// ``` md
/// >>>
/// A paragraph.
///
/// - item one
/// - item two
/// >>>
/// ```
MultilineBlockQuote(NodeMultilineBlockQuote),
}

/// Alignment of a single table cell.
Expand Down Expand Up @@ -391,6 +406,7 @@ impl NodeValue {
| NodeValue::TableRow(..)
| NodeValue::TableCell
| NodeValue::TaskItem(..)
| NodeValue::MultilineBlockQuote(_)
)
}

Expand Down Expand Up @@ -464,6 +480,7 @@ impl NodeValue {
NodeValue::FootnoteReference(..) => "footnote_reference",
#[cfg(feature = "shortcodes")]
NodeValue::ShortCode(_) => "shortcode",
NodeValue::MultilineBlockQuote(_) => "block_quote",
digitalmoksha marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down Expand Up @@ -647,6 +664,10 @@ pub fn can_contain_type<'a>(node: &'a AstNode<'a>, child: &NodeValue) -> bool {
| NodeValue::HtmlInline(..)
),

NodeValue::MultilineBlockQuote(_) => {
child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
}

_ => false,
}
}
Expand Down
80 changes: 79 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod inlines;
pub mod shortcodes;
mod table;

pub mod multiline_block_quote;

use crate::adapters::SyntaxHighlighterAdapter;
use crate::arena_tree::Node;
use crate::ctype::{isdigit, isspace};
Expand All @@ -25,6 +27,7 @@ use std::str;
use typed_arena::Arena;

use crate::adapters::HeadingAdapter;
use crate::parser::multiline_block_quote::NodeMultilineBlockQuote;

use self::inlines::RefMap;

Expand Down Expand Up @@ -963,6 +966,16 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
return (false, container, should_continue);
}
}
NodeValue::MultilineBlockQuote(..) => {
if !self.parse_multiline_block_quote_prefix(
line,
container,
ast,
&mut should_continue,
) {
return (false, container, should_continue);
}
}
_ => {}
}
}
Expand All @@ -985,7 +998,25 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
self.find_first_nonspace(line);
let indented = self.indent >= CODE_INDENT;

if !indented && line[self.first_nonspace] == b'>' {
if !indented
&& unwrap_into(
scanners::open_multiline_block_quote_fence(&line[self.first_nonspace..]),
&mut matched,
)
{
let first_nonspace = self.first_nonspace;
let offset = self.offset;
let nmbc = NodeMultilineBlockQuote {
fence_length: matched,
fence_offset: first_nonspace - offset,
};
*container = self.add_child(
container,
NodeValue::MultilineBlockQuote(nmbc),
self.first_nonspace + 1,
);
self.advance_offset(line, first_nonspace + matched - offset, false);
} else if !indented && line[self.first_nonspace] == b'>' {
let blockquote_startpos = self.first_nonspace;

let offset = self.first_nonspace + 1 - self.offset;
Expand Down Expand Up @@ -1444,6 +1475,51 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
}
}

fn parse_multiline_block_quote_prefix(
&mut self,
line: &[u8],
container: &'a AstNode<'a>,
ast: &mut Ast,
should_continue: &mut bool,
) -> bool {
let (fence_length, fence_offset) = match ast.value {
NodeValue::MultilineBlockQuote(ref node_value) => {
(node_value.fence_length, node_value.fence_offset)
}
_ => unreachable!(),
};

let matched = if self.indent <= 3 && line[self.first_nonspace] == b'>' {
scanners::close_multiline_block_quote_fence(&line[self.first_nonspace..]).unwrap_or(0)
} else {
0
};

if matched >= fence_length {
*should_continue = false;
self.advance_offset(line, matched, false);

// The last child, like an indented codeblock, could be left open.
// Make sure it's finalized.
if nodes::last_child_is_open(container) {
let child = container.last_child().unwrap();
let child_ast = &mut *child.data.borrow_mut();

self.finalize_borrowed(child, child_ast).unwrap();
}

self.current = self.finalize_borrowed(container, ast).unwrap();
return false;
}

let mut i = fence_offset;
while i > 0 && strings::is_space_or_tab(line[self.offset]) {
self.advance_offset(line, 1, true);
i -= 1;
}
true
}

fn add_child(
&mut self,
mut parent: &'a AstNode<'a>,
Expand Down Expand Up @@ -1484,6 +1560,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
container.first_child().is_some()
|| container.data.borrow().sourcepos.start.line != self.line_number
}
NodeValue::MultilineBlockQuote(..) => false,
_ => true,
};

Expand Down Expand Up @@ -1664,6 +1741,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
NodeValue::Document => true,
NodeValue::CodeBlock(ref ncb) => ncb.fenced,
NodeValue::Heading(ref nh) => nh.setext,
NodeValue::MultilineBlockQuote(..) => true,
_ => false,
} {
ast.sourcepos.end = (self.line_number, self.curline_end_col).into();
Expand Down
9 changes: 9 additions & 0 deletions src/parser/multiline_block_quote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// The metadata of a multiline blockquote.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeMultilineBlockQuote {
/// The length of the fence.
pub fence_length: usize,

/// ??? The indentation level of the code within the block.
digitalmoksha marked this conversation as resolved.
Show resolved Hide resolved
pub fence_offset: usize,
}
22 changes: 22 additions & 0 deletions src/scanners.re
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,28 @@ pub fn shortcode(s: &[u8]) -> Option<usize> {
*/
}

pub fn open_multiline_block_quote_fence(s: &[u8]) -> Option<usize> {
let mut cursor = 0;
let mut marker = 0;
let mut ctxmarker = 0;
let len = s.len();
/*!re2c
[>]{3,} / [ \t]*[\r\n] { return Some(cursor); }
* { return None; }
*/
}

pub fn close_multiline_block_quote_fence(s: &[u8]) -> Option<usize> {
let mut cursor = 0;
let mut marker = 0;
let mut ctxmarker = 0;
let len = s.len();
/*!re2c
[>]{3,} / [ \t]*[\r\n] { return Some(cursor); }
* { return None; }
*/
}

// Returns both the length of the match, and the tasklist character.
pub fn tasklist(s: &[u8]) -> Option<(usize, u8)> {
let mut cursor = 0;
Expand Down
Loading