block: emit list events around list items

This commit is contained in:
Noah Hellman 2023-01-21 20:53:12 +01:00
parent 3123bd1a57
commit 50632204a3
3 changed files with 125 additions and 4 deletions

View file

@ -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'-')))), "-"),
); );
} }

View file

@ -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)
} }

View file

@ -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);