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 myst_directive_closer: self.innermost_myst_directive_closer(),
1981 blockquote_depth: current_bq_depth,
1982 config: self.config,
1983 diags: self.diagnostics.clone(),
1984 content_indent: 0,
1985 indent_to_emit: None,
1986 list_indent_info: None,
1987 in_list: lists::in_list(&self.containers),
1988 in_marker_only_list_item: matches!(
1989 self.containers.last(),
1990 Some(Container::ListItem {
1991 marker_only: true,
1992 ..
1993 })
1994 ),
1995 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1996 paragraph_open: self.is_paragraph_open(),
1997 next_line: if self.pos + 1 < self.lines.len() {
1998 Some(self.lines[self.pos + 1])
1999 } else {
2000 None
2001 },
2002 open_alpha_hint: lists::open_list_hint_at_indent(
2003 &self.containers,
2004 leading_indent(line).0,
2005 ),
2006 })
2007 } else {
2008 None
2009 };
2010
2011 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
2012 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2013 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2014 self.block_registry
2015 .detect_prepared(dispatcher_ctx, &stripped)
2016 .and_then(|prepared| {
2017 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
2018 blockquote_match = Some(prepared);
2019 blockquote_match.as_ref().and_then(|prepared| {
2020 prepared
2021 .payload
2022 .as_ref()
2023 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
2024 .cloned()
2025 })
2026 } else {
2027 None
2028 }
2029 })
2030 } else {
2031 None
2032 };
2033
2034 log::trace!(
2035 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
2036 self.pos,
2037 bq_depth,
2038 current_bq_depth,
2039 self.containers.depth(),
2040 line.trim_end()
2041 );
2042
2043 let inner_blank_in_blockquote = bq_depth > 0
2050 && is_blank_line(inner_content)
2051 && (current_bq_depth > 0
2052 || !self.config.extensions.blank_before_blockquote
2053 || blockquotes::can_start_blockquote(
2054 self.pos,
2055 &self.lines,
2056 self.config.extensions.fenced_divs,
2057 ));
2058 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
2059
2060 if is_blank {
2061 if self.is_paragraph_open()
2062 && paragraphs::has_open_inline_math_environment(&self.containers)
2063 {
2064 paragraphs::append_paragraph_line(
2065 &mut self.containers,
2066 &mut self.builder,
2067 line,
2068 self.config,
2069 );
2070 return LineDispatch::consumed(1);
2071 }
2072
2073 self.close_paragraph_if_open();
2075
2076 self.emit_buffered_plain_if_needed();
2080
2081 if bq_depth > current_bq_depth {
2089 for _ in current_bq_depth..bq_depth {
2091 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2092 self.containers.push(Container::BlockQuote {});
2093 }
2094 } else if bq_depth < current_bq_depth {
2095 self.close_blockquotes_to_depth(bq_depth);
2097 }
2098
2099 let mut peek = self.pos + 1;
2106 while peek < self.lines.len() {
2107 let peek_line = self.lines[peek];
2108 if is_blank_line(peek_line) {
2109 peek += 1;
2110 continue;
2111 }
2112 if bq_depth > 0 {
2113 let (peek_bq, _) = count_blockquote_markers(peek_line);
2114 if peek_bq >= bq_depth {
2115 let peek_inner =
2116 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
2117 if is_blank_line(peek_inner) {
2118 peek += 1;
2119 continue;
2120 }
2121 }
2122 }
2123 break;
2124 }
2125
2126 let levels_to_keep = if peek < self.lines.len() {
2128 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
2129 self.current_blockquote_depth(),
2130 &self.containers,
2131 &self.lines,
2132 peek,
2133 self.lines[peek],
2134 )
2135 } else {
2136 0
2137 };
2138 log::trace!(
2139 "Blank line: depth={}, levels_to_keep={}, next='{}'",
2140 self.containers.depth(),
2141 levels_to_keep,
2142 if peek < self.lines.len() {
2143 self.lines[peek]
2144 } else {
2145 "<EOF>"
2146 }
2147 );
2148
2149 while self.containers.depth() > levels_to_keep {
2153 match self.containers.last() {
2154 Some(Container::ListItem { .. }) => {
2155 log::trace!(
2157 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
2158 levels_to_keep,
2159 self.containers.depth()
2160 );
2161 self.close_containers_to(self.containers.depth() - 1);
2162 }
2163 Some(Container::List { .. })
2164 | Some(Container::FootnoteDefinition { .. })
2165 | Some(Container::Admonition { .. })
2166 | Some(Container::Alert { .. })
2167 | Some(Container::Paragraph { .. })
2168 | Some(Container::Definition { .. })
2169 | Some(Container::DefinitionItem { .. })
2170 | Some(Container::DefinitionList { .. }) => {
2171 log::trace!(
2172 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
2173 self.containers.last(),
2174 self.containers.depth(),
2175 levels_to_keep
2176 );
2177
2178 self.close_containers_to(self.containers.depth() - 1);
2179 }
2180 _ => break,
2181 }
2182 }
2183
2184 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2188 self.emit_list_item_buffer_if_needed();
2189 }
2190
2191 if bq_depth > 0 {
2193 let marker_info = self.marker_info_for_line(
2194 blockquote_payload.as_ref(),
2195 line,
2196 bq_marker_line,
2197 shifted_bq_prefix,
2198 used_shifted_bq,
2199 );
2200 self.emit_blockquote_markers(&marker_info, bq_depth);
2201 }
2202
2203 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
2204 self.builder
2205 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
2206 self.builder.finish_node();
2207
2208 return LineDispatch::consumed(1);
2209 }
2210
2211 if bq_depth > current_bq_depth {
2213 if self.config.extensions.blank_before_blockquote
2216 && current_bq_depth == 0
2217 && !used_shifted_bq
2218 && !blockquote_payload
2219 .as_ref()
2220 .map(|payload| payload.can_start)
2221 .unwrap_or_else(|| {
2222 blockquotes::can_start_blockquote(
2223 self.pos,
2224 &self.lines,
2225 self.config.extensions.fenced_divs,
2226 )
2227 })
2228 {
2229 self.emit_list_item_buffer_if_needed();
2233 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2234 paragraphs::append_paragraph_line(
2235 &mut self.containers,
2236 &mut self.builder,
2237 line,
2238 self.config,
2239 );
2240 return LineDispatch::consumed(1);
2241 }
2242
2243 let can_nest = if current_bq_depth > 0 {
2246 if self.config.extensions.blank_before_blockquote {
2247 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2249 || (self.pos > 0 && {
2250 let prev_line = self.lines[self.pos - 1];
2251 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2252 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2253 })
2254 } else {
2255 true
2256 }
2257 } else {
2258 blockquote_payload
2259 .as_ref()
2260 .map(|payload| payload.can_nest)
2261 .unwrap_or(true)
2262 };
2263
2264 if !can_nest {
2265 let content_at_current_depth =
2268 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2269
2270 let marker_info = self.marker_info_for_line(
2272 blockquote_payload.as_ref(),
2273 line,
2274 bq_marker_line,
2275 shifted_bq_prefix,
2276 used_shifted_bq,
2277 );
2278 for i in 0..current_bq_depth {
2279 if let Some(info) = marker_info.get(i) {
2280 self.emit_or_buffer_blockquote_marker(
2281 info.leading_spaces,
2282 info.has_trailing_space,
2283 );
2284 }
2285 }
2286
2287 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2288 paragraphs::append_paragraph_line(
2290 &mut self.containers,
2291 &mut self.builder,
2292 content_at_current_depth,
2293 self.config,
2294 );
2295 return LineDispatch::consumed(1);
2296 } else {
2297 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2299 paragraphs::append_paragraph_line(
2300 &mut self.containers,
2301 &mut self.builder,
2302 content_at_current_depth,
2303 self.config,
2304 );
2305 return LineDispatch::consumed(1);
2306 }
2307 }
2308
2309 self.emit_list_item_buffer_if_needed();
2312
2313 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2315 self.close_containers_to(self.containers.depth() - 1);
2316 }
2317
2318 let marker_info = self.marker_info_for_line(
2320 blockquote_payload.as_ref(),
2321 line,
2322 bq_marker_line,
2323 shifted_bq_prefix,
2324 used_shifted_bq,
2325 );
2326
2327 if let (Some(dispatcher_ctx), Some(prepared)) =
2328 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2329 {
2330 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2331 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2332 let _ = self.block_registry.parse_prepared(
2333 prepared,
2334 dispatcher_ctx,
2335 &mut self.builder,
2336 &stripped,
2337 );
2338 for _ in 0..bq_depth {
2339 self.containers.push(Container::BlockQuote {});
2340 }
2341 } else {
2342 for level in 0..current_bq_depth {
2344 if let Some(info) = marker_info.get(level) {
2345 self.emit_or_buffer_blockquote_marker(
2346 info.leading_spaces,
2347 info.has_trailing_space,
2348 );
2349 }
2350 }
2351
2352 for level in current_bq_depth..bq_depth {
2354 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2355
2356 if let Some(info) = marker_info.get(level) {
2358 blockquotes::emit_one_blockquote_marker(
2359 &mut self.builder,
2360 info.leading_spaces,
2361 info.has_trailing_space,
2362 );
2363 }
2364
2365 self.containers.push(Container::BlockQuote {});
2366 }
2367 }
2368
2369 let prev_flag = self.dispatch_list_marker_consumed;
2382 if used_shifted_bq && !self.innermost_li_above_bq() {
2383 self.dispatch_list_marker_consumed = true;
2384 }
2385 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2386 self.dispatch_list_marker_consumed = prev_flag;
2387 return dispatch;
2388 } else if bq_depth < current_bq_depth {
2389 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2395 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2402 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2403 let interrupts_via_fence = is_commonmark
2404 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2405 let interrupts_via_div_close = self.config.extensions.fenced_divs
2410 && self.in_fenced_div()
2411 && fenced_divs::is_div_closing_fence(line);
2412 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2413 if bq_depth > 0 {
2414 let marker_info = self.marker_info_for_line(
2420 blockquote_payload.as_ref(),
2421 line,
2422 bq_marker_line,
2423 shifted_bq_prefix,
2424 used_shifted_bq,
2425 );
2426 for i in 0..bq_depth {
2427 if let Some(info) = marker_info.get(i) {
2428 paragraphs::append_paragraph_marker(
2429 &mut self.containers,
2430 info.leading_spaces,
2431 info.has_trailing_space,
2432 );
2433 }
2434 }
2435 paragraphs::append_paragraph_line(
2436 &mut self.containers,
2437 &mut self.builder,
2438 inner_content,
2439 self.config,
2440 );
2441 } else {
2442 paragraphs::append_paragraph_line(
2443 &mut self.containers,
2444 &mut self.builder,
2445 line,
2446 self.config,
2447 );
2448 }
2449 return LineDispatch::consumed(1);
2450 }
2451 }
2452 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2460 && lists::in_blockquote_list(&self.containers)
2461 && try_parse_list_marker(
2462 line,
2463 self.config,
2464 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2465 )
2466 .is_none()
2467 {
2468 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2469 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2470 let interrupts_via_fence = is_commonmark
2471 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2472 if !interrupts_via_hr && !interrupts_via_fence {
2473 if bq_depth > 0 {
2474 let marker_info = self.marker_info_for_line(
2475 blockquote_payload.as_ref(),
2476 line,
2477 bq_marker_line,
2478 shifted_bq_prefix,
2479 used_shifted_bq,
2480 );
2481 if let Some(Container::ListItem {
2482 buffer,
2483 marker_only,
2484 ..
2485 }) = self.containers.stack.last_mut()
2486 {
2487 for i in 0..bq_depth {
2488 if let Some(info) = marker_info.get(i) {
2489 buffer.push_blockquote_marker(
2490 info.leading_spaces,
2491 info.has_trailing_space,
2492 );
2493 }
2494 }
2495 buffer.push_text(inner_content);
2496 if !inner_content.trim().is_empty() {
2497 *marker_only = false;
2498 }
2499 }
2500 } else if let Some(Container::ListItem {
2501 buffer,
2502 marker_only,
2503 ..
2504 }) = self.containers.stack.last_mut()
2505 {
2506 buffer.push_text(line);
2507 if !line.trim().is_empty() {
2508 *marker_only = false;
2509 }
2510 }
2511 return LineDispatch::consumed(1);
2512 }
2513 }
2514 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2520 if lists::in_blockquote_list(&self.containers)
2523 && let Some(marker_match) = try_parse_list_marker(
2524 line,
2525 self.config,
2526 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2527 )
2528 {
2529 let (indent_cols, indent_bytes) = leading_indent(line);
2530 if let Some(level) = lists::find_matching_list_level(
2531 &self.containers,
2532 &marker_match.marker,
2533 indent_cols,
2534 self.config.dialect,
2535 ) {
2536 self.close_containers_to(level + 1);
2539
2540 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2542 self.close_containers_to(self.containers.depth() - 1);
2543 }
2544 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2545 self.close_containers_to(self.containers.depth() - 1);
2546 }
2547
2548 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2550 line,
2551 marker_match.marker_len,
2552 marker_match.spaces_after_bytes,
2553 ) {
2554 let list_item = ListItemEmissionInput {
2555 content: line,
2556 marker_len: marker_match.marker_len,
2557 spaces_after_cols: marker_match.spaces_after_cols,
2558 spaces_after_bytes: marker_match.spaces_after_bytes,
2559 indent_cols,
2560 indent_bytes,
2561 virtual_marker_space: marker_match.virtual_marker_space,
2562 };
2563 lists::add_list_item_with_nested_empty_list(
2564 &mut self.containers,
2565 &mut self.builder,
2566 &list_item,
2567 nested_marker,
2568 self.config,
2569 );
2570 0
2571 } else {
2572 let list_item = ListItemEmissionInput {
2573 content: line,
2574 marker_len: marker_match.marker_len,
2575 spaces_after_cols: marker_match.spaces_after_cols,
2576 spaces_after_bytes: marker_match.spaces_after_bytes,
2577 indent_cols,
2578 indent_bytes,
2579 virtual_marker_space: marker_match.virtual_marker_space,
2580 };
2581 let finish = lists::add_list_item(
2582 &mut self.containers,
2583 &mut self.builder,
2584 &list_item,
2585 self.config,
2586 );
2587 self.dispatch_bq_after_list_item(finish)
2588 };
2589 return LineDispatch::consumed(1 + extras);
2590 }
2591 }
2592 }
2593
2594 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2596 self.close_containers_to(self.containers.depth() - 1);
2597 }
2598
2599 self.close_blockquotes_to_depth(bq_depth);
2601
2602 if bq_depth > 0 {
2604 let marker_info = self.marker_info_for_line(
2606 blockquote_payload.as_ref(),
2607 line,
2608 bq_marker_line,
2609 shifted_bq_prefix,
2610 used_shifted_bq,
2611 );
2612 for i in 0..bq_depth {
2613 if let Some(info) = marker_info.get(i) {
2614 self.emit_or_buffer_blockquote_marker(
2615 info.leading_spaces,
2616 info.has_trailing_space,
2617 );
2618 }
2619 }
2620 return self.parse_inner_content(inner_content, Some(inner_content));
2622 } else {
2623 return self.parse_inner_content(line, None);
2625 }
2626 } else if bq_depth > 0 {
2627 let mut list_item_continuation = false;
2629 let same_depth_marker_info = self.marker_info_for_line(
2630 blockquote_payload.as_ref(),
2631 line,
2632 bq_marker_line,
2633 shifted_bq_prefix,
2634 used_shifted_bq,
2635 );
2636 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2637
2638 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2650 if let Some(marker_match) = try_parse_list_marker(
2651 inner_content,
2652 self.config,
2653 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2654 ) {
2655 let inner_content_threshold =
2659 marker_match.marker_len + marker_match.spaces_after_cols;
2660 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2661 let sibling_list_level = if is_sibling_candidate {
2662 self.containers
2663 .stack
2664 .iter()
2665 .enumerate()
2666 .rev()
2667 .find_map(|(i, c)| match c {
2668 Container::List { marker, .. }
2669 if lists::markers_match(
2670 &marker_match.marker,
2671 marker,
2672 self.config.dialect,
2673 ) && self.containers.stack[..i]
2674 .iter()
2675 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2676 .count()
2677 == bq_depth =>
2678 {
2679 Some(i)
2680 }
2681 _ => None,
2682 })
2683 } else {
2684 None
2685 };
2686 if let Some(list_level) = sibling_list_level {
2687 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2693 Some(Container::List {
2694 base_indent_cols, ..
2695 }) => *base_indent_cols,
2696 _ => 0,
2697 };
2698
2699 self.emit_list_item_buffer_if_needed();
2701 self.close_containers_to(list_level + 1);
2704
2705 for i in 0..bq_depth {
2709 if let Some(info) = same_depth_marker_info.get(i) {
2710 self.emit_or_buffer_blockquote_marker(
2711 info.leading_spaces,
2712 info.has_trailing_space,
2713 );
2714 }
2715 }
2716
2717 let list_item = ListItemEmissionInput {
2719 content: inner_content,
2720 marker_len: marker_match.marker_len,
2721 spaces_after_cols: marker_match.spaces_after_cols,
2722 spaces_after_bytes: marker_match.spaces_after_bytes,
2723 indent_cols: sibling_base_indent_cols,
2724 indent_bytes: inner_indent_bytes,
2725 virtual_marker_space: marker_match.virtual_marker_space,
2726 };
2727 let finish = lists::add_list_item(
2728 &mut self.containers,
2729 &mut self.builder,
2730 &list_item,
2731 self.config,
2732 );
2733 let extras = if let Some(extras) =
2734 self.maybe_open_fenced_code_in_new_list_item()
2735 {
2736 extras
2737 } else if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
2738 extras
2739 } else if let Some(extras) =
2740 self.maybe_open_table_with_trailing_caption_in_new_list_item()
2741 {
2742 extras
2743 } else {
2744 self.maybe_open_indented_code_in_new_list_item();
2745 self.dispatch_bq_after_list_item(finish)
2746 };
2747 return LineDispatch::consumed(1 + extras);
2748 }
2749 }
2750
2751 if matches!(
2754 self.containers.last(),
2755 Some(Container::ListItem { content_col: _, .. })
2756 ) {
2757 let (indent_cols, _) = leading_indent(inner_content);
2758 let content_indent = self.content_container_indent_to_strip();
2759 let effective_indent = indent_cols.saturating_sub(content_indent);
2760 let content_col = match self.containers.last() {
2761 Some(Container::ListItem { content_col, .. }) => *content_col,
2762 _ => 0,
2763 };
2764
2765 let is_new_item_at_outer_level = if try_parse_list_marker(
2767 inner_content,
2768 self.config,
2769 lists::open_list_hint_at_indent(
2770 &self.containers,
2771 leading_indent(inner_content).0,
2772 ),
2773 )
2774 .is_some()
2775 {
2776 effective_indent < content_col
2777 } else {
2778 false
2779 };
2780
2781 if is_new_item_at_outer_level
2785 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2786 {
2787 log::trace!(
2788 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2789 is_new_item_at_outer_level,
2790 effective_indent,
2791 content_col
2792 );
2793 self.close_containers_to(self.containers.depth() - 1);
2794 } else {
2795 log::trace!(
2796 "Keeping ListItem: effective_indent={} >= content_col={}",
2797 effective_indent,
2798 content_col
2799 );
2800 list_item_continuation = true;
2801 }
2802 }
2803
2804 if list_item_continuation
2808 && code_blocks::try_parse_fence_open(inner_content, self.config.dialect).is_some()
2809 {
2810 list_item_continuation = false;
2811 }
2812
2813 let continuation_has_explicit_marker = list_item_continuation && {
2814 if has_explicit_same_depth_marker {
2815 for i in 0..bq_depth {
2816 if let Some(info) = same_depth_marker_info.get(i) {
2817 self.emit_or_buffer_blockquote_marker(
2818 info.leading_spaces,
2819 info.has_trailing_space,
2820 );
2821 }
2822 }
2823 true
2824 } else {
2825 false
2826 }
2827 };
2828
2829 if !list_item_continuation {
2830 let marker_info = self.marker_info_for_line(
2831 blockquote_payload.as_ref(),
2832 line,
2833 bq_marker_line,
2834 shifted_bq_prefix,
2835 used_shifted_bq,
2836 );
2837 for i in 0..bq_depth {
2838 if let Some(info) = marker_info.get(i) {
2839 self.emit_or_buffer_blockquote_marker(
2840 info.leading_spaces,
2841 info.has_trailing_space,
2842 );
2843 }
2844 }
2845 }
2846 let line_to_append = if list_item_continuation {
2847 if continuation_has_explicit_marker {
2848 Some(inner_content)
2849 } else {
2850 Some(line)
2851 }
2852 } else {
2853 Some(inner_content)
2854 };
2855 let prev_flag = self.dispatch_list_marker_consumed;
2861 if used_shifted_bq && !self.innermost_li_above_bq() {
2862 self.dispatch_list_marker_consumed = true;
2863 }
2864 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2865 self.dispatch_list_marker_consumed = prev_flag;
2866 return dispatch;
2867 }
2868
2869 if current_bq_depth > 0 {
2872 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2874 paragraphs::append_paragraph_line(
2875 &mut self.containers,
2876 &mut self.builder,
2877 line,
2878 self.config,
2879 );
2880 return LineDispatch::consumed(1);
2881 }
2882
2883 if lists::in_blockquote_list(&self.containers)
2885 && let Some(marker_match) = try_parse_list_marker(
2886 line,
2887 self.config,
2888 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2889 )
2890 {
2891 let (indent_cols, indent_bytes) = leading_indent(line);
2892 if let Some(level) = lists::find_matching_list_level(
2893 &self.containers,
2894 &marker_match.marker,
2895 indent_cols,
2896 self.config.dialect,
2897 ) {
2898 self.close_containers_to(level + 1);
2900
2901 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2903 self.close_containers_to(self.containers.depth() - 1);
2904 }
2905 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2906 self.close_containers_to(self.containers.depth() - 1);
2907 }
2908
2909 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2911 line,
2912 marker_match.marker_len,
2913 marker_match.spaces_after_bytes,
2914 ) {
2915 let list_item = ListItemEmissionInput {
2916 content: line,
2917 marker_len: marker_match.marker_len,
2918 spaces_after_cols: marker_match.spaces_after_cols,
2919 spaces_after_bytes: marker_match.spaces_after_bytes,
2920 indent_cols,
2921 indent_bytes,
2922 virtual_marker_space: marker_match.virtual_marker_space,
2923 };
2924 lists::add_list_item_with_nested_empty_list(
2925 &mut self.containers,
2926 &mut self.builder,
2927 &list_item,
2928 nested_marker,
2929 self.config,
2930 );
2931 0
2932 } else {
2933 let list_item = ListItemEmissionInput {
2934 content: line,
2935 marker_len: marker_match.marker_len,
2936 spaces_after_cols: marker_match.spaces_after_cols,
2937 spaces_after_bytes: marker_match.spaces_after_bytes,
2938 indent_cols,
2939 indent_bytes,
2940 virtual_marker_space: marker_match.virtual_marker_space,
2941 };
2942 let finish = lists::add_list_item(
2943 &mut self.containers,
2944 &mut self.builder,
2945 &list_item,
2946 self.config,
2947 );
2948 self.dispatch_bq_after_list_item(finish)
2949 };
2950 return LineDispatch::consumed(1 + extras);
2951 }
2952 }
2953 }
2954
2955 self.parse_inner_content(line, None)
2957 }
2958
2959 fn close_dedented_admonitions(&mut self, content: &str) {
2968 if !self
2969 .containers
2970 .stack
2971 .iter()
2972 .any(|c| matches!(c, Container::Admonition { .. }))
2973 {
2974 return;
2975 }
2976 if self
2977 .containers
2978 .stack
2979 .iter()
2980 .any(|c| matches!(c, Container::ListItem { .. }))
2981 {
2982 return;
2983 }
2984
2985 let (without_newline, _) = strip_newline(content);
2986 if without_newline.trim().is_empty() {
2987 return;
2988 }
2989 let (indent_cols, _) = leading_indent(without_newline);
2990
2991 let mut acc = 0usize;
2992 let mut close_to: Option<usize> = None;
2993 for (idx, c) in self.containers.stack.iter().enumerate() {
2994 match c {
2995 Container::FootnoteDefinition { content_col, .. }
2996 | Container::Definition { content_col, .. } => {
2997 acc += *content_col;
2998 }
2999 Container::Admonition { content_col } => {
3000 acc += *content_col;
3001 if indent_cols < acc {
3002 close_to = Some(idx);
3003 break;
3004 }
3005 }
3006 _ => {}
3007 }
3008 }
3009 if let Some(idx) = close_to {
3010 self.close_containers_to(idx);
3011 }
3012 }
3013
3014 fn content_container_indent_to_strip(&self) -> usize {
3016 self.containers
3017 .stack
3018 .iter()
3019 .filter_map(|c| match c {
3020 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
3021 Container::Definition { content_col, .. } => Some(*content_col),
3022 Container::Admonition { content_col } => Some(*content_col),
3023 _ => None,
3024 })
3025 .sum()
3026 }
3027
3028 fn innermost_li_above_bq(&self) -> bool {
3035 for c in self.containers.stack.iter().rev() {
3036 match c {
3037 Container::ListItem { .. } => return true,
3038 Container::BlockQuote { .. } => return false,
3039 _ => continue,
3040 }
3041 }
3042 false
3043 }
3044
3045 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
3051 log::trace!(
3052 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
3053 self.pos,
3054 self.containers.depth(),
3055 self.containers.last(),
3056 content.trim_end()
3057 );
3058 self.close_dedented_admonitions(content);
3062
3063 let content_indent = self.content_container_indent_to_strip();
3068 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
3069
3070 if self.config.extensions.alerts
3071 && self.current_blockquote_depth() > 0
3072 && !self.in_active_alert()
3073 && !self.is_paragraph_open()
3074 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
3075 {
3076 let (_, newline_str) = strip_newline(stripped_content);
3077 self.builder.start_node(SyntaxKind::ALERT.into());
3078 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
3079 if !newline_str.is_empty() {
3080 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
3081 }
3082 self.containers.push(Container::Alert {
3083 blockquote_depth: self.current_blockquote_depth(),
3084 });
3085 return LineDispatch::consumed(1);
3086 }
3087
3088 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
3092 let is_definition_marker =
3093 definition_lists::try_parse_definition_marker(stripped_content).is_some()
3094 && !stripped_content.starts_with(':');
3095 if content_indent == 0 && is_definition_marker {
3096 } else {
3098 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
3099
3100 if policy.definition_plain_can_continue(
3101 stripped_content,
3102 content,
3103 content_indent,
3104 &BlockContext {
3105 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
3106 has_blank_before_strict: self.pos == 0
3107 || is_blank_line(self.lines[self.pos - 1]),
3108 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
3109 in_fenced_div: self.in_fenced_div(),
3110 myst_directive_closer: self.innermost_myst_directive_closer(),
3111 blockquote_depth: self.current_blockquote_depth(),
3112 config: self.config,
3113 diags: self.diagnostics.clone(),
3114 content_indent,
3115 indent_to_emit: None,
3116 list_indent_info: None,
3117 in_list: lists::in_list(&self.containers),
3118 in_marker_only_list_item: matches!(
3119 self.containers.last(),
3120 Some(Container::ListItem {
3121 marker_only: true,
3122 ..
3123 })
3124 ),
3125 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3126 paragraph_open: self.is_paragraph_open(),
3127 next_line: if self.pos + 1 < self.lines.len() {
3128 Some(self.lines[self.pos + 1])
3129 } else {
3130 None
3131 },
3132 open_alpha_hint: lists::open_list_hint_at_indent(
3133 &self.containers,
3134 leading_indent(stripped_content).0,
3135 ),
3136 },
3137 &self.lines,
3138 self.pos,
3139 ) {
3140 let content_line = stripped_content;
3141 let (text_without_newline, newline_str) = strip_newline(content_line);
3142 let indent_prefix = if !text_without_newline.trim().is_empty() {
3143 indent_to_emit.unwrap_or("")
3144 } else {
3145 ""
3146 };
3147 let content_line = format!("{}{}", indent_prefix, text_without_newline);
3148
3149 if let Some(Container::Definition {
3150 plain_open,
3151 plain_buffer,
3152 ..
3153 }) = self.containers.stack.last_mut()
3154 {
3155 let line_with_newline = if !newline_str.is_empty() {
3156 format!("{}{}", content_line, newline_str)
3157 } else {
3158 content_line
3159 };
3160 plain_buffer.push_line(line_with_newline);
3161 *plain_open = true;
3162 }
3163
3164 return LineDispatch::consumed(1);
3165 }
3166 }
3167 }
3168
3169 if content_indent > 0 {
3172 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
3173 let current_bq_depth = self.current_blockquote_depth();
3174 let in_footnote_definition = self
3175 .containers
3176 .stack
3177 .iter()
3178 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
3179
3180 if bq_depth > 0 {
3181 if in_footnote_definition
3182 && self.config.extensions.blank_before_blockquote
3183 && current_bq_depth == 0
3184 && !blockquotes::can_start_blockquote(
3185 self.pos,
3186 &self.lines,
3187 self.config.extensions.fenced_divs,
3188 )
3189 {
3190 } else {
3194 self.emit_buffered_plain_if_needed();
3197 self.emit_list_item_buffer_if_needed();
3198
3199 self.close_paragraph_if_open();
3202
3203 if bq_depth < current_bq_depth {
3204 self.close_blockquotes_to_depth(bq_depth);
3205 } else {
3206 let marker_info = parse_blockquote_marker_info(stripped_content);
3207
3208 if bq_depth > current_bq_depth {
3209 for level in current_bq_depth..bq_depth {
3211 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
3212
3213 if level == current_bq_depth
3214 && let Some(indent_str) = indent_to_emit
3215 {
3216 self.builder
3217 .token(SyntaxKind::WHITESPACE.into(), indent_str);
3218 }
3219
3220 if let Some(info) = marker_info.get(level) {
3221 blockquotes::emit_one_blockquote_marker(
3222 &mut self.builder,
3223 info.leading_spaces,
3224 info.has_trailing_space,
3225 );
3226 }
3227
3228 self.containers.push(Container::BlockQuote {});
3229 }
3230 } else {
3231 self.emit_blockquote_markers(&marker_info, bq_depth);
3233 }
3234 }
3235
3236 return self.parse_inner_content(inner_content, Some(inner_content));
3237 }
3238 }
3239 }
3240
3241 let content = stripped_content;
3243
3244 if self.is_paragraph_open()
3245 && (paragraphs::has_open_inline_math_environment(&self.containers)
3246 || paragraphs::has_open_display_math_dollars(&self.containers))
3247 {
3248 paragraphs::append_paragraph_line(
3249 &mut self.containers,
3250 &mut self.builder,
3251 line_to_append.unwrap_or(self.lines[self.pos]),
3252 self.config,
3253 );
3254 return LineDispatch::consumed(1);
3255 }
3256
3257 use super::blocks::lists;
3261 use super::blocks::paragraphs;
3262 let list_indent_info = if lists::in_list(&self.containers) {
3263 let content_col = paragraphs::current_content_col(&self.containers);
3264 if content_col > 0 {
3265 Some(super::block_dispatcher::ListIndentInfo { content_col })
3266 } else {
3267 None
3268 }
3269 } else {
3270 None
3271 };
3272
3273 let next_line = if self.pos + 1 < self.lines.len() {
3274 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
3277 } else {
3278 None
3279 };
3280
3281 let current_bq_depth = self.current_blockquote_depth();
3282 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
3283 && current_bq_depth < alert_bq_depth
3284 {
3285 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
3286 self.close_containers_to(self.containers.depth() - 1);
3287 }
3288 }
3289
3290 let dispatcher_ctx = BlockContext {
3291 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
3295 myst_directive_closer: self.innermost_myst_directive_closer(),
3296 blockquote_depth: current_bq_depth,
3297 config: self.config,
3298 diags: self.diagnostics.clone(),
3299 content_indent,
3300 indent_to_emit,
3301 list_indent_info,
3302 in_list: lists::in_list(&self.containers),
3303 in_marker_only_list_item: matches!(
3304 self.containers.last(),
3305 Some(Container::ListItem {
3306 marker_only: true,
3307 ..
3308 })
3309 ),
3310 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3311 paragraph_open: self.is_paragraph_open(),
3312 next_line,
3313 open_alpha_hint: lists::open_list_hint_at_indent(
3314 &self.containers,
3315 leading_indent(content).0,
3316 ),
3317 };
3318
3319 let mut dispatcher_ctx = dispatcher_ctx;
3322
3323 let dispatcher_prefix =
3330 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3331
3332 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3336 return dispatch;
3337 }
3338
3339 let dispatcher_match = {
3342 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3343 self.block_registry
3344 .detect_prepared(&dispatcher_ctx, &stripped)
3345 };
3346
3347 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3353 let has_blank_before = if self.pos == 0 || after_metadata_block {
3354 true
3355 } else {
3356 let prev_line = self.lines[self.pos - 1];
3357 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3358 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3359 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3360 && fenced_divs::try_parse_div_fence_open(
3361 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3362 )
3363 .is_some();
3364
3365 let prev_line_blank = is_blank_line(prev_line);
3366 prev_line_blank
3367 || prev_is_fenced_div_open
3368 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3369 || !self.previous_block_requires_blank_before_heading()
3370 };
3371
3372 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3375
3376 let prev_line_blank = if self.pos > 0 {
3377 let prev_line = self.lines[self.pos - 1];
3378 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3379 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3380 } else {
3381 false
3382 };
3383 let has_blank_before_strict = at_document_start || prev_line_blank;
3384
3385 dispatcher_ctx.has_blank_before = has_blank_before;
3386 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3387 dispatcher_ctx.at_document_start = at_document_start;
3388
3389 let dispatcher_match =
3390 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3391 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3393 self.block_registry
3394 .detect_prepared(&dispatcher_ctx, &stripped)
3395 } else {
3396 dispatcher_match
3397 };
3398
3399 if has_blank_before {
3400 if let Some(env_name) = extract_environment_name(content)
3401 && is_inline_math_environment(env_name)
3402 {
3403 if !self.is_paragraph_open() {
3404 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3405 }
3406 paragraphs::append_paragraph_line(
3407 &mut self.containers,
3408 &mut self.builder,
3409 line_to_append.unwrap_or(self.lines[self.pos]),
3410 self.config,
3411 );
3412 return LineDispatch::consumed(1);
3413 }
3414
3415 if let Some(block_match) = dispatcher_match.as_ref() {
3416 let detection = block_match.detection;
3417
3418 match detection {
3419 BlockDetectionResult::YesCanInterrupt => {
3420 self.emit_list_item_buffer_if_needed();
3421 if self.is_paragraph_open() {
3422 self.close_containers_to(self.containers.depth() - 1);
3423 }
3424 }
3425 BlockDetectionResult::Yes => {
3426 self.prepare_for_block_element();
3427 }
3428 BlockDetectionResult::No => unreachable!(),
3429 }
3430
3431 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3432 self.close_containers_to_fenced_div();
3433 }
3434
3435 if matches!(block_match.effect, BlockEffect::CloseMystDirective) {
3436 self.close_containers_to_myst_directive();
3437 }
3438
3439 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3440 self.close_open_footnote_definition();
3441 }
3442
3443 let lines_consumed = {
3444 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3445 self.block_registry.parse_prepared(
3446 block_match,
3447 &dispatcher_ctx,
3448 &mut self.builder,
3449 &stripped,
3450 )
3451 };
3452
3453 if matches!(
3454 self.block_registry.parser_name(block_match),
3455 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3456 ) {
3457 self.after_metadata_block = true;
3458 }
3459
3460 let extras = match block_match.effect {
3461 BlockEffect::None => 0,
3462 BlockEffect::OpenFencedDiv => {
3463 self.containers.push(Container::FencedDiv {});
3464 0
3465 }
3466 BlockEffect::CloseFencedDiv => {
3467 self.close_fenced_div();
3468 0
3469 }
3470 BlockEffect::OpenMystDirective => {
3471 self.push_myst_directive_container(block_match);
3472 0
3473 }
3474 BlockEffect::CloseMystDirective => {
3475 self.close_myst_directive();
3476 0
3477 }
3478 BlockEffect::OpenAdmonition => {
3479 self.containers
3480 .push(Container::Admonition { content_col: 4 });
3481 0
3482 }
3483 BlockEffect::OpenFootnoteDefinition => {
3484 self.handle_footnote_open_effect(block_match, content)
3485 }
3486 BlockEffect::OpenList => {
3487 self.handle_list_open_effect(block_match, content, indent_to_emit)
3488 }
3489 BlockEffect::OpenDefinitionList => {
3490 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3491 }
3492 BlockEffect::OpenBlockQuote => {
3493 0
3495 }
3496 };
3497
3498 if lines_consumed == 0 {
3499 log::warn!(
3500 "block parser made no progress at line {} (parser={})",
3501 self.pos + 1,
3502 self.block_registry.parser_name(block_match)
3503 );
3504 return LineDispatch::Rejected;
3505 }
3506
3507 return LineDispatch::consumed(lines_consumed + extras);
3508 }
3509 } else if let Some(block_match) = dispatcher_match.as_ref() {
3510 let parser_name = self.block_registry.parser_name(block_match);
3513 match block_match.detection {
3514 BlockDetectionResult::YesCanInterrupt => {
3515 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3516 && self.is_paragraph_open()
3517 {
3518 if !self.is_paragraph_open() {
3520 paragraphs::start_paragraph_if_needed(
3521 &mut self.containers,
3522 &mut self.builder,
3523 );
3524 }
3525 paragraphs::append_paragraph_line(
3526 &mut self.containers,
3527 &mut self.builder,
3528 line_to_append.unwrap_or(self.lines[self.pos]),
3529 self.config,
3530 );
3531 return LineDispatch::consumed(1);
3532 }
3533
3534 if matches!(block_match.effect, BlockEffect::OpenList)
3535 && self.is_paragraph_open()
3536 && !lists::in_list(&self.containers)
3537 && (self.content_container_indent_to_strip() == 0
3538 || self.in_footnote_definition())
3539 {
3540 let allow_interrupt =
3549 self.config.dialect == crate::options::Dialect::CommonMark && {
3550 use super::block_dispatcher::ListPrepared;
3551 use super::blocks::lists::OrderedMarker;
3552 let prepared = block_match
3553 .payload
3554 .as_ref()
3555 .and_then(|p| p.downcast_ref::<ListPrepared>());
3556 match prepared.map(|p| &p.marker) {
3557 Some(ListMarker::Bullet(_)) => true,
3558 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3559 number,
3560 ..
3561 })) => number == "1",
3562 _ => false,
3563 }
3564 };
3565 if !allow_interrupt {
3566 paragraphs::append_paragraph_line(
3567 &mut self.containers,
3568 &mut self.builder,
3569 line_to_append.unwrap_or(self.lines[self.pos]),
3570 self.config,
3571 );
3572 return LineDispatch::consumed(1);
3573 }
3574 }
3575
3576 if matches!(block_match.effect, BlockEffect::OpenList)
3583 && self.try_lazy_list_continuation(block_match, content)
3584 {
3585 return LineDispatch::consumed(1);
3586 }
3587
3588 self.emit_list_item_buffer_if_needed();
3589 if self.is_paragraph_open() {
3590 if self.html_block_demotes_paragraph_to_plain(block_match) {
3591 self.close_paragraph_as_plain_if_open();
3592 } else {
3593 self.close_containers_to(self.containers.depth() - 1);
3594 }
3595 }
3596
3597 if self.config.dialect == crate::options::Dialect::CommonMark
3604 && !matches!(block_match.effect, BlockEffect::OpenList)
3605 {
3606 let (indent_cols, _) = leading_indent(content);
3607 self.close_lists_above_indent(indent_cols);
3608 }
3609 }
3610 BlockDetectionResult::Yes => {
3611 if parser_name == "setext_heading"
3623 && self.is_paragraph_open()
3624 && self.config.dialect == crate::options::Dialect::CommonMark
3625 {
3626 let text_line = self.lines[self.pos];
3627 let underline_line = self.lines[self.pos + 1];
3628 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3629 let level = if underline_char == '=' { 1 } else { 2 };
3630 self.emit_setext_heading_folding_paragraph(
3631 text_line,
3632 underline_line,
3633 level,
3634 );
3635 return LineDispatch::consumed(2);
3636 }
3637
3638 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3641 if !self.is_paragraph_open() {
3642 paragraphs::start_paragraph_if_needed(
3643 &mut self.containers,
3644 &mut self.builder,
3645 );
3646 }
3647 paragraphs::append_paragraph_line(
3648 &mut self.containers,
3649 &mut self.builder,
3650 line_to_append.unwrap_or(self.lines[self.pos]),
3651 self.config,
3652 );
3653 return LineDispatch::consumed(1);
3654 }
3655
3656 if parser_name == "reference_definition" && self.is_paragraph_open() {
3659 paragraphs::append_paragraph_line(
3660 &mut self.containers,
3661 &mut self.builder,
3662 line_to_append.unwrap_or(self.lines[self.pos]),
3663 self.config,
3664 );
3665 return LineDispatch::consumed(1);
3666 }
3667 }
3668 BlockDetectionResult::No => unreachable!(),
3669 }
3670
3671 if !matches!(block_match.detection, BlockDetectionResult::No) {
3672 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3673 self.close_containers_to_fenced_div();
3674 }
3675
3676 if matches!(block_match.effect, BlockEffect::CloseMystDirective) {
3677 self.close_containers_to_myst_directive();
3678 }
3679
3680 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3681 self.close_open_footnote_definition();
3682 }
3683
3684 let lines_consumed = {
3685 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3686 self.block_registry.parse_prepared(
3687 block_match,
3688 &dispatcher_ctx,
3689 &mut self.builder,
3690 &stripped,
3691 )
3692 };
3693
3694 let extras = match block_match.effect {
3695 BlockEffect::None => 0,
3696 BlockEffect::OpenFencedDiv => {
3697 self.containers.push(Container::FencedDiv {});
3698 0
3699 }
3700 BlockEffect::CloseFencedDiv => {
3701 self.close_fenced_div();
3702 0
3703 }
3704 BlockEffect::OpenMystDirective => {
3705 self.push_myst_directive_container(block_match);
3706 0
3707 }
3708 BlockEffect::CloseMystDirective => {
3709 self.close_myst_directive();
3710 0
3711 }
3712 BlockEffect::OpenAdmonition => {
3713 self.containers
3714 .push(Container::Admonition { content_col: 4 });
3715 0
3716 }
3717 BlockEffect::OpenFootnoteDefinition => {
3718 self.handle_footnote_open_effect(block_match, content)
3719 }
3720 BlockEffect::OpenList => {
3721 self.handle_list_open_effect(block_match, content, indent_to_emit)
3722 }
3723 BlockEffect::OpenDefinitionList => {
3724 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3725 }
3726 BlockEffect::OpenBlockQuote => {
3727 0
3729 }
3730 };
3731
3732 if lines_consumed == 0 {
3733 log::warn!(
3734 "block parser made no progress at line {} (parser={})",
3735 self.pos + 1,
3736 self.block_registry.parser_name(block_match)
3737 );
3738 return LineDispatch::Rejected;
3739 }
3740
3741 return LineDispatch::consumed(lines_consumed + extras);
3742 }
3743 }
3744
3745 if self.config.extensions.line_blocks
3747 && (has_blank_before || self.pos == 0)
3748 && try_parse_line_block_start(content).is_some()
3749 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3753 {
3754 log::trace!("Parsed line block at line {}", self.pos);
3755 self.close_paragraph_if_open();
3757
3758 let prefix = ContainerPrefix::default();
3764 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3765 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3766 if new_pos > self.pos {
3767 return LineDispatch::consumed(new_pos - self.pos);
3768 }
3769 }
3770
3771 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3774 log::trace!(
3775 "Inside ListItem - buffering content: {:?}",
3776 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3777 );
3778 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3780
3781 if let Some(Container::ListItem {
3783 buffer,
3784 marker_only,
3785 ..
3786 }) = self.containers.stack.last_mut()
3787 {
3788 buffer.push_text(line);
3789 if !is_blank_line(line) {
3790 *marker_only = false;
3791 }
3792 }
3793
3794 return LineDispatch::consumed(1);
3795 }
3796
3797 log::trace!(
3798 "Not in ListItem - creating paragraph for: {:?}",
3799 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3800 );
3801 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3803 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3806 paragraphs::append_paragraph_line(
3807 &mut self.containers,
3808 &mut self.builder,
3809 line,
3810 self.config,
3811 );
3812 LineDispatch::consumed(1)
3813 }
3814
3815 fn fenced_div_container_index(&self) -> Option<usize> {
3816 self.containers
3817 .stack
3818 .iter()
3819 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3820 }
3821
3822 fn close_containers_to_fenced_div(&mut self) {
3823 if let Some(index) = self.fenced_div_container_index() {
3824 self.close_containers_to(index + 1);
3825 }
3826 }
3827
3828 fn close_fenced_div(&mut self) {
3829 if let Some(index) = self.fenced_div_container_index() {
3830 self.close_containers_to(index);
3831 }
3832 }
3833
3834 fn in_fenced_div(&self) -> bool {
3835 self.containers
3836 .stack
3837 .iter()
3838 .any(|c| matches!(c, Container::FencedDiv { .. }))
3839 }
3840
3841 fn myst_directive_container_index(&self) -> Option<usize> {
3842 self.containers
3843 .stack
3844 .iter()
3845 .rposition(|c| matches!(c, Container::MystDirective { .. }))
3846 }
3847
3848 fn close_containers_to_myst_directive(&mut self) {
3851 if let Some(index) = self.myst_directive_container_index() {
3852 self.close_containers_to(index + 1);
3853 }
3854 }
3855
3856 fn close_myst_directive(&mut self) {
3858 if let Some(index) = self.myst_directive_container_index() {
3859 self.close_containers_to(index);
3860 }
3861 }
3862
3863 fn push_myst_directive_container(&mut self, block_match: &PreparedBlockMatch) {
3867 use crate::parser::blocks::myst_directives::DirectiveOpen;
3868 let open = block_match
3869 .payload
3870 .as_ref()
3871 .and_then(|p| p.downcast_ref::<DirectiveOpen>());
3872 if open.is_some_and(|open| open.is_verbatim) {
3876 return;
3877 }
3878 let (fence_char, fence_count) = open
3879 .map(|open| (open.fence_char, open.fence_count))
3880 .unwrap_or((b'`', 3));
3881 self.containers.push(Container::MystDirective {
3882 fence_char,
3883 fence_count,
3884 });
3885 }
3886
3887 fn innermost_myst_directive_closer(&self) -> Option<(u8, usize)> {
3890 self.containers.stack.iter().rev().find_map(|c| match c {
3891 Container::MystDirective {
3892 fence_char,
3893 fence_count,
3894 } => Some((*fence_char, *fence_count)),
3895 _ => None,
3896 })
3897 }
3898
3899 fn in_footnote_definition(&self) -> bool {
3907 self.containers
3908 .stack
3909 .iter()
3910 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3911 }
3912}
3913
3914fn try_parse_any_table_kind(
3926 window: &StrippedLines,
3927 builder: &mut GreenNodeBuilder<'static>,
3928 config: &ParserOptions,
3929) -> Option<usize> {
3930 let mut consumed = None;
3931 if config.extensions.grid_tables {
3932 consumed = tables::try_parse_grid_table(window, builder, config);
3933 }
3934 if consumed.is_none() && config.extensions.multiline_tables {
3935 consumed = tables::try_parse_multiline_table(window, builder, config);
3936 }
3937 if consumed.is_none() && config.extensions.pipe_tables {
3938 consumed = tables::try_parse_pipe_table(window, builder, config);
3939 }
3940 if consumed.is_none() && config.extensions.simple_tables {
3941 consumed = tables::try_parse_simple_table(window, builder, config);
3942 }
3943 consumed
3944}
3945
3946fn emit_definition_plain_or_heading(
3947 builder: &mut GreenNodeBuilder<'static>,
3948 text: &str,
3949 config: &ParserOptions,
3950 suppress_footnote_refs: bool,
3951) {
3952 let line_without_newline = text
3953 .strip_suffix("\r\n")
3954 .or_else(|| text.strip_suffix('\n'));
3955 if let Some(line) = line_without_newline
3956 && !line.contains('\n')
3957 && !line.contains('\r')
3958 && let Some(level) = try_parse_atx_heading(line)
3959 {
3960 emit_atx_heading(builder, text, level, config);
3961 return;
3962 }
3963
3964 if let Some(first_nl) = text.find('\n') {
3966 let first_line = &text[..first_nl];
3967 let after_first = &text[first_nl + 1..];
3968 if !after_first.is_empty()
3969 && let Some(level) = try_parse_atx_heading(first_line)
3970 {
3971 let heading_bytes = &text[..first_nl + 1];
3972 emit_atx_heading(builder, heading_bytes, level, config);
3973 builder.start_node(SyntaxKind::PLAIN.into());
3974 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3975 builder.finish_node();
3976 return;
3977 }
3978 }
3979
3980 builder.start_node(SyntaxKind::PLAIN.into());
3981 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3982 builder.finish_node();
3983}
3984
3985fn footnote_first_line_term_lookahead(
3994 lines: &[&str],
3995 pos: usize,
3996 content_col: usize,
3997 table_captions_enabled: bool,
3998) -> Option<usize> {
3999 let mut check_pos = pos + 1;
4000 let mut blank_count = 0;
4001 while check_pos < lines.len() {
4002 let line = lines[check_pos];
4003 let (trimmed, _) = strip_newline(line);
4004 if trimmed.trim().is_empty() {
4005 blank_count += 1;
4006 check_pos += 1;
4007 continue;
4008 }
4009 let (line_indent_cols, _) = leading_indent(trimmed);
4010 if line_indent_cols < content_col {
4011 return None;
4012 }
4013 let strip_bytes = byte_index_at_column(trimmed, content_col);
4014 if strip_bytes > trimmed.len() {
4015 return None;
4016 }
4017 let stripped = &trimmed[strip_bytes..];
4018 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
4019 if marker == ':'
4025 && table_captions_enabled
4026 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
4027 {
4028 return None;
4029 }
4030 return Some(blank_count);
4031 }
4032 return None;
4033 }
4034 None
4035}