block: add hierarchical heading sections
This commit is contained in:
		
					parent
					
						
							
								5347def13c
							
						
					
				
			
			
				commit
				
					
						530820a04e
					
				
			
		
					 3 changed files with 59 additions and 13 deletions
				
			
		
							
								
								
									
										61
									
								
								src/block.rs
									
										
									
									
									
								
							
							
						
						
									
										61
									
								
								src/block.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -102,6 +102,9 @@ pub enum Container {
 | 
			
		|||
 | 
			
		||||
    /// Span is first '|' character.
 | 
			
		||||
    TableRow { head: bool },
 | 
			
		||||
 | 
			
		||||
    /// Span is '#' characters of heading.
 | 
			
		||||
    Section,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +135,8 @@ struct TreeParser<'s> {
 | 
			
		|||
    prev_blankline: bool,
 | 
			
		||||
    /// Stack of currently open lists.
 | 
			
		||||
    open_lists: Vec<OpenList>,
 | 
			
		||||
    /// Stack of currently open sections.
 | 
			
		||||
    open_sections: Vec<u32>,
 | 
			
		||||
    /// Alignments for each column in for the current table.
 | 
			
		||||
    alignments: Vec<Alignment>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +150,7 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
            prev_blankline: false,
 | 
			
		||||
            open_lists: Vec::new(),
 | 
			
		||||
            alignments: Vec::new(),
 | 
			
		||||
            open_sections: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +159,7 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
        let mut lines = lines(self.src).collect::<Vec<_>>();
 | 
			
		||||
        let mut line_pos = 0;
 | 
			
		||||
        while line_pos < lines.len() {
 | 
			
		||||
            let line_count = self.parse_block(&mut lines[line_pos..]);
 | 
			
		||||
            let line_count = self.parse_block(&mut lines[line_pos..], true);
 | 
			
		||||
            if line_count == 0 {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -162,11 +168,14 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
        for _ in self.open_lists.drain(..) {
 | 
			
		||||
            self.tree.exit(); // list
 | 
			
		||||
        }
 | 
			
		||||
        for _ in self.open_sections.drain(..) {
 | 
			
		||||
            self.tree.exit(); // section
 | 
			
		||||
        }
 | 
			
		||||
        self.tree.finish()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Recursively parse a block and all of its children. Return number of lines the block uses.
 | 
			
		||||
    fn parse_block(&mut self, lines: &mut [Span]) -> 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(
 | 
			
		||||
            0,
 | 
			
		||||
            |(indent, kind, span, line_count)| {
 | 
			
		||||
| 
						 | 
				
			
			@ -236,7 +245,7 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
 | 
			
		||||
                match kind {
 | 
			
		||||
                    Block::Atom(a) => self.tree.atom(a, span),
 | 
			
		||||
                    Block::Leaf(l) => self.parse_leaf(l, lines, span, indent),
 | 
			
		||||
                    Block::Leaf(l) => self.parse_leaf(l, lines, span, indent, top_level),
 | 
			
		||||
                    Block::Container(Table) => self.parse_table(lines, span),
 | 
			
		||||
                    Block::Container(c) => self.parse_container(c, lines, span, indent),
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +255,14 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn parse_leaf(&mut self, l: Leaf, lines: &mut [Span], span: Span, indent: usize) {
 | 
			
		||||
    fn parse_leaf(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        l: Leaf,
 | 
			
		||||
        lines: &mut [Span],
 | 
			
		||||
        span: Span,
 | 
			
		||||
        indent: usize,
 | 
			
		||||
        top_level: bool,
 | 
			
		||||
    ) {
 | 
			
		||||
        if matches!(l, Leaf::CodeBlock) {
 | 
			
		||||
            for line in lines.iter_mut() {
 | 
			
		||||
                let indent_line = line.len() - line.trim_start(self.src).len();
 | 
			
		||||
| 
						 | 
				
			
			@ -266,6 +282,21 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if matches!(l, Leaf::Heading) && top_level {
 | 
			
		||||
            let level = u32::try_from(span.len()).unwrap();
 | 
			
		||||
            let first_close = self
 | 
			
		||||
                .open_sections
 | 
			
		||||
                .iter()
 | 
			
		||||
                .rev()
 | 
			
		||||
                .rposition(|l| *l > level)
 | 
			
		||||
                .map_or(0, |i| i + 1);
 | 
			
		||||
            self.open_sections.drain(first_close..).for_each(|_| {
 | 
			
		||||
                self.tree.exit(); // section
 | 
			
		||||
            });
 | 
			
		||||
            self.open_sections.push(level);
 | 
			
		||||
            self.tree.enter(Node::Container(Section), span);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.tree.enter(Node::Leaf(l), span);
 | 
			
		||||
        lines.iter().for_each(|line| self.tree.inline(*line));
 | 
			
		||||
        self.tree.exit();
 | 
			
		||||
| 
						 | 
				
			
			@ -288,7 +319,7 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                ListItem(..) | Footnote | Div => spaces.min(indent),
 | 
			
		||||
                List { .. } | DescriptionList | Table | TableRow { .. } => {
 | 
			
		||||
                List { .. } | DescriptionList | Table | TableRow { .. } | Section => {
 | 
			
		||||
                    panic!()
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +350,7 @@ impl<'s> TreeParser<'s> {
 | 
			
		|||
        self.tree.enter(Node::Container(c), span);
 | 
			
		||||
        let mut l = 0;
 | 
			
		||||
        while l < lines.len() {
 | 
			
		||||
            l += self.parse_block(&mut lines[l..]);
 | 
			
		||||
            l += self.parse_block(&mut lines[l..], false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(OpenList { depth, .. }) = self.open_lists.last() {
 | 
			
		||||
| 
						 | 
				
			
			@ -663,7 +694,7 @@ impl BlockParser {
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Block::Container(List { .. } | DescriptionList | TableRow { .. })
 | 
			
		||||
            Block::Container(List { .. } | DescriptionList | TableRow { .. } | Section)
 | 
			
		||||
            | Block::Leaf(TableCell(..) | Caption) => {
 | 
			
		||||
                panic!()
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -867,21 +898,25 @@ mod test {
 | 
			
		|||
    fn parse_heading_multi() {
 | 
			
		||||
        test_parse!(
 | 
			
		||||
            concat!(
 | 
			
		||||
                            "# 2\n",
 | 
			
		||||
                            "\n",
 | 
			
		||||
                            " #   8\n",
 | 
			
		||||
                            "  12\n",
 | 
			
		||||
                            "15\n", //
 | 
			
		||||
                        ),
 | 
			
		||||
                    "# 2\n",
 | 
			
		||||
                    "\n",
 | 
			
		||||
                    " #   8\n",
 | 
			
		||||
                    "  12\n",
 | 
			
		||||
                    "15\n", //
 | 
			
		||||
                ),
 | 
			
		||||
            (Enter(Container(Section)), "#"),
 | 
			
		||||
            (Enter(Leaf(Heading)), "#"),
 | 
			
		||||
            (Inline, "2"),
 | 
			
		||||
            (Exit(Leaf(Heading)), "#"),
 | 
			
		||||
            (Atom(Blankline), "\n"),
 | 
			
		||||
            (Exit(Container(Section)), "#"),
 | 
			
		||||
            (Enter(Container(Section)), "#"),
 | 
			
		||||
            (Enter(Leaf(Heading)), "#"),
 | 
			
		||||
            (Inline, "8\n"),
 | 
			
		||||
            (Inline, "12\n"),
 | 
			
		||||
            (Inline, "15"),
 | 
			
		||||
            (Exit(Leaf(Heading)), "#"),
 | 
			
		||||
            (Exit(Container(Section)), "#"),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -148,6 +148,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                        }
 | 
			
		||||
                        Container::Table => self.out.write_str("<table")?,
 | 
			
		||||
                        Container::TableRow { .. } => self.out.write_str("<tr")?,
 | 
			
		||||
                        Container::Section => self.out.write_str("<section")?,
 | 
			
		||||
                        Container::Div { .. } => self.out.write_str("<div")?,
 | 
			
		||||
                        Container::Paragraph => {
 | 
			
		||||
                            if matches!(self.list_tightness.last(), Some(true)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -311,6 +312,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                        }
 | 
			
		||||
                        Container::Table => self.out.write_str("</table>")?,
 | 
			
		||||
                        Container::TableRow { .. } => self.out.write_str("</tr>")?,
 | 
			
		||||
                        Container::Section => self.out.write_str("</section>")?,
 | 
			
		||||
                        Container::Div { .. } => self.out.write_str("</div>")?,
 | 
			
		||||
                        Container::Paragraph => {
 | 
			
		||||
                            if matches!(self.list_tightness.last(), Some(true)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,6 +48,8 @@ pub enum Container<'s> {
 | 
			
		|||
    Table,
 | 
			
		||||
    /// A row element of a table.
 | 
			
		||||
    TableRow { head: bool },
 | 
			
		||||
    /// A section belonging to a top level heading.
 | 
			
		||||
    Section,
 | 
			
		||||
    /// A block-level divider element.
 | 
			
		||||
    Div { class: Option<&'s str> },
 | 
			
		||||
    /// A paragraph.
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +107,7 @@ impl<'s> Container<'s> {
 | 
			
		|||
            | Self::Footnote { .. }
 | 
			
		||||
            | Self::Table
 | 
			
		||||
            | Self::TableRow { .. }
 | 
			
		||||
            | Self::Section
 | 
			
		||||
            | Self::Div { .. }
 | 
			
		||||
            | Self::Paragraph
 | 
			
		||||
            | Self::Heading { .. }
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +144,7 @@ impl<'s> Container<'s> {
 | 
			
		|||
            | Self::Footnote { .. }
 | 
			
		||||
            | Self::Table
 | 
			
		||||
            | Self::TableRow { .. }
 | 
			
		||||
            | Self::Section
 | 
			
		||||
            | Self::Div { .. } => true,
 | 
			
		||||
            Self::Paragraph
 | 
			
		||||
            | Self::Heading { .. }
 | 
			
		||||
| 
						 | 
				
			
			@ -620,6 +624,7 @@ impl<'s> Parser<'s> {
 | 
			
		|||
                                }
 | 
			
		||||
                                Container::TableRow { head }
 | 
			
		||||
                            }
 | 
			
		||||
                            block::Container::Section => Container::Section,
 | 
			
		||||
                        },
 | 
			
		||||
                    };
 | 
			
		||||
                    if enter {
 | 
			
		||||
| 
						 | 
				
			
			@ -739,16 +744,20 @@ mod test {
 | 
			
		|||
    fn heading() {
 | 
			
		||||
        test_parse!(
 | 
			
		||||
            "#\n",
 | 
			
		||||
            Start(Section, Attributes::new()),
 | 
			
		||||
            Start(Heading { level: 1 }, Attributes::new()),
 | 
			
		||||
            End(Heading { level: 1 }),
 | 
			
		||||
            End(Section),
 | 
			
		||||
        );
 | 
			
		||||
        test_parse!(
 | 
			
		||||
            "# abc\ndef\n",
 | 
			
		||||
            Start(Section, Attributes::new()),
 | 
			
		||||
            Start(Heading { level: 1 }, Attributes::new()),
 | 
			
		||||
            Str("abc".into()),
 | 
			
		||||
            Atom(Softbreak),
 | 
			
		||||
            Str("def".into()),
 | 
			
		||||
            End(Heading { level: 1 }),
 | 
			
		||||
            End(Section),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue