block: emit list events around list items
This commit is contained in:
parent
3123bd1a57
commit
50632204a3
3 changed files with 125 additions and 4 deletions
114
src/block.rs
114
src/block.rs
|
@ -80,6 +80,9 @@ pub enum Container {
|
||||||
/// Span is class specifier, possibly empty.
|
/// Span is class specifier, possibly empty.
|
||||||
Div,
|
Div,
|
||||||
|
|
||||||
|
/// Span is the list marker of the first list item in the list.
|
||||||
|
List(ListType),
|
||||||
|
|
||||||
/// Span is the list marker.
|
/// Span is the list marker.
|
||||||
ListItem(ListType),
|
ListItem(ListType),
|
||||||
|
|
||||||
|
@ -95,10 +98,22 @@ pub enum ListType {
|
||||||
Description,
|
Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct OpenList {
|
||||||
|
/// Type of the list, used to determine whether this list should be continued or a new one
|
||||||
|
/// should be created.
|
||||||
|
ty: ListType,
|
||||||
|
/// Depth in the tree where the direct list items of the list are. Needed to determine when to
|
||||||
|
/// close the list.
|
||||||
|
depth: u16,
|
||||||
|
}
|
||||||
|
|
||||||
/// Parser for block-level tree structure of entire document.
|
/// Parser for block-level tree structure of entire document.
|
||||||
struct TreeParser<'s> {
|
struct TreeParser<'s> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
tree: TreeBuilder,
|
tree: TreeBuilder,
|
||||||
|
|
||||||
|
lists_open: Vec<OpenList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> TreeParser<'s> {
|
impl<'s> TreeParser<'s> {
|
||||||
|
@ -107,6 +122,7 @@ impl<'s> TreeParser<'s> {
|
||||||
Self {
|
Self {
|
||||||
src,
|
src,
|
||||||
tree: TreeBuilder::new(),
|
tree: TreeBuilder::new(),
|
||||||
|
lists_open: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +137,9 @@ impl<'s> TreeParser<'s> {
|
||||||
}
|
}
|
||||||
line_pos += line_count;
|
line_pos += line_count;
|
||||||
}
|
}
|
||||||
|
for _ in self.lists_open.drain(..) {
|
||||||
|
self.tree.exit(); // list
|
||||||
|
}
|
||||||
self.tree.finish()
|
self.tree.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +209,7 @@ impl<'s> TreeParser<'s> {
|
||||||
Block::Container(c) => {
|
Block::Container(c) => {
|
||||||
let (skip_chars, skip_lines_suffix) = match c {
|
let (skip_chars, skip_lines_suffix) = match c {
|
||||||
Blockquote => (2, 0),
|
Blockquote => (2, 0),
|
||||||
|
List(..) => panic!(),
|
||||||
ListItem(..) | Footnote => (indent, 0),
|
ListItem(..) | Footnote => (indent, 0),
|
||||||
Div => (0, 1),
|
Div => (0, 1),
|
||||||
};
|
};
|
||||||
|
@ -211,11 +231,36 @@ impl<'s> TreeParser<'s> {
|
||||||
*sp = sp.skip(skip);
|
*sp = sp.skip(skip);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Container::ListItem(ty) = c {
|
||||||
|
if self
|
||||||
|
.lists_open
|
||||||
|
.last()
|
||||||
|
.map_or(true, |OpenList { depth, .. }| {
|
||||||
|
usize::from(*depth) < self.tree.depth()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
self.tree.enter(Node::Container(Container::List(ty)), span);
|
||||||
|
self.lists_open.push(OpenList {
|
||||||
|
ty,
|
||||||
|
depth: self.tree.depth().try_into().unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.tree.enter(Node::Container(c), span);
|
self.tree.enter(Node::Container(c), span);
|
||||||
let mut l = 0;
|
let mut l = 0;
|
||||||
while l < line_count_inner {
|
while l < line_count_inner {
|
||||||
l += self.parse_block(&mut lines[l..line_count_inner]);
|
l += self.parse_block(&mut lines[l..line_count_inner]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(OpenList { depth, .. }) = self.lists_open.last() {
|
||||||
|
assert!(usize::from(*depth) <= self.tree.depth());
|
||||||
|
if self.tree.depth() == (*depth).into() {
|
||||||
|
self.tree.exit(); // list
|
||||||
|
self.lists_open.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.tree.exit();
|
self.tree.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,6 +435,7 @@ impl BlockParser {
|
||||||
!((&mut c).take(fence_length).all(|c| c == fence)
|
!((&mut c).take(fence_length).all(|c| c == fence)
|
||||||
&& c.next().map_or(true, char::is_whitespace))
|
&& c.next().map_or(true, char::is_whitespace))
|
||||||
}
|
}
|
||||||
|
Block::Container(List(..)) => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -759,14 +805,80 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_list() {
|
fn parse_list_single_item() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
|
concat!(
|
||||||
"- abc\n",
|
"- abc\n",
|
||||||
|
"\n",
|
||||||
|
"\n", //
|
||||||
|
),
|
||||||
|
(Enter(Container(List(Unordered(b'-')))), "-"),
|
||||||
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
(Enter(Leaf(Paragraph)), ""),
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
(Inline, "abc"),
|
(Inline, "abc"),
|
||||||
(Exit(Leaf(Paragraph)), ""),
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Exit(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_list_multi_item() {
|
||||||
|
test_parse!(
|
||||||
|
"- abc\n\n\n- def\n\n",
|
||||||
|
(Enter(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "abc"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "def"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Exit(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_list_nest() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"- a\n", //
|
||||||
|
"\n", //
|
||||||
|
" - aa\n", //
|
||||||
|
"\n", //
|
||||||
|
"\n", //
|
||||||
|
"- b\n", //
|
||||||
|
),
|
||||||
|
(Enter(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "a"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Enter(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "aa"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Exit(Container(List(Unordered(b'-')))), "-"),
|
||||||
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "b"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
||||||
|
(Exit(Container(List(Unordered(b'-')))), "-"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -480,7 +480,7 @@ impl<'s> Parser<'s> {
|
||||||
self.footnotes.insert(content, self.tree.take_branch());
|
self.footnotes.insert(content, self.tree.take_branch());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
block::Container::ListItem(..) => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
Event::Start(container, attributes)
|
Event::Start(container, attributes)
|
||||||
}
|
}
|
||||||
|
@ -494,7 +494,7 @@ impl<'s> Parser<'s> {
|
||||||
class: (!ev.span.is_empty()).then(|| content),
|
class: (!ev.span.is_empty()).then(|| content),
|
||||||
},
|
},
|
||||||
block::Container::Footnote => panic!(),
|
block::Container::Footnote => panic!(),
|
||||||
block::Container::ListItem(..) => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
Event::End(container)
|
Event::End(container)
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,7 @@ pub struct Builder<C, A> {
|
||||||
nodes: Vec<Node<C, A>>,
|
nodes: Vec<Node<C, A>>,
|
||||||
branch: Vec<NodeIndex>,
|
branch: Vec<NodeIndex>,
|
||||||
head: Option<NodeIndex>,
|
head: Option<NodeIndex>,
|
||||||
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Clone, A: Clone> Builder<C, A> {
|
impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
|
@ -158,6 +159,7 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}],
|
}],
|
||||||
branch: vec![],
|
branch: vec![],
|
||||||
head: Some(NodeIndex::root()),
|
head: Some(NodeIndex::root()),
|
||||||
|
depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +180,7 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn enter(&mut self, c: C, span: Span) {
|
pub(super) fn enter(&mut self, c: C, span: Span) {
|
||||||
|
self.depth += 1;
|
||||||
self.add_node(Node {
|
self.add_node(Node {
|
||||||
span,
|
span,
|
||||||
kind: NodeKind::Container(c, None),
|
kind: NodeKind::Container(c, None),
|
||||||
|
@ -186,6 +189,7 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn exit(&mut self) {
|
pub(super) fn exit(&mut self) {
|
||||||
|
self.depth -= 1;
|
||||||
if self.head.is_some() {
|
if self.head.is_some() {
|
||||||
self.head = None;
|
self.head = None;
|
||||||
} else {
|
} else {
|
||||||
|
@ -195,6 +199,7 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn finish(self) -> Tree<C, A> {
|
pub(super) fn finish(self) -> Tree<C, A> {
|
||||||
|
assert_eq!(self.depth, 0);
|
||||||
let head = self.nodes[NodeIndex::root().index()].next;
|
let head = self.nodes[NodeIndex::root().index()].next;
|
||||||
Tree {
|
Tree {
|
||||||
nodes: self.nodes.into_boxed_slice().into(),
|
nodes: self.nodes.into_boxed_slice().into(),
|
||||||
|
@ -203,6 +208,10 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn depth(&self) -> usize {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
fn add_node(&mut self, node: Node<C, A>) {
|
fn add_node(&mut self, node: Node<C, A>) {
|
||||||
let ni = NodeIndex::new(self.nodes.len());
|
let ni = NodeIndex::new(self.nodes.len());
|
||||||
self.nodes.push(node);
|
self.nodes.push(node);
|
||||||
|
|
Loading…
Reference in a new issue