| 1 | //! Thematic breaks |
| 2 | //! |
| 3 | //! `***`, `---`, `___` |
| 4 | //! |
| 5 | //! <https://spec.commonmark.org/0.30/#thematic-breaks> |
| 6 | use crate::parser::block::{BlockRule, BlockState}; |
| 7 | use crate::{MarkdownIt, Node, NodeValue, Renderer}; |
| 8 | |
| 9 | #[derive(Debug)] |
| 10 | pub struct ThematicBreak { |
| 11 | pub marker: char, |
| 12 | pub marker_len: usize, |
| 13 | } |
| 14 | |
| 15 | impl NodeValue for ThematicBreak { |
| 16 | fn render(&self, node: &Node, fmt: &mut dyn Renderer) { |
| 17 | fmt.cr(); |
| 18 | fmt.self_close("hr", &node.attrs); |
| 19 | fmt.cr(); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | pub fn add(md: &mut MarkdownIt) { |
| 24 | md.block.add_rule::<HrScanner>(); |
| 25 | } |
| 26 | |
| 27 | #[doc(hidden)] |
| 28 | pub struct HrScanner; |
| 29 | impl BlockRule for HrScanner { |
| 30 | fn run(state: &mut BlockState) -> Option<(Node, usize)> { |
| 31 | |
| 32 | if state.line_indent(state.line) >= state.md.max_indent { return None; } |
| 33 | |
| 34 | let mut chars = state.get_line(state.line).chars(); |
| 35 | |
| 36 | // Check hr marker |
| 37 | let marker = chars.next()?; |
| 38 | if marker != '*' && marker != '-' && marker != '_' { return None; } |
| 39 | |
| 40 | // markers can be mixed with spaces, but there should be at least 3 of them |
| 41 | let mut cnt = 1; |
| 42 | for ch in chars { |
| 43 | if ch == marker { |
| 44 | cnt += 1; |
| 45 | } else if ch != ' ' && ch != '\t' { |
| 46 | return None; |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | if cnt < 3 { return None; } |
| 51 | |
| 52 | let node = Node::new(ThematicBreak { marker, marker_len: cnt }); |
| 53 | Some((node, 1)) |
| 54 | } |
| 55 | } |