markdown_that/plugins/cmark/block/
blockquote.rs1use crate::common::utils::find_indent_of;
7use crate::parser::block::{BlockRule, BlockState};
8use crate::{MarkdownThat, Node, NodeValue, Renderer};
9
10#[derive(Debug)]
11pub struct Blockquote;
12
13impl NodeValue for Blockquote {
14 fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
15 fmt.cr();
16 fmt.open("blockquote", &node.attrs);
17 fmt.cr();
18 fmt.contents(&node.children);
19 fmt.cr();
20 fmt.close("blockquote");
21 fmt.cr();
22 }
23}
24
25pub fn add(md: &mut MarkdownThat) {
26 md.block.add_rule::<BlockquoteScanner>();
27}
28
29#[doc(hidden)]
30pub struct BlockquoteScanner;
31impl BlockRule for BlockquoteScanner {
32 fn check(state: &mut BlockState) -> Option<()> {
33 if state.line_indent(state.line) >= state.md.max_indent {
34 return None;
35 }
36
37 let Some('>') = state.get_line(state.line).chars().next() else {
39 return None;
40 };
41
42 Some(())
43 }
44
45 fn run(state: &mut BlockState) -> Option<(Node, usize)> {
46 Self::check(state)?;
47
48 let mut old_line_offsets = Vec::new();
49 let start_line = state.line;
50 let mut next_line = state.line;
51 let mut last_line_empty = false;
52
53 while next_line < state.line_max {
72 let is_outdented = state.line_indent(next_line) < 0;
81 let line = state.get_line(next_line).to_owned();
82 let mut chars = line.chars();
83
84 match chars.next() {
85 None => {
86 break;
88 }
89 Some('>') if !is_outdented => {
90 let offsets = &state.line_offsets[next_line];
94 let pos_after_marker = offsets.first_nonspace + 1;
95
96 old_line_offsets.push(state.line_offsets[next_line].clone());
97
98 let (mut indent_after_marker, first_nonspace) = find_indent_of(
99 &state.src[offsets.line_start..offsets.line_end],
100 pos_after_marker - offsets.line_start,
101 );
102
103 last_line_empty = first_nonspace == offsets.line_end - offsets.line_start;
104
105 if matches!(chars.next(), Some(' ' | '\t')) {
107 indent_after_marker -= 1;
108 }
109
110 state.line_offsets[next_line].indent_nonspace = indent_after_marker as i32;
111 state.line_offsets[next_line].first_nonspace =
112 first_nonspace + state.line_offsets[next_line].line_start;
113 next_line += 1;
114 continue;
115 }
116 _ => {}
117 }
118
119 if last_line_empty {
121 break;
122 }
123
124 state.line = next_line;
126
127 if state.test_rules_at_line() {
128 if state.blk_indent != 0 {
135 old_line_offsets.push(state.line_offsets[next_line].clone());
139 state.line_offsets[next_line].indent_nonspace -= state.blk_indent as i32;
140 }
141
142 break;
143 }
144
145 old_line_offsets.push(state.line_offsets[next_line].clone());
146
147 state.line_offsets[next_line].indent_nonspace = -1;
150 next_line += 1;
151 }
152
153 let old_indent = state.blk_indent;
154 state.blk_indent = 0;
155
156 let old_node = std::mem::replace(&mut state.node, Node::new(Blockquote));
157 let old_line_max = state.line_max;
158 state.line = start_line;
159 state.line_max = next_line;
160 state.md.block.tokenize(state);
161 next_line = state.line;
162 state.line = start_line;
163 state.line_max = old_line_max;
164
165 for (idx, line_offset) in old_line_offsets.iter_mut().enumerate() {
168 std::mem::swap(&mut state.line_offsets[idx + start_line], line_offset);
169 }
170 state.blk_indent = old_indent;
171
172 let node = std::mem::replace(&mut state.node, old_node);
173 Some((node, next_line - start_line))
174 }
175}