block: add MeteredBlock as intermediate struct
This commit is contained in:
parent
1c77b035b2
commit
dcb3b787a2
3 changed files with 460 additions and 291 deletions
607
src/block.rs
607
src/block.rs
|
@ -85,9 +85,6 @@ pub enum Container {
|
||||||
/// Span is class specifier, possibly empty.
|
/// Span is class specifier, possibly empty.
|
||||||
Div,
|
Div,
|
||||||
|
|
||||||
/// Span is `:`.
|
|
||||||
DescriptionList,
|
|
||||||
|
|
||||||
/// Span is the list marker of the first list item in the list.
|
/// Span is the list marker of the first list item in the list.
|
||||||
List { ty: ListType, tight: bool },
|
List { ty: ListType, tight: bool },
|
||||||
|
|
||||||
|
@ -112,6 +109,7 @@ pub enum ListType {
|
||||||
Unordered(u8),
|
Unordered(u8),
|
||||||
Ordered(crate::OrderedListNumbering, crate::OrderedListStyle),
|
Ordered(crate::OrderedListNumbering, crate::OrderedListStyle),
|
||||||
Task,
|
Task,
|
||||||
|
Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -136,7 +134,7 @@ struct TreeParser<'s> {
|
||||||
/// Stack of currently open lists.
|
/// Stack of currently open lists.
|
||||||
open_lists: Vec<OpenList>,
|
open_lists: Vec<OpenList>,
|
||||||
/// Stack of currently open sections.
|
/// Stack of currently open sections.
|
||||||
open_sections: Vec<u32>,
|
open_sections: Vec<usize>,
|
||||||
/// Alignments for each column in for the current table.
|
/// Alignments for each column in for the current table.
|
||||||
alignments: Vec<Alignment>,
|
alignments: Vec<Alignment>,
|
||||||
}
|
}
|
||||||
|
@ -176,29 +174,30 @@ impl<'s> TreeParser<'s> {
|
||||||
|
|
||||||
/// Recursively parse a block and all of its children. Return number of lines the block uses.
|
/// Recursively parse a block and all of its children. Return number of lines the block uses.
|
||||||
fn parse_block(&mut self, lines: &mut [Span], top_level: bool) -> usize {
|
fn parse_block(&mut self, lines: &mut [Span], top_level: bool) -> usize {
|
||||||
BlockParser::parse(lines.iter().map(|sp| sp.of(self.src))).map_or(
|
if let Some(MeteredBlock {
|
||||||
0,
|
kind,
|
||||||
|(indent, kind, span, line_count)| {
|
span,
|
||||||
let truncated = line_count > lines.len();
|
line_count,
|
||||||
let l = line_count.min(lines.len());
|
}) = MeteredBlock::new(lines.iter().map(|sp| sp.of(self.src)))
|
||||||
let lines = &mut lines[..l];
|
{
|
||||||
|
let lines = &mut lines[..line_count];
|
||||||
let span = span.translate(lines[0].start());
|
let span = span.translate(lines[0].start());
|
||||||
|
|
||||||
// skip part of first inline that is shared with the block span
|
// skip part of first inline that is shared with the block span
|
||||||
lines[0] = lines[0].with_start(span.end());
|
lines[0] = lines[0].with_start(span.end());
|
||||||
|
|
||||||
// remove junk from footnotes / link defs
|
// remove "]:" from footnote / link def
|
||||||
if matches!(
|
if matches!(kind, Kind::Definition { .. }) {
|
||||||
kind,
|
|
||||||
Block::Leaf(LinkDefinition) | Block::Container(Footnote { .. })
|
|
||||||
) {
|
|
||||||
assert_eq!(&lines[0].of(self.src).chars().as_str()[0..2], "]:");
|
assert_eq!(&lines[0].of(self.src).chars().as_str()[0..2], "]:");
|
||||||
lines[0] = lines[0].skip(2);
|
lines[0] = lines[0].skip(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip fences of code blocks / divs
|
// skip opening and closing fence of code block / div
|
||||||
let lines = if matches!(kind, Block::Leaf(CodeBlock) | Block::Container(Div)) {
|
let lines = if let Kind::Fenced {
|
||||||
let l = lines.len() - usize::from(!truncated);
|
has_closing_fence, ..
|
||||||
|
} = kind
|
||||||
|
{
|
||||||
|
let l = lines.len() - usize::from(has_closing_fence);
|
||||||
&mut lines[1..l]
|
&mut lines[1..l]
|
||||||
} else {
|
} else {
|
||||||
lines
|
lines
|
||||||
|
@ -208,10 +207,7 @@ impl<'s> TreeParser<'s> {
|
||||||
if let Some(OpenList { ty, depth, .. }) = self.open_lists.last() {
|
if let Some(OpenList { ty, depth, .. }) = self.open_lists.last() {
|
||||||
assert!(usize::from(*depth) <= self.tree.depth());
|
assert!(usize::from(*depth) <= self.tree.depth());
|
||||||
if self.tree.depth() == (*depth).into()
|
if self.tree.depth() == (*depth).into()
|
||||||
&& !matches!(
|
&& !matches!(kind, Kind::ListItem{ ty: ty_new, .. } if *ty == ty_new)
|
||||||
kind,
|
|
||||||
Block::Container(Container::ListItem(ty_new)) if *ty == ty_new,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
self.tree.exit(); // list
|
self.tree.exit(); // list
|
||||||
self.open_lists.pop();
|
self.open_lists.pop();
|
||||||
|
@ -219,13 +215,13 @@ impl<'s> TreeParser<'s> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set list to loose if blankline discovered
|
// set list to loose if blankline discovered
|
||||||
if matches!(kind, Block::Atom(Atom::Blankline)) {
|
if matches!(kind, Kind::Atom(Atom::Blankline)) {
|
||||||
self.prev_blankline = true;
|
self.prev_blankline = true;
|
||||||
} else {
|
} else {
|
||||||
if self.prev_blankline {
|
if self.prev_blankline {
|
||||||
for OpenList { node, depth, .. } in &self.open_lists {
|
for OpenList { node, depth, .. } in &self.open_lists {
|
||||||
if usize::from(*depth) < self.tree.depth()
|
if usize::from(*depth) < self.tree.depth()
|
||||||
&& matches!(kind, Block::Container(Container::ListItem { .. }))
|
&& matches!(kind, Kind::ListItem { .. })
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -243,30 +239,31 @@ impl<'s> TreeParser<'s> {
|
||||||
self.prev_blankline = false;
|
self.prev_blankline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match kind {
|
match Block::from(&kind) {
|
||||||
Block::Atom(a) => self.tree.atom(a, span),
|
Block::Atom(a) => self.tree.atom(a, span),
|
||||||
Block::Leaf(l) => self.parse_leaf(l, lines, span, indent, top_level),
|
Block::Leaf(l) => self.parse_leaf(l, &kind, span, lines, top_level),
|
||||||
Block::Container(Table) => self.parse_table(lines, span),
|
Block::Container(Table) => self.parse_table(lines, span),
|
||||||
Block::Container(c) => self.parse_container(c, lines, span, indent),
|
Block::Container(c) => self.parse_container(c, &kind, span, lines),
|
||||||
}
|
}
|
||||||
|
|
||||||
line_count
|
line_count
|
||||||
},
|
} else {
|
||||||
)
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_leaf(
|
fn parse_leaf(
|
||||||
&mut self,
|
&mut self,
|
||||||
l: Leaf,
|
leaf: Leaf,
|
||||||
lines: &mut [Span],
|
k: &Kind,
|
||||||
span: Span,
|
span: Span,
|
||||||
indent: usize,
|
lines: &mut [Span],
|
||||||
top_level: bool,
|
top_level: bool,
|
||||||
) {
|
) {
|
||||||
if matches!(l, Leaf::CodeBlock) {
|
if let Kind::Fenced { indent, .. } = k {
|
||||||
for line in lines.iter_mut() {
|
for line in lines.iter_mut() {
|
||||||
let indent_line = line.len() - line.trim_start(self.src).len();
|
let indent_line = line.len() - line.trim_start(self.src).len();
|
||||||
*line = line.skip(indent.min(indent_line));
|
*line = line.skip((*indent).min(indent_line));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// trim starting whitespace of each inline
|
// trim starting whitespace of each inline
|
||||||
|
@ -282,33 +279,35 @@ impl<'s> TreeParser<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(l, Leaf::Heading) && top_level {
|
if let Kind::Heading { level, .. } = k {
|
||||||
let level = u32::try_from(span.len()).unwrap();
|
// open and close sections
|
||||||
|
if top_level {
|
||||||
let first_close = self
|
let first_close = self
|
||||||
.open_sections
|
.open_sections
|
||||||
.iter()
|
.iter()
|
||||||
.rposition(|l| *l < level)
|
.rposition(|l| l < level)
|
||||||
.map_or(0, |i| i + 1);
|
.map_or(0, |i| i + 1);
|
||||||
self.open_sections.drain(first_close..).for_each(|_| {
|
self.open_sections.drain(first_close..).for_each(|_| {
|
||||||
self.tree.exit(); // section
|
self.tree.exit(); // section
|
||||||
});
|
});
|
||||||
self.open_sections.push(level);
|
self.open_sections.push(*level);
|
||||||
self.tree.enter(Node::Container(Section), span);
|
self.tree.enter(Node::Container(Section), span);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.tree.enter(Node::Leaf(l), span);
|
self.tree.enter(Node::Leaf(leaf), span);
|
||||||
lines.iter().for_each(|line| self.tree.inline(*line));
|
lines.iter().for_each(|line| self.tree.inline(*line));
|
||||||
self.tree.exit();
|
self.tree.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_container(&mut self, c: Container, lines: &mut [Span], span: Span, indent: usize) {
|
fn parse_container(&mut self, c: Container, k: &Kind, span: Span, lines: &mut [Span]) {
|
||||||
// update spans, remove indentation / container prefix
|
// update spans, remove indentation / container prefix
|
||||||
lines.iter_mut().skip(1).for_each(|sp| {
|
lines.iter_mut().skip(1).for_each(|sp| {
|
||||||
let src = sp.of(self.src);
|
let src = sp.of(self.src);
|
||||||
let src_t = src.trim();
|
let src_t = src.trim();
|
||||||
let spaces = src.len() - src.trim_start().len();
|
let spaces = src.len() - src.trim_start().len();
|
||||||
let skip = match c {
|
let skip = match k {
|
||||||
Blockquote => {
|
Kind::Blockquote => {
|
||||||
if src_t == ">" {
|
if src_t == ">" {
|
||||||
spaces + 1
|
spaces + 1
|
||||||
} else if src_t.starts_with("> ") {
|
} else if src_t.starts_with("> ") {
|
||||||
|
@ -317,16 +316,16 @@ impl<'s> TreeParser<'s> {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListItem(..) | Footnote | Div => spaces.min(indent),
|
Kind::ListItem { indent, .. }
|
||||||
List { .. } | DescriptionList | Table | TableRow { .. } | Section => {
|
| Kind::Definition { indent, .. }
|
||||||
panic!()
|
| Kind::Fenced { indent, .. } => spaces.min(*indent),
|
||||||
}
|
_ => panic!("non-container {:?}", k),
|
||||||
};
|
};
|
||||||
let len = sp.len() - usize::from(sp.of(self.src).ends_with('\n'));
|
let len = sp.len() - usize::from(sp.of(self.src).ends_with('\n'));
|
||||||
*sp = sp.skip(skip.min(len));
|
*sp = sp.skip(skip.min(len));
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Container::ListItem(ty) = c {
|
if let ListItem(ty) = c {
|
||||||
if self
|
if self
|
||||||
.open_lists
|
.open_lists
|
||||||
.last()
|
.last()
|
||||||
|
@ -500,141 +499,201 @@ impl<'s> TreeParser<'s> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for a single block.
|
/// Parser for a single block.
|
||||||
struct BlockParser {
|
struct MeteredBlock {
|
||||||
indent: usize,
|
kind: Kind,
|
||||||
kind: Block,
|
|
||||||
span: Span,
|
span: Span,
|
||||||
fence: Option<(char, usize)>,
|
line_count: usize,
|
||||||
caption: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockParser {
|
impl MeteredBlock {
|
||||||
/// Parse a single block. Return number of lines the block uses.
|
/// Identify and measure the line length of a single block.
|
||||||
fn parse<'s, I: Iterator<Item = &'s str>>(mut lines: I) -> Option<(usize, Block, Span, usize)> {
|
fn new<'s, I: Iterator<Item = &'s str>>(mut lines: I) -> Option<Self> {
|
||||||
lines.next().map(|l| {
|
lines.next().map(|l| {
|
||||||
let mut p = BlockParser::new(l);
|
let IdentifiedBlock { mut kind, span } = IdentifiedBlock::new(l);
|
||||||
let has_end_delimiter =
|
let line_count = 1 + lines.take_while(|l| kind.continues(l)).count();
|
||||||
matches!(p.kind, Block::Leaf(CodeBlock) | Block::Container(Div));
|
Self {
|
||||||
let line_count_match = lines.take_while(|l| p.continues(l)).count();
|
kind,
|
||||||
let line_count = 1 + line_count_match + usize::from(has_end_delimiter);
|
span,
|
||||||
(p.indent, p.kind, p.span, line_count)
|
line_count,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum FenceKind {
|
||||||
|
Div,
|
||||||
|
CodeBlock(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Kind {
|
||||||
|
Atom(Atom),
|
||||||
|
Paragraph,
|
||||||
|
Heading {
|
||||||
|
level: usize,
|
||||||
|
},
|
||||||
|
Fenced {
|
||||||
|
indent: usize,
|
||||||
|
fence_length: usize,
|
||||||
|
kind: FenceKind,
|
||||||
|
has_spec: bool,
|
||||||
|
has_closing_fence: bool,
|
||||||
|
},
|
||||||
|
Definition {
|
||||||
|
indent: usize,
|
||||||
|
footnote: bool,
|
||||||
|
},
|
||||||
|
Blockquote,
|
||||||
|
ListItem {
|
||||||
|
indent: usize,
|
||||||
|
ty: ListType,
|
||||||
|
},
|
||||||
|
Table {
|
||||||
|
caption: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IdentifiedBlock {
|
||||||
|
kind: Kind,
|
||||||
|
span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Kind> for Block {
|
||||||
|
fn from(kind: &Kind) -> Self {
|
||||||
|
match kind {
|
||||||
|
Kind::Atom(a) => Self::Atom(*a),
|
||||||
|
Kind::Paragraph => Self::Leaf(Paragraph),
|
||||||
|
Kind::Heading { .. } => Self::Leaf(Heading),
|
||||||
|
Kind::Fenced {
|
||||||
|
kind: FenceKind::CodeBlock(..),
|
||||||
|
..
|
||||||
|
} => Self::Leaf(CodeBlock),
|
||||||
|
Kind::Fenced {
|
||||||
|
kind: FenceKind::Div,
|
||||||
|
..
|
||||||
|
} => Self::Container(Div),
|
||||||
|
Kind::Definition {
|
||||||
|
footnote: false, ..
|
||||||
|
} => Self::Leaf(LinkDefinition),
|
||||||
|
Kind::Definition { footnote: true, .. } => Self::Container(Footnote),
|
||||||
|
Kind::Blockquote => Self::Container(Blockquote),
|
||||||
|
Kind::ListItem { ty, .. } => Self::Container(ListItem(*ty)),
|
||||||
|
Kind::Table { .. } => Self::Container(Table),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdentifiedBlock {
|
||||||
fn new(line: &str) -> Self {
|
fn new(line: &str) -> Self {
|
||||||
let start = line
|
let indent = line
|
||||||
.chars()
|
.chars()
|
||||||
.take_while(|c| *c != '\n' && c.is_whitespace())
|
.take_while(|c| *c != '\n' && c.is_whitespace())
|
||||||
.map(char::len_utf8)
|
.map(char::len_utf8)
|
||||||
.sum();
|
.sum();
|
||||||
let line_t = &line[start..];
|
let line = &line[indent..];
|
||||||
let mut chars = line_t.chars();
|
let line_t = line.trim_end();
|
||||||
|
let l = line.len();
|
||||||
|
let lt = line_t.len();
|
||||||
|
|
||||||
let mut fence = None;
|
let mut chars = line.chars();
|
||||||
let (kind, span) = match chars.next().unwrap_or(EOF) {
|
match chars.next().unwrap_or(EOF) {
|
||||||
EOF => Some((Block::Atom(Blankline), Span::empty_at(start))),
|
EOF => Some((Kind::Atom(Blankline), Span::empty_at(indent))),
|
||||||
'\n' => Some((Block::Atom(Blankline), Span::by_len(start, 1))),
|
'\n' => Some((Kind::Atom(Blankline), Span::by_len(indent, 1))),
|
||||||
'#' => chars
|
'#' => chars
|
||||||
.find(|c| *c != '#')
|
.find(|c| *c != '#')
|
||||||
.map_or(true, char::is_whitespace)
|
.map_or(true, char::is_whitespace)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
(
|
let level = l - chars.as_str().len() - 1;
|
||||||
Block::Leaf(Heading),
|
(Kind::Heading { level }, Span::by_len(indent, level))
|
||||||
Span::by_len(start, line_t.len() - chars.as_str().len() - 1),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
'>' => {
|
'>' => chars
|
||||||
if let Some(c) = chars.next() {
|
.next()
|
||||||
c.is_whitespace().then(|| {
|
.map_or(Some(false), |c| c.is_whitespace().then(|| true))
|
||||||
(
|
.map(|space_after| {
|
||||||
Block::Container(Blockquote),
|
let len = l - chars.as_str().len() - usize::from(space_after);
|
||||||
Span::by_len(start, line_t.len() - chars.as_str().len() - 1),
|
(Kind::Blockquote, Span::by_len(indent, len))
|
||||||
)
|
}),
|
||||||
})
|
'{' => (attr::valid(line.chars()).0 == lt)
|
||||||
} else {
|
.then(|| (Kind::Atom(Attributes), Span::by_len(indent, l))),
|
||||||
Some((
|
|
||||||
Block::Container(Blockquote),
|
|
||||||
Span::by_len(start, line_t.len() - chars.as_str().len()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'{' => (attr::valid(line_t.chars()).0 == line_t.trim_end().len())
|
|
||||||
.then(|| (Block::Atom(Attributes), Span::by_len(start, line_t.len()))),
|
|
||||||
'|' => {
|
'|' => {
|
||||||
let l = line_t.trim_end().len();
|
|
||||||
// FIXME: last byte may be pipe but end of prefixed unicode char
|
// FIXME: last byte may be pipe but end of prefixed unicode char
|
||||||
(line_t.as_bytes()[l - 1] == b'|' && line_t.as_bytes()[l - 2] != b'\\')
|
(line.as_bytes()[lt - 1] == b'|' && line.as_bytes()[lt - 2] != b'\\')
|
||||||
.then(|| (Block::Container(Table), Span::empty_at(start)))
|
.then(|| (Kind::Table { caption: false }, Span::empty_at(indent)))
|
||||||
}
|
}
|
||||||
'[' => chars.as_str().find("]:").map(|l| {
|
'[' => chars.as_str().find("]:").map(|l| {
|
||||||
let tag = &chars.as_str()[0..l];
|
let tag = &chars.as_str()[0..l];
|
||||||
let (tag, is_footnote) = if let Some(tag) = tag.strip_prefix('^') {
|
let footnote = tag.starts_with('^');
|
||||||
(tag, true)
|
|
||||||
} else {
|
|
||||||
(tag, false)
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
if is_footnote {
|
Kind::Definition { indent, footnote },
|
||||||
Block::Container(Footnote)
|
Span::by_len(indent + 1, l).skip(usize::from(footnote)),
|
||||||
} else {
|
|
||||||
Block::Leaf(LinkDefinition)
|
|
||||||
},
|
|
||||||
Span::from_slice(line, tag),
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
'-' | '*' if Self::is_thematic_break(chars.clone()) => Some((
|
'-' | '*' if Self::is_thematic_break(chars.clone()) => {
|
||||||
Block::Atom(ThematicBreak),
|
Some((Kind::Atom(ThematicBreak), Span::by_len(indent, lt)))
|
||||||
Span::from_slice(line, line_t.trim()),
|
}
|
||||||
)),
|
|
||||||
b @ ('-' | '*' | '+') => chars.next().map_or(true, char::is_whitespace).then(|| {
|
b @ ('-' | '*' | '+') => chars.next().map_or(true, char::is_whitespace).then(|| {
|
||||||
let task_list = chars.next() == Some('[')
|
let task_list = chars.next() == Some('[')
|
||||||
&& matches!(chars.next(), Some('x' | 'X' | ' '))
|
&& matches!(chars.next(), Some('x' | 'X' | ' '))
|
||||||
&& chars.next() == Some(']')
|
&& chars.next() == Some(']')
|
||||||
&& chars.next().map_or(true, char::is_whitespace);
|
&& chars.next().map_or(true, char::is_whitespace);
|
||||||
if task_list {
|
if task_list {
|
||||||
(Block::Container(ListItem(Task)), Span::by_len(start, 5))
|
(Kind::ListItem { indent, ty: Task }, Span::by_len(indent, 5))
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
Block::Container(ListItem(Unordered(b as u8))),
|
Kind::ListItem {
|
||||||
Span::by_len(start, 1),
|
indent,
|
||||||
|
ty: Unordered(b as u8),
|
||||||
|
},
|
||||||
|
Span::by_len(indent, 1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
':' if chars.clone().next().map_or(true, char::is_whitespace) => {
|
':' if chars.clone().next().map_or(true, char::is_whitespace) => Some((
|
||||||
Some((Block::Container(DescriptionList), Span::by_len(start, 1)))
|
Kind::ListItem {
|
||||||
}
|
indent,
|
||||||
|
ty: Description,
|
||||||
|
},
|
||||||
|
Span::by_len(indent, 1),
|
||||||
|
)),
|
||||||
f @ ('`' | ':' | '~') => {
|
f @ ('`' | ':' | '~') => {
|
||||||
let fence_length = (&mut chars).take_while(|c| *c == f).count() + 1;
|
let fence_length = 1 + (&mut chars).take_while(|c| *c == f).count();
|
||||||
fence = Some((f, fence_length));
|
let spec = &line_t[fence_length..].trim_start();
|
||||||
let lang = line_t[fence_length..].trim();
|
|
||||||
let valid_spec =
|
let valid_spec =
|
||||||
!lang.chars().any(char::is_whitespace) && !lang.chars().any(|c| c == '`');
|
!spec.chars().any(char::is_whitespace) && !spec.chars().any(|c| c == '`');
|
||||||
|
let skip = line_t.len() - spec.len();
|
||||||
(valid_spec && fence_length >= 3).then(|| {
|
(valid_spec && fence_length >= 3).then(|| {
|
||||||
(
|
(
|
||||||
match f {
|
Kind::Fenced {
|
||||||
':' => Block::Container(Div),
|
indent,
|
||||||
_ => Block::Leaf(CodeBlock),
|
fence_length,
|
||||||
|
kind: match f {
|
||||||
|
':' => FenceKind::Div,
|
||||||
|
_ => FenceKind::CodeBlock(f as u8),
|
||||||
},
|
},
|
||||||
Span::from_slice(line, lang),
|
has_spec: !spec.is_empty(),
|
||||||
|
has_closing_fence: false,
|
||||||
|
},
|
||||||
|
Span::by_len(indent + skip, spec.len()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
c => Self::maybe_ordered_list_item(c, &mut chars).map(|(num, style, len)| {
|
c => Self::maybe_ordered_list_item(c, chars).map(|(num, style, len)| {
|
||||||
(
|
(
|
||||||
Block::Container(ListItem(Ordered(num, style))),
|
Kind::ListItem {
|
||||||
Span::by_len(start, len),
|
indent,
|
||||||
|
ty: Ordered(num, style),
|
||||||
|
},
|
||||||
|
Span::by_len(indent, len),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
.unwrap_or((Block::Leaf(Paragraph), Span::new(0, 0)));
|
.map(|(kind, span)| Self { kind, span })
|
||||||
|
.unwrap_or(Self {
|
||||||
Self {
|
kind: Kind::Paragraph,
|
||||||
indent: start,
|
span: Span::empty_at(indent),
|
||||||
kind,
|
})
|
||||||
span,
|
|
||||||
fence,
|
|
||||||
caption: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_thematic_break(chars: std::str::Chars) -> bool {
|
fn is_thematic_break(chars: std::str::Chars) -> bool {
|
||||||
|
@ -649,60 +708,9 @@ impl BlockParser {
|
||||||
n >= 3
|
n >= 3
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if this line continues the block.
|
|
||||||
fn continues(&mut self, line: &str) -> bool {
|
|
||||||
let line_t = line.trim();
|
|
||||||
let empty = line_t.is_empty();
|
|
||||||
match self.kind {
|
|
||||||
Block::Atom(..) => false,
|
|
||||||
Block::Leaf(Paragraph | Heading) | Block::Container(Blockquote) => !empty,
|
|
||||||
Block::Leaf(LinkDefinition) => line.starts_with(' ') && !empty,
|
|
||||||
Block::Container(ListItem(..)) => {
|
|
||||||
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
|
||||||
empty
|
|
||||||
|| spaces > self.indent
|
|
||||||
|| matches!(
|
|
||||||
Self::parse(std::iter::once(line)),
|
|
||||||
Some((_, Block::Leaf(Leaf::Paragraph), _, _)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Block::Container(Footnote) => {
|
|
||||||
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
|
||||||
empty || spaces > self.indent
|
|
||||||
}
|
|
||||||
Block::Container(Div) | Block::Leaf(CodeBlock) => {
|
|
||||||
let (fence_char, l0) = self.fence.unwrap();
|
|
||||||
let l1 = line_t.len();
|
|
||||||
let is_end_fence = line_t.chars().all(|c| c == fence_char)
|
|
||||||
&& (l0 == l1 || (l0 < l1 && matches!(self.kind, Block::Container(..))));
|
|
||||||
!is_end_fence
|
|
||||||
}
|
|
||||||
Block::Container(Table) if self.caption => !empty,
|
|
||||||
Block::Container(Table) => {
|
|
||||||
let l = line_t.len();
|
|
||||||
match l {
|
|
||||||
0 => true,
|
|
||||||
1..=2 => false,
|
|
||||||
_ => {
|
|
||||||
if line_t.starts_with("^ ") {
|
|
||||||
self.caption = true;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
line_t.as_bytes()[l - 1] == b'|' && line_t.as_bytes()[l - 2] != b'\\'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Block::Container(List { .. } | DescriptionList | TableRow { .. } | Section)
|
|
||||||
| Block::Leaf(TableCell(..) | Caption) => {
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn maybe_ordered_list_item(
|
fn maybe_ordered_list_item(
|
||||||
mut first: char,
|
mut first: char,
|
||||||
chars: &mut std::str::Chars,
|
mut chars: std::str::Chars,
|
||||||
) -> Option<(crate::OrderedListNumbering, crate::OrderedListStyle, usize)> {
|
) -> Option<(crate::OrderedListNumbering, crate::OrderedListStyle, usize)> {
|
||||||
fn is_roman_lower_digit(c: char) -> bool {
|
fn is_roman_lower_digit(c: char) -> bool {
|
||||||
matches!(c, 'i' | 'v' | 'x' | 'l' | 'c' | 'd' | 'm')
|
matches!(c, 'i' | 'v' | 'x' | 'l' | 'c' | 'd' | 'm')
|
||||||
|
@ -778,6 +786,68 @@ impl BlockParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
/// Determine if a line continues the block.
|
||||||
|
fn continues(&mut self, line: &str) -> bool {
|
||||||
|
let IdentifiedBlock { kind: next, .. } = IdentifiedBlock::new(line);
|
||||||
|
match self {
|
||||||
|
Self::Atom(..)
|
||||||
|
| Self::Fenced {
|
||||||
|
has_closing_fence: true,
|
||||||
|
..
|
||||||
|
} => false,
|
||||||
|
Self::Blockquote => matches!(next, Self::Blockquote | Self::Paragraph),
|
||||||
|
Self::Heading { .. } => matches!(next, Self::Paragraph),
|
||||||
|
Self::Paragraph | Self::Table { caption: true } => {
|
||||||
|
!matches!(next, Self::Atom(Blankline))
|
||||||
|
}
|
||||||
|
Self::ListItem { indent, .. } => {
|
||||||
|
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
||||||
|
matches!(next, Self::Atom(Blankline))
|
||||||
|
|| spaces > *indent
|
||||||
|
|| matches!(next, Self::Paragraph)
|
||||||
|
}
|
||||||
|
Self::Definition { indent, footnote } => {
|
||||||
|
if *footnote {
|
||||||
|
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
||||||
|
matches!(next, Self::Atom(Blankline)) || spaces > *indent
|
||||||
|
} else {
|
||||||
|
line.starts_with(' ') && !matches!(next, Self::Atom(Blankline))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Fenced {
|
||||||
|
fence_length,
|
||||||
|
kind,
|
||||||
|
has_closing_fence,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Kind::Fenced {
|
||||||
|
kind: k,
|
||||||
|
fence_length: l,
|
||||||
|
has_spec: false,
|
||||||
|
..
|
||||||
|
} = next
|
||||||
|
{
|
||||||
|
*has_closing_fence = k == *kind
|
||||||
|
&& (l == *fence_length
|
||||||
|
|| (matches!(k, FenceKind::Div) && l > *fence_length));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Self::Table { caption } => {
|
||||||
|
matches!(next, Self::Table { .. } | Self::Atom(Blankline)) || {
|
||||||
|
if line.trim().starts_with("^ ") {
|
||||||
|
*caption = true;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Block {
|
impl std::fmt::Display for Block {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -822,8 +892,9 @@ mod test {
|
||||||
use crate::OrderedListStyle::*;
|
use crate::OrderedListStyle::*;
|
||||||
|
|
||||||
use super::Atom::*;
|
use super::Atom::*;
|
||||||
use super::Block;
|
|
||||||
use super::Container::*;
|
use super::Container::*;
|
||||||
|
use super::FenceKind;
|
||||||
|
use super::Kind;
|
||||||
use super::Leaf::*;
|
use super::Leaf::*;
|
||||||
use super::ListType::*;
|
use super::ListType::*;
|
||||||
use super::Node::*;
|
use super::Node::*;
|
||||||
|
@ -1677,9 +1748,9 @@ mod test {
|
||||||
macro_rules! test_block {
|
macro_rules! test_block {
|
||||||
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
||||||
let lines = super::lines($src).map(|sp| sp.of($src));
|
let lines = super::lines($src).map(|sp| sp.of($src));
|
||||||
let (_indent, kind, sp, len) = super::BlockParser::parse(lines).unwrap();
|
let mb = super::MeteredBlock::new(lines).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(kind, sp.of($src), len),
|
(mb.kind, mb.span.of($src), mb.line_count),
|
||||||
($kind, $str, $len),
|
($kind, $str, $len),
|
||||||
"\n\n{}\n\n",
|
"\n\n{}\n\n",
|
||||||
$src
|
$src
|
||||||
|
@ -1689,15 +1760,15 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_blankline() {
|
fn block_blankline() {
|
||||||
test_block!("\n", Block::Atom(Blankline), "\n", 1);
|
test_block!("\n", Kind::Atom(Blankline), "\n", 1);
|
||||||
test_block!(" \n", Block::Atom(Blankline), "\n", 1);
|
test_block!(" \n", Kind::Atom(Blankline), "\n", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_multiline() {
|
fn block_multiline() {
|
||||||
test_block!(
|
test_block!(
|
||||||
"# heading\n spanning two lines\n",
|
"# heading\n spanning two lines\n",
|
||||||
Block::Leaf(Heading),
|
Kind::Heading { level: 1 },
|
||||||
"#",
|
"#",
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
@ -1713,7 +1784,7 @@ mod test {
|
||||||
">\n", //
|
">\n", //
|
||||||
"> c\n", //
|
"> c\n", //
|
||||||
),
|
),
|
||||||
Block::Container(Blockquote),
|
Kind::Blockquote,
|
||||||
">",
|
">",
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
|
@ -1721,14 +1792,14 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_thematic_break() {
|
fn block_thematic_break() {
|
||||||
test_block!("---\n", Block::Atom(ThematicBreak), "---", 1);
|
test_block!("---\n", Kind::Atom(ThematicBreak), "---", 1);
|
||||||
test_block!(
|
test_block!(
|
||||||
concat!(
|
concat!(
|
||||||
" -*- -*-\n",
|
" -*- -*-\n",
|
||||||
"\n",
|
"\n",
|
||||||
"para", //
|
"para", //
|
||||||
),
|
),
|
||||||
Block::Atom(ThematicBreak),
|
Kind::Atom(ThematicBreak),
|
||||||
"-*- -*-",
|
"-*- -*-",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
@ -1744,7 +1815,13 @@ mod test {
|
||||||
" l1\n",
|
" l1\n",
|
||||||
"````", //
|
"````", //
|
||||||
),
|
),
|
||||||
Block::Leaf(CodeBlock),
|
Kind::Fenced {
|
||||||
|
indent: 0,
|
||||||
|
kind: FenceKind::CodeBlock(b'`'),
|
||||||
|
fence_length: 4,
|
||||||
|
has_spec: true,
|
||||||
|
has_closing_fence: true,
|
||||||
|
},
|
||||||
"lang",
|
"lang",
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
|
@ -1757,7 +1834,13 @@ mod test {
|
||||||
"bbb\n", //
|
"bbb\n", //
|
||||||
"```\n", //
|
"```\n", //
|
||||||
),
|
),
|
||||||
Block::Leaf(CodeBlock),
|
Kind::Fenced {
|
||||||
|
indent: 0,
|
||||||
|
kind: FenceKind::CodeBlock(b'`'),
|
||||||
|
fence_length: 3,
|
||||||
|
has_spec: false,
|
||||||
|
has_closing_fence: true,
|
||||||
|
},
|
||||||
"",
|
"",
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
|
@ -1767,7 +1850,7 @@ mod test {
|
||||||
"l0\n",
|
"l0\n",
|
||||||
"```\n", //
|
"```\n", //
|
||||||
),
|
),
|
||||||
Block::Leaf(Paragraph),
|
Kind::Paragraph,
|
||||||
"",
|
"",
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
|
@ -1775,7 +1858,15 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_link_definition() {
|
fn block_link_definition() {
|
||||||
test_block!("[tag]: url\n", Block::Leaf(LinkDefinition), "tag", 1);
|
test_block!(
|
||||||
|
"[tag]: url\n",
|
||||||
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: false
|
||||||
|
},
|
||||||
|
"tag",
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1785,7 +1876,10 @@ mod test {
|
||||||
"[tag]: uuu\n",
|
"[tag]: uuu\n",
|
||||||
" rl\n", //
|
" rl\n", //
|
||||||
),
|
),
|
||||||
Block::Leaf(LinkDefinition),
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: false
|
||||||
|
},
|
||||||
"tag",
|
"tag",
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
|
@ -1794,7 +1888,10 @@ mod test {
|
||||||
"[tag]: url\n",
|
"[tag]: url\n",
|
||||||
"para\n", //
|
"para\n", //
|
||||||
),
|
),
|
||||||
Block::Leaf(LinkDefinition),
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: false
|
||||||
|
},
|
||||||
"tag",
|
"tag",
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
@ -1802,12 +1899,28 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_footnote_empty() {
|
fn block_footnote_empty() {
|
||||||
test_block!("[^tag]:\n", Block::Container(Footnote), "tag", 1);
|
test_block!(
|
||||||
|
"[^tag]:\n",
|
||||||
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: true
|
||||||
|
},
|
||||||
|
"tag",
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_footnote_single() {
|
fn block_footnote_single() {
|
||||||
test_block!("[^tag]: a\n", Block::Container(Footnote), "tag", 1);
|
test_block!(
|
||||||
|
"[^tag]: a\n",
|
||||||
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: true
|
||||||
|
},
|
||||||
|
"tag",
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1817,7 +1930,10 @@ mod test {
|
||||||
"[^tag]: a\n",
|
"[^tag]: a\n",
|
||||||
" b\n", //
|
" b\n", //
|
||||||
),
|
),
|
||||||
Block::Container(Footnote),
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: true
|
||||||
|
},
|
||||||
"tag",
|
"tag",
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
|
@ -1832,7 +1948,10 @@ mod test {
|
||||||
"\n",
|
"\n",
|
||||||
"para\n", //
|
"para\n", //
|
||||||
),
|
),
|
||||||
Block::Container(Footnote),
|
Kind::Definition {
|
||||||
|
indent: 0,
|
||||||
|
footnote: true
|
||||||
|
},
|
||||||
"tag",
|
"tag",
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
|
@ -1842,71 +1961,117 @@ mod test {
|
||||||
fn block_list_bullet() {
|
fn block_list_bullet() {
|
||||||
test_block!(
|
test_block!(
|
||||||
"- abc\n",
|
"- abc\n",
|
||||||
Block::Container(ListItem(Unordered(b'-'))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Unordered(b'-')
|
||||||
|
},
|
||||||
"-",
|
"-",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"+ abc\n",
|
"+ abc\n",
|
||||||
Block::Container(ListItem(Unordered(b'+'))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Unordered(b'+')
|
||||||
|
},
|
||||||
"+",
|
"+",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"* abc\n",
|
"* abc\n",
|
||||||
Block::Container(ListItem(Unordered(b'*'))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Unordered(b'*')
|
||||||
|
},
|
||||||
"*",
|
"*",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_list_description() {
|
|
||||||
test_block!(": abc\n", Block::Container(DescriptionList), ":", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_list_task() {
|
fn block_list_task() {
|
||||||
test_block!("- [ ] abc\n", Block::Container(ListItem(Task)), "- [ ]", 1);
|
test_block!(
|
||||||
test_block!("+ [x] abc\n", Block::Container(ListItem(Task)), "+ [x]", 1);
|
"- [ ] abc\n",
|
||||||
test_block!("* [X] abc\n", Block::Container(ListItem(Task)), "* [X]", 1);
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Task
|
||||||
|
},
|
||||||
|
"- [ ]",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
test_block!(
|
||||||
|
"+ [x] abc\n",
|
||||||
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Task
|
||||||
|
},
|
||||||
|
"+ [x]",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
test_block!(
|
||||||
|
"* [X] abc\n",
|
||||||
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Task
|
||||||
|
},
|
||||||
|
"* [X]",
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_list_ordered() {
|
fn block_list_ordered() {
|
||||||
test_block!(
|
test_block!(
|
||||||
"123. abc\n",
|
"123. abc\n",
|
||||||
Block::Container(ListItem(Ordered(Decimal, Period))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(Decimal, Period)
|
||||||
|
},
|
||||||
"123.",
|
"123.",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"i. abc\n",
|
"i. abc\n",
|
||||||
Block::Container(ListItem(Ordered(RomanLower, Period))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(RomanLower, Period)
|
||||||
|
},
|
||||||
"i.",
|
"i.",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"I. abc\n",
|
"I. abc\n",
|
||||||
Block::Container(ListItem(Ordered(RomanUpper, Period))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(RomanUpper, Period)
|
||||||
|
},
|
||||||
"I.",
|
"I.",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"IJ. abc\n",
|
"IJ. abc\n",
|
||||||
Block::Container(ListItem(Ordered(AlphaUpper, Period))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(AlphaUpper, Period)
|
||||||
|
},
|
||||||
"IJ.",
|
"IJ.",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"(a) abc\n",
|
"(a) abc\n",
|
||||||
Block::Container(ListItem(Ordered(AlphaLower, ParenParen))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(AlphaLower, ParenParen)
|
||||||
|
},
|
||||||
"(a)",
|
"(a)",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
test_block!(
|
test_block!(
|
||||||
"a) abc\n",
|
"a) abc\n",
|
||||||
Block::Container(ListItem(Ordered(AlphaLower, Paren))),
|
Kind::ListItem {
|
||||||
|
indent: 0,
|
||||||
|
ty: Ordered(AlphaLower, Paren)
|
||||||
|
},
|
||||||
"a)",
|
"a)",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -589,24 +589,28 @@ impl<'s> Parser<'s> {
|
||||||
attributes = Attributes::new();
|
attributes = Attributes::new();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
block::Container::DescriptionList => Container::DescriptionList,
|
|
||||||
block::Container::List { ty, tight } => {
|
block::Container::List { ty, tight } => {
|
||||||
|
if matches!(ty, block::ListType::Description) {
|
||||||
|
Container::DescriptionList
|
||||||
|
} else {
|
||||||
let kind = match ty {
|
let kind = match ty {
|
||||||
block::ListType::Unordered(..) => ListKind::Unordered,
|
block::ListType::Unordered(..) => ListKind::Unordered,
|
||||||
|
block::ListType::Task => ListKind::Task,
|
||||||
block::ListType::Ordered(numbering, style) => {
|
block::ListType::Ordered(numbering, style) => {
|
||||||
let marker = ev.span.of(self.src);
|
let start = numbering
|
||||||
let start =
|
.parse_number(style.number(content))
|
||||||
numbering.parse_number(style.number(marker)).max(1);
|
.max(1);
|
||||||
ListKind::Ordered {
|
ListKind::Ordered {
|
||||||
numbering,
|
numbering,
|
||||||
style,
|
style,
|
||||||
start,
|
start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
block::ListType::Task => ListKind::Task,
|
block::ListType::Description => unreachable!(),
|
||||||
};
|
};
|
||||||
Container::List { kind, tight }
|
Container::List { kind, tight }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
block::Container::ListItem(ty) => {
|
block::Container::ListItem(ty) => {
|
||||||
if matches!(ty, block::ListType::Task) {
|
if matches!(ty, block::ListType::Task) {
|
||||||
let marker = ev.span.of(self.src);
|
let marker = ev.span.of(self.src);
|
||||||
|
|
|
@ -89,10 +89,6 @@ impl Span {
|
||||||
&s[self.start()..self.end()]
|
&s[self.start()..self.end()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice(s: &str, slice: &str) -> Self {
|
|
||||||
Self::by_len(slice.as_ptr() as usize - s.as_ptr() as usize, slice.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trim_start(self, s: &str) -> Self {
|
pub fn trim_start(self, s: &str) -> Self {
|
||||||
Self::from_slice(s, self.of(s).trim_start())
|
Self::from_slice(s, self.of(s).trim_start())
|
||||||
}
|
}
|
||||||
|
@ -104,6 +100,10 @@ impl Span {
|
||||||
pub fn trim(self, s: &str) -> Self {
|
pub fn trim(self, s: &str) -> Self {
|
||||||
Self::from_slice(s, self.of(s).trim_start().trim_end())
|
Self::from_slice(s, self.of(s).trim_start().trim_end())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_slice(s: &str, slice: &str) -> Self {
|
||||||
|
Self::by_len(slice.as_ptr() as usize - s.as_ptr() as usize, slice.len())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DiscontinuousString<'s> {
|
pub trait DiscontinuousString<'s> {
|
||||||
|
|
Loading…
Reference in a new issue