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 {
105 keep_level = i + 1;
106 }
107 crate::parser::utils::container_stack::Container::DefinitionList { .. }
108 if next_is_definition_marker || next_is_definition_term =>
109 {
110 keep_level = i + 1;
111 }
112 crate::parser::utils::container_stack::Container::List {
113 marker,
114 base_indent_cols,
115 ..
116 } => {
117 let definition_ancestor_kept = containers.stack[..i]
118 .iter()
119 .enumerate()
120 .rev()
121 .find_map(|(idx, container)| {
122 matches!(
123 container,
124 crate::parser::utils::container_stack::Container::Definition { .. }
125 )
126 .then_some(keep_level > idx)
127 })
128 .unwrap_or(true);
129 if !definition_ancestor_kept {
130 continue;
131 }
132
133 let effective_indent = raw_indent_cols.saturating_sub(content_indent_so_far);
134 let continues_list = if let Some(ref marker_match) = next_marker {
135 lists::markers_match(marker, &marker_match.marker)
136 && effective_indent <= base_indent_cols + 3
137 } else {
138 let item_content_col = containers
139 .stack
140 .get(i + 1)
141 .and_then(|c| match c {
142 crate::parser::utils::container_stack::Container::ListItem {
143 content_col,
144 ..
145 } => Some(*content_col),
146 _ => None,
147 })
148 .unwrap_or(1);
149 effective_indent >= item_content_col
150 };
151 if continues_list {
152 keep_level = i + 1;
153 }
154 }
155 crate::parser::utils::container_stack::Container::ListItem {
156 content_col, ..
157 } => {
158 let definition_ancestor_kept = containers.stack[..i]
159 .iter()
160 .enumerate()
161 .rev()
162 .find_map(|(idx, container)| {
163 matches!(
164 container,
165 crate::parser::utils::container_stack::Container::Definition { .. }
166 )
167 .then_some(keep_level > idx)
168 })
169 .unwrap_or(true);
170 if !definition_ancestor_kept {
171 continue;
172 }
173
174 let effective_indent = if next_bq_depth > current_bq_depth {
175 let after_current_bq =
176 strip_n_blockquote_markers(next_line, current_bq_depth);
177 let (spaces_before_next_marker, _) = leading_indent(after_current_bq);
178 spaces_before_next_marker.saturating_sub(content_indent_so_far)
179 } else {
180 raw_indent_cols.saturating_sub(content_indent_so_far)
181 };
182
183 let is_new_item_at_outer_level = if next_marker.is_some() {
184 effective_indent < *content_col
185 } else {
186 false
187 };
188
189 if !is_new_item_at_outer_level && effective_indent >= *content_col {
190 keep_level = i + 1;
191 }
192 }
193 _ => {}
194 }
195 }
196
197 keep_level
198 }
199
200 pub(crate) fn definition_plain_can_continue(
203 &self,
204 stripped_content: &str,
205 raw_content: &str,
206 content_indent: usize,
207 block_ctx: &BlockContext,
208 lines: &[&str],
209 pos: usize,
210 ) -> bool {
211 let prev_line_blank = if pos > 0 {
212 let prev_line = lines[pos - 1];
213 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
214 prev_line.trim().is_empty() || (prev_bq_depth > 0 && prev_inner.trim().is_empty())
215 } else {
216 false
217 };
218
219 let (indent_cols, _) = leading_indent(raw_content);
221 if raw_content.trim().is_empty() && indent_cols < content_indent {
222 return false;
223 }
224 let min_block_indent = self.definition_min_block_indent(content_indent);
225 if prev_line_blank && indent_cols < min_block_indent {
226 return false;
227 }
228
229 if definition_lists::try_parse_definition_marker(stripped_content).is_some()
231 && leading_indent(raw_content).0 <= 3
232 && !stripped_content.starts_with(':')
233 {
234 let is_next_definition = self
235 .block_registry
236 .detect_prepared(block_ctx, lines, pos)
237 .map(|match_result| {
238 match_result.effect
239 == crate::parser::block_dispatcher::BlockEffect::OpenDefinitionList
240 })
241 .unwrap_or(false);
242 if is_next_definition {
243 return false;
244 }
245 }
246 if lists::try_parse_list_marker(stripped_content, self.config).is_some() {
247 if prev_line_blank {
248 return false;
249 }
250 if block_ctx.in_list {
251 return false;
252 }
253 }
254 if count_blockquote_markers(stripped_content).0 > 0 {
255 return false;
256 }
257 if self.config.extensions.raw_html
258 && html_blocks::try_parse_html_block_start(stripped_content).is_some()
259 {
260 return false;
261 }
262 if self.config.extensions.raw_tex
263 && raw_blocks::extract_environment_name(stripped_content).is_some()
264 {
265 return false;
266 }
267
268 if let Some(match_result) = self.block_registry.detect_prepared(block_ctx, lines, pos) {
269 if match_result.effect == crate::parser::block_dispatcher::BlockEffect::OpenList
270 && !prev_line_blank
271 {
272 return true;
273 }
274 if match_result.effect
275 == crate::parser::block_dispatcher::BlockEffect::OpenDefinitionList
276 && match_result
277 .payload
278 .as_ref()
279 .and_then(|payload| {
280 payload
281 .downcast_ref::<crate::parser::block_dispatcher::DefinitionPrepared>()
282 })
283 .is_some_and(|prepared| {
284 matches!(
285 prepared,
286 crate::parser::block_dispatcher::DefinitionPrepared::Term { .. }
287 )
288 })
289 {
290 return true;
291 }
292 return false;
293 }
294
295 true
296 }
297}