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::container_prefix::{ContainerPrefix, StrippedLines, strip_content_indent};
12use super::blocks::definition_lists;
13use super::blocks::fenced_divs;
14use super::blocks::headings::{
15 emit_atx_heading, emit_setext_heading, emit_setext_heading_body, try_parse_atx_heading,
16 try_parse_setext_heading,
17};
18use super::blocks::horizontal_rules::try_parse_horizontal_rule;
19use super::blocks::line_blocks;
20use super::blocks::lists;
21use super::blocks::paragraphs;
22use super::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
23use super::blocks::tables;
24use super::diagnostics::{Diagnostics, SyntaxError};
25use super::utils::container_stack;
26use super::utils::helpers::{is_blank_line, split_lines_inclusive, strip_newline};
27use super::utils::inline_emission;
28use super::utils::marker_utils;
29use super::utils::text_buffer;
30
31use super::blocks::blockquotes::strip_n_blockquote_markers;
32use super::utils::continuation::ContinuationPolicy;
33use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
34use definition_lists::{emit_definition_marker, emit_term};
35use line_blocks::{parse_line_block, try_parse_line_block_start};
36use lists::{
37 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
38 try_parse_list_marker,
39};
40use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
41use text_buffer::TextBuffer;
42
43const GITHUB_ALERT_MARKERS: [&str; 5] = [
44 "[!TIP]",
45 "[!WARNING]",
46 "[!IMPORTANT]",
47 "[!CAUTION]",
48 "[!NOTE]",
49];
50
51#[must_use]
56#[derive(Debug, Clone, Copy)]
57pub(crate) enum LineDispatch {
58 Consumed(usize),
60 Rejected,
62}
63
64impl LineDispatch {
65 #[inline]
69 pub(crate) fn consumed(n: usize) -> Self {
70 debug_assert!(n >= 1, "LineDispatch::Consumed requires n >= 1");
71 LineDispatch::Consumed(n)
72 }
73}
74
75pub struct Parser<'a> {
76 lines: Vec<&'a str>,
77 pos: usize,
78 builder: GreenNodeBuilder<'static>,
79 containers: ContainerStack,
80 config: &'a ParserOptions,
81 block_registry: BlockParserRegistry,
82 after_metadata_block: bool,
86 dispatch_list_marker_consumed: bool,
96 diagnostics: Diagnostics,
100}
101
102impl<'a> Parser<'a> {
103 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
104 let lines = split_lines_inclusive(input);
106 Self {
107 lines,
108 pos: 0,
109 builder: GreenNodeBuilder::new(),
110 containers: ContainerStack::new(),
111 config,
112 block_registry: BlockParserRegistry::new(),
113 after_metadata_block: false,
114 dispatch_list_marker_consumed: false,
115 diagnostics: Diagnostics::new(),
116 }
117 }
118
119 pub fn parse(self) -> SyntaxNode {
120 self.parse_with_errors().0
121 }
122
123 pub fn parse_with_errors(mut self) -> (SyntaxNode, Vec<SyntaxError>) {
126 self.parse_document_stack();
127 let node = SyntaxNode::new_root(self.builder.finish());
128 let errors = self.diagnostics.take();
129 (node, errors)
130 }
131
132 fn close_lists_above_indent(&mut self, indent_cols: usize) {
143 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
144 if indent_cols >= *content_col {
145 break;
146 }
147 self.close_containers_to(self.containers.depth() - 1);
148 if matches!(self.containers.last(), Some(Container::List { .. })) {
149 self.close_containers_to(self.containers.depth() - 1);
150 }
151 }
152 }
153
154 fn close_containers_to(&mut self, keep: usize) {
157 while self.containers.depth() > keep {
159 match self.containers.stack.last() {
160 Some(Container::ListItem {
162 buffer,
163 content_col,
164 ..
165 }) if !buffer.is_empty() => {
166 let buffer_clone = buffer.clone();
168 let item_content_col = *content_col;
169
170 log::trace!(
171 "Closing ListItem with buffer (is_empty={}, segment_count={})",
172 buffer_clone.is_empty(),
173 buffer_clone.segment_count()
174 );
175
176 let parent_list_is_loose = self
180 .containers
181 .stack
182 .iter()
183 .rev()
184 .find_map(|c| match c {
185 Container::List {
186 has_blank_between_items,
187 ..
188 } => Some(*has_blank_between_items),
189 _ => None,
190 })
191 .unwrap_or(false);
192
193 let use_paragraph =
194 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
195
196 log::trace!(
197 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
198 use_paragraph,
199 parent_list_is_loose,
200 buffer_clone.has_blank_lines_between_content()
201 );
202
203 let suppress_footnote_refs = self.in_footnote_definition();
204 self.containers.stack.pop();
206 buffer_clone.emit_as_block(
208 &mut self.builder,
209 use_paragraph,
210 self.config,
211 item_content_col,
212 suppress_footnote_refs,
213 );
214 self.builder.finish_node(); }
216 Some(Container::ListItem { .. }) => {
218 log::trace!("Closing empty ListItem (no buffer content)");
219 self.containers.stack.pop();
221 self.builder.finish_node();
222 }
223 Some(Container::Paragraph {
225 buffer,
226 start_checkpoint,
227 ..
228 }) if !buffer.is_empty() => {
229 let buffer_clone = buffer.clone();
231 let checkpoint = *start_checkpoint;
232 let suppress_footnote_refs = self.in_footnote_definition();
233 self.containers.stack.pop();
235 self.builder
237 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
238 buffer_clone.emit_with_inlines(
239 &mut self.builder,
240 self.config,
241 suppress_footnote_refs,
242 );
243 self.builder.finish_node();
244 }
245 Some(Container::Paragraph {
247 start_checkpoint, ..
248 }) => {
249 let checkpoint = *start_checkpoint;
250 self.containers.stack.pop();
252 self.builder
253 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
254 self.builder.finish_node();
255 }
256 Some(Container::Definition {
258 plain_open: true,
259 plain_buffer,
260 ..
261 }) if !plain_buffer.is_empty() => {
262 let text = plain_buffer.get_accumulated_text();
263 let suppress_footnote_refs = self.in_footnote_definition();
264 emit_definition_plain_or_heading(
265 &mut self.builder,
266 &text,
267 self.config,
268 suppress_footnote_refs,
269 );
270
271 if let Some(Container::Definition {
273 plain_open,
274 plain_buffer,
275 ..
276 }) = self.containers.stack.last_mut()
277 {
278 plain_buffer.clear();
279 *plain_open = false;
280 }
281
282 self.containers.stack.pop();
284 self.builder.finish_node();
285 }
286 Some(Container::Definition {
288 plain_open: true, ..
289 }) => {
290 if let Some(Container::Definition {
292 plain_open,
293 plain_buffer,
294 ..
295 }) = self.containers.stack.last_mut()
296 {
297 plain_buffer.clear();
298 *plain_open = false;
299 }
300
301 self.containers.stack.pop();
303 self.builder.finish_node();
304 }
305 _ => {
307 self.containers.stack.pop();
308 self.builder.finish_node();
309 }
310 }
311 }
312 }
313
314 fn emit_buffered_plain_if_needed(&mut self) {
317 if let Some(Container::Definition {
319 plain_open: true,
320 plain_buffer,
321 ..
322 }) = self.containers.stack.last()
323 && !plain_buffer.is_empty()
324 {
325 let text = plain_buffer.get_accumulated_text();
326 let suppress_footnote_refs = self.in_footnote_definition();
327 emit_definition_plain_or_heading(
328 &mut self.builder,
329 &text,
330 self.config,
331 suppress_footnote_refs,
332 );
333 }
334
335 if let Some(Container::Definition {
337 plain_open,
338 plain_buffer,
339 ..
340 }) = self.containers.stack.last_mut()
341 && *plain_open
342 {
343 plain_buffer.clear();
344 *plain_open = false;
345 }
346 }
347
348 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
353 let mut current = self.current_blockquote_depth();
354 while current > target_depth {
355 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
356 if self.containers.depth() == 0 {
357 break;
358 }
359 self.close_containers_to(self.containers.depth() - 1);
360 }
361 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
362 self.close_containers_to(self.containers.depth() - 1);
363 current -= 1;
364 } else {
365 break;
366 }
367 }
368 }
369
370 fn active_alert_blockquote_depth(&self) -> Option<usize> {
371 self.containers.stack.iter().rev().find_map(|c| match c {
372 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
373 _ => None,
374 })
375 }
376
377 fn in_active_alert(&self) -> bool {
378 self.active_alert_blockquote_depth().is_some()
379 }
380
381 fn previous_block_requires_blank_before_heading(&self) -> bool {
382 matches!(
383 self.containers.last(),
384 Some(Container::Paragraph { .. })
385 | Some(Container::ListItem { .. })
386 | Some(Container::Definition { .. })
387 | Some(Container::DefinitionItem { .. })
388 | Some(Container::FootnoteDefinition { .. })
389 )
390 }
391
392 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
393 let (without_newline, _) = strip_newline(content);
394 let trimmed = without_newline.trim();
395 GITHUB_ALERT_MARKERS
396 .into_iter()
397 .find(|marker| *marker == trimmed)
398 }
399
400 fn emit_list_item_buffer_if_needed(&mut self) {
403 if let Some(Container::ListItem {
404 buffer,
405 content_col,
406 ..
407 }) = self.containers.stack.last_mut()
408 && !buffer.is_empty()
409 {
410 let buffer_clone = buffer.clone();
411 let item_content_col = *content_col;
412 buffer.clear();
413 let use_paragraph = buffer_clone.has_blank_lines_between_content();
414 let suppress_footnote_refs = self.in_footnote_definition();
415 buffer_clone.emit_as_block(
416 &mut self.builder,
417 use_paragraph,
418 self.config,
419 item_content_col,
420 suppress_footnote_refs,
421 );
422 }
423 }
424
425 fn dispatch_bq_after_list_item(
442 &mut self,
443 result: super::blocks::lists::ListItemFinish,
444 ) -> usize {
445 let super::blocks::lists::ListItemFinish::BqDispatch { content } = result else {
446 return 0;
447 };
448 let pos_before = self.pos;
449 self.dispatch_list_marker_consumed = true;
454 let dispatch = self.parse_inner_content(&content, Some(&content));
455 self.dispatch_list_marker_consumed = false;
456 self.pos = pos_before;
457 match dispatch {
458 LineDispatch::Consumed(n) => n.saturating_sub(1),
459 LineDispatch::Rejected => 0,
460 }
461 }
462
463 fn maybe_open_fenced_code_in_new_list_item(&mut self) -> Option<usize> {
474 let Some(Container::ListItem {
475 content_col,
476 buffer,
477 ..
478 }) = self.containers.stack.last()
479 else {
480 return None;
481 };
482 let content_col = *content_col;
483 let text = buffer.first_text()?;
484 if buffer.segment_count() != 1 {
485 return None;
486 }
487 let text_owned = text.to_string();
488 let fence = code_blocks::try_parse_fence_open(&text_owned, self.config.dialect)?;
489 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
490 let has_info = !fence.info_string.trim().is_empty();
491 let bq_depth = self.current_blockquote_depth();
492 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
493 if !(has_info || has_matching_closer || common_mark_dialect) {
494 return None;
495 }
496 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
498 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
499 {
500 return None;
501 }
502 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
503 buffer.clear();
504 }
505 let prefix = ContainerPrefix::from_scalars(bq_depth, content_col, bq_depth > 0, 0, true);
509 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
510 let new_pos = code_blocks::parse_fenced_code_block(
511 &mut self.builder,
512 &window,
513 fence,
514 Some(&text_owned),
515 &self.diagnostics,
516 self.config.flavor,
517 );
518 Some(new_pos.saturating_sub(self.pos).saturating_sub(1))
519 }
520
521 fn maybe_open_caption_table_in_new_list_item(&mut self) -> Option<usize> {
535 if !self.config.extensions.table_captions {
536 return None;
537 }
538 if !(self.config.extensions.simple_tables
539 || self.config.extensions.multiline_tables
540 || self.config.extensions.grid_tables
541 || self.config.extensions.pipe_tables)
542 {
543 return None;
544 }
545
546 let Some(Container::ListItem {
547 content_col,
548 buffer,
549 ..
550 }) = self.containers.stack.last()
551 else {
552 return None;
553 };
554 if buffer.segment_count() != 1 || buffer.first_text().is_none() {
557 return None;
558 }
559 let content_col = *content_col;
560
561 let bq_depth = self.current_blockquote_depth();
565 let prefix = ContainerPrefix::from_scalars(bq_depth, content_col, bq_depth > 0, 0, true);
566 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
567 if !tables::is_caption_followed_by_table(&window, self.pos) {
568 return None;
569 }
570
571 let mut consumed = None;
581 if self.config.extensions.grid_tables {
582 consumed = tables::try_parse_grid_table(&window, &mut self.builder, self.config);
583 }
584 if consumed.is_none() && self.config.extensions.multiline_tables {
585 consumed = tables::try_parse_multiline_table(&window, &mut self.builder, self.config);
586 }
587 if consumed.is_none() && self.config.extensions.pipe_tables {
588 consumed = tables::try_parse_pipe_table(&window, &mut self.builder, self.config);
589 }
590 if consumed.is_none() && self.config.extensions.simple_tables {
591 consumed = tables::try_parse_simple_table(&window, &mut self.builder, self.config);
592 }
593 let consumed = consumed?;
594
595 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
599 buffer.clear();
600 }
601 Some(consumed.saturating_sub(1))
602 }
603
604 fn maybe_open_table_with_trailing_caption_in_new_list_item(&mut self) -> Option<usize> {
624 if !self.config.extensions.table_captions {
625 return None;
626 }
627 if !(self.config.extensions.simple_tables
628 || self.config.extensions.multiline_tables
629 || self.config.extensions.grid_tables
630 || self.config.extensions.pipe_tables)
631 {
632 return None;
633 }
634
635 let Some(Container::ListItem {
636 content_col,
637 buffer,
638 ..
639 }) = self.containers.stack.last()
640 else {
641 return None;
642 };
643 if buffer.segment_count() != 1 {
646 return None;
647 }
648 let first = buffer.first_text()?;
650 if !matches!(
651 first.trim_start().as_bytes().first(),
652 Some(b'|') | Some(b'+')
653 ) {
654 return None;
655 }
656 let content_col = *content_col;
657
658 let bq_depth = self.current_blockquote_depth();
659 let prefix = ContainerPrefix::from_scalars(bq_depth, content_col, bq_depth > 0, 0, true);
660 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
661
662 if tables::is_caption_followed_by_table(&window, self.pos) {
665 return None;
666 }
667
668 let mut probe = GreenNodeBuilder::new();
672 let _ = try_parse_any_table_kind(&window, &mut probe, self.config)?;
673 let probe_root = SyntaxNode::new_root(probe.finish());
674 let has_caption = probe_root
675 .children()
676 .any(|c| c.kind() == SyntaxKind::TABLE_CAPTION);
677 if !has_caption {
678 return None;
679 }
680
681 let consumed = try_parse_any_table_kind(&window, &mut self.builder, self.config)?;
684 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
685 buffer.clear();
686 }
687 Some(consumed.saturating_sub(1))
688 }
689
690 fn maybe_open_indented_code_in_new_list_item(&mut self) {
701 let Some(Container::ListItem {
702 content_col,
703 buffer,
704 marker_only,
705 virtual_marker_space,
706 }) = self.containers.stack.last()
707 else {
708 return;
709 };
710 if *marker_only {
711 return;
712 }
713 if buffer.segment_count() != 1 {
714 return;
715 }
716 let Some(text) = buffer.first_text() else {
717 return;
718 };
719 let content_col = *content_col;
720 let virtual_marker_space = *virtual_marker_space;
721 let text_owned = text.to_string();
722
723 let mut iter = text_owned.split_inclusive('\n');
725 let line_with_nl = iter.next().unwrap_or("").to_string();
726 if iter.next().is_some() {
727 return;
728 }
729
730 let line_no_nl = line_with_nl
731 .strip_suffix("\r\n")
732 .or_else(|| line_with_nl.strip_suffix('\n'))
733 .unwrap_or(&line_with_nl);
734 let nl_suffix = &line_with_nl[line_no_nl.len()..];
735
736 let buffer_start_col = if virtual_marker_space {
737 content_col.saturating_sub(1)
738 } else {
739 content_col
740 };
741
742 let target = content_col + 4;
743 let (cols_walked, ws_bytes) =
744 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
745
746 if buffer_start_col + cols_walked < target {
747 return;
748 }
749 if ws_bytes >= line_no_nl.len() {
750 return;
751 }
752
753 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
754 buffer.clear();
755 }
756
757 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
758 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
759 if ws_bytes > 0 {
760 self.builder
761 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
762 }
763 let rest = &line_no_nl[ws_bytes..];
764 if !rest.is_empty() {
765 self.builder.token(SyntaxKind::TEXT.into(), rest);
766 }
767 if !nl_suffix.is_empty() {
768 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
769 }
770 self.builder.finish_node();
771 self.builder.finish_node();
772 }
773
774 fn has_matching_fence_closer(
775 &self,
776 fence: &code_blocks::FenceInfo,
777 bq_depth: usize,
778 content_col: usize,
779 ) -> bool {
780 for raw_line in self.lines.iter().skip(self.pos + 1) {
781 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
782 if line_bq_depth < bq_depth {
783 break;
784 }
785 let candidate = if content_col > 0 && !inner.is_empty() {
786 let idx = byte_index_at_column(inner, content_col);
787 if idx <= inner.len() {
788 &inner[idx..]
789 } else {
790 inner
791 }
792 } else {
793 inner
794 };
795 if code_blocks::is_closing_fence(candidate, fence) {
796 return true;
797 }
798 }
799 false
800 }
801
802 fn is_paragraph_open(&self) -> bool {
804 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
805 }
806
807 fn emit_setext_heading_folding_paragraph(
815 &mut self,
816 text_line: &str,
817 underline_line: &str,
818 level: usize,
819 ) {
820 let (buffered_text, checkpoint) = match self.containers.stack.last() {
821 Some(Container::Paragraph {
822 buffer,
823 start_checkpoint,
824 ..
825 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
826 _ => (String::new(), None),
827 };
828
829 if checkpoint.is_some() {
830 self.containers.stack.pop();
831 }
832
833 let combined_text = if buffered_text.is_empty() {
834 text_line.to_string()
835 } else {
836 format!("{}{}", buffered_text, text_line)
837 };
838
839 let cp = checkpoint.expect(
840 "emit_setext_heading_folding_paragraph requires an open paragraph; \
841 single-line setext should go through the regular dispatcher path",
842 );
843 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
844 emit_setext_heading_body(
845 &mut self.builder,
846 &combined_text,
847 underline_line,
848 level,
849 self.config,
850 );
851 self.builder.finish_node();
852 }
853
854 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
872 let Some(Container::ListItem {
873 buffer,
874 content_col,
875 ..
876 }) = self.containers.stack.last()
877 else {
878 return None;
879 };
880 if buffer.segment_count() != 1 {
881 return None;
882 }
883 let text_line = buffer.first_text()?;
884
885 let content_col = *content_col;
890 let (underline_indent_cols, _) = leading_indent(content);
891 if underline_indent_cols < content_col {
892 return None;
893 }
894
895 let lines = [text_line, content];
896 let (level, _) = try_parse_setext_heading(&lines, 0)?;
897
898 let (text_no_newline, _) = strip_newline(text_line);
899 if text_no_newline.trim().is_empty() {
900 return None;
901 }
902 if try_parse_horizontal_rule(text_no_newline).is_some() {
903 return None;
904 }
905
906 let text_owned = text_line.to_string();
907 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
908 buffer.clear();
909 }
910 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
911 Some(LineDispatch::consumed(1))
912 }
913
914 fn close_paragraph_if_open(&mut self) {
916 if self.is_paragraph_open() {
917 self.close_containers_to(self.containers.depth() - 1);
918 }
919 }
920
921 fn close_paragraph_as_plain_if_open(&mut self) {
932 if !self.is_paragraph_open() {
933 return;
934 }
935 let Some(Container::Paragraph {
936 buffer,
937 start_checkpoint,
938 ..
939 }) = self.containers.stack.last()
940 else {
941 return;
942 };
943 let buffer_clone = buffer.clone();
944 let checkpoint = *start_checkpoint;
945 let suppress_footnote_refs = self.in_footnote_definition();
946 self.containers.stack.pop();
947 self.builder
948 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
949 if !buffer_clone.is_empty() {
950 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
951 }
952 self.builder.finish_node();
953 }
954
955 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
964 if self.config.dialect != crate::options::Dialect::Pandoc {
965 return false;
966 }
967 if self.block_registry.parser_name(block_match) != "html_block" {
968 return false;
969 }
970 let html_block_type = block_match
971 .payload
972 .as_ref()
973 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
974 matches!(
975 html_block_type,
976 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
977 )
978 }
979
980 fn prepare_for_block_element(&mut self) {
983 self.emit_list_item_buffer_if_needed();
984 self.close_paragraph_if_open();
985 }
986
987 fn close_open_footnote_definition(&mut self) {
991 while matches!(
992 self.containers.last(),
993 Some(Container::FootnoteDefinition { .. })
994 ) {
995 self.close_containers_to(self.containers.depth() - 1);
996 }
997 }
998
999 fn handle_footnote_open_effect(
1003 &mut self,
1004 block_match: &super::block_dispatcher::PreparedBlockMatch,
1005 content: &str,
1006 ) -> usize {
1007 let content_start = block_match
1008 .payload
1009 .as_ref()
1010 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
1011 .map(|p| p.content_start)
1012 .unwrap_or(0);
1013
1014 let content_col = 4;
1015 self.containers
1016 .push(Container::FootnoteDefinition { content_col });
1017
1018 if content_start == 0 {
1019 return 0;
1020 }
1021 let first_line_content = &content[content_start..];
1022 if first_line_content.trim().is_empty() {
1023 let (_, newline_str) = strip_newline(content);
1024 if !newline_str.is_empty() {
1025 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1026 }
1027 return 0;
1028 }
1029
1030 if self.config.extensions.definition_lists
1031 && let Some(blank_count) = footnote_first_line_term_lookahead(
1032 &self.lines,
1033 self.pos,
1034 content_col,
1035 self.config.extensions.table_captions,
1036 )
1037 {
1038 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1039 self.containers.push(Container::DefinitionList {});
1040 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1041 self.containers.push(Container::DefinitionItem {});
1042 emit_term(&mut self.builder, first_line_content, self.config);
1043 for i in 0..blank_count {
1044 let blank_pos = self.pos + 1 + i;
1045 if blank_pos < self.lines.len() {
1046 let blank_line = self.lines[blank_pos];
1047 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1048 self.builder
1049 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1050 self.builder.finish_node();
1051 }
1052 }
1053 return blank_count;
1054 }
1055
1056 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1057 paragraphs::append_paragraph_line(
1058 &mut self.containers,
1059 &mut self.builder,
1060 first_line_content,
1061 self.config,
1062 );
1063 0
1064 }
1065
1066 fn try_lazy_list_continuation(
1078 &mut self,
1079 block_match: &super::block_dispatcher::PreparedBlockMatch,
1080 content: &str,
1081 ) -> bool {
1082 use super::block_dispatcher::ListPrepared;
1083
1084 let Some(prepared) = block_match
1085 .payload
1086 .as_ref()
1087 .and_then(|p| p.downcast_ref::<ListPrepared>())
1088 else {
1089 return false;
1090 };
1091
1092 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
1093 return false;
1094 }
1095
1096 let current_content_col = paragraphs::current_content_col(&self.containers);
1103 if prepared.indent_cols >= current_content_col
1104 && prepared.indent_cols < current_content_col + 4
1105 {
1106 return false;
1107 }
1108
1109 if lists::find_matching_list_level(
1110 &self.containers,
1111 &prepared.marker,
1112 prepared.indent_cols,
1113 self.config.dialect,
1114 )
1115 .is_some()
1116 {
1117 return false;
1118 }
1119
1120 match self.containers.last() {
1121 Some(Container::Paragraph { .. }) => {
1122 paragraphs::append_paragraph_line(
1123 &mut self.containers,
1124 &mut self.builder,
1125 content,
1126 self.config,
1127 );
1128 true
1129 }
1130 Some(Container::ListItem { .. }) => {
1131 if let Some(Container::ListItem {
1132 buffer,
1133 marker_only,
1134 ..
1135 }) = self.containers.stack.last_mut()
1136 {
1137 buffer.push_text(content);
1138 if !content.trim().is_empty() {
1139 *marker_only = false;
1140 }
1141 }
1142 true
1143 }
1144 _ => false,
1145 }
1146 }
1147
1148 fn handle_list_open_effect(
1154 &mut self,
1155 block_match: &super::block_dispatcher::PreparedBlockMatch,
1156 content: &str,
1157 indent_to_emit: Option<&str>,
1158 ) -> usize {
1159 use super::block_dispatcher::ListPrepared;
1160
1161 let prepared = block_match
1162 .payload
1163 .as_ref()
1164 .and_then(|p| p.downcast_ref::<ListPrepared>());
1165 let Some(prepared) = prepared else {
1166 return 0;
1167 };
1168
1169 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
1170 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1171 paragraphs::append_paragraph_line(
1172 &mut self.containers,
1173 &mut self.builder,
1174 content,
1175 self.config,
1176 );
1177 return 0;
1178 }
1179
1180 if self.is_paragraph_open() {
1181 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
1182 paragraphs::append_paragraph_line(
1183 &mut self.containers,
1184 &mut self.builder,
1185 content,
1186 self.config,
1187 );
1188 return 0;
1189 }
1190 self.close_containers_to(self.containers.depth() - 1);
1191 }
1192
1193 if matches!(
1194 self.containers.last(),
1195 Some(Container::Definition {
1196 plain_open: true,
1197 ..
1198 })
1199 ) {
1200 self.emit_buffered_plain_if_needed();
1201 }
1202
1203 let matched_level = lists::find_matching_list_level(
1204 &self.containers,
1205 &prepared.marker,
1206 prepared.indent_cols,
1207 self.config.dialect,
1208 );
1209 let list_item = ListItemEmissionInput {
1210 content,
1211 marker_len: prepared.marker_len,
1212 spaces_after_cols: prepared.spaces_after_cols,
1213 spaces_after_bytes: prepared.spaces_after,
1214 indent_cols: prepared.indent_cols,
1215 indent_bytes: prepared.indent_bytes,
1216 virtual_marker_space: prepared.virtual_marker_space,
1217 };
1218 let current_content_col = paragraphs::current_content_col(&self.containers);
1219 let deep_ordered_matched_level = matched_level
1220 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1221 .and_then(|(level, container)| match container {
1222 Container::List {
1223 marker: list_marker,
1224 base_indent_cols,
1225 ..
1226 } if matches!(
1227 (&prepared.marker, list_marker),
1228 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1229 ) && prepared.indent_cols >= 4
1230 && *base_indent_cols >= 4
1231 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1232 {
1233 Some(level)
1234 }
1235 _ => None,
1236 });
1237
1238 if deep_ordered_matched_level.is_none()
1239 && current_content_col > 0
1240 && prepared.indent_cols >= current_content_col
1241 {
1242 if let Some(level) = matched_level
1243 && let Some(Container::List {
1244 base_indent_cols, ..
1245 }) = self.containers.stack.get(level)
1246 && prepared.indent_cols == *base_indent_cols
1247 {
1248 let num_parent_lists = self.containers.stack[..level]
1249 .iter()
1250 .filter(|c| matches!(c, Container::List { .. }))
1251 .count();
1252
1253 if num_parent_lists > 0 {
1254 self.close_containers_to(level + 1);
1255
1256 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1257 self.close_containers_to(self.containers.depth() - 1);
1258 }
1259 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1260 self.close_containers_to(self.containers.depth() - 1);
1261 }
1262
1263 if let Some(indent_str) = indent_to_emit {
1264 self.builder
1265 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1266 }
1267
1268 let finish = if let Some(nested_marker) = prepared.nested_marker {
1269 lists::add_list_item_with_nested_empty_list(
1270 &mut self.containers,
1271 &mut self.builder,
1272 &list_item,
1273 nested_marker,
1274 self.config,
1275 );
1276 lists::ListItemFinish::Done
1277 } else {
1278 lists::add_list_item(
1279 &mut self.containers,
1280 &mut self.builder,
1281 &list_item,
1282 self.config,
1283 )
1284 };
1285 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1286 return extras;
1287 }
1288 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1289 return extras;
1290 }
1291 if let Some(extras) =
1292 self.maybe_open_table_with_trailing_caption_in_new_list_item()
1293 {
1294 return extras;
1295 }
1296 self.maybe_open_indented_code_in_new_list_item();
1297 return self.dispatch_bq_after_list_item(finish);
1298 }
1299 }
1300
1301 self.emit_list_item_buffer_if_needed();
1302
1303 let finish = start_nested_list(
1304 &mut self.containers,
1305 &mut self.builder,
1306 &prepared.marker,
1307 &list_item,
1308 indent_to_emit,
1309 self.config,
1310 );
1311 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1312 return extras;
1313 }
1314 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1315 return extras;
1316 }
1317 if let Some(extras) = self.maybe_open_table_with_trailing_caption_in_new_list_item() {
1318 return extras;
1319 }
1320 self.maybe_open_indented_code_in_new_list_item();
1321 return self.dispatch_bq_after_list_item(finish);
1322 }
1323
1324 if let Some(level) = matched_level {
1325 self.close_containers_to(level + 1);
1326
1327 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1328 self.close_containers_to(self.containers.depth() - 1);
1329 }
1330 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1331 self.close_containers_to(self.containers.depth() - 1);
1332 }
1333
1334 if let Some(indent_str) = indent_to_emit {
1335 self.builder
1336 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1337 }
1338
1339 let finish = if let Some(nested_marker) = prepared.nested_marker {
1340 lists::add_list_item_with_nested_empty_list(
1341 &mut self.containers,
1342 &mut self.builder,
1343 &list_item,
1344 nested_marker,
1345 self.config,
1346 );
1347 lists::ListItemFinish::Done
1348 } else {
1349 lists::add_list_item(
1350 &mut self.containers,
1351 &mut self.builder,
1352 &list_item,
1353 self.config,
1354 )
1355 };
1356 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1357 return extras;
1358 }
1359 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1360 return extras;
1361 }
1362 if let Some(extras) = self.maybe_open_table_with_trailing_caption_in_new_list_item() {
1363 return extras;
1364 }
1365 self.maybe_open_indented_code_in_new_list_item();
1366 return self.dispatch_bq_after_list_item(finish);
1367 }
1368
1369 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1370 self.close_containers_to(self.containers.depth() - 1);
1371 }
1372 while matches!(
1373 self.containers.last(),
1374 Some(Container::ListItem { .. } | Container::List { .. })
1375 ) {
1376 self.close_containers_to(self.containers.depth() - 1);
1377 }
1378
1379 self.builder.start_node(SyntaxKind::LIST.into());
1380 if let Some(indent_str) = indent_to_emit {
1381 self.builder
1382 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1383 }
1384 self.containers.push(Container::List {
1385 marker: prepared.marker.clone(),
1386 base_indent_cols: prepared.indent_cols,
1387 has_blank_between_items: false,
1388 });
1389
1390 let finish = if let Some(nested_marker) = prepared.nested_marker {
1391 lists::add_list_item_with_nested_empty_list(
1392 &mut self.containers,
1393 &mut self.builder,
1394 &list_item,
1395 nested_marker,
1396 self.config,
1397 );
1398 lists::ListItemFinish::Done
1399 } else {
1400 lists::add_list_item(
1401 &mut self.containers,
1402 &mut self.builder,
1403 &list_item,
1404 self.config,
1405 )
1406 };
1407 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1408 return extras;
1409 }
1410 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1411 return extras;
1412 }
1413 if let Some(extras) = self.maybe_open_table_with_trailing_caption_in_new_list_item() {
1414 return extras;
1415 }
1416 self.maybe_open_indented_code_in_new_list_item();
1417 self.dispatch_bq_after_list_item(finish)
1418 }
1419
1420 fn handle_definition_list_effect(
1427 &mut self,
1428 block_match: &super::block_dispatcher::PreparedBlockMatch,
1429 content: &str,
1430 indent_to_emit: Option<&str>,
1431 ) -> usize {
1432 use super::block_dispatcher::DefinitionPrepared;
1433
1434 let prepared = block_match
1435 .payload
1436 .as_ref()
1437 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1438 let Some(prepared) = prepared else {
1439 return 0;
1440 };
1441
1442 let mut extras: usize = 0;
1443 match prepared {
1444 DefinitionPrepared::Definition {
1445 marker_char,
1446 indent,
1447 spaces_after,
1448 spaces_after_cols,
1449 has_content,
1450 } => {
1451 self.emit_buffered_plain_if_needed();
1452
1453 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1454 self.close_containers_to(self.containers.depth() - 1);
1455 }
1456 while matches!(self.containers.last(), Some(Container::List { .. })) {
1457 self.close_containers_to(self.containers.depth() - 1);
1458 }
1459
1460 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1461 self.close_containers_to(self.containers.depth() - 1);
1462 }
1463
1464 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1465 self.close_containers_to(self.containers.depth() - 1);
1466 }
1467
1468 if definition_lists::in_definition_list(&self.containers)
1472 && !matches!(
1473 self.containers.last(),
1474 Some(Container::DefinitionItem { .. })
1475 )
1476 {
1477 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1478 self.containers.push(Container::DefinitionItem {});
1479 }
1480
1481 if !definition_lists::in_definition_list(&self.containers) {
1482 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1483 self.containers.push(Container::DefinitionList {});
1484 }
1485
1486 if !matches!(
1487 self.containers.last(),
1488 Some(Container::DefinitionItem { .. })
1489 ) {
1490 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1491 self.containers.push(Container::DefinitionItem {});
1492 }
1493
1494 self.builder.start_node(SyntaxKind::DEFINITION.into());
1495
1496 if let Some(indent_str) = indent_to_emit {
1497 self.builder
1498 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1499 }
1500
1501 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1502 let indent_bytes = byte_index_at_column(content, *indent);
1503 if *spaces_after > 0 {
1504 let space_start = indent_bytes + 1;
1505 let space_end = space_start + *spaces_after;
1506 if space_end <= content.len() {
1507 self.builder.token(
1508 SyntaxKind::WHITESPACE.into(),
1509 &content[space_start..space_end],
1510 );
1511 }
1512 }
1513
1514 if !*has_content {
1515 let current_line = self.lines[self.pos];
1516 let (_, newline_str) = strip_newline(current_line);
1517 if !newline_str.is_empty() {
1518 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1519 }
1520 }
1521
1522 let content_col = *indent + 1 + *spaces_after_cols;
1523 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1524 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1525 let mut plain_buffer = TextBuffer::new();
1526 let mut definition_pushed = false;
1527
1528 if *has_content {
1529 let current_line = self.lines[self.pos];
1530 let (trimmed_content, _) = strip_newline(content);
1531
1532 let content_start = content_start_bytes.min(trimmed_content.len());
1539 let content_slice = &trimmed_content[content_start..];
1540 let content_line = &content[content_start_bytes.min(content.len())..];
1541
1542 let (blockquote_depth, inner_blockquote_content) =
1543 count_blockquote_markers(content_line);
1544
1545 let should_start_list_from_first_line = self
1546 .lines
1547 .get(self.pos + 1)
1548 .map(|next_line| {
1549 let (next_without_newline, _) = strip_newline(next_line);
1550 if next_without_newline.trim().is_empty() {
1551 return true;
1552 }
1553
1554 let (next_indent_cols, _) = leading_indent(next_without_newline);
1555 next_indent_cols >= content_col
1556 })
1557 .unwrap_or(true);
1558
1559 if blockquote_depth > 0 {
1560 self.containers.push(Container::Definition {
1561 content_col,
1562 plain_open: false,
1563 plain_buffer: TextBuffer::new(),
1564 });
1565 definition_pushed = true;
1566
1567 let marker_info = parse_blockquote_marker_info(content_line);
1568 for level in 0..blockquote_depth {
1569 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1570 if let Some(info) = marker_info.get(level) {
1571 blockquotes::emit_one_blockquote_marker(
1572 &mut self.builder,
1573 info.leading_spaces,
1574 info.has_trailing_space,
1575 );
1576 }
1577 self.containers.push(Container::BlockQuote {});
1578 }
1579
1580 if !inner_blockquote_content.trim().is_empty() {
1581 paragraphs::start_paragraph_if_needed(
1582 &mut self.containers,
1583 &mut self.builder,
1584 );
1585 paragraphs::append_paragraph_line(
1586 &mut self.containers,
1587 &mut self.builder,
1588 inner_blockquote_content,
1589 self.config,
1590 );
1591 }
1592 } else if let Some(marker_match) = try_parse_list_marker(
1593 content_slice,
1594 self.config,
1595 lists::open_list_hint_at_indent(
1596 &self.containers,
1597 leading_indent(content_slice).0,
1598 ),
1599 ) && should_start_list_from_first_line
1600 {
1601 self.containers.push(Container::Definition {
1602 content_col,
1603 plain_open: false,
1604 plain_buffer: TextBuffer::new(),
1605 });
1606 definition_pushed = true;
1607
1608 let (indent_cols, indent_bytes) = leading_indent(content_line);
1609 self.builder.start_node(SyntaxKind::LIST.into());
1610 self.containers.push(Container::List {
1611 marker: marker_match.marker.clone(),
1612 base_indent_cols: indent_cols,
1613 has_blank_between_items: false,
1614 });
1615
1616 let list_item = ListItemEmissionInput {
1617 content: content_line,
1618 marker_len: marker_match.marker_len,
1619 spaces_after_cols: marker_match.spaces_after_cols,
1620 spaces_after_bytes: marker_match.spaces_after_bytes,
1621 indent_cols,
1622 indent_bytes,
1623 virtual_marker_space: marker_match.virtual_marker_space,
1624 };
1625
1626 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1627 content_line,
1628 marker_match.marker_len,
1629 marker_match.spaces_after_bytes,
1630 ) {
1631 lists::add_list_item_with_nested_empty_list(
1632 &mut self.containers,
1633 &mut self.builder,
1634 &list_item,
1635 nested_marker,
1636 self.config,
1637 );
1638 lists::ListItemFinish::Done
1639 } else {
1640 lists::add_list_item(
1641 &mut self.containers,
1642 &mut self.builder,
1643 &list_item,
1644 self.config,
1645 )
1646 };
1647 extras = self.dispatch_bq_after_list_item(finish);
1648 } else if let Some(fence) =
1649 code_blocks::try_parse_fence_open(content_slice, self.config.dialect)
1650 {
1651 self.containers.push(Container::Definition {
1652 content_col,
1653 plain_open: false,
1654 plain_buffer: TextBuffer::new(),
1655 });
1656 definition_pushed = true;
1657
1658 let bq_depth = self.current_blockquote_depth();
1659 if let Some(indent_str) = indent_to_emit {
1660 self.builder
1661 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1662 }
1663 let fence_line = content[content_start..].to_string();
1664 let prefix = ContainerPrefix::from_scalars(
1668 bq_depth,
1669 0,
1670 bq_depth > 0,
1671 content_col,
1672 false,
1673 );
1674 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
1675 let new_pos = if self.config.extensions.tex_math_gfm
1676 && code_blocks::is_gfm_math_fence(&fence)
1677 {
1678 code_blocks::parse_fenced_math_block(
1679 &mut self.builder,
1680 &window,
1681 fence,
1682 Some(&fence_line),
1683 )
1684 } else {
1685 code_blocks::parse_fenced_code_block(
1686 &mut self.builder,
1687 &window,
1688 fence,
1689 Some(&fence_line),
1690 &self.diagnostics,
1691 self.config.flavor,
1692 )
1693 };
1694 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1695 } else {
1696 let (_, newline_str) = strip_newline(current_line);
1697 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1698 if content_without_newline.is_empty() {
1699 plain_buffer.push_line(newline_str);
1700 } else {
1701 let line_with_newline = if !newline_str.is_empty() {
1702 format!("{}{}", content_without_newline, newline_str)
1703 } else {
1704 content_without_newline.to_string()
1705 };
1706 plain_buffer.push_line(line_with_newline);
1707 }
1708 }
1709 }
1710
1711 if !definition_pushed {
1712 self.containers.push(Container::Definition {
1713 content_col,
1714 plain_open: *has_content,
1715 plain_buffer,
1716 });
1717 }
1718 }
1719 DefinitionPrepared::Term { blank_count } => {
1720 self.emit_buffered_plain_if_needed();
1721
1722 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1723 self.close_containers_to(self.containers.depth() - 1);
1724 }
1725
1726 if !definition_lists::in_definition_list(&self.containers) {
1727 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1728 self.containers.push(Container::DefinitionList {});
1729 }
1730
1731 while matches!(
1732 self.containers.last(),
1733 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1734 ) {
1735 self.close_containers_to(self.containers.depth() - 1);
1736 }
1737
1738 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1739 self.containers.push(Container::DefinitionItem {});
1740
1741 emit_term(&mut self.builder, content, self.config);
1742
1743 for i in 0..*blank_count {
1744 let blank_pos = self.pos + 1 + i;
1745 if blank_pos < self.lines.len() {
1746 let blank_line = self.lines[blank_pos];
1747 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1748 self.builder
1749 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1750 self.builder.finish_node();
1751 }
1752 }
1753 extras = *blank_count;
1754 }
1755 };
1756 extras
1757 }
1758
1759 fn blockquote_marker_info(
1761 &self,
1762 payload: Option<&BlockQuotePrepared>,
1763 line: &str,
1764 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1765 payload
1766 .map(|payload| payload.marker_info.clone())
1767 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1768 }
1769
1770 fn marker_info_for_line(
1776 &self,
1777 payload: Option<&BlockQuotePrepared>,
1778 raw_line: &str,
1779 marker_line: &str,
1780 shifted_prefix: &str,
1781 used_shifted: bool,
1782 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1783 let mut marker_info = if used_shifted {
1784 parse_blockquote_marker_info(marker_line)
1785 } else {
1786 self.blockquote_marker_info(payload, raw_line)
1787 };
1788 if used_shifted && !shifted_prefix.is_empty() {
1789 let (prefix_cols, _) = leading_indent(shifted_prefix);
1790 if let Some(first) = marker_info.first_mut() {
1791 first.leading_spaces += prefix_cols;
1792 }
1793 }
1794 marker_info
1795 }
1796
1797 fn shifted_blockquote_from_list<'b>(
1800 &self,
1801 line: &'b str,
1802 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1803 let list_content_col = self
1812 .containers
1813 .stack
1814 .iter()
1815 .rev()
1816 .find_map(|c| match c {
1817 Container::ListItem { content_col, .. } => Some(*content_col),
1818 _ => None,
1819 })
1820 .unwrap_or(0);
1821 let content_container_indent = self.content_container_indent_to_strip();
1822 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1830 return None;
1831 }
1832 let marker_col = list_content_col.saturating_add(content_container_indent);
1833 if marker_col == 0 {
1834 return None;
1835 }
1836
1837 let (indent_cols, _) = leading_indent(line);
1838 if indent_cols < marker_col {
1839 return None;
1840 }
1841
1842 let idx = byte_index_at_column(line, marker_col);
1843 if idx > line.len() {
1844 return None;
1845 }
1846
1847 let candidate = &line[idx..];
1848 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1849 if candidate_depth == 0 {
1850 return None;
1851 }
1852
1853 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1854 }
1855
1856 fn emit_blockquote_markers(
1857 &mut self,
1858 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1859 depth: usize,
1860 ) {
1861 for i in 0..depth {
1862 if let Some(info) = marker_info.get(i) {
1863 blockquotes::emit_one_blockquote_marker(
1864 &mut self.builder,
1865 info.leading_spaces,
1866 info.has_trailing_space,
1867 );
1868 }
1869 }
1870 }
1871
1872 fn current_blockquote_depth(&self) -> usize {
1873 blockquotes::current_blockquote_depth(&self.containers)
1874 }
1875
1876 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1884 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1885 return None;
1886 };
1887 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1888 }
1889
1890 fn emit_or_buffer_blockquote_marker(
1895 &mut self,
1896 leading_spaces: usize,
1897 has_trailing_space: bool,
1898 ) {
1899 if let Some(Container::ListItem {
1900 buffer,
1901 marker_only,
1902 ..
1903 }) = self.containers.stack.last_mut()
1904 {
1905 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1906 *marker_only = false;
1907 return;
1908 }
1909
1910 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1912 paragraphs::append_paragraph_marker(
1914 &mut self.containers,
1915 leading_spaces,
1916 has_trailing_space,
1917 );
1918 } else {
1919 blockquotes::emit_one_blockquote_marker(
1921 &mut self.builder,
1922 leading_spaces,
1923 has_trailing_space,
1924 );
1925 }
1926 }
1927
1928 fn parse_document_stack(&mut self) {
1929 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1930
1931 log::trace!("Starting document parse");
1932
1933 while self.pos < self.lines.len() {
1936 let line = self.lines[self.pos];
1937
1938 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1939
1940 match self.parse_line(line) {
1941 LineDispatch::Consumed(n) => self.pos += n,
1942 LineDispatch::Rejected => self.pos += 1,
1943 }
1944 }
1945
1946 self.close_containers_to(0);
1947 self.builder.finish_node(); }
1949
1950 fn parse_line(&mut self, line: &str) -> LineDispatch {
1954 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1957 let mut bq_marker_line = line;
1958 let mut shifted_bq_prefix = "";
1959 let mut used_shifted_bq = false;
1960 if bq_depth == 0
1961 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1962 self.shifted_blockquote_from_list(line)
1963 {
1964 bq_depth = candidate_depth;
1965 inner_content = candidate_inner;
1966 bq_marker_line = candidate_line;
1967 shifted_bq_prefix = candidate_prefix;
1968 used_shifted_bq = true;
1969 }
1970 let current_bq_depth = self.current_blockquote_depth();
1971
1972 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1973 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1974 let dispatcher_ctx = if current_bq_depth == 0 {
1975 Some(BlockContext {
1976 has_blank_before,
1977 has_blank_before_strict: has_blank_before,
1978 at_document_start: self.pos == 0,
1979 in_fenced_div: self.in_fenced_div(),
1980 blockquote_depth: current_bq_depth,
1981 config: self.config,
1982 diags: self.diagnostics.clone(),
1983 content_indent: 0,
1984 indent_to_emit: None,
1985 list_indent_info: None,
1986 in_list: lists::in_list(&self.containers),
1987 in_marker_only_list_item: matches!(
1988 self.containers.last(),
1989 Some(Container::ListItem {
1990 marker_only: true,
1991 ..
1992 })
1993 ),
1994 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1995 paragraph_open: self.is_paragraph_open(),
1996 next_line: if self.pos + 1 < self.lines.len() {
1997 Some(self.lines[self.pos + 1])
1998 } else {
1999 None
2000 },
2001 open_alpha_hint: lists::open_list_hint_at_indent(
2002 &self.containers,
2003 leading_indent(line).0,
2004 ),
2005 })
2006 } else {
2007 None
2008 };
2009
2010 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
2011 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2012 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2013 self.block_registry
2014 .detect_prepared(dispatcher_ctx, &stripped)
2015 .and_then(|prepared| {
2016 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
2017 blockquote_match = Some(prepared);
2018 blockquote_match.as_ref().and_then(|prepared| {
2019 prepared
2020 .payload
2021 .as_ref()
2022 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
2023 .cloned()
2024 })
2025 } else {
2026 None
2027 }
2028 })
2029 } else {
2030 None
2031 };
2032
2033 log::trace!(
2034 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
2035 self.pos,
2036 bq_depth,
2037 current_bq_depth,
2038 self.containers.depth(),
2039 line.trim_end()
2040 );
2041
2042 let inner_blank_in_blockquote = bq_depth > 0
2049 && is_blank_line(inner_content)
2050 && (current_bq_depth > 0
2051 || !self.config.extensions.blank_before_blockquote
2052 || blockquotes::can_start_blockquote(
2053 self.pos,
2054 &self.lines,
2055 self.config.extensions.fenced_divs,
2056 ));
2057 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
2058
2059 if is_blank {
2060 if self.is_paragraph_open()
2061 && paragraphs::has_open_inline_math_environment(&self.containers)
2062 {
2063 paragraphs::append_paragraph_line(
2064 &mut self.containers,
2065 &mut self.builder,
2066 line,
2067 self.config,
2068 );
2069 return LineDispatch::consumed(1);
2070 }
2071
2072 self.close_paragraph_if_open();
2074
2075 self.emit_buffered_plain_if_needed();
2079
2080 if bq_depth > current_bq_depth {
2088 for _ in current_bq_depth..bq_depth {
2090 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2091 self.containers.push(Container::BlockQuote {});
2092 }
2093 } else if bq_depth < current_bq_depth {
2094 self.close_blockquotes_to_depth(bq_depth);
2096 }
2097
2098 let mut peek = self.pos + 1;
2105 while peek < self.lines.len() {
2106 let peek_line = self.lines[peek];
2107 if is_blank_line(peek_line) {
2108 peek += 1;
2109 continue;
2110 }
2111 if bq_depth > 0 {
2112 let (peek_bq, _) = count_blockquote_markers(peek_line);
2113 if peek_bq >= bq_depth {
2114 let peek_inner =
2115 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
2116 if is_blank_line(peek_inner) {
2117 peek += 1;
2118 continue;
2119 }
2120 }
2121 }
2122 break;
2123 }
2124
2125 let levels_to_keep = if peek < self.lines.len() {
2127 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
2128 self.current_blockquote_depth(),
2129 &self.containers,
2130 &self.lines,
2131 peek,
2132 self.lines[peek],
2133 )
2134 } else {
2135 0
2136 };
2137 log::trace!(
2138 "Blank line: depth={}, levels_to_keep={}, next='{}'",
2139 self.containers.depth(),
2140 levels_to_keep,
2141 if peek < self.lines.len() {
2142 self.lines[peek]
2143 } else {
2144 "<EOF>"
2145 }
2146 );
2147
2148 while self.containers.depth() > levels_to_keep {
2152 match self.containers.last() {
2153 Some(Container::ListItem { .. }) => {
2154 log::trace!(
2156 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
2157 levels_to_keep,
2158 self.containers.depth()
2159 );
2160 self.close_containers_to(self.containers.depth() - 1);
2161 }
2162 Some(Container::List { .. })
2163 | Some(Container::FootnoteDefinition { .. })
2164 | Some(Container::Alert { .. })
2165 | Some(Container::Paragraph { .. })
2166 | Some(Container::Definition { .. })
2167 | Some(Container::DefinitionItem { .. })
2168 | Some(Container::DefinitionList { .. }) => {
2169 log::trace!(
2170 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
2171 self.containers.last(),
2172 self.containers.depth(),
2173 levels_to_keep
2174 );
2175
2176 self.close_containers_to(self.containers.depth() - 1);
2177 }
2178 _ => break,
2179 }
2180 }
2181
2182 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2186 self.emit_list_item_buffer_if_needed();
2187 }
2188
2189 if bq_depth > 0 {
2191 let marker_info = self.marker_info_for_line(
2192 blockquote_payload.as_ref(),
2193 line,
2194 bq_marker_line,
2195 shifted_bq_prefix,
2196 used_shifted_bq,
2197 );
2198 self.emit_blockquote_markers(&marker_info, bq_depth);
2199 }
2200
2201 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
2202 self.builder
2203 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
2204 self.builder.finish_node();
2205
2206 return LineDispatch::consumed(1);
2207 }
2208
2209 if bq_depth > current_bq_depth {
2211 if self.config.extensions.blank_before_blockquote
2214 && current_bq_depth == 0
2215 && !used_shifted_bq
2216 && !blockquote_payload
2217 .as_ref()
2218 .map(|payload| payload.can_start)
2219 .unwrap_or_else(|| {
2220 blockquotes::can_start_blockquote(
2221 self.pos,
2222 &self.lines,
2223 self.config.extensions.fenced_divs,
2224 )
2225 })
2226 {
2227 self.emit_list_item_buffer_if_needed();
2231 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2232 paragraphs::append_paragraph_line(
2233 &mut self.containers,
2234 &mut self.builder,
2235 line,
2236 self.config,
2237 );
2238 return LineDispatch::consumed(1);
2239 }
2240
2241 let can_nest = if current_bq_depth > 0 {
2244 if self.config.extensions.blank_before_blockquote {
2245 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2247 || (self.pos > 0 && {
2248 let prev_line = self.lines[self.pos - 1];
2249 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2250 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2251 })
2252 } else {
2253 true
2254 }
2255 } else {
2256 blockquote_payload
2257 .as_ref()
2258 .map(|payload| payload.can_nest)
2259 .unwrap_or(true)
2260 };
2261
2262 if !can_nest {
2263 let content_at_current_depth =
2266 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2267
2268 let marker_info = self.marker_info_for_line(
2270 blockquote_payload.as_ref(),
2271 line,
2272 bq_marker_line,
2273 shifted_bq_prefix,
2274 used_shifted_bq,
2275 );
2276 for i in 0..current_bq_depth {
2277 if let Some(info) = marker_info.get(i) {
2278 self.emit_or_buffer_blockquote_marker(
2279 info.leading_spaces,
2280 info.has_trailing_space,
2281 );
2282 }
2283 }
2284
2285 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2286 paragraphs::append_paragraph_line(
2288 &mut self.containers,
2289 &mut self.builder,
2290 content_at_current_depth,
2291 self.config,
2292 );
2293 return LineDispatch::consumed(1);
2294 } else {
2295 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2297 paragraphs::append_paragraph_line(
2298 &mut self.containers,
2299 &mut self.builder,
2300 content_at_current_depth,
2301 self.config,
2302 );
2303 return LineDispatch::consumed(1);
2304 }
2305 }
2306
2307 self.emit_list_item_buffer_if_needed();
2310
2311 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2313 self.close_containers_to(self.containers.depth() - 1);
2314 }
2315
2316 let marker_info = self.marker_info_for_line(
2318 blockquote_payload.as_ref(),
2319 line,
2320 bq_marker_line,
2321 shifted_bq_prefix,
2322 used_shifted_bq,
2323 );
2324
2325 if let (Some(dispatcher_ctx), Some(prepared)) =
2326 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2327 {
2328 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2329 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2330 let _ = self.block_registry.parse_prepared(
2331 prepared,
2332 dispatcher_ctx,
2333 &mut self.builder,
2334 &stripped,
2335 );
2336 for _ in 0..bq_depth {
2337 self.containers.push(Container::BlockQuote {});
2338 }
2339 } else {
2340 for level in 0..current_bq_depth {
2342 if let Some(info) = marker_info.get(level) {
2343 self.emit_or_buffer_blockquote_marker(
2344 info.leading_spaces,
2345 info.has_trailing_space,
2346 );
2347 }
2348 }
2349
2350 for level in current_bq_depth..bq_depth {
2352 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2353
2354 if let Some(info) = marker_info.get(level) {
2356 blockquotes::emit_one_blockquote_marker(
2357 &mut self.builder,
2358 info.leading_spaces,
2359 info.has_trailing_space,
2360 );
2361 }
2362
2363 self.containers.push(Container::BlockQuote {});
2364 }
2365 }
2366
2367 let prev_flag = self.dispatch_list_marker_consumed;
2380 if used_shifted_bq && !self.innermost_li_above_bq() {
2381 self.dispatch_list_marker_consumed = true;
2382 }
2383 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2384 self.dispatch_list_marker_consumed = prev_flag;
2385 return dispatch;
2386 } else if bq_depth < current_bq_depth {
2387 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2393 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2400 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2401 let interrupts_via_fence = is_commonmark
2402 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2403 let interrupts_via_div_close = self.config.extensions.fenced_divs
2408 && self.in_fenced_div()
2409 && fenced_divs::is_div_closing_fence(line);
2410 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2411 if bq_depth > 0 {
2412 let marker_info = self.marker_info_for_line(
2418 blockquote_payload.as_ref(),
2419 line,
2420 bq_marker_line,
2421 shifted_bq_prefix,
2422 used_shifted_bq,
2423 );
2424 for i in 0..bq_depth {
2425 if let Some(info) = marker_info.get(i) {
2426 paragraphs::append_paragraph_marker(
2427 &mut self.containers,
2428 info.leading_spaces,
2429 info.has_trailing_space,
2430 );
2431 }
2432 }
2433 paragraphs::append_paragraph_line(
2434 &mut self.containers,
2435 &mut self.builder,
2436 inner_content,
2437 self.config,
2438 );
2439 } else {
2440 paragraphs::append_paragraph_line(
2441 &mut self.containers,
2442 &mut self.builder,
2443 line,
2444 self.config,
2445 );
2446 }
2447 return LineDispatch::consumed(1);
2448 }
2449 }
2450 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2458 && lists::in_blockquote_list(&self.containers)
2459 && try_parse_list_marker(
2460 line,
2461 self.config,
2462 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2463 )
2464 .is_none()
2465 {
2466 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2467 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2468 let interrupts_via_fence = is_commonmark
2469 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2470 if !interrupts_via_hr && !interrupts_via_fence {
2471 if bq_depth > 0 {
2472 let marker_info = self.marker_info_for_line(
2473 blockquote_payload.as_ref(),
2474 line,
2475 bq_marker_line,
2476 shifted_bq_prefix,
2477 used_shifted_bq,
2478 );
2479 if let Some(Container::ListItem {
2480 buffer,
2481 marker_only,
2482 ..
2483 }) = self.containers.stack.last_mut()
2484 {
2485 for i in 0..bq_depth {
2486 if let Some(info) = marker_info.get(i) {
2487 buffer.push_blockquote_marker(
2488 info.leading_spaces,
2489 info.has_trailing_space,
2490 );
2491 }
2492 }
2493 buffer.push_text(inner_content);
2494 if !inner_content.trim().is_empty() {
2495 *marker_only = false;
2496 }
2497 }
2498 } else if let Some(Container::ListItem {
2499 buffer,
2500 marker_only,
2501 ..
2502 }) = self.containers.stack.last_mut()
2503 {
2504 buffer.push_text(line);
2505 if !line.trim().is_empty() {
2506 *marker_only = false;
2507 }
2508 }
2509 return LineDispatch::consumed(1);
2510 }
2511 }
2512 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2518 if lists::in_blockquote_list(&self.containers)
2521 && let Some(marker_match) = try_parse_list_marker(
2522 line,
2523 self.config,
2524 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2525 )
2526 {
2527 let (indent_cols, indent_bytes) = leading_indent(line);
2528 if let Some(level) = lists::find_matching_list_level(
2529 &self.containers,
2530 &marker_match.marker,
2531 indent_cols,
2532 self.config.dialect,
2533 ) {
2534 self.close_containers_to(level + 1);
2537
2538 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2540 self.close_containers_to(self.containers.depth() - 1);
2541 }
2542 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2543 self.close_containers_to(self.containers.depth() - 1);
2544 }
2545
2546 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2548 line,
2549 marker_match.marker_len,
2550 marker_match.spaces_after_bytes,
2551 ) {
2552 let list_item = ListItemEmissionInput {
2553 content: line,
2554 marker_len: marker_match.marker_len,
2555 spaces_after_cols: marker_match.spaces_after_cols,
2556 spaces_after_bytes: marker_match.spaces_after_bytes,
2557 indent_cols,
2558 indent_bytes,
2559 virtual_marker_space: marker_match.virtual_marker_space,
2560 };
2561 lists::add_list_item_with_nested_empty_list(
2562 &mut self.containers,
2563 &mut self.builder,
2564 &list_item,
2565 nested_marker,
2566 self.config,
2567 );
2568 0
2569 } else {
2570 let list_item = ListItemEmissionInput {
2571 content: line,
2572 marker_len: marker_match.marker_len,
2573 spaces_after_cols: marker_match.spaces_after_cols,
2574 spaces_after_bytes: marker_match.spaces_after_bytes,
2575 indent_cols,
2576 indent_bytes,
2577 virtual_marker_space: marker_match.virtual_marker_space,
2578 };
2579 let finish = lists::add_list_item(
2580 &mut self.containers,
2581 &mut self.builder,
2582 &list_item,
2583 self.config,
2584 );
2585 self.dispatch_bq_after_list_item(finish)
2586 };
2587 return LineDispatch::consumed(1 + extras);
2588 }
2589 }
2590 }
2591
2592 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2594 self.close_containers_to(self.containers.depth() - 1);
2595 }
2596
2597 self.close_blockquotes_to_depth(bq_depth);
2599
2600 if bq_depth > 0 {
2602 let marker_info = self.marker_info_for_line(
2604 blockquote_payload.as_ref(),
2605 line,
2606 bq_marker_line,
2607 shifted_bq_prefix,
2608 used_shifted_bq,
2609 );
2610 for i in 0..bq_depth {
2611 if let Some(info) = marker_info.get(i) {
2612 self.emit_or_buffer_blockquote_marker(
2613 info.leading_spaces,
2614 info.has_trailing_space,
2615 );
2616 }
2617 }
2618 return self.parse_inner_content(inner_content, Some(inner_content));
2620 } else {
2621 return self.parse_inner_content(line, None);
2623 }
2624 } else if bq_depth > 0 {
2625 let mut list_item_continuation = false;
2627 let same_depth_marker_info = self.marker_info_for_line(
2628 blockquote_payload.as_ref(),
2629 line,
2630 bq_marker_line,
2631 shifted_bq_prefix,
2632 used_shifted_bq,
2633 );
2634 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2635
2636 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2648 if let Some(marker_match) = try_parse_list_marker(
2649 inner_content,
2650 self.config,
2651 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2652 ) {
2653 let inner_content_threshold =
2657 marker_match.marker_len + marker_match.spaces_after_cols;
2658 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2659 let sibling_list_level = if is_sibling_candidate {
2660 self.containers
2661 .stack
2662 .iter()
2663 .enumerate()
2664 .rev()
2665 .find_map(|(i, c)| match c {
2666 Container::List { marker, .. }
2667 if lists::markers_match(
2668 &marker_match.marker,
2669 marker,
2670 self.config.dialect,
2671 ) && self.containers.stack[..i]
2672 .iter()
2673 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2674 .count()
2675 == bq_depth =>
2676 {
2677 Some(i)
2678 }
2679 _ => None,
2680 })
2681 } else {
2682 None
2683 };
2684 if let Some(list_level) = sibling_list_level {
2685 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2691 Some(Container::List {
2692 base_indent_cols, ..
2693 }) => *base_indent_cols,
2694 _ => 0,
2695 };
2696
2697 self.emit_list_item_buffer_if_needed();
2699 self.close_containers_to(list_level + 1);
2702
2703 for i in 0..bq_depth {
2707 if let Some(info) = same_depth_marker_info.get(i) {
2708 self.emit_or_buffer_blockquote_marker(
2709 info.leading_spaces,
2710 info.has_trailing_space,
2711 );
2712 }
2713 }
2714
2715 let list_item = ListItemEmissionInput {
2717 content: inner_content,
2718 marker_len: marker_match.marker_len,
2719 spaces_after_cols: marker_match.spaces_after_cols,
2720 spaces_after_bytes: marker_match.spaces_after_bytes,
2721 indent_cols: sibling_base_indent_cols,
2722 indent_bytes: inner_indent_bytes,
2723 virtual_marker_space: marker_match.virtual_marker_space,
2724 };
2725 let finish = lists::add_list_item(
2726 &mut self.containers,
2727 &mut self.builder,
2728 &list_item,
2729 self.config,
2730 );
2731 let extras = if let Some(extras) =
2732 self.maybe_open_fenced_code_in_new_list_item()
2733 {
2734 extras
2735 } else if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
2736 extras
2737 } else if let Some(extras) =
2738 self.maybe_open_table_with_trailing_caption_in_new_list_item()
2739 {
2740 extras
2741 } else {
2742 self.maybe_open_indented_code_in_new_list_item();
2743 self.dispatch_bq_after_list_item(finish)
2744 };
2745 return LineDispatch::consumed(1 + extras);
2746 }
2747 }
2748
2749 if matches!(
2752 self.containers.last(),
2753 Some(Container::ListItem { content_col: _, .. })
2754 ) {
2755 let (indent_cols, _) = leading_indent(inner_content);
2756 let content_indent = self.content_container_indent_to_strip();
2757 let effective_indent = indent_cols.saturating_sub(content_indent);
2758 let content_col = match self.containers.last() {
2759 Some(Container::ListItem { content_col, .. }) => *content_col,
2760 _ => 0,
2761 };
2762
2763 let is_new_item_at_outer_level = if try_parse_list_marker(
2765 inner_content,
2766 self.config,
2767 lists::open_list_hint_at_indent(
2768 &self.containers,
2769 leading_indent(inner_content).0,
2770 ),
2771 )
2772 .is_some()
2773 {
2774 effective_indent < content_col
2775 } else {
2776 false
2777 };
2778
2779 if is_new_item_at_outer_level
2783 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2784 {
2785 log::trace!(
2786 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2787 is_new_item_at_outer_level,
2788 effective_indent,
2789 content_col
2790 );
2791 self.close_containers_to(self.containers.depth() - 1);
2792 } else {
2793 log::trace!(
2794 "Keeping ListItem: effective_indent={} >= content_col={}",
2795 effective_indent,
2796 content_col
2797 );
2798 list_item_continuation = true;
2799 }
2800 }
2801
2802 if list_item_continuation
2806 && code_blocks::try_parse_fence_open(inner_content, self.config.dialect).is_some()
2807 {
2808 list_item_continuation = false;
2809 }
2810
2811 let continuation_has_explicit_marker = list_item_continuation && {
2812 if has_explicit_same_depth_marker {
2813 for i in 0..bq_depth {
2814 if let Some(info) = same_depth_marker_info.get(i) {
2815 self.emit_or_buffer_blockquote_marker(
2816 info.leading_spaces,
2817 info.has_trailing_space,
2818 );
2819 }
2820 }
2821 true
2822 } else {
2823 false
2824 }
2825 };
2826
2827 if !list_item_continuation {
2828 let marker_info = self.marker_info_for_line(
2829 blockquote_payload.as_ref(),
2830 line,
2831 bq_marker_line,
2832 shifted_bq_prefix,
2833 used_shifted_bq,
2834 );
2835 for i in 0..bq_depth {
2836 if let Some(info) = marker_info.get(i) {
2837 self.emit_or_buffer_blockquote_marker(
2838 info.leading_spaces,
2839 info.has_trailing_space,
2840 );
2841 }
2842 }
2843 }
2844 let line_to_append = if list_item_continuation {
2845 if continuation_has_explicit_marker {
2846 Some(inner_content)
2847 } else {
2848 Some(line)
2849 }
2850 } else {
2851 Some(inner_content)
2852 };
2853 let prev_flag = self.dispatch_list_marker_consumed;
2859 if used_shifted_bq && !self.innermost_li_above_bq() {
2860 self.dispatch_list_marker_consumed = true;
2861 }
2862 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2863 self.dispatch_list_marker_consumed = prev_flag;
2864 return dispatch;
2865 }
2866
2867 if current_bq_depth > 0 {
2870 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2872 paragraphs::append_paragraph_line(
2873 &mut self.containers,
2874 &mut self.builder,
2875 line,
2876 self.config,
2877 );
2878 return LineDispatch::consumed(1);
2879 }
2880
2881 if lists::in_blockquote_list(&self.containers)
2883 && let Some(marker_match) = try_parse_list_marker(
2884 line,
2885 self.config,
2886 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2887 )
2888 {
2889 let (indent_cols, indent_bytes) = leading_indent(line);
2890 if let Some(level) = lists::find_matching_list_level(
2891 &self.containers,
2892 &marker_match.marker,
2893 indent_cols,
2894 self.config.dialect,
2895 ) {
2896 self.close_containers_to(level + 1);
2898
2899 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2901 self.close_containers_to(self.containers.depth() - 1);
2902 }
2903 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2904 self.close_containers_to(self.containers.depth() - 1);
2905 }
2906
2907 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2909 line,
2910 marker_match.marker_len,
2911 marker_match.spaces_after_bytes,
2912 ) {
2913 let list_item = ListItemEmissionInput {
2914 content: line,
2915 marker_len: marker_match.marker_len,
2916 spaces_after_cols: marker_match.spaces_after_cols,
2917 spaces_after_bytes: marker_match.spaces_after_bytes,
2918 indent_cols,
2919 indent_bytes,
2920 virtual_marker_space: marker_match.virtual_marker_space,
2921 };
2922 lists::add_list_item_with_nested_empty_list(
2923 &mut self.containers,
2924 &mut self.builder,
2925 &list_item,
2926 nested_marker,
2927 self.config,
2928 );
2929 0
2930 } else {
2931 let list_item = ListItemEmissionInput {
2932 content: line,
2933 marker_len: marker_match.marker_len,
2934 spaces_after_cols: marker_match.spaces_after_cols,
2935 spaces_after_bytes: marker_match.spaces_after_bytes,
2936 indent_cols,
2937 indent_bytes,
2938 virtual_marker_space: marker_match.virtual_marker_space,
2939 };
2940 let finish = lists::add_list_item(
2941 &mut self.containers,
2942 &mut self.builder,
2943 &list_item,
2944 self.config,
2945 );
2946 self.dispatch_bq_after_list_item(finish)
2947 };
2948 return LineDispatch::consumed(1 + extras);
2949 }
2950 }
2951 }
2952
2953 self.parse_inner_content(line, None)
2955 }
2956
2957 fn content_container_indent_to_strip(&self) -> usize {
2959 self.containers
2960 .stack
2961 .iter()
2962 .filter_map(|c| match c {
2963 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2964 Container::Definition { content_col, .. } => Some(*content_col),
2965 _ => None,
2966 })
2967 .sum()
2968 }
2969
2970 fn innermost_li_above_bq(&self) -> bool {
2977 for c in self.containers.stack.iter().rev() {
2978 match c {
2979 Container::ListItem { .. } => return true,
2980 Container::BlockQuote { .. } => return false,
2981 _ => continue,
2982 }
2983 }
2984 false
2985 }
2986
2987 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2993 log::trace!(
2994 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2995 self.pos,
2996 self.containers.depth(),
2997 self.containers.last(),
2998 content.trim_end()
2999 );
3000 let content_indent = self.content_container_indent_to_strip();
3005 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
3006
3007 if self.config.extensions.alerts
3008 && self.current_blockquote_depth() > 0
3009 && !self.in_active_alert()
3010 && !self.is_paragraph_open()
3011 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
3012 {
3013 let (_, newline_str) = strip_newline(stripped_content);
3014 self.builder.start_node(SyntaxKind::ALERT.into());
3015 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
3016 if !newline_str.is_empty() {
3017 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
3018 }
3019 self.containers.push(Container::Alert {
3020 blockquote_depth: self.current_blockquote_depth(),
3021 });
3022 return LineDispatch::consumed(1);
3023 }
3024
3025 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
3029 let is_definition_marker =
3030 definition_lists::try_parse_definition_marker(stripped_content).is_some()
3031 && !stripped_content.starts_with(':');
3032 if content_indent == 0 && is_definition_marker {
3033 } else {
3035 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
3036
3037 if policy.definition_plain_can_continue(
3038 stripped_content,
3039 content,
3040 content_indent,
3041 &BlockContext {
3042 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
3043 has_blank_before_strict: self.pos == 0
3044 || is_blank_line(self.lines[self.pos - 1]),
3045 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
3046 in_fenced_div: self.in_fenced_div(),
3047 blockquote_depth: self.current_blockquote_depth(),
3048 config: self.config,
3049 diags: self.diagnostics.clone(),
3050 content_indent,
3051 indent_to_emit: None,
3052 list_indent_info: None,
3053 in_list: lists::in_list(&self.containers),
3054 in_marker_only_list_item: matches!(
3055 self.containers.last(),
3056 Some(Container::ListItem {
3057 marker_only: true,
3058 ..
3059 })
3060 ),
3061 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3062 paragraph_open: self.is_paragraph_open(),
3063 next_line: if self.pos + 1 < self.lines.len() {
3064 Some(self.lines[self.pos + 1])
3065 } else {
3066 None
3067 },
3068 open_alpha_hint: lists::open_list_hint_at_indent(
3069 &self.containers,
3070 leading_indent(stripped_content).0,
3071 ),
3072 },
3073 &self.lines,
3074 self.pos,
3075 ) {
3076 let content_line = stripped_content;
3077 let (text_without_newline, newline_str) = strip_newline(content_line);
3078 let indent_prefix = if !text_without_newline.trim().is_empty() {
3079 indent_to_emit.unwrap_or("")
3080 } else {
3081 ""
3082 };
3083 let content_line = format!("{}{}", indent_prefix, text_without_newline);
3084
3085 if let Some(Container::Definition {
3086 plain_open,
3087 plain_buffer,
3088 ..
3089 }) = self.containers.stack.last_mut()
3090 {
3091 let line_with_newline = if !newline_str.is_empty() {
3092 format!("{}{}", content_line, newline_str)
3093 } else {
3094 content_line
3095 };
3096 plain_buffer.push_line(line_with_newline);
3097 *plain_open = true;
3098 }
3099
3100 return LineDispatch::consumed(1);
3101 }
3102 }
3103 }
3104
3105 if content_indent > 0 {
3108 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
3109 let current_bq_depth = self.current_blockquote_depth();
3110 let in_footnote_definition = self
3111 .containers
3112 .stack
3113 .iter()
3114 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
3115
3116 if bq_depth > 0 {
3117 if in_footnote_definition
3118 && self.config.extensions.blank_before_blockquote
3119 && current_bq_depth == 0
3120 && !blockquotes::can_start_blockquote(
3121 self.pos,
3122 &self.lines,
3123 self.config.extensions.fenced_divs,
3124 )
3125 {
3126 } else {
3130 self.emit_buffered_plain_if_needed();
3133 self.emit_list_item_buffer_if_needed();
3134
3135 self.close_paragraph_if_open();
3138
3139 if bq_depth < current_bq_depth {
3140 self.close_blockquotes_to_depth(bq_depth);
3141 } else {
3142 let marker_info = parse_blockquote_marker_info(stripped_content);
3143
3144 if bq_depth > current_bq_depth {
3145 for level in current_bq_depth..bq_depth {
3147 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
3148
3149 if level == current_bq_depth
3150 && let Some(indent_str) = indent_to_emit
3151 {
3152 self.builder
3153 .token(SyntaxKind::WHITESPACE.into(), indent_str);
3154 }
3155
3156 if let Some(info) = marker_info.get(level) {
3157 blockquotes::emit_one_blockquote_marker(
3158 &mut self.builder,
3159 info.leading_spaces,
3160 info.has_trailing_space,
3161 );
3162 }
3163
3164 self.containers.push(Container::BlockQuote {});
3165 }
3166 } else {
3167 self.emit_blockquote_markers(&marker_info, bq_depth);
3169 }
3170 }
3171
3172 return self.parse_inner_content(inner_content, Some(inner_content));
3173 }
3174 }
3175 }
3176
3177 let content = stripped_content;
3179
3180 if self.is_paragraph_open()
3181 && (paragraphs::has_open_inline_math_environment(&self.containers)
3182 || paragraphs::has_open_display_math_dollars(&self.containers))
3183 {
3184 paragraphs::append_paragraph_line(
3185 &mut self.containers,
3186 &mut self.builder,
3187 line_to_append.unwrap_or(self.lines[self.pos]),
3188 self.config,
3189 );
3190 return LineDispatch::consumed(1);
3191 }
3192
3193 use super::blocks::lists;
3197 use super::blocks::paragraphs;
3198 let list_indent_info = if lists::in_list(&self.containers) {
3199 let content_col = paragraphs::current_content_col(&self.containers);
3200 if content_col > 0 {
3201 Some(super::block_dispatcher::ListIndentInfo { content_col })
3202 } else {
3203 None
3204 }
3205 } else {
3206 None
3207 };
3208
3209 let next_line = if self.pos + 1 < self.lines.len() {
3210 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
3213 } else {
3214 None
3215 };
3216
3217 let current_bq_depth = self.current_blockquote_depth();
3218 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
3219 && current_bq_depth < alert_bq_depth
3220 {
3221 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
3222 self.close_containers_to(self.containers.depth() - 1);
3223 }
3224 }
3225
3226 let dispatcher_ctx = BlockContext {
3227 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
3231 blockquote_depth: current_bq_depth,
3232 config: self.config,
3233 diags: self.diagnostics.clone(),
3234 content_indent,
3235 indent_to_emit,
3236 list_indent_info,
3237 in_list: lists::in_list(&self.containers),
3238 in_marker_only_list_item: matches!(
3239 self.containers.last(),
3240 Some(Container::ListItem {
3241 marker_only: true,
3242 ..
3243 })
3244 ),
3245 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3246 paragraph_open: self.is_paragraph_open(),
3247 next_line,
3248 open_alpha_hint: lists::open_list_hint_at_indent(
3249 &self.containers,
3250 leading_indent(content).0,
3251 ),
3252 };
3253
3254 let mut dispatcher_ctx = dispatcher_ctx;
3257
3258 let dispatcher_prefix =
3265 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3266
3267 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3271 return dispatch;
3272 }
3273
3274 let dispatcher_match = {
3277 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3278 self.block_registry
3279 .detect_prepared(&dispatcher_ctx, &stripped)
3280 };
3281
3282 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3288 let has_blank_before = if self.pos == 0 || after_metadata_block {
3289 true
3290 } else {
3291 let prev_line = self.lines[self.pos - 1];
3292 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3293 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3294 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3295 && fenced_divs::try_parse_div_fence_open(
3296 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3297 )
3298 .is_some();
3299
3300 let prev_line_blank = is_blank_line(prev_line);
3301 prev_line_blank
3302 || prev_is_fenced_div_open
3303 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3304 || !self.previous_block_requires_blank_before_heading()
3305 };
3306
3307 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3310
3311 let prev_line_blank = if self.pos > 0 {
3312 let prev_line = self.lines[self.pos - 1];
3313 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3314 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3315 } else {
3316 false
3317 };
3318 let has_blank_before_strict = at_document_start || prev_line_blank;
3319
3320 dispatcher_ctx.has_blank_before = has_blank_before;
3321 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3322 dispatcher_ctx.at_document_start = at_document_start;
3323
3324 let dispatcher_match =
3325 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3326 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3328 self.block_registry
3329 .detect_prepared(&dispatcher_ctx, &stripped)
3330 } else {
3331 dispatcher_match
3332 };
3333
3334 if has_blank_before {
3335 if let Some(env_name) = extract_environment_name(content)
3336 && is_inline_math_environment(env_name)
3337 {
3338 if !self.is_paragraph_open() {
3339 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3340 }
3341 paragraphs::append_paragraph_line(
3342 &mut self.containers,
3343 &mut self.builder,
3344 line_to_append.unwrap_or(self.lines[self.pos]),
3345 self.config,
3346 );
3347 return LineDispatch::consumed(1);
3348 }
3349
3350 if let Some(block_match) = dispatcher_match.as_ref() {
3351 let detection = block_match.detection;
3352
3353 match detection {
3354 BlockDetectionResult::YesCanInterrupt => {
3355 self.emit_list_item_buffer_if_needed();
3356 if self.is_paragraph_open() {
3357 self.close_containers_to(self.containers.depth() - 1);
3358 }
3359 }
3360 BlockDetectionResult::Yes => {
3361 self.prepare_for_block_element();
3362 }
3363 BlockDetectionResult::No => unreachable!(),
3364 }
3365
3366 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3367 self.close_containers_to_fenced_div();
3368 }
3369
3370 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3371 self.close_open_footnote_definition();
3372 }
3373
3374 let lines_consumed = {
3375 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3376 self.block_registry.parse_prepared(
3377 block_match,
3378 &dispatcher_ctx,
3379 &mut self.builder,
3380 &stripped,
3381 )
3382 };
3383
3384 if matches!(
3385 self.block_registry.parser_name(block_match),
3386 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3387 ) {
3388 self.after_metadata_block = true;
3389 }
3390
3391 let extras = match block_match.effect {
3392 BlockEffect::None => 0,
3393 BlockEffect::OpenFencedDiv => {
3394 self.containers.push(Container::FencedDiv {});
3395 0
3396 }
3397 BlockEffect::CloseFencedDiv => {
3398 self.close_fenced_div();
3399 0
3400 }
3401 BlockEffect::OpenFootnoteDefinition => {
3402 self.handle_footnote_open_effect(block_match, content)
3403 }
3404 BlockEffect::OpenList => {
3405 self.handle_list_open_effect(block_match, content, indent_to_emit)
3406 }
3407 BlockEffect::OpenDefinitionList => {
3408 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3409 }
3410 BlockEffect::OpenBlockQuote => {
3411 0
3413 }
3414 };
3415
3416 if lines_consumed == 0 {
3417 log::warn!(
3418 "block parser made no progress at line {} (parser={})",
3419 self.pos + 1,
3420 self.block_registry.parser_name(block_match)
3421 );
3422 return LineDispatch::Rejected;
3423 }
3424
3425 return LineDispatch::consumed(lines_consumed + extras);
3426 }
3427 } else if let Some(block_match) = dispatcher_match.as_ref() {
3428 let parser_name = self.block_registry.parser_name(block_match);
3431 match block_match.detection {
3432 BlockDetectionResult::YesCanInterrupt => {
3433 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3434 && self.is_paragraph_open()
3435 {
3436 if !self.is_paragraph_open() {
3438 paragraphs::start_paragraph_if_needed(
3439 &mut self.containers,
3440 &mut self.builder,
3441 );
3442 }
3443 paragraphs::append_paragraph_line(
3444 &mut self.containers,
3445 &mut self.builder,
3446 line_to_append.unwrap_or(self.lines[self.pos]),
3447 self.config,
3448 );
3449 return LineDispatch::consumed(1);
3450 }
3451
3452 if matches!(block_match.effect, BlockEffect::OpenList)
3453 && self.is_paragraph_open()
3454 && !lists::in_list(&self.containers)
3455 && (self.content_container_indent_to_strip() == 0
3456 || self.in_footnote_definition())
3457 {
3458 let allow_interrupt =
3467 self.config.dialect == crate::options::Dialect::CommonMark && {
3468 use super::block_dispatcher::ListPrepared;
3469 use super::blocks::lists::OrderedMarker;
3470 let prepared = block_match
3471 .payload
3472 .as_ref()
3473 .and_then(|p| p.downcast_ref::<ListPrepared>());
3474 match prepared.map(|p| &p.marker) {
3475 Some(ListMarker::Bullet(_)) => true,
3476 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3477 number,
3478 ..
3479 })) => number == "1",
3480 _ => false,
3481 }
3482 };
3483 if !allow_interrupt {
3484 paragraphs::append_paragraph_line(
3485 &mut self.containers,
3486 &mut self.builder,
3487 line_to_append.unwrap_or(self.lines[self.pos]),
3488 self.config,
3489 );
3490 return LineDispatch::consumed(1);
3491 }
3492 }
3493
3494 if matches!(block_match.effect, BlockEffect::OpenList)
3501 && self.try_lazy_list_continuation(block_match, content)
3502 {
3503 return LineDispatch::consumed(1);
3504 }
3505
3506 self.emit_list_item_buffer_if_needed();
3507 if self.is_paragraph_open() {
3508 if self.html_block_demotes_paragraph_to_plain(block_match) {
3509 self.close_paragraph_as_plain_if_open();
3510 } else {
3511 self.close_containers_to(self.containers.depth() - 1);
3512 }
3513 }
3514
3515 if self.config.dialect == crate::options::Dialect::CommonMark
3522 && !matches!(block_match.effect, BlockEffect::OpenList)
3523 {
3524 let (indent_cols, _) = leading_indent(content);
3525 self.close_lists_above_indent(indent_cols);
3526 }
3527 }
3528 BlockDetectionResult::Yes => {
3529 if parser_name == "setext_heading"
3541 && self.is_paragraph_open()
3542 && self.config.dialect == crate::options::Dialect::CommonMark
3543 {
3544 let text_line = self.lines[self.pos];
3545 let underline_line = self.lines[self.pos + 1];
3546 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3547 let level = if underline_char == '=' { 1 } else { 2 };
3548 self.emit_setext_heading_folding_paragraph(
3549 text_line,
3550 underline_line,
3551 level,
3552 );
3553 return LineDispatch::consumed(2);
3554 }
3555
3556 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3559 if !self.is_paragraph_open() {
3560 paragraphs::start_paragraph_if_needed(
3561 &mut self.containers,
3562 &mut self.builder,
3563 );
3564 }
3565 paragraphs::append_paragraph_line(
3566 &mut self.containers,
3567 &mut self.builder,
3568 line_to_append.unwrap_or(self.lines[self.pos]),
3569 self.config,
3570 );
3571 return LineDispatch::consumed(1);
3572 }
3573
3574 if parser_name == "reference_definition" && self.is_paragraph_open() {
3577 paragraphs::append_paragraph_line(
3578 &mut self.containers,
3579 &mut self.builder,
3580 line_to_append.unwrap_or(self.lines[self.pos]),
3581 self.config,
3582 );
3583 return LineDispatch::consumed(1);
3584 }
3585 }
3586 BlockDetectionResult::No => unreachable!(),
3587 }
3588
3589 if !matches!(block_match.detection, BlockDetectionResult::No) {
3590 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3591 self.close_containers_to_fenced_div();
3592 }
3593
3594 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3595 self.close_open_footnote_definition();
3596 }
3597
3598 let lines_consumed = {
3599 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3600 self.block_registry.parse_prepared(
3601 block_match,
3602 &dispatcher_ctx,
3603 &mut self.builder,
3604 &stripped,
3605 )
3606 };
3607
3608 let extras = match block_match.effect {
3609 BlockEffect::None => 0,
3610 BlockEffect::OpenFencedDiv => {
3611 self.containers.push(Container::FencedDiv {});
3612 0
3613 }
3614 BlockEffect::CloseFencedDiv => {
3615 self.close_fenced_div();
3616 0
3617 }
3618 BlockEffect::OpenFootnoteDefinition => {
3619 self.handle_footnote_open_effect(block_match, content)
3620 }
3621 BlockEffect::OpenList => {
3622 self.handle_list_open_effect(block_match, content, indent_to_emit)
3623 }
3624 BlockEffect::OpenDefinitionList => {
3625 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3626 }
3627 BlockEffect::OpenBlockQuote => {
3628 0
3630 }
3631 };
3632
3633 if lines_consumed == 0 {
3634 log::warn!(
3635 "block parser made no progress at line {} (parser={})",
3636 self.pos + 1,
3637 self.block_registry.parser_name(block_match)
3638 );
3639 return LineDispatch::Rejected;
3640 }
3641
3642 return LineDispatch::consumed(lines_consumed + extras);
3643 }
3644 }
3645
3646 if self.config.extensions.line_blocks
3648 && (has_blank_before || self.pos == 0)
3649 && try_parse_line_block_start(content).is_some()
3650 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3654 {
3655 log::trace!("Parsed line block at line {}", self.pos);
3656 self.close_paragraph_if_open();
3658
3659 let prefix = ContainerPrefix::default();
3665 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3666 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3667 if new_pos > self.pos {
3668 return LineDispatch::consumed(new_pos - self.pos);
3669 }
3670 }
3671
3672 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3675 log::trace!(
3676 "Inside ListItem - buffering content: {:?}",
3677 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3678 );
3679 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3681
3682 if let Some(Container::ListItem {
3684 buffer,
3685 marker_only,
3686 ..
3687 }) = self.containers.stack.last_mut()
3688 {
3689 buffer.push_text(line);
3690 if !is_blank_line(line) {
3691 *marker_only = false;
3692 }
3693 }
3694
3695 return LineDispatch::consumed(1);
3696 }
3697
3698 log::trace!(
3699 "Not in ListItem - creating paragraph for: {:?}",
3700 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3701 );
3702 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3704 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3707 paragraphs::append_paragraph_line(
3708 &mut self.containers,
3709 &mut self.builder,
3710 line,
3711 self.config,
3712 );
3713 LineDispatch::consumed(1)
3714 }
3715
3716 fn fenced_div_container_index(&self) -> Option<usize> {
3717 self.containers
3718 .stack
3719 .iter()
3720 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3721 }
3722
3723 fn close_containers_to_fenced_div(&mut self) {
3724 if let Some(index) = self.fenced_div_container_index() {
3725 self.close_containers_to(index + 1);
3726 }
3727 }
3728
3729 fn close_fenced_div(&mut self) {
3730 if let Some(index) = self.fenced_div_container_index() {
3731 self.close_containers_to(index);
3732 }
3733 }
3734
3735 fn in_fenced_div(&self) -> bool {
3736 self.containers
3737 .stack
3738 .iter()
3739 .any(|c| matches!(c, Container::FencedDiv { .. }))
3740 }
3741
3742 fn in_footnote_definition(&self) -> bool {
3750 self.containers
3751 .stack
3752 .iter()
3753 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3754 }
3755}
3756
3757fn try_parse_any_table_kind(
3769 window: &StrippedLines,
3770 builder: &mut GreenNodeBuilder<'static>,
3771 config: &ParserOptions,
3772) -> Option<usize> {
3773 let mut consumed = None;
3774 if config.extensions.grid_tables {
3775 consumed = tables::try_parse_grid_table(window, builder, config);
3776 }
3777 if consumed.is_none() && config.extensions.multiline_tables {
3778 consumed = tables::try_parse_multiline_table(window, builder, config);
3779 }
3780 if consumed.is_none() && config.extensions.pipe_tables {
3781 consumed = tables::try_parse_pipe_table(window, builder, config);
3782 }
3783 if consumed.is_none() && config.extensions.simple_tables {
3784 consumed = tables::try_parse_simple_table(window, builder, config);
3785 }
3786 consumed
3787}
3788
3789fn emit_definition_plain_or_heading(
3790 builder: &mut GreenNodeBuilder<'static>,
3791 text: &str,
3792 config: &ParserOptions,
3793 suppress_footnote_refs: bool,
3794) {
3795 let line_without_newline = text
3796 .strip_suffix("\r\n")
3797 .or_else(|| text.strip_suffix('\n'));
3798 if let Some(line) = line_without_newline
3799 && !line.contains('\n')
3800 && !line.contains('\r')
3801 && let Some(level) = try_parse_atx_heading(line)
3802 {
3803 emit_atx_heading(builder, text, level, config);
3804 return;
3805 }
3806
3807 if let Some(first_nl) = text.find('\n') {
3809 let first_line = &text[..first_nl];
3810 let after_first = &text[first_nl + 1..];
3811 if !after_first.is_empty()
3812 && let Some(level) = try_parse_atx_heading(first_line)
3813 {
3814 let heading_bytes = &text[..first_nl + 1];
3815 emit_atx_heading(builder, heading_bytes, level, config);
3816 builder.start_node(SyntaxKind::PLAIN.into());
3817 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3818 builder.finish_node();
3819 return;
3820 }
3821 }
3822
3823 builder.start_node(SyntaxKind::PLAIN.into());
3824 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3825 builder.finish_node();
3826}
3827
3828fn footnote_first_line_term_lookahead(
3837 lines: &[&str],
3838 pos: usize,
3839 content_col: usize,
3840 table_captions_enabled: bool,
3841) -> Option<usize> {
3842 let mut check_pos = pos + 1;
3843 let mut blank_count = 0;
3844 while check_pos < lines.len() {
3845 let line = lines[check_pos];
3846 let (trimmed, _) = strip_newline(line);
3847 if trimmed.trim().is_empty() {
3848 blank_count += 1;
3849 check_pos += 1;
3850 continue;
3851 }
3852 let (line_indent_cols, _) = leading_indent(trimmed);
3853 if line_indent_cols < content_col {
3854 return None;
3855 }
3856 let strip_bytes = byte_index_at_column(trimmed, content_col);
3857 if strip_bytes > trimmed.len() {
3858 return None;
3859 }
3860 let stripped = &trimmed[strip_bytes..];
3861 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3862 if marker == ':'
3868 && table_captions_enabled
3869 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3870 {
3871 return None;
3872 }
3873 return Some(blank_count);
3874 }
3875 return None;
3876 }
3877 None
3878}