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::utils::container_stack;
24use super::utils::helpers::{is_blank_line, split_lines_inclusive, strip_newline};
25use super::utils::inline_emission;
26use super::utils::marker_utils;
27use super::utils::text_buffer;
28
29use super::blocks::blockquotes::strip_n_blockquote_markers;
30use super::utils::continuation::ContinuationPolicy;
31use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
32use definition_lists::{emit_definition_marker, emit_term};
33use line_blocks::{parse_line_block, try_parse_line_block_start};
34use lists::{
35 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
36 try_parse_list_marker,
37};
38use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
39use text_buffer::TextBuffer;
40
41const GITHUB_ALERT_MARKERS: [&str; 5] = [
42 "[!TIP]",
43 "[!WARNING]",
44 "[!IMPORTANT]",
45 "[!CAUTION]",
46 "[!NOTE]",
47];
48
49#[must_use]
54#[derive(Debug, Clone, Copy)]
55pub(crate) enum LineDispatch {
56 Consumed(usize),
58 Rejected,
60}
61
62impl LineDispatch {
63 #[inline]
67 pub(crate) fn consumed(n: usize) -> Self {
68 debug_assert!(n >= 1, "LineDispatch::Consumed requires n >= 1");
69 LineDispatch::Consumed(n)
70 }
71}
72
73pub struct Parser<'a> {
74 lines: Vec<&'a str>,
75 pos: usize,
76 builder: GreenNodeBuilder<'static>,
77 containers: ContainerStack,
78 config: &'a ParserOptions,
79 block_registry: BlockParserRegistry,
80 after_metadata_block: bool,
84 dispatch_list_marker_consumed: bool,
94}
95
96impl<'a> Parser<'a> {
97 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
98 let lines = split_lines_inclusive(input);
100 Self {
101 lines,
102 pos: 0,
103 builder: GreenNodeBuilder::new(),
104 containers: ContainerStack::new(),
105 config,
106 block_registry: BlockParserRegistry::new(),
107 after_metadata_block: false,
108 dispatch_list_marker_consumed: false,
109 }
110 }
111
112 pub fn parse(mut self) -> SyntaxNode {
113 self.parse_document_stack();
114
115 SyntaxNode::new_root(self.builder.finish())
116 }
117
118 fn close_lists_above_indent(&mut self, indent_cols: usize) {
129 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
130 if indent_cols >= *content_col {
131 break;
132 }
133 self.close_containers_to(self.containers.depth() - 1);
134 if matches!(self.containers.last(), Some(Container::List { .. })) {
135 self.close_containers_to(self.containers.depth() - 1);
136 }
137 }
138 }
139
140 fn close_containers_to(&mut self, keep: usize) {
143 while self.containers.depth() > keep {
145 match self.containers.stack.last() {
146 Some(Container::ListItem {
148 buffer,
149 content_col,
150 ..
151 }) if !buffer.is_empty() => {
152 let buffer_clone = buffer.clone();
154 let item_content_col = *content_col;
155
156 log::trace!(
157 "Closing ListItem with buffer (is_empty={}, segment_count={})",
158 buffer_clone.is_empty(),
159 buffer_clone.segment_count()
160 );
161
162 let parent_list_is_loose = self
166 .containers
167 .stack
168 .iter()
169 .rev()
170 .find_map(|c| match c {
171 Container::List {
172 has_blank_between_items,
173 ..
174 } => Some(*has_blank_between_items),
175 _ => None,
176 })
177 .unwrap_or(false);
178
179 let use_paragraph =
180 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
181
182 log::trace!(
183 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
184 use_paragraph,
185 parent_list_is_loose,
186 buffer_clone.has_blank_lines_between_content()
187 );
188
189 let suppress_footnote_refs = self.in_footnote_definition();
190 self.containers.stack.pop();
192 buffer_clone.emit_as_block(
194 &mut self.builder,
195 use_paragraph,
196 self.config,
197 item_content_col,
198 suppress_footnote_refs,
199 );
200 self.builder.finish_node(); }
202 Some(Container::ListItem { .. }) => {
204 log::trace!("Closing empty ListItem (no buffer content)");
205 self.containers.stack.pop();
207 self.builder.finish_node();
208 }
209 Some(Container::Paragraph {
211 buffer,
212 start_checkpoint,
213 ..
214 }) if !buffer.is_empty() => {
215 let buffer_clone = buffer.clone();
217 let checkpoint = *start_checkpoint;
218 let suppress_footnote_refs = self.in_footnote_definition();
219 self.containers.stack.pop();
221 self.builder
223 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
224 buffer_clone.emit_with_inlines(
225 &mut self.builder,
226 self.config,
227 suppress_footnote_refs,
228 );
229 self.builder.finish_node();
230 }
231 Some(Container::Paragraph {
233 start_checkpoint, ..
234 }) => {
235 let checkpoint = *start_checkpoint;
236 self.containers.stack.pop();
238 self.builder
239 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
240 self.builder.finish_node();
241 }
242 Some(Container::Definition {
244 plain_open: true,
245 plain_buffer,
246 ..
247 }) if !plain_buffer.is_empty() => {
248 let text = plain_buffer.get_accumulated_text();
249 let suppress_footnote_refs = self.in_footnote_definition();
250 emit_definition_plain_or_heading(
251 &mut self.builder,
252 &text,
253 self.config,
254 suppress_footnote_refs,
255 );
256
257 if let Some(Container::Definition {
259 plain_open,
260 plain_buffer,
261 ..
262 }) = self.containers.stack.last_mut()
263 {
264 plain_buffer.clear();
265 *plain_open = false;
266 }
267
268 self.containers.stack.pop();
270 self.builder.finish_node();
271 }
272 Some(Container::Definition {
274 plain_open: true, ..
275 }) => {
276 if let Some(Container::Definition {
278 plain_open,
279 plain_buffer,
280 ..
281 }) = self.containers.stack.last_mut()
282 {
283 plain_buffer.clear();
284 *plain_open = false;
285 }
286
287 self.containers.stack.pop();
289 self.builder.finish_node();
290 }
291 _ => {
293 self.containers.stack.pop();
294 self.builder.finish_node();
295 }
296 }
297 }
298 }
299
300 fn emit_buffered_plain_if_needed(&mut self) {
303 if let Some(Container::Definition {
305 plain_open: true,
306 plain_buffer,
307 ..
308 }) = self.containers.stack.last()
309 && !plain_buffer.is_empty()
310 {
311 let text = plain_buffer.get_accumulated_text();
312 let suppress_footnote_refs = self.in_footnote_definition();
313 emit_definition_plain_or_heading(
314 &mut self.builder,
315 &text,
316 self.config,
317 suppress_footnote_refs,
318 );
319 }
320
321 if let Some(Container::Definition {
323 plain_open,
324 plain_buffer,
325 ..
326 }) = self.containers.stack.last_mut()
327 && *plain_open
328 {
329 plain_buffer.clear();
330 *plain_open = false;
331 }
332 }
333
334 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
339 let mut current = self.current_blockquote_depth();
340 while current > target_depth {
341 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
342 if self.containers.depth() == 0 {
343 break;
344 }
345 self.close_containers_to(self.containers.depth() - 1);
346 }
347 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
348 self.close_containers_to(self.containers.depth() - 1);
349 current -= 1;
350 } else {
351 break;
352 }
353 }
354 }
355
356 fn active_alert_blockquote_depth(&self) -> Option<usize> {
357 self.containers.stack.iter().rev().find_map(|c| match c {
358 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
359 _ => None,
360 })
361 }
362
363 fn in_active_alert(&self) -> bool {
364 self.active_alert_blockquote_depth().is_some()
365 }
366
367 fn previous_block_requires_blank_before_heading(&self) -> bool {
368 matches!(
369 self.containers.last(),
370 Some(Container::Paragraph { .. })
371 | Some(Container::ListItem { .. })
372 | Some(Container::Definition { .. })
373 | Some(Container::DefinitionItem { .. })
374 | Some(Container::FootnoteDefinition { .. })
375 )
376 }
377
378 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
379 let (without_newline, _) = strip_newline(content);
380 let trimmed = without_newline.trim();
381 GITHUB_ALERT_MARKERS
382 .into_iter()
383 .find(|marker| *marker == trimmed)
384 }
385
386 fn emit_list_item_buffer_if_needed(&mut self) {
389 if let Some(Container::ListItem {
390 buffer,
391 content_col,
392 ..
393 }) = self.containers.stack.last_mut()
394 && !buffer.is_empty()
395 {
396 let buffer_clone = buffer.clone();
397 let item_content_col = *content_col;
398 buffer.clear();
399 let use_paragraph = buffer_clone.has_blank_lines_between_content();
400 let suppress_footnote_refs = self.in_footnote_definition();
401 buffer_clone.emit_as_block(
402 &mut self.builder,
403 use_paragraph,
404 self.config,
405 item_content_col,
406 suppress_footnote_refs,
407 );
408 }
409 }
410
411 fn dispatch_bq_after_list_item(
428 &mut self,
429 result: super::blocks::lists::ListItemFinish,
430 ) -> usize {
431 let super::blocks::lists::ListItemFinish::BqDispatch { content } = result else {
432 return 0;
433 };
434 let pos_before = self.pos;
435 self.dispatch_list_marker_consumed = true;
440 let dispatch = self.parse_inner_content(&content, Some(&content));
441 self.dispatch_list_marker_consumed = false;
442 self.pos = pos_before;
443 match dispatch {
444 LineDispatch::Consumed(n) => n.saturating_sub(1),
445 LineDispatch::Rejected => 0,
446 }
447 }
448
449 fn maybe_open_fenced_code_in_new_list_item(&mut self) -> Option<usize> {
460 let Some(Container::ListItem {
461 content_col,
462 buffer,
463 ..
464 }) = self.containers.stack.last()
465 else {
466 return None;
467 };
468 let content_col = *content_col;
469 let text = buffer.first_text()?;
470 if buffer.segment_count() != 1 {
471 return None;
472 }
473 let text_owned = text.to_string();
474 let fence = code_blocks::try_parse_fence_open(&text_owned)?;
475 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
476 let has_info = !fence.info_string.trim().is_empty();
477 let bq_depth = self.current_blockquote_depth();
478 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
479 if !(has_info || has_matching_closer || common_mark_dialect) {
480 return None;
481 }
482 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
484 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
485 {
486 return None;
487 }
488 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
489 buffer.clear();
490 }
491 let prefix = ContainerPrefix::from_scalars(bq_depth, content_col, bq_depth > 0, 0, true);
495 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
496 let new_pos = code_blocks::parse_fenced_code_block(
497 &mut self.builder,
498 &window,
499 fence,
500 Some(&text_owned),
501 );
502 Some(new_pos.saturating_sub(self.pos).saturating_sub(1))
503 }
504
505 fn maybe_open_indented_code_in_new_list_item(&mut self) {
516 let Some(Container::ListItem {
517 content_col,
518 buffer,
519 marker_only,
520 virtual_marker_space,
521 }) = self.containers.stack.last()
522 else {
523 return;
524 };
525 if *marker_only {
526 return;
527 }
528 if buffer.segment_count() != 1 {
529 return;
530 }
531 let Some(text) = buffer.first_text() else {
532 return;
533 };
534 let content_col = *content_col;
535 let virtual_marker_space = *virtual_marker_space;
536 let text_owned = text.to_string();
537
538 let mut iter = text_owned.split_inclusive('\n');
540 let line_with_nl = iter.next().unwrap_or("").to_string();
541 if iter.next().is_some() {
542 return;
543 }
544
545 let line_no_nl = line_with_nl
546 .strip_suffix("\r\n")
547 .or_else(|| line_with_nl.strip_suffix('\n'))
548 .unwrap_or(&line_with_nl);
549 let nl_suffix = &line_with_nl[line_no_nl.len()..];
550
551 let buffer_start_col = if virtual_marker_space {
552 content_col.saturating_sub(1)
553 } else {
554 content_col
555 };
556
557 let target = content_col + 4;
558 let (cols_walked, ws_bytes) =
559 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
560
561 if buffer_start_col + cols_walked < target {
562 return;
563 }
564 if ws_bytes >= line_no_nl.len() {
565 return;
566 }
567
568 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
569 buffer.clear();
570 }
571
572 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
573 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
574 if ws_bytes > 0 {
575 self.builder
576 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
577 }
578 let rest = &line_no_nl[ws_bytes..];
579 if !rest.is_empty() {
580 self.builder.token(SyntaxKind::TEXT.into(), rest);
581 }
582 if !nl_suffix.is_empty() {
583 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
584 }
585 self.builder.finish_node();
586 self.builder.finish_node();
587 }
588
589 fn has_matching_fence_closer(
590 &self,
591 fence: &code_blocks::FenceInfo,
592 bq_depth: usize,
593 content_col: usize,
594 ) -> bool {
595 for raw_line in self.lines.iter().skip(self.pos + 1) {
596 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
597 if line_bq_depth < bq_depth {
598 break;
599 }
600 let candidate = if content_col > 0 && !inner.is_empty() {
601 let idx = byte_index_at_column(inner, content_col);
602 if idx <= inner.len() {
603 &inner[idx..]
604 } else {
605 inner
606 }
607 } else {
608 inner
609 };
610 if code_blocks::is_closing_fence(candidate, fence) {
611 return true;
612 }
613 }
614 false
615 }
616
617 fn is_paragraph_open(&self) -> bool {
619 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
620 }
621
622 fn emit_setext_heading_folding_paragraph(
630 &mut self,
631 text_line: &str,
632 underline_line: &str,
633 level: usize,
634 ) {
635 let (buffered_text, checkpoint) = match self.containers.stack.last() {
636 Some(Container::Paragraph {
637 buffer,
638 start_checkpoint,
639 ..
640 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
641 _ => (String::new(), None),
642 };
643
644 if checkpoint.is_some() {
645 self.containers.stack.pop();
646 }
647
648 let combined_text = if buffered_text.is_empty() {
649 text_line.to_string()
650 } else {
651 format!("{}{}", buffered_text, text_line)
652 };
653
654 let cp = checkpoint.expect(
655 "emit_setext_heading_folding_paragraph requires an open paragraph; \
656 single-line setext should go through the regular dispatcher path",
657 );
658 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
659 emit_setext_heading_body(
660 &mut self.builder,
661 &combined_text,
662 underline_line,
663 level,
664 self.config,
665 );
666 self.builder.finish_node();
667 }
668
669 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
687 let Some(Container::ListItem {
688 buffer,
689 content_col,
690 ..
691 }) = self.containers.stack.last()
692 else {
693 return None;
694 };
695 if buffer.segment_count() != 1 {
696 return None;
697 }
698 let text_line = buffer.first_text()?;
699
700 let content_col = *content_col;
705 let (underline_indent_cols, _) = leading_indent(content);
706 if underline_indent_cols < content_col {
707 return None;
708 }
709
710 let lines = [text_line, content];
711 let (level, _) = try_parse_setext_heading(&lines, 0)?;
712
713 let (text_no_newline, _) = strip_newline(text_line);
714 if text_no_newline.trim().is_empty() {
715 return None;
716 }
717 if try_parse_horizontal_rule(text_no_newline).is_some() {
718 return None;
719 }
720
721 let text_owned = text_line.to_string();
722 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
723 buffer.clear();
724 }
725 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
726 Some(LineDispatch::consumed(1))
727 }
728
729 fn close_paragraph_if_open(&mut self) {
731 if self.is_paragraph_open() {
732 self.close_containers_to(self.containers.depth() - 1);
733 }
734 }
735
736 fn close_paragraph_as_plain_if_open(&mut self) {
747 if !self.is_paragraph_open() {
748 return;
749 }
750 let Some(Container::Paragraph {
751 buffer,
752 start_checkpoint,
753 ..
754 }) = self.containers.stack.last()
755 else {
756 return;
757 };
758 let buffer_clone = buffer.clone();
759 let checkpoint = *start_checkpoint;
760 let suppress_footnote_refs = self.in_footnote_definition();
761 self.containers.stack.pop();
762 self.builder
763 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
764 if !buffer_clone.is_empty() {
765 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
766 }
767 self.builder.finish_node();
768 }
769
770 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
779 if self.config.dialect != crate::options::Dialect::Pandoc {
780 return false;
781 }
782 if self.block_registry.parser_name(block_match) != "html_block" {
783 return false;
784 }
785 let html_block_type = block_match
786 .payload
787 .as_ref()
788 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
789 matches!(
790 html_block_type,
791 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
792 )
793 }
794
795 fn prepare_for_block_element(&mut self) {
798 self.emit_list_item_buffer_if_needed();
799 self.close_paragraph_if_open();
800 }
801
802 fn close_open_footnote_definition(&mut self) {
806 while matches!(
807 self.containers.last(),
808 Some(Container::FootnoteDefinition { .. })
809 ) {
810 self.close_containers_to(self.containers.depth() - 1);
811 }
812 }
813
814 fn handle_footnote_open_effect(
818 &mut self,
819 block_match: &super::block_dispatcher::PreparedBlockMatch,
820 content: &str,
821 ) -> usize {
822 let content_start = block_match
823 .payload
824 .as_ref()
825 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
826 .map(|p| p.content_start)
827 .unwrap_or(0);
828
829 let content_col = 4;
830 self.containers
831 .push(Container::FootnoteDefinition { content_col });
832
833 if content_start == 0 {
834 return 0;
835 }
836 let first_line_content = &content[content_start..];
837 if first_line_content.trim().is_empty() {
838 let (_, newline_str) = strip_newline(content);
839 if !newline_str.is_empty() {
840 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
841 }
842 return 0;
843 }
844
845 if self.config.extensions.definition_lists
846 && let Some(blank_count) = footnote_first_line_term_lookahead(
847 &self.lines,
848 self.pos,
849 content_col,
850 self.config.extensions.table_captions,
851 )
852 {
853 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
854 self.containers.push(Container::DefinitionList {});
855 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
856 self.containers.push(Container::DefinitionItem {});
857 emit_term(&mut self.builder, first_line_content, self.config);
858 for i in 0..blank_count {
859 let blank_pos = self.pos + 1 + i;
860 if blank_pos < self.lines.len() {
861 let blank_line = self.lines[blank_pos];
862 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
863 self.builder
864 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
865 self.builder.finish_node();
866 }
867 }
868 return blank_count;
869 }
870
871 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
872 paragraphs::append_paragraph_line(
873 &mut self.containers,
874 &mut self.builder,
875 first_line_content,
876 self.config,
877 );
878 0
879 }
880
881 fn try_lazy_list_continuation(
893 &mut self,
894 block_match: &super::block_dispatcher::PreparedBlockMatch,
895 content: &str,
896 ) -> bool {
897 use super::block_dispatcher::ListPrepared;
898
899 let Some(prepared) = block_match
900 .payload
901 .as_ref()
902 .and_then(|p| p.downcast_ref::<ListPrepared>())
903 else {
904 return false;
905 };
906
907 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
908 return false;
909 }
910
911 let current_content_col = paragraphs::current_content_col(&self.containers);
912 if prepared.indent_cols >= current_content_col {
913 return false;
914 }
915
916 if lists::find_matching_list_level(
917 &self.containers,
918 &prepared.marker,
919 prepared.indent_cols,
920 self.config.dialect,
921 )
922 .is_some()
923 {
924 return false;
925 }
926
927 match self.containers.last() {
928 Some(Container::Paragraph { .. }) => {
929 paragraphs::append_paragraph_line(
930 &mut self.containers,
931 &mut self.builder,
932 content,
933 self.config,
934 );
935 true
936 }
937 Some(Container::ListItem { .. }) => {
938 if let Some(Container::ListItem {
939 buffer,
940 marker_only,
941 ..
942 }) = self.containers.stack.last_mut()
943 {
944 buffer.push_text(content);
945 if !content.trim().is_empty() {
946 *marker_only = false;
947 }
948 }
949 true
950 }
951 _ => false,
952 }
953 }
954
955 fn handle_list_open_effect(
961 &mut self,
962 block_match: &super::block_dispatcher::PreparedBlockMatch,
963 content: &str,
964 indent_to_emit: Option<&str>,
965 ) -> usize {
966 use super::block_dispatcher::ListPrepared;
967
968 let prepared = block_match
969 .payload
970 .as_ref()
971 .and_then(|p| p.downcast_ref::<ListPrepared>());
972 let Some(prepared) = prepared else {
973 return 0;
974 };
975
976 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
977 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
978 paragraphs::append_paragraph_line(
979 &mut self.containers,
980 &mut self.builder,
981 content,
982 self.config,
983 );
984 return 0;
985 }
986
987 if self.is_paragraph_open() {
988 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
989 paragraphs::append_paragraph_line(
990 &mut self.containers,
991 &mut self.builder,
992 content,
993 self.config,
994 );
995 return 0;
996 }
997 self.close_containers_to(self.containers.depth() - 1);
998 }
999
1000 if matches!(
1001 self.containers.last(),
1002 Some(Container::Definition {
1003 plain_open: true,
1004 ..
1005 })
1006 ) {
1007 self.emit_buffered_plain_if_needed();
1008 }
1009
1010 let matched_level = lists::find_matching_list_level(
1011 &self.containers,
1012 &prepared.marker,
1013 prepared.indent_cols,
1014 self.config.dialect,
1015 );
1016 let list_item = ListItemEmissionInput {
1017 content,
1018 marker_len: prepared.marker_len,
1019 spaces_after_cols: prepared.spaces_after_cols,
1020 spaces_after_bytes: prepared.spaces_after,
1021 indent_cols: prepared.indent_cols,
1022 indent_bytes: prepared.indent_bytes,
1023 virtual_marker_space: prepared.virtual_marker_space,
1024 };
1025 let current_content_col = paragraphs::current_content_col(&self.containers);
1026 let deep_ordered_matched_level = matched_level
1027 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1028 .and_then(|(level, container)| match container {
1029 Container::List {
1030 marker: list_marker,
1031 base_indent_cols,
1032 ..
1033 } if matches!(
1034 (&prepared.marker, list_marker),
1035 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1036 ) && prepared.indent_cols >= 4
1037 && *base_indent_cols >= 4
1038 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1039 {
1040 Some(level)
1041 }
1042 _ => None,
1043 });
1044
1045 if deep_ordered_matched_level.is_none()
1046 && current_content_col > 0
1047 && prepared.indent_cols >= current_content_col
1048 {
1049 if let Some(level) = matched_level
1050 && let Some(Container::List {
1051 base_indent_cols, ..
1052 }) = self.containers.stack.get(level)
1053 && prepared.indent_cols == *base_indent_cols
1054 {
1055 let num_parent_lists = self.containers.stack[..level]
1056 .iter()
1057 .filter(|c| matches!(c, Container::List { .. }))
1058 .count();
1059
1060 if num_parent_lists > 0 {
1061 self.close_containers_to(level + 1);
1062
1063 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1064 self.close_containers_to(self.containers.depth() - 1);
1065 }
1066 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1067 self.close_containers_to(self.containers.depth() - 1);
1068 }
1069
1070 if let Some(indent_str) = indent_to_emit {
1071 self.builder
1072 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1073 }
1074
1075 let finish = if let Some(nested_marker) = prepared.nested_marker {
1076 lists::add_list_item_with_nested_empty_list(
1077 &mut self.containers,
1078 &mut self.builder,
1079 &list_item,
1080 nested_marker,
1081 self.config,
1082 );
1083 lists::ListItemFinish::Done
1084 } else {
1085 lists::add_list_item(
1086 &mut self.containers,
1087 &mut self.builder,
1088 &list_item,
1089 self.config,
1090 )
1091 };
1092 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1093 return extras;
1094 }
1095 self.maybe_open_indented_code_in_new_list_item();
1096 return self.dispatch_bq_after_list_item(finish);
1097 }
1098 }
1099
1100 self.emit_list_item_buffer_if_needed();
1101
1102 let finish = start_nested_list(
1103 &mut self.containers,
1104 &mut self.builder,
1105 &prepared.marker,
1106 &list_item,
1107 indent_to_emit,
1108 self.config,
1109 );
1110 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1111 return extras;
1112 }
1113 self.maybe_open_indented_code_in_new_list_item();
1114 return self.dispatch_bq_after_list_item(finish);
1115 }
1116
1117 if let Some(level) = matched_level {
1118 self.close_containers_to(level + 1);
1119
1120 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1121 self.close_containers_to(self.containers.depth() - 1);
1122 }
1123 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1124 self.close_containers_to(self.containers.depth() - 1);
1125 }
1126
1127 if let Some(indent_str) = indent_to_emit {
1128 self.builder
1129 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1130 }
1131
1132 let finish = if let Some(nested_marker) = prepared.nested_marker {
1133 lists::add_list_item_with_nested_empty_list(
1134 &mut self.containers,
1135 &mut self.builder,
1136 &list_item,
1137 nested_marker,
1138 self.config,
1139 );
1140 lists::ListItemFinish::Done
1141 } else {
1142 lists::add_list_item(
1143 &mut self.containers,
1144 &mut self.builder,
1145 &list_item,
1146 self.config,
1147 )
1148 };
1149 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1150 return extras;
1151 }
1152 self.maybe_open_indented_code_in_new_list_item();
1153 return self.dispatch_bq_after_list_item(finish);
1154 }
1155
1156 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1157 self.close_containers_to(self.containers.depth() - 1);
1158 }
1159 while matches!(
1160 self.containers.last(),
1161 Some(Container::ListItem { .. } | Container::List { .. })
1162 ) {
1163 self.close_containers_to(self.containers.depth() - 1);
1164 }
1165
1166 self.builder.start_node(SyntaxKind::LIST.into());
1167 if let Some(indent_str) = indent_to_emit {
1168 self.builder
1169 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1170 }
1171 self.containers.push(Container::List {
1172 marker: prepared.marker.clone(),
1173 base_indent_cols: prepared.indent_cols,
1174 has_blank_between_items: false,
1175 });
1176
1177 let finish = if let Some(nested_marker) = prepared.nested_marker {
1178 lists::add_list_item_with_nested_empty_list(
1179 &mut self.containers,
1180 &mut self.builder,
1181 &list_item,
1182 nested_marker,
1183 self.config,
1184 );
1185 lists::ListItemFinish::Done
1186 } else {
1187 lists::add_list_item(
1188 &mut self.containers,
1189 &mut self.builder,
1190 &list_item,
1191 self.config,
1192 )
1193 };
1194 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1195 return extras;
1196 }
1197 self.maybe_open_indented_code_in_new_list_item();
1198 self.dispatch_bq_after_list_item(finish)
1199 }
1200
1201 fn handle_definition_list_effect(
1208 &mut self,
1209 block_match: &super::block_dispatcher::PreparedBlockMatch,
1210 content: &str,
1211 indent_to_emit: Option<&str>,
1212 ) -> usize {
1213 use super::block_dispatcher::DefinitionPrepared;
1214
1215 let prepared = block_match
1216 .payload
1217 .as_ref()
1218 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1219 let Some(prepared) = prepared else {
1220 return 0;
1221 };
1222
1223 let mut extras: usize = 0;
1224 match prepared {
1225 DefinitionPrepared::Definition {
1226 marker_char,
1227 indent,
1228 spaces_after,
1229 spaces_after_cols,
1230 has_content,
1231 } => {
1232 self.emit_buffered_plain_if_needed();
1233
1234 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1235 self.close_containers_to(self.containers.depth() - 1);
1236 }
1237 while matches!(self.containers.last(), Some(Container::List { .. })) {
1238 self.close_containers_to(self.containers.depth() - 1);
1239 }
1240
1241 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1242 self.close_containers_to(self.containers.depth() - 1);
1243 }
1244
1245 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1246 self.close_containers_to(self.containers.depth() - 1);
1247 }
1248
1249 if definition_lists::in_definition_list(&self.containers)
1253 && !matches!(
1254 self.containers.last(),
1255 Some(Container::DefinitionItem { .. })
1256 )
1257 {
1258 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1259 self.containers.push(Container::DefinitionItem {});
1260 }
1261
1262 if !definition_lists::in_definition_list(&self.containers) {
1263 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1264 self.containers.push(Container::DefinitionList {});
1265 }
1266
1267 if !matches!(
1268 self.containers.last(),
1269 Some(Container::DefinitionItem { .. })
1270 ) {
1271 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1272 self.containers.push(Container::DefinitionItem {});
1273 }
1274
1275 self.builder.start_node(SyntaxKind::DEFINITION.into());
1276
1277 if let Some(indent_str) = indent_to_emit {
1278 self.builder
1279 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1280 }
1281
1282 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1283 let indent_bytes = byte_index_at_column(content, *indent);
1284 if *spaces_after > 0 {
1285 let space_start = indent_bytes + 1;
1286 let space_end = space_start + *spaces_after;
1287 if space_end <= content.len() {
1288 self.builder.token(
1289 SyntaxKind::WHITESPACE.into(),
1290 &content[space_start..space_end],
1291 );
1292 }
1293 }
1294
1295 if !*has_content {
1296 let current_line = self.lines[self.pos];
1297 let (_, newline_str) = strip_newline(current_line);
1298 if !newline_str.is_empty() {
1299 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1300 }
1301 }
1302
1303 let content_col = *indent + 1 + *spaces_after_cols;
1304 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1305 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1306 let mut plain_buffer = TextBuffer::new();
1307 let mut definition_pushed = false;
1308
1309 if *has_content {
1310 let current_line = self.lines[self.pos];
1311 let (trimmed_content, _) = strip_newline(content);
1312
1313 let content_start = content_start_bytes.min(trimmed_content.len());
1320 let content_slice = &trimmed_content[content_start..];
1321 let content_line = &content[content_start_bytes.min(content.len())..];
1322
1323 let (blockquote_depth, inner_blockquote_content) =
1324 count_blockquote_markers(content_line);
1325
1326 let should_start_list_from_first_line = self
1327 .lines
1328 .get(self.pos + 1)
1329 .map(|next_line| {
1330 let (next_without_newline, _) = strip_newline(next_line);
1331 if next_without_newline.trim().is_empty() {
1332 return true;
1333 }
1334
1335 let (next_indent_cols, _) = leading_indent(next_without_newline);
1336 next_indent_cols >= content_col
1337 })
1338 .unwrap_or(true);
1339
1340 if blockquote_depth > 0 {
1341 self.containers.push(Container::Definition {
1342 content_col,
1343 plain_open: false,
1344 plain_buffer: TextBuffer::new(),
1345 });
1346 definition_pushed = true;
1347
1348 let marker_info = parse_blockquote_marker_info(content_line);
1349 for level in 0..blockquote_depth {
1350 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1351 if let Some(info) = marker_info.get(level) {
1352 blockquotes::emit_one_blockquote_marker(
1353 &mut self.builder,
1354 info.leading_spaces,
1355 info.has_trailing_space,
1356 );
1357 }
1358 self.containers.push(Container::BlockQuote {});
1359 }
1360
1361 if !inner_blockquote_content.trim().is_empty() {
1362 paragraphs::start_paragraph_if_needed(
1363 &mut self.containers,
1364 &mut self.builder,
1365 );
1366 paragraphs::append_paragraph_line(
1367 &mut self.containers,
1368 &mut self.builder,
1369 inner_blockquote_content,
1370 self.config,
1371 );
1372 }
1373 } else if let Some(marker_match) = try_parse_list_marker(
1374 content_slice,
1375 self.config,
1376 lists::open_list_hint_at_indent(
1377 &self.containers,
1378 leading_indent(content_slice).0,
1379 ),
1380 ) && should_start_list_from_first_line
1381 {
1382 self.containers.push(Container::Definition {
1383 content_col,
1384 plain_open: false,
1385 plain_buffer: TextBuffer::new(),
1386 });
1387 definition_pushed = true;
1388
1389 let (indent_cols, indent_bytes) = leading_indent(content_line);
1390 self.builder.start_node(SyntaxKind::LIST.into());
1391 self.containers.push(Container::List {
1392 marker: marker_match.marker.clone(),
1393 base_indent_cols: indent_cols,
1394 has_blank_between_items: false,
1395 });
1396
1397 let list_item = ListItemEmissionInput {
1398 content: content_line,
1399 marker_len: marker_match.marker_len,
1400 spaces_after_cols: marker_match.spaces_after_cols,
1401 spaces_after_bytes: marker_match.spaces_after_bytes,
1402 indent_cols,
1403 indent_bytes,
1404 virtual_marker_space: marker_match.virtual_marker_space,
1405 };
1406
1407 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1408 content_line,
1409 marker_match.marker_len,
1410 marker_match.spaces_after_bytes,
1411 ) {
1412 lists::add_list_item_with_nested_empty_list(
1413 &mut self.containers,
1414 &mut self.builder,
1415 &list_item,
1416 nested_marker,
1417 self.config,
1418 );
1419 lists::ListItemFinish::Done
1420 } else {
1421 lists::add_list_item(
1422 &mut self.containers,
1423 &mut self.builder,
1424 &list_item,
1425 self.config,
1426 )
1427 };
1428 extras = self.dispatch_bq_after_list_item(finish);
1429 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
1430 self.containers.push(Container::Definition {
1431 content_col,
1432 plain_open: false,
1433 plain_buffer: TextBuffer::new(),
1434 });
1435 definition_pushed = true;
1436
1437 let bq_depth = self.current_blockquote_depth();
1438 if let Some(indent_str) = indent_to_emit {
1439 self.builder
1440 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1441 }
1442 let fence_line = content[content_start..].to_string();
1443 let prefix = ContainerPrefix::from_scalars(
1447 bq_depth,
1448 0,
1449 bq_depth > 0,
1450 content_col,
1451 false,
1452 );
1453 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
1454 let new_pos = if self.config.extensions.tex_math_gfm
1455 && code_blocks::is_gfm_math_fence(&fence)
1456 {
1457 code_blocks::parse_fenced_math_block(
1458 &mut self.builder,
1459 &window,
1460 fence,
1461 Some(&fence_line),
1462 )
1463 } else {
1464 code_blocks::parse_fenced_code_block(
1465 &mut self.builder,
1466 &window,
1467 fence,
1468 Some(&fence_line),
1469 )
1470 };
1471 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1472 } else {
1473 let (_, newline_str) = strip_newline(current_line);
1474 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1475 if content_without_newline.is_empty() {
1476 plain_buffer.push_line(newline_str);
1477 } else {
1478 let line_with_newline = if !newline_str.is_empty() {
1479 format!("{}{}", content_without_newline, newline_str)
1480 } else {
1481 content_without_newline.to_string()
1482 };
1483 plain_buffer.push_line(line_with_newline);
1484 }
1485 }
1486 }
1487
1488 if !definition_pushed {
1489 self.containers.push(Container::Definition {
1490 content_col,
1491 plain_open: *has_content,
1492 plain_buffer,
1493 });
1494 }
1495 }
1496 DefinitionPrepared::Term { blank_count } => {
1497 self.emit_buffered_plain_if_needed();
1498
1499 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1500 self.close_containers_to(self.containers.depth() - 1);
1501 }
1502
1503 if !definition_lists::in_definition_list(&self.containers) {
1504 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1505 self.containers.push(Container::DefinitionList {});
1506 }
1507
1508 while matches!(
1509 self.containers.last(),
1510 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1511 ) {
1512 self.close_containers_to(self.containers.depth() - 1);
1513 }
1514
1515 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1516 self.containers.push(Container::DefinitionItem {});
1517
1518 emit_term(&mut self.builder, content, self.config);
1519
1520 for i in 0..*blank_count {
1521 let blank_pos = self.pos + 1 + i;
1522 if blank_pos < self.lines.len() {
1523 let blank_line = self.lines[blank_pos];
1524 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1525 self.builder
1526 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1527 self.builder.finish_node();
1528 }
1529 }
1530 extras = *blank_count;
1531 }
1532 };
1533 extras
1534 }
1535
1536 fn blockquote_marker_info(
1538 &self,
1539 payload: Option<&BlockQuotePrepared>,
1540 line: &str,
1541 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1542 payload
1543 .map(|payload| payload.marker_info.clone())
1544 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1545 }
1546
1547 fn marker_info_for_line(
1553 &self,
1554 payload: Option<&BlockQuotePrepared>,
1555 raw_line: &str,
1556 marker_line: &str,
1557 shifted_prefix: &str,
1558 used_shifted: bool,
1559 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1560 let mut marker_info = if used_shifted {
1561 parse_blockquote_marker_info(marker_line)
1562 } else {
1563 self.blockquote_marker_info(payload, raw_line)
1564 };
1565 if used_shifted && !shifted_prefix.is_empty() {
1566 let (prefix_cols, _) = leading_indent(shifted_prefix);
1567 if let Some(first) = marker_info.first_mut() {
1568 first.leading_spaces += prefix_cols;
1569 }
1570 }
1571 marker_info
1572 }
1573
1574 fn shifted_blockquote_from_list<'b>(
1577 &self,
1578 line: &'b str,
1579 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1580 let list_content_col = self
1589 .containers
1590 .stack
1591 .iter()
1592 .rev()
1593 .find_map(|c| match c {
1594 Container::ListItem { content_col, .. } => Some(*content_col),
1595 _ => None,
1596 })
1597 .unwrap_or(0);
1598 let content_container_indent = self.content_container_indent_to_strip();
1599 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1607 return None;
1608 }
1609 let marker_col = list_content_col.saturating_add(content_container_indent);
1610 if marker_col == 0 {
1611 return None;
1612 }
1613
1614 let (indent_cols, _) = leading_indent(line);
1615 if indent_cols < marker_col {
1616 return None;
1617 }
1618
1619 let idx = byte_index_at_column(line, marker_col);
1620 if idx > line.len() {
1621 return None;
1622 }
1623
1624 let candidate = &line[idx..];
1625 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1626 if candidate_depth == 0 {
1627 return None;
1628 }
1629
1630 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1631 }
1632
1633 fn emit_blockquote_markers(
1634 &mut self,
1635 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1636 depth: usize,
1637 ) {
1638 for i in 0..depth {
1639 if let Some(info) = marker_info.get(i) {
1640 blockquotes::emit_one_blockquote_marker(
1641 &mut self.builder,
1642 info.leading_spaces,
1643 info.has_trailing_space,
1644 );
1645 }
1646 }
1647 }
1648
1649 fn current_blockquote_depth(&self) -> usize {
1650 blockquotes::current_blockquote_depth(&self.containers)
1651 }
1652
1653 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1661 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1662 return None;
1663 };
1664 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1665 }
1666
1667 fn emit_or_buffer_blockquote_marker(
1672 &mut self,
1673 leading_spaces: usize,
1674 has_trailing_space: bool,
1675 ) {
1676 if let Some(Container::ListItem {
1677 buffer,
1678 marker_only,
1679 ..
1680 }) = self.containers.stack.last_mut()
1681 {
1682 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1683 *marker_only = false;
1684 return;
1685 }
1686
1687 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1689 paragraphs::append_paragraph_marker(
1691 &mut self.containers,
1692 leading_spaces,
1693 has_trailing_space,
1694 );
1695 } else {
1696 blockquotes::emit_one_blockquote_marker(
1698 &mut self.builder,
1699 leading_spaces,
1700 has_trailing_space,
1701 );
1702 }
1703 }
1704
1705 fn parse_document_stack(&mut self) {
1706 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1707
1708 log::trace!("Starting document parse");
1709
1710 while self.pos < self.lines.len() {
1713 let line = self.lines[self.pos];
1714
1715 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1716
1717 match self.parse_line(line) {
1718 LineDispatch::Consumed(n) => self.pos += n,
1719 LineDispatch::Rejected => self.pos += 1,
1720 }
1721 }
1722
1723 self.close_containers_to(0);
1724 self.builder.finish_node(); }
1726
1727 fn parse_line(&mut self, line: &str) -> LineDispatch {
1731 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1734 let mut bq_marker_line = line;
1735 let mut shifted_bq_prefix = "";
1736 let mut used_shifted_bq = false;
1737 if bq_depth == 0
1738 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1739 self.shifted_blockquote_from_list(line)
1740 {
1741 bq_depth = candidate_depth;
1742 inner_content = candidate_inner;
1743 bq_marker_line = candidate_line;
1744 shifted_bq_prefix = candidate_prefix;
1745 used_shifted_bq = true;
1746 }
1747 let current_bq_depth = self.current_blockquote_depth();
1748
1749 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1750 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1751 let dispatcher_ctx = if current_bq_depth == 0 {
1752 Some(BlockContext {
1753 has_blank_before,
1754 has_blank_before_strict: has_blank_before,
1755 at_document_start: self.pos == 0,
1756 in_fenced_div: self.in_fenced_div(),
1757 blockquote_depth: current_bq_depth,
1758 config: self.config,
1759 content_indent: 0,
1760 indent_to_emit: None,
1761 list_indent_info: None,
1762 in_list: lists::in_list(&self.containers),
1763 in_marker_only_list_item: matches!(
1764 self.containers.last(),
1765 Some(Container::ListItem {
1766 marker_only: true,
1767 ..
1768 })
1769 ),
1770 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1771 paragraph_open: self.is_paragraph_open(),
1772 next_line: if self.pos + 1 < self.lines.len() {
1773 Some(self.lines[self.pos + 1])
1774 } else {
1775 None
1776 },
1777 open_alpha_hint: lists::open_list_hint_at_indent(
1778 &self.containers,
1779 leading_indent(line).0,
1780 ),
1781 })
1782 } else {
1783 None
1784 };
1785
1786 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1787 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
1788 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
1789 self.block_registry
1790 .detect_prepared(dispatcher_ctx, &stripped)
1791 .and_then(|prepared| {
1792 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1793 blockquote_match = Some(prepared);
1794 blockquote_match.as_ref().and_then(|prepared| {
1795 prepared
1796 .payload
1797 .as_ref()
1798 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1799 .cloned()
1800 })
1801 } else {
1802 None
1803 }
1804 })
1805 } else {
1806 None
1807 };
1808
1809 log::trace!(
1810 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1811 self.pos,
1812 bq_depth,
1813 current_bq_depth,
1814 self.containers.depth(),
1815 line.trim_end()
1816 );
1817
1818 let inner_blank_in_blockquote = bq_depth > 0
1825 && is_blank_line(inner_content)
1826 && (current_bq_depth > 0
1827 || !self.config.extensions.blank_before_blockquote
1828 || blockquotes::can_start_blockquote(
1829 self.pos,
1830 &self.lines,
1831 self.config.extensions.fenced_divs,
1832 ));
1833 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1834
1835 if is_blank {
1836 if self.is_paragraph_open()
1837 && paragraphs::has_open_inline_math_environment(&self.containers)
1838 {
1839 paragraphs::append_paragraph_line(
1840 &mut self.containers,
1841 &mut self.builder,
1842 line,
1843 self.config,
1844 );
1845 return LineDispatch::consumed(1);
1846 }
1847
1848 self.close_paragraph_if_open();
1850
1851 self.emit_buffered_plain_if_needed();
1855
1856 if bq_depth > current_bq_depth {
1864 for _ in current_bq_depth..bq_depth {
1866 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1867 self.containers.push(Container::BlockQuote {});
1868 }
1869 } else if bq_depth < current_bq_depth {
1870 self.close_blockquotes_to_depth(bq_depth);
1872 }
1873
1874 let mut peek = self.pos + 1;
1881 while peek < self.lines.len() {
1882 let peek_line = self.lines[peek];
1883 if is_blank_line(peek_line) {
1884 peek += 1;
1885 continue;
1886 }
1887 if bq_depth > 0 {
1888 let (peek_bq, _) = count_blockquote_markers(peek_line);
1889 if peek_bq >= bq_depth {
1890 let peek_inner =
1891 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1892 if is_blank_line(peek_inner) {
1893 peek += 1;
1894 continue;
1895 }
1896 }
1897 }
1898 break;
1899 }
1900
1901 let levels_to_keep = if peek < self.lines.len() {
1903 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1904 self.current_blockquote_depth(),
1905 &self.containers,
1906 &self.lines,
1907 peek,
1908 self.lines[peek],
1909 )
1910 } else {
1911 0
1912 };
1913 log::trace!(
1914 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1915 self.containers.depth(),
1916 levels_to_keep,
1917 if peek < self.lines.len() {
1918 self.lines[peek]
1919 } else {
1920 "<EOF>"
1921 }
1922 );
1923
1924 while self.containers.depth() > levels_to_keep {
1928 match self.containers.last() {
1929 Some(Container::ListItem { .. }) => {
1930 log::trace!(
1932 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1933 levels_to_keep,
1934 self.containers.depth()
1935 );
1936 self.close_containers_to(self.containers.depth() - 1);
1937 }
1938 Some(Container::List { .. })
1939 | Some(Container::FootnoteDefinition { .. })
1940 | Some(Container::Alert { .. })
1941 | Some(Container::Paragraph { .. })
1942 | Some(Container::Definition { .. })
1943 | Some(Container::DefinitionItem { .. })
1944 | Some(Container::DefinitionList { .. }) => {
1945 log::trace!(
1946 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1947 self.containers.last(),
1948 self.containers.depth(),
1949 levels_to_keep
1950 );
1951
1952 self.close_containers_to(self.containers.depth() - 1);
1953 }
1954 _ => break,
1955 }
1956 }
1957
1958 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1962 self.emit_list_item_buffer_if_needed();
1963 }
1964
1965 if bq_depth > 0 {
1967 let marker_info = self.marker_info_for_line(
1968 blockquote_payload.as_ref(),
1969 line,
1970 bq_marker_line,
1971 shifted_bq_prefix,
1972 used_shifted_bq,
1973 );
1974 self.emit_blockquote_markers(&marker_info, bq_depth);
1975 }
1976
1977 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1978 self.builder
1979 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1980 self.builder.finish_node();
1981
1982 return LineDispatch::consumed(1);
1983 }
1984
1985 if bq_depth > current_bq_depth {
1987 if self.config.extensions.blank_before_blockquote
1990 && current_bq_depth == 0
1991 && !used_shifted_bq
1992 && !blockquote_payload
1993 .as_ref()
1994 .map(|payload| payload.can_start)
1995 .unwrap_or_else(|| {
1996 blockquotes::can_start_blockquote(
1997 self.pos,
1998 &self.lines,
1999 self.config.extensions.fenced_divs,
2000 )
2001 })
2002 {
2003 self.emit_list_item_buffer_if_needed();
2007 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2008 paragraphs::append_paragraph_line(
2009 &mut self.containers,
2010 &mut self.builder,
2011 line,
2012 self.config,
2013 );
2014 return LineDispatch::consumed(1);
2015 }
2016
2017 let can_nest = if current_bq_depth > 0 {
2020 if self.config.extensions.blank_before_blockquote {
2021 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2023 || (self.pos > 0 && {
2024 let prev_line = self.lines[self.pos - 1];
2025 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2026 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2027 })
2028 } else {
2029 true
2030 }
2031 } else {
2032 blockquote_payload
2033 .as_ref()
2034 .map(|payload| payload.can_nest)
2035 .unwrap_or(true)
2036 };
2037
2038 if !can_nest {
2039 let content_at_current_depth =
2042 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2043
2044 let marker_info = self.marker_info_for_line(
2046 blockquote_payload.as_ref(),
2047 line,
2048 bq_marker_line,
2049 shifted_bq_prefix,
2050 used_shifted_bq,
2051 );
2052 for i in 0..current_bq_depth {
2053 if let Some(info) = marker_info.get(i) {
2054 self.emit_or_buffer_blockquote_marker(
2055 info.leading_spaces,
2056 info.has_trailing_space,
2057 );
2058 }
2059 }
2060
2061 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2062 paragraphs::append_paragraph_line(
2064 &mut self.containers,
2065 &mut self.builder,
2066 content_at_current_depth,
2067 self.config,
2068 );
2069 return LineDispatch::consumed(1);
2070 } else {
2071 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2073 paragraphs::append_paragraph_line(
2074 &mut self.containers,
2075 &mut self.builder,
2076 content_at_current_depth,
2077 self.config,
2078 );
2079 return LineDispatch::consumed(1);
2080 }
2081 }
2082
2083 self.emit_list_item_buffer_if_needed();
2086
2087 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2089 self.close_containers_to(self.containers.depth() - 1);
2090 }
2091
2092 let marker_info = self.marker_info_for_line(
2094 blockquote_payload.as_ref(),
2095 line,
2096 bq_marker_line,
2097 shifted_bq_prefix,
2098 used_shifted_bq,
2099 );
2100
2101 if let (Some(dispatcher_ctx), Some(prepared)) =
2102 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2103 {
2104 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2105 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2106 let _ = self.block_registry.parse_prepared(
2107 prepared,
2108 dispatcher_ctx,
2109 &mut self.builder,
2110 &stripped,
2111 );
2112 for _ in 0..bq_depth {
2113 self.containers.push(Container::BlockQuote {});
2114 }
2115 } else {
2116 for level in 0..current_bq_depth {
2118 if let Some(info) = marker_info.get(level) {
2119 self.emit_or_buffer_blockquote_marker(
2120 info.leading_spaces,
2121 info.has_trailing_space,
2122 );
2123 }
2124 }
2125
2126 for level in current_bq_depth..bq_depth {
2128 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2129
2130 if let Some(info) = marker_info.get(level) {
2132 blockquotes::emit_one_blockquote_marker(
2133 &mut self.builder,
2134 info.leading_spaces,
2135 info.has_trailing_space,
2136 );
2137 }
2138
2139 self.containers.push(Container::BlockQuote {});
2140 }
2141 }
2142
2143 let prev_flag = self.dispatch_list_marker_consumed;
2156 if used_shifted_bq && !self.innermost_li_above_bq() {
2157 self.dispatch_list_marker_consumed = true;
2158 }
2159 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2160 self.dispatch_list_marker_consumed = prev_flag;
2161 return dispatch;
2162 } else if bq_depth < current_bq_depth {
2163 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2169 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2176 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2177 let interrupts_via_fence =
2178 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2179 let interrupts_via_div_close = self.config.extensions.fenced_divs
2184 && self.in_fenced_div()
2185 && fenced_divs::is_div_closing_fence(line);
2186 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2187 if bq_depth > 0 {
2188 let marker_info = self.marker_info_for_line(
2194 blockquote_payload.as_ref(),
2195 line,
2196 bq_marker_line,
2197 shifted_bq_prefix,
2198 used_shifted_bq,
2199 );
2200 for i in 0..bq_depth {
2201 if let Some(info) = marker_info.get(i) {
2202 paragraphs::append_paragraph_marker(
2203 &mut self.containers,
2204 info.leading_spaces,
2205 info.has_trailing_space,
2206 );
2207 }
2208 }
2209 paragraphs::append_paragraph_line(
2210 &mut self.containers,
2211 &mut self.builder,
2212 inner_content,
2213 self.config,
2214 );
2215 } else {
2216 paragraphs::append_paragraph_line(
2217 &mut self.containers,
2218 &mut self.builder,
2219 line,
2220 self.config,
2221 );
2222 }
2223 return LineDispatch::consumed(1);
2224 }
2225 }
2226 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2234 && lists::in_blockquote_list(&self.containers)
2235 && try_parse_list_marker(
2236 line,
2237 self.config,
2238 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2239 )
2240 .is_none()
2241 {
2242 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2243 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2244 let interrupts_via_fence =
2245 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2246 if !interrupts_via_hr && !interrupts_via_fence {
2247 if bq_depth > 0 {
2248 let marker_info = self.marker_info_for_line(
2249 blockquote_payload.as_ref(),
2250 line,
2251 bq_marker_line,
2252 shifted_bq_prefix,
2253 used_shifted_bq,
2254 );
2255 if let Some(Container::ListItem {
2256 buffer,
2257 marker_only,
2258 ..
2259 }) = self.containers.stack.last_mut()
2260 {
2261 for i in 0..bq_depth {
2262 if let Some(info) = marker_info.get(i) {
2263 buffer.push_blockquote_marker(
2264 info.leading_spaces,
2265 info.has_trailing_space,
2266 );
2267 }
2268 }
2269 buffer.push_text(inner_content);
2270 if !inner_content.trim().is_empty() {
2271 *marker_only = false;
2272 }
2273 }
2274 } else if let Some(Container::ListItem {
2275 buffer,
2276 marker_only,
2277 ..
2278 }) = self.containers.stack.last_mut()
2279 {
2280 buffer.push_text(line);
2281 if !line.trim().is_empty() {
2282 *marker_only = false;
2283 }
2284 }
2285 return LineDispatch::consumed(1);
2286 }
2287 }
2288 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2294 if lists::in_blockquote_list(&self.containers)
2297 && let Some(marker_match) = try_parse_list_marker(
2298 line,
2299 self.config,
2300 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2301 )
2302 {
2303 let (indent_cols, indent_bytes) = leading_indent(line);
2304 if let Some(level) = lists::find_matching_list_level(
2305 &self.containers,
2306 &marker_match.marker,
2307 indent_cols,
2308 self.config.dialect,
2309 ) {
2310 self.close_containers_to(level + 1);
2313
2314 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2316 self.close_containers_to(self.containers.depth() - 1);
2317 }
2318 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2319 self.close_containers_to(self.containers.depth() - 1);
2320 }
2321
2322 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2324 line,
2325 marker_match.marker_len,
2326 marker_match.spaces_after_bytes,
2327 ) {
2328 let list_item = ListItemEmissionInput {
2329 content: line,
2330 marker_len: marker_match.marker_len,
2331 spaces_after_cols: marker_match.spaces_after_cols,
2332 spaces_after_bytes: marker_match.spaces_after_bytes,
2333 indent_cols,
2334 indent_bytes,
2335 virtual_marker_space: marker_match.virtual_marker_space,
2336 };
2337 lists::add_list_item_with_nested_empty_list(
2338 &mut self.containers,
2339 &mut self.builder,
2340 &list_item,
2341 nested_marker,
2342 self.config,
2343 );
2344 0
2345 } else {
2346 let list_item = ListItemEmissionInput {
2347 content: line,
2348 marker_len: marker_match.marker_len,
2349 spaces_after_cols: marker_match.spaces_after_cols,
2350 spaces_after_bytes: marker_match.spaces_after_bytes,
2351 indent_cols,
2352 indent_bytes,
2353 virtual_marker_space: marker_match.virtual_marker_space,
2354 };
2355 let finish = lists::add_list_item(
2356 &mut self.containers,
2357 &mut self.builder,
2358 &list_item,
2359 self.config,
2360 );
2361 self.dispatch_bq_after_list_item(finish)
2362 };
2363 return LineDispatch::consumed(1 + extras);
2364 }
2365 }
2366 }
2367
2368 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2370 self.close_containers_to(self.containers.depth() - 1);
2371 }
2372
2373 self.close_blockquotes_to_depth(bq_depth);
2375
2376 if bq_depth > 0 {
2378 let marker_info = self.marker_info_for_line(
2380 blockquote_payload.as_ref(),
2381 line,
2382 bq_marker_line,
2383 shifted_bq_prefix,
2384 used_shifted_bq,
2385 );
2386 for i in 0..bq_depth {
2387 if let Some(info) = marker_info.get(i) {
2388 self.emit_or_buffer_blockquote_marker(
2389 info.leading_spaces,
2390 info.has_trailing_space,
2391 );
2392 }
2393 }
2394 return self.parse_inner_content(inner_content, Some(inner_content));
2396 } else {
2397 return self.parse_inner_content(line, None);
2399 }
2400 } else if bq_depth > 0 {
2401 let mut list_item_continuation = false;
2403 let same_depth_marker_info = self.marker_info_for_line(
2404 blockquote_payload.as_ref(),
2405 line,
2406 bq_marker_line,
2407 shifted_bq_prefix,
2408 used_shifted_bq,
2409 );
2410 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2411
2412 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2424 if let Some(marker_match) = try_parse_list_marker(
2425 inner_content,
2426 self.config,
2427 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2428 ) {
2429 let inner_content_threshold =
2433 marker_match.marker_len + marker_match.spaces_after_cols;
2434 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2435 let sibling_list_level = if is_sibling_candidate {
2436 self.containers
2437 .stack
2438 .iter()
2439 .enumerate()
2440 .rev()
2441 .find_map(|(i, c)| match c {
2442 Container::List { marker, .. }
2443 if lists::markers_match(
2444 &marker_match.marker,
2445 marker,
2446 self.config.dialect,
2447 ) && self.containers.stack[..i]
2448 .iter()
2449 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2450 .count()
2451 == bq_depth =>
2452 {
2453 Some(i)
2454 }
2455 _ => None,
2456 })
2457 } else {
2458 None
2459 };
2460 if let Some(list_level) = sibling_list_level {
2461 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2467 Some(Container::List {
2468 base_indent_cols, ..
2469 }) => *base_indent_cols,
2470 _ => 0,
2471 };
2472
2473 self.emit_list_item_buffer_if_needed();
2475 self.close_containers_to(list_level + 1);
2478
2479 for i in 0..bq_depth {
2483 if let Some(info) = same_depth_marker_info.get(i) {
2484 self.emit_or_buffer_blockquote_marker(
2485 info.leading_spaces,
2486 info.has_trailing_space,
2487 );
2488 }
2489 }
2490
2491 let list_item = ListItemEmissionInput {
2493 content: inner_content,
2494 marker_len: marker_match.marker_len,
2495 spaces_after_cols: marker_match.spaces_after_cols,
2496 spaces_after_bytes: marker_match.spaces_after_bytes,
2497 indent_cols: sibling_base_indent_cols,
2498 indent_bytes: inner_indent_bytes,
2499 virtual_marker_space: marker_match.virtual_marker_space,
2500 };
2501 let finish = lists::add_list_item(
2502 &mut self.containers,
2503 &mut self.builder,
2504 &list_item,
2505 self.config,
2506 );
2507 let extras =
2508 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
2509 extras
2510 } else {
2511 self.maybe_open_indented_code_in_new_list_item();
2512 self.dispatch_bq_after_list_item(finish)
2513 };
2514 return LineDispatch::consumed(1 + extras);
2515 }
2516 }
2517
2518 if matches!(
2521 self.containers.last(),
2522 Some(Container::ListItem { content_col: _, .. })
2523 ) {
2524 let (indent_cols, _) = leading_indent(inner_content);
2525 let content_indent = self.content_container_indent_to_strip();
2526 let effective_indent = indent_cols.saturating_sub(content_indent);
2527 let content_col = match self.containers.last() {
2528 Some(Container::ListItem { content_col, .. }) => *content_col,
2529 _ => 0,
2530 };
2531
2532 let is_new_item_at_outer_level = if try_parse_list_marker(
2534 inner_content,
2535 self.config,
2536 lists::open_list_hint_at_indent(
2537 &self.containers,
2538 leading_indent(inner_content).0,
2539 ),
2540 )
2541 .is_some()
2542 {
2543 effective_indent < content_col
2544 } else {
2545 false
2546 };
2547
2548 if is_new_item_at_outer_level
2552 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2553 {
2554 log::trace!(
2555 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2556 is_new_item_at_outer_level,
2557 effective_indent,
2558 content_col
2559 );
2560 self.close_containers_to(self.containers.depth() - 1);
2561 } else {
2562 log::trace!(
2563 "Keeping ListItem: effective_indent={} >= content_col={}",
2564 effective_indent,
2565 content_col
2566 );
2567 list_item_continuation = true;
2568 }
2569 }
2570
2571 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
2575 {
2576 list_item_continuation = false;
2577 }
2578
2579 let continuation_has_explicit_marker = list_item_continuation && {
2580 if has_explicit_same_depth_marker {
2581 for i in 0..bq_depth {
2582 if let Some(info) = same_depth_marker_info.get(i) {
2583 self.emit_or_buffer_blockquote_marker(
2584 info.leading_spaces,
2585 info.has_trailing_space,
2586 );
2587 }
2588 }
2589 true
2590 } else {
2591 false
2592 }
2593 };
2594
2595 if !list_item_continuation {
2596 let marker_info = self.marker_info_for_line(
2597 blockquote_payload.as_ref(),
2598 line,
2599 bq_marker_line,
2600 shifted_bq_prefix,
2601 used_shifted_bq,
2602 );
2603 for i in 0..bq_depth {
2604 if let Some(info) = marker_info.get(i) {
2605 self.emit_or_buffer_blockquote_marker(
2606 info.leading_spaces,
2607 info.has_trailing_space,
2608 );
2609 }
2610 }
2611 }
2612 let line_to_append = if list_item_continuation {
2613 if continuation_has_explicit_marker {
2614 Some(inner_content)
2615 } else {
2616 Some(line)
2617 }
2618 } else {
2619 Some(inner_content)
2620 };
2621 let prev_flag = self.dispatch_list_marker_consumed;
2627 if used_shifted_bq && !self.innermost_li_above_bq() {
2628 self.dispatch_list_marker_consumed = true;
2629 }
2630 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2631 self.dispatch_list_marker_consumed = prev_flag;
2632 return dispatch;
2633 }
2634
2635 if current_bq_depth > 0 {
2638 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2640 paragraphs::append_paragraph_line(
2641 &mut self.containers,
2642 &mut self.builder,
2643 line,
2644 self.config,
2645 );
2646 return LineDispatch::consumed(1);
2647 }
2648
2649 if lists::in_blockquote_list(&self.containers)
2651 && let Some(marker_match) = try_parse_list_marker(
2652 line,
2653 self.config,
2654 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2655 )
2656 {
2657 let (indent_cols, indent_bytes) = leading_indent(line);
2658 if let Some(level) = lists::find_matching_list_level(
2659 &self.containers,
2660 &marker_match.marker,
2661 indent_cols,
2662 self.config.dialect,
2663 ) {
2664 self.close_containers_to(level + 1);
2666
2667 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2669 self.close_containers_to(self.containers.depth() - 1);
2670 }
2671 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2672 self.close_containers_to(self.containers.depth() - 1);
2673 }
2674
2675 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2677 line,
2678 marker_match.marker_len,
2679 marker_match.spaces_after_bytes,
2680 ) {
2681 let list_item = ListItemEmissionInput {
2682 content: line,
2683 marker_len: marker_match.marker_len,
2684 spaces_after_cols: marker_match.spaces_after_cols,
2685 spaces_after_bytes: marker_match.spaces_after_bytes,
2686 indent_cols,
2687 indent_bytes,
2688 virtual_marker_space: marker_match.virtual_marker_space,
2689 };
2690 lists::add_list_item_with_nested_empty_list(
2691 &mut self.containers,
2692 &mut self.builder,
2693 &list_item,
2694 nested_marker,
2695 self.config,
2696 );
2697 0
2698 } else {
2699 let list_item = ListItemEmissionInput {
2700 content: line,
2701 marker_len: marker_match.marker_len,
2702 spaces_after_cols: marker_match.spaces_after_cols,
2703 spaces_after_bytes: marker_match.spaces_after_bytes,
2704 indent_cols,
2705 indent_bytes,
2706 virtual_marker_space: marker_match.virtual_marker_space,
2707 };
2708 let finish = lists::add_list_item(
2709 &mut self.containers,
2710 &mut self.builder,
2711 &list_item,
2712 self.config,
2713 );
2714 self.dispatch_bq_after_list_item(finish)
2715 };
2716 return LineDispatch::consumed(1 + extras);
2717 }
2718 }
2719 }
2720
2721 self.parse_inner_content(line, None)
2723 }
2724
2725 fn content_container_indent_to_strip(&self) -> usize {
2727 self.containers
2728 .stack
2729 .iter()
2730 .filter_map(|c| match c {
2731 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2732 Container::Definition { content_col, .. } => Some(*content_col),
2733 _ => None,
2734 })
2735 .sum()
2736 }
2737
2738 fn innermost_li_above_bq(&self) -> bool {
2745 for c in self.containers.stack.iter().rev() {
2746 match c {
2747 Container::ListItem { .. } => return true,
2748 Container::BlockQuote { .. } => return false,
2749 _ => continue,
2750 }
2751 }
2752 false
2753 }
2754
2755 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2761 log::trace!(
2762 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2763 self.pos,
2764 self.containers.depth(),
2765 self.containers.last(),
2766 content.trim_end()
2767 );
2768 let content_indent = self.content_container_indent_to_strip();
2773 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
2774
2775 if self.config.extensions.alerts
2776 && self.current_blockquote_depth() > 0
2777 && !self.in_active_alert()
2778 && !self.is_paragraph_open()
2779 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2780 {
2781 let (_, newline_str) = strip_newline(stripped_content);
2782 self.builder.start_node(SyntaxKind::ALERT.into());
2783 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2784 if !newline_str.is_empty() {
2785 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2786 }
2787 self.containers.push(Container::Alert {
2788 blockquote_depth: self.current_blockquote_depth(),
2789 });
2790 return LineDispatch::consumed(1);
2791 }
2792
2793 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2797 let is_definition_marker =
2798 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2799 && !stripped_content.starts_with(':');
2800 if content_indent == 0 && is_definition_marker {
2801 } else {
2803 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2804
2805 if policy.definition_plain_can_continue(
2806 stripped_content,
2807 content,
2808 content_indent,
2809 &BlockContext {
2810 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2811 has_blank_before_strict: self.pos == 0
2812 || is_blank_line(self.lines[self.pos - 1]),
2813 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2814 in_fenced_div: self.in_fenced_div(),
2815 blockquote_depth: self.current_blockquote_depth(),
2816 config: self.config,
2817 content_indent,
2818 indent_to_emit: None,
2819 list_indent_info: None,
2820 in_list: lists::in_list(&self.containers),
2821 in_marker_only_list_item: matches!(
2822 self.containers.last(),
2823 Some(Container::ListItem {
2824 marker_only: true,
2825 ..
2826 })
2827 ),
2828 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2829 paragraph_open: self.is_paragraph_open(),
2830 next_line: if self.pos + 1 < self.lines.len() {
2831 Some(self.lines[self.pos + 1])
2832 } else {
2833 None
2834 },
2835 open_alpha_hint: lists::open_list_hint_at_indent(
2836 &self.containers,
2837 leading_indent(stripped_content).0,
2838 ),
2839 },
2840 &self.lines,
2841 self.pos,
2842 ) {
2843 let content_line = stripped_content;
2844 let (text_without_newline, newline_str) = strip_newline(content_line);
2845 let indent_prefix = if !text_without_newline.trim().is_empty() {
2846 indent_to_emit.unwrap_or("")
2847 } else {
2848 ""
2849 };
2850 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2851
2852 if let Some(Container::Definition {
2853 plain_open,
2854 plain_buffer,
2855 ..
2856 }) = self.containers.stack.last_mut()
2857 {
2858 let line_with_newline = if !newline_str.is_empty() {
2859 format!("{}{}", content_line, newline_str)
2860 } else {
2861 content_line
2862 };
2863 plain_buffer.push_line(line_with_newline);
2864 *plain_open = true;
2865 }
2866
2867 return LineDispatch::consumed(1);
2868 }
2869 }
2870 }
2871
2872 if content_indent > 0 {
2875 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2876 let current_bq_depth = self.current_blockquote_depth();
2877 let in_footnote_definition = self
2878 .containers
2879 .stack
2880 .iter()
2881 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2882
2883 if bq_depth > 0 {
2884 if in_footnote_definition
2885 && self.config.extensions.blank_before_blockquote
2886 && current_bq_depth == 0
2887 && !blockquotes::can_start_blockquote(
2888 self.pos,
2889 &self.lines,
2890 self.config.extensions.fenced_divs,
2891 )
2892 {
2893 } else {
2897 self.emit_buffered_plain_if_needed();
2900 self.emit_list_item_buffer_if_needed();
2901
2902 self.close_paragraph_if_open();
2905
2906 if bq_depth < current_bq_depth {
2907 self.close_blockquotes_to_depth(bq_depth);
2908 } else {
2909 let marker_info = parse_blockquote_marker_info(stripped_content);
2910
2911 if bq_depth > current_bq_depth {
2912 for level in current_bq_depth..bq_depth {
2914 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2915
2916 if level == current_bq_depth
2917 && let Some(indent_str) = indent_to_emit
2918 {
2919 self.builder
2920 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2921 }
2922
2923 if let Some(info) = marker_info.get(level) {
2924 blockquotes::emit_one_blockquote_marker(
2925 &mut self.builder,
2926 info.leading_spaces,
2927 info.has_trailing_space,
2928 );
2929 }
2930
2931 self.containers.push(Container::BlockQuote {});
2932 }
2933 } else {
2934 self.emit_blockquote_markers(&marker_info, bq_depth);
2936 }
2937 }
2938
2939 return self.parse_inner_content(inner_content, Some(inner_content));
2940 }
2941 }
2942 }
2943
2944 let content = stripped_content;
2946
2947 if self.is_paragraph_open()
2948 && (paragraphs::has_open_inline_math_environment(&self.containers)
2949 || paragraphs::has_open_display_math_dollars(&self.containers))
2950 {
2951 paragraphs::append_paragraph_line(
2952 &mut self.containers,
2953 &mut self.builder,
2954 line_to_append.unwrap_or(self.lines[self.pos]),
2955 self.config,
2956 );
2957 return LineDispatch::consumed(1);
2958 }
2959
2960 use super::blocks::lists;
2964 use super::blocks::paragraphs;
2965 let list_indent_info = if lists::in_list(&self.containers) {
2966 let content_col = paragraphs::current_content_col(&self.containers);
2967 if content_col > 0 {
2968 Some(super::block_dispatcher::ListIndentInfo { content_col })
2969 } else {
2970 None
2971 }
2972 } else {
2973 None
2974 };
2975
2976 let next_line = if self.pos + 1 < self.lines.len() {
2977 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2980 } else {
2981 None
2982 };
2983
2984 let current_bq_depth = self.current_blockquote_depth();
2985 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2986 && current_bq_depth < alert_bq_depth
2987 {
2988 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2989 self.close_containers_to(self.containers.depth() - 1);
2990 }
2991 }
2992
2993 let dispatcher_ctx = BlockContext {
2994 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2998 blockquote_depth: current_bq_depth,
2999 config: self.config,
3000 content_indent,
3001 indent_to_emit,
3002 list_indent_info,
3003 in_list: lists::in_list(&self.containers),
3004 in_marker_only_list_item: matches!(
3005 self.containers.last(),
3006 Some(Container::ListItem {
3007 marker_only: true,
3008 ..
3009 })
3010 ),
3011 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3012 paragraph_open: self.is_paragraph_open(),
3013 next_line,
3014 open_alpha_hint: lists::open_list_hint_at_indent(
3015 &self.containers,
3016 leading_indent(content).0,
3017 ),
3018 };
3019
3020 let mut dispatcher_ctx = dispatcher_ctx;
3023
3024 let dispatcher_prefix =
3031 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3032
3033 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3037 return dispatch;
3038 }
3039
3040 let dispatcher_match = {
3043 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3044 self.block_registry
3045 .detect_prepared(&dispatcher_ctx, &stripped)
3046 };
3047
3048 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3054 let has_blank_before = if self.pos == 0 || after_metadata_block {
3055 true
3056 } else {
3057 let prev_line = self.lines[self.pos - 1];
3058 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3059 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3060 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3061 && fenced_divs::try_parse_div_fence_open(
3062 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3063 )
3064 .is_some();
3065
3066 let prev_line_blank = is_blank_line(prev_line);
3067 prev_line_blank
3068 || prev_is_fenced_div_open
3069 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3070 || !self.previous_block_requires_blank_before_heading()
3071 };
3072
3073 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3076
3077 let prev_line_blank = if self.pos > 0 {
3078 let prev_line = self.lines[self.pos - 1];
3079 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3080 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3081 } else {
3082 false
3083 };
3084 let has_blank_before_strict = at_document_start || prev_line_blank;
3085
3086 dispatcher_ctx.has_blank_before = has_blank_before;
3087 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3088 dispatcher_ctx.at_document_start = at_document_start;
3089
3090 let dispatcher_match =
3091 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3092 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3094 self.block_registry
3095 .detect_prepared(&dispatcher_ctx, &stripped)
3096 } else {
3097 dispatcher_match
3098 };
3099
3100 if has_blank_before {
3101 if let Some(env_name) = extract_environment_name(content)
3102 && is_inline_math_environment(env_name)
3103 {
3104 if !self.is_paragraph_open() {
3105 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3106 }
3107 paragraphs::append_paragraph_line(
3108 &mut self.containers,
3109 &mut self.builder,
3110 line_to_append.unwrap_or(self.lines[self.pos]),
3111 self.config,
3112 );
3113 return LineDispatch::consumed(1);
3114 }
3115
3116 if let Some(block_match) = dispatcher_match.as_ref() {
3117 let detection = block_match.detection;
3118
3119 match detection {
3120 BlockDetectionResult::YesCanInterrupt => {
3121 self.emit_list_item_buffer_if_needed();
3122 if self.is_paragraph_open() {
3123 self.close_containers_to(self.containers.depth() - 1);
3124 }
3125 }
3126 BlockDetectionResult::Yes => {
3127 self.prepare_for_block_element();
3128 }
3129 BlockDetectionResult::No => unreachable!(),
3130 }
3131
3132 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3133 self.close_containers_to_fenced_div();
3134 }
3135
3136 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3137 self.close_open_footnote_definition();
3138 }
3139
3140 let lines_consumed = {
3141 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3142 self.block_registry.parse_prepared(
3143 block_match,
3144 &dispatcher_ctx,
3145 &mut self.builder,
3146 &stripped,
3147 )
3148 };
3149
3150 if matches!(
3151 self.block_registry.parser_name(block_match),
3152 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3153 ) {
3154 self.after_metadata_block = true;
3155 }
3156
3157 let extras = match block_match.effect {
3158 BlockEffect::None => 0,
3159 BlockEffect::OpenFencedDiv => {
3160 self.containers.push(Container::FencedDiv {});
3161 0
3162 }
3163 BlockEffect::CloseFencedDiv => {
3164 self.close_fenced_div();
3165 0
3166 }
3167 BlockEffect::OpenFootnoteDefinition => {
3168 self.handle_footnote_open_effect(block_match, content)
3169 }
3170 BlockEffect::OpenList => {
3171 self.handle_list_open_effect(block_match, content, indent_to_emit)
3172 }
3173 BlockEffect::OpenDefinitionList => {
3174 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3175 }
3176 BlockEffect::OpenBlockQuote => {
3177 0
3179 }
3180 };
3181
3182 if lines_consumed == 0 {
3183 log::warn!(
3184 "block parser made no progress at line {} (parser={})",
3185 self.pos + 1,
3186 self.block_registry.parser_name(block_match)
3187 );
3188 return LineDispatch::Rejected;
3189 }
3190
3191 return LineDispatch::consumed(lines_consumed + extras);
3192 }
3193 } else if let Some(block_match) = dispatcher_match.as_ref() {
3194 let parser_name = self.block_registry.parser_name(block_match);
3197 match block_match.detection {
3198 BlockDetectionResult::YesCanInterrupt => {
3199 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3200 && self.is_paragraph_open()
3201 {
3202 if !self.is_paragraph_open() {
3204 paragraphs::start_paragraph_if_needed(
3205 &mut self.containers,
3206 &mut self.builder,
3207 );
3208 }
3209 paragraphs::append_paragraph_line(
3210 &mut self.containers,
3211 &mut self.builder,
3212 line_to_append.unwrap_or(self.lines[self.pos]),
3213 self.config,
3214 );
3215 return LineDispatch::consumed(1);
3216 }
3217
3218 if matches!(block_match.effect, BlockEffect::OpenList)
3219 && self.is_paragraph_open()
3220 && !lists::in_list(&self.containers)
3221 && self.content_container_indent_to_strip() == 0
3222 {
3223 let allow_interrupt =
3229 self.config.dialect == crate::options::Dialect::CommonMark && {
3230 use super::block_dispatcher::ListPrepared;
3231 use super::blocks::lists::OrderedMarker;
3232 let prepared = block_match
3233 .payload
3234 .as_ref()
3235 .and_then(|p| p.downcast_ref::<ListPrepared>());
3236 match prepared.map(|p| &p.marker) {
3237 Some(ListMarker::Bullet(_)) => true,
3238 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3239 number,
3240 ..
3241 })) => number == "1",
3242 _ => false,
3243 }
3244 };
3245 if !allow_interrupt {
3246 paragraphs::append_paragraph_line(
3247 &mut self.containers,
3248 &mut self.builder,
3249 line_to_append.unwrap_or(self.lines[self.pos]),
3250 self.config,
3251 );
3252 return LineDispatch::consumed(1);
3253 }
3254 }
3255
3256 if matches!(block_match.effect, BlockEffect::OpenList)
3263 && self.try_lazy_list_continuation(block_match, content)
3264 {
3265 return LineDispatch::consumed(1);
3266 }
3267
3268 self.emit_list_item_buffer_if_needed();
3269 if self.is_paragraph_open() {
3270 if self.html_block_demotes_paragraph_to_plain(block_match) {
3271 self.close_paragraph_as_plain_if_open();
3272 } else {
3273 self.close_containers_to(self.containers.depth() - 1);
3274 }
3275 }
3276
3277 if self.config.dialect == crate::options::Dialect::CommonMark
3284 && !matches!(block_match.effect, BlockEffect::OpenList)
3285 {
3286 let (indent_cols, _) = leading_indent(content);
3287 self.close_lists_above_indent(indent_cols);
3288 }
3289 }
3290 BlockDetectionResult::Yes => {
3291 if parser_name == "setext_heading"
3303 && self.is_paragraph_open()
3304 && self.config.dialect == crate::options::Dialect::CommonMark
3305 {
3306 let text_line = self.lines[self.pos];
3307 let underline_line = self.lines[self.pos + 1];
3308 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3309 let level = if underline_char == '=' { 1 } else { 2 };
3310 self.emit_setext_heading_folding_paragraph(
3311 text_line,
3312 underline_line,
3313 level,
3314 );
3315 return LineDispatch::consumed(2);
3316 }
3317
3318 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3321 if !self.is_paragraph_open() {
3322 paragraphs::start_paragraph_if_needed(
3323 &mut self.containers,
3324 &mut self.builder,
3325 );
3326 }
3327 paragraphs::append_paragraph_line(
3328 &mut self.containers,
3329 &mut self.builder,
3330 line_to_append.unwrap_or(self.lines[self.pos]),
3331 self.config,
3332 );
3333 return LineDispatch::consumed(1);
3334 }
3335
3336 if parser_name == "reference_definition" && self.is_paragraph_open() {
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 BlockDetectionResult::No => unreachable!(),
3349 }
3350
3351 if !matches!(block_match.detection, BlockDetectionResult::No) {
3352 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3353 self.close_containers_to_fenced_div();
3354 }
3355
3356 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3357 self.close_open_footnote_definition();
3358 }
3359
3360 let lines_consumed = {
3361 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3362 self.block_registry.parse_prepared(
3363 block_match,
3364 &dispatcher_ctx,
3365 &mut self.builder,
3366 &stripped,
3367 )
3368 };
3369
3370 let extras = match block_match.effect {
3371 BlockEffect::None => 0,
3372 BlockEffect::OpenFencedDiv => {
3373 self.containers.push(Container::FencedDiv {});
3374 0
3375 }
3376 BlockEffect::CloseFencedDiv => {
3377 self.close_fenced_div();
3378 0
3379 }
3380 BlockEffect::OpenFootnoteDefinition => {
3381 self.handle_footnote_open_effect(block_match, content)
3382 }
3383 BlockEffect::OpenList => {
3384 self.handle_list_open_effect(block_match, content, indent_to_emit)
3385 }
3386 BlockEffect::OpenDefinitionList => {
3387 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3388 }
3389 BlockEffect::OpenBlockQuote => {
3390 0
3392 }
3393 };
3394
3395 if lines_consumed == 0 {
3396 log::warn!(
3397 "block parser made no progress at line {} (parser={})",
3398 self.pos + 1,
3399 self.block_registry.parser_name(block_match)
3400 );
3401 return LineDispatch::Rejected;
3402 }
3403
3404 return LineDispatch::consumed(lines_consumed + extras);
3405 }
3406 }
3407
3408 if self.config.extensions.line_blocks
3410 && (has_blank_before || self.pos == 0)
3411 && try_parse_line_block_start(content).is_some()
3412 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3416 {
3417 log::trace!("Parsed line block at line {}", self.pos);
3418 self.close_paragraph_if_open();
3420
3421 let prefix = ContainerPrefix::default();
3427 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3428 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3429 if new_pos > self.pos {
3430 return LineDispatch::consumed(new_pos - self.pos);
3431 }
3432 }
3433
3434 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3437 log::trace!(
3438 "Inside ListItem - buffering content: {:?}",
3439 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3440 );
3441 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3443
3444 if let Some(Container::ListItem {
3446 buffer,
3447 marker_only,
3448 ..
3449 }) = self.containers.stack.last_mut()
3450 {
3451 buffer.push_text(line);
3452 if !is_blank_line(line) {
3453 *marker_only = false;
3454 }
3455 }
3456
3457 return LineDispatch::consumed(1);
3458 }
3459
3460 log::trace!(
3461 "Not in ListItem - creating paragraph for: {:?}",
3462 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3463 );
3464 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3466 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3469 paragraphs::append_paragraph_line(
3470 &mut self.containers,
3471 &mut self.builder,
3472 line,
3473 self.config,
3474 );
3475 LineDispatch::consumed(1)
3476 }
3477
3478 fn fenced_div_container_index(&self) -> Option<usize> {
3479 self.containers
3480 .stack
3481 .iter()
3482 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3483 }
3484
3485 fn close_containers_to_fenced_div(&mut self) {
3486 if let Some(index) = self.fenced_div_container_index() {
3487 self.close_containers_to(index + 1);
3488 }
3489 }
3490
3491 fn close_fenced_div(&mut self) {
3492 if let Some(index) = self.fenced_div_container_index() {
3493 self.close_containers_to(index);
3494 }
3495 }
3496
3497 fn in_fenced_div(&self) -> bool {
3498 self.containers
3499 .stack
3500 .iter()
3501 .any(|c| matches!(c, Container::FencedDiv { .. }))
3502 }
3503
3504 fn in_footnote_definition(&self) -> bool {
3512 self.containers
3513 .stack
3514 .iter()
3515 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3516 }
3517}
3518
3519fn emit_definition_plain_or_heading(
3526 builder: &mut GreenNodeBuilder<'static>,
3527 text: &str,
3528 config: &ParserOptions,
3529 suppress_footnote_refs: bool,
3530) {
3531 let line_without_newline = text
3532 .strip_suffix("\r\n")
3533 .or_else(|| text.strip_suffix('\n'));
3534 if let Some(line) = line_without_newline
3535 && !line.contains('\n')
3536 && !line.contains('\r')
3537 && let Some(level) = try_parse_atx_heading(line)
3538 {
3539 emit_atx_heading(builder, text, level, config);
3540 return;
3541 }
3542
3543 if let Some(first_nl) = text.find('\n') {
3545 let first_line = &text[..first_nl];
3546 let after_first = &text[first_nl + 1..];
3547 if !after_first.is_empty()
3548 && let Some(level) = try_parse_atx_heading(first_line)
3549 {
3550 let heading_bytes = &text[..first_nl + 1];
3551 emit_atx_heading(builder, heading_bytes, level, config);
3552 builder.start_node(SyntaxKind::PLAIN.into());
3553 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3554 builder.finish_node();
3555 return;
3556 }
3557 }
3558
3559 builder.start_node(SyntaxKind::PLAIN.into());
3560 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3561 builder.finish_node();
3562}
3563
3564fn footnote_first_line_term_lookahead(
3573 lines: &[&str],
3574 pos: usize,
3575 content_col: usize,
3576 table_captions_enabled: bool,
3577) -> Option<usize> {
3578 let mut check_pos = pos + 1;
3579 let mut blank_count = 0;
3580 while check_pos < lines.len() {
3581 let line = lines[check_pos];
3582 let (trimmed, _) = strip_newline(line);
3583 if trimmed.trim().is_empty() {
3584 blank_count += 1;
3585 check_pos += 1;
3586 continue;
3587 }
3588 let (line_indent_cols, _) = leading_indent(trimmed);
3589 if line_indent_cols < content_col {
3590 return None;
3591 }
3592 let strip_bytes = byte_index_at_column(trimmed, content_col);
3593 if strip_bytes > trimmed.len() {
3594 return None;
3595 }
3596 let stripped = &trimmed[strip_bytes..];
3597 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3598 if marker == ':'
3602 && table_captions_enabled
3603 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3604 {
3605 return None;
3606 }
3607 return Some(blank_count);
3608 }
3609 return None;
3610 }
3611 None
3612}