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 new_pos = code_blocks::parse_fenced_code_block(
492 &mut self.builder,
493 &self.lines,
494 self.pos,
495 fence,
496 bq_depth,
497 content_col,
498 true,
499 bq_depth > 0,
500 0,
501 Some(&text_owned),
502 );
503 Some(new_pos.saturating_sub(self.pos).saturating_sub(1))
504 }
505
506 fn maybe_open_indented_code_in_new_list_item(&mut self) {
517 let Some(Container::ListItem {
518 content_col,
519 buffer,
520 marker_only,
521 virtual_marker_space,
522 }) = self.containers.stack.last()
523 else {
524 return;
525 };
526 if *marker_only {
527 return;
528 }
529 if buffer.segment_count() != 1 {
530 return;
531 }
532 let Some(text) = buffer.first_text() else {
533 return;
534 };
535 let content_col = *content_col;
536 let virtual_marker_space = *virtual_marker_space;
537 let text_owned = text.to_string();
538
539 let mut iter = text_owned.split_inclusive('\n');
541 let line_with_nl = iter.next().unwrap_or("").to_string();
542 if iter.next().is_some() {
543 return;
544 }
545
546 let line_no_nl = line_with_nl
547 .strip_suffix("\r\n")
548 .or_else(|| line_with_nl.strip_suffix('\n'))
549 .unwrap_or(&line_with_nl);
550 let nl_suffix = &line_with_nl[line_no_nl.len()..];
551
552 let buffer_start_col = if virtual_marker_space {
553 content_col.saturating_sub(1)
554 } else {
555 content_col
556 };
557
558 let target = content_col + 4;
559 let (cols_walked, ws_bytes) =
560 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
561
562 if buffer_start_col + cols_walked < target {
563 return;
564 }
565 if ws_bytes >= line_no_nl.len() {
566 return;
567 }
568
569 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
570 buffer.clear();
571 }
572
573 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
574 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
575 if ws_bytes > 0 {
576 self.builder
577 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
578 }
579 let rest = &line_no_nl[ws_bytes..];
580 if !rest.is_empty() {
581 self.builder.token(SyntaxKind::TEXT.into(), rest);
582 }
583 if !nl_suffix.is_empty() {
584 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
585 }
586 self.builder.finish_node();
587 self.builder.finish_node();
588 }
589
590 fn has_matching_fence_closer(
591 &self,
592 fence: &code_blocks::FenceInfo,
593 bq_depth: usize,
594 content_col: usize,
595 ) -> bool {
596 for raw_line in self.lines.iter().skip(self.pos + 1) {
597 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
598 if line_bq_depth < bq_depth {
599 break;
600 }
601 let candidate = if content_col > 0 && !inner.is_empty() {
602 let idx = byte_index_at_column(inner, content_col);
603 if idx <= inner.len() {
604 &inner[idx..]
605 } else {
606 inner
607 }
608 } else {
609 inner
610 };
611 if code_blocks::is_closing_fence(candidate, fence) {
612 return true;
613 }
614 }
615 false
616 }
617
618 fn is_paragraph_open(&self) -> bool {
620 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
621 }
622
623 fn emit_setext_heading_folding_paragraph(
631 &mut self,
632 text_line: &str,
633 underline_line: &str,
634 level: usize,
635 ) {
636 let (buffered_text, checkpoint) = match self.containers.stack.last() {
637 Some(Container::Paragraph {
638 buffer,
639 start_checkpoint,
640 ..
641 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
642 _ => (String::new(), None),
643 };
644
645 if checkpoint.is_some() {
646 self.containers.stack.pop();
647 }
648
649 let combined_text = if buffered_text.is_empty() {
650 text_line.to_string()
651 } else {
652 format!("{}{}", buffered_text, text_line)
653 };
654
655 let cp = checkpoint.expect(
656 "emit_setext_heading_folding_paragraph requires an open paragraph; \
657 single-line setext should go through the regular dispatcher path",
658 );
659 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
660 emit_setext_heading_body(
661 &mut self.builder,
662 &combined_text,
663 underline_line,
664 level,
665 self.config,
666 );
667 self.builder.finish_node();
668 }
669
670 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
688 let Some(Container::ListItem {
689 buffer,
690 content_col,
691 ..
692 }) = self.containers.stack.last()
693 else {
694 return None;
695 };
696 if buffer.segment_count() != 1 {
697 return None;
698 }
699 let text_line = buffer.first_text()?;
700
701 let content_col = *content_col;
706 let (underline_indent_cols, _) = leading_indent(content);
707 if underline_indent_cols < content_col {
708 return None;
709 }
710
711 let lines = [text_line, content];
712 let (level, _) = try_parse_setext_heading(&lines, 0)?;
713
714 let (text_no_newline, _) = strip_newline(text_line);
715 if text_no_newline.trim().is_empty() {
716 return None;
717 }
718 if try_parse_horizontal_rule(text_no_newline).is_some() {
719 return None;
720 }
721
722 let text_owned = text_line.to_string();
723 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
724 buffer.clear();
725 }
726 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
727 Some(LineDispatch::consumed(1))
728 }
729
730 fn close_paragraph_if_open(&mut self) {
732 if self.is_paragraph_open() {
733 self.close_containers_to(self.containers.depth() - 1);
734 }
735 }
736
737 fn close_paragraph_as_plain_if_open(&mut self) {
748 if !self.is_paragraph_open() {
749 return;
750 }
751 let Some(Container::Paragraph {
752 buffer,
753 start_checkpoint,
754 ..
755 }) = self.containers.stack.last()
756 else {
757 return;
758 };
759 let buffer_clone = buffer.clone();
760 let checkpoint = *start_checkpoint;
761 let suppress_footnote_refs = self.in_footnote_definition();
762 self.containers.stack.pop();
763 self.builder
764 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
765 if !buffer_clone.is_empty() {
766 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
767 }
768 self.builder.finish_node();
769 }
770
771 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
780 if self.config.dialect != crate::options::Dialect::Pandoc {
781 return false;
782 }
783 if self.block_registry.parser_name(block_match) != "html_block" {
784 return false;
785 }
786 let html_block_type = block_match
787 .payload
788 .as_ref()
789 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
790 matches!(
791 html_block_type,
792 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
793 )
794 }
795
796 fn prepare_for_block_element(&mut self) {
799 self.emit_list_item_buffer_if_needed();
800 self.close_paragraph_if_open();
801 }
802
803 fn close_open_footnote_definition(&mut self) {
807 while matches!(
808 self.containers.last(),
809 Some(Container::FootnoteDefinition { .. })
810 ) {
811 self.close_containers_to(self.containers.depth() - 1);
812 }
813 }
814
815 fn handle_footnote_open_effect(
819 &mut self,
820 block_match: &super::block_dispatcher::PreparedBlockMatch,
821 content: &str,
822 ) -> usize {
823 let content_start = block_match
824 .payload
825 .as_ref()
826 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
827 .map(|p| p.content_start)
828 .unwrap_or(0);
829
830 let content_col = 4;
831 self.containers
832 .push(Container::FootnoteDefinition { content_col });
833
834 if content_start == 0 {
835 return 0;
836 }
837 let first_line_content = &content[content_start..];
838 if first_line_content.trim().is_empty() {
839 let (_, newline_str) = strip_newline(content);
840 if !newline_str.is_empty() {
841 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
842 }
843 return 0;
844 }
845
846 if self.config.extensions.definition_lists
847 && let Some(blank_count) = footnote_first_line_term_lookahead(
848 &self.lines,
849 self.pos,
850 content_col,
851 self.config.extensions.table_captions,
852 )
853 {
854 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
855 self.containers.push(Container::DefinitionList {});
856 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
857 self.containers.push(Container::DefinitionItem {});
858 emit_term(&mut self.builder, first_line_content, self.config);
859 for i in 0..blank_count {
860 let blank_pos = self.pos + 1 + i;
861 if blank_pos < self.lines.len() {
862 let blank_line = self.lines[blank_pos];
863 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
864 self.builder
865 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
866 self.builder.finish_node();
867 }
868 }
869 return blank_count;
870 }
871
872 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
873 paragraphs::append_paragraph_line(
874 &mut self.containers,
875 &mut self.builder,
876 first_line_content,
877 self.config,
878 );
879 0
880 }
881
882 fn try_lazy_list_continuation(
894 &mut self,
895 block_match: &super::block_dispatcher::PreparedBlockMatch,
896 content: &str,
897 ) -> bool {
898 use super::block_dispatcher::ListPrepared;
899
900 let Some(prepared) = block_match
901 .payload
902 .as_ref()
903 .and_then(|p| p.downcast_ref::<ListPrepared>())
904 else {
905 return false;
906 };
907
908 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
909 return false;
910 }
911
912 let current_content_col = paragraphs::current_content_col(&self.containers);
913 if prepared.indent_cols >= current_content_col {
914 return false;
915 }
916
917 if lists::find_matching_list_level(
918 &self.containers,
919 &prepared.marker,
920 prepared.indent_cols,
921 self.config.dialect,
922 )
923 .is_some()
924 {
925 return false;
926 }
927
928 match self.containers.last() {
929 Some(Container::Paragraph { .. }) => {
930 paragraphs::append_paragraph_line(
931 &mut self.containers,
932 &mut self.builder,
933 content,
934 self.config,
935 );
936 true
937 }
938 Some(Container::ListItem { .. }) => {
939 if let Some(Container::ListItem {
940 buffer,
941 marker_only,
942 ..
943 }) = self.containers.stack.last_mut()
944 {
945 buffer.push_text(content);
946 if !content.trim().is_empty() {
947 *marker_only = false;
948 }
949 }
950 true
951 }
952 _ => false,
953 }
954 }
955
956 fn handle_list_open_effect(
962 &mut self,
963 block_match: &super::block_dispatcher::PreparedBlockMatch,
964 content: &str,
965 indent_to_emit: Option<&str>,
966 ) -> usize {
967 use super::block_dispatcher::ListPrepared;
968
969 let prepared = block_match
970 .payload
971 .as_ref()
972 .and_then(|p| p.downcast_ref::<ListPrepared>());
973 let Some(prepared) = prepared else {
974 return 0;
975 };
976
977 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
978 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
979 paragraphs::append_paragraph_line(
980 &mut self.containers,
981 &mut self.builder,
982 content,
983 self.config,
984 );
985 return 0;
986 }
987
988 if self.is_paragraph_open() {
989 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
990 paragraphs::append_paragraph_line(
991 &mut self.containers,
992 &mut self.builder,
993 content,
994 self.config,
995 );
996 return 0;
997 }
998 self.close_containers_to(self.containers.depth() - 1);
999 }
1000
1001 if matches!(
1002 self.containers.last(),
1003 Some(Container::Definition {
1004 plain_open: true,
1005 ..
1006 })
1007 ) {
1008 self.emit_buffered_plain_if_needed();
1009 }
1010
1011 let matched_level = lists::find_matching_list_level(
1012 &self.containers,
1013 &prepared.marker,
1014 prepared.indent_cols,
1015 self.config.dialect,
1016 );
1017 let list_item = ListItemEmissionInput {
1018 content,
1019 marker_len: prepared.marker_len,
1020 spaces_after_cols: prepared.spaces_after_cols,
1021 spaces_after_bytes: prepared.spaces_after,
1022 indent_cols: prepared.indent_cols,
1023 indent_bytes: prepared.indent_bytes,
1024 virtual_marker_space: prepared.virtual_marker_space,
1025 };
1026 let current_content_col = paragraphs::current_content_col(&self.containers);
1027 let deep_ordered_matched_level = matched_level
1028 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1029 .and_then(|(level, container)| match container {
1030 Container::List {
1031 marker: list_marker,
1032 base_indent_cols,
1033 ..
1034 } if matches!(
1035 (&prepared.marker, list_marker),
1036 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1037 ) && prepared.indent_cols >= 4
1038 && *base_indent_cols >= 4
1039 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1040 {
1041 Some(level)
1042 }
1043 _ => None,
1044 });
1045
1046 if deep_ordered_matched_level.is_none()
1047 && current_content_col > 0
1048 && prepared.indent_cols >= current_content_col
1049 {
1050 if let Some(level) = matched_level
1051 && let Some(Container::List {
1052 base_indent_cols, ..
1053 }) = self.containers.stack.get(level)
1054 && prepared.indent_cols == *base_indent_cols
1055 {
1056 let num_parent_lists = self.containers.stack[..level]
1057 .iter()
1058 .filter(|c| matches!(c, Container::List { .. }))
1059 .count();
1060
1061 if num_parent_lists > 0 {
1062 self.close_containers_to(level + 1);
1063
1064 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1065 self.close_containers_to(self.containers.depth() - 1);
1066 }
1067 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1068 self.close_containers_to(self.containers.depth() - 1);
1069 }
1070
1071 if let Some(indent_str) = indent_to_emit {
1072 self.builder
1073 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1074 }
1075
1076 let finish = if let Some(nested_marker) = prepared.nested_marker {
1077 lists::add_list_item_with_nested_empty_list(
1078 &mut self.containers,
1079 &mut self.builder,
1080 &list_item,
1081 nested_marker,
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 );
1139 lists::ListItemFinish::Done
1140 } else {
1141 lists::add_list_item(
1142 &mut self.containers,
1143 &mut self.builder,
1144 &list_item,
1145 self.config,
1146 )
1147 };
1148 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1149 return extras;
1150 }
1151 self.maybe_open_indented_code_in_new_list_item();
1152 return self.dispatch_bq_after_list_item(finish);
1153 }
1154
1155 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1156 self.close_containers_to(self.containers.depth() - 1);
1157 }
1158 while matches!(
1159 self.containers.last(),
1160 Some(Container::ListItem { .. } | Container::List { .. })
1161 ) {
1162 self.close_containers_to(self.containers.depth() - 1);
1163 }
1164
1165 self.builder.start_node(SyntaxKind::LIST.into());
1166 if let Some(indent_str) = indent_to_emit {
1167 self.builder
1168 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1169 }
1170 self.containers.push(Container::List {
1171 marker: prepared.marker.clone(),
1172 base_indent_cols: prepared.indent_cols,
1173 has_blank_between_items: false,
1174 });
1175
1176 let finish = if let Some(nested_marker) = prepared.nested_marker {
1177 lists::add_list_item_with_nested_empty_list(
1178 &mut self.containers,
1179 &mut self.builder,
1180 &list_item,
1181 nested_marker,
1182 );
1183 lists::ListItemFinish::Done
1184 } else {
1185 lists::add_list_item(
1186 &mut self.containers,
1187 &mut self.builder,
1188 &list_item,
1189 self.config,
1190 )
1191 };
1192 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1193 return extras;
1194 }
1195 self.maybe_open_indented_code_in_new_list_item();
1196 self.dispatch_bq_after_list_item(finish)
1197 }
1198
1199 fn handle_definition_list_effect(
1206 &mut self,
1207 block_match: &super::block_dispatcher::PreparedBlockMatch,
1208 content: &str,
1209 indent_to_emit: Option<&str>,
1210 ) -> usize {
1211 use super::block_dispatcher::DefinitionPrepared;
1212
1213 let prepared = block_match
1214 .payload
1215 .as_ref()
1216 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1217 let Some(prepared) = prepared else {
1218 return 0;
1219 };
1220
1221 let mut extras: usize = 0;
1222 match prepared {
1223 DefinitionPrepared::Definition {
1224 marker_char,
1225 indent,
1226 spaces_after,
1227 spaces_after_cols,
1228 has_content,
1229 } => {
1230 self.emit_buffered_plain_if_needed();
1231
1232 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1233 self.close_containers_to(self.containers.depth() - 1);
1234 }
1235 while matches!(self.containers.last(), Some(Container::List { .. })) {
1236 self.close_containers_to(self.containers.depth() - 1);
1237 }
1238
1239 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1240 self.close_containers_to(self.containers.depth() - 1);
1241 }
1242
1243 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1244 self.close_containers_to(self.containers.depth() - 1);
1245 }
1246
1247 if definition_lists::in_definition_list(&self.containers)
1251 && !matches!(
1252 self.containers.last(),
1253 Some(Container::DefinitionItem { .. })
1254 )
1255 {
1256 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1257 self.containers.push(Container::DefinitionItem {});
1258 }
1259
1260 if !definition_lists::in_definition_list(&self.containers) {
1261 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1262 self.containers.push(Container::DefinitionList {});
1263 }
1264
1265 if !matches!(
1266 self.containers.last(),
1267 Some(Container::DefinitionItem { .. })
1268 ) {
1269 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1270 self.containers.push(Container::DefinitionItem {});
1271 }
1272
1273 self.builder.start_node(SyntaxKind::DEFINITION.into());
1274
1275 if let Some(indent_str) = indent_to_emit {
1276 self.builder
1277 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1278 }
1279
1280 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1281 let indent_bytes = byte_index_at_column(content, *indent);
1282 if *spaces_after > 0 {
1283 let space_start = indent_bytes + 1;
1284 let space_end = space_start + *spaces_after;
1285 if space_end <= content.len() {
1286 self.builder.token(
1287 SyntaxKind::WHITESPACE.into(),
1288 &content[space_start..space_end],
1289 );
1290 }
1291 }
1292
1293 if !*has_content {
1294 let current_line = self.lines[self.pos];
1295 let (_, newline_str) = strip_newline(current_line);
1296 if !newline_str.is_empty() {
1297 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1298 }
1299 }
1300
1301 let content_col = *indent + 1 + *spaces_after_cols;
1302 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1303 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1304 let mut plain_buffer = TextBuffer::new();
1305 let mut definition_pushed = false;
1306
1307 if *has_content {
1308 let current_line = self.lines[self.pos];
1309 let (trimmed_content, _) = strip_newline(content);
1310
1311 let content_start = content_start_bytes.min(trimmed_content.len());
1318 let content_slice = &trimmed_content[content_start..];
1319 let content_line = &content[content_start_bytes.min(content.len())..];
1320
1321 let (blockquote_depth, inner_blockquote_content) =
1322 count_blockquote_markers(content_line);
1323
1324 let should_start_list_from_first_line = self
1325 .lines
1326 .get(self.pos + 1)
1327 .map(|next_line| {
1328 let (next_without_newline, _) = strip_newline(next_line);
1329 if next_without_newline.trim().is_empty() {
1330 return true;
1331 }
1332
1333 let (next_indent_cols, _) = leading_indent(next_without_newline);
1334 next_indent_cols >= content_col
1335 })
1336 .unwrap_or(true);
1337
1338 if blockquote_depth > 0 {
1339 self.containers.push(Container::Definition {
1340 content_col,
1341 plain_open: false,
1342 plain_buffer: TextBuffer::new(),
1343 });
1344 definition_pushed = true;
1345
1346 let marker_info = parse_blockquote_marker_info(content_line);
1347 for level in 0..blockquote_depth {
1348 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1349 if let Some(info) = marker_info.get(level) {
1350 blockquotes::emit_one_blockquote_marker(
1351 &mut self.builder,
1352 info.leading_spaces,
1353 info.has_trailing_space,
1354 );
1355 }
1356 self.containers.push(Container::BlockQuote {});
1357 }
1358
1359 if !inner_blockquote_content.trim().is_empty() {
1360 paragraphs::start_paragraph_if_needed(
1361 &mut self.containers,
1362 &mut self.builder,
1363 );
1364 paragraphs::append_paragraph_line(
1365 &mut self.containers,
1366 &mut self.builder,
1367 inner_blockquote_content,
1368 self.config,
1369 );
1370 }
1371 } else if let Some(marker_match) = try_parse_list_marker(
1372 content_slice,
1373 self.config,
1374 lists::open_list_hint_at_indent(
1375 &self.containers,
1376 leading_indent(content_slice).0,
1377 ),
1378 ) && should_start_list_from_first_line
1379 {
1380 self.containers.push(Container::Definition {
1381 content_col,
1382 plain_open: false,
1383 plain_buffer: TextBuffer::new(),
1384 });
1385 definition_pushed = true;
1386
1387 let (indent_cols, indent_bytes) = leading_indent(content_line);
1388 self.builder.start_node(SyntaxKind::LIST.into());
1389 self.containers.push(Container::List {
1390 marker: marker_match.marker.clone(),
1391 base_indent_cols: indent_cols,
1392 has_blank_between_items: false,
1393 });
1394
1395 let list_item = ListItemEmissionInput {
1396 content: content_line,
1397 marker_len: marker_match.marker_len,
1398 spaces_after_cols: marker_match.spaces_after_cols,
1399 spaces_after_bytes: marker_match.spaces_after_bytes,
1400 indent_cols,
1401 indent_bytes,
1402 virtual_marker_space: marker_match.virtual_marker_space,
1403 };
1404
1405 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1406 content_line,
1407 marker_match.marker_len,
1408 marker_match.spaces_after_bytes,
1409 ) {
1410 lists::add_list_item_with_nested_empty_list(
1411 &mut self.containers,
1412 &mut self.builder,
1413 &list_item,
1414 nested_marker,
1415 );
1416 lists::ListItemFinish::Done
1417 } else {
1418 lists::add_list_item(
1419 &mut self.containers,
1420 &mut self.builder,
1421 &list_item,
1422 self.config,
1423 )
1424 };
1425 extras = self.dispatch_bq_after_list_item(finish);
1426 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
1427 self.containers.push(Container::Definition {
1428 content_col,
1429 plain_open: false,
1430 plain_buffer: TextBuffer::new(),
1431 });
1432 definition_pushed = true;
1433
1434 let bq_depth = self.current_blockquote_depth();
1435 if let Some(indent_str) = indent_to_emit {
1436 self.builder
1437 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1438 }
1439 let fence_line = content[content_start..].to_string();
1440 let new_pos = if self.config.extensions.tex_math_gfm
1441 && code_blocks::is_gfm_math_fence(&fence)
1442 {
1443 code_blocks::parse_fenced_math_block(
1444 &mut self.builder,
1445 &self.lines,
1446 self.pos,
1447 fence,
1448 bq_depth,
1449 0,
1450 false,
1451 bq_depth > 0,
1452 content_col,
1453 Some(&fence_line),
1454 )
1455 } else {
1456 code_blocks::parse_fenced_code_block(
1457 &mut self.builder,
1458 &self.lines,
1459 self.pos,
1460 fence,
1461 bq_depth,
1462 0,
1463 false,
1464 bq_depth > 0,
1465 content_col,
1466 Some(&fence_line),
1467 )
1468 };
1469 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1470 } else {
1471 let (_, newline_str) = strip_newline(current_line);
1472 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1473 if content_without_newline.is_empty() {
1474 plain_buffer.push_line(newline_str);
1475 } else {
1476 let line_with_newline = if !newline_str.is_empty() {
1477 format!("{}{}", content_without_newline, newline_str)
1478 } else {
1479 content_without_newline.to_string()
1480 };
1481 plain_buffer.push_line(line_with_newline);
1482 }
1483 }
1484 }
1485
1486 if !definition_pushed {
1487 self.containers.push(Container::Definition {
1488 content_col,
1489 plain_open: *has_content,
1490 plain_buffer,
1491 });
1492 }
1493 }
1494 DefinitionPrepared::Term { blank_count } => {
1495 self.emit_buffered_plain_if_needed();
1496
1497 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1498 self.close_containers_to(self.containers.depth() - 1);
1499 }
1500
1501 if !definition_lists::in_definition_list(&self.containers) {
1502 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1503 self.containers.push(Container::DefinitionList {});
1504 }
1505
1506 while matches!(
1507 self.containers.last(),
1508 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1509 ) {
1510 self.close_containers_to(self.containers.depth() - 1);
1511 }
1512
1513 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1514 self.containers.push(Container::DefinitionItem {});
1515
1516 emit_term(&mut self.builder, content, self.config);
1517
1518 for i in 0..*blank_count {
1519 let blank_pos = self.pos + 1 + i;
1520 if blank_pos < self.lines.len() {
1521 let blank_line = self.lines[blank_pos];
1522 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1523 self.builder
1524 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1525 self.builder.finish_node();
1526 }
1527 }
1528 extras = *blank_count;
1529 }
1530 };
1531 extras
1532 }
1533
1534 fn blockquote_marker_info(
1536 &self,
1537 payload: Option<&BlockQuotePrepared>,
1538 line: &str,
1539 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1540 payload
1541 .map(|payload| payload.marker_info.clone())
1542 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1543 }
1544
1545 fn marker_info_for_line(
1551 &self,
1552 payload: Option<&BlockQuotePrepared>,
1553 raw_line: &str,
1554 marker_line: &str,
1555 shifted_prefix: &str,
1556 used_shifted: bool,
1557 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1558 let mut marker_info = if used_shifted {
1559 parse_blockquote_marker_info(marker_line)
1560 } else {
1561 self.blockquote_marker_info(payload, raw_line)
1562 };
1563 if used_shifted && !shifted_prefix.is_empty() {
1564 let (prefix_cols, _) = leading_indent(shifted_prefix);
1565 if let Some(first) = marker_info.first_mut() {
1566 first.leading_spaces += prefix_cols;
1567 }
1568 }
1569 marker_info
1570 }
1571
1572 fn shifted_blockquote_from_list<'b>(
1575 &self,
1576 line: &'b str,
1577 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1578 let list_content_col = self
1587 .containers
1588 .stack
1589 .iter()
1590 .rev()
1591 .find_map(|c| match c {
1592 Container::ListItem { content_col, .. } => Some(*content_col),
1593 _ => None,
1594 })
1595 .unwrap_or(0);
1596 let content_container_indent = self.content_container_indent_to_strip();
1597 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1605 return None;
1606 }
1607 let marker_col = list_content_col.saturating_add(content_container_indent);
1608 if marker_col == 0 {
1609 return None;
1610 }
1611
1612 let (indent_cols, _) = leading_indent(line);
1613 if indent_cols < marker_col {
1614 return None;
1615 }
1616
1617 let idx = byte_index_at_column(line, marker_col);
1618 if idx > line.len() {
1619 return None;
1620 }
1621
1622 let candidate = &line[idx..];
1623 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1624 if candidate_depth == 0 {
1625 return None;
1626 }
1627
1628 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1629 }
1630
1631 fn emit_blockquote_markers(
1632 &mut self,
1633 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1634 depth: usize,
1635 ) {
1636 for i in 0..depth {
1637 if let Some(info) = marker_info.get(i) {
1638 blockquotes::emit_one_blockquote_marker(
1639 &mut self.builder,
1640 info.leading_spaces,
1641 info.has_trailing_space,
1642 );
1643 }
1644 }
1645 }
1646
1647 fn current_blockquote_depth(&self) -> usize {
1648 blockquotes::current_blockquote_depth(&self.containers)
1649 }
1650
1651 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1659 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1660 return None;
1661 };
1662 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1663 }
1664
1665 fn emit_or_buffer_blockquote_marker(
1670 &mut self,
1671 leading_spaces: usize,
1672 has_trailing_space: bool,
1673 ) {
1674 if let Some(Container::ListItem {
1675 buffer,
1676 marker_only,
1677 ..
1678 }) = self.containers.stack.last_mut()
1679 {
1680 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1681 *marker_only = false;
1682 return;
1683 }
1684
1685 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1687 paragraphs::append_paragraph_marker(
1689 &mut self.containers,
1690 leading_spaces,
1691 has_trailing_space,
1692 );
1693 } else {
1694 blockquotes::emit_one_blockquote_marker(
1696 &mut self.builder,
1697 leading_spaces,
1698 has_trailing_space,
1699 );
1700 }
1701 }
1702
1703 fn parse_document_stack(&mut self) {
1704 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1705
1706 log::trace!("Starting document parse");
1707
1708 while self.pos < self.lines.len() {
1711 let line = self.lines[self.pos];
1712
1713 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1714
1715 match self.parse_line(line) {
1716 LineDispatch::Consumed(n) => self.pos += n,
1717 LineDispatch::Rejected => self.pos += 1,
1718 }
1719 }
1720
1721 self.close_containers_to(0);
1722 self.builder.finish_node(); }
1724
1725 fn parse_line(&mut self, line: &str) -> LineDispatch {
1729 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1732 let mut bq_marker_line = line;
1733 let mut shifted_bq_prefix = "";
1734 let mut used_shifted_bq = false;
1735 if bq_depth == 0
1736 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1737 self.shifted_blockquote_from_list(line)
1738 {
1739 bq_depth = candidate_depth;
1740 inner_content = candidate_inner;
1741 bq_marker_line = candidate_line;
1742 shifted_bq_prefix = candidate_prefix;
1743 used_shifted_bq = true;
1744 }
1745 let current_bq_depth = self.current_blockquote_depth();
1746
1747 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1748 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1749 let dispatcher_ctx = if current_bq_depth == 0 {
1750 Some(BlockContext {
1751 has_blank_before,
1752 has_blank_before_strict: has_blank_before,
1753 at_document_start: self.pos == 0,
1754 in_fenced_div: self.in_fenced_div(),
1755 blockquote_depth: current_bq_depth,
1756 config: self.config,
1757 content_indent: 0,
1758 indent_to_emit: None,
1759 list_indent_info: None,
1760 in_list: lists::in_list(&self.containers),
1761 in_marker_only_list_item: matches!(
1762 self.containers.last(),
1763 Some(Container::ListItem {
1764 marker_only: true,
1765 ..
1766 })
1767 ),
1768 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1769 paragraph_open: self.is_paragraph_open(),
1770 next_line: if self.pos + 1 < self.lines.len() {
1771 Some(self.lines[self.pos + 1])
1772 } else {
1773 None
1774 },
1775 open_alpha_hint: lists::open_list_hint_at_indent(
1776 &self.containers,
1777 leading_indent(line).0,
1778 ),
1779 })
1780 } else {
1781 None
1782 };
1783
1784 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1785 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
1786 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
1787 self.block_registry
1788 .detect_prepared(dispatcher_ctx, &stripped)
1789 .and_then(|prepared| {
1790 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1791 blockquote_match = Some(prepared);
1792 blockquote_match.as_ref().and_then(|prepared| {
1793 prepared
1794 .payload
1795 .as_ref()
1796 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1797 .cloned()
1798 })
1799 } else {
1800 None
1801 }
1802 })
1803 } else {
1804 None
1805 };
1806
1807 log::trace!(
1808 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1809 self.pos,
1810 bq_depth,
1811 current_bq_depth,
1812 self.containers.depth(),
1813 line.trim_end()
1814 );
1815
1816 let inner_blank_in_blockquote = bq_depth > 0
1823 && is_blank_line(inner_content)
1824 && (current_bq_depth > 0
1825 || !self.config.extensions.blank_before_blockquote
1826 || blockquotes::can_start_blockquote(self.pos, &self.lines));
1827 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1828
1829 if is_blank {
1830 if self.is_paragraph_open()
1831 && paragraphs::has_open_inline_math_environment(&self.containers)
1832 {
1833 paragraphs::append_paragraph_line(
1834 &mut self.containers,
1835 &mut self.builder,
1836 line,
1837 self.config,
1838 );
1839 return LineDispatch::consumed(1);
1840 }
1841
1842 self.close_paragraph_if_open();
1844
1845 self.emit_buffered_plain_if_needed();
1849
1850 if bq_depth > current_bq_depth {
1858 for _ in current_bq_depth..bq_depth {
1860 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1861 self.containers.push(Container::BlockQuote {});
1862 }
1863 } else if bq_depth < current_bq_depth {
1864 self.close_blockquotes_to_depth(bq_depth);
1866 }
1867
1868 let mut peek = self.pos + 1;
1875 while peek < self.lines.len() {
1876 let peek_line = self.lines[peek];
1877 if is_blank_line(peek_line) {
1878 peek += 1;
1879 continue;
1880 }
1881 if bq_depth > 0 {
1882 let (peek_bq, _) = count_blockquote_markers(peek_line);
1883 if peek_bq >= bq_depth {
1884 let peek_inner =
1885 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1886 if is_blank_line(peek_inner) {
1887 peek += 1;
1888 continue;
1889 }
1890 }
1891 }
1892 break;
1893 }
1894
1895 let levels_to_keep = if peek < self.lines.len() {
1897 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1898 self.current_blockquote_depth(),
1899 &self.containers,
1900 &self.lines,
1901 peek,
1902 self.lines[peek],
1903 )
1904 } else {
1905 0
1906 };
1907 log::trace!(
1908 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1909 self.containers.depth(),
1910 levels_to_keep,
1911 if peek < self.lines.len() {
1912 self.lines[peek]
1913 } else {
1914 "<EOF>"
1915 }
1916 );
1917
1918 while self.containers.depth() > levels_to_keep {
1922 match self.containers.last() {
1923 Some(Container::ListItem { .. }) => {
1924 log::trace!(
1926 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1927 levels_to_keep,
1928 self.containers.depth()
1929 );
1930 self.close_containers_to(self.containers.depth() - 1);
1931 }
1932 Some(Container::List { .. })
1933 | Some(Container::FootnoteDefinition { .. })
1934 | Some(Container::Alert { .. })
1935 | Some(Container::Paragraph { .. })
1936 | Some(Container::Definition { .. })
1937 | Some(Container::DefinitionItem { .. })
1938 | Some(Container::DefinitionList { .. }) => {
1939 log::trace!(
1940 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1941 self.containers.last(),
1942 self.containers.depth(),
1943 levels_to_keep
1944 );
1945
1946 self.close_containers_to(self.containers.depth() - 1);
1947 }
1948 _ => break,
1949 }
1950 }
1951
1952 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1956 self.emit_list_item_buffer_if_needed();
1957 }
1958
1959 if bq_depth > 0 {
1961 let marker_info = self.marker_info_for_line(
1962 blockquote_payload.as_ref(),
1963 line,
1964 bq_marker_line,
1965 shifted_bq_prefix,
1966 used_shifted_bq,
1967 );
1968 self.emit_blockquote_markers(&marker_info, bq_depth);
1969 }
1970
1971 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1972 self.builder
1973 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1974 self.builder.finish_node();
1975
1976 return LineDispatch::consumed(1);
1977 }
1978
1979 if bq_depth > current_bq_depth {
1981 if self.config.extensions.blank_before_blockquote
1984 && current_bq_depth == 0
1985 && !used_shifted_bq
1986 && !blockquote_payload
1987 .as_ref()
1988 .map(|payload| payload.can_start)
1989 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1990 {
1991 self.emit_list_item_buffer_if_needed();
1995 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1996 paragraphs::append_paragraph_line(
1997 &mut self.containers,
1998 &mut self.builder,
1999 line,
2000 self.config,
2001 );
2002 return LineDispatch::consumed(1);
2003 }
2004
2005 let can_nest = if current_bq_depth > 0 {
2008 if self.config.extensions.blank_before_blockquote {
2009 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2011 || (self.pos > 0 && {
2012 let prev_line = self.lines[self.pos - 1];
2013 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2014 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2015 })
2016 } else {
2017 true
2018 }
2019 } else {
2020 blockquote_payload
2021 .as_ref()
2022 .map(|payload| payload.can_nest)
2023 .unwrap_or(true)
2024 };
2025
2026 if !can_nest {
2027 let content_at_current_depth =
2030 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2031
2032 let marker_info = self.marker_info_for_line(
2034 blockquote_payload.as_ref(),
2035 line,
2036 bq_marker_line,
2037 shifted_bq_prefix,
2038 used_shifted_bq,
2039 );
2040 for i in 0..current_bq_depth {
2041 if let Some(info) = marker_info.get(i) {
2042 self.emit_or_buffer_blockquote_marker(
2043 info.leading_spaces,
2044 info.has_trailing_space,
2045 );
2046 }
2047 }
2048
2049 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2050 paragraphs::append_paragraph_line(
2052 &mut self.containers,
2053 &mut self.builder,
2054 content_at_current_depth,
2055 self.config,
2056 );
2057 return LineDispatch::consumed(1);
2058 } else {
2059 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2061 paragraphs::append_paragraph_line(
2062 &mut self.containers,
2063 &mut self.builder,
2064 content_at_current_depth,
2065 self.config,
2066 );
2067 return LineDispatch::consumed(1);
2068 }
2069 }
2070
2071 self.emit_list_item_buffer_if_needed();
2074
2075 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2077 self.close_containers_to(self.containers.depth() - 1);
2078 }
2079
2080 let marker_info = self.marker_info_for_line(
2082 blockquote_payload.as_ref(),
2083 line,
2084 bq_marker_line,
2085 shifted_bq_prefix,
2086 used_shifted_bq,
2087 );
2088
2089 if let (Some(dispatcher_ctx), Some(prepared)) =
2090 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2091 {
2092 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2093 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2094 let _ = self.block_registry.parse_prepared(
2095 prepared,
2096 dispatcher_ctx,
2097 &mut self.builder,
2098 &stripped,
2099 );
2100 for _ in 0..bq_depth {
2101 self.containers.push(Container::BlockQuote {});
2102 }
2103 } else {
2104 for level in 0..current_bq_depth {
2106 if let Some(info) = marker_info.get(level) {
2107 self.emit_or_buffer_blockquote_marker(
2108 info.leading_spaces,
2109 info.has_trailing_space,
2110 );
2111 }
2112 }
2113
2114 for level in current_bq_depth..bq_depth {
2116 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2117
2118 if let Some(info) = marker_info.get(level) {
2120 blockquotes::emit_one_blockquote_marker(
2121 &mut self.builder,
2122 info.leading_spaces,
2123 info.has_trailing_space,
2124 );
2125 }
2126
2127 self.containers.push(Container::BlockQuote {});
2128 }
2129 }
2130
2131 let prev_flag = self.dispatch_list_marker_consumed;
2144 if used_shifted_bq && !self.innermost_li_above_bq() {
2145 self.dispatch_list_marker_consumed = true;
2146 }
2147 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2148 self.dispatch_list_marker_consumed = prev_flag;
2149 return dispatch;
2150 } else if bq_depth < current_bq_depth {
2151 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2157 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2164 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2165 let interrupts_via_fence =
2166 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2167 if !interrupts_via_hr && !interrupts_via_fence {
2168 if bq_depth > 0 {
2169 let marker_info = self.marker_info_for_line(
2175 blockquote_payload.as_ref(),
2176 line,
2177 bq_marker_line,
2178 shifted_bq_prefix,
2179 used_shifted_bq,
2180 );
2181 for i in 0..bq_depth {
2182 if let Some(info) = marker_info.get(i) {
2183 paragraphs::append_paragraph_marker(
2184 &mut self.containers,
2185 info.leading_spaces,
2186 info.has_trailing_space,
2187 );
2188 }
2189 }
2190 paragraphs::append_paragraph_line(
2191 &mut self.containers,
2192 &mut self.builder,
2193 inner_content,
2194 self.config,
2195 );
2196 } else {
2197 paragraphs::append_paragraph_line(
2198 &mut self.containers,
2199 &mut self.builder,
2200 line,
2201 self.config,
2202 );
2203 }
2204 return LineDispatch::consumed(1);
2205 }
2206 }
2207 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2215 && lists::in_blockquote_list(&self.containers)
2216 && try_parse_list_marker(
2217 line,
2218 self.config,
2219 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2220 )
2221 .is_none()
2222 {
2223 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2224 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2225 let interrupts_via_fence =
2226 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2227 if !interrupts_via_hr && !interrupts_via_fence {
2228 if bq_depth > 0 {
2229 let marker_info = self.marker_info_for_line(
2230 blockquote_payload.as_ref(),
2231 line,
2232 bq_marker_line,
2233 shifted_bq_prefix,
2234 used_shifted_bq,
2235 );
2236 if let Some(Container::ListItem {
2237 buffer,
2238 marker_only,
2239 ..
2240 }) = self.containers.stack.last_mut()
2241 {
2242 for i in 0..bq_depth {
2243 if let Some(info) = marker_info.get(i) {
2244 buffer.push_blockquote_marker(
2245 info.leading_spaces,
2246 info.has_trailing_space,
2247 );
2248 }
2249 }
2250 buffer.push_text(inner_content);
2251 if !inner_content.trim().is_empty() {
2252 *marker_only = false;
2253 }
2254 }
2255 } else if let Some(Container::ListItem {
2256 buffer,
2257 marker_only,
2258 ..
2259 }) = self.containers.stack.last_mut()
2260 {
2261 buffer.push_text(line);
2262 if !line.trim().is_empty() {
2263 *marker_only = false;
2264 }
2265 }
2266 return LineDispatch::consumed(1);
2267 }
2268 }
2269 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2275 if lists::in_blockquote_list(&self.containers)
2278 && let Some(marker_match) = try_parse_list_marker(
2279 line,
2280 self.config,
2281 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2282 )
2283 {
2284 let (indent_cols, indent_bytes) = leading_indent(line);
2285 if let Some(level) = lists::find_matching_list_level(
2286 &self.containers,
2287 &marker_match.marker,
2288 indent_cols,
2289 self.config.dialect,
2290 ) {
2291 self.close_containers_to(level + 1);
2294
2295 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2297 self.close_containers_to(self.containers.depth() - 1);
2298 }
2299 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2300 self.close_containers_to(self.containers.depth() - 1);
2301 }
2302
2303 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2305 line,
2306 marker_match.marker_len,
2307 marker_match.spaces_after_bytes,
2308 ) {
2309 let list_item = ListItemEmissionInput {
2310 content: line,
2311 marker_len: marker_match.marker_len,
2312 spaces_after_cols: marker_match.spaces_after_cols,
2313 spaces_after_bytes: marker_match.spaces_after_bytes,
2314 indent_cols,
2315 indent_bytes,
2316 virtual_marker_space: marker_match.virtual_marker_space,
2317 };
2318 lists::add_list_item_with_nested_empty_list(
2319 &mut self.containers,
2320 &mut self.builder,
2321 &list_item,
2322 nested_marker,
2323 );
2324 0
2325 } else {
2326 let list_item = ListItemEmissionInput {
2327 content: line,
2328 marker_len: marker_match.marker_len,
2329 spaces_after_cols: marker_match.spaces_after_cols,
2330 spaces_after_bytes: marker_match.spaces_after_bytes,
2331 indent_cols,
2332 indent_bytes,
2333 virtual_marker_space: marker_match.virtual_marker_space,
2334 };
2335 let finish = lists::add_list_item(
2336 &mut self.containers,
2337 &mut self.builder,
2338 &list_item,
2339 self.config,
2340 );
2341 self.dispatch_bq_after_list_item(finish)
2342 };
2343 return LineDispatch::consumed(1 + extras);
2344 }
2345 }
2346 }
2347
2348 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2350 self.close_containers_to(self.containers.depth() - 1);
2351 }
2352
2353 self.close_blockquotes_to_depth(bq_depth);
2355
2356 if bq_depth > 0 {
2358 let marker_info = self.marker_info_for_line(
2360 blockquote_payload.as_ref(),
2361 line,
2362 bq_marker_line,
2363 shifted_bq_prefix,
2364 used_shifted_bq,
2365 );
2366 for i in 0..bq_depth {
2367 if let Some(info) = marker_info.get(i) {
2368 self.emit_or_buffer_blockquote_marker(
2369 info.leading_spaces,
2370 info.has_trailing_space,
2371 );
2372 }
2373 }
2374 return self.parse_inner_content(inner_content, Some(inner_content));
2376 } else {
2377 return self.parse_inner_content(line, None);
2379 }
2380 } else if bq_depth > 0 {
2381 let mut list_item_continuation = false;
2383 let same_depth_marker_info = self.marker_info_for_line(
2384 blockquote_payload.as_ref(),
2385 line,
2386 bq_marker_line,
2387 shifted_bq_prefix,
2388 used_shifted_bq,
2389 );
2390 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2391
2392 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2404 if let Some(marker_match) = try_parse_list_marker(
2405 inner_content,
2406 self.config,
2407 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2408 ) {
2409 let inner_content_threshold =
2413 marker_match.marker_len + marker_match.spaces_after_cols;
2414 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2415 let sibling_list_level = if is_sibling_candidate {
2416 self.containers
2417 .stack
2418 .iter()
2419 .enumerate()
2420 .rev()
2421 .find_map(|(i, c)| match c {
2422 Container::List { marker, .. }
2423 if lists::markers_match(
2424 &marker_match.marker,
2425 marker,
2426 self.config.dialect,
2427 ) && self.containers.stack[..i]
2428 .iter()
2429 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2430 .count()
2431 == bq_depth =>
2432 {
2433 Some(i)
2434 }
2435 _ => None,
2436 })
2437 } else {
2438 None
2439 };
2440 if let Some(list_level) = sibling_list_level {
2441 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2447 Some(Container::List {
2448 base_indent_cols, ..
2449 }) => *base_indent_cols,
2450 _ => 0,
2451 };
2452
2453 self.emit_list_item_buffer_if_needed();
2455 self.close_containers_to(list_level + 1);
2458
2459 for i in 0..bq_depth {
2463 if let Some(info) = same_depth_marker_info.get(i) {
2464 self.emit_or_buffer_blockquote_marker(
2465 info.leading_spaces,
2466 info.has_trailing_space,
2467 );
2468 }
2469 }
2470
2471 let list_item = ListItemEmissionInput {
2473 content: inner_content,
2474 marker_len: marker_match.marker_len,
2475 spaces_after_cols: marker_match.spaces_after_cols,
2476 spaces_after_bytes: marker_match.spaces_after_bytes,
2477 indent_cols: sibling_base_indent_cols,
2478 indent_bytes: inner_indent_bytes,
2479 virtual_marker_space: marker_match.virtual_marker_space,
2480 };
2481 let finish = lists::add_list_item(
2482 &mut self.containers,
2483 &mut self.builder,
2484 &list_item,
2485 self.config,
2486 );
2487 let extras =
2488 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
2489 extras
2490 } else {
2491 self.maybe_open_indented_code_in_new_list_item();
2492 self.dispatch_bq_after_list_item(finish)
2493 };
2494 return LineDispatch::consumed(1 + extras);
2495 }
2496 }
2497
2498 if matches!(
2501 self.containers.last(),
2502 Some(Container::ListItem { content_col: _, .. })
2503 ) {
2504 let (indent_cols, _) = leading_indent(inner_content);
2505 let content_indent = self.content_container_indent_to_strip();
2506 let effective_indent = indent_cols.saturating_sub(content_indent);
2507 let content_col = match self.containers.last() {
2508 Some(Container::ListItem { content_col, .. }) => *content_col,
2509 _ => 0,
2510 };
2511
2512 let is_new_item_at_outer_level = if try_parse_list_marker(
2514 inner_content,
2515 self.config,
2516 lists::open_list_hint_at_indent(
2517 &self.containers,
2518 leading_indent(inner_content).0,
2519 ),
2520 )
2521 .is_some()
2522 {
2523 effective_indent < content_col
2524 } else {
2525 false
2526 };
2527
2528 if is_new_item_at_outer_level
2532 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2533 {
2534 log::trace!(
2535 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2536 is_new_item_at_outer_level,
2537 effective_indent,
2538 content_col
2539 );
2540 self.close_containers_to(self.containers.depth() - 1);
2541 } else {
2542 log::trace!(
2543 "Keeping ListItem: effective_indent={} >= content_col={}",
2544 effective_indent,
2545 content_col
2546 );
2547 list_item_continuation = true;
2548 }
2549 }
2550
2551 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
2555 {
2556 list_item_continuation = false;
2557 }
2558
2559 let continuation_has_explicit_marker = list_item_continuation && {
2560 if has_explicit_same_depth_marker {
2561 for i in 0..bq_depth {
2562 if let Some(info) = same_depth_marker_info.get(i) {
2563 self.emit_or_buffer_blockquote_marker(
2564 info.leading_spaces,
2565 info.has_trailing_space,
2566 );
2567 }
2568 }
2569 true
2570 } else {
2571 false
2572 }
2573 };
2574
2575 if !list_item_continuation {
2576 let marker_info = self.marker_info_for_line(
2577 blockquote_payload.as_ref(),
2578 line,
2579 bq_marker_line,
2580 shifted_bq_prefix,
2581 used_shifted_bq,
2582 );
2583 for i in 0..bq_depth {
2584 if let Some(info) = marker_info.get(i) {
2585 self.emit_or_buffer_blockquote_marker(
2586 info.leading_spaces,
2587 info.has_trailing_space,
2588 );
2589 }
2590 }
2591 }
2592 let line_to_append = if list_item_continuation {
2593 if continuation_has_explicit_marker {
2594 Some(inner_content)
2595 } else {
2596 Some(line)
2597 }
2598 } else {
2599 Some(inner_content)
2600 };
2601 let prev_flag = self.dispatch_list_marker_consumed;
2607 if used_shifted_bq && !self.innermost_li_above_bq() {
2608 self.dispatch_list_marker_consumed = true;
2609 }
2610 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2611 self.dispatch_list_marker_consumed = prev_flag;
2612 return dispatch;
2613 }
2614
2615 if current_bq_depth > 0 {
2618 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2620 paragraphs::append_paragraph_line(
2621 &mut self.containers,
2622 &mut self.builder,
2623 line,
2624 self.config,
2625 );
2626 return LineDispatch::consumed(1);
2627 }
2628
2629 if lists::in_blockquote_list(&self.containers)
2631 && let Some(marker_match) = try_parse_list_marker(
2632 line,
2633 self.config,
2634 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2635 )
2636 {
2637 let (indent_cols, indent_bytes) = leading_indent(line);
2638 if let Some(level) = lists::find_matching_list_level(
2639 &self.containers,
2640 &marker_match.marker,
2641 indent_cols,
2642 self.config.dialect,
2643 ) {
2644 self.close_containers_to(level + 1);
2646
2647 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2649 self.close_containers_to(self.containers.depth() - 1);
2650 }
2651 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2652 self.close_containers_to(self.containers.depth() - 1);
2653 }
2654
2655 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2657 line,
2658 marker_match.marker_len,
2659 marker_match.spaces_after_bytes,
2660 ) {
2661 let list_item = ListItemEmissionInput {
2662 content: line,
2663 marker_len: marker_match.marker_len,
2664 spaces_after_cols: marker_match.spaces_after_cols,
2665 spaces_after_bytes: marker_match.spaces_after_bytes,
2666 indent_cols,
2667 indent_bytes,
2668 virtual_marker_space: marker_match.virtual_marker_space,
2669 };
2670 lists::add_list_item_with_nested_empty_list(
2671 &mut self.containers,
2672 &mut self.builder,
2673 &list_item,
2674 nested_marker,
2675 );
2676 0
2677 } else {
2678 let list_item = ListItemEmissionInput {
2679 content: line,
2680 marker_len: marker_match.marker_len,
2681 spaces_after_cols: marker_match.spaces_after_cols,
2682 spaces_after_bytes: marker_match.spaces_after_bytes,
2683 indent_cols,
2684 indent_bytes,
2685 virtual_marker_space: marker_match.virtual_marker_space,
2686 };
2687 let finish = lists::add_list_item(
2688 &mut self.containers,
2689 &mut self.builder,
2690 &list_item,
2691 self.config,
2692 );
2693 self.dispatch_bq_after_list_item(finish)
2694 };
2695 return LineDispatch::consumed(1 + extras);
2696 }
2697 }
2698 }
2699
2700 self.parse_inner_content(line, None)
2702 }
2703
2704 fn content_container_indent_to_strip(&self) -> usize {
2706 self.containers
2707 .stack
2708 .iter()
2709 .filter_map(|c| match c {
2710 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2711 Container::Definition { content_col, .. } => Some(*content_col),
2712 _ => None,
2713 })
2714 .sum()
2715 }
2716
2717 fn innermost_li_above_bq(&self) -> bool {
2724 for c in self.containers.stack.iter().rev() {
2725 match c {
2726 Container::ListItem { .. } => return true,
2727 Container::BlockQuote { .. } => return false,
2728 _ => continue,
2729 }
2730 }
2731 false
2732 }
2733
2734 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2740 log::trace!(
2741 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2742 self.pos,
2743 self.containers.depth(),
2744 self.containers.last(),
2745 content.trim_end()
2746 );
2747 let content_indent = self.content_container_indent_to_strip();
2752 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
2753
2754 if self.config.extensions.alerts
2755 && self.current_blockquote_depth() > 0
2756 && !self.in_active_alert()
2757 && !self.is_paragraph_open()
2758 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2759 {
2760 let (_, newline_str) = strip_newline(stripped_content);
2761 self.builder.start_node(SyntaxKind::ALERT.into());
2762 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2763 if !newline_str.is_empty() {
2764 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2765 }
2766 self.containers.push(Container::Alert {
2767 blockquote_depth: self.current_blockquote_depth(),
2768 });
2769 return LineDispatch::consumed(1);
2770 }
2771
2772 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2776 let is_definition_marker =
2777 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2778 && !stripped_content.starts_with(':');
2779 if content_indent == 0 && is_definition_marker {
2780 } else {
2782 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2783
2784 if policy.definition_plain_can_continue(
2785 stripped_content,
2786 content,
2787 content_indent,
2788 &BlockContext {
2789 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2790 has_blank_before_strict: self.pos == 0
2791 || is_blank_line(self.lines[self.pos - 1]),
2792 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2793 in_fenced_div: self.in_fenced_div(),
2794 blockquote_depth: self.current_blockquote_depth(),
2795 config: self.config,
2796 content_indent,
2797 indent_to_emit: None,
2798 list_indent_info: None,
2799 in_list: lists::in_list(&self.containers),
2800 in_marker_only_list_item: matches!(
2801 self.containers.last(),
2802 Some(Container::ListItem {
2803 marker_only: true,
2804 ..
2805 })
2806 ),
2807 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2808 paragraph_open: self.is_paragraph_open(),
2809 next_line: if self.pos + 1 < self.lines.len() {
2810 Some(self.lines[self.pos + 1])
2811 } else {
2812 None
2813 },
2814 open_alpha_hint: lists::open_list_hint_at_indent(
2815 &self.containers,
2816 leading_indent(stripped_content).0,
2817 ),
2818 },
2819 &self.lines,
2820 self.pos,
2821 ) {
2822 let content_line = stripped_content;
2823 let (text_without_newline, newline_str) = strip_newline(content_line);
2824 let indent_prefix = if !text_without_newline.trim().is_empty() {
2825 indent_to_emit.unwrap_or("")
2826 } else {
2827 ""
2828 };
2829 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2830
2831 if let Some(Container::Definition {
2832 plain_open,
2833 plain_buffer,
2834 ..
2835 }) = self.containers.stack.last_mut()
2836 {
2837 let line_with_newline = if !newline_str.is_empty() {
2838 format!("{}{}", content_line, newline_str)
2839 } else {
2840 content_line
2841 };
2842 plain_buffer.push_line(line_with_newline);
2843 *plain_open = true;
2844 }
2845
2846 return LineDispatch::consumed(1);
2847 }
2848 }
2849 }
2850
2851 if content_indent > 0 {
2854 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2855 let current_bq_depth = self.current_blockquote_depth();
2856 let in_footnote_definition = self
2857 .containers
2858 .stack
2859 .iter()
2860 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2861
2862 if bq_depth > 0 {
2863 if in_footnote_definition
2864 && self.config.extensions.blank_before_blockquote
2865 && current_bq_depth == 0
2866 && !blockquotes::can_start_blockquote(self.pos, &self.lines)
2867 {
2868 } else {
2872 self.emit_buffered_plain_if_needed();
2875 self.emit_list_item_buffer_if_needed();
2876
2877 self.close_paragraph_if_open();
2880
2881 if bq_depth < current_bq_depth {
2882 self.close_blockquotes_to_depth(bq_depth);
2883 } else {
2884 let marker_info = parse_blockquote_marker_info(stripped_content);
2885
2886 if bq_depth > current_bq_depth {
2887 for level in current_bq_depth..bq_depth {
2889 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2890
2891 if level == current_bq_depth
2892 && let Some(indent_str) = indent_to_emit
2893 {
2894 self.builder
2895 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2896 }
2897
2898 if let Some(info) = marker_info.get(level) {
2899 blockquotes::emit_one_blockquote_marker(
2900 &mut self.builder,
2901 info.leading_spaces,
2902 info.has_trailing_space,
2903 );
2904 }
2905
2906 self.containers.push(Container::BlockQuote {});
2907 }
2908 } else {
2909 self.emit_blockquote_markers(&marker_info, bq_depth);
2911 }
2912 }
2913
2914 return self.parse_inner_content(inner_content, Some(inner_content));
2915 }
2916 }
2917 }
2918
2919 let content = stripped_content;
2921
2922 if self.is_paragraph_open()
2923 && (paragraphs::has_open_inline_math_environment(&self.containers)
2924 || paragraphs::has_open_display_math_dollars(&self.containers))
2925 {
2926 paragraphs::append_paragraph_line(
2927 &mut self.containers,
2928 &mut self.builder,
2929 line_to_append.unwrap_or(self.lines[self.pos]),
2930 self.config,
2931 );
2932 return LineDispatch::consumed(1);
2933 }
2934
2935 use super::blocks::lists;
2939 use super::blocks::paragraphs;
2940 let list_indent_info = if lists::in_list(&self.containers) {
2941 let content_col = paragraphs::current_content_col(&self.containers);
2942 if content_col > 0 {
2943 Some(super::block_dispatcher::ListIndentInfo { content_col })
2944 } else {
2945 None
2946 }
2947 } else {
2948 None
2949 };
2950
2951 let next_line = if self.pos + 1 < self.lines.len() {
2952 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2955 } else {
2956 None
2957 };
2958
2959 let current_bq_depth = self.current_blockquote_depth();
2960 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2961 && current_bq_depth < alert_bq_depth
2962 {
2963 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2964 self.close_containers_to(self.containers.depth() - 1);
2965 }
2966 }
2967
2968 let dispatcher_ctx = BlockContext {
2969 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2973 blockquote_depth: current_bq_depth,
2974 config: self.config,
2975 content_indent,
2976 indent_to_emit,
2977 list_indent_info,
2978 in_list: lists::in_list(&self.containers),
2979 in_marker_only_list_item: matches!(
2980 self.containers.last(),
2981 Some(Container::ListItem {
2982 marker_only: true,
2983 ..
2984 })
2985 ),
2986 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2987 paragraph_open: self.is_paragraph_open(),
2988 next_line,
2989 open_alpha_hint: lists::open_list_hint_at_indent(
2990 &self.containers,
2991 leading_indent(content).0,
2992 ),
2993 };
2994
2995 let mut dispatcher_ctx = dispatcher_ctx;
2998
2999 let dispatcher_prefix =
3006 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3007
3008 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3012 return dispatch;
3013 }
3014
3015 let dispatcher_match = {
3018 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3019 self.block_registry
3020 .detect_prepared(&dispatcher_ctx, &stripped)
3021 };
3022
3023 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3029 let has_blank_before = if self.pos == 0 || after_metadata_block {
3030 true
3031 } else {
3032 let prev_line = self.lines[self.pos - 1];
3033 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3034 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3035 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3036 && fenced_divs::try_parse_div_fence_open(
3037 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3038 )
3039 .is_some();
3040
3041 let prev_line_blank = is_blank_line(prev_line);
3042 prev_line_blank
3043 || prev_is_fenced_div_open
3044 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3045 || !self.previous_block_requires_blank_before_heading()
3046 };
3047
3048 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3051
3052 let prev_line_blank = if self.pos > 0 {
3053 let prev_line = self.lines[self.pos - 1];
3054 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3055 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3056 } else {
3057 false
3058 };
3059 let has_blank_before_strict = at_document_start || prev_line_blank;
3060
3061 dispatcher_ctx.has_blank_before = has_blank_before;
3062 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3063 dispatcher_ctx.at_document_start = at_document_start;
3064
3065 let dispatcher_match =
3066 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3067 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3069 self.block_registry
3070 .detect_prepared(&dispatcher_ctx, &stripped)
3071 } else {
3072 dispatcher_match
3073 };
3074
3075 if has_blank_before {
3076 if let Some(env_name) = extract_environment_name(content)
3077 && is_inline_math_environment(env_name)
3078 {
3079 if !self.is_paragraph_open() {
3080 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3081 }
3082 paragraphs::append_paragraph_line(
3083 &mut self.containers,
3084 &mut self.builder,
3085 line_to_append.unwrap_or(self.lines[self.pos]),
3086 self.config,
3087 );
3088 return LineDispatch::consumed(1);
3089 }
3090
3091 if let Some(block_match) = dispatcher_match.as_ref() {
3092 let detection = block_match.detection;
3093
3094 match detection {
3095 BlockDetectionResult::YesCanInterrupt => {
3096 self.emit_list_item_buffer_if_needed();
3097 if self.is_paragraph_open() {
3098 self.close_containers_to(self.containers.depth() - 1);
3099 }
3100 }
3101 BlockDetectionResult::Yes => {
3102 self.prepare_for_block_element();
3103 }
3104 BlockDetectionResult::No => unreachable!(),
3105 }
3106
3107 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3108 self.close_containers_to_fenced_div();
3109 }
3110
3111 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3112 self.close_open_footnote_definition();
3113 }
3114
3115 let lines_consumed = {
3116 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3117 self.block_registry.parse_prepared(
3118 block_match,
3119 &dispatcher_ctx,
3120 &mut self.builder,
3121 &stripped,
3122 )
3123 };
3124
3125 if matches!(
3126 self.block_registry.parser_name(block_match),
3127 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3128 ) {
3129 self.after_metadata_block = true;
3130 }
3131
3132 let extras = match block_match.effect {
3133 BlockEffect::None => 0,
3134 BlockEffect::OpenFencedDiv => {
3135 self.containers.push(Container::FencedDiv {});
3136 0
3137 }
3138 BlockEffect::CloseFencedDiv => {
3139 self.close_fenced_div();
3140 0
3141 }
3142 BlockEffect::OpenFootnoteDefinition => {
3143 self.handle_footnote_open_effect(block_match, content)
3144 }
3145 BlockEffect::OpenList => {
3146 self.handle_list_open_effect(block_match, content, indent_to_emit)
3147 }
3148 BlockEffect::OpenDefinitionList => {
3149 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3150 }
3151 BlockEffect::OpenBlockQuote => {
3152 0
3154 }
3155 };
3156
3157 if lines_consumed == 0 {
3158 log::warn!(
3159 "block parser made no progress at line {} (parser={})",
3160 self.pos + 1,
3161 self.block_registry.parser_name(block_match)
3162 );
3163 return LineDispatch::Rejected;
3164 }
3165
3166 return LineDispatch::consumed(lines_consumed + extras);
3167 }
3168 } else if let Some(block_match) = dispatcher_match.as_ref() {
3169 let parser_name = self.block_registry.parser_name(block_match);
3172 match block_match.detection {
3173 BlockDetectionResult::YesCanInterrupt => {
3174 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3175 && self.is_paragraph_open()
3176 {
3177 if !self.is_paragraph_open() {
3179 paragraphs::start_paragraph_if_needed(
3180 &mut self.containers,
3181 &mut self.builder,
3182 );
3183 }
3184 paragraphs::append_paragraph_line(
3185 &mut self.containers,
3186 &mut self.builder,
3187 line_to_append.unwrap_or(self.lines[self.pos]),
3188 self.config,
3189 );
3190 return LineDispatch::consumed(1);
3191 }
3192
3193 if matches!(block_match.effect, BlockEffect::OpenList)
3194 && self.is_paragraph_open()
3195 && !lists::in_list(&self.containers)
3196 && self.content_container_indent_to_strip() == 0
3197 {
3198 let allow_interrupt =
3204 self.config.dialect == crate::options::Dialect::CommonMark && {
3205 use super::block_dispatcher::ListPrepared;
3206 use super::blocks::lists::OrderedMarker;
3207 let prepared = block_match
3208 .payload
3209 .as_ref()
3210 .and_then(|p| p.downcast_ref::<ListPrepared>());
3211 match prepared.map(|p| &p.marker) {
3212 Some(ListMarker::Bullet(_)) => true,
3213 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3214 number,
3215 ..
3216 })) => number == "1",
3217 _ => false,
3218 }
3219 };
3220 if !allow_interrupt {
3221 paragraphs::append_paragraph_line(
3222 &mut self.containers,
3223 &mut self.builder,
3224 line_to_append.unwrap_or(self.lines[self.pos]),
3225 self.config,
3226 );
3227 return LineDispatch::consumed(1);
3228 }
3229 }
3230
3231 if matches!(block_match.effect, BlockEffect::OpenList)
3238 && self.try_lazy_list_continuation(block_match, content)
3239 {
3240 return LineDispatch::consumed(1);
3241 }
3242
3243 self.emit_list_item_buffer_if_needed();
3244 if self.is_paragraph_open() {
3245 if self.html_block_demotes_paragraph_to_plain(block_match) {
3246 self.close_paragraph_as_plain_if_open();
3247 } else {
3248 self.close_containers_to(self.containers.depth() - 1);
3249 }
3250 }
3251
3252 if self.config.dialect == crate::options::Dialect::CommonMark
3259 && !matches!(block_match.effect, BlockEffect::OpenList)
3260 {
3261 let (indent_cols, _) = leading_indent(content);
3262 self.close_lists_above_indent(indent_cols);
3263 }
3264 }
3265 BlockDetectionResult::Yes => {
3266 if parser_name == "setext_heading"
3278 && self.is_paragraph_open()
3279 && self.config.dialect == crate::options::Dialect::CommonMark
3280 {
3281 let text_line = self.lines[self.pos];
3282 let underline_line = self.lines[self.pos + 1];
3283 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3284 let level = if underline_char == '=' { 1 } else { 2 };
3285 self.emit_setext_heading_folding_paragraph(
3286 text_line,
3287 underline_line,
3288 level,
3289 );
3290 return LineDispatch::consumed(2);
3291 }
3292
3293 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3296 if !self.is_paragraph_open() {
3297 paragraphs::start_paragraph_if_needed(
3298 &mut self.containers,
3299 &mut self.builder,
3300 );
3301 }
3302 paragraphs::append_paragraph_line(
3303 &mut self.containers,
3304 &mut self.builder,
3305 line_to_append.unwrap_or(self.lines[self.pos]),
3306 self.config,
3307 );
3308 return LineDispatch::consumed(1);
3309 }
3310
3311 if parser_name == "reference_definition" && self.is_paragraph_open() {
3314 paragraphs::append_paragraph_line(
3315 &mut self.containers,
3316 &mut self.builder,
3317 line_to_append.unwrap_or(self.lines[self.pos]),
3318 self.config,
3319 );
3320 return LineDispatch::consumed(1);
3321 }
3322 }
3323 BlockDetectionResult::No => unreachable!(),
3324 }
3325
3326 if !matches!(block_match.detection, BlockDetectionResult::No) {
3327 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3328 self.close_containers_to_fenced_div();
3329 }
3330
3331 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3332 self.close_open_footnote_definition();
3333 }
3334
3335 let lines_consumed = {
3336 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3337 self.block_registry.parse_prepared(
3338 block_match,
3339 &dispatcher_ctx,
3340 &mut self.builder,
3341 &stripped,
3342 )
3343 };
3344
3345 let extras = match block_match.effect {
3346 BlockEffect::None => 0,
3347 BlockEffect::OpenFencedDiv => {
3348 self.containers.push(Container::FencedDiv {});
3349 0
3350 }
3351 BlockEffect::CloseFencedDiv => {
3352 self.close_fenced_div();
3353 0
3354 }
3355 BlockEffect::OpenFootnoteDefinition => {
3356 self.handle_footnote_open_effect(block_match, content)
3357 }
3358 BlockEffect::OpenList => {
3359 self.handle_list_open_effect(block_match, content, indent_to_emit)
3360 }
3361 BlockEffect::OpenDefinitionList => {
3362 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3363 }
3364 BlockEffect::OpenBlockQuote => {
3365 0
3367 }
3368 };
3369
3370 if lines_consumed == 0 {
3371 log::warn!(
3372 "block parser made no progress at line {} (parser={})",
3373 self.pos + 1,
3374 self.block_registry.parser_name(block_match)
3375 );
3376 return LineDispatch::Rejected;
3377 }
3378
3379 return LineDispatch::consumed(lines_consumed + extras);
3380 }
3381 }
3382
3383 if self.config.extensions.line_blocks
3385 && (has_blank_before || self.pos == 0)
3386 && try_parse_line_block_start(content).is_some()
3387 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3391 {
3392 log::trace!("Parsed line block at line {}", self.pos);
3393 self.close_paragraph_if_open();
3395
3396 let new_pos = parse_line_block(
3402 &self.lines,
3403 self.pos,
3404 &mut self.builder,
3405 self.config,
3406 0,
3407 0,
3408 false,
3409 false,
3410 0,
3411 );
3412 if new_pos > self.pos {
3413 return LineDispatch::consumed(new_pos - self.pos);
3414 }
3415 }
3416
3417 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3420 log::trace!(
3421 "Inside ListItem - buffering content: {:?}",
3422 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3423 );
3424 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3426
3427 if let Some(Container::ListItem {
3429 buffer,
3430 marker_only,
3431 ..
3432 }) = self.containers.stack.last_mut()
3433 {
3434 buffer.push_text(line);
3435 if !is_blank_line(line) {
3436 *marker_only = false;
3437 }
3438 }
3439
3440 return LineDispatch::consumed(1);
3441 }
3442
3443 log::trace!(
3444 "Not in ListItem - creating paragraph for: {:?}",
3445 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3446 );
3447 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3449 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3452 paragraphs::append_paragraph_line(
3453 &mut self.containers,
3454 &mut self.builder,
3455 line,
3456 self.config,
3457 );
3458 LineDispatch::consumed(1)
3459 }
3460
3461 fn fenced_div_container_index(&self) -> Option<usize> {
3462 self.containers
3463 .stack
3464 .iter()
3465 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3466 }
3467
3468 fn close_containers_to_fenced_div(&mut self) {
3469 if let Some(index) = self.fenced_div_container_index() {
3470 self.close_containers_to(index + 1);
3471 }
3472 }
3473
3474 fn close_fenced_div(&mut self) {
3475 if let Some(index) = self.fenced_div_container_index() {
3476 self.close_containers_to(index);
3477 }
3478 }
3479
3480 fn in_fenced_div(&self) -> bool {
3481 self.containers
3482 .stack
3483 .iter()
3484 .any(|c| matches!(c, Container::FencedDiv { .. }))
3485 }
3486
3487 fn in_footnote_definition(&self) -> bool {
3495 self.containers
3496 .stack
3497 .iter()
3498 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3499 }
3500}
3501
3502fn emit_definition_plain_or_heading(
3509 builder: &mut GreenNodeBuilder<'static>,
3510 text: &str,
3511 config: &ParserOptions,
3512 suppress_footnote_refs: bool,
3513) {
3514 let line_without_newline = text
3515 .strip_suffix("\r\n")
3516 .or_else(|| text.strip_suffix('\n'));
3517 if let Some(line) = line_without_newline
3518 && !line.contains('\n')
3519 && !line.contains('\r')
3520 && let Some(level) = try_parse_atx_heading(line)
3521 {
3522 emit_atx_heading(builder, text, level, config);
3523 return;
3524 }
3525
3526 if let Some(first_nl) = text.find('\n') {
3528 let first_line = &text[..first_nl];
3529 let after_first = &text[first_nl + 1..];
3530 if !after_first.is_empty()
3531 && let Some(level) = try_parse_atx_heading(first_line)
3532 {
3533 let heading_bytes = &text[..first_nl + 1];
3534 emit_atx_heading(builder, heading_bytes, level, config);
3535 builder.start_node(SyntaxKind::PLAIN.into());
3536 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3537 builder.finish_node();
3538 return;
3539 }
3540 }
3541
3542 builder.start_node(SyntaxKind::PLAIN.into());
3543 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3544 builder.finish_node();
3545}
3546
3547fn footnote_first_line_term_lookahead(
3556 lines: &[&str],
3557 pos: usize,
3558 content_col: usize,
3559 table_captions_enabled: bool,
3560) -> Option<usize> {
3561 let mut check_pos = pos + 1;
3562 let mut blank_count = 0;
3563 while check_pos < lines.len() {
3564 let line = lines[check_pos];
3565 let (trimmed, _) = strip_newline(line);
3566 if trimmed.trim().is_empty() {
3567 blank_count += 1;
3568 check_pos += 1;
3569 continue;
3570 }
3571 let (line_indent_cols, _) = leading_indent(trimmed);
3572 if line_indent_cols < content_col {
3573 return None;
3574 }
3575 let strip_bytes = byte_index_at_column(trimmed, content_col);
3576 if strip_bytes > trimmed.len() {
3577 return None;
3578 }
3579 let stripped = &trimmed[strip_bytes..];
3580 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3581 if marker == ':'
3585 && table_captions_enabled
3586 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3587 {
3588 return None;
3589 }
3590 return Some(blank_count);
3591 }
3592 return None;
3593 }
3594 None
3595}