panache_parser/parser/utils/
continuation.rs1use crate::options::{PandocCompat, ParserOptions};
8
9use crate::parser::block_dispatcher::{BlockContext, BlockParserRegistry};
10use crate::parser::blocks::blockquotes::{count_blockquote_markers, strip_n_blockquote_markers};
11use crate::parser::blocks::{definition_lists, html_blocks, lists, raw_blocks};
12use crate::parser::utils::container_stack::{ContainerStack, leading_indent};
13
14pub(crate) struct ContinuationPolicy<'a, 'cfg> {
15 config: &'cfg ParserOptions,
16 block_registry: &'a BlockParserRegistry,
17}
18
19impl<'a, 'cfg> ContinuationPolicy<'a, 'cfg> {
20 pub(crate) fn new(
21 config: &'cfg ParserOptions,
22 block_registry: &'a BlockParserRegistry,
23 ) -> Self {
24 Self {
25 config,
26 block_registry,
27 }
28 }
29
30 fn definition_min_block_indent(&self, content_col: usize) -> usize {
31 if self.config.effective_pandoc_compat() == PandocCompat::V3_7 {
32 content_col.max(4)
33 } else {
34 content_col
35 }
36 }
37
38 pub(crate) fn compute_levels_to_keep(
39 &self,
40 current_bq_depth: usize,
41 containers: &ContainerStack,
42 lines: &[&str],
43 next_line_pos: usize,
44 next_line: &str,
45 ) -> usize {
46 let (next_bq_depth, next_inner) = count_blockquote_markers(next_line);
47 let (raw_indent_cols, _) = leading_indent(next_inner);
48 let next_marker = lists::try_parse_list_marker(next_inner, self.config);
49 let next_is_definition_marker =
50 definition_lists::try_parse_definition_marker(next_inner).is_some();
51 let next_is_definition_term = !next_inner.trim().is_empty()
52 && definition_lists::next_line_is_definition_marker(lines, next_line_pos).is_some();
53
54 let mut keep_level = 0;
58 let mut content_indent_so_far = 0usize;
59
60 for (i, c) in containers.stack.iter().enumerate() {
62 match c {
63 crate::parser::utils::container_stack::Container::BlockQuote { .. } => {
64 let bq_count = containers.stack[..=i]
65 .iter()
66 .filter(|x| {
67 matches!(
68 x,
69 crate::parser::utils::container_stack::Container::BlockQuote { .. }
70 )
71 })
72 .count();
73 if bq_count <= next_bq_depth {
74 keep_level = i + 1;
75 }
76 }
77 crate::parser::utils::container_stack::Container::FootnoteDefinition {
78 content_col,
79 ..
80 } => {
81 content_indent_so_far += *content_col;
82 let min_indent = (*content_col).max(4);
83 if raw_indent_cols >= min_indent {
84 keep_level = i + 1;
85 }
86 }
87 crate::parser::utils::container_stack::Container::Definition {
88 content_col,
89 ..
90 } => {
91 let min_indent = self.definition_min_block_indent(*content_col);
96 let effective_indent = raw_indent_cols.saturating_sub(content_indent_so_far);
97 if effective_indent >= min_indent {
98 keep_level = i + 1;
99 }
100 content_indent_so_far += *content_col;
101 }
102 crate::parser::utils::container_stack::Container::DefinitionItem { .. } => {
103 if next_is_definition_marker {
104 keep_level = i + 1;
105 }
106 }
107 crate::parser::utils::container_stack::Container::DefinitionList { .. } => {
108 if next_is_definition_marker || next_is_definition_term {
109 keep_level = i + 1;
110 }
111 }
112 crate::parser::utils::container_stack::Container::List {
113 marker,
114 base_indent_cols,
115 ..
116 } => {
117 let effective_indent = raw_indent_cols.saturating_sub(content_indent_so_far);
118 let continues_list = if let Some(ref marker_match) = next_marker {
119 lists::markers_match(marker, &marker_match.marker)
120 && effective_indent <= base_indent_cols + 3
121 } else {
122 let item_content_col = containers
123 .stack
124 .get(i + 1)
125 .and_then(|c| match c {
126 crate::parser::utils::container_stack::Container::ListItem {
127 content_col,
128 ..
129 } => Some(*content_col),
130 _ => None,
131 })
132 .unwrap_or(1);
133 effective_indent >= item_content_col
134 };
135 if continues_list {
136 keep_level = i + 1;
137 }
138 }
139 crate::parser::utils::container_stack::Container::ListItem {
140 content_col, ..
141 } => {
142 let effective_indent = if next_bq_depth > current_bq_depth {
143 let after_current_bq =
144 strip_n_blockquote_markers(next_line, current_bq_depth);
145 let (spaces_before_next_marker, _) = leading_indent(after_current_bq);
146 spaces_before_next_marker.saturating_sub(content_indent_so_far)
147 } else {
148 raw_indent_cols.saturating_sub(content_indent_so_far)
149 };
150
151 let is_new_item_at_outer_level = if next_marker.is_some() {
152 effective_indent < *content_col
153 } else {
154 false
155 };
156
157 if !is_new_item_at_outer_level && effective_indent >= *content_col {
158 keep_level = i + 1;
159 }
160 }
161 _ => {}
162 }
163 }
164
165 keep_level
166 }
167
168 pub(crate) fn definition_plain_can_continue(
171 &self,
172 stripped_content: &str,
173 raw_content: &str,
174 content_indent: usize,
175 block_ctx: &BlockContext,
176 lines: &[&str],
177 pos: usize,
178 ) -> bool {
179 let prev_line_blank = if pos > 0 {
180 let prev_line = lines[pos - 1];
181 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
182 prev_line.trim().is_empty() || (prev_bq_depth > 0 && prev_inner.trim().is_empty())
183 } else {
184 false
185 };
186
187 let (indent_cols, _) = leading_indent(raw_content);
189 if raw_content.trim().is_empty() && indent_cols < content_indent {
190 return false;
191 }
192 let min_block_indent = self.definition_min_block_indent(content_indent);
193 if prev_line_blank && indent_cols < min_block_indent {
194 return false;
195 }
196
197 if definition_lists::try_parse_definition_marker(stripped_content).is_some()
199 && leading_indent(raw_content).0 <= 3
200 && !stripped_content.starts_with(':')
201 {
202 let is_next_definition = self
203 .block_registry
204 .detect_prepared(block_ctx, lines, pos)
205 .map(|match_result| {
206 match_result.effect
207 == crate::parser::block_dispatcher::BlockEffect::OpenDefinitionList
208 })
209 .unwrap_or(false);
210 if is_next_definition {
211 return false;
212 }
213 }
214 if lists::try_parse_list_marker(stripped_content, self.config).is_some() {
215 if prev_line_blank {
216 return false;
217 }
218 if block_ctx.in_list {
219 return false;
220 }
221 }
222 if count_blockquote_markers(stripped_content).0 > 0 {
223 return false;
224 }
225 if self.config.extensions.raw_html
226 && html_blocks::try_parse_html_block_start(stripped_content).is_some()
227 {
228 return false;
229 }
230 if self.config.extensions.raw_tex
231 && raw_blocks::extract_environment_name(stripped_content).is_some()
232 {
233 return false;
234 }
235
236 if let Some(match_result) = self.block_registry.detect_prepared(block_ctx, lines, pos) {
237 if match_result.effect == crate::parser::block_dispatcher::BlockEffect::OpenList
238 && !prev_line_blank
239 {
240 return true;
241 }
242 if match_result.effect
243 == crate::parser::block_dispatcher::BlockEffect::OpenDefinitionList
244 && match_result
245 .payload
246 .as_ref()
247 .and_then(|payload| {
248 payload
249 .downcast_ref::<crate::parser::block_dispatcher::DefinitionPrepared>()
250 })
251 .is_some_and(|prepared| {
252 matches!(
253 prepared,
254 crate::parser::block_dispatcher::DefinitionPrepared::Term { .. }
255 )
256 })
257 {
258 return true;
259 }
260 return false;
261 }
262
263 true
264 }
265}