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_indented_code_in_new_list_item(&mut self) {
615 let Some(Container::ListItem {
616 content_col,
617 buffer,
618 marker_only,
619 virtual_marker_space,
620 }) = self.containers.stack.last()
621 else {
622 return;
623 };
624 if *marker_only {
625 return;
626 }
627 if buffer.segment_count() != 1 {
628 return;
629 }
630 let Some(text) = buffer.first_text() else {
631 return;
632 };
633 let content_col = *content_col;
634 let virtual_marker_space = *virtual_marker_space;
635 let text_owned = text.to_string();
636
637 let mut iter = text_owned.split_inclusive('\n');
639 let line_with_nl = iter.next().unwrap_or("").to_string();
640 if iter.next().is_some() {
641 return;
642 }
643
644 let line_no_nl = line_with_nl
645 .strip_suffix("\r\n")
646 .or_else(|| line_with_nl.strip_suffix('\n'))
647 .unwrap_or(&line_with_nl);
648 let nl_suffix = &line_with_nl[line_no_nl.len()..];
649
650 let buffer_start_col = if virtual_marker_space {
651 content_col.saturating_sub(1)
652 } else {
653 content_col
654 };
655
656 let target = content_col + 4;
657 let (cols_walked, ws_bytes) =
658 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
659
660 if buffer_start_col + cols_walked < target {
661 return;
662 }
663 if ws_bytes >= line_no_nl.len() {
664 return;
665 }
666
667 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
668 buffer.clear();
669 }
670
671 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
672 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
673 if ws_bytes > 0 {
674 self.builder
675 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
676 }
677 let rest = &line_no_nl[ws_bytes..];
678 if !rest.is_empty() {
679 self.builder.token(SyntaxKind::TEXT.into(), rest);
680 }
681 if !nl_suffix.is_empty() {
682 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
683 }
684 self.builder.finish_node();
685 self.builder.finish_node();
686 }
687
688 fn has_matching_fence_closer(
689 &self,
690 fence: &code_blocks::FenceInfo,
691 bq_depth: usize,
692 content_col: usize,
693 ) -> bool {
694 for raw_line in self.lines.iter().skip(self.pos + 1) {
695 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
696 if line_bq_depth < bq_depth {
697 break;
698 }
699 let candidate = if content_col > 0 && !inner.is_empty() {
700 let idx = byte_index_at_column(inner, content_col);
701 if idx <= inner.len() {
702 &inner[idx..]
703 } else {
704 inner
705 }
706 } else {
707 inner
708 };
709 if code_blocks::is_closing_fence(candidate, fence) {
710 return true;
711 }
712 }
713 false
714 }
715
716 fn is_paragraph_open(&self) -> bool {
718 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
719 }
720
721 fn emit_setext_heading_folding_paragraph(
729 &mut self,
730 text_line: &str,
731 underline_line: &str,
732 level: usize,
733 ) {
734 let (buffered_text, checkpoint) = match self.containers.stack.last() {
735 Some(Container::Paragraph {
736 buffer,
737 start_checkpoint,
738 ..
739 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
740 _ => (String::new(), None),
741 };
742
743 if checkpoint.is_some() {
744 self.containers.stack.pop();
745 }
746
747 let combined_text = if buffered_text.is_empty() {
748 text_line.to_string()
749 } else {
750 format!("{}{}", buffered_text, text_line)
751 };
752
753 let cp = checkpoint.expect(
754 "emit_setext_heading_folding_paragraph requires an open paragraph; \
755 single-line setext should go through the regular dispatcher path",
756 );
757 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
758 emit_setext_heading_body(
759 &mut self.builder,
760 &combined_text,
761 underline_line,
762 level,
763 self.config,
764 );
765 self.builder.finish_node();
766 }
767
768 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
786 let Some(Container::ListItem {
787 buffer,
788 content_col,
789 ..
790 }) = self.containers.stack.last()
791 else {
792 return None;
793 };
794 if buffer.segment_count() != 1 {
795 return None;
796 }
797 let text_line = buffer.first_text()?;
798
799 let content_col = *content_col;
804 let (underline_indent_cols, _) = leading_indent(content);
805 if underline_indent_cols < content_col {
806 return None;
807 }
808
809 let lines = [text_line, content];
810 let (level, _) = try_parse_setext_heading(&lines, 0)?;
811
812 let (text_no_newline, _) = strip_newline(text_line);
813 if text_no_newline.trim().is_empty() {
814 return None;
815 }
816 if try_parse_horizontal_rule(text_no_newline).is_some() {
817 return None;
818 }
819
820 let text_owned = text_line.to_string();
821 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
822 buffer.clear();
823 }
824 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
825 Some(LineDispatch::consumed(1))
826 }
827
828 fn close_paragraph_if_open(&mut self) {
830 if self.is_paragraph_open() {
831 self.close_containers_to(self.containers.depth() - 1);
832 }
833 }
834
835 fn close_paragraph_as_plain_if_open(&mut self) {
846 if !self.is_paragraph_open() {
847 return;
848 }
849 let Some(Container::Paragraph {
850 buffer,
851 start_checkpoint,
852 ..
853 }) = self.containers.stack.last()
854 else {
855 return;
856 };
857 let buffer_clone = buffer.clone();
858 let checkpoint = *start_checkpoint;
859 let suppress_footnote_refs = self.in_footnote_definition();
860 self.containers.stack.pop();
861 self.builder
862 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
863 if !buffer_clone.is_empty() {
864 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
865 }
866 self.builder.finish_node();
867 }
868
869 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
878 if self.config.dialect != crate::options::Dialect::Pandoc {
879 return false;
880 }
881 if self.block_registry.parser_name(block_match) != "html_block" {
882 return false;
883 }
884 let html_block_type = block_match
885 .payload
886 .as_ref()
887 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
888 matches!(
889 html_block_type,
890 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
891 )
892 }
893
894 fn prepare_for_block_element(&mut self) {
897 self.emit_list_item_buffer_if_needed();
898 self.close_paragraph_if_open();
899 }
900
901 fn close_open_footnote_definition(&mut self) {
905 while matches!(
906 self.containers.last(),
907 Some(Container::FootnoteDefinition { .. })
908 ) {
909 self.close_containers_to(self.containers.depth() - 1);
910 }
911 }
912
913 fn handle_footnote_open_effect(
917 &mut self,
918 block_match: &super::block_dispatcher::PreparedBlockMatch,
919 content: &str,
920 ) -> usize {
921 let content_start = block_match
922 .payload
923 .as_ref()
924 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
925 .map(|p| p.content_start)
926 .unwrap_or(0);
927
928 let content_col = 4;
929 self.containers
930 .push(Container::FootnoteDefinition { content_col });
931
932 if content_start == 0 {
933 return 0;
934 }
935 let first_line_content = &content[content_start..];
936 if first_line_content.trim().is_empty() {
937 let (_, newline_str) = strip_newline(content);
938 if !newline_str.is_empty() {
939 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
940 }
941 return 0;
942 }
943
944 if self.config.extensions.definition_lists
945 && let Some(blank_count) = footnote_first_line_term_lookahead(
946 &self.lines,
947 self.pos,
948 content_col,
949 self.config.extensions.table_captions,
950 )
951 {
952 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
953 self.containers.push(Container::DefinitionList {});
954 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
955 self.containers.push(Container::DefinitionItem {});
956 emit_term(&mut self.builder, first_line_content, self.config);
957 for i in 0..blank_count {
958 let blank_pos = self.pos + 1 + i;
959 if blank_pos < self.lines.len() {
960 let blank_line = self.lines[blank_pos];
961 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
962 self.builder
963 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
964 self.builder.finish_node();
965 }
966 }
967 return blank_count;
968 }
969
970 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
971 paragraphs::append_paragraph_line(
972 &mut self.containers,
973 &mut self.builder,
974 first_line_content,
975 self.config,
976 );
977 0
978 }
979
980 fn try_lazy_list_continuation(
992 &mut self,
993 block_match: &super::block_dispatcher::PreparedBlockMatch,
994 content: &str,
995 ) -> bool {
996 use super::block_dispatcher::ListPrepared;
997
998 let Some(prepared) = block_match
999 .payload
1000 .as_ref()
1001 .and_then(|p| p.downcast_ref::<ListPrepared>())
1002 else {
1003 return false;
1004 };
1005
1006 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
1007 return false;
1008 }
1009
1010 let current_content_col = paragraphs::current_content_col(&self.containers);
1017 if prepared.indent_cols >= current_content_col
1018 && prepared.indent_cols < current_content_col + 4
1019 {
1020 return false;
1021 }
1022
1023 if lists::find_matching_list_level(
1024 &self.containers,
1025 &prepared.marker,
1026 prepared.indent_cols,
1027 self.config.dialect,
1028 )
1029 .is_some()
1030 {
1031 return false;
1032 }
1033
1034 match self.containers.last() {
1035 Some(Container::Paragraph { .. }) => {
1036 paragraphs::append_paragraph_line(
1037 &mut self.containers,
1038 &mut self.builder,
1039 content,
1040 self.config,
1041 );
1042 true
1043 }
1044 Some(Container::ListItem { .. }) => {
1045 if let Some(Container::ListItem {
1046 buffer,
1047 marker_only,
1048 ..
1049 }) = self.containers.stack.last_mut()
1050 {
1051 buffer.push_text(content);
1052 if !content.trim().is_empty() {
1053 *marker_only = false;
1054 }
1055 }
1056 true
1057 }
1058 _ => false,
1059 }
1060 }
1061
1062 fn handle_list_open_effect(
1068 &mut self,
1069 block_match: &super::block_dispatcher::PreparedBlockMatch,
1070 content: &str,
1071 indent_to_emit: Option<&str>,
1072 ) -> usize {
1073 use super::block_dispatcher::ListPrepared;
1074
1075 let prepared = block_match
1076 .payload
1077 .as_ref()
1078 .and_then(|p| p.downcast_ref::<ListPrepared>());
1079 let Some(prepared) = prepared else {
1080 return 0;
1081 };
1082
1083 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
1084 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1085 paragraphs::append_paragraph_line(
1086 &mut self.containers,
1087 &mut self.builder,
1088 content,
1089 self.config,
1090 );
1091 return 0;
1092 }
1093
1094 if self.is_paragraph_open() {
1095 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
1096 paragraphs::append_paragraph_line(
1097 &mut self.containers,
1098 &mut self.builder,
1099 content,
1100 self.config,
1101 );
1102 return 0;
1103 }
1104 self.close_containers_to(self.containers.depth() - 1);
1105 }
1106
1107 if matches!(
1108 self.containers.last(),
1109 Some(Container::Definition {
1110 plain_open: true,
1111 ..
1112 })
1113 ) {
1114 self.emit_buffered_plain_if_needed();
1115 }
1116
1117 let matched_level = lists::find_matching_list_level(
1118 &self.containers,
1119 &prepared.marker,
1120 prepared.indent_cols,
1121 self.config.dialect,
1122 );
1123 let list_item = ListItemEmissionInput {
1124 content,
1125 marker_len: prepared.marker_len,
1126 spaces_after_cols: prepared.spaces_after_cols,
1127 spaces_after_bytes: prepared.spaces_after,
1128 indent_cols: prepared.indent_cols,
1129 indent_bytes: prepared.indent_bytes,
1130 virtual_marker_space: prepared.virtual_marker_space,
1131 };
1132 let current_content_col = paragraphs::current_content_col(&self.containers);
1133 let deep_ordered_matched_level = matched_level
1134 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1135 .and_then(|(level, container)| match container {
1136 Container::List {
1137 marker: list_marker,
1138 base_indent_cols,
1139 ..
1140 } if matches!(
1141 (&prepared.marker, list_marker),
1142 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1143 ) && prepared.indent_cols >= 4
1144 && *base_indent_cols >= 4
1145 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1146 {
1147 Some(level)
1148 }
1149 _ => None,
1150 });
1151
1152 if deep_ordered_matched_level.is_none()
1153 && current_content_col > 0
1154 && prepared.indent_cols >= current_content_col
1155 {
1156 if let Some(level) = matched_level
1157 && let Some(Container::List {
1158 base_indent_cols, ..
1159 }) = self.containers.stack.get(level)
1160 && prepared.indent_cols == *base_indent_cols
1161 {
1162 let num_parent_lists = self.containers.stack[..level]
1163 .iter()
1164 .filter(|c| matches!(c, Container::List { .. }))
1165 .count();
1166
1167 if num_parent_lists > 0 {
1168 self.close_containers_to(level + 1);
1169
1170 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1171 self.close_containers_to(self.containers.depth() - 1);
1172 }
1173 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1174 self.close_containers_to(self.containers.depth() - 1);
1175 }
1176
1177 if let Some(indent_str) = indent_to_emit {
1178 self.builder
1179 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1180 }
1181
1182 let finish = if let Some(nested_marker) = prepared.nested_marker {
1183 lists::add_list_item_with_nested_empty_list(
1184 &mut self.containers,
1185 &mut self.builder,
1186 &list_item,
1187 nested_marker,
1188 self.config,
1189 );
1190 lists::ListItemFinish::Done
1191 } else {
1192 lists::add_list_item(
1193 &mut self.containers,
1194 &mut self.builder,
1195 &list_item,
1196 self.config,
1197 )
1198 };
1199 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1200 return extras;
1201 }
1202 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1203 return extras;
1204 }
1205 self.maybe_open_indented_code_in_new_list_item();
1206 return self.dispatch_bq_after_list_item(finish);
1207 }
1208 }
1209
1210 self.emit_list_item_buffer_if_needed();
1211
1212 let finish = start_nested_list(
1213 &mut self.containers,
1214 &mut self.builder,
1215 &prepared.marker,
1216 &list_item,
1217 indent_to_emit,
1218 self.config,
1219 );
1220 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1221 return extras;
1222 }
1223 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1224 return extras;
1225 }
1226 self.maybe_open_indented_code_in_new_list_item();
1227 return self.dispatch_bq_after_list_item(finish);
1228 }
1229
1230 if let Some(level) = matched_level {
1231 self.close_containers_to(level + 1);
1232
1233 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1234 self.close_containers_to(self.containers.depth() - 1);
1235 }
1236 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1237 self.close_containers_to(self.containers.depth() - 1);
1238 }
1239
1240 if let Some(indent_str) = indent_to_emit {
1241 self.builder
1242 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1243 }
1244
1245 let finish = if let Some(nested_marker) = prepared.nested_marker {
1246 lists::add_list_item_with_nested_empty_list(
1247 &mut self.containers,
1248 &mut self.builder,
1249 &list_item,
1250 nested_marker,
1251 self.config,
1252 );
1253 lists::ListItemFinish::Done
1254 } else {
1255 lists::add_list_item(
1256 &mut self.containers,
1257 &mut self.builder,
1258 &list_item,
1259 self.config,
1260 )
1261 };
1262 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1263 return extras;
1264 }
1265 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1266 return extras;
1267 }
1268 self.maybe_open_indented_code_in_new_list_item();
1269 return self.dispatch_bq_after_list_item(finish);
1270 }
1271
1272 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1273 self.close_containers_to(self.containers.depth() - 1);
1274 }
1275 while matches!(
1276 self.containers.last(),
1277 Some(Container::ListItem { .. } | Container::List { .. })
1278 ) {
1279 self.close_containers_to(self.containers.depth() - 1);
1280 }
1281
1282 self.builder.start_node(SyntaxKind::LIST.into());
1283 if let Some(indent_str) = indent_to_emit {
1284 self.builder
1285 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1286 }
1287 self.containers.push(Container::List {
1288 marker: prepared.marker.clone(),
1289 base_indent_cols: prepared.indent_cols,
1290 has_blank_between_items: false,
1291 });
1292
1293 let finish = if let Some(nested_marker) = prepared.nested_marker {
1294 lists::add_list_item_with_nested_empty_list(
1295 &mut self.containers,
1296 &mut self.builder,
1297 &list_item,
1298 nested_marker,
1299 self.config,
1300 );
1301 lists::ListItemFinish::Done
1302 } else {
1303 lists::add_list_item(
1304 &mut self.containers,
1305 &mut self.builder,
1306 &list_item,
1307 self.config,
1308 )
1309 };
1310 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1311 return extras;
1312 }
1313 if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
1314 return extras;
1315 }
1316 self.maybe_open_indented_code_in_new_list_item();
1317 self.dispatch_bq_after_list_item(finish)
1318 }
1319
1320 fn handle_definition_list_effect(
1327 &mut self,
1328 block_match: &super::block_dispatcher::PreparedBlockMatch,
1329 content: &str,
1330 indent_to_emit: Option<&str>,
1331 ) -> usize {
1332 use super::block_dispatcher::DefinitionPrepared;
1333
1334 let prepared = block_match
1335 .payload
1336 .as_ref()
1337 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1338 let Some(prepared) = prepared else {
1339 return 0;
1340 };
1341
1342 let mut extras: usize = 0;
1343 match prepared {
1344 DefinitionPrepared::Definition {
1345 marker_char,
1346 indent,
1347 spaces_after,
1348 spaces_after_cols,
1349 has_content,
1350 } => {
1351 self.emit_buffered_plain_if_needed();
1352
1353 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1354 self.close_containers_to(self.containers.depth() - 1);
1355 }
1356 while matches!(self.containers.last(), Some(Container::List { .. })) {
1357 self.close_containers_to(self.containers.depth() - 1);
1358 }
1359
1360 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1361 self.close_containers_to(self.containers.depth() - 1);
1362 }
1363
1364 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1365 self.close_containers_to(self.containers.depth() - 1);
1366 }
1367
1368 if definition_lists::in_definition_list(&self.containers)
1372 && !matches!(
1373 self.containers.last(),
1374 Some(Container::DefinitionItem { .. })
1375 )
1376 {
1377 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1378 self.containers.push(Container::DefinitionItem {});
1379 }
1380
1381 if !definition_lists::in_definition_list(&self.containers) {
1382 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1383 self.containers.push(Container::DefinitionList {});
1384 }
1385
1386 if !matches!(
1387 self.containers.last(),
1388 Some(Container::DefinitionItem { .. })
1389 ) {
1390 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1391 self.containers.push(Container::DefinitionItem {});
1392 }
1393
1394 self.builder.start_node(SyntaxKind::DEFINITION.into());
1395
1396 if let Some(indent_str) = indent_to_emit {
1397 self.builder
1398 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1399 }
1400
1401 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1402 let indent_bytes = byte_index_at_column(content, *indent);
1403 if *spaces_after > 0 {
1404 let space_start = indent_bytes + 1;
1405 let space_end = space_start + *spaces_after;
1406 if space_end <= content.len() {
1407 self.builder.token(
1408 SyntaxKind::WHITESPACE.into(),
1409 &content[space_start..space_end],
1410 );
1411 }
1412 }
1413
1414 if !*has_content {
1415 let current_line = self.lines[self.pos];
1416 let (_, newline_str) = strip_newline(current_line);
1417 if !newline_str.is_empty() {
1418 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1419 }
1420 }
1421
1422 let content_col = *indent + 1 + *spaces_after_cols;
1423 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1424 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1425 let mut plain_buffer = TextBuffer::new();
1426 let mut definition_pushed = false;
1427
1428 if *has_content {
1429 let current_line = self.lines[self.pos];
1430 let (trimmed_content, _) = strip_newline(content);
1431
1432 let content_start = content_start_bytes.min(trimmed_content.len());
1439 let content_slice = &trimmed_content[content_start..];
1440 let content_line = &content[content_start_bytes.min(content.len())..];
1441
1442 let (blockquote_depth, inner_blockquote_content) =
1443 count_blockquote_markers(content_line);
1444
1445 let should_start_list_from_first_line = self
1446 .lines
1447 .get(self.pos + 1)
1448 .map(|next_line| {
1449 let (next_without_newline, _) = strip_newline(next_line);
1450 if next_without_newline.trim().is_empty() {
1451 return true;
1452 }
1453
1454 let (next_indent_cols, _) = leading_indent(next_without_newline);
1455 next_indent_cols >= content_col
1456 })
1457 .unwrap_or(true);
1458
1459 if blockquote_depth > 0 {
1460 self.containers.push(Container::Definition {
1461 content_col,
1462 plain_open: false,
1463 plain_buffer: TextBuffer::new(),
1464 });
1465 definition_pushed = true;
1466
1467 let marker_info = parse_blockquote_marker_info(content_line);
1468 for level in 0..blockquote_depth {
1469 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1470 if let Some(info) = marker_info.get(level) {
1471 blockquotes::emit_one_blockquote_marker(
1472 &mut self.builder,
1473 info.leading_spaces,
1474 info.has_trailing_space,
1475 );
1476 }
1477 self.containers.push(Container::BlockQuote {});
1478 }
1479
1480 if !inner_blockquote_content.trim().is_empty() {
1481 paragraphs::start_paragraph_if_needed(
1482 &mut self.containers,
1483 &mut self.builder,
1484 );
1485 paragraphs::append_paragraph_line(
1486 &mut self.containers,
1487 &mut self.builder,
1488 inner_blockquote_content,
1489 self.config,
1490 );
1491 }
1492 } else if let Some(marker_match) = try_parse_list_marker(
1493 content_slice,
1494 self.config,
1495 lists::open_list_hint_at_indent(
1496 &self.containers,
1497 leading_indent(content_slice).0,
1498 ),
1499 ) && should_start_list_from_first_line
1500 {
1501 self.containers.push(Container::Definition {
1502 content_col,
1503 plain_open: false,
1504 plain_buffer: TextBuffer::new(),
1505 });
1506 definition_pushed = true;
1507
1508 let (indent_cols, indent_bytes) = leading_indent(content_line);
1509 self.builder.start_node(SyntaxKind::LIST.into());
1510 self.containers.push(Container::List {
1511 marker: marker_match.marker.clone(),
1512 base_indent_cols: indent_cols,
1513 has_blank_between_items: false,
1514 });
1515
1516 let list_item = ListItemEmissionInput {
1517 content: content_line,
1518 marker_len: marker_match.marker_len,
1519 spaces_after_cols: marker_match.spaces_after_cols,
1520 spaces_after_bytes: marker_match.spaces_after_bytes,
1521 indent_cols,
1522 indent_bytes,
1523 virtual_marker_space: marker_match.virtual_marker_space,
1524 };
1525
1526 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1527 content_line,
1528 marker_match.marker_len,
1529 marker_match.spaces_after_bytes,
1530 ) {
1531 lists::add_list_item_with_nested_empty_list(
1532 &mut self.containers,
1533 &mut self.builder,
1534 &list_item,
1535 nested_marker,
1536 self.config,
1537 );
1538 lists::ListItemFinish::Done
1539 } else {
1540 lists::add_list_item(
1541 &mut self.containers,
1542 &mut self.builder,
1543 &list_item,
1544 self.config,
1545 )
1546 };
1547 extras = self.dispatch_bq_after_list_item(finish);
1548 } else if let Some(fence) =
1549 code_blocks::try_parse_fence_open(content_slice, self.config.dialect)
1550 {
1551 self.containers.push(Container::Definition {
1552 content_col,
1553 plain_open: false,
1554 plain_buffer: TextBuffer::new(),
1555 });
1556 definition_pushed = true;
1557
1558 let bq_depth = self.current_blockquote_depth();
1559 if let Some(indent_str) = indent_to_emit {
1560 self.builder
1561 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1562 }
1563 let fence_line = content[content_start..].to_string();
1564 let prefix = ContainerPrefix::from_scalars(
1568 bq_depth,
1569 0,
1570 bq_depth > 0,
1571 content_col,
1572 false,
1573 );
1574 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
1575 let new_pos = if self.config.extensions.tex_math_gfm
1576 && code_blocks::is_gfm_math_fence(&fence)
1577 {
1578 code_blocks::parse_fenced_math_block(
1579 &mut self.builder,
1580 &window,
1581 fence,
1582 Some(&fence_line),
1583 )
1584 } else {
1585 code_blocks::parse_fenced_code_block(
1586 &mut self.builder,
1587 &window,
1588 fence,
1589 Some(&fence_line),
1590 &self.diagnostics,
1591 self.config.flavor,
1592 )
1593 };
1594 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1595 } else {
1596 let (_, newline_str) = strip_newline(current_line);
1597 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1598 if content_without_newline.is_empty() {
1599 plain_buffer.push_line(newline_str);
1600 } else {
1601 let line_with_newline = if !newline_str.is_empty() {
1602 format!("{}{}", content_without_newline, newline_str)
1603 } else {
1604 content_without_newline.to_string()
1605 };
1606 plain_buffer.push_line(line_with_newline);
1607 }
1608 }
1609 }
1610
1611 if !definition_pushed {
1612 self.containers.push(Container::Definition {
1613 content_col,
1614 plain_open: *has_content,
1615 plain_buffer,
1616 });
1617 }
1618 }
1619 DefinitionPrepared::Term { blank_count } => {
1620 self.emit_buffered_plain_if_needed();
1621
1622 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1623 self.close_containers_to(self.containers.depth() - 1);
1624 }
1625
1626 if !definition_lists::in_definition_list(&self.containers) {
1627 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1628 self.containers.push(Container::DefinitionList {});
1629 }
1630
1631 while matches!(
1632 self.containers.last(),
1633 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1634 ) {
1635 self.close_containers_to(self.containers.depth() - 1);
1636 }
1637
1638 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1639 self.containers.push(Container::DefinitionItem {});
1640
1641 emit_term(&mut self.builder, content, self.config);
1642
1643 for i in 0..*blank_count {
1644 let blank_pos = self.pos + 1 + i;
1645 if blank_pos < self.lines.len() {
1646 let blank_line = self.lines[blank_pos];
1647 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1648 self.builder
1649 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1650 self.builder.finish_node();
1651 }
1652 }
1653 extras = *blank_count;
1654 }
1655 };
1656 extras
1657 }
1658
1659 fn blockquote_marker_info(
1661 &self,
1662 payload: Option<&BlockQuotePrepared>,
1663 line: &str,
1664 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1665 payload
1666 .map(|payload| payload.marker_info.clone())
1667 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1668 }
1669
1670 fn marker_info_for_line(
1676 &self,
1677 payload: Option<&BlockQuotePrepared>,
1678 raw_line: &str,
1679 marker_line: &str,
1680 shifted_prefix: &str,
1681 used_shifted: bool,
1682 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1683 let mut marker_info = if used_shifted {
1684 parse_blockquote_marker_info(marker_line)
1685 } else {
1686 self.blockquote_marker_info(payload, raw_line)
1687 };
1688 if used_shifted && !shifted_prefix.is_empty() {
1689 let (prefix_cols, _) = leading_indent(shifted_prefix);
1690 if let Some(first) = marker_info.first_mut() {
1691 first.leading_spaces += prefix_cols;
1692 }
1693 }
1694 marker_info
1695 }
1696
1697 fn shifted_blockquote_from_list<'b>(
1700 &self,
1701 line: &'b str,
1702 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1703 let list_content_col = self
1712 .containers
1713 .stack
1714 .iter()
1715 .rev()
1716 .find_map(|c| match c {
1717 Container::ListItem { content_col, .. } => Some(*content_col),
1718 _ => None,
1719 })
1720 .unwrap_or(0);
1721 let content_container_indent = self.content_container_indent_to_strip();
1722 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1730 return None;
1731 }
1732 let marker_col = list_content_col.saturating_add(content_container_indent);
1733 if marker_col == 0 {
1734 return None;
1735 }
1736
1737 let (indent_cols, _) = leading_indent(line);
1738 if indent_cols < marker_col {
1739 return None;
1740 }
1741
1742 let idx = byte_index_at_column(line, marker_col);
1743 if idx > line.len() {
1744 return None;
1745 }
1746
1747 let candidate = &line[idx..];
1748 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1749 if candidate_depth == 0 {
1750 return None;
1751 }
1752
1753 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1754 }
1755
1756 fn emit_blockquote_markers(
1757 &mut self,
1758 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1759 depth: usize,
1760 ) {
1761 for i in 0..depth {
1762 if let Some(info) = marker_info.get(i) {
1763 blockquotes::emit_one_blockquote_marker(
1764 &mut self.builder,
1765 info.leading_spaces,
1766 info.has_trailing_space,
1767 );
1768 }
1769 }
1770 }
1771
1772 fn current_blockquote_depth(&self) -> usize {
1773 blockquotes::current_blockquote_depth(&self.containers)
1774 }
1775
1776 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1784 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1785 return None;
1786 };
1787 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1788 }
1789
1790 fn emit_or_buffer_blockquote_marker(
1795 &mut self,
1796 leading_spaces: usize,
1797 has_trailing_space: bool,
1798 ) {
1799 if let Some(Container::ListItem {
1800 buffer,
1801 marker_only,
1802 ..
1803 }) = self.containers.stack.last_mut()
1804 {
1805 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1806 *marker_only = false;
1807 return;
1808 }
1809
1810 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1812 paragraphs::append_paragraph_marker(
1814 &mut self.containers,
1815 leading_spaces,
1816 has_trailing_space,
1817 );
1818 } else {
1819 blockquotes::emit_one_blockquote_marker(
1821 &mut self.builder,
1822 leading_spaces,
1823 has_trailing_space,
1824 );
1825 }
1826 }
1827
1828 fn parse_document_stack(&mut self) {
1829 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1830
1831 log::trace!("Starting document parse");
1832
1833 while self.pos < self.lines.len() {
1836 let line = self.lines[self.pos];
1837
1838 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1839
1840 match self.parse_line(line) {
1841 LineDispatch::Consumed(n) => self.pos += n,
1842 LineDispatch::Rejected => self.pos += 1,
1843 }
1844 }
1845
1846 self.close_containers_to(0);
1847 self.builder.finish_node(); }
1849
1850 fn parse_line(&mut self, line: &str) -> LineDispatch {
1854 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1857 let mut bq_marker_line = line;
1858 let mut shifted_bq_prefix = "";
1859 let mut used_shifted_bq = false;
1860 if bq_depth == 0
1861 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1862 self.shifted_blockquote_from_list(line)
1863 {
1864 bq_depth = candidate_depth;
1865 inner_content = candidate_inner;
1866 bq_marker_line = candidate_line;
1867 shifted_bq_prefix = candidate_prefix;
1868 used_shifted_bq = true;
1869 }
1870 let current_bq_depth = self.current_blockquote_depth();
1871
1872 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1873 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1874 let dispatcher_ctx = if current_bq_depth == 0 {
1875 Some(BlockContext {
1876 has_blank_before,
1877 has_blank_before_strict: has_blank_before,
1878 at_document_start: self.pos == 0,
1879 in_fenced_div: self.in_fenced_div(),
1880 blockquote_depth: current_bq_depth,
1881 config: self.config,
1882 diags: self.diagnostics.clone(),
1883 content_indent: 0,
1884 indent_to_emit: None,
1885 list_indent_info: None,
1886 in_list: lists::in_list(&self.containers),
1887 in_marker_only_list_item: matches!(
1888 self.containers.last(),
1889 Some(Container::ListItem {
1890 marker_only: true,
1891 ..
1892 })
1893 ),
1894 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1895 paragraph_open: self.is_paragraph_open(),
1896 next_line: if self.pos + 1 < self.lines.len() {
1897 Some(self.lines[self.pos + 1])
1898 } else {
1899 None
1900 },
1901 open_alpha_hint: lists::open_list_hint_at_indent(
1902 &self.containers,
1903 leading_indent(line).0,
1904 ),
1905 })
1906 } else {
1907 None
1908 };
1909
1910 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1911 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
1912 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
1913 self.block_registry
1914 .detect_prepared(dispatcher_ctx, &stripped)
1915 .and_then(|prepared| {
1916 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1917 blockquote_match = Some(prepared);
1918 blockquote_match.as_ref().and_then(|prepared| {
1919 prepared
1920 .payload
1921 .as_ref()
1922 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1923 .cloned()
1924 })
1925 } else {
1926 None
1927 }
1928 })
1929 } else {
1930 None
1931 };
1932
1933 log::trace!(
1934 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1935 self.pos,
1936 bq_depth,
1937 current_bq_depth,
1938 self.containers.depth(),
1939 line.trim_end()
1940 );
1941
1942 let inner_blank_in_blockquote = bq_depth > 0
1949 && is_blank_line(inner_content)
1950 && (current_bq_depth > 0
1951 || !self.config.extensions.blank_before_blockquote
1952 || blockquotes::can_start_blockquote(
1953 self.pos,
1954 &self.lines,
1955 self.config.extensions.fenced_divs,
1956 ));
1957 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1958
1959 if is_blank {
1960 if self.is_paragraph_open()
1961 && paragraphs::has_open_inline_math_environment(&self.containers)
1962 {
1963 paragraphs::append_paragraph_line(
1964 &mut self.containers,
1965 &mut self.builder,
1966 line,
1967 self.config,
1968 );
1969 return LineDispatch::consumed(1);
1970 }
1971
1972 self.close_paragraph_if_open();
1974
1975 self.emit_buffered_plain_if_needed();
1979
1980 if bq_depth > current_bq_depth {
1988 for _ in current_bq_depth..bq_depth {
1990 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1991 self.containers.push(Container::BlockQuote {});
1992 }
1993 } else if bq_depth < current_bq_depth {
1994 self.close_blockquotes_to_depth(bq_depth);
1996 }
1997
1998 let mut peek = self.pos + 1;
2005 while peek < self.lines.len() {
2006 let peek_line = self.lines[peek];
2007 if is_blank_line(peek_line) {
2008 peek += 1;
2009 continue;
2010 }
2011 if bq_depth > 0 {
2012 let (peek_bq, _) = count_blockquote_markers(peek_line);
2013 if peek_bq >= bq_depth {
2014 let peek_inner =
2015 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
2016 if is_blank_line(peek_inner) {
2017 peek += 1;
2018 continue;
2019 }
2020 }
2021 }
2022 break;
2023 }
2024
2025 let levels_to_keep = if peek < self.lines.len() {
2027 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
2028 self.current_blockquote_depth(),
2029 &self.containers,
2030 &self.lines,
2031 peek,
2032 self.lines[peek],
2033 )
2034 } else {
2035 0
2036 };
2037 log::trace!(
2038 "Blank line: depth={}, levels_to_keep={}, next='{}'",
2039 self.containers.depth(),
2040 levels_to_keep,
2041 if peek < self.lines.len() {
2042 self.lines[peek]
2043 } else {
2044 "<EOF>"
2045 }
2046 );
2047
2048 while self.containers.depth() > levels_to_keep {
2052 match self.containers.last() {
2053 Some(Container::ListItem { .. }) => {
2054 log::trace!(
2056 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
2057 levels_to_keep,
2058 self.containers.depth()
2059 );
2060 self.close_containers_to(self.containers.depth() - 1);
2061 }
2062 Some(Container::List { .. })
2063 | Some(Container::FootnoteDefinition { .. })
2064 | Some(Container::Alert { .. })
2065 | Some(Container::Paragraph { .. })
2066 | Some(Container::Definition { .. })
2067 | Some(Container::DefinitionItem { .. })
2068 | Some(Container::DefinitionList { .. }) => {
2069 log::trace!(
2070 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
2071 self.containers.last(),
2072 self.containers.depth(),
2073 levels_to_keep
2074 );
2075
2076 self.close_containers_to(self.containers.depth() - 1);
2077 }
2078 _ => break,
2079 }
2080 }
2081
2082 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2086 self.emit_list_item_buffer_if_needed();
2087 }
2088
2089 if bq_depth > 0 {
2091 let marker_info = self.marker_info_for_line(
2092 blockquote_payload.as_ref(),
2093 line,
2094 bq_marker_line,
2095 shifted_bq_prefix,
2096 used_shifted_bq,
2097 );
2098 self.emit_blockquote_markers(&marker_info, bq_depth);
2099 }
2100
2101 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
2102 self.builder
2103 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
2104 self.builder.finish_node();
2105
2106 return LineDispatch::consumed(1);
2107 }
2108
2109 if bq_depth > current_bq_depth {
2111 if self.config.extensions.blank_before_blockquote
2114 && current_bq_depth == 0
2115 && !used_shifted_bq
2116 && !blockquote_payload
2117 .as_ref()
2118 .map(|payload| payload.can_start)
2119 .unwrap_or_else(|| {
2120 blockquotes::can_start_blockquote(
2121 self.pos,
2122 &self.lines,
2123 self.config.extensions.fenced_divs,
2124 )
2125 })
2126 {
2127 self.emit_list_item_buffer_if_needed();
2131 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2132 paragraphs::append_paragraph_line(
2133 &mut self.containers,
2134 &mut self.builder,
2135 line,
2136 self.config,
2137 );
2138 return LineDispatch::consumed(1);
2139 }
2140
2141 let can_nest = if current_bq_depth > 0 {
2144 if self.config.extensions.blank_before_blockquote {
2145 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2147 || (self.pos > 0 && {
2148 let prev_line = self.lines[self.pos - 1];
2149 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2150 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2151 })
2152 } else {
2153 true
2154 }
2155 } else {
2156 blockquote_payload
2157 .as_ref()
2158 .map(|payload| payload.can_nest)
2159 .unwrap_or(true)
2160 };
2161
2162 if !can_nest {
2163 let content_at_current_depth =
2166 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2167
2168 let marker_info = self.marker_info_for_line(
2170 blockquote_payload.as_ref(),
2171 line,
2172 bq_marker_line,
2173 shifted_bq_prefix,
2174 used_shifted_bq,
2175 );
2176 for i in 0..current_bq_depth {
2177 if let Some(info) = marker_info.get(i) {
2178 self.emit_or_buffer_blockquote_marker(
2179 info.leading_spaces,
2180 info.has_trailing_space,
2181 );
2182 }
2183 }
2184
2185 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2186 paragraphs::append_paragraph_line(
2188 &mut self.containers,
2189 &mut self.builder,
2190 content_at_current_depth,
2191 self.config,
2192 );
2193 return LineDispatch::consumed(1);
2194 } else {
2195 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2197 paragraphs::append_paragraph_line(
2198 &mut self.containers,
2199 &mut self.builder,
2200 content_at_current_depth,
2201 self.config,
2202 );
2203 return LineDispatch::consumed(1);
2204 }
2205 }
2206
2207 self.emit_list_item_buffer_if_needed();
2210
2211 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2213 self.close_containers_to(self.containers.depth() - 1);
2214 }
2215
2216 let marker_info = self.marker_info_for_line(
2218 blockquote_payload.as_ref(),
2219 line,
2220 bq_marker_line,
2221 shifted_bq_prefix,
2222 used_shifted_bq,
2223 );
2224
2225 if let (Some(dispatcher_ctx), Some(prepared)) =
2226 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2227 {
2228 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2229 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2230 let _ = self.block_registry.parse_prepared(
2231 prepared,
2232 dispatcher_ctx,
2233 &mut self.builder,
2234 &stripped,
2235 );
2236 for _ in 0..bq_depth {
2237 self.containers.push(Container::BlockQuote {});
2238 }
2239 } else {
2240 for level in 0..current_bq_depth {
2242 if let Some(info) = marker_info.get(level) {
2243 self.emit_or_buffer_blockquote_marker(
2244 info.leading_spaces,
2245 info.has_trailing_space,
2246 );
2247 }
2248 }
2249
2250 for level in current_bq_depth..bq_depth {
2252 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2253
2254 if let Some(info) = marker_info.get(level) {
2256 blockquotes::emit_one_blockquote_marker(
2257 &mut self.builder,
2258 info.leading_spaces,
2259 info.has_trailing_space,
2260 );
2261 }
2262
2263 self.containers.push(Container::BlockQuote {});
2264 }
2265 }
2266
2267 let prev_flag = self.dispatch_list_marker_consumed;
2280 if used_shifted_bq && !self.innermost_li_above_bq() {
2281 self.dispatch_list_marker_consumed = true;
2282 }
2283 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2284 self.dispatch_list_marker_consumed = prev_flag;
2285 return dispatch;
2286 } else if bq_depth < current_bq_depth {
2287 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2293 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2300 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2301 let interrupts_via_fence = is_commonmark
2302 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2303 let interrupts_via_div_close = self.config.extensions.fenced_divs
2308 && self.in_fenced_div()
2309 && fenced_divs::is_div_closing_fence(line);
2310 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2311 if bq_depth > 0 {
2312 let marker_info = self.marker_info_for_line(
2318 blockquote_payload.as_ref(),
2319 line,
2320 bq_marker_line,
2321 shifted_bq_prefix,
2322 used_shifted_bq,
2323 );
2324 for i in 0..bq_depth {
2325 if let Some(info) = marker_info.get(i) {
2326 paragraphs::append_paragraph_marker(
2327 &mut self.containers,
2328 info.leading_spaces,
2329 info.has_trailing_space,
2330 );
2331 }
2332 }
2333 paragraphs::append_paragraph_line(
2334 &mut self.containers,
2335 &mut self.builder,
2336 inner_content,
2337 self.config,
2338 );
2339 } else {
2340 paragraphs::append_paragraph_line(
2341 &mut self.containers,
2342 &mut self.builder,
2343 line,
2344 self.config,
2345 );
2346 }
2347 return LineDispatch::consumed(1);
2348 }
2349 }
2350 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2358 && lists::in_blockquote_list(&self.containers)
2359 && try_parse_list_marker(
2360 line,
2361 self.config,
2362 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2363 )
2364 .is_none()
2365 {
2366 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2367 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2368 let interrupts_via_fence = is_commonmark
2369 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2370 if !interrupts_via_hr && !interrupts_via_fence {
2371 if bq_depth > 0 {
2372 let marker_info = self.marker_info_for_line(
2373 blockquote_payload.as_ref(),
2374 line,
2375 bq_marker_line,
2376 shifted_bq_prefix,
2377 used_shifted_bq,
2378 );
2379 if let Some(Container::ListItem {
2380 buffer,
2381 marker_only,
2382 ..
2383 }) = self.containers.stack.last_mut()
2384 {
2385 for i in 0..bq_depth {
2386 if let Some(info) = marker_info.get(i) {
2387 buffer.push_blockquote_marker(
2388 info.leading_spaces,
2389 info.has_trailing_space,
2390 );
2391 }
2392 }
2393 buffer.push_text(inner_content);
2394 if !inner_content.trim().is_empty() {
2395 *marker_only = false;
2396 }
2397 }
2398 } else if let Some(Container::ListItem {
2399 buffer,
2400 marker_only,
2401 ..
2402 }) = self.containers.stack.last_mut()
2403 {
2404 buffer.push_text(line);
2405 if !line.trim().is_empty() {
2406 *marker_only = false;
2407 }
2408 }
2409 return LineDispatch::consumed(1);
2410 }
2411 }
2412 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2418 if lists::in_blockquote_list(&self.containers)
2421 && let Some(marker_match) = try_parse_list_marker(
2422 line,
2423 self.config,
2424 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2425 )
2426 {
2427 let (indent_cols, indent_bytes) = leading_indent(line);
2428 if let Some(level) = lists::find_matching_list_level(
2429 &self.containers,
2430 &marker_match.marker,
2431 indent_cols,
2432 self.config.dialect,
2433 ) {
2434 self.close_containers_to(level + 1);
2437
2438 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2440 self.close_containers_to(self.containers.depth() - 1);
2441 }
2442 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2443 self.close_containers_to(self.containers.depth() - 1);
2444 }
2445
2446 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2448 line,
2449 marker_match.marker_len,
2450 marker_match.spaces_after_bytes,
2451 ) {
2452 let list_item = ListItemEmissionInput {
2453 content: line,
2454 marker_len: marker_match.marker_len,
2455 spaces_after_cols: marker_match.spaces_after_cols,
2456 spaces_after_bytes: marker_match.spaces_after_bytes,
2457 indent_cols,
2458 indent_bytes,
2459 virtual_marker_space: marker_match.virtual_marker_space,
2460 };
2461 lists::add_list_item_with_nested_empty_list(
2462 &mut self.containers,
2463 &mut self.builder,
2464 &list_item,
2465 nested_marker,
2466 self.config,
2467 );
2468 0
2469 } else {
2470 let list_item = ListItemEmissionInput {
2471 content: line,
2472 marker_len: marker_match.marker_len,
2473 spaces_after_cols: marker_match.spaces_after_cols,
2474 spaces_after_bytes: marker_match.spaces_after_bytes,
2475 indent_cols,
2476 indent_bytes,
2477 virtual_marker_space: marker_match.virtual_marker_space,
2478 };
2479 let finish = lists::add_list_item(
2480 &mut self.containers,
2481 &mut self.builder,
2482 &list_item,
2483 self.config,
2484 );
2485 self.dispatch_bq_after_list_item(finish)
2486 };
2487 return LineDispatch::consumed(1 + extras);
2488 }
2489 }
2490 }
2491
2492 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2494 self.close_containers_to(self.containers.depth() - 1);
2495 }
2496
2497 self.close_blockquotes_to_depth(bq_depth);
2499
2500 if bq_depth > 0 {
2502 let marker_info = self.marker_info_for_line(
2504 blockquote_payload.as_ref(),
2505 line,
2506 bq_marker_line,
2507 shifted_bq_prefix,
2508 used_shifted_bq,
2509 );
2510 for i in 0..bq_depth {
2511 if let Some(info) = marker_info.get(i) {
2512 self.emit_or_buffer_blockquote_marker(
2513 info.leading_spaces,
2514 info.has_trailing_space,
2515 );
2516 }
2517 }
2518 return self.parse_inner_content(inner_content, Some(inner_content));
2520 } else {
2521 return self.parse_inner_content(line, None);
2523 }
2524 } else if bq_depth > 0 {
2525 let mut list_item_continuation = false;
2527 let same_depth_marker_info = self.marker_info_for_line(
2528 blockquote_payload.as_ref(),
2529 line,
2530 bq_marker_line,
2531 shifted_bq_prefix,
2532 used_shifted_bq,
2533 );
2534 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2535
2536 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2548 if let Some(marker_match) = try_parse_list_marker(
2549 inner_content,
2550 self.config,
2551 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2552 ) {
2553 let inner_content_threshold =
2557 marker_match.marker_len + marker_match.spaces_after_cols;
2558 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2559 let sibling_list_level = if is_sibling_candidate {
2560 self.containers
2561 .stack
2562 .iter()
2563 .enumerate()
2564 .rev()
2565 .find_map(|(i, c)| match c {
2566 Container::List { marker, .. }
2567 if lists::markers_match(
2568 &marker_match.marker,
2569 marker,
2570 self.config.dialect,
2571 ) && self.containers.stack[..i]
2572 .iter()
2573 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2574 .count()
2575 == bq_depth =>
2576 {
2577 Some(i)
2578 }
2579 _ => None,
2580 })
2581 } else {
2582 None
2583 };
2584 if let Some(list_level) = sibling_list_level {
2585 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2591 Some(Container::List {
2592 base_indent_cols, ..
2593 }) => *base_indent_cols,
2594 _ => 0,
2595 };
2596
2597 self.emit_list_item_buffer_if_needed();
2599 self.close_containers_to(list_level + 1);
2602
2603 for i in 0..bq_depth {
2607 if let Some(info) = same_depth_marker_info.get(i) {
2608 self.emit_or_buffer_blockquote_marker(
2609 info.leading_spaces,
2610 info.has_trailing_space,
2611 );
2612 }
2613 }
2614
2615 let list_item = ListItemEmissionInput {
2617 content: inner_content,
2618 marker_len: marker_match.marker_len,
2619 spaces_after_cols: marker_match.spaces_after_cols,
2620 spaces_after_bytes: marker_match.spaces_after_bytes,
2621 indent_cols: sibling_base_indent_cols,
2622 indent_bytes: inner_indent_bytes,
2623 virtual_marker_space: marker_match.virtual_marker_space,
2624 };
2625 let finish = lists::add_list_item(
2626 &mut self.containers,
2627 &mut self.builder,
2628 &list_item,
2629 self.config,
2630 );
2631 let extras = if let Some(extras) =
2632 self.maybe_open_fenced_code_in_new_list_item()
2633 {
2634 extras
2635 } else if let Some(extras) = self.maybe_open_caption_table_in_new_list_item() {
2636 extras
2637 } else {
2638 self.maybe_open_indented_code_in_new_list_item();
2639 self.dispatch_bq_after_list_item(finish)
2640 };
2641 return LineDispatch::consumed(1 + extras);
2642 }
2643 }
2644
2645 if matches!(
2648 self.containers.last(),
2649 Some(Container::ListItem { content_col: _, .. })
2650 ) {
2651 let (indent_cols, _) = leading_indent(inner_content);
2652 let content_indent = self.content_container_indent_to_strip();
2653 let effective_indent = indent_cols.saturating_sub(content_indent);
2654 let content_col = match self.containers.last() {
2655 Some(Container::ListItem { content_col, .. }) => *content_col,
2656 _ => 0,
2657 };
2658
2659 let is_new_item_at_outer_level = if try_parse_list_marker(
2661 inner_content,
2662 self.config,
2663 lists::open_list_hint_at_indent(
2664 &self.containers,
2665 leading_indent(inner_content).0,
2666 ),
2667 )
2668 .is_some()
2669 {
2670 effective_indent < content_col
2671 } else {
2672 false
2673 };
2674
2675 if is_new_item_at_outer_level
2679 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2680 {
2681 log::trace!(
2682 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2683 is_new_item_at_outer_level,
2684 effective_indent,
2685 content_col
2686 );
2687 self.close_containers_to(self.containers.depth() - 1);
2688 } else {
2689 log::trace!(
2690 "Keeping ListItem: effective_indent={} >= content_col={}",
2691 effective_indent,
2692 content_col
2693 );
2694 list_item_continuation = true;
2695 }
2696 }
2697
2698 if list_item_continuation
2702 && code_blocks::try_parse_fence_open(inner_content, self.config.dialect).is_some()
2703 {
2704 list_item_continuation = false;
2705 }
2706
2707 let continuation_has_explicit_marker = list_item_continuation && {
2708 if has_explicit_same_depth_marker {
2709 for i in 0..bq_depth {
2710 if let Some(info) = same_depth_marker_info.get(i) {
2711 self.emit_or_buffer_blockquote_marker(
2712 info.leading_spaces,
2713 info.has_trailing_space,
2714 );
2715 }
2716 }
2717 true
2718 } else {
2719 false
2720 }
2721 };
2722
2723 if !list_item_continuation {
2724 let marker_info = self.marker_info_for_line(
2725 blockquote_payload.as_ref(),
2726 line,
2727 bq_marker_line,
2728 shifted_bq_prefix,
2729 used_shifted_bq,
2730 );
2731 for i in 0..bq_depth {
2732 if let Some(info) = marker_info.get(i) {
2733 self.emit_or_buffer_blockquote_marker(
2734 info.leading_spaces,
2735 info.has_trailing_space,
2736 );
2737 }
2738 }
2739 }
2740 let line_to_append = if list_item_continuation {
2741 if continuation_has_explicit_marker {
2742 Some(inner_content)
2743 } else {
2744 Some(line)
2745 }
2746 } else {
2747 Some(inner_content)
2748 };
2749 let prev_flag = self.dispatch_list_marker_consumed;
2755 if used_shifted_bq && !self.innermost_li_above_bq() {
2756 self.dispatch_list_marker_consumed = true;
2757 }
2758 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2759 self.dispatch_list_marker_consumed = prev_flag;
2760 return dispatch;
2761 }
2762
2763 if current_bq_depth > 0 {
2766 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2768 paragraphs::append_paragraph_line(
2769 &mut self.containers,
2770 &mut self.builder,
2771 line,
2772 self.config,
2773 );
2774 return LineDispatch::consumed(1);
2775 }
2776
2777 if lists::in_blockquote_list(&self.containers)
2779 && let Some(marker_match) = try_parse_list_marker(
2780 line,
2781 self.config,
2782 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2783 )
2784 {
2785 let (indent_cols, indent_bytes) = leading_indent(line);
2786 if let Some(level) = lists::find_matching_list_level(
2787 &self.containers,
2788 &marker_match.marker,
2789 indent_cols,
2790 self.config.dialect,
2791 ) {
2792 self.close_containers_to(level + 1);
2794
2795 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2797 self.close_containers_to(self.containers.depth() - 1);
2798 }
2799 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2800 self.close_containers_to(self.containers.depth() - 1);
2801 }
2802
2803 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2805 line,
2806 marker_match.marker_len,
2807 marker_match.spaces_after_bytes,
2808 ) {
2809 let list_item = ListItemEmissionInput {
2810 content: line,
2811 marker_len: marker_match.marker_len,
2812 spaces_after_cols: marker_match.spaces_after_cols,
2813 spaces_after_bytes: marker_match.spaces_after_bytes,
2814 indent_cols,
2815 indent_bytes,
2816 virtual_marker_space: marker_match.virtual_marker_space,
2817 };
2818 lists::add_list_item_with_nested_empty_list(
2819 &mut self.containers,
2820 &mut self.builder,
2821 &list_item,
2822 nested_marker,
2823 self.config,
2824 );
2825 0
2826 } else {
2827 let list_item = ListItemEmissionInput {
2828 content: line,
2829 marker_len: marker_match.marker_len,
2830 spaces_after_cols: marker_match.spaces_after_cols,
2831 spaces_after_bytes: marker_match.spaces_after_bytes,
2832 indent_cols,
2833 indent_bytes,
2834 virtual_marker_space: marker_match.virtual_marker_space,
2835 };
2836 let finish = lists::add_list_item(
2837 &mut self.containers,
2838 &mut self.builder,
2839 &list_item,
2840 self.config,
2841 );
2842 self.dispatch_bq_after_list_item(finish)
2843 };
2844 return LineDispatch::consumed(1 + extras);
2845 }
2846 }
2847 }
2848
2849 self.parse_inner_content(line, None)
2851 }
2852
2853 fn content_container_indent_to_strip(&self) -> usize {
2855 self.containers
2856 .stack
2857 .iter()
2858 .filter_map(|c| match c {
2859 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2860 Container::Definition { content_col, .. } => Some(*content_col),
2861 _ => None,
2862 })
2863 .sum()
2864 }
2865
2866 fn innermost_li_above_bq(&self) -> bool {
2873 for c in self.containers.stack.iter().rev() {
2874 match c {
2875 Container::ListItem { .. } => return true,
2876 Container::BlockQuote { .. } => return false,
2877 _ => continue,
2878 }
2879 }
2880 false
2881 }
2882
2883 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2889 log::trace!(
2890 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2891 self.pos,
2892 self.containers.depth(),
2893 self.containers.last(),
2894 content.trim_end()
2895 );
2896 let content_indent = self.content_container_indent_to_strip();
2901 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
2902
2903 if self.config.extensions.alerts
2904 && self.current_blockquote_depth() > 0
2905 && !self.in_active_alert()
2906 && !self.is_paragraph_open()
2907 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2908 {
2909 let (_, newline_str) = strip_newline(stripped_content);
2910 self.builder.start_node(SyntaxKind::ALERT.into());
2911 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2912 if !newline_str.is_empty() {
2913 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2914 }
2915 self.containers.push(Container::Alert {
2916 blockquote_depth: self.current_blockquote_depth(),
2917 });
2918 return LineDispatch::consumed(1);
2919 }
2920
2921 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2925 let is_definition_marker =
2926 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2927 && !stripped_content.starts_with(':');
2928 if content_indent == 0 && is_definition_marker {
2929 } else {
2931 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2932
2933 if policy.definition_plain_can_continue(
2934 stripped_content,
2935 content,
2936 content_indent,
2937 &BlockContext {
2938 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2939 has_blank_before_strict: self.pos == 0
2940 || is_blank_line(self.lines[self.pos - 1]),
2941 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2942 in_fenced_div: self.in_fenced_div(),
2943 blockquote_depth: self.current_blockquote_depth(),
2944 config: self.config,
2945 diags: self.diagnostics.clone(),
2946 content_indent,
2947 indent_to_emit: None,
2948 list_indent_info: None,
2949 in_list: lists::in_list(&self.containers),
2950 in_marker_only_list_item: matches!(
2951 self.containers.last(),
2952 Some(Container::ListItem {
2953 marker_only: true,
2954 ..
2955 })
2956 ),
2957 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2958 paragraph_open: self.is_paragraph_open(),
2959 next_line: if self.pos + 1 < self.lines.len() {
2960 Some(self.lines[self.pos + 1])
2961 } else {
2962 None
2963 },
2964 open_alpha_hint: lists::open_list_hint_at_indent(
2965 &self.containers,
2966 leading_indent(stripped_content).0,
2967 ),
2968 },
2969 &self.lines,
2970 self.pos,
2971 ) {
2972 let content_line = stripped_content;
2973 let (text_without_newline, newline_str) = strip_newline(content_line);
2974 let indent_prefix = if !text_without_newline.trim().is_empty() {
2975 indent_to_emit.unwrap_or("")
2976 } else {
2977 ""
2978 };
2979 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2980
2981 if let Some(Container::Definition {
2982 plain_open,
2983 plain_buffer,
2984 ..
2985 }) = self.containers.stack.last_mut()
2986 {
2987 let line_with_newline = if !newline_str.is_empty() {
2988 format!("{}{}", content_line, newline_str)
2989 } else {
2990 content_line
2991 };
2992 plain_buffer.push_line(line_with_newline);
2993 *plain_open = true;
2994 }
2995
2996 return LineDispatch::consumed(1);
2997 }
2998 }
2999 }
3000
3001 if content_indent > 0 {
3004 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
3005 let current_bq_depth = self.current_blockquote_depth();
3006 let in_footnote_definition = self
3007 .containers
3008 .stack
3009 .iter()
3010 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
3011
3012 if bq_depth > 0 {
3013 if in_footnote_definition
3014 && self.config.extensions.blank_before_blockquote
3015 && current_bq_depth == 0
3016 && !blockquotes::can_start_blockquote(
3017 self.pos,
3018 &self.lines,
3019 self.config.extensions.fenced_divs,
3020 )
3021 {
3022 } else {
3026 self.emit_buffered_plain_if_needed();
3029 self.emit_list_item_buffer_if_needed();
3030
3031 self.close_paragraph_if_open();
3034
3035 if bq_depth < current_bq_depth {
3036 self.close_blockquotes_to_depth(bq_depth);
3037 } else {
3038 let marker_info = parse_blockquote_marker_info(stripped_content);
3039
3040 if bq_depth > current_bq_depth {
3041 for level in current_bq_depth..bq_depth {
3043 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
3044
3045 if level == current_bq_depth
3046 && let Some(indent_str) = indent_to_emit
3047 {
3048 self.builder
3049 .token(SyntaxKind::WHITESPACE.into(), indent_str);
3050 }
3051
3052 if let Some(info) = marker_info.get(level) {
3053 blockquotes::emit_one_blockquote_marker(
3054 &mut self.builder,
3055 info.leading_spaces,
3056 info.has_trailing_space,
3057 );
3058 }
3059
3060 self.containers.push(Container::BlockQuote {});
3061 }
3062 } else {
3063 self.emit_blockquote_markers(&marker_info, bq_depth);
3065 }
3066 }
3067
3068 return self.parse_inner_content(inner_content, Some(inner_content));
3069 }
3070 }
3071 }
3072
3073 let content = stripped_content;
3075
3076 if self.is_paragraph_open()
3077 && (paragraphs::has_open_inline_math_environment(&self.containers)
3078 || paragraphs::has_open_display_math_dollars(&self.containers))
3079 {
3080 paragraphs::append_paragraph_line(
3081 &mut self.containers,
3082 &mut self.builder,
3083 line_to_append.unwrap_or(self.lines[self.pos]),
3084 self.config,
3085 );
3086 return LineDispatch::consumed(1);
3087 }
3088
3089 use super::blocks::lists;
3093 use super::blocks::paragraphs;
3094 let list_indent_info = if lists::in_list(&self.containers) {
3095 let content_col = paragraphs::current_content_col(&self.containers);
3096 if content_col > 0 {
3097 Some(super::block_dispatcher::ListIndentInfo { content_col })
3098 } else {
3099 None
3100 }
3101 } else {
3102 None
3103 };
3104
3105 let next_line = if self.pos + 1 < self.lines.len() {
3106 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
3109 } else {
3110 None
3111 };
3112
3113 let current_bq_depth = self.current_blockquote_depth();
3114 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
3115 && current_bq_depth < alert_bq_depth
3116 {
3117 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
3118 self.close_containers_to(self.containers.depth() - 1);
3119 }
3120 }
3121
3122 let dispatcher_ctx = BlockContext {
3123 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
3127 blockquote_depth: current_bq_depth,
3128 config: self.config,
3129 diags: self.diagnostics.clone(),
3130 content_indent,
3131 indent_to_emit,
3132 list_indent_info,
3133 in_list: lists::in_list(&self.containers),
3134 in_marker_only_list_item: matches!(
3135 self.containers.last(),
3136 Some(Container::ListItem {
3137 marker_only: true,
3138 ..
3139 })
3140 ),
3141 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3142 paragraph_open: self.is_paragraph_open(),
3143 next_line,
3144 open_alpha_hint: lists::open_list_hint_at_indent(
3145 &self.containers,
3146 leading_indent(content).0,
3147 ),
3148 };
3149
3150 let mut dispatcher_ctx = dispatcher_ctx;
3153
3154 let dispatcher_prefix =
3161 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3162
3163 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3167 return dispatch;
3168 }
3169
3170 let dispatcher_match = {
3173 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3174 self.block_registry
3175 .detect_prepared(&dispatcher_ctx, &stripped)
3176 };
3177
3178 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3184 let has_blank_before = if self.pos == 0 || after_metadata_block {
3185 true
3186 } else {
3187 let prev_line = self.lines[self.pos - 1];
3188 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3189 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3190 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3191 && fenced_divs::try_parse_div_fence_open(
3192 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3193 )
3194 .is_some();
3195
3196 let prev_line_blank = is_blank_line(prev_line);
3197 prev_line_blank
3198 || prev_is_fenced_div_open
3199 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3200 || !self.previous_block_requires_blank_before_heading()
3201 };
3202
3203 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3206
3207 let prev_line_blank = if self.pos > 0 {
3208 let prev_line = self.lines[self.pos - 1];
3209 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3210 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3211 } else {
3212 false
3213 };
3214 let has_blank_before_strict = at_document_start || prev_line_blank;
3215
3216 dispatcher_ctx.has_blank_before = has_blank_before;
3217 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3218 dispatcher_ctx.at_document_start = at_document_start;
3219
3220 let dispatcher_match =
3221 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3222 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3224 self.block_registry
3225 .detect_prepared(&dispatcher_ctx, &stripped)
3226 } else {
3227 dispatcher_match
3228 };
3229
3230 if has_blank_before {
3231 if let Some(env_name) = extract_environment_name(content)
3232 && is_inline_math_environment(env_name)
3233 {
3234 if !self.is_paragraph_open() {
3235 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3236 }
3237 paragraphs::append_paragraph_line(
3238 &mut self.containers,
3239 &mut self.builder,
3240 line_to_append.unwrap_or(self.lines[self.pos]),
3241 self.config,
3242 );
3243 return LineDispatch::consumed(1);
3244 }
3245
3246 if let Some(block_match) = dispatcher_match.as_ref() {
3247 let detection = block_match.detection;
3248
3249 match detection {
3250 BlockDetectionResult::YesCanInterrupt => {
3251 self.emit_list_item_buffer_if_needed();
3252 if self.is_paragraph_open() {
3253 self.close_containers_to(self.containers.depth() - 1);
3254 }
3255 }
3256 BlockDetectionResult::Yes => {
3257 self.prepare_for_block_element();
3258 }
3259 BlockDetectionResult::No => unreachable!(),
3260 }
3261
3262 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3263 self.close_containers_to_fenced_div();
3264 }
3265
3266 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3267 self.close_open_footnote_definition();
3268 }
3269
3270 let lines_consumed = {
3271 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3272 self.block_registry.parse_prepared(
3273 block_match,
3274 &dispatcher_ctx,
3275 &mut self.builder,
3276 &stripped,
3277 )
3278 };
3279
3280 if matches!(
3281 self.block_registry.parser_name(block_match),
3282 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3283 ) {
3284 self.after_metadata_block = true;
3285 }
3286
3287 let extras = match block_match.effect {
3288 BlockEffect::None => 0,
3289 BlockEffect::OpenFencedDiv => {
3290 self.containers.push(Container::FencedDiv {});
3291 0
3292 }
3293 BlockEffect::CloseFencedDiv => {
3294 self.close_fenced_div();
3295 0
3296 }
3297 BlockEffect::OpenFootnoteDefinition => {
3298 self.handle_footnote_open_effect(block_match, content)
3299 }
3300 BlockEffect::OpenList => {
3301 self.handle_list_open_effect(block_match, content, indent_to_emit)
3302 }
3303 BlockEffect::OpenDefinitionList => {
3304 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3305 }
3306 BlockEffect::OpenBlockQuote => {
3307 0
3309 }
3310 };
3311
3312 if lines_consumed == 0 {
3313 log::warn!(
3314 "block parser made no progress at line {} (parser={})",
3315 self.pos + 1,
3316 self.block_registry.parser_name(block_match)
3317 );
3318 return LineDispatch::Rejected;
3319 }
3320
3321 return LineDispatch::consumed(lines_consumed + extras);
3322 }
3323 } else if let Some(block_match) = dispatcher_match.as_ref() {
3324 let parser_name = self.block_registry.parser_name(block_match);
3327 match block_match.detection {
3328 BlockDetectionResult::YesCanInterrupt => {
3329 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3330 && self.is_paragraph_open()
3331 {
3332 if !self.is_paragraph_open() {
3334 paragraphs::start_paragraph_if_needed(
3335 &mut self.containers,
3336 &mut self.builder,
3337 );
3338 }
3339 paragraphs::append_paragraph_line(
3340 &mut self.containers,
3341 &mut self.builder,
3342 line_to_append.unwrap_or(self.lines[self.pos]),
3343 self.config,
3344 );
3345 return LineDispatch::consumed(1);
3346 }
3347
3348 if matches!(block_match.effect, BlockEffect::OpenList)
3349 && self.is_paragraph_open()
3350 && !lists::in_list(&self.containers)
3351 && (self.content_container_indent_to_strip() == 0
3352 || self.in_footnote_definition())
3353 {
3354 let allow_interrupt =
3363 self.config.dialect == crate::options::Dialect::CommonMark && {
3364 use super::block_dispatcher::ListPrepared;
3365 use super::blocks::lists::OrderedMarker;
3366 let prepared = block_match
3367 .payload
3368 .as_ref()
3369 .and_then(|p| p.downcast_ref::<ListPrepared>());
3370 match prepared.map(|p| &p.marker) {
3371 Some(ListMarker::Bullet(_)) => true,
3372 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3373 number,
3374 ..
3375 })) => number == "1",
3376 _ => false,
3377 }
3378 };
3379 if !allow_interrupt {
3380 paragraphs::append_paragraph_line(
3381 &mut self.containers,
3382 &mut self.builder,
3383 line_to_append.unwrap_or(self.lines[self.pos]),
3384 self.config,
3385 );
3386 return LineDispatch::consumed(1);
3387 }
3388 }
3389
3390 if matches!(block_match.effect, BlockEffect::OpenList)
3397 && self.try_lazy_list_continuation(block_match, content)
3398 {
3399 return LineDispatch::consumed(1);
3400 }
3401
3402 self.emit_list_item_buffer_if_needed();
3403 if self.is_paragraph_open() {
3404 if self.html_block_demotes_paragraph_to_plain(block_match) {
3405 self.close_paragraph_as_plain_if_open();
3406 } else {
3407 self.close_containers_to(self.containers.depth() - 1);
3408 }
3409 }
3410
3411 if self.config.dialect == crate::options::Dialect::CommonMark
3418 && !matches!(block_match.effect, BlockEffect::OpenList)
3419 {
3420 let (indent_cols, _) = leading_indent(content);
3421 self.close_lists_above_indent(indent_cols);
3422 }
3423 }
3424 BlockDetectionResult::Yes => {
3425 if parser_name == "setext_heading"
3437 && self.is_paragraph_open()
3438 && self.config.dialect == crate::options::Dialect::CommonMark
3439 {
3440 let text_line = self.lines[self.pos];
3441 let underline_line = self.lines[self.pos + 1];
3442 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3443 let level = if underline_char == '=' { 1 } else { 2 };
3444 self.emit_setext_heading_folding_paragraph(
3445 text_line,
3446 underline_line,
3447 level,
3448 );
3449 return LineDispatch::consumed(2);
3450 }
3451
3452 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3455 if !self.is_paragraph_open() {
3456 paragraphs::start_paragraph_if_needed(
3457 &mut self.containers,
3458 &mut self.builder,
3459 );
3460 }
3461 paragraphs::append_paragraph_line(
3462 &mut self.containers,
3463 &mut self.builder,
3464 line_to_append.unwrap_or(self.lines[self.pos]),
3465 self.config,
3466 );
3467 return LineDispatch::consumed(1);
3468 }
3469
3470 if parser_name == "reference_definition" && self.is_paragraph_open() {
3473 paragraphs::append_paragraph_line(
3474 &mut self.containers,
3475 &mut self.builder,
3476 line_to_append.unwrap_or(self.lines[self.pos]),
3477 self.config,
3478 );
3479 return LineDispatch::consumed(1);
3480 }
3481 }
3482 BlockDetectionResult::No => unreachable!(),
3483 }
3484
3485 if !matches!(block_match.detection, BlockDetectionResult::No) {
3486 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3487 self.close_containers_to_fenced_div();
3488 }
3489
3490 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3491 self.close_open_footnote_definition();
3492 }
3493
3494 let lines_consumed = {
3495 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3496 self.block_registry.parse_prepared(
3497 block_match,
3498 &dispatcher_ctx,
3499 &mut self.builder,
3500 &stripped,
3501 )
3502 };
3503
3504 let extras = match block_match.effect {
3505 BlockEffect::None => 0,
3506 BlockEffect::OpenFencedDiv => {
3507 self.containers.push(Container::FencedDiv {});
3508 0
3509 }
3510 BlockEffect::CloseFencedDiv => {
3511 self.close_fenced_div();
3512 0
3513 }
3514 BlockEffect::OpenFootnoteDefinition => {
3515 self.handle_footnote_open_effect(block_match, content)
3516 }
3517 BlockEffect::OpenList => {
3518 self.handle_list_open_effect(block_match, content, indent_to_emit)
3519 }
3520 BlockEffect::OpenDefinitionList => {
3521 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3522 }
3523 BlockEffect::OpenBlockQuote => {
3524 0
3526 }
3527 };
3528
3529 if lines_consumed == 0 {
3530 log::warn!(
3531 "block parser made no progress at line {} (parser={})",
3532 self.pos + 1,
3533 self.block_registry.parser_name(block_match)
3534 );
3535 return LineDispatch::Rejected;
3536 }
3537
3538 return LineDispatch::consumed(lines_consumed + extras);
3539 }
3540 }
3541
3542 if self.config.extensions.line_blocks
3544 && (has_blank_before || self.pos == 0)
3545 && try_parse_line_block_start(content).is_some()
3546 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3550 {
3551 log::trace!("Parsed line block at line {}", self.pos);
3552 self.close_paragraph_if_open();
3554
3555 let prefix = ContainerPrefix::default();
3561 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3562 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3563 if new_pos > self.pos {
3564 return LineDispatch::consumed(new_pos - self.pos);
3565 }
3566 }
3567
3568 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3571 log::trace!(
3572 "Inside ListItem - buffering content: {:?}",
3573 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3574 );
3575 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3577
3578 if let Some(Container::ListItem {
3580 buffer,
3581 marker_only,
3582 ..
3583 }) = self.containers.stack.last_mut()
3584 {
3585 buffer.push_text(line);
3586 if !is_blank_line(line) {
3587 *marker_only = false;
3588 }
3589 }
3590
3591 return LineDispatch::consumed(1);
3592 }
3593
3594 log::trace!(
3595 "Not in ListItem - creating paragraph for: {:?}",
3596 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3597 );
3598 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3600 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3603 paragraphs::append_paragraph_line(
3604 &mut self.containers,
3605 &mut self.builder,
3606 line,
3607 self.config,
3608 );
3609 LineDispatch::consumed(1)
3610 }
3611
3612 fn fenced_div_container_index(&self) -> Option<usize> {
3613 self.containers
3614 .stack
3615 .iter()
3616 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3617 }
3618
3619 fn close_containers_to_fenced_div(&mut self) {
3620 if let Some(index) = self.fenced_div_container_index() {
3621 self.close_containers_to(index + 1);
3622 }
3623 }
3624
3625 fn close_fenced_div(&mut self) {
3626 if let Some(index) = self.fenced_div_container_index() {
3627 self.close_containers_to(index);
3628 }
3629 }
3630
3631 fn in_fenced_div(&self) -> bool {
3632 self.containers
3633 .stack
3634 .iter()
3635 .any(|c| matches!(c, Container::FencedDiv { .. }))
3636 }
3637
3638 fn in_footnote_definition(&self) -> bool {
3646 self.containers
3647 .stack
3648 .iter()
3649 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3650 }
3651}
3652
3653fn emit_definition_plain_or_heading(
3660 builder: &mut GreenNodeBuilder<'static>,
3661 text: &str,
3662 config: &ParserOptions,
3663 suppress_footnote_refs: bool,
3664) {
3665 let line_without_newline = text
3666 .strip_suffix("\r\n")
3667 .or_else(|| text.strip_suffix('\n'));
3668 if let Some(line) = line_without_newline
3669 && !line.contains('\n')
3670 && !line.contains('\r')
3671 && let Some(level) = try_parse_atx_heading(line)
3672 {
3673 emit_atx_heading(builder, text, level, config);
3674 return;
3675 }
3676
3677 if let Some(first_nl) = text.find('\n') {
3679 let first_line = &text[..first_nl];
3680 let after_first = &text[first_nl + 1..];
3681 if !after_first.is_empty()
3682 && let Some(level) = try_parse_atx_heading(first_line)
3683 {
3684 let heading_bytes = &text[..first_nl + 1];
3685 emit_atx_heading(builder, heading_bytes, level, config);
3686 builder.start_node(SyntaxKind::PLAIN.into());
3687 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3688 builder.finish_node();
3689 return;
3690 }
3691 }
3692
3693 builder.start_node(SyntaxKind::PLAIN.into());
3694 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3695 builder.finish_node();
3696}
3697
3698fn footnote_first_line_term_lookahead(
3707 lines: &[&str],
3708 pos: usize,
3709 content_col: usize,
3710 table_captions_enabled: bool,
3711) -> Option<usize> {
3712 let mut check_pos = pos + 1;
3713 let mut blank_count = 0;
3714 while check_pos < lines.len() {
3715 let line = lines[check_pos];
3716 let (trimmed, _) = strip_newline(line);
3717 if trimmed.trim().is_empty() {
3718 blank_count += 1;
3719 check_pos += 1;
3720 continue;
3721 }
3722 let (line_indent_cols, _) = leading_indent(trimmed);
3723 if line_indent_cols < content_col {
3724 return None;
3725 }
3726 let strip_bytes = byte_index_at_column(trimmed, content_col);
3727 if strip_bytes > trimmed.len() {
3728 return None;
3729 }
3730 let stripped = &trimmed[strip_bytes..];
3731 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3732 if marker == ':'
3738 && table_captions_enabled
3739 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3740 {
3741 return None;
3742 }
3743 return Some(blank_count);
3744 }
3745 return None;
3746 }
3747 None
3748}