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::{
14 emit_atx_heading, emit_setext_heading, emit_setext_heading_body, try_parse_atx_heading,
15 try_parse_setext_heading,
16};
17use super::blocks::horizontal_rules::try_parse_horizontal_rule;
18use super::blocks::line_blocks;
19use super::blocks::lists;
20use super::blocks::paragraphs;
21use super::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
22use super::utils::container_stack;
23use super::utils::helpers::{is_blank_line, split_lines_inclusive, strip_newline};
24use super::utils::inline_emission;
25use super::utils::marker_utils;
26use super::utils::text_buffer;
27
28use super::blocks::blockquotes::strip_n_blockquote_markers;
29use super::utils::continuation::ContinuationPolicy;
30use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
31use definition_lists::{emit_definition_marker, emit_term};
32use line_blocks::{parse_line_block, try_parse_line_block_start};
33use lists::{
34 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
35 try_parse_list_marker,
36};
37use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
38use text_buffer::TextBuffer;
39
40const GITHUB_ALERT_MARKERS: [&str; 5] = [
41 "[!TIP]",
42 "[!WARNING]",
43 "[!IMPORTANT]",
44 "[!CAUTION]",
45 "[!NOTE]",
46];
47
48pub struct Parser<'a> {
49 lines: Vec<&'a str>,
50 pos: usize,
51 builder: GreenNodeBuilder<'static>,
52 containers: ContainerStack,
53 config: &'a ParserOptions,
54 block_registry: BlockParserRegistry,
55 after_metadata_block: bool,
59}
60
61impl<'a> Parser<'a> {
62 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
63 let lines = split_lines_inclusive(input);
65 Self {
66 lines,
67 pos: 0,
68 builder: GreenNodeBuilder::new(),
69 containers: ContainerStack::new(),
70 config,
71 block_registry: BlockParserRegistry::new(),
72 after_metadata_block: false,
73 }
74 }
75
76 pub fn parse(mut self) -> SyntaxNode {
77 self.parse_document_stack();
78
79 SyntaxNode::new_root(self.builder.finish())
80 }
81
82 fn close_lists_above_indent(&mut self, indent_cols: usize) {
93 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
94 if indent_cols >= *content_col {
95 break;
96 }
97 self.close_containers_to(self.containers.depth() - 1);
98 if matches!(self.containers.last(), Some(Container::List { .. })) {
99 self.close_containers_to(self.containers.depth() - 1);
100 }
101 }
102 }
103
104 fn close_containers_to(&mut self, keep: usize) {
107 while self.containers.depth() > keep {
109 match self.containers.stack.last() {
110 Some(Container::ListItem { buffer, .. }) if !buffer.is_empty() => {
112 let buffer_clone = buffer.clone();
114
115 log::trace!(
116 "Closing ListItem with buffer (is_empty={}, segment_count={})",
117 buffer_clone.is_empty(),
118 buffer_clone.segment_count()
119 );
120
121 let parent_list_is_loose = self
125 .containers
126 .stack
127 .iter()
128 .rev()
129 .find_map(|c| match c {
130 Container::List {
131 has_blank_between_items,
132 ..
133 } => Some(*has_blank_between_items),
134 _ => None,
135 })
136 .unwrap_or(false);
137
138 let use_paragraph =
139 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
140
141 log::trace!(
142 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
143 use_paragraph,
144 parent_list_is_loose,
145 buffer_clone.has_blank_lines_between_content()
146 );
147
148 self.containers.stack.pop();
150 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
152 self.builder.finish_node(); }
154 Some(Container::ListItem { .. }) => {
156 log::trace!("Closing empty ListItem (no buffer content)");
157 self.containers.stack.pop();
159 self.builder.finish_node();
160 }
161 Some(Container::Paragraph {
163 buffer,
164 start_checkpoint,
165 ..
166 }) if !buffer.is_empty() => {
167 let buffer_clone = buffer.clone();
169 let checkpoint = *start_checkpoint;
170 self.containers.stack.pop();
172 self.builder
174 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
175 buffer_clone.emit_with_inlines(&mut self.builder, self.config);
176 self.builder.finish_node();
177 }
178 Some(Container::Paragraph {
180 start_checkpoint, ..
181 }) => {
182 let checkpoint = *start_checkpoint;
183 self.containers.stack.pop();
185 self.builder
186 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
187 self.builder.finish_node();
188 }
189 Some(Container::Definition {
191 plain_open: true,
192 plain_buffer,
193 ..
194 }) if !plain_buffer.is_empty() => {
195 let text = plain_buffer.get_accumulated_text();
196 let line_without_newline = text
197 .strip_suffix("\r\n")
198 .or_else(|| text.strip_suffix('\n'));
199 if let Some(line) = line_without_newline
200 && !line.contains('\n')
201 && !line.contains('\r')
202 && let Some(level) = try_parse_atx_heading(line)
203 {
204 emit_atx_heading(&mut self.builder, &text, level, self.config);
205 } else {
206 self.builder.start_node(SyntaxKind::PLAIN.into());
208 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
209 self.builder.finish_node();
210 }
211
212 if let Some(Container::Definition {
214 plain_open,
215 plain_buffer,
216 ..
217 }) = self.containers.stack.last_mut()
218 {
219 plain_buffer.clear();
220 *plain_open = false;
221 }
222
223 self.containers.stack.pop();
225 self.builder.finish_node();
226 }
227 Some(Container::Definition {
229 plain_open: true, ..
230 }) => {
231 if let Some(Container::Definition {
233 plain_open,
234 plain_buffer,
235 ..
236 }) = self.containers.stack.last_mut()
237 {
238 plain_buffer.clear();
239 *plain_open = false;
240 }
241
242 self.containers.stack.pop();
244 self.builder.finish_node();
245 }
246 _ => {
248 self.containers.stack.pop();
249 self.builder.finish_node();
250 }
251 }
252 }
253 }
254
255 fn emit_buffered_plain_if_needed(&mut self) {
258 if let Some(Container::Definition {
260 plain_open: true,
261 plain_buffer,
262 ..
263 }) = self.containers.stack.last()
264 && !plain_buffer.is_empty()
265 {
266 let text = plain_buffer.get_accumulated_text();
267 let line_without_newline = text
268 .strip_suffix("\r\n")
269 .or_else(|| text.strip_suffix('\n'));
270 if let Some(line) = line_without_newline
271 && !line.contains('\n')
272 && !line.contains('\r')
273 && let Some(level) = try_parse_atx_heading(line)
274 {
275 emit_atx_heading(&mut self.builder, &text, level, self.config);
276 } else {
277 self.builder.start_node(SyntaxKind::PLAIN.into());
279 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
280 self.builder.finish_node();
281 }
282 }
283
284 if let Some(Container::Definition {
286 plain_open,
287 plain_buffer,
288 ..
289 }) = self.containers.stack.last_mut()
290 && *plain_open
291 {
292 plain_buffer.clear();
293 *plain_open = false;
294 }
295 }
296
297 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
302 let mut current = self.current_blockquote_depth();
303 while current > target_depth {
304 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
305 if self.containers.depth() == 0 {
306 break;
307 }
308 self.close_containers_to(self.containers.depth() - 1);
309 }
310 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
311 self.close_containers_to(self.containers.depth() - 1);
312 current -= 1;
313 } else {
314 break;
315 }
316 }
317 }
318
319 fn active_alert_blockquote_depth(&self) -> Option<usize> {
320 self.containers.stack.iter().rev().find_map(|c| match c {
321 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
322 _ => None,
323 })
324 }
325
326 fn in_active_alert(&self) -> bool {
327 self.active_alert_blockquote_depth().is_some()
328 }
329
330 fn previous_block_requires_blank_before_heading(&self) -> bool {
331 matches!(
332 self.containers.last(),
333 Some(Container::Paragraph { .. })
334 | Some(Container::ListItem { .. })
335 | Some(Container::Definition { .. })
336 | Some(Container::DefinitionItem { .. })
337 | Some(Container::FootnoteDefinition { .. })
338 )
339 }
340
341 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
342 let (without_newline, _) = strip_newline(content);
343 let trimmed = without_newline.trim();
344 GITHUB_ALERT_MARKERS
345 .into_iter()
346 .find(|marker| *marker == trimmed)
347 }
348
349 fn emit_list_item_buffer_if_needed(&mut self) {
352 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut()
353 && !buffer.is_empty()
354 {
355 let buffer_clone = buffer.clone();
356 buffer.clear();
357 let use_paragraph = buffer_clone.has_blank_lines_between_content();
358 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
359 }
360 }
361
362 fn maybe_open_fenced_code_in_new_list_item(&mut self) {
374 let Some(Container::ListItem {
375 content_col,
376 buffer,
377 ..
378 }) = self.containers.stack.last()
379 else {
380 return;
381 };
382 let content_col = *content_col;
383 let Some(text) = buffer.first_text() else {
384 return;
385 };
386 if buffer.segment_count() != 1 {
387 return;
388 }
389 let text_owned = text.to_string();
390 let Some(fence) = code_blocks::try_parse_fence_open(&text_owned) else {
391 return;
392 };
393 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
394 let has_info = !fence.info_string.trim().is_empty();
395 let bq_depth = self.current_blockquote_depth();
396 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
397 if !(has_info || has_matching_closer || common_mark_dialect) {
398 return;
399 }
400 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
402 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
403 {
404 return;
405 }
406 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
407 buffer.clear();
408 }
409 let new_pos = code_blocks::parse_fenced_code_block(
410 &mut self.builder,
411 &self.lines,
412 self.pos,
413 fence,
414 bq_depth,
415 content_col,
416 Some(&text_owned),
417 );
418 self.pos = new_pos.saturating_sub(1);
422 }
423
424 fn maybe_open_indented_code_in_new_list_item(&mut self) {
435 let Some(Container::ListItem {
436 content_col,
437 buffer,
438 marker_only,
439 virtual_marker_space,
440 }) = self.containers.stack.last()
441 else {
442 return;
443 };
444 if *marker_only {
445 return;
446 }
447 if buffer.segment_count() != 1 {
448 return;
449 }
450 let Some(text) = buffer.first_text() else {
451 return;
452 };
453 let content_col = *content_col;
454 let virtual_marker_space = *virtual_marker_space;
455 let text_owned = text.to_string();
456
457 let mut iter = text_owned.split_inclusive('\n');
459 let line_with_nl = iter.next().unwrap_or("").to_string();
460 if iter.next().is_some() {
461 return;
462 }
463
464 let line_no_nl = line_with_nl
465 .strip_suffix("\r\n")
466 .or_else(|| line_with_nl.strip_suffix('\n'))
467 .unwrap_or(&line_with_nl);
468 let nl_suffix = &line_with_nl[line_no_nl.len()..];
469
470 let buffer_start_col = if virtual_marker_space {
471 content_col.saturating_sub(1)
472 } else {
473 content_col
474 };
475
476 let target = content_col + 4;
477 let (cols_walked, ws_bytes) =
478 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
479
480 if buffer_start_col + cols_walked < target {
481 return;
482 }
483 if ws_bytes >= line_no_nl.len() {
484 return;
485 }
486
487 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
488 buffer.clear();
489 }
490
491 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
492 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
493 if ws_bytes > 0 {
494 self.builder
495 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
496 }
497 let rest = &line_no_nl[ws_bytes..];
498 if !rest.is_empty() {
499 self.builder.token(SyntaxKind::TEXT.into(), rest);
500 }
501 if !nl_suffix.is_empty() {
502 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
503 }
504 self.builder.finish_node();
505 self.builder.finish_node();
506 }
507
508 fn has_matching_fence_closer(
509 &self,
510 fence: &code_blocks::FenceInfo,
511 bq_depth: usize,
512 content_col: usize,
513 ) -> bool {
514 for raw_line in self.lines.iter().skip(self.pos + 1) {
515 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
516 if line_bq_depth < bq_depth {
517 break;
518 }
519 let candidate = if content_col > 0 && !inner.is_empty() {
520 let idx = byte_index_at_column(inner, content_col);
521 if idx <= inner.len() {
522 &inner[idx..]
523 } else {
524 inner
525 }
526 } else {
527 inner
528 };
529 if code_blocks::is_closing_fence(candidate, fence) {
530 return true;
531 }
532 }
533 false
534 }
535
536 fn is_paragraph_open(&self) -> bool {
538 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
539 }
540
541 fn emit_setext_heading_folding_paragraph(
549 &mut self,
550 text_line: &str,
551 underline_line: &str,
552 level: usize,
553 ) {
554 let (buffered_text, checkpoint) = match self.containers.stack.last() {
555 Some(Container::Paragraph {
556 buffer,
557 start_checkpoint,
558 ..
559 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
560 _ => (String::new(), None),
561 };
562
563 if checkpoint.is_some() {
564 self.containers.stack.pop();
565 }
566
567 let combined_text = if buffered_text.is_empty() {
568 text_line.to_string()
569 } else {
570 format!("{}{}", buffered_text, text_line)
571 };
572
573 let cp = checkpoint.expect(
574 "emit_setext_heading_folding_paragraph requires an open paragraph; \
575 single-line setext should go through the regular dispatcher path",
576 );
577 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
578 emit_setext_heading_body(
579 &mut self.builder,
580 &combined_text,
581 underline_line,
582 level,
583 self.config,
584 );
585 self.builder.finish_node();
586 }
587
588 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> bool {
606 let Some(Container::ListItem {
607 buffer,
608 content_col,
609 ..
610 }) = self.containers.stack.last()
611 else {
612 return false;
613 };
614 if buffer.segment_count() != 1 {
615 return false;
616 }
617 let Some(text_line) = buffer.first_text() else {
618 return false;
619 };
620
621 let content_col = *content_col;
626 let (underline_indent_cols, _) = leading_indent(content);
627 if underline_indent_cols < content_col {
628 return false;
629 }
630
631 let lines = [text_line, content];
632 let Some((level, _)) = try_parse_setext_heading(&lines, 0) else {
633 return false;
634 };
635
636 let (text_no_newline, _) = strip_newline(text_line);
637 if text_no_newline.trim().is_empty() {
638 return false;
639 }
640 if try_parse_horizontal_rule(text_no_newline).is_some() {
641 return false;
642 }
643
644 let text_owned = text_line.to_string();
645 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
646 buffer.clear();
647 }
648 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
649 self.pos += 1;
650 true
651 }
652
653 fn close_paragraph_if_open(&mut self) {
655 if self.is_paragraph_open() {
656 self.close_containers_to(self.containers.depth() - 1);
657 }
658 }
659
660 fn prepare_for_block_element(&mut self) {
663 self.emit_list_item_buffer_if_needed();
664 self.close_paragraph_if_open();
665 }
666
667 fn close_open_footnote_definition(&mut self) {
671 while matches!(
672 self.containers.last(),
673 Some(Container::FootnoteDefinition { .. })
674 ) {
675 self.close_containers_to(self.containers.depth() - 1);
676 }
677 }
678
679 fn handle_footnote_open_effect(
680 &mut self,
681 block_match: &super::block_dispatcher::PreparedBlockMatch,
682 content: &str,
683 ) {
684 let content_start = block_match
685 .payload
686 .as_ref()
687 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
688 .map(|p| p.content_start)
689 .unwrap_or(0);
690
691 let content_col = 4;
692 self.containers
693 .push(Container::FootnoteDefinition { content_col });
694
695 if content_start > 0 {
696 let first_line_content = &content[content_start..];
697 if !first_line_content.trim().is_empty() {
698 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
699 paragraphs::append_paragraph_line(
700 &mut self.containers,
701 &mut self.builder,
702 first_line_content,
703 self.config,
704 );
705 } else {
706 let (_, newline_str) = strip_newline(content);
707 if !newline_str.is_empty() {
708 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
709 }
710 }
711 }
712 }
713
714 fn try_lazy_list_continuation(
726 &mut self,
727 block_match: &super::block_dispatcher::PreparedBlockMatch,
728 content: &str,
729 ) -> bool {
730 use super::block_dispatcher::ListPrepared;
731
732 let Some(prepared) = block_match
733 .payload
734 .as_ref()
735 .and_then(|p| p.downcast_ref::<ListPrepared>())
736 else {
737 return false;
738 };
739
740 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
741 return false;
742 }
743
744 let current_content_col = paragraphs::current_content_col(&self.containers);
745 if prepared.indent_cols >= current_content_col {
746 return false;
747 }
748
749 if lists::find_matching_list_level(
750 &self.containers,
751 &prepared.marker,
752 prepared.indent_cols,
753 self.config.dialect,
754 )
755 .is_some()
756 {
757 return false;
758 }
759
760 match self.containers.last() {
761 Some(Container::Paragraph { .. }) => {
762 paragraphs::append_paragraph_line(
763 &mut self.containers,
764 &mut self.builder,
765 content,
766 self.config,
767 );
768 true
769 }
770 Some(Container::ListItem { .. }) => {
771 if let Some(Container::ListItem {
772 buffer,
773 marker_only,
774 ..
775 }) = self.containers.stack.last_mut()
776 {
777 buffer.push_text(content);
778 if !content.trim().is_empty() {
779 *marker_only = false;
780 }
781 }
782 true
783 }
784 _ => false,
785 }
786 }
787
788 fn handle_list_open_effect(
789 &mut self,
790 block_match: &super::block_dispatcher::PreparedBlockMatch,
791 content: &str,
792 indent_to_emit: Option<&str>,
793 ) {
794 use super::block_dispatcher::ListPrepared;
795
796 let prepared = block_match
797 .payload
798 .as_ref()
799 .and_then(|p| p.downcast_ref::<ListPrepared>());
800 let Some(prepared) = prepared else {
801 return;
802 };
803
804 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
805 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
806 paragraphs::append_paragraph_line(
807 &mut self.containers,
808 &mut self.builder,
809 content,
810 self.config,
811 );
812 return;
813 }
814
815 if self.is_paragraph_open() {
816 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
817 paragraphs::append_paragraph_line(
818 &mut self.containers,
819 &mut self.builder,
820 content,
821 self.config,
822 );
823 return;
824 }
825 self.close_containers_to(self.containers.depth() - 1);
826 }
827
828 if matches!(
829 self.containers.last(),
830 Some(Container::Definition {
831 plain_open: true,
832 ..
833 })
834 ) {
835 self.emit_buffered_plain_if_needed();
836 }
837
838 let matched_level = lists::find_matching_list_level(
839 &self.containers,
840 &prepared.marker,
841 prepared.indent_cols,
842 self.config.dialect,
843 );
844 let list_item = ListItemEmissionInput {
845 content,
846 marker_len: prepared.marker_len,
847 spaces_after_cols: prepared.spaces_after_cols,
848 spaces_after_bytes: prepared.spaces_after,
849 indent_cols: prepared.indent_cols,
850 indent_bytes: prepared.indent_bytes,
851 virtual_marker_space: prepared.virtual_marker_space,
852 };
853 let current_content_col = paragraphs::current_content_col(&self.containers);
854 let deep_ordered_matched_level = matched_level
855 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
856 .and_then(|(level, container)| match container {
857 Container::List {
858 marker: list_marker,
859 base_indent_cols,
860 ..
861 } if matches!(
862 (&prepared.marker, list_marker),
863 (ListMarker::Ordered(_), ListMarker::Ordered(_))
864 ) && prepared.indent_cols >= 4
865 && *base_indent_cols >= 4
866 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
867 {
868 Some(level)
869 }
870 _ => None,
871 });
872
873 if deep_ordered_matched_level.is_none()
874 && current_content_col > 0
875 && prepared.indent_cols >= current_content_col
876 {
877 if let Some(level) = matched_level
878 && let Some(Container::List {
879 base_indent_cols, ..
880 }) = self.containers.stack.get(level)
881 && prepared.indent_cols == *base_indent_cols
882 {
883 let num_parent_lists = self.containers.stack[..level]
884 .iter()
885 .filter(|c| matches!(c, Container::List { .. }))
886 .count();
887
888 if num_parent_lists > 0 {
889 self.close_containers_to(level + 1);
890
891 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
892 self.close_containers_to(self.containers.depth() - 1);
893 }
894 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
895 self.close_containers_to(self.containers.depth() - 1);
896 }
897
898 if let Some(indent_str) = indent_to_emit {
899 self.builder
900 .token(SyntaxKind::WHITESPACE.into(), indent_str);
901 }
902
903 if let Some(nested_marker) = prepared.nested_marker {
904 lists::add_list_item_with_nested_empty_list(
905 &mut self.containers,
906 &mut self.builder,
907 &list_item,
908 nested_marker,
909 );
910 } else {
911 lists::add_list_item(
912 &mut self.containers,
913 &mut self.builder,
914 &list_item,
915 self.config,
916 );
917 }
918 self.maybe_open_fenced_code_in_new_list_item();
919 self.maybe_open_indented_code_in_new_list_item();
920 return;
921 }
922 }
923
924 self.emit_list_item_buffer_if_needed();
925
926 start_nested_list(
927 &mut self.containers,
928 &mut self.builder,
929 &prepared.marker,
930 &list_item,
931 indent_to_emit,
932 self.config,
933 );
934 self.maybe_open_fenced_code_in_new_list_item();
935 self.maybe_open_indented_code_in_new_list_item();
936 return;
937 }
938
939 if let Some(level) = matched_level {
940 self.close_containers_to(level + 1);
941
942 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
943 self.close_containers_to(self.containers.depth() - 1);
944 }
945 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
946 self.close_containers_to(self.containers.depth() - 1);
947 }
948
949 if let Some(indent_str) = indent_to_emit {
950 self.builder
951 .token(SyntaxKind::WHITESPACE.into(), indent_str);
952 }
953
954 if let Some(nested_marker) = prepared.nested_marker {
955 lists::add_list_item_with_nested_empty_list(
956 &mut self.containers,
957 &mut self.builder,
958 &list_item,
959 nested_marker,
960 );
961 } else {
962 lists::add_list_item(
963 &mut self.containers,
964 &mut self.builder,
965 &list_item,
966 self.config,
967 );
968 }
969 self.maybe_open_fenced_code_in_new_list_item();
970 self.maybe_open_indented_code_in_new_list_item();
971 return;
972 }
973
974 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
975 self.close_containers_to(self.containers.depth() - 1);
976 }
977 while matches!(
978 self.containers.last(),
979 Some(Container::ListItem { .. } | Container::List { .. })
980 ) {
981 self.close_containers_to(self.containers.depth() - 1);
982 }
983
984 self.builder.start_node(SyntaxKind::LIST.into());
985 if let Some(indent_str) = indent_to_emit {
986 self.builder
987 .token(SyntaxKind::WHITESPACE.into(), indent_str);
988 }
989 self.containers.push(Container::List {
990 marker: prepared.marker.clone(),
991 base_indent_cols: prepared.indent_cols,
992 has_blank_between_items: false,
993 });
994
995 if let Some(nested_marker) = prepared.nested_marker {
996 lists::add_list_item_with_nested_empty_list(
997 &mut self.containers,
998 &mut self.builder,
999 &list_item,
1000 nested_marker,
1001 );
1002 } else {
1003 lists::add_list_item(
1004 &mut self.containers,
1005 &mut self.builder,
1006 &list_item,
1007 self.config,
1008 );
1009 }
1010 self.maybe_open_fenced_code_in_new_list_item();
1011 self.maybe_open_indented_code_in_new_list_item();
1012 }
1013
1014 fn handle_definition_list_effect(
1015 &mut self,
1016 block_match: &super::block_dispatcher::PreparedBlockMatch,
1017 content: &str,
1018 indent_to_emit: Option<&str>,
1019 ) {
1020 use super::block_dispatcher::DefinitionPrepared;
1021
1022 let prepared = block_match
1023 .payload
1024 .as_ref()
1025 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1026 let Some(prepared) = prepared else {
1027 return;
1028 };
1029
1030 match prepared {
1031 DefinitionPrepared::Definition {
1032 marker_char,
1033 indent,
1034 spaces_after,
1035 spaces_after_cols,
1036 has_content,
1037 } => {
1038 self.emit_buffered_plain_if_needed();
1039
1040 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1041 self.close_containers_to(self.containers.depth() - 1);
1042 }
1043 while matches!(self.containers.last(), Some(Container::List { .. })) {
1044 self.close_containers_to(self.containers.depth() - 1);
1045 }
1046
1047 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1048 self.close_containers_to(self.containers.depth() - 1);
1049 }
1050
1051 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1052 self.close_containers_to(self.containers.depth() - 1);
1053 }
1054
1055 if definition_lists::in_definition_list(&self.containers)
1059 && !matches!(
1060 self.containers.last(),
1061 Some(Container::DefinitionItem { .. })
1062 )
1063 {
1064 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1065 self.containers.push(Container::DefinitionItem {});
1066 }
1067
1068 if !definition_lists::in_definition_list(&self.containers) {
1069 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1070 self.containers.push(Container::DefinitionList {});
1071 }
1072
1073 if !matches!(
1074 self.containers.last(),
1075 Some(Container::DefinitionItem { .. })
1076 ) {
1077 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1078 self.containers.push(Container::DefinitionItem {});
1079 }
1080
1081 self.builder.start_node(SyntaxKind::DEFINITION.into());
1082
1083 if let Some(indent_str) = indent_to_emit {
1084 self.builder
1085 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1086 }
1087
1088 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1089 let indent_bytes = byte_index_at_column(content, *indent);
1090 if *spaces_after > 0 {
1091 let space_start = indent_bytes + 1;
1092 let space_end = space_start + *spaces_after;
1093 if space_end <= content.len() {
1094 self.builder.token(
1095 SyntaxKind::WHITESPACE.into(),
1096 &content[space_start..space_end],
1097 );
1098 }
1099 }
1100
1101 if !*has_content {
1102 let current_line = self.lines[self.pos];
1103 let (_, newline_str) = strip_newline(current_line);
1104 if !newline_str.is_empty() {
1105 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1106 }
1107 }
1108
1109 let content_col = *indent + 1 + *spaces_after_cols;
1110 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1111 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1112 let mut plain_buffer = TextBuffer::new();
1113 let mut definition_pushed = false;
1114
1115 if *has_content {
1116 let current_line = self.lines[self.pos];
1117 let (trimmed_line, _) = strip_newline(current_line);
1118
1119 let content_start = content_start_bytes.min(trimmed_line.len());
1120 let content_slice = &trimmed_line[content_start..];
1121 let content_line = ¤t_line[content_start_bytes.min(current_line.len())..];
1122
1123 let (blockquote_depth, inner_blockquote_content) =
1124 count_blockquote_markers(content_line);
1125
1126 let should_start_list_from_first_line = self
1127 .lines
1128 .get(self.pos + 1)
1129 .map(|next_line| {
1130 let (next_without_newline, _) = strip_newline(next_line);
1131 if next_without_newline.trim().is_empty() {
1132 return false;
1133 }
1134
1135 let (next_indent_cols, _) = leading_indent(next_without_newline);
1136 next_indent_cols >= content_col
1137 })
1138 .unwrap_or(false);
1139
1140 if blockquote_depth > 0 {
1141 self.containers.push(Container::Definition {
1142 content_col,
1143 plain_open: false,
1144 plain_buffer: TextBuffer::new(),
1145 });
1146 definition_pushed = true;
1147
1148 let marker_info = parse_blockquote_marker_info(content_line);
1149 for level in 0..blockquote_depth {
1150 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1151 if let Some(info) = marker_info.get(level) {
1152 blockquotes::emit_one_blockquote_marker(
1153 &mut self.builder,
1154 info.leading_spaces,
1155 info.has_trailing_space,
1156 );
1157 }
1158 self.containers.push(Container::BlockQuote {});
1159 }
1160
1161 if !inner_blockquote_content.trim().is_empty() {
1162 paragraphs::start_paragraph_if_needed(
1163 &mut self.containers,
1164 &mut self.builder,
1165 );
1166 paragraphs::append_paragraph_line(
1167 &mut self.containers,
1168 &mut self.builder,
1169 inner_blockquote_content,
1170 self.config,
1171 );
1172 }
1173 } else if let Some(marker_match) =
1174 try_parse_list_marker(content_slice, self.config)
1175 && should_start_list_from_first_line
1176 {
1177 self.containers.push(Container::Definition {
1178 content_col,
1179 plain_open: false,
1180 plain_buffer: TextBuffer::new(),
1181 });
1182 definition_pushed = true;
1183
1184 let (indent_cols, indent_bytes) = leading_indent(content_line);
1185 self.builder.start_node(SyntaxKind::LIST.into());
1186 self.containers.push(Container::List {
1187 marker: marker_match.marker.clone(),
1188 base_indent_cols: indent_cols,
1189 has_blank_between_items: false,
1190 });
1191
1192 let list_item = ListItemEmissionInput {
1193 content: content_line,
1194 marker_len: marker_match.marker_len,
1195 spaces_after_cols: marker_match.spaces_after_cols,
1196 spaces_after_bytes: marker_match.spaces_after_bytes,
1197 indent_cols,
1198 indent_bytes,
1199 virtual_marker_space: marker_match.virtual_marker_space,
1200 };
1201
1202 if let Some(nested_marker) = is_content_nested_bullet_marker(
1203 content_line,
1204 marker_match.marker_len,
1205 marker_match.spaces_after_bytes,
1206 ) {
1207 lists::add_list_item_with_nested_empty_list(
1208 &mut self.containers,
1209 &mut self.builder,
1210 &list_item,
1211 nested_marker,
1212 );
1213 } else {
1214 lists::add_list_item(
1215 &mut self.containers,
1216 &mut self.builder,
1217 &list_item,
1218 self.config,
1219 );
1220 }
1221 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
1222 self.containers.push(Container::Definition {
1223 content_col,
1224 plain_open: false,
1225 plain_buffer: TextBuffer::new(),
1226 });
1227 definition_pushed = true;
1228
1229 let bq_depth = self.current_blockquote_depth();
1230 if let Some(indent_str) = indent_to_emit {
1231 self.builder
1232 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1233 }
1234 let fence_line = current_line[content_start..].to_string();
1235 let new_pos = if self.config.extensions.tex_math_gfm
1236 && code_blocks::is_gfm_math_fence(&fence)
1237 {
1238 code_blocks::parse_fenced_math_block(
1239 &mut self.builder,
1240 &self.lines,
1241 self.pos,
1242 fence,
1243 bq_depth,
1244 content_col,
1245 Some(&fence_line),
1246 )
1247 } else {
1248 code_blocks::parse_fenced_code_block(
1249 &mut self.builder,
1250 &self.lines,
1251 self.pos,
1252 fence,
1253 bq_depth,
1254 content_col,
1255 Some(&fence_line),
1256 )
1257 };
1258 self.pos = new_pos - 1;
1259 } else {
1260 let (_, newline_str) = strip_newline(current_line);
1261 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1262 if content_without_newline.is_empty() {
1263 plain_buffer.push_line(newline_str);
1264 } else {
1265 let line_with_newline = if !newline_str.is_empty() {
1266 format!("{}{}", content_without_newline, newline_str)
1267 } else {
1268 content_without_newline.to_string()
1269 };
1270 plain_buffer.push_line(line_with_newline);
1271 }
1272 }
1273 }
1274
1275 if !definition_pushed {
1276 self.containers.push(Container::Definition {
1277 content_col,
1278 plain_open: *has_content,
1279 plain_buffer,
1280 });
1281 }
1282 }
1283 DefinitionPrepared::Term { blank_count } => {
1284 self.emit_buffered_plain_if_needed();
1285
1286 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1287 self.close_containers_to(self.containers.depth() - 1);
1288 }
1289
1290 if !definition_lists::in_definition_list(&self.containers) {
1291 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1292 self.containers.push(Container::DefinitionList {});
1293 }
1294
1295 while matches!(
1296 self.containers.last(),
1297 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1298 ) {
1299 self.close_containers_to(self.containers.depth() - 1);
1300 }
1301
1302 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1303 self.containers.push(Container::DefinitionItem {});
1304
1305 emit_term(&mut self.builder, content, self.config);
1306
1307 for i in 0..*blank_count {
1308 let blank_pos = self.pos + 1 + i;
1309 if blank_pos < self.lines.len() {
1310 let blank_line = self.lines[blank_pos];
1311 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1312 self.builder
1313 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1314 self.builder.finish_node();
1315 }
1316 }
1317 self.pos += *blank_count;
1318 }
1319 }
1320 }
1321
1322 fn blockquote_marker_info(
1324 &self,
1325 payload: Option<&BlockQuotePrepared>,
1326 line: &str,
1327 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1328 payload
1329 .map(|payload| payload.marker_info.clone())
1330 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1331 }
1332
1333 fn marker_info_for_line(
1339 &self,
1340 payload: Option<&BlockQuotePrepared>,
1341 raw_line: &str,
1342 marker_line: &str,
1343 shifted_prefix: &str,
1344 used_shifted: bool,
1345 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1346 let mut marker_info = if used_shifted {
1347 parse_blockquote_marker_info(marker_line)
1348 } else {
1349 self.blockquote_marker_info(payload, raw_line)
1350 };
1351 if used_shifted && !shifted_prefix.is_empty() {
1352 let (prefix_cols, _) = leading_indent(shifted_prefix);
1353 if let Some(first) = marker_info.first_mut() {
1354 first.leading_spaces += prefix_cols;
1355 }
1356 }
1357 marker_info
1358 }
1359
1360 fn shifted_blockquote_from_list<'b>(
1363 &self,
1364 line: &'b str,
1365 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1366 if !lists::in_list(&self.containers) {
1367 return None;
1368 }
1369 let list_content_col = paragraphs::current_content_col(&self.containers);
1370 let content_container_indent = self.content_container_indent_to_strip();
1371 let marker_col = list_content_col.saturating_add(content_container_indent);
1372 if marker_col == 0 {
1373 return None;
1374 }
1375
1376 let (indent_cols, _) = leading_indent(line);
1377 if indent_cols < marker_col {
1378 return None;
1379 }
1380
1381 let idx = byte_index_at_column(line, marker_col);
1382 if idx > line.len() {
1383 return None;
1384 }
1385
1386 let candidate = &line[idx..];
1387 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1388 if candidate_depth == 0 {
1389 return None;
1390 }
1391
1392 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1393 }
1394
1395 fn emit_blockquote_markers(
1396 &mut self,
1397 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1398 depth: usize,
1399 ) {
1400 for i in 0..depth {
1401 if let Some(info) = marker_info.get(i) {
1402 blockquotes::emit_one_blockquote_marker(
1403 &mut self.builder,
1404 info.leading_spaces,
1405 info.has_trailing_space,
1406 );
1407 }
1408 }
1409 }
1410
1411 fn current_blockquote_depth(&self) -> usize {
1412 blockquotes::current_blockquote_depth(&self.containers)
1413 }
1414
1415 fn emit_or_buffer_blockquote_marker(
1420 &mut self,
1421 leading_spaces: usize,
1422 has_trailing_space: bool,
1423 ) {
1424 if let Some(Container::ListItem {
1425 buffer,
1426 marker_only,
1427 ..
1428 }) = self.containers.stack.last_mut()
1429 {
1430 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1431 *marker_only = false;
1432 return;
1433 }
1434
1435 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1437 paragraphs::append_paragraph_marker(
1439 &mut self.containers,
1440 leading_spaces,
1441 has_trailing_space,
1442 );
1443 } else {
1444 blockquotes::emit_one_blockquote_marker(
1446 &mut self.builder,
1447 leading_spaces,
1448 has_trailing_space,
1449 );
1450 }
1451 }
1452
1453 fn parse_document_stack(&mut self) {
1454 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1455
1456 log::trace!("Starting document parse");
1457
1458 while self.pos < self.lines.len() {
1461 let line = self.lines[self.pos];
1462
1463 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1464
1465 if self.parse_line(line) {
1466 continue;
1467 }
1468 self.pos += 1;
1469 }
1470
1471 self.close_containers_to(0);
1472 self.builder.finish_node(); }
1474
1475 fn parse_line(&mut self, line: &str) -> bool {
1477 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1480 let mut bq_marker_line = line;
1481 let mut shifted_bq_prefix = "";
1482 let mut used_shifted_bq = false;
1483 if bq_depth == 0
1484 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1485 self.shifted_blockquote_from_list(line)
1486 {
1487 bq_depth = candidate_depth;
1488 inner_content = candidate_inner;
1489 bq_marker_line = candidate_line;
1490 shifted_bq_prefix = candidate_prefix;
1491 used_shifted_bq = true;
1492 }
1493 let current_bq_depth = self.current_blockquote_depth();
1494
1495 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1496 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1497 let dispatcher_ctx = if current_bq_depth == 0 {
1498 Some(BlockContext {
1499 content: line,
1500 has_blank_before,
1501 has_blank_before_strict: has_blank_before,
1502 at_document_start: self.pos == 0,
1503 in_fenced_div: self.in_fenced_div(),
1504 blockquote_depth: current_bq_depth,
1505 config: self.config,
1506 content_indent: 0,
1507 indent_to_emit: None,
1508 list_indent_info: None,
1509 in_list: lists::in_list(&self.containers),
1510 in_marker_only_list_item: matches!(
1511 self.containers.last(),
1512 Some(Container::ListItem {
1513 marker_only: true,
1514 ..
1515 })
1516 ),
1517 next_line: if self.pos + 1 < self.lines.len() {
1518 Some(self.lines[self.pos + 1])
1519 } else {
1520 None
1521 },
1522 })
1523 } else {
1524 None
1525 };
1526
1527 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1528 self.block_registry
1529 .detect_prepared(dispatcher_ctx, &self.lines, self.pos)
1530 .and_then(|prepared| {
1531 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1532 blockquote_match = Some(prepared);
1533 blockquote_match.as_ref().and_then(|prepared| {
1534 prepared
1535 .payload
1536 .as_ref()
1537 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1538 .cloned()
1539 })
1540 } else {
1541 None
1542 }
1543 })
1544 } else {
1545 None
1546 };
1547
1548 log::trace!(
1549 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1550 self.pos,
1551 bq_depth,
1552 current_bq_depth,
1553 self.containers.depth(),
1554 line.trim_end()
1555 );
1556
1557 let is_blank = is_blank_line(line) || (bq_depth > 0 && is_blank_line(inner_content));
1560
1561 if is_blank {
1562 if self.is_paragraph_open()
1563 && paragraphs::has_open_inline_math_environment(&self.containers)
1564 {
1565 paragraphs::append_paragraph_line(
1566 &mut self.containers,
1567 &mut self.builder,
1568 line,
1569 self.config,
1570 );
1571 self.pos += 1;
1572 return true;
1573 }
1574
1575 self.close_paragraph_if_open();
1577
1578 self.emit_buffered_plain_if_needed();
1582
1583 if bq_depth > current_bq_depth {
1591 for _ in current_bq_depth..bq_depth {
1593 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1594 self.containers.push(Container::BlockQuote {});
1595 }
1596 } else if bq_depth < current_bq_depth {
1597 self.close_blockquotes_to_depth(bq_depth);
1599 }
1600
1601 let mut peek = self.pos + 1;
1603 while peek < self.lines.len() && is_blank_line(self.lines[peek]) {
1604 peek += 1;
1605 }
1606
1607 let levels_to_keep = if peek < self.lines.len() {
1609 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1610 self.current_blockquote_depth(),
1611 &self.containers,
1612 &self.lines,
1613 peek,
1614 self.lines[peek],
1615 )
1616 } else {
1617 0
1618 };
1619 log::trace!(
1620 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1621 self.containers.depth(),
1622 levels_to_keep,
1623 if peek < self.lines.len() {
1624 self.lines[peek]
1625 } else {
1626 "<EOF>"
1627 }
1628 );
1629
1630 while self.containers.depth() > levels_to_keep {
1634 match self.containers.last() {
1635 Some(Container::ListItem { .. }) => {
1636 log::trace!(
1638 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1639 levels_to_keep,
1640 self.containers.depth()
1641 );
1642 self.close_containers_to(self.containers.depth() - 1);
1643 }
1644 Some(Container::List { .. })
1645 | Some(Container::FootnoteDefinition { .. })
1646 | Some(Container::Alert { .. })
1647 | Some(Container::Paragraph { .. })
1648 | Some(Container::Definition { .. })
1649 | Some(Container::DefinitionItem { .. })
1650 | Some(Container::DefinitionList { .. }) => {
1651 log::trace!(
1652 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1653 self.containers.last(),
1654 self.containers.depth(),
1655 levels_to_keep
1656 );
1657
1658 self.close_containers_to(self.containers.depth() - 1);
1659 }
1660 _ => break,
1661 }
1662 }
1663
1664 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1668 self.emit_list_item_buffer_if_needed();
1669 }
1670
1671 if bq_depth > 0 {
1673 let marker_info = self.marker_info_for_line(
1674 blockquote_payload.as_ref(),
1675 line,
1676 bq_marker_line,
1677 shifted_bq_prefix,
1678 used_shifted_bq,
1679 );
1680 self.emit_blockquote_markers(&marker_info, bq_depth);
1681 }
1682
1683 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1684 self.builder
1685 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1686 self.builder.finish_node();
1687
1688 self.pos += 1;
1689 return true;
1690 }
1691
1692 if bq_depth > current_bq_depth {
1694 if self.config.extensions.blank_before_blockquote
1697 && current_bq_depth == 0
1698 && !used_shifted_bq
1699 && !blockquote_payload
1700 .as_ref()
1701 .map(|payload| payload.can_start)
1702 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1703 {
1704 self.emit_list_item_buffer_if_needed();
1708 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1709 paragraphs::append_paragraph_line(
1710 &mut self.containers,
1711 &mut self.builder,
1712 line,
1713 self.config,
1714 );
1715 self.pos += 1;
1716 return true;
1717 }
1718
1719 let can_nest = if current_bq_depth > 0 {
1722 if self.config.extensions.blank_before_blockquote {
1723 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1725 || (self.pos > 0 && {
1726 let prev_line = self.lines[self.pos - 1];
1727 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1728 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
1729 })
1730 } else {
1731 true
1732 }
1733 } else {
1734 blockquote_payload
1735 .as_ref()
1736 .map(|payload| payload.can_nest)
1737 .unwrap_or(true)
1738 };
1739
1740 if !can_nest {
1741 let content_at_current_depth =
1744 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
1745
1746 let marker_info = self.marker_info_for_line(
1748 blockquote_payload.as_ref(),
1749 line,
1750 bq_marker_line,
1751 shifted_bq_prefix,
1752 used_shifted_bq,
1753 );
1754 for i in 0..current_bq_depth {
1755 if let Some(info) = marker_info.get(i) {
1756 self.emit_or_buffer_blockquote_marker(
1757 info.leading_spaces,
1758 info.has_trailing_space,
1759 );
1760 }
1761 }
1762
1763 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1764 paragraphs::append_paragraph_line(
1766 &mut self.containers,
1767 &mut self.builder,
1768 content_at_current_depth,
1769 self.config,
1770 );
1771 self.pos += 1;
1772 return true;
1773 } else {
1774 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1776 paragraphs::append_paragraph_line(
1777 &mut self.containers,
1778 &mut self.builder,
1779 content_at_current_depth,
1780 self.config,
1781 );
1782 self.pos += 1;
1783 return true;
1784 }
1785 }
1786
1787 self.emit_list_item_buffer_if_needed();
1790
1791 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1793 self.close_containers_to(self.containers.depth() - 1);
1794 }
1795
1796 let marker_info = self.marker_info_for_line(
1798 blockquote_payload.as_ref(),
1799 line,
1800 bq_marker_line,
1801 shifted_bq_prefix,
1802 used_shifted_bq,
1803 );
1804
1805 if let (Some(dispatcher_ctx), Some(prepared)) =
1806 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
1807 {
1808 let _ = self.block_registry.parse_prepared(
1809 prepared,
1810 dispatcher_ctx,
1811 &mut self.builder,
1812 &self.lines,
1813 self.pos,
1814 );
1815 for _ in 0..bq_depth {
1816 self.containers.push(Container::BlockQuote {});
1817 }
1818 } else {
1819 for level in 0..current_bq_depth {
1821 if let Some(info) = marker_info.get(level) {
1822 self.emit_or_buffer_blockquote_marker(
1823 info.leading_spaces,
1824 info.has_trailing_space,
1825 );
1826 }
1827 }
1828
1829 for level in current_bq_depth..bq_depth {
1831 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1832
1833 if let Some(info) = marker_info.get(level) {
1835 blockquotes::emit_one_blockquote_marker(
1836 &mut self.builder,
1837 info.leading_spaces,
1838 info.has_trailing_space,
1839 );
1840 }
1841
1842 self.containers.push(Container::BlockQuote {});
1843 }
1844 }
1845
1846 return self.parse_inner_content(inner_content, Some(inner_content));
1849 } else if bq_depth < current_bq_depth {
1850 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1856 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
1863 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
1864 let interrupts_via_fence =
1865 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
1866 if !interrupts_via_hr && !interrupts_via_fence {
1867 if bq_depth > 0 {
1868 let marker_info = self.marker_info_for_line(
1874 blockquote_payload.as_ref(),
1875 line,
1876 bq_marker_line,
1877 shifted_bq_prefix,
1878 used_shifted_bq,
1879 );
1880 for i in 0..bq_depth {
1881 if let Some(info) = marker_info.get(i) {
1882 paragraphs::append_paragraph_marker(
1883 &mut self.containers,
1884 info.leading_spaces,
1885 info.has_trailing_space,
1886 );
1887 }
1888 }
1889 paragraphs::append_paragraph_line(
1890 &mut self.containers,
1891 &mut self.builder,
1892 inner_content,
1893 self.config,
1894 );
1895 } else {
1896 paragraphs::append_paragraph_line(
1897 &mut self.containers,
1898 &mut self.builder,
1899 line,
1900 self.config,
1901 );
1902 }
1903 self.pos += 1;
1904 return true;
1905 }
1906 }
1907 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
1913 if lists::in_blockquote_list(&self.containers)
1916 && let Some(marker_match) = try_parse_list_marker(line, self.config)
1917 {
1918 let (indent_cols, indent_bytes) = leading_indent(line);
1919 if let Some(level) = lists::find_matching_list_level(
1920 &self.containers,
1921 &marker_match.marker,
1922 indent_cols,
1923 self.config.dialect,
1924 ) {
1925 self.close_containers_to(level + 1);
1928
1929 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1931 self.close_containers_to(self.containers.depth() - 1);
1932 }
1933 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1934 self.close_containers_to(self.containers.depth() - 1);
1935 }
1936
1937 if let Some(nested_marker) = is_content_nested_bullet_marker(
1939 line,
1940 marker_match.marker_len,
1941 marker_match.spaces_after_bytes,
1942 ) {
1943 let list_item = ListItemEmissionInput {
1944 content: line,
1945 marker_len: marker_match.marker_len,
1946 spaces_after_cols: marker_match.spaces_after_cols,
1947 spaces_after_bytes: marker_match.spaces_after_bytes,
1948 indent_cols,
1949 indent_bytes,
1950 virtual_marker_space: marker_match.virtual_marker_space,
1951 };
1952 lists::add_list_item_with_nested_empty_list(
1953 &mut self.containers,
1954 &mut self.builder,
1955 &list_item,
1956 nested_marker,
1957 );
1958 } else {
1959 let list_item = ListItemEmissionInput {
1960 content: line,
1961 marker_len: marker_match.marker_len,
1962 spaces_after_cols: marker_match.spaces_after_cols,
1963 spaces_after_bytes: marker_match.spaces_after_bytes,
1964 indent_cols,
1965 indent_bytes,
1966 virtual_marker_space: marker_match.virtual_marker_space,
1967 };
1968 lists::add_list_item(
1969 &mut self.containers,
1970 &mut self.builder,
1971 &list_item,
1972 self.config,
1973 );
1974 }
1975 self.pos += 1;
1976 return true;
1977 }
1978 }
1979 }
1980
1981 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1983 self.close_containers_to(self.containers.depth() - 1);
1984 }
1985
1986 self.close_blockquotes_to_depth(bq_depth);
1988
1989 if bq_depth > 0 {
1991 let marker_info = self.marker_info_for_line(
1993 blockquote_payload.as_ref(),
1994 line,
1995 bq_marker_line,
1996 shifted_bq_prefix,
1997 used_shifted_bq,
1998 );
1999 for i in 0..bq_depth {
2000 if let Some(info) = marker_info.get(i) {
2001 self.emit_or_buffer_blockquote_marker(
2002 info.leading_spaces,
2003 info.has_trailing_space,
2004 );
2005 }
2006 }
2007 return self.parse_inner_content(inner_content, Some(inner_content));
2009 } else {
2010 return self.parse_inner_content(line, None);
2012 }
2013 } else if bq_depth > 0 {
2014 let mut list_item_continuation = false;
2016 let same_depth_marker_info = self.marker_info_for_line(
2017 blockquote_payload.as_ref(),
2018 line,
2019 bq_marker_line,
2020 shifted_bq_prefix,
2021 used_shifted_bq,
2022 );
2023 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2024
2025 if matches!(
2028 self.containers.last(),
2029 Some(Container::ListItem { content_col: _, .. })
2030 ) {
2031 let (indent_cols, _) = leading_indent(inner_content);
2032 let content_indent = self.content_container_indent_to_strip();
2033 let effective_indent = indent_cols.saturating_sub(content_indent);
2034 let content_col = match self.containers.last() {
2035 Some(Container::ListItem { content_col, .. }) => *content_col,
2036 _ => 0,
2037 };
2038
2039 let is_new_item_at_outer_level =
2041 if try_parse_list_marker(inner_content, self.config).is_some() {
2042 effective_indent < content_col
2043 } else {
2044 false
2045 };
2046
2047 if is_new_item_at_outer_level
2051 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2052 {
2053 log::trace!(
2054 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2055 is_new_item_at_outer_level,
2056 effective_indent,
2057 content_col
2058 );
2059 self.close_containers_to(self.containers.depth() - 1);
2060 } else {
2061 log::trace!(
2062 "Keeping ListItem: effective_indent={} >= content_col={}",
2063 effective_indent,
2064 content_col
2065 );
2066 list_item_continuation = true;
2067 }
2068 }
2069
2070 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
2074 {
2075 list_item_continuation = false;
2076 }
2077
2078 let continuation_has_explicit_marker = list_item_continuation && {
2079 if has_explicit_same_depth_marker {
2080 for i in 0..bq_depth {
2081 if let Some(info) = same_depth_marker_info.get(i) {
2082 self.emit_or_buffer_blockquote_marker(
2083 info.leading_spaces,
2084 info.has_trailing_space,
2085 );
2086 }
2087 }
2088 true
2089 } else {
2090 false
2091 }
2092 };
2093
2094 if !list_item_continuation {
2095 let marker_info = self.marker_info_for_line(
2096 blockquote_payload.as_ref(),
2097 line,
2098 bq_marker_line,
2099 shifted_bq_prefix,
2100 used_shifted_bq,
2101 );
2102 for i in 0..bq_depth {
2103 if let Some(info) = marker_info.get(i) {
2104 self.emit_or_buffer_blockquote_marker(
2105 info.leading_spaces,
2106 info.has_trailing_space,
2107 );
2108 }
2109 }
2110 }
2111 let line_to_append = if list_item_continuation {
2112 if continuation_has_explicit_marker {
2113 Some(inner_content)
2114 } else {
2115 Some(line)
2116 }
2117 } else {
2118 Some(inner_content)
2119 };
2120 return self.parse_inner_content(inner_content, line_to_append);
2121 }
2122
2123 if current_bq_depth > 0 {
2126 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2128 paragraphs::append_paragraph_line(
2129 &mut self.containers,
2130 &mut self.builder,
2131 line,
2132 self.config,
2133 );
2134 self.pos += 1;
2135 return true;
2136 }
2137
2138 if lists::in_blockquote_list(&self.containers)
2140 && let Some(marker_match) = try_parse_list_marker(line, self.config)
2141 {
2142 let (indent_cols, indent_bytes) = leading_indent(line);
2143 if let Some(level) = lists::find_matching_list_level(
2144 &self.containers,
2145 &marker_match.marker,
2146 indent_cols,
2147 self.config.dialect,
2148 ) {
2149 self.close_containers_to(level + 1);
2151
2152 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2154 self.close_containers_to(self.containers.depth() - 1);
2155 }
2156 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2157 self.close_containers_to(self.containers.depth() - 1);
2158 }
2159
2160 if let Some(nested_marker) = is_content_nested_bullet_marker(
2162 line,
2163 marker_match.marker_len,
2164 marker_match.spaces_after_bytes,
2165 ) {
2166 let list_item = ListItemEmissionInput {
2167 content: line,
2168 marker_len: marker_match.marker_len,
2169 spaces_after_cols: marker_match.spaces_after_cols,
2170 spaces_after_bytes: marker_match.spaces_after_bytes,
2171 indent_cols,
2172 indent_bytes,
2173 virtual_marker_space: marker_match.virtual_marker_space,
2174 };
2175 lists::add_list_item_with_nested_empty_list(
2176 &mut self.containers,
2177 &mut self.builder,
2178 &list_item,
2179 nested_marker,
2180 );
2181 } else {
2182 let list_item = ListItemEmissionInput {
2183 content: line,
2184 marker_len: marker_match.marker_len,
2185 spaces_after_cols: marker_match.spaces_after_cols,
2186 spaces_after_bytes: marker_match.spaces_after_bytes,
2187 indent_cols,
2188 indent_bytes,
2189 virtual_marker_space: marker_match.virtual_marker_space,
2190 };
2191 lists::add_list_item(
2192 &mut self.containers,
2193 &mut self.builder,
2194 &list_item,
2195 self.config,
2196 );
2197 }
2198 self.pos += 1;
2199 return true;
2200 }
2201 }
2202 }
2203
2204 self.parse_inner_content(line, None)
2206 }
2207
2208 fn content_container_indent_to_strip(&self) -> usize {
2210 self.containers
2211 .stack
2212 .iter()
2213 .filter_map(|c| match c {
2214 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2215 Container::Definition { content_col, .. } => Some(*content_col),
2216 _ => None,
2217 })
2218 .sum()
2219 }
2220
2221 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> bool {
2227 log::trace!(
2228 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2229 self.pos,
2230 self.containers.depth(),
2231 self.containers.last(),
2232 content.trim_end()
2233 );
2234 let content_indent = self.content_container_indent_to_strip();
2237 let (stripped_content, indent_to_emit) = if content_indent > 0 {
2238 let (indent_cols, _) = leading_indent(content);
2239 if indent_cols >= content_indent {
2240 let idx = byte_index_at_column(content, content_indent);
2241 (&content[idx..], Some(&content[..idx]))
2242 } else {
2243 let trimmed_start = content.trim_start();
2245 let ws_len = content.len() - trimmed_start.len();
2246 if ws_len > 0 {
2247 (trimmed_start, Some(&content[..ws_len]))
2248 } else {
2249 (content, None)
2250 }
2251 }
2252 } else {
2253 (content, None)
2254 };
2255
2256 if self.config.extensions.alerts
2257 && self.current_blockquote_depth() > 0
2258 && !self.in_active_alert()
2259 && !self.is_paragraph_open()
2260 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2261 {
2262 let (_, newline_str) = strip_newline(stripped_content);
2263 self.builder.start_node(SyntaxKind::ALERT.into());
2264 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2265 if !newline_str.is_empty() {
2266 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2267 }
2268 self.containers.push(Container::Alert {
2269 blockquote_depth: self.current_blockquote_depth(),
2270 });
2271 self.pos += 1;
2272 return true;
2273 }
2274
2275 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2279 let is_definition_marker =
2280 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2281 && !stripped_content.starts_with(':');
2282 if content_indent == 0 && is_definition_marker {
2283 } else {
2285 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2286
2287 if policy.definition_plain_can_continue(
2288 stripped_content,
2289 content,
2290 content_indent,
2291 &BlockContext {
2292 content: stripped_content,
2293 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2294 has_blank_before_strict: self.pos == 0
2295 || is_blank_line(self.lines[self.pos - 1]),
2296 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2297 in_fenced_div: self.in_fenced_div(),
2298 blockquote_depth: self.current_blockquote_depth(),
2299 config: self.config,
2300 content_indent,
2301 indent_to_emit: None,
2302 list_indent_info: None,
2303 in_list: lists::in_list(&self.containers),
2304 in_marker_only_list_item: matches!(
2305 self.containers.last(),
2306 Some(Container::ListItem {
2307 marker_only: true,
2308 ..
2309 })
2310 ),
2311 next_line: if self.pos + 1 < self.lines.len() {
2312 Some(self.lines[self.pos + 1])
2313 } else {
2314 None
2315 },
2316 },
2317 &self.lines,
2318 self.pos,
2319 ) {
2320 let content_line = stripped_content;
2321 let (text_without_newline, newline_str) = strip_newline(content_line);
2322 let indent_prefix = if !text_without_newline.trim().is_empty() {
2323 indent_to_emit.unwrap_or("")
2324 } else {
2325 ""
2326 };
2327 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2328
2329 if let Some(Container::Definition {
2330 plain_open,
2331 plain_buffer,
2332 ..
2333 }) = self.containers.stack.last_mut()
2334 {
2335 let line_with_newline = if !newline_str.is_empty() {
2336 format!("{}{}", content_line, newline_str)
2337 } else {
2338 content_line
2339 };
2340 plain_buffer.push_line(line_with_newline);
2341 *plain_open = true;
2342 }
2343
2344 self.pos += 1;
2345 return true;
2346 }
2347 }
2348 }
2349
2350 if content_indent > 0 {
2353 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2354 let current_bq_depth = self.current_blockquote_depth();
2355 let in_footnote_definition = self
2356 .containers
2357 .stack
2358 .iter()
2359 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2360
2361 if bq_depth > 0 {
2362 if in_footnote_definition
2363 && self.config.extensions.blank_before_blockquote
2364 && current_bq_depth == 0
2365 && !blockquotes::can_start_blockquote(self.pos, &self.lines)
2366 {
2367 } else {
2371 self.emit_buffered_plain_if_needed();
2374 self.emit_list_item_buffer_if_needed();
2375
2376 self.close_paragraph_if_open();
2379
2380 if bq_depth > current_bq_depth {
2381 let marker_info = parse_blockquote_marker_info(stripped_content);
2382
2383 for level in current_bq_depth..bq_depth {
2385 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2386
2387 if level == current_bq_depth
2388 && let Some(indent_str) = indent_to_emit
2389 {
2390 self.builder
2391 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2392 }
2393
2394 if let Some(info) = marker_info.get(level) {
2395 blockquotes::emit_one_blockquote_marker(
2396 &mut self.builder,
2397 info.leading_spaces,
2398 info.has_trailing_space,
2399 );
2400 }
2401
2402 self.containers.push(Container::BlockQuote {});
2403 }
2404 } else if bq_depth < current_bq_depth {
2405 self.close_blockquotes_to_depth(bq_depth);
2406 } else {
2407 let marker_info = parse_blockquote_marker_info(stripped_content);
2409 self.emit_blockquote_markers(&marker_info, bq_depth);
2410 }
2411
2412 return self.parse_inner_content(inner_content, Some(inner_content));
2413 }
2414 }
2415 }
2416
2417 let content = stripped_content;
2419
2420 if self.is_paragraph_open()
2421 && (paragraphs::has_open_inline_math_environment(&self.containers)
2422 || paragraphs::has_open_display_math_dollars(&self.containers))
2423 {
2424 paragraphs::append_paragraph_line(
2425 &mut self.containers,
2426 &mut self.builder,
2427 line_to_append.unwrap_or(self.lines[self.pos]),
2428 self.config,
2429 );
2430 self.pos += 1;
2431 return true;
2432 }
2433
2434 use super::blocks::lists;
2438 use super::blocks::paragraphs;
2439 let list_indent_info = if lists::in_list(&self.containers) {
2440 let content_col = paragraphs::current_content_col(&self.containers);
2441 if content_col > 0 {
2442 Some(super::block_dispatcher::ListIndentInfo { content_col })
2443 } else {
2444 None
2445 }
2446 } else {
2447 None
2448 };
2449
2450 let next_line = if self.pos + 1 < self.lines.len() {
2451 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2454 } else {
2455 None
2456 };
2457
2458 let current_bq_depth = self.current_blockquote_depth();
2459 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2460 && current_bq_depth < alert_bq_depth
2461 {
2462 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2463 self.close_containers_to(self.containers.depth() - 1);
2464 }
2465 }
2466
2467 let dispatcher_ctx = BlockContext {
2468 content,
2469 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2473 blockquote_depth: current_bq_depth,
2474 config: self.config,
2475 content_indent,
2476 indent_to_emit,
2477 list_indent_info,
2478 in_list: lists::in_list(&self.containers),
2479 in_marker_only_list_item: matches!(
2480 self.containers.last(),
2481 Some(Container::ListItem {
2482 marker_only: true,
2483 ..
2484 })
2485 ),
2486 next_line,
2487 };
2488
2489 let mut dispatcher_ctx = dispatcher_ctx;
2492
2493 if self.try_fold_list_item_buffer_into_setext(stripped_content) {
2497 return true;
2498 }
2499
2500 let dispatcher_match =
2503 self.block_registry
2504 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos);
2505
2506 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
2512 let has_blank_before = if self.pos == 0 || after_metadata_block {
2513 true
2514 } else {
2515 let prev_line = self.lines[self.pos - 1];
2516 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2517 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
2518 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
2519 && fenced_divs::try_parse_div_fence_open(
2520 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
2521 )
2522 .is_some();
2523
2524 let prev_line_blank = is_blank_line(prev_line);
2525 prev_line_blank
2526 || prev_is_fenced_div_open
2527 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2528 || !self.previous_block_requires_blank_before_heading()
2529 };
2530
2531 let at_document_start = self.pos == 0 && current_bq_depth == 0;
2534
2535 let prev_line_blank = if self.pos > 0 {
2536 let prev_line = self.lines[self.pos - 1];
2537 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2538 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
2539 } else {
2540 false
2541 };
2542 let has_blank_before_strict = at_document_start || prev_line_blank;
2543
2544 dispatcher_ctx.has_blank_before = has_blank_before;
2545 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
2546 dispatcher_ctx.at_document_start = at_document_start;
2547
2548 let dispatcher_match =
2549 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
2550 self.block_registry
2552 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos)
2553 } else {
2554 dispatcher_match
2555 };
2556
2557 if has_blank_before {
2558 if let Some(env_name) = extract_environment_name(content)
2559 && is_inline_math_environment(env_name)
2560 {
2561 if !self.is_paragraph_open() {
2562 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2563 }
2564 paragraphs::append_paragraph_line(
2565 &mut self.containers,
2566 &mut self.builder,
2567 line_to_append.unwrap_or(self.lines[self.pos]),
2568 self.config,
2569 );
2570 self.pos += 1;
2571 return true;
2572 }
2573
2574 if let Some(block_match) = dispatcher_match.as_ref() {
2575 let detection = block_match.detection;
2576
2577 match detection {
2578 BlockDetectionResult::YesCanInterrupt => {
2579 self.emit_list_item_buffer_if_needed();
2580 if self.is_paragraph_open() {
2581 self.close_containers_to(self.containers.depth() - 1);
2582 }
2583 }
2584 BlockDetectionResult::Yes => {
2585 self.prepare_for_block_element();
2586 }
2587 BlockDetectionResult::No => unreachable!(),
2588 }
2589
2590 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2591 self.close_containers_to_fenced_div();
2592 }
2593
2594 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2595 self.close_open_footnote_definition();
2596 }
2597
2598 let lines_consumed = self.block_registry.parse_prepared(
2599 block_match,
2600 &dispatcher_ctx,
2601 &mut self.builder,
2602 &self.lines,
2603 self.pos,
2604 );
2605
2606 if matches!(
2607 self.block_registry.parser_name(block_match),
2608 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
2609 ) {
2610 self.after_metadata_block = true;
2611 }
2612
2613 match block_match.effect {
2614 BlockEffect::None => {}
2615 BlockEffect::OpenFencedDiv => {
2616 self.containers.push(Container::FencedDiv {});
2617 }
2618 BlockEffect::CloseFencedDiv => {
2619 self.close_fenced_div();
2620 }
2621 BlockEffect::OpenFootnoteDefinition => {
2622 self.handle_footnote_open_effect(block_match, content);
2623 }
2624 BlockEffect::OpenList => {
2625 self.handle_list_open_effect(block_match, content, indent_to_emit);
2626 }
2627 BlockEffect::OpenDefinitionList => {
2628 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2629 }
2630 BlockEffect::OpenBlockQuote => {
2631 }
2633 }
2634
2635 if lines_consumed == 0 {
2636 log::warn!(
2637 "block parser made no progress at line {} (parser={})",
2638 self.pos + 1,
2639 self.block_registry.parser_name(block_match)
2640 );
2641 return false;
2642 }
2643
2644 self.pos += lines_consumed;
2645 return true;
2646 }
2647 } else if let Some(block_match) = dispatcher_match.as_ref() {
2648 let parser_name = self.block_registry.parser_name(block_match);
2651 match block_match.detection {
2652 BlockDetectionResult::YesCanInterrupt => {
2653 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
2654 && self.is_paragraph_open()
2655 {
2656 if !self.is_paragraph_open() {
2658 paragraphs::start_paragraph_if_needed(
2659 &mut self.containers,
2660 &mut self.builder,
2661 );
2662 }
2663 paragraphs::append_paragraph_line(
2664 &mut self.containers,
2665 &mut self.builder,
2666 line_to_append.unwrap_or(self.lines[self.pos]),
2667 self.config,
2668 );
2669 self.pos += 1;
2670 return true;
2671 }
2672
2673 if matches!(block_match.effect, BlockEffect::OpenList)
2674 && self.is_paragraph_open()
2675 && !lists::in_list(&self.containers)
2676 && self.content_container_indent_to_strip() == 0
2677 {
2678 let allow_interrupt =
2684 self.config.dialect == crate::options::Dialect::CommonMark && {
2685 use super::block_dispatcher::ListPrepared;
2686 use super::blocks::lists::OrderedMarker;
2687 let prepared = block_match
2688 .payload
2689 .as_ref()
2690 .and_then(|p| p.downcast_ref::<ListPrepared>());
2691 match prepared.map(|p| &p.marker) {
2692 Some(ListMarker::Bullet(_)) => true,
2693 Some(ListMarker::Ordered(OrderedMarker::Decimal {
2694 number,
2695 ..
2696 })) => number == "1",
2697 _ => false,
2698 }
2699 };
2700 if !allow_interrupt {
2701 paragraphs::append_paragraph_line(
2702 &mut self.containers,
2703 &mut self.builder,
2704 line_to_append.unwrap_or(self.lines[self.pos]),
2705 self.config,
2706 );
2707 self.pos += 1;
2708 return true;
2709 }
2710 }
2711
2712 if matches!(block_match.effect, BlockEffect::OpenList)
2719 && self.try_lazy_list_continuation(block_match, content)
2720 {
2721 self.pos += 1;
2722 return true;
2723 }
2724
2725 self.emit_list_item_buffer_if_needed();
2726 if self.is_paragraph_open() {
2727 self.close_containers_to(self.containers.depth() - 1);
2728 }
2729
2730 if self.config.dialect == crate::options::Dialect::CommonMark
2737 && !matches!(block_match.effect, BlockEffect::OpenList)
2738 {
2739 let (indent_cols, _) = leading_indent(content);
2740 self.close_lists_above_indent(indent_cols);
2741 }
2742 }
2743 BlockDetectionResult::Yes => {
2744 if parser_name == "setext_heading"
2756 && self.is_paragraph_open()
2757 && self.config.dialect == crate::options::Dialect::CommonMark
2758 {
2759 let text_line = self.lines[self.pos];
2760 let underline_line = self.lines[self.pos + 1];
2761 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
2762 let level = if underline_char == '=' { 1 } else { 2 };
2763 self.emit_setext_heading_folding_paragraph(
2764 text_line,
2765 underline_line,
2766 level,
2767 );
2768 self.pos += 2;
2769 return true;
2770 }
2771
2772 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
2775 if !self.is_paragraph_open() {
2776 paragraphs::start_paragraph_if_needed(
2777 &mut self.containers,
2778 &mut self.builder,
2779 );
2780 }
2781 paragraphs::append_paragraph_line(
2782 &mut self.containers,
2783 &mut self.builder,
2784 line_to_append.unwrap_or(self.lines[self.pos]),
2785 self.config,
2786 );
2787 self.pos += 1;
2788 return true;
2789 }
2790
2791 if parser_name == "reference_definition" && self.is_paragraph_open() {
2794 paragraphs::append_paragraph_line(
2795 &mut self.containers,
2796 &mut self.builder,
2797 line_to_append.unwrap_or(self.lines[self.pos]),
2798 self.config,
2799 );
2800 self.pos += 1;
2801 return true;
2802 }
2803 }
2804 BlockDetectionResult::No => unreachable!(),
2805 }
2806
2807 if !matches!(block_match.detection, BlockDetectionResult::No) {
2808 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2809 self.close_containers_to_fenced_div();
2810 }
2811
2812 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2813 self.close_open_footnote_definition();
2814 }
2815
2816 let lines_consumed = self.block_registry.parse_prepared(
2817 block_match,
2818 &dispatcher_ctx,
2819 &mut self.builder,
2820 &self.lines,
2821 self.pos,
2822 );
2823
2824 match block_match.effect {
2825 BlockEffect::None => {}
2826 BlockEffect::OpenFencedDiv => {
2827 self.containers.push(Container::FencedDiv {});
2828 }
2829 BlockEffect::CloseFencedDiv => {
2830 self.close_fenced_div();
2831 }
2832 BlockEffect::OpenFootnoteDefinition => {
2833 self.handle_footnote_open_effect(block_match, content);
2834 }
2835 BlockEffect::OpenList => {
2836 self.handle_list_open_effect(block_match, content, indent_to_emit);
2837 }
2838 BlockEffect::OpenDefinitionList => {
2839 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2840 }
2841 BlockEffect::OpenBlockQuote => {
2842 }
2844 }
2845
2846 if lines_consumed == 0 {
2847 log::warn!(
2848 "block parser made no progress at line {} (parser={})",
2849 self.pos + 1,
2850 self.block_registry.parser_name(block_match)
2851 );
2852 return false;
2853 }
2854
2855 self.pos += lines_consumed;
2856 return true;
2857 }
2858 }
2859
2860 if self.config.extensions.line_blocks
2862 && (has_blank_before || self.pos == 0)
2863 && try_parse_line_block_start(content).is_some()
2864 && try_parse_line_block_start(self.lines[self.pos]).is_some()
2868 {
2869 log::trace!("Parsed line block at line {}", self.pos);
2870 self.close_paragraph_if_open();
2872
2873 let new_pos = parse_line_block(&self.lines, self.pos, &mut self.builder, self.config);
2874 if new_pos > self.pos {
2875 self.pos = new_pos;
2876 return true;
2877 }
2878 }
2879
2880 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2883 log::trace!(
2884 "Inside ListItem - buffering content: {:?}",
2885 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2886 );
2887 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2889
2890 if let Some(Container::ListItem {
2892 buffer,
2893 marker_only,
2894 ..
2895 }) = self.containers.stack.last_mut()
2896 {
2897 buffer.push_text(line);
2898 if !is_blank_line(line) {
2899 *marker_only = false;
2900 }
2901 }
2902
2903 self.pos += 1;
2904 return true;
2905 }
2906
2907 log::trace!(
2908 "Not in ListItem - creating paragraph for: {:?}",
2909 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2910 );
2911 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2913 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2916 paragraphs::append_paragraph_line(
2917 &mut self.containers,
2918 &mut self.builder,
2919 line,
2920 self.config,
2921 );
2922 self.pos += 1;
2923 true
2924 }
2925
2926 fn fenced_div_container_index(&self) -> Option<usize> {
2927 self.containers
2928 .stack
2929 .iter()
2930 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
2931 }
2932
2933 fn close_containers_to_fenced_div(&mut self) {
2934 if let Some(index) = self.fenced_div_container_index() {
2935 self.close_containers_to(index + 1);
2936 }
2937 }
2938
2939 fn close_fenced_div(&mut self) {
2940 if let Some(index) = self.fenced_div_container_index() {
2941 self.close_containers_to(index);
2942 }
2943 }
2944
2945 fn in_fenced_div(&self) -> bool {
2946 self.containers
2947 .stack
2948 .iter()
2949 .any(|c| matches!(c, Container::FencedDiv { .. }))
2950 }
2951}