1use crate::options::ParserOptions;
2use crate::syntax::{SyntaxKind, SyntaxNode};
3use rowan::GreenNodeBuilder;
4
5use super::block_dispatcher::{
6 BlockContext, BlockDetectionResult, BlockEffect, BlockParserRegistry, BlockQuotePrepared,
7 PreparedBlockMatch,
8};
9use super::blocks::blockquotes;
10use super::blocks::code_blocks;
11use super::blocks::definition_lists;
12use super::blocks::fenced_divs;
13use super::blocks::headings::{emit_atx_heading, try_parse_atx_heading};
14use super::blocks::horizontal_rules::try_parse_horizontal_rule;
15use super::blocks::line_blocks;
16use super::blocks::lists;
17use super::blocks::paragraphs;
18use super::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
19use super::utils::container_stack;
20use super::utils::helpers::{split_lines_inclusive, strip_newline};
21use super::utils::inline_emission;
22use super::utils::marker_utils;
23use super::utils::text_buffer;
24
25use super::blocks::blockquotes::strip_n_blockquote_markers;
26use super::utils::continuation::ContinuationPolicy;
27use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
28use definition_lists::{emit_definition_marker, emit_term};
29use line_blocks::{parse_line_block, try_parse_line_block_start};
30use lists::{
31 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
32 try_parse_list_marker,
33};
34use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
35use text_buffer::TextBuffer;
36
37const GITHUB_ALERT_MARKERS: [&str; 5] = [
38 "[!TIP]",
39 "[!WARNING]",
40 "[!IMPORTANT]",
41 "[!CAUTION]",
42 "[!NOTE]",
43];
44
45pub struct Parser<'a> {
46 lines: Vec<&'a str>,
47 pos: usize,
48 builder: GreenNodeBuilder<'static>,
49 containers: ContainerStack,
50 config: &'a ParserOptions,
51 block_registry: BlockParserRegistry,
52 after_metadata_block: bool,
56}
57
58impl<'a> Parser<'a> {
59 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
60 let lines = split_lines_inclusive(input);
62 Self {
63 lines,
64 pos: 0,
65 builder: GreenNodeBuilder::new(),
66 containers: ContainerStack::new(),
67 config,
68 block_registry: BlockParserRegistry::new(),
69 after_metadata_block: false,
70 }
71 }
72
73 pub fn parse(mut self) -> SyntaxNode {
74 self.parse_document_stack();
75
76 SyntaxNode::new_root(self.builder.finish())
77 }
78
79 fn close_lists_above_indent(&mut self, indent_cols: usize) {
90 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
91 if indent_cols >= *content_col {
92 break;
93 }
94 self.close_containers_to(self.containers.depth() - 1);
95 if matches!(self.containers.last(), Some(Container::List { .. })) {
96 self.close_containers_to(self.containers.depth() - 1);
97 }
98 }
99 }
100
101 fn close_containers_to(&mut self, keep: usize) {
104 while self.containers.depth() > keep {
106 match self.containers.stack.last() {
107 Some(Container::ListItem { buffer, .. }) if !buffer.is_empty() => {
109 let buffer_clone = buffer.clone();
111
112 log::trace!(
113 "Closing ListItem with buffer (is_empty={}, segment_count={})",
114 buffer_clone.is_empty(),
115 buffer_clone.segment_count()
116 );
117
118 let parent_list_is_loose = self
122 .containers
123 .stack
124 .iter()
125 .rev()
126 .find_map(|c| match c {
127 Container::List {
128 has_blank_between_items,
129 ..
130 } => Some(*has_blank_between_items),
131 _ => None,
132 })
133 .unwrap_or(false);
134
135 let use_paragraph =
136 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
137
138 log::trace!(
139 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
140 use_paragraph,
141 parent_list_is_loose,
142 buffer_clone.has_blank_lines_between_content()
143 );
144
145 self.containers.stack.pop();
147 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
149 self.builder.finish_node(); }
151 Some(Container::ListItem { .. }) => {
153 log::trace!("Closing empty ListItem (no buffer content)");
154 self.containers.stack.pop();
156 self.builder.finish_node();
157 }
158 Some(Container::Paragraph { buffer, .. }) if !buffer.is_empty() => {
160 let buffer_clone = buffer.clone();
162 self.containers.stack.pop();
164 buffer_clone.emit_with_inlines(&mut self.builder, self.config);
166 self.builder.finish_node();
167 }
168 Some(Container::Paragraph { .. }) => {
170 self.containers.stack.pop();
172 self.builder.finish_node();
173 }
174 Some(Container::Definition {
176 plain_open: true,
177 plain_buffer,
178 ..
179 }) if !plain_buffer.is_empty() => {
180 let text = plain_buffer.get_accumulated_text();
181 let line_without_newline = text
182 .strip_suffix("\r\n")
183 .or_else(|| text.strip_suffix('\n'));
184 if let Some(line) = line_without_newline
185 && !line.contains('\n')
186 && !line.contains('\r')
187 && let Some(level) = try_parse_atx_heading(line)
188 {
189 emit_atx_heading(&mut self.builder, &text, level, self.config);
190 } else {
191 self.builder.start_node(SyntaxKind::PLAIN.into());
193 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
194 self.builder.finish_node();
195 }
196
197 if let Some(Container::Definition {
199 plain_open,
200 plain_buffer,
201 ..
202 }) = self.containers.stack.last_mut()
203 {
204 plain_buffer.clear();
205 *plain_open = false;
206 }
207
208 self.containers.stack.pop();
210 self.builder.finish_node();
211 }
212 Some(Container::Definition {
214 plain_open: true, ..
215 }) => {
216 if let Some(Container::Definition {
218 plain_open,
219 plain_buffer,
220 ..
221 }) = self.containers.stack.last_mut()
222 {
223 plain_buffer.clear();
224 *plain_open = false;
225 }
226
227 self.containers.stack.pop();
229 self.builder.finish_node();
230 }
231 _ => {
233 self.containers.stack.pop();
234 self.builder.finish_node();
235 }
236 }
237 }
238 }
239
240 fn emit_buffered_plain_if_needed(&mut self) {
243 if let Some(Container::Definition {
245 plain_open: true,
246 plain_buffer,
247 ..
248 }) = self.containers.stack.last()
249 && !plain_buffer.is_empty()
250 {
251 let text = plain_buffer.get_accumulated_text();
252 let line_without_newline = text
253 .strip_suffix("\r\n")
254 .or_else(|| text.strip_suffix('\n'));
255 if let Some(line) = line_without_newline
256 && !line.contains('\n')
257 && !line.contains('\r')
258 && let Some(level) = try_parse_atx_heading(line)
259 {
260 emit_atx_heading(&mut self.builder, &text, level, self.config);
261 } else {
262 self.builder.start_node(SyntaxKind::PLAIN.into());
264 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
265 self.builder.finish_node();
266 }
267 }
268
269 if let Some(Container::Definition {
271 plain_open,
272 plain_buffer,
273 ..
274 }) = self.containers.stack.last_mut()
275 && *plain_open
276 {
277 plain_buffer.clear();
278 *plain_open = false;
279 }
280 }
281
282 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
287 let mut current = self.current_blockquote_depth();
288 while current > target_depth {
289 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
290 if self.containers.depth() == 0 {
291 break;
292 }
293 self.close_containers_to(self.containers.depth() - 1);
294 }
295 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
296 self.close_containers_to(self.containers.depth() - 1);
297 current -= 1;
298 } else {
299 break;
300 }
301 }
302 }
303
304 fn active_alert_blockquote_depth(&self) -> Option<usize> {
305 self.containers.stack.iter().rev().find_map(|c| match c {
306 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
307 _ => None,
308 })
309 }
310
311 fn in_active_alert(&self) -> bool {
312 self.active_alert_blockquote_depth().is_some()
313 }
314
315 fn previous_block_requires_blank_before_heading(&self) -> bool {
316 matches!(
317 self.containers.last(),
318 Some(Container::Paragraph { .. })
319 | Some(Container::ListItem { .. })
320 | Some(Container::Definition { .. })
321 | Some(Container::DefinitionItem { .. })
322 | Some(Container::FootnoteDefinition { .. })
323 )
324 }
325
326 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
327 let (without_newline, _) = strip_newline(content);
328 let trimmed = without_newline.trim();
329 GITHUB_ALERT_MARKERS
330 .into_iter()
331 .find(|marker| *marker == trimmed)
332 }
333
334 fn emit_list_item_buffer_if_needed(&mut self) {
337 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut()
338 && !buffer.is_empty()
339 {
340 let buffer_clone = buffer.clone();
341 buffer.clear();
342 let use_paragraph = buffer_clone.has_blank_lines_between_content();
343 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
344 }
345 }
346
347 fn maybe_open_fenced_code_in_new_list_item(&mut self) {
359 let Some(Container::ListItem {
360 content_col,
361 buffer,
362 }) = self.containers.stack.last()
363 else {
364 return;
365 };
366 let content_col = *content_col;
367 let Some(text) = buffer.first_text() else {
368 return;
369 };
370 if buffer.segment_count() != 1 {
371 return;
372 }
373 let text_owned = text.to_string();
374 let Some(fence) = code_blocks::try_parse_fence_open(&text_owned) else {
375 return;
376 };
377 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
378 let has_info = !fence.info_string.trim().is_empty();
379 let bq_depth = self.current_blockquote_depth();
380 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
381 if !(has_info || has_matching_closer || common_mark_dialect) {
382 return;
383 }
384 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
386 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
387 {
388 return;
389 }
390 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
391 buffer.clear();
392 }
393 let new_pos = code_blocks::parse_fenced_code_block(
394 &mut self.builder,
395 &self.lines,
396 self.pos,
397 fence,
398 bq_depth,
399 content_col,
400 Some(&text_owned),
401 );
402 self.pos = new_pos.saturating_sub(1);
406 }
407
408 fn has_matching_fence_closer(
409 &self,
410 fence: &code_blocks::FenceInfo,
411 bq_depth: usize,
412 content_col: usize,
413 ) -> bool {
414 for raw_line in self.lines.iter().skip(self.pos + 1) {
415 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
416 if line_bq_depth < bq_depth {
417 break;
418 }
419 let candidate = if content_col > 0 && !inner.is_empty() {
420 let idx = byte_index_at_column(inner, content_col);
421 if idx <= inner.len() {
422 &inner[idx..]
423 } else {
424 inner
425 }
426 } else {
427 inner
428 };
429 if code_blocks::is_closing_fence(candidate, fence) {
430 return true;
431 }
432 }
433 false
434 }
435
436 fn is_paragraph_open(&self) -> bool {
438 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
439 }
440
441 fn close_paragraph_if_open(&mut self) {
443 if self.is_paragraph_open() {
444 self.close_containers_to(self.containers.depth() - 1);
445 }
446 }
447
448 fn prepare_for_block_element(&mut self) {
451 self.emit_list_item_buffer_if_needed();
452 self.close_paragraph_if_open();
453 }
454
455 fn close_open_footnote_definition(&mut self) {
459 while matches!(
460 self.containers.last(),
461 Some(Container::FootnoteDefinition { .. })
462 ) {
463 self.close_containers_to(self.containers.depth() - 1);
464 }
465 }
466
467 fn handle_footnote_open_effect(
468 &mut self,
469 block_match: &super::block_dispatcher::PreparedBlockMatch,
470 content: &str,
471 ) {
472 let content_start = block_match
473 .payload
474 .as_ref()
475 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
476 .map(|p| p.content_start)
477 .unwrap_or(0);
478
479 let content_col = 4;
480 self.containers
481 .push(Container::FootnoteDefinition { content_col });
482
483 if content_start > 0 {
484 let first_line_content = &content[content_start..];
485 if !first_line_content.trim().is_empty() {
486 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
487 paragraphs::append_paragraph_line(
488 &mut self.containers,
489 &mut self.builder,
490 first_line_content,
491 self.config,
492 );
493 } else {
494 let (_, newline_str) = strip_newline(content);
495 if !newline_str.is_empty() {
496 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
497 }
498 }
499 }
500 }
501
502 fn handle_list_open_effect(
503 &mut self,
504 block_match: &super::block_dispatcher::PreparedBlockMatch,
505 content: &str,
506 indent_to_emit: Option<&str>,
507 ) {
508 use super::block_dispatcher::ListPrepared;
509
510 let prepared = block_match
511 .payload
512 .as_ref()
513 .and_then(|p| p.downcast_ref::<ListPrepared>());
514 let Some(prepared) = prepared else {
515 return;
516 };
517
518 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
519 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
520 paragraphs::append_paragraph_line(
521 &mut self.containers,
522 &mut self.builder,
523 content,
524 self.config,
525 );
526 return;
527 }
528
529 if self.is_paragraph_open() {
530 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
531 paragraphs::append_paragraph_line(
532 &mut self.containers,
533 &mut self.builder,
534 content,
535 self.config,
536 );
537 return;
538 }
539 self.close_containers_to(self.containers.depth() - 1);
540 }
541
542 if matches!(
543 self.containers.last(),
544 Some(Container::Definition {
545 plain_open: true,
546 ..
547 })
548 ) {
549 self.emit_buffered_plain_if_needed();
550 }
551
552 let matched_level = lists::find_matching_list_level(
553 &self.containers,
554 &prepared.marker,
555 prepared.indent_cols,
556 );
557 let list_item = ListItemEmissionInput {
558 content,
559 marker_len: prepared.marker_len,
560 spaces_after_cols: prepared.spaces_after_cols,
561 spaces_after_bytes: prepared.spaces_after,
562 indent_cols: prepared.indent_cols,
563 indent_bytes: prepared.indent_bytes,
564 };
565 let current_content_col = paragraphs::current_content_col(&self.containers);
566 let deep_ordered_matched_level = matched_level
567 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
568 .and_then(|(level, container)| match container {
569 Container::List {
570 marker: list_marker,
571 base_indent_cols,
572 ..
573 } if matches!(
574 (&prepared.marker, list_marker),
575 (ListMarker::Ordered(_), ListMarker::Ordered(_))
576 ) && prepared.indent_cols >= 4
577 && *base_indent_cols >= 4
578 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
579 {
580 Some(level)
581 }
582 _ => None,
583 });
584
585 if deep_ordered_matched_level.is_none()
586 && current_content_col > 0
587 && prepared.indent_cols >= current_content_col
588 {
589 if let Some(level) = matched_level
590 && let Some(Container::List {
591 base_indent_cols, ..
592 }) = self.containers.stack.get(level)
593 && prepared.indent_cols == *base_indent_cols
594 {
595 let num_parent_lists = self.containers.stack[..level]
596 .iter()
597 .filter(|c| matches!(c, Container::List { .. }))
598 .count();
599
600 if num_parent_lists > 0 {
601 self.close_containers_to(level + 1);
602
603 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
604 self.close_containers_to(self.containers.depth() - 1);
605 }
606 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
607 self.close_containers_to(self.containers.depth() - 1);
608 }
609
610 if let Some(indent_str) = indent_to_emit {
611 self.builder
612 .token(SyntaxKind::WHITESPACE.into(), indent_str);
613 }
614
615 if let Some(nested_marker) = prepared.nested_marker {
616 lists::add_list_item_with_nested_empty_list(
617 &mut self.containers,
618 &mut self.builder,
619 &list_item,
620 nested_marker,
621 );
622 } else {
623 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
624 }
625 self.maybe_open_fenced_code_in_new_list_item();
626 return;
627 }
628 }
629
630 self.emit_list_item_buffer_if_needed();
631
632 start_nested_list(
633 &mut self.containers,
634 &mut self.builder,
635 &prepared.marker,
636 &list_item,
637 indent_to_emit,
638 );
639 self.maybe_open_fenced_code_in_new_list_item();
640 return;
641 }
642
643 if let Some(level) = matched_level {
644 self.close_containers_to(level + 1);
645
646 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
647 self.close_containers_to(self.containers.depth() - 1);
648 }
649 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
650 self.close_containers_to(self.containers.depth() - 1);
651 }
652
653 if let Some(indent_str) = indent_to_emit {
654 self.builder
655 .token(SyntaxKind::WHITESPACE.into(), indent_str);
656 }
657
658 if let Some(nested_marker) = prepared.nested_marker {
659 lists::add_list_item_with_nested_empty_list(
660 &mut self.containers,
661 &mut self.builder,
662 &list_item,
663 nested_marker,
664 );
665 } else {
666 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
667 }
668 self.maybe_open_fenced_code_in_new_list_item();
669 return;
670 }
671
672 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
673 self.close_containers_to(self.containers.depth() - 1);
674 }
675 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
676 self.close_containers_to(self.containers.depth() - 1);
677 }
678 while matches!(self.containers.last(), Some(Container::List { .. })) {
679 self.close_containers_to(self.containers.depth() - 1);
680 }
681
682 self.builder.start_node(SyntaxKind::LIST.into());
683 if let Some(indent_str) = indent_to_emit {
684 self.builder
685 .token(SyntaxKind::WHITESPACE.into(), indent_str);
686 }
687 self.containers.push(Container::List {
688 marker: prepared.marker.clone(),
689 base_indent_cols: prepared.indent_cols,
690 has_blank_between_items: false,
691 });
692
693 if let Some(nested_marker) = prepared.nested_marker {
694 lists::add_list_item_with_nested_empty_list(
695 &mut self.containers,
696 &mut self.builder,
697 &list_item,
698 nested_marker,
699 );
700 } else {
701 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
702 }
703 self.maybe_open_fenced_code_in_new_list_item();
704 }
705
706 fn handle_definition_list_effect(
707 &mut self,
708 block_match: &super::block_dispatcher::PreparedBlockMatch,
709 content: &str,
710 indent_to_emit: Option<&str>,
711 ) {
712 use super::block_dispatcher::DefinitionPrepared;
713
714 let prepared = block_match
715 .payload
716 .as_ref()
717 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
718 let Some(prepared) = prepared else {
719 return;
720 };
721
722 match prepared {
723 DefinitionPrepared::Definition {
724 marker_char,
725 indent,
726 spaces_after,
727 spaces_after_cols,
728 has_content,
729 } => {
730 self.emit_buffered_plain_if_needed();
731
732 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
733 self.close_containers_to(self.containers.depth() - 1);
734 }
735 while matches!(self.containers.last(), Some(Container::List { .. })) {
736 self.close_containers_to(self.containers.depth() - 1);
737 }
738
739 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
740 self.close_containers_to(self.containers.depth() - 1);
741 }
742
743 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
744 self.close_containers_to(self.containers.depth() - 1);
745 }
746
747 if definition_lists::in_definition_list(&self.containers)
751 && !matches!(
752 self.containers.last(),
753 Some(Container::DefinitionItem { .. })
754 )
755 {
756 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
757 self.containers.push(Container::DefinitionItem {});
758 }
759
760 if !definition_lists::in_definition_list(&self.containers) {
761 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
762 self.containers.push(Container::DefinitionList {});
763 }
764
765 if !matches!(
766 self.containers.last(),
767 Some(Container::DefinitionItem { .. })
768 ) {
769 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
770 self.containers.push(Container::DefinitionItem {});
771 }
772
773 self.builder.start_node(SyntaxKind::DEFINITION.into());
774
775 if let Some(indent_str) = indent_to_emit {
776 self.builder
777 .token(SyntaxKind::WHITESPACE.into(), indent_str);
778 }
779
780 emit_definition_marker(&mut self.builder, *marker_char, *indent);
781 let indent_bytes = byte_index_at_column(content, *indent);
782 if *spaces_after > 0 {
783 let space_start = indent_bytes + 1;
784 let space_end = space_start + *spaces_after;
785 if space_end <= content.len() {
786 self.builder.token(
787 SyntaxKind::WHITESPACE.into(),
788 &content[space_start..space_end],
789 );
790 }
791 }
792
793 if !*has_content {
794 let current_line = self.lines[self.pos];
795 let (_, newline_str) = strip_newline(current_line);
796 if !newline_str.is_empty() {
797 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
798 }
799 }
800
801 let content_col = *indent + 1 + *spaces_after_cols;
802 let content_start_bytes = indent_bytes + 1 + *spaces_after;
803 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
804 let mut plain_buffer = TextBuffer::new();
805 let mut definition_pushed = false;
806
807 if *has_content {
808 let current_line = self.lines[self.pos];
809 let (trimmed_line, _) = strip_newline(current_line);
810
811 let content_start = content_start_bytes.min(trimmed_line.len());
812 let content_slice = &trimmed_line[content_start..];
813 let content_line = ¤t_line[content_start_bytes.min(current_line.len())..];
814
815 let (blockquote_depth, inner_blockquote_content) =
816 count_blockquote_markers(content_line);
817
818 let should_start_list_from_first_line = self
819 .lines
820 .get(self.pos + 1)
821 .map(|next_line| {
822 let (next_without_newline, _) = strip_newline(next_line);
823 if next_without_newline.trim().is_empty() {
824 return false;
825 }
826
827 let (next_indent_cols, _) = leading_indent(next_without_newline);
828 next_indent_cols >= content_col
829 })
830 .unwrap_or(false);
831
832 if blockquote_depth > 0 {
833 self.containers.push(Container::Definition {
834 content_col,
835 plain_open: false,
836 plain_buffer: TextBuffer::new(),
837 });
838 definition_pushed = true;
839
840 let marker_info = parse_blockquote_marker_info(content_line);
841 for level in 0..blockquote_depth {
842 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
843 if let Some(info) = marker_info.get(level) {
844 blockquotes::emit_one_blockquote_marker(
845 &mut self.builder,
846 info.leading_spaces,
847 info.has_trailing_space,
848 );
849 }
850 self.containers.push(Container::BlockQuote {});
851 }
852
853 if !inner_blockquote_content.trim().is_empty() {
854 paragraphs::start_paragraph_if_needed(
855 &mut self.containers,
856 &mut self.builder,
857 );
858 paragraphs::append_paragraph_line(
859 &mut self.containers,
860 &mut self.builder,
861 inner_blockquote_content,
862 self.config,
863 );
864 }
865 } else if let Some(marker_match) =
866 try_parse_list_marker(content_slice, self.config)
867 && should_start_list_from_first_line
868 {
869 self.containers.push(Container::Definition {
870 content_col,
871 plain_open: false,
872 plain_buffer: TextBuffer::new(),
873 });
874 definition_pushed = true;
875
876 let (indent_cols, indent_bytes) = leading_indent(content_line);
877 self.builder.start_node(SyntaxKind::LIST.into());
878 self.containers.push(Container::List {
879 marker: marker_match.marker.clone(),
880 base_indent_cols: indent_cols,
881 has_blank_between_items: false,
882 });
883
884 let list_item = ListItemEmissionInput {
885 content: content_line,
886 marker_len: marker_match.marker_len,
887 spaces_after_cols: marker_match.spaces_after_cols,
888 spaces_after_bytes: marker_match.spaces_after_bytes,
889 indent_cols,
890 indent_bytes,
891 };
892
893 if let Some(nested_marker) = is_content_nested_bullet_marker(
894 content_line,
895 marker_match.marker_len,
896 marker_match.spaces_after_bytes,
897 ) {
898 lists::add_list_item_with_nested_empty_list(
899 &mut self.containers,
900 &mut self.builder,
901 &list_item,
902 nested_marker,
903 );
904 } else {
905 lists::add_list_item(
906 &mut self.containers,
907 &mut self.builder,
908 &list_item,
909 );
910 }
911 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
912 self.containers.push(Container::Definition {
913 content_col,
914 plain_open: false,
915 plain_buffer: TextBuffer::new(),
916 });
917 definition_pushed = true;
918
919 let bq_depth = self.current_blockquote_depth();
920 if let Some(indent_str) = indent_to_emit {
921 self.builder
922 .token(SyntaxKind::WHITESPACE.into(), indent_str);
923 }
924 let fence_line = current_line[content_start..].to_string();
925 let new_pos = if self.config.extensions.tex_math_gfm
926 && code_blocks::is_gfm_math_fence(&fence)
927 {
928 code_blocks::parse_fenced_math_block(
929 &mut self.builder,
930 &self.lines,
931 self.pos,
932 fence,
933 bq_depth,
934 content_col,
935 Some(&fence_line),
936 )
937 } else {
938 code_blocks::parse_fenced_code_block(
939 &mut self.builder,
940 &self.lines,
941 self.pos,
942 fence,
943 bq_depth,
944 content_col,
945 Some(&fence_line),
946 )
947 };
948 self.pos = new_pos - 1;
949 } else {
950 let (_, newline_str) = strip_newline(current_line);
951 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
952 if content_without_newline.is_empty() {
953 plain_buffer.push_line(newline_str);
954 } else {
955 let line_with_newline = if !newline_str.is_empty() {
956 format!("{}{}", content_without_newline, newline_str)
957 } else {
958 content_without_newline.to_string()
959 };
960 plain_buffer.push_line(line_with_newline);
961 }
962 }
963 }
964
965 if !definition_pushed {
966 self.containers.push(Container::Definition {
967 content_col,
968 plain_open: *has_content,
969 plain_buffer,
970 });
971 }
972 }
973 DefinitionPrepared::Term { blank_count } => {
974 self.emit_buffered_plain_if_needed();
975
976 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
977 self.close_containers_to(self.containers.depth() - 1);
978 }
979
980 if !definition_lists::in_definition_list(&self.containers) {
981 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
982 self.containers.push(Container::DefinitionList {});
983 }
984
985 while matches!(
986 self.containers.last(),
987 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
988 ) {
989 self.close_containers_to(self.containers.depth() - 1);
990 }
991
992 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
993 self.containers.push(Container::DefinitionItem {});
994
995 emit_term(&mut self.builder, content, self.config);
996
997 for i in 0..*blank_count {
998 let blank_pos = self.pos + 1 + i;
999 if blank_pos < self.lines.len() {
1000 let blank_line = self.lines[blank_pos];
1001 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1002 self.builder
1003 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1004 self.builder.finish_node();
1005 }
1006 }
1007 self.pos += *blank_count;
1008 }
1009 }
1010 }
1011
1012 fn blockquote_marker_info(
1014 &self,
1015 payload: Option<&BlockQuotePrepared>,
1016 line: &str,
1017 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1018 payload
1019 .map(|payload| payload.marker_info.clone())
1020 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1021 }
1022
1023 fn marker_info_for_line(
1029 &self,
1030 payload: Option<&BlockQuotePrepared>,
1031 raw_line: &str,
1032 marker_line: &str,
1033 shifted_prefix: &str,
1034 used_shifted: bool,
1035 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1036 let mut marker_info = if used_shifted {
1037 parse_blockquote_marker_info(marker_line)
1038 } else {
1039 self.blockquote_marker_info(payload, raw_line)
1040 };
1041 if used_shifted && !shifted_prefix.is_empty() {
1042 let (prefix_cols, _) = leading_indent(shifted_prefix);
1043 if let Some(first) = marker_info.first_mut() {
1044 first.leading_spaces += prefix_cols;
1045 }
1046 }
1047 marker_info
1048 }
1049
1050 fn shifted_blockquote_from_list<'b>(
1053 &self,
1054 line: &'b str,
1055 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1056 if !lists::in_list(&self.containers) {
1057 return None;
1058 }
1059 let list_content_col = paragraphs::current_content_col(&self.containers);
1060 let content_container_indent = self.content_container_indent_to_strip();
1061 let marker_col = list_content_col.saturating_add(content_container_indent);
1062 if marker_col == 0 {
1063 return None;
1064 }
1065
1066 let (indent_cols, _) = leading_indent(line);
1067 if indent_cols < marker_col {
1068 return None;
1069 }
1070
1071 let idx = byte_index_at_column(line, marker_col);
1072 if idx > line.len() {
1073 return None;
1074 }
1075
1076 let candidate = &line[idx..];
1077 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1078 if candidate_depth == 0 {
1079 return None;
1080 }
1081
1082 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1083 }
1084
1085 fn emit_blockquote_markers(
1086 &mut self,
1087 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1088 depth: usize,
1089 ) {
1090 for i in 0..depth {
1091 if let Some(info) = marker_info.get(i) {
1092 blockquotes::emit_one_blockquote_marker(
1093 &mut self.builder,
1094 info.leading_spaces,
1095 info.has_trailing_space,
1096 );
1097 }
1098 }
1099 }
1100
1101 fn current_blockquote_depth(&self) -> usize {
1102 blockquotes::current_blockquote_depth(&self.containers)
1103 }
1104
1105 fn emit_or_buffer_blockquote_marker(
1110 &mut self,
1111 leading_spaces: usize,
1112 has_trailing_space: bool,
1113 ) {
1114 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
1115 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1116 return;
1117 }
1118
1119 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1121 paragraphs::append_paragraph_marker(
1123 &mut self.containers,
1124 leading_spaces,
1125 has_trailing_space,
1126 );
1127 } else {
1128 blockquotes::emit_one_blockquote_marker(
1130 &mut self.builder,
1131 leading_spaces,
1132 has_trailing_space,
1133 );
1134 }
1135 }
1136
1137 fn parse_document_stack(&mut self) {
1138 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1139
1140 log::trace!("Starting document parse");
1141
1142 while self.pos < self.lines.len() {
1145 let line = self.lines[self.pos];
1146
1147 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1148
1149 if self.parse_line(line) {
1150 continue;
1151 }
1152 self.pos += 1;
1153 }
1154
1155 self.close_containers_to(0);
1156 self.builder.finish_node(); }
1158
1159 fn parse_line(&mut self, line: &str) -> bool {
1161 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1164 let mut bq_marker_line = line;
1165 let mut shifted_bq_prefix = "";
1166 let mut used_shifted_bq = false;
1167 if bq_depth == 0
1168 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1169 self.shifted_blockquote_from_list(line)
1170 {
1171 bq_depth = candidate_depth;
1172 inner_content = candidate_inner;
1173 bq_marker_line = candidate_line;
1174 shifted_bq_prefix = candidate_prefix;
1175 used_shifted_bq = true;
1176 }
1177 let current_bq_depth = self.current_blockquote_depth();
1178
1179 let has_blank_before = self.pos == 0 || self.lines[self.pos - 1].trim().is_empty();
1180 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1181 let dispatcher_ctx = if current_bq_depth == 0 {
1182 Some(BlockContext {
1183 content: line,
1184 has_blank_before,
1185 has_blank_before_strict: has_blank_before,
1186 at_document_start: self.pos == 0,
1187 in_fenced_div: self.in_fenced_div(),
1188 blockquote_depth: current_bq_depth,
1189 config: self.config,
1190 content_indent: 0,
1191 indent_to_emit: None,
1192 list_indent_info: None,
1193 in_list: lists::in_list(&self.containers),
1194 next_line: if self.pos + 1 < self.lines.len() {
1195 Some(self.lines[self.pos + 1])
1196 } else {
1197 None
1198 },
1199 })
1200 } else {
1201 None
1202 };
1203
1204 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1205 self.block_registry
1206 .detect_prepared(dispatcher_ctx, &self.lines, self.pos)
1207 .and_then(|prepared| {
1208 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1209 blockquote_match = Some(prepared);
1210 blockquote_match.as_ref().and_then(|prepared| {
1211 prepared
1212 .payload
1213 .as_ref()
1214 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1215 .cloned()
1216 })
1217 } else {
1218 None
1219 }
1220 })
1221 } else {
1222 None
1223 };
1224
1225 log::trace!(
1226 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1227 self.pos,
1228 bq_depth,
1229 current_bq_depth,
1230 self.containers.depth(),
1231 line.trim_end()
1232 );
1233
1234 let is_blank = line.trim_end_matches('\n').trim().is_empty()
1237 || (bq_depth > 0 && inner_content.trim_end_matches('\n').trim().is_empty());
1238
1239 if is_blank {
1240 if self.is_paragraph_open()
1241 && paragraphs::has_open_inline_math_environment(&self.containers)
1242 {
1243 paragraphs::append_paragraph_line(
1244 &mut self.containers,
1245 &mut self.builder,
1246 line,
1247 self.config,
1248 );
1249 self.pos += 1;
1250 return true;
1251 }
1252
1253 self.close_paragraph_if_open();
1255
1256 self.emit_buffered_plain_if_needed();
1260
1261 if bq_depth > current_bq_depth {
1269 for _ in current_bq_depth..bq_depth {
1271 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1272 self.containers.push(Container::BlockQuote {});
1273 }
1274 } else if bq_depth < current_bq_depth {
1275 self.close_blockquotes_to_depth(bq_depth);
1277 }
1278
1279 let mut peek = self.pos + 1;
1281 while peek < self.lines.len() && self.lines[peek].trim().is_empty() {
1282 peek += 1;
1283 }
1284
1285 let levels_to_keep = if peek < self.lines.len() {
1287 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1288 self.current_blockquote_depth(),
1289 &self.containers,
1290 &self.lines,
1291 peek,
1292 self.lines[peek],
1293 )
1294 } else {
1295 0
1296 };
1297 log::trace!(
1298 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1299 self.containers.depth(),
1300 levels_to_keep,
1301 if peek < self.lines.len() {
1302 self.lines[peek]
1303 } else {
1304 "<EOF>"
1305 }
1306 );
1307
1308 while self.containers.depth() > levels_to_keep {
1312 match self.containers.last() {
1313 Some(Container::ListItem { .. }) => {
1314 log::trace!(
1316 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1317 levels_to_keep,
1318 self.containers.depth()
1319 );
1320 self.close_containers_to(self.containers.depth() - 1);
1321 }
1322 Some(Container::List { .. })
1323 | Some(Container::FootnoteDefinition { .. })
1324 | Some(Container::Alert { .. })
1325 | Some(Container::Paragraph { .. })
1326 | Some(Container::Definition { .. })
1327 | Some(Container::DefinitionItem { .. })
1328 | Some(Container::DefinitionList { .. }) => {
1329 log::trace!(
1330 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1331 self.containers.last(),
1332 self.containers.depth(),
1333 levels_to_keep
1334 );
1335
1336 self.close_containers_to(self.containers.depth() - 1);
1337 }
1338 _ => break,
1339 }
1340 }
1341
1342 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1346 self.emit_list_item_buffer_if_needed();
1347 }
1348
1349 if bq_depth > 0 {
1351 let marker_info = self.marker_info_for_line(
1352 blockquote_payload.as_ref(),
1353 line,
1354 bq_marker_line,
1355 shifted_bq_prefix,
1356 used_shifted_bq,
1357 );
1358 self.emit_blockquote_markers(&marker_info, bq_depth);
1359 }
1360
1361 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1362 self.builder
1363 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1364 self.builder.finish_node();
1365
1366 self.pos += 1;
1367 return true;
1368 }
1369
1370 if bq_depth > current_bq_depth {
1372 if self.config.extensions.blank_before_blockquote
1375 && current_bq_depth == 0
1376 && !used_shifted_bq
1377 && !blockquote_payload
1378 .as_ref()
1379 .map(|payload| payload.can_start)
1380 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1381 {
1382 self.emit_list_item_buffer_if_needed();
1386 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1387 paragraphs::append_paragraph_line(
1388 &mut self.containers,
1389 &mut self.builder,
1390 line,
1391 self.config,
1392 );
1393 self.pos += 1;
1394 return true;
1395 }
1396
1397 let can_nest = if current_bq_depth > 0 {
1400 if self.config.extensions.blank_before_blockquote {
1401 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1403 || (self.pos > 0 && {
1404 let prev_line = self.lines[self.pos - 1];
1405 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1406 prev_bq_depth >= current_bq_depth && prev_inner.trim().is_empty()
1407 })
1408 } else {
1409 true
1410 }
1411 } else {
1412 blockquote_payload
1413 .as_ref()
1414 .map(|payload| payload.can_nest)
1415 .unwrap_or(true)
1416 };
1417
1418 if !can_nest {
1419 let content_at_current_depth =
1422 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
1423
1424 let marker_info = self.marker_info_for_line(
1426 blockquote_payload.as_ref(),
1427 line,
1428 bq_marker_line,
1429 shifted_bq_prefix,
1430 used_shifted_bq,
1431 );
1432 for i in 0..current_bq_depth {
1433 if let Some(info) = marker_info.get(i) {
1434 self.emit_or_buffer_blockquote_marker(
1435 info.leading_spaces,
1436 info.has_trailing_space,
1437 );
1438 }
1439 }
1440
1441 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1442 paragraphs::append_paragraph_line(
1444 &mut self.containers,
1445 &mut self.builder,
1446 content_at_current_depth,
1447 self.config,
1448 );
1449 self.pos += 1;
1450 return true;
1451 } else {
1452 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1454 paragraphs::append_paragraph_line(
1455 &mut self.containers,
1456 &mut self.builder,
1457 content_at_current_depth,
1458 self.config,
1459 );
1460 self.pos += 1;
1461 return true;
1462 }
1463 }
1464
1465 self.emit_list_item_buffer_if_needed();
1468
1469 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1471 self.close_containers_to(self.containers.depth() - 1);
1472 }
1473
1474 let marker_info = self.marker_info_for_line(
1476 blockquote_payload.as_ref(),
1477 line,
1478 bq_marker_line,
1479 shifted_bq_prefix,
1480 used_shifted_bq,
1481 );
1482
1483 if let (Some(dispatcher_ctx), Some(prepared)) =
1484 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
1485 {
1486 let _ = self.block_registry.parse_prepared(
1487 prepared,
1488 dispatcher_ctx,
1489 &mut self.builder,
1490 &self.lines,
1491 self.pos,
1492 );
1493 for _ in 0..bq_depth {
1494 self.containers.push(Container::BlockQuote {});
1495 }
1496 } else {
1497 for level in 0..current_bq_depth {
1499 if let Some(info) = marker_info.get(level) {
1500 self.emit_or_buffer_blockquote_marker(
1501 info.leading_spaces,
1502 info.has_trailing_space,
1503 );
1504 }
1505 }
1506
1507 for level in current_bq_depth..bq_depth {
1509 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1510
1511 if let Some(info) = marker_info.get(level) {
1513 blockquotes::emit_one_blockquote_marker(
1514 &mut self.builder,
1515 info.leading_spaces,
1516 info.has_trailing_space,
1517 );
1518 }
1519
1520 self.containers.push(Container::BlockQuote {});
1521 }
1522 }
1523
1524 return self.parse_inner_content(inner_content, Some(inner_content));
1527 } else if bq_depth < current_bq_depth {
1528 if bq_depth == 0 {
1531 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1533 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
1540 let interrupts_via_hr =
1541 is_commonmark && try_parse_horizontal_rule(line).is_some();
1542 if !interrupts_via_hr {
1543 paragraphs::append_paragraph_line(
1544 &mut self.containers,
1545 &mut self.builder,
1546 line,
1547 self.config,
1548 );
1549 self.pos += 1;
1550 return true;
1551 }
1552 }
1553
1554 if lists::in_blockquote_list(&self.containers)
1557 && let Some(marker_match) = try_parse_list_marker(line, self.config)
1558 {
1559 let (indent_cols, indent_bytes) = leading_indent(line);
1560 if let Some(level) = lists::find_matching_list_level(
1561 &self.containers,
1562 &marker_match.marker,
1563 indent_cols,
1564 ) {
1565 self.close_containers_to(level + 1);
1568
1569 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1571 self.close_containers_to(self.containers.depth() - 1);
1572 }
1573 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1574 self.close_containers_to(self.containers.depth() - 1);
1575 }
1576
1577 if let Some(nested_marker) = is_content_nested_bullet_marker(
1579 line,
1580 marker_match.marker_len,
1581 marker_match.spaces_after_bytes,
1582 ) {
1583 let list_item = ListItemEmissionInput {
1584 content: line,
1585 marker_len: marker_match.marker_len,
1586 spaces_after_cols: marker_match.spaces_after_cols,
1587 spaces_after_bytes: marker_match.spaces_after_bytes,
1588 indent_cols,
1589 indent_bytes,
1590 };
1591 lists::add_list_item_with_nested_empty_list(
1592 &mut self.containers,
1593 &mut self.builder,
1594 &list_item,
1595 nested_marker,
1596 );
1597 } else {
1598 let list_item = ListItemEmissionInput {
1599 content: line,
1600 marker_len: marker_match.marker_len,
1601 spaces_after_cols: marker_match.spaces_after_cols,
1602 spaces_after_bytes: marker_match.spaces_after_bytes,
1603 indent_cols,
1604 indent_bytes,
1605 };
1606 lists::add_list_item(
1607 &mut self.containers,
1608 &mut self.builder,
1609 &list_item,
1610 );
1611 }
1612 self.pos += 1;
1613 return true;
1614 }
1615 }
1616 }
1617
1618 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1620 self.close_containers_to(self.containers.depth() - 1);
1621 }
1622
1623 self.close_blockquotes_to_depth(bq_depth);
1625
1626 if bq_depth > 0 {
1628 let marker_info = self.marker_info_for_line(
1630 blockquote_payload.as_ref(),
1631 line,
1632 bq_marker_line,
1633 shifted_bq_prefix,
1634 used_shifted_bq,
1635 );
1636 for i in 0..bq_depth {
1637 if let Some(info) = marker_info.get(i) {
1638 self.emit_or_buffer_blockquote_marker(
1639 info.leading_spaces,
1640 info.has_trailing_space,
1641 );
1642 }
1643 }
1644 return self.parse_inner_content(inner_content, Some(inner_content));
1646 } else {
1647 return self.parse_inner_content(line, None);
1649 }
1650 } else if bq_depth > 0 {
1651 let mut list_item_continuation = false;
1653 let same_depth_marker_info = self.marker_info_for_line(
1654 blockquote_payload.as_ref(),
1655 line,
1656 bq_marker_line,
1657 shifted_bq_prefix,
1658 used_shifted_bq,
1659 );
1660 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
1661
1662 if matches!(
1665 self.containers.last(),
1666 Some(Container::ListItem { content_col: _, .. })
1667 ) {
1668 let (indent_cols, _) = leading_indent(inner_content);
1669 let content_indent = self.content_container_indent_to_strip();
1670 let effective_indent = indent_cols.saturating_sub(content_indent);
1671 let content_col = match self.containers.last() {
1672 Some(Container::ListItem { content_col, .. }) => *content_col,
1673 _ => 0,
1674 };
1675
1676 let is_new_item_at_outer_level =
1678 if try_parse_list_marker(inner_content, self.config).is_some() {
1679 effective_indent < content_col
1680 } else {
1681 false
1682 };
1683
1684 if is_new_item_at_outer_level
1688 || (effective_indent < content_col && !has_explicit_same_depth_marker)
1689 {
1690 log::trace!(
1691 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
1692 is_new_item_at_outer_level,
1693 effective_indent,
1694 content_col
1695 );
1696 self.close_containers_to(self.containers.depth() - 1);
1697 } else {
1698 log::trace!(
1699 "Keeping ListItem: effective_indent={} >= content_col={}",
1700 effective_indent,
1701 content_col
1702 );
1703 list_item_continuation = true;
1704 }
1705 }
1706
1707 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
1711 {
1712 list_item_continuation = false;
1713 }
1714
1715 let continuation_has_explicit_marker = list_item_continuation && {
1716 if has_explicit_same_depth_marker {
1717 for i in 0..bq_depth {
1718 if let Some(info) = same_depth_marker_info.get(i) {
1719 self.emit_or_buffer_blockquote_marker(
1720 info.leading_spaces,
1721 info.has_trailing_space,
1722 );
1723 }
1724 }
1725 true
1726 } else {
1727 false
1728 }
1729 };
1730
1731 if !list_item_continuation {
1732 let marker_info = self.marker_info_for_line(
1733 blockquote_payload.as_ref(),
1734 line,
1735 bq_marker_line,
1736 shifted_bq_prefix,
1737 used_shifted_bq,
1738 );
1739 for i in 0..bq_depth {
1740 if let Some(info) = marker_info.get(i) {
1741 self.emit_or_buffer_blockquote_marker(
1742 info.leading_spaces,
1743 info.has_trailing_space,
1744 );
1745 }
1746 }
1747 }
1748 let line_to_append = if list_item_continuation {
1749 if continuation_has_explicit_marker {
1750 Some(inner_content)
1751 } else {
1752 Some(line)
1753 }
1754 } else {
1755 Some(inner_content)
1756 };
1757 return self.parse_inner_content(inner_content, line_to_append);
1758 }
1759
1760 if current_bq_depth > 0 {
1763 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1765 paragraphs::append_paragraph_line(
1766 &mut self.containers,
1767 &mut self.builder,
1768 line,
1769 self.config,
1770 );
1771 self.pos += 1;
1772 return true;
1773 }
1774
1775 if lists::in_blockquote_list(&self.containers)
1777 && let Some(marker_match) = try_parse_list_marker(line, self.config)
1778 {
1779 let (indent_cols, indent_bytes) = leading_indent(line);
1780 if let Some(level) = lists::find_matching_list_level(
1781 &self.containers,
1782 &marker_match.marker,
1783 indent_cols,
1784 ) {
1785 self.close_containers_to(level + 1);
1787
1788 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1790 self.close_containers_to(self.containers.depth() - 1);
1791 }
1792 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1793 self.close_containers_to(self.containers.depth() - 1);
1794 }
1795
1796 if let Some(nested_marker) = is_content_nested_bullet_marker(
1798 line,
1799 marker_match.marker_len,
1800 marker_match.spaces_after_bytes,
1801 ) {
1802 let list_item = ListItemEmissionInput {
1803 content: line,
1804 marker_len: marker_match.marker_len,
1805 spaces_after_cols: marker_match.spaces_after_cols,
1806 spaces_after_bytes: marker_match.spaces_after_bytes,
1807 indent_cols,
1808 indent_bytes,
1809 };
1810 lists::add_list_item_with_nested_empty_list(
1811 &mut self.containers,
1812 &mut self.builder,
1813 &list_item,
1814 nested_marker,
1815 );
1816 } else {
1817 let list_item = ListItemEmissionInput {
1818 content: line,
1819 marker_len: marker_match.marker_len,
1820 spaces_after_cols: marker_match.spaces_after_cols,
1821 spaces_after_bytes: marker_match.spaces_after_bytes,
1822 indent_cols,
1823 indent_bytes,
1824 };
1825 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
1826 }
1827 self.pos += 1;
1828 return true;
1829 }
1830 }
1831 }
1832
1833 self.parse_inner_content(line, None)
1835 }
1836
1837 fn content_container_indent_to_strip(&self) -> usize {
1839 self.containers
1840 .stack
1841 .iter()
1842 .filter_map(|c| match c {
1843 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
1844 Container::Definition { content_col, .. } => Some(*content_col),
1845 _ => None,
1846 })
1847 .sum()
1848 }
1849
1850 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> bool {
1856 log::trace!(
1857 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
1858 self.pos,
1859 self.containers.depth(),
1860 self.containers.last(),
1861 content.trim_end()
1862 );
1863 let content_indent = self.content_container_indent_to_strip();
1866 let (stripped_content, indent_to_emit) = if content_indent > 0 {
1867 let (indent_cols, _) = leading_indent(content);
1868 if indent_cols >= content_indent {
1869 let idx = byte_index_at_column(content, content_indent);
1870 (&content[idx..], Some(&content[..idx]))
1871 } else {
1872 let trimmed_start = content.trim_start();
1874 let ws_len = content.len() - trimmed_start.len();
1875 if ws_len > 0 {
1876 (trimmed_start, Some(&content[..ws_len]))
1877 } else {
1878 (content, None)
1879 }
1880 }
1881 } else {
1882 (content, None)
1883 };
1884
1885 if self.config.extensions.alerts
1886 && self.current_blockquote_depth() > 0
1887 && !self.in_active_alert()
1888 && !self.is_paragraph_open()
1889 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
1890 {
1891 let (_, newline_str) = strip_newline(stripped_content);
1892 self.builder.start_node(SyntaxKind::ALERT.into());
1893 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
1894 if !newline_str.is_empty() {
1895 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1896 }
1897 self.containers.push(Container::Alert {
1898 blockquote_depth: self.current_blockquote_depth(),
1899 });
1900 self.pos += 1;
1901 return true;
1902 }
1903
1904 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1908 let is_definition_marker =
1909 definition_lists::try_parse_definition_marker(stripped_content).is_some()
1910 && !stripped_content.starts_with(':');
1911 if content_indent == 0 && is_definition_marker {
1912 } else {
1914 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
1915
1916 if policy.definition_plain_can_continue(
1917 stripped_content,
1918 content,
1919 content_indent,
1920 &BlockContext {
1921 content: stripped_content,
1922 has_blank_before: self.pos == 0
1923 || self.lines[self.pos - 1].trim().is_empty(),
1924 has_blank_before_strict: self.pos == 0
1925 || self.lines[self.pos - 1].trim().is_empty(),
1926 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
1927 in_fenced_div: self.in_fenced_div(),
1928 blockquote_depth: self.current_blockquote_depth(),
1929 config: self.config,
1930 content_indent,
1931 indent_to_emit: None,
1932 list_indent_info: None,
1933 in_list: lists::in_list(&self.containers),
1934 next_line: if self.pos + 1 < self.lines.len() {
1935 Some(self.lines[self.pos + 1])
1936 } else {
1937 None
1938 },
1939 },
1940 &self.lines,
1941 self.pos,
1942 ) {
1943 let content_line = stripped_content;
1944 let (text_without_newline, newline_str) = strip_newline(content_line);
1945 let indent_prefix = if !text_without_newline.trim().is_empty() {
1946 indent_to_emit.unwrap_or("")
1947 } else {
1948 ""
1949 };
1950 let content_line = format!("{}{}", indent_prefix, text_without_newline);
1951
1952 if let Some(Container::Definition {
1953 plain_open,
1954 plain_buffer,
1955 ..
1956 }) = self.containers.stack.last_mut()
1957 {
1958 let line_with_newline = if !newline_str.is_empty() {
1959 format!("{}{}", content_line, newline_str)
1960 } else {
1961 content_line
1962 };
1963 plain_buffer.push_line(line_with_newline);
1964 *plain_open = true;
1965 }
1966
1967 self.pos += 1;
1968 return true;
1969 }
1970 }
1971 }
1972
1973 if content_indent > 0 {
1976 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
1977 let current_bq_depth = self.current_blockquote_depth();
1978 let in_footnote_definition = self
1979 .containers
1980 .stack
1981 .iter()
1982 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
1983
1984 if bq_depth > 0 {
1985 if in_footnote_definition
1986 && self.config.extensions.blank_before_blockquote
1987 && current_bq_depth == 0
1988 && !blockquotes::can_start_blockquote(self.pos, &self.lines)
1989 {
1990 } else {
1994 self.emit_buffered_plain_if_needed();
1997 self.emit_list_item_buffer_if_needed();
1998
1999 self.close_paragraph_if_open();
2002
2003 if bq_depth > current_bq_depth {
2004 let marker_info = parse_blockquote_marker_info(stripped_content);
2005
2006 for level in current_bq_depth..bq_depth {
2008 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2009
2010 if level == current_bq_depth
2011 && let Some(indent_str) = indent_to_emit
2012 {
2013 self.builder
2014 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2015 }
2016
2017 if let Some(info) = marker_info.get(level) {
2018 blockquotes::emit_one_blockquote_marker(
2019 &mut self.builder,
2020 info.leading_spaces,
2021 info.has_trailing_space,
2022 );
2023 }
2024
2025 self.containers.push(Container::BlockQuote {});
2026 }
2027 } else if bq_depth < current_bq_depth {
2028 self.close_blockquotes_to_depth(bq_depth);
2029 } else {
2030 let marker_info = parse_blockquote_marker_info(stripped_content);
2032 self.emit_blockquote_markers(&marker_info, bq_depth);
2033 }
2034
2035 return self.parse_inner_content(inner_content, Some(inner_content));
2036 }
2037 }
2038 }
2039
2040 let content = stripped_content;
2042
2043 if self.is_paragraph_open()
2044 && (paragraphs::has_open_inline_math_environment(&self.containers)
2045 || paragraphs::has_open_display_math_dollars(&self.containers))
2046 {
2047 paragraphs::append_paragraph_line(
2048 &mut self.containers,
2049 &mut self.builder,
2050 line_to_append.unwrap_or(self.lines[self.pos]),
2051 self.config,
2052 );
2053 self.pos += 1;
2054 return true;
2055 }
2056
2057 use super::blocks::lists;
2061 use super::blocks::paragraphs;
2062 let list_indent_info = if lists::in_list(&self.containers) {
2063 let content_col = paragraphs::current_content_col(&self.containers);
2064 if content_col > 0 {
2065 Some(super::block_dispatcher::ListIndentInfo { content_col })
2066 } else {
2067 None
2068 }
2069 } else {
2070 None
2071 };
2072
2073 let next_line = if self.pos + 1 < self.lines.len() {
2074 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2077 } else {
2078 None
2079 };
2080
2081 let current_bq_depth = self.current_blockquote_depth();
2082 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2083 && current_bq_depth < alert_bq_depth
2084 {
2085 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2086 self.close_containers_to(self.containers.depth() - 1);
2087 }
2088 }
2089
2090 let dispatcher_ctx = BlockContext {
2091 content,
2092 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2096 blockquote_depth: current_bq_depth,
2097 config: self.config,
2098 content_indent,
2099 indent_to_emit,
2100 list_indent_info,
2101 in_list: lists::in_list(&self.containers),
2102 next_line,
2103 };
2104
2105 let mut dispatcher_ctx = dispatcher_ctx;
2108
2109 let dispatcher_match =
2112 self.block_registry
2113 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos);
2114
2115 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
2121 let has_blank_before = if self.pos == 0 || after_metadata_block {
2122 true
2123 } else {
2124 let prev_line = self.lines[self.pos - 1];
2125 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2126 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
2127 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
2128 && fenced_divs::try_parse_div_fence_open(
2129 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
2130 )
2131 .is_some();
2132
2133 let prev_line_blank = prev_line.trim().is_empty();
2134 prev_line_blank
2135 || prev_is_fenced_div_open
2136 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2137 || !self.previous_block_requires_blank_before_heading()
2138 };
2139
2140 let at_document_start = self.pos == 0 && current_bq_depth == 0;
2143
2144 let prev_line_blank = if self.pos > 0 {
2145 let prev_line = self.lines[self.pos - 1];
2146 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2147 prev_line.trim().is_empty() || (prev_bq_depth > 0 && prev_inner.trim().is_empty())
2148 } else {
2149 false
2150 };
2151 let has_blank_before_strict = at_document_start || prev_line_blank;
2152
2153 dispatcher_ctx.has_blank_before = has_blank_before;
2154 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
2155 dispatcher_ctx.at_document_start = at_document_start;
2156
2157 let dispatcher_match =
2158 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
2159 self.block_registry
2161 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos)
2162 } else {
2163 dispatcher_match
2164 };
2165
2166 if has_blank_before {
2167 if let Some(env_name) = extract_environment_name(content)
2168 && is_inline_math_environment(&env_name)
2169 {
2170 if !self.is_paragraph_open() {
2171 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2172 }
2173 paragraphs::append_paragraph_line(
2174 &mut self.containers,
2175 &mut self.builder,
2176 line_to_append.unwrap_or(self.lines[self.pos]),
2177 self.config,
2178 );
2179 self.pos += 1;
2180 return true;
2181 }
2182
2183 if let Some(block_match) = dispatcher_match.as_ref() {
2184 let detection = block_match.detection;
2185
2186 match detection {
2187 BlockDetectionResult::YesCanInterrupt => {
2188 self.emit_list_item_buffer_if_needed();
2189 if self.is_paragraph_open() {
2190 self.close_containers_to(self.containers.depth() - 1);
2191 }
2192 }
2193 BlockDetectionResult::Yes => {
2194 self.prepare_for_block_element();
2195 }
2196 BlockDetectionResult::No => unreachable!(),
2197 }
2198
2199 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2200 self.close_containers_to_fenced_div();
2201 }
2202
2203 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2204 self.close_open_footnote_definition();
2205 }
2206
2207 let lines_consumed = self.block_registry.parse_prepared(
2208 block_match,
2209 &dispatcher_ctx,
2210 &mut self.builder,
2211 &self.lines,
2212 self.pos,
2213 );
2214
2215 if matches!(
2216 self.block_registry.parser_name(block_match),
2217 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
2218 ) {
2219 self.after_metadata_block = true;
2220 }
2221
2222 match block_match.effect {
2223 BlockEffect::None => {}
2224 BlockEffect::OpenFencedDiv => {
2225 self.containers.push(Container::FencedDiv {});
2226 }
2227 BlockEffect::CloseFencedDiv => {
2228 self.close_fenced_div();
2229 }
2230 BlockEffect::OpenFootnoteDefinition => {
2231 self.handle_footnote_open_effect(block_match, content);
2232 }
2233 BlockEffect::OpenList => {
2234 self.handle_list_open_effect(block_match, content, indent_to_emit);
2235 }
2236 BlockEffect::OpenDefinitionList => {
2237 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2238 }
2239 BlockEffect::OpenBlockQuote => {
2240 }
2242 }
2243
2244 if lines_consumed == 0 {
2245 log::warn!(
2246 "block parser made no progress at line {} (parser={})",
2247 self.pos + 1,
2248 self.block_registry.parser_name(block_match)
2249 );
2250 return false;
2251 }
2252
2253 self.pos += lines_consumed;
2254 return true;
2255 }
2256 } else if let Some(block_match) = dispatcher_match.as_ref() {
2257 let parser_name = self.block_registry.parser_name(block_match);
2260 match block_match.detection {
2261 BlockDetectionResult::YesCanInterrupt => {
2262 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
2263 && self.is_paragraph_open()
2264 {
2265 if !self.is_paragraph_open() {
2267 paragraphs::start_paragraph_if_needed(
2268 &mut self.containers,
2269 &mut self.builder,
2270 );
2271 }
2272 paragraphs::append_paragraph_line(
2273 &mut self.containers,
2274 &mut self.builder,
2275 line_to_append.unwrap_or(self.lines[self.pos]),
2276 self.config,
2277 );
2278 self.pos += 1;
2279 return true;
2280 }
2281
2282 if matches!(block_match.effect, BlockEffect::OpenList)
2283 && self.is_paragraph_open()
2284 && !lists::in_list(&self.containers)
2285 && self.content_container_indent_to_strip() == 0
2286 {
2287 let allow_interrupt =
2293 self.config.dialect == crate::options::Dialect::CommonMark && {
2294 use super::block_dispatcher::ListPrepared;
2295 use super::blocks::lists::OrderedMarker;
2296 let prepared = block_match
2297 .payload
2298 .as_ref()
2299 .and_then(|p| p.downcast_ref::<ListPrepared>());
2300 match prepared.map(|p| &p.marker) {
2301 Some(ListMarker::Bullet(_)) => true,
2302 Some(ListMarker::Ordered(OrderedMarker::Decimal {
2303 number,
2304 ..
2305 })) => number == "1",
2306 _ => false,
2307 }
2308 };
2309 if !allow_interrupt {
2310 paragraphs::append_paragraph_line(
2311 &mut self.containers,
2312 &mut self.builder,
2313 line_to_append.unwrap_or(self.lines[self.pos]),
2314 self.config,
2315 );
2316 self.pos += 1;
2317 return true;
2318 }
2319 }
2320
2321 self.emit_list_item_buffer_if_needed();
2322 if self.is_paragraph_open() {
2323 self.close_containers_to(self.containers.depth() - 1);
2324 }
2325
2326 if self.config.dialect == crate::options::Dialect::CommonMark
2333 && !matches!(block_match.effect, BlockEffect::OpenList)
2334 {
2335 let (indent_cols, _) = leading_indent(content);
2336 self.close_lists_above_indent(indent_cols);
2337 }
2338 }
2339 BlockDetectionResult::Yes => {
2340 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
2343 if !self.is_paragraph_open() {
2344 paragraphs::start_paragraph_if_needed(
2345 &mut self.containers,
2346 &mut self.builder,
2347 );
2348 }
2349 paragraphs::append_paragraph_line(
2350 &mut self.containers,
2351 &mut self.builder,
2352 line_to_append.unwrap_or(self.lines[self.pos]),
2353 self.config,
2354 );
2355 self.pos += 1;
2356 return true;
2357 }
2358
2359 if parser_name == "reference_definition" && self.is_paragraph_open() {
2362 paragraphs::append_paragraph_line(
2363 &mut self.containers,
2364 &mut self.builder,
2365 line_to_append.unwrap_or(self.lines[self.pos]),
2366 self.config,
2367 );
2368 self.pos += 1;
2369 return true;
2370 }
2371 }
2372 BlockDetectionResult::No => unreachable!(),
2373 }
2374
2375 if !matches!(block_match.detection, BlockDetectionResult::No) {
2376 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2377 self.close_containers_to_fenced_div();
2378 }
2379
2380 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2381 self.close_open_footnote_definition();
2382 }
2383
2384 let lines_consumed = self.block_registry.parse_prepared(
2385 block_match,
2386 &dispatcher_ctx,
2387 &mut self.builder,
2388 &self.lines,
2389 self.pos,
2390 );
2391
2392 match block_match.effect {
2393 BlockEffect::None => {}
2394 BlockEffect::OpenFencedDiv => {
2395 self.containers.push(Container::FencedDiv {});
2396 }
2397 BlockEffect::CloseFencedDiv => {
2398 self.close_fenced_div();
2399 }
2400 BlockEffect::OpenFootnoteDefinition => {
2401 self.handle_footnote_open_effect(block_match, content);
2402 }
2403 BlockEffect::OpenList => {
2404 self.handle_list_open_effect(block_match, content, indent_to_emit);
2405 }
2406 BlockEffect::OpenDefinitionList => {
2407 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2408 }
2409 BlockEffect::OpenBlockQuote => {
2410 }
2412 }
2413
2414 if lines_consumed == 0 {
2415 log::warn!(
2416 "block parser made no progress at line {} (parser={})",
2417 self.pos + 1,
2418 self.block_registry.parser_name(block_match)
2419 );
2420 return false;
2421 }
2422
2423 self.pos += lines_consumed;
2424 return true;
2425 }
2426 }
2427
2428 if self.config.extensions.line_blocks
2430 && (has_blank_before || self.pos == 0)
2431 && try_parse_line_block_start(content).is_some()
2432 && try_parse_line_block_start(self.lines[self.pos]).is_some()
2436 {
2437 log::trace!("Parsed line block at line {}", self.pos);
2438 self.close_paragraph_if_open();
2440
2441 let new_pos = parse_line_block(&self.lines, self.pos, &mut self.builder, self.config);
2442 if new_pos > self.pos {
2443 self.pos = new_pos;
2444 return true;
2445 }
2446 }
2447
2448 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2451 log::trace!(
2452 "Inside ListItem - buffering content: {:?}",
2453 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2454 );
2455 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2457
2458 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
2460 buffer.push_text(line);
2461 }
2462
2463 self.pos += 1;
2464 return true;
2465 }
2466
2467 log::trace!(
2468 "Not in ListItem - creating paragraph for: {:?}",
2469 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2470 );
2471 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2473 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2476 paragraphs::append_paragraph_line(
2477 &mut self.containers,
2478 &mut self.builder,
2479 line,
2480 self.config,
2481 );
2482 self.pos += 1;
2483 true
2484 }
2485
2486 fn fenced_div_container_index(&self) -> Option<usize> {
2487 self.containers
2488 .stack
2489 .iter()
2490 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
2491 }
2492
2493 fn close_containers_to_fenced_div(&mut self) {
2494 if let Some(index) = self.fenced_div_container_index() {
2495 self.close_containers_to(index + 1);
2496 }
2497 }
2498
2499 fn close_fenced_div(&mut self) {
2500 if let Some(index) = self.fenced_div_container_index() {
2501 self.close_containers_to(index);
2502 }
2503 }
2504
2505 fn in_fenced_div(&self) -> bool {
2506 self.containers
2507 .stack
2508 .iter()
2509 .any(|c| matches!(c, Container::FencedDiv { .. }))
2510 }
2511}