markdown_that/plugins/cmark/block/
heading.rs1use crate::parser::block::{BlockRule, BlockState};
7use crate::parser::inline::InlineRoot;
8use crate::{MarkdownThat, Node, NodeValue, Renderer};
9
10#[derive(Debug)]
11pub struct ATXHeading {
12 pub level: u8,
13}
14
15impl NodeValue for ATXHeading {
16 fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
17 static TAG: [&str; 6] = ["h1", "h2", "h3", "h4", "h5", "h6"];
18 debug_assert!(self.level >= 1 && self.level <= 6);
19
20 fmt.cr();
21 fmt.open(TAG[self.level as usize - 1], &node.attrs);
22 fmt.contents(&node.children);
23 fmt.close(TAG[self.level as usize - 1]);
24 fmt.cr();
25 }
26}
27
28pub fn add(md: &mut MarkdownThat) {
29 md.block.add_rule::<HeadingScanner>();
30}
31
32#[doc(hidden)]
33pub struct HeadingScanner;
34impl BlockRule for HeadingScanner {
35 fn run(state: &mut BlockState) -> Option<(Node, usize)> {
36 if state.line_indent(state.line) >= state.md.max_indent {
37 return None;
38 }
39
40 let line = state.get_line(state.line);
41 let Some('#') = line.chars().next() else {
42 return None;
43 };
44
45 let text_pos;
46
47 let mut level = 0u8;
49 let mut chars = line.char_indices();
50 loop {
51 match chars.next() {
52 Some((_, '#')) => {
53 level += 1;
54 if level > 6 {
55 return None;
56 }
57 }
58 Some((x, ' ' | '\t')) => {
59 text_pos = x;
60 break;
61 }
62 None => {
63 text_pos = level as usize;
64 break;
65 }
66 Some(_) => return None,
67 }
68 }
69
70 let mut chars_back = chars.rev().peekable();
73 while let Some((_, ' ' | '\t')) = chars_back.peek() {
74 chars_back.next();
75 }
76 while let Some((_, '#')) = chars_back.peek() {
77 chars_back.next();
78 }
79
80 let text_max = match chars_back.next() {
81 Some((last_pos, ' ' | '\t')) => last_pos + 1,
83 Some(_) => line.len(),
85 None => text_pos,
87 };
88
89 let content = line[text_pos..text_max].to_owned();
90 let mapping = vec![(0, state.line_offsets[state.line].first_nonspace + text_pos)];
91
92 let mut node = Node::new(ATXHeading { level });
93 node.children
94 .push(Node::new(InlineRoot::new(content, mapping)));
95 Some((node, 1))
96 }
97}