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