| 1 | //! Line breaks |
| 2 | //! |
| 3 | //! Processes EOL (`\n`, soft and hard breaks). |
| 4 | //! |
| 5 | //! - <https://spec.commonmark.org/0.30/#hard-line-breaks> |
| 6 | //! - <https://spec.commonmark.org/0.30/#soft-line-breaks> |
| 7 | use crate::parser::inline::{InlineRule, InlineState}; |
| 8 | use crate::{MarkdownIt, Node, NodeValue, Renderer}; |
| 9 | |
| 10 | #[derive(Debug)] |
| 11 | pub struct Hardbreak; |
| 12 | |
| 13 | impl NodeValue for Hardbreak { |
| 14 | fn render(&self, _: &Node, fmt: &mut dyn Renderer) { |
| 15 | fmt.self_close("br", &[]); |
| 16 | fmt.cr(); |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | #[derive(Debug)] |
| 21 | pub struct Softbreak; |
| 22 | |
| 23 | impl NodeValue for Softbreak { |
| 24 | fn render(&self, _: &Node, fmt: &mut dyn Renderer) { |
| 25 | fmt.cr(); |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | pub fn add(md: &mut MarkdownIt) { |
| 30 | md.inline.add_rule::<NewlineScanner>(); |
| 31 | } |
| 32 | |
| 33 | #[doc(hidden)] |
| 34 | pub struct NewlineScanner; |
| 35 | impl InlineRule for NewlineScanner { |
| 36 | const MARKER: char = '\n'; |
| 37 | |
| 38 | fn check(state: &mut InlineState) -> Option<usize> { |
| 39 | // check rule is required because run() modifies trailing text |
| 40 | let mut chars = state.src[state.pos..state.pos_max].chars(); |
| 41 | if chars.next().unwrap() != '\n' { return None; } |
| 42 | Some(1) |
| 43 | } |
| 44 | |
| 45 | fn run(state: &mut InlineState) -> Option<(Node, usize)> { |
| 46 | let mut chars = state.src[state.pos..state.pos_max].chars(); |
| 47 | |
| 48 | if chars.next().unwrap() != '\n' { return None; } |
| 49 | |
| 50 | let mut pos = state.pos; |
| 51 | pos += 1; |
| 52 | |
| 53 | // skip leading whitespaces from next line |
| 54 | while let Some(' ' | '\t') = chars.next() { |
| 55 | pos += 1; |
| 56 | } |
| 57 | |
| 58 | // ' \n' -> hardbreak |
| 59 | let mut tail_size = 0; |
| 60 | let trailing_text = state.trailing_text_get(); |
| 61 | |
| 62 | for ch in trailing_text.chars().rev() { |
| 63 | if ch == ' ' { |
| 64 | tail_size += 1; |
| 65 | } else { |
| 66 | break; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | state.trailing_text_pop(tail_size); |
| 71 | |
| 72 | let node = if tail_size >= 2 { |
| 73 | Node::new(Hardbreak) |
| 74 | } else { |
| 75 | Node::new(Softbreak) |
| 76 | }; |
| 77 | |
| 78 | state.pos -= tail_size; // backtrack to include tail in source maps |
| 79 | Some((node, pos - state.pos)) |
| 80 | } |
| 81 | } |