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::diagnostics::{Diagnostics, SyntaxError};
24use super::utils::container_stack;
25use super::utils::helpers::{is_blank_line, split_lines_inclusive, strip_newline};
26use super::utils::inline_emission;
27use super::utils::marker_utils;
28use super::utils::text_buffer;
29
30use super::blocks::blockquotes::strip_n_blockquote_markers;
31use super::utils::continuation::ContinuationPolicy;
32use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
33use definition_lists::{emit_definition_marker, emit_term};
34use line_blocks::{parse_line_block, try_parse_line_block_start};
35use lists::{
36 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
37 try_parse_list_marker,
38};
39use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
40use text_buffer::TextBuffer;
41
42const GITHUB_ALERT_MARKERS: [&str; 5] = [
43 "[!TIP]",
44 "[!WARNING]",
45 "[!IMPORTANT]",
46 "[!CAUTION]",
47 "[!NOTE]",
48];
49
50#[must_use]
55#[derive(Debug, Clone, Copy)]
56pub(crate) enum LineDispatch {
57 Consumed(usize),
59 Rejected,
61}
62
63impl LineDispatch {
64 #[inline]
68 pub(crate) fn consumed(n: usize) -> Self {
69 debug_assert!(n >= 1, "LineDispatch::Consumed requires n >= 1");
70 LineDispatch::Consumed(n)
71 }
72}
73
74pub struct Parser<'a> {
75 lines: Vec<&'a str>,
76 pos: usize,
77 builder: GreenNodeBuilder<'static>,
78 containers: ContainerStack,
79 config: &'a ParserOptions,
80 block_registry: BlockParserRegistry,
81 after_metadata_block: bool,
85 dispatch_list_marker_consumed: bool,
95 diagnostics: Diagnostics,
99}
100
101impl<'a> Parser<'a> {
102 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
103 let lines = split_lines_inclusive(input);
105 Self {
106 lines,
107 pos: 0,
108 builder: GreenNodeBuilder::new(),
109 containers: ContainerStack::new(),
110 config,
111 block_registry: BlockParserRegistry::new(),
112 after_metadata_block: false,
113 dispatch_list_marker_consumed: false,
114 diagnostics: Diagnostics::new(),
115 }
116 }
117
118 pub fn parse(self) -> SyntaxNode {
119 self.parse_with_errors().0
120 }
121
122 pub fn parse_with_errors(mut self) -> (SyntaxNode, Vec<SyntaxError>) {
125 self.parse_document_stack();
126 let node = SyntaxNode::new_root(self.builder.finish());
127 let errors = self.diagnostics.take();
128 (node, errors)
129 }
130
131 fn close_lists_above_indent(&mut self, indent_cols: usize) {
142 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
143 if indent_cols >= *content_col {
144 break;
145 }
146 self.close_containers_to(self.containers.depth() - 1);
147 if matches!(self.containers.last(), Some(Container::List { .. })) {
148 self.close_containers_to(self.containers.depth() - 1);
149 }
150 }
151 }
152
153 fn close_containers_to(&mut self, keep: usize) {
156 while self.containers.depth() > keep {
158 match self.containers.stack.last() {
159 Some(Container::ListItem {
161 buffer,
162 content_col,
163 ..
164 }) if !buffer.is_empty() => {
165 let buffer_clone = buffer.clone();
167 let item_content_col = *content_col;
168
169 log::trace!(
170 "Closing ListItem with buffer (is_empty={}, segment_count={})",
171 buffer_clone.is_empty(),
172 buffer_clone.segment_count()
173 );
174
175 let parent_list_is_loose = self
179 .containers
180 .stack
181 .iter()
182 .rev()
183 .find_map(|c| match c {
184 Container::List {
185 has_blank_between_items,
186 ..
187 } => Some(*has_blank_between_items),
188 _ => None,
189 })
190 .unwrap_or(false);
191
192 let use_paragraph =
193 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
194
195 log::trace!(
196 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
197 use_paragraph,
198 parent_list_is_loose,
199 buffer_clone.has_blank_lines_between_content()
200 );
201
202 let suppress_footnote_refs = self.in_footnote_definition();
203 self.containers.stack.pop();
205 buffer_clone.emit_as_block(
207 &mut self.builder,
208 use_paragraph,
209 self.config,
210 item_content_col,
211 suppress_footnote_refs,
212 );
213 self.builder.finish_node(); }
215 Some(Container::ListItem { .. }) => {
217 log::trace!("Closing empty ListItem (no buffer content)");
218 self.containers.stack.pop();
220 self.builder.finish_node();
221 }
222 Some(Container::Paragraph {
224 buffer,
225 start_checkpoint,
226 ..
227 }) if !buffer.is_empty() => {
228 let buffer_clone = buffer.clone();
230 let checkpoint = *start_checkpoint;
231 let suppress_footnote_refs = self.in_footnote_definition();
232 self.containers.stack.pop();
234 self.builder
236 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
237 buffer_clone.emit_with_inlines(
238 &mut self.builder,
239 self.config,
240 suppress_footnote_refs,
241 );
242 self.builder.finish_node();
243 }
244 Some(Container::Paragraph {
246 start_checkpoint, ..
247 }) => {
248 let checkpoint = *start_checkpoint;
249 self.containers.stack.pop();
251 self.builder
252 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
253 self.builder.finish_node();
254 }
255 Some(Container::Definition {
257 plain_open: true,
258 plain_buffer,
259 ..
260 }) if !plain_buffer.is_empty() => {
261 let text = plain_buffer.get_accumulated_text();
262 let suppress_footnote_refs = self.in_footnote_definition();
263 emit_definition_plain_or_heading(
264 &mut self.builder,
265 &text,
266 self.config,
267 suppress_footnote_refs,
268 );
269
270 if let Some(Container::Definition {
272 plain_open,
273 plain_buffer,
274 ..
275 }) = self.containers.stack.last_mut()
276 {
277 plain_buffer.clear();
278 *plain_open = false;
279 }
280
281 self.containers.stack.pop();
283 self.builder.finish_node();
284 }
285 Some(Container::Definition {
287 plain_open: true, ..
288 }) => {
289 if let Some(Container::Definition {
291 plain_open,
292 plain_buffer,
293 ..
294 }) = self.containers.stack.last_mut()
295 {
296 plain_buffer.clear();
297 *plain_open = false;
298 }
299
300 self.containers.stack.pop();
302 self.builder.finish_node();
303 }
304 _ => {
306 self.containers.stack.pop();
307 self.builder.finish_node();
308 }
309 }
310 }
311 }
312
313 fn emit_buffered_plain_if_needed(&mut self) {
316 if let Some(Container::Definition {
318 plain_open: true,
319 plain_buffer,
320 ..
321 }) = self.containers.stack.last()
322 && !plain_buffer.is_empty()
323 {
324 let text = plain_buffer.get_accumulated_text();
325 let suppress_footnote_refs = self.in_footnote_definition();
326 emit_definition_plain_or_heading(
327 &mut self.builder,
328 &text,
329 self.config,
330 suppress_footnote_refs,
331 );
332 }
333
334 if let Some(Container::Definition {
336 plain_open,
337 plain_buffer,
338 ..
339 }) = self.containers.stack.last_mut()
340 && *plain_open
341 {
342 plain_buffer.clear();
343 *plain_open = false;
344 }
345 }
346
347 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
352 let mut current = self.current_blockquote_depth();
353 while current > target_depth {
354 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
355 if self.containers.depth() == 0 {
356 break;
357 }
358 self.close_containers_to(self.containers.depth() - 1);
359 }
360 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
361 self.close_containers_to(self.containers.depth() - 1);
362 current -= 1;
363 } else {
364 break;
365 }
366 }
367 }
368
369 fn active_alert_blockquote_depth(&self) -> Option<usize> {
370 self.containers.stack.iter().rev().find_map(|c| match c {
371 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
372 _ => None,
373 })
374 }
375
376 fn in_active_alert(&self) -> bool {
377 self.active_alert_blockquote_depth().is_some()
378 }
379
380 fn previous_block_requires_blank_before_heading(&self) -> bool {
381 matches!(
382 self.containers.last(),
383 Some(Container::Paragraph { .. })
384 | Some(Container::ListItem { .. })
385 | Some(Container::Definition { .. })
386 | Some(Container::DefinitionItem { .. })
387 | Some(Container::FootnoteDefinition { .. })
388 )
389 }
390
391 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
392 let (without_newline, _) = strip_newline(content);
393 let trimmed = without_newline.trim();
394 GITHUB_ALERT_MARKERS
395 .into_iter()
396 .find(|marker| *marker == trimmed)
397 }
398
399 fn emit_list_item_buffer_if_needed(&mut self) {
402 if let Some(Container::ListItem {
403 buffer,
404 content_col,
405 ..
406 }) = self.containers.stack.last_mut()
407 && !buffer.is_empty()
408 {
409 let buffer_clone = buffer.clone();
410 let item_content_col = *content_col;
411 buffer.clear();
412 let use_paragraph = buffer_clone.has_blank_lines_between_content();
413 let suppress_footnote_refs = self.in_footnote_definition();
414 buffer_clone.emit_as_block(
415 &mut self.builder,
416 use_paragraph,
417 self.config,
418 item_content_col,
419 suppress_footnote_refs,
420 );
421 }
422 }
423
424 fn dispatch_bq_after_list_item(
441 &mut self,
442 result: super::blocks::lists::ListItemFinish,
443 ) -> usize {
444 let super::blocks::lists::ListItemFinish::BqDispatch { content } = result else {
445 return 0;
446 };
447 let pos_before = self.pos;
448 self.dispatch_list_marker_consumed = true;
453 let dispatch = self.parse_inner_content(&content, Some(&content));
454 self.dispatch_list_marker_consumed = false;
455 self.pos = pos_before;
456 match dispatch {
457 LineDispatch::Consumed(n) => n.saturating_sub(1),
458 LineDispatch::Rejected => 0,
459 }
460 }
461
462 fn maybe_open_fenced_code_in_new_list_item(&mut self) -> Option<usize> {
473 let Some(Container::ListItem {
474 content_col,
475 buffer,
476 ..
477 }) = self.containers.stack.last()
478 else {
479 return None;
480 };
481 let content_col = *content_col;
482 let text = buffer.first_text()?;
483 if buffer.segment_count() != 1 {
484 return None;
485 }
486 let text_owned = text.to_string();
487 let fence = code_blocks::try_parse_fence_open(&text_owned, self.config.dialect)?;
488 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
489 let has_info = !fence.info_string.trim().is_empty();
490 let bq_depth = self.current_blockquote_depth();
491 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
492 if !(has_info || has_matching_closer || common_mark_dialect) {
493 return None;
494 }
495 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
497 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
498 {
499 return None;
500 }
501 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
502 buffer.clear();
503 }
504 let prefix = ContainerPrefix::from_scalars(bq_depth, content_col, bq_depth > 0, 0, true);
508 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
509 let new_pos = code_blocks::parse_fenced_code_block(
510 &mut self.builder,
511 &window,
512 fence,
513 Some(&text_owned),
514 &self.diagnostics,
515 );
516 Some(new_pos.saturating_sub(self.pos).saturating_sub(1))
517 }
518
519 fn maybe_open_indented_code_in_new_list_item(&mut self) {
530 let Some(Container::ListItem {
531 content_col,
532 buffer,
533 marker_only,
534 virtual_marker_space,
535 }) = self.containers.stack.last()
536 else {
537 return;
538 };
539 if *marker_only {
540 return;
541 }
542 if buffer.segment_count() != 1 {
543 return;
544 }
545 let Some(text) = buffer.first_text() else {
546 return;
547 };
548 let content_col = *content_col;
549 let virtual_marker_space = *virtual_marker_space;
550 let text_owned = text.to_string();
551
552 let mut iter = text_owned.split_inclusive('\n');
554 let line_with_nl = iter.next().unwrap_or("").to_string();
555 if iter.next().is_some() {
556 return;
557 }
558
559 let line_no_nl = line_with_nl
560 .strip_suffix("\r\n")
561 .or_else(|| line_with_nl.strip_suffix('\n'))
562 .unwrap_or(&line_with_nl);
563 let nl_suffix = &line_with_nl[line_no_nl.len()..];
564
565 let buffer_start_col = if virtual_marker_space {
566 content_col.saturating_sub(1)
567 } else {
568 content_col
569 };
570
571 let target = content_col + 4;
572 let (cols_walked, ws_bytes) =
573 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
574
575 if buffer_start_col + cols_walked < target {
576 return;
577 }
578 if ws_bytes >= line_no_nl.len() {
579 return;
580 }
581
582 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
583 buffer.clear();
584 }
585
586 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
587 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
588 if ws_bytes > 0 {
589 self.builder
590 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
591 }
592 let rest = &line_no_nl[ws_bytes..];
593 if !rest.is_empty() {
594 self.builder.token(SyntaxKind::TEXT.into(), rest);
595 }
596 if !nl_suffix.is_empty() {
597 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
598 }
599 self.builder.finish_node();
600 self.builder.finish_node();
601 }
602
603 fn has_matching_fence_closer(
604 &self,
605 fence: &code_blocks::FenceInfo,
606 bq_depth: usize,
607 content_col: usize,
608 ) -> bool {
609 for raw_line in self.lines.iter().skip(self.pos + 1) {
610 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
611 if line_bq_depth < bq_depth {
612 break;
613 }
614 let candidate = if content_col > 0 && !inner.is_empty() {
615 let idx = byte_index_at_column(inner, content_col);
616 if idx <= inner.len() {
617 &inner[idx..]
618 } else {
619 inner
620 }
621 } else {
622 inner
623 };
624 if code_blocks::is_closing_fence(candidate, fence) {
625 return true;
626 }
627 }
628 false
629 }
630
631 fn is_paragraph_open(&self) -> bool {
633 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
634 }
635
636 fn emit_setext_heading_folding_paragraph(
644 &mut self,
645 text_line: &str,
646 underline_line: &str,
647 level: usize,
648 ) {
649 let (buffered_text, checkpoint) = match self.containers.stack.last() {
650 Some(Container::Paragraph {
651 buffer,
652 start_checkpoint,
653 ..
654 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
655 _ => (String::new(), None),
656 };
657
658 if checkpoint.is_some() {
659 self.containers.stack.pop();
660 }
661
662 let combined_text = if buffered_text.is_empty() {
663 text_line.to_string()
664 } else {
665 format!("{}{}", buffered_text, text_line)
666 };
667
668 let cp = checkpoint.expect(
669 "emit_setext_heading_folding_paragraph requires an open paragraph; \
670 single-line setext should go through the regular dispatcher path",
671 );
672 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
673 emit_setext_heading_body(
674 &mut self.builder,
675 &combined_text,
676 underline_line,
677 level,
678 self.config,
679 );
680 self.builder.finish_node();
681 }
682
683 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
701 let Some(Container::ListItem {
702 buffer,
703 content_col,
704 ..
705 }) = self.containers.stack.last()
706 else {
707 return None;
708 };
709 if buffer.segment_count() != 1 {
710 return None;
711 }
712 let text_line = buffer.first_text()?;
713
714 let content_col = *content_col;
719 let (underline_indent_cols, _) = leading_indent(content);
720 if underline_indent_cols < content_col {
721 return None;
722 }
723
724 let lines = [text_line, content];
725 let (level, _) = try_parse_setext_heading(&lines, 0)?;
726
727 let (text_no_newline, _) = strip_newline(text_line);
728 if text_no_newline.trim().is_empty() {
729 return None;
730 }
731 if try_parse_horizontal_rule(text_no_newline).is_some() {
732 return None;
733 }
734
735 let text_owned = text_line.to_string();
736 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
737 buffer.clear();
738 }
739 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
740 Some(LineDispatch::consumed(1))
741 }
742
743 fn close_paragraph_if_open(&mut self) {
745 if self.is_paragraph_open() {
746 self.close_containers_to(self.containers.depth() - 1);
747 }
748 }
749
750 fn close_paragraph_as_plain_if_open(&mut self) {
761 if !self.is_paragraph_open() {
762 return;
763 }
764 let Some(Container::Paragraph {
765 buffer,
766 start_checkpoint,
767 ..
768 }) = self.containers.stack.last()
769 else {
770 return;
771 };
772 let buffer_clone = buffer.clone();
773 let checkpoint = *start_checkpoint;
774 let suppress_footnote_refs = self.in_footnote_definition();
775 self.containers.stack.pop();
776 self.builder
777 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
778 if !buffer_clone.is_empty() {
779 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
780 }
781 self.builder.finish_node();
782 }
783
784 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
793 if self.config.dialect != crate::options::Dialect::Pandoc {
794 return false;
795 }
796 if self.block_registry.parser_name(block_match) != "html_block" {
797 return false;
798 }
799 let html_block_type = block_match
800 .payload
801 .as_ref()
802 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
803 matches!(
804 html_block_type,
805 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
806 )
807 }
808
809 fn prepare_for_block_element(&mut self) {
812 self.emit_list_item_buffer_if_needed();
813 self.close_paragraph_if_open();
814 }
815
816 fn close_open_footnote_definition(&mut self) {
820 while matches!(
821 self.containers.last(),
822 Some(Container::FootnoteDefinition { .. })
823 ) {
824 self.close_containers_to(self.containers.depth() - 1);
825 }
826 }
827
828 fn handle_footnote_open_effect(
832 &mut self,
833 block_match: &super::block_dispatcher::PreparedBlockMatch,
834 content: &str,
835 ) -> usize {
836 let content_start = block_match
837 .payload
838 .as_ref()
839 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
840 .map(|p| p.content_start)
841 .unwrap_or(0);
842
843 let content_col = 4;
844 self.containers
845 .push(Container::FootnoteDefinition { content_col });
846
847 if content_start == 0 {
848 return 0;
849 }
850 let first_line_content = &content[content_start..];
851 if first_line_content.trim().is_empty() {
852 let (_, newline_str) = strip_newline(content);
853 if !newline_str.is_empty() {
854 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
855 }
856 return 0;
857 }
858
859 if self.config.extensions.definition_lists
860 && let Some(blank_count) = footnote_first_line_term_lookahead(
861 &self.lines,
862 self.pos,
863 content_col,
864 self.config.extensions.table_captions,
865 )
866 {
867 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
868 self.containers.push(Container::DefinitionList {});
869 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
870 self.containers.push(Container::DefinitionItem {});
871 emit_term(&mut self.builder, first_line_content, self.config);
872 for i in 0..blank_count {
873 let blank_pos = self.pos + 1 + i;
874 if blank_pos < self.lines.len() {
875 let blank_line = self.lines[blank_pos];
876 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
877 self.builder
878 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
879 self.builder.finish_node();
880 }
881 }
882 return blank_count;
883 }
884
885 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
886 paragraphs::append_paragraph_line(
887 &mut self.containers,
888 &mut self.builder,
889 first_line_content,
890 self.config,
891 );
892 0
893 }
894
895 fn try_lazy_list_continuation(
907 &mut self,
908 block_match: &super::block_dispatcher::PreparedBlockMatch,
909 content: &str,
910 ) -> bool {
911 use super::block_dispatcher::ListPrepared;
912
913 let Some(prepared) = block_match
914 .payload
915 .as_ref()
916 .and_then(|p| p.downcast_ref::<ListPrepared>())
917 else {
918 return false;
919 };
920
921 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
922 return false;
923 }
924
925 let current_content_col = paragraphs::current_content_col(&self.containers);
926 if prepared.indent_cols >= current_content_col {
927 return false;
928 }
929
930 if lists::find_matching_list_level(
931 &self.containers,
932 &prepared.marker,
933 prepared.indent_cols,
934 self.config.dialect,
935 )
936 .is_some()
937 {
938 return false;
939 }
940
941 match self.containers.last() {
942 Some(Container::Paragraph { .. }) => {
943 paragraphs::append_paragraph_line(
944 &mut self.containers,
945 &mut self.builder,
946 content,
947 self.config,
948 );
949 true
950 }
951 Some(Container::ListItem { .. }) => {
952 if let Some(Container::ListItem {
953 buffer,
954 marker_only,
955 ..
956 }) = self.containers.stack.last_mut()
957 {
958 buffer.push_text(content);
959 if !content.trim().is_empty() {
960 *marker_only = false;
961 }
962 }
963 true
964 }
965 _ => false,
966 }
967 }
968
969 fn handle_list_open_effect(
975 &mut self,
976 block_match: &super::block_dispatcher::PreparedBlockMatch,
977 content: &str,
978 indent_to_emit: Option<&str>,
979 ) -> usize {
980 use super::block_dispatcher::ListPrepared;
981
982 let prepared = block_match
983 .payload
984 .as_ref()
985 .and_then(|p| p.downcast_ref::<ListPrepared>());
986 let Some(prepared) = prepared else {
987 return 0;
988 };
989
990 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
991 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
992 paragraphs::append_paragraph_line(
993 &mut self.containers,
994 &mut self.builder,
995 content,
996 self.config,
997 );
998 return 0;
999 }
1000
1001 if self.is_paragraph_open() {
1002 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
1003 paragraphs::append_paragraph_line(
1004 &mut self.containers,
1005 &mut self.builder,
1006 content,
1007 self.config,
1008 );
1009 return 0;
1010 }
1011 self.close_containers_to(self.containers.depth() - 1);
1012 }
1013
1014 if matches!(
1015 self.containers.last(),
1016 Some(Container::Definition {
1017 plain_open: true,
1018 ..
1019 })
1020 ) {
1021 self.emit_buffered_plain_if_needed();
1022 }
1023
1024 let matched_level = lists::find_matching_list_level(
1025 &self.containers,
1026 &prepared.marker,
1027 prepared.indent_cols,
1028 self.config.dialect,
1029 );
1030 let list_item = ListItemEmissionInput {
1031 content,
1032 marker_len: prepared.marker_len,
1033 spaces_after_cols: prepared.spaces_after_cols,
1034 spaces_after_bytes: prepared.spaces_after,
1035 indent_cols: prepared.indent_cols,
1036 indent_bytes: prepared.indent_bytes,
1037 virtual_marker_space: prepared.virtual_marker_space,
1038 };
1039 let current_content_col = paragraphs::current_content_col(&self.containers);
1040 let deep_ordered_matched_level = matched_level
1041 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1042 .and_then(|(level, container)| match container {
1043 Container::List {
1044 marker: list_marker,
1045 base_indent_cols,
1046 ..
1047 } if matches!(
1048 (&prepared.marker, list_marker),
1049 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1050 ) && prepared.indent_cols >= 4
1051 && *base_indent_cols >= 4
1052 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1053 {
1054 Some(level)
1055 }
1056 _ => None,
1057 });
1058
1059 if deep_ordered_matched_level.is_none()
1060 && current_content_col > 0
1061 && prepared.indent_cols >= current_content_col
1062 {
1063 if let Some(level) = matched_level
1064 && let Some(Container::List {
1065 base_indent_cols, ..
1066 }) = self.containers.stack.get(level)
1067 && prepared.indent_cols == *base_indent_cols
1068 {
1069 let num_parent_lists = self.containers.stack[..level]
1070 .iter()
1071 .filter(|c| matches!(c, Container::List { .. }))
1072 .count();
1073
1074 if num_parent_lists > 0 {
1075 self.close_containers_to(level + 1);
1076
1077 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1078 self.close_containers_to(self.containers.depth() - 1);
1079 }
1080 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1081 self.close_containers_to(self.containers.depth() - 1);
1082 }
1083
1084 if let Some(indent_str) = indent_to_emit {
1085 self.builder
1086 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1087 }
1088
1089 let finish = if let Some(nested_marker) = prepared.nested_marker {
1090 lists::add_list_item_with_nested_empty_list(
1091 &mut self.containers,
1092 &mut self.builder,
1093 &list_item,
1094 nested_marker,
1095 self.config,
1096 );
1097 lists::ListItemFinish::Done
1098 } else {
1099 lists::add_list_item(
1100 &mut self.containers,
1101 &mut self.builder,
1102 &list_item,
1103 self.config,
1104 )
1105 };
1106 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1107 return extras;
1108 }
1109 self.maybe_open_indented_code_in_new_list_item();
1110 return self.dispatch_bq_after_list_item(finish);
1111 }
1112 }
1113
1114 self.emit_list_item_buffer_if_needed();
1115
1116 let finish = start_nested_list(
1117 &mut self.containers,
1118 &mut self.builder,
1119 &prepared.marker,
1120 &list_item,
1121 indent_to_emit,
1122 self.config,
1123 );
1124 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1125 return extras;
1126 }
1127 self.maybe_open_indented_code_in_new_list_item();
1128 return self.dispatch_bq_after_list_item(finish);
1129 }
1130
1131 if let Some(level) = matched_level {
1132 self.close_containers_to(level + 1);
1133
1134 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1135 self.close_containers_to(self.containers.depth() - 1);
1136 }
1137 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1138 self.close_containers_to(self.containers.depth() - 1);
1139 }
1140
1141 if let Some(indent_str) = indent_to_emit {
1142 self.builder
1143 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1144 }
1145
1146 let finish = if let Some(nested_marker) = prepared.nested_marker {
1147 lists::add_list_item_with_nested_empty_list(
1148 &mut self.containers,
1149 &mut self.builder,
1150 &list_item,
1151 nested_marker,
1152 self.config,
1153 );
1154 lists::ListItemFinish::Done
1155 } else {
1156 lists::add_list_item(
1157 &mut self.containers,
1158 &mut self.builder,
1159 &list_item,
1160 self.config,
1161 )
1162 };
1163 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1164 return extras;
1165 }
1166 self.maybe_open_indented_code_in_new_list_item();
1167 return self.dispatch_bq_after_list_item(finish);
1168 }
1169
1170 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1171 self.close_containers_to(self.containers.depth() - 1);
1172 }
1173 while matches!(
1174 self.containers.last(),
1175 Some(Container::ListItem { .. } | Container::List { .. })
1176 ) {
1177 self.close_containers_to(self.containers.depth() - 1);
1178 }
1179
1180 self.builder.start_node(SyntaxKind::LIST.into());
1181 if let Some(indent_str) = indent_to_emit {
1182 self.builder
1183 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1184 }
1185 self.containers.push(Container::List {
1186 marker: prepared.marker.clone(),
1187 base_indent_cols: prepared.indent_cols,
1188 has_blank_between_items: false,
1189 });
1190
1191 let finish = if let Some(nested_marker) = prepared.nested_marker {
1192 lists::add_list_item_with_nested_empty_list(
1193 &mut self.containers,
1194 &mut self.builder,
1195 &list_item,
1196 nested_marker,
1197 self.config,
1198 );
1199 lists::ListItemFinish::Done
1200 } else {
1201 lists::add_list_item(
1202 &mut self.containers,
1203 &mut self.builder,
1204 &list_item,
1205 self.config,
1206 )
1207 };
1208 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1209 return extras;
1210 }
1211 self.maybe_open_indented_code_in_new_list_item();
1212 self.dispatch_bq_after_list_item(finish)
1213 }
1214
1215 fn handle_definition_list_effect(
1222 &mut self,
1223 block_match: &super::block_dispatcher::PreparedBlockMatch,
1224 content: &str,
1225 indent_to_emit: Option<&str>,
1226 ) -> usize {
1227 use super::block_dispatcher::DefinitionPrepared;
1228
1229 let prepared = block_match
1230 .payload
1231 .as_ref()
1232 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1233 let Some(prepared) = prepared else {
1234 return 0;
1235 };
1236
1237 let mut extras: usize = 0;
1238 match prepared {
1239 DefinitionPrepared::Definition {
1240 marker_char,
1241 indent,
1242 spaces_after,
1243 spaces_after_cols,
1244 has_content,
1245 } => {
1246 self.emit_buffered_plain_if_needed();
1247
1248 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1249 self.close_containers_to(self.containers.depth() - 1);
1250 }
1251 while matches!(self.containers.last(), Some(Container::List { .. })) {
1252 self.close_containers_to(self.containers.depth() - 1);
1253 }
1254
1255 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1256 self.close_containers_to(self.containers.depth() - 1);
1257 }
1258
1259 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1260 self.close_containers_to(self.containers.depth() - 1);
1261 }
1262
1263 if definition_lists::in_definition_list(&self.containers)
1267 && !matches!(
1268 self.containers.last(),
1269 Some(Container::DefinitionItem { .. })
1270 )
1271 {
1272 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1273 self.containers.push(Container::DefinitionItem {});
1274 }
1275
1276 if !definition_lists::in_definition_list(&self.containers) {
1277 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1278 self.containers.push(Container::DefinitionList {});
1279 }
1280
1281 if !matches!(
1282 self.containers.last(),
1283 Some(Container::DefinitionItem { .. })
1284 ) {
1285 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1286 self.containers.push(Container::DefinitionItem {});
1287 }
1288
1289 self.builder.start_node(SyntaxKind::DEFINITION.into());
1290
1291 if let Some(indent_str) = indent_to_emit {
1292 self.builder
1293 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1294 }
1295
1296 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1297 let indent_bytes = byte_index_at_column(content, *indent);
1298 if *spaces_after > 0 {
1299 let space_start = indent_bytes + 1;
1300 let space_end = space_start + *spaces_after;
1301 if space_end <= content.len() {
1302 self.builder.token(
1303 SyntaxKind::WHITESPACE.into(),
1304 &content[space_start..space_end],
1305 );
1306 }
1307 }
1308
1309 if !*has_content {
1310 let current_line = self.lines[self.pos];
1311 let (_, newline_str) = strip_newline(current_line);
1312 if !newline_str.is_empty() {
1313 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1314 }
1315 }
1316
1317 let content_col = *indent + 1 + *spaces_after_cols;
1318 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1319 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1320 let mut plain_buffer = TextBuffer::new();
1321 let mut definition_pushed = false;
1322
1323 if *has_content {
1324 let current_line = self.lines[self.pos];
1325 let (trimmed_content, _) = strip_newline(content);
1326
1327 let content_start = content_start_bytes.min(trimmed_content.len());
1334 let content_slice = &trimmed_content[content_start..];
1335 let content_line = &content[content_start_bytes.min(content.len())..];
1336
1337 let (blockquote_depth, inner_blockquote_content) =
1338 count_blockquote_markers(content_line);
1339
1340 let should_start_list_from_first_line = self
1341 .lines
1342 .get(self.pos + 1)
1343 .map(|next_line| {
1344 let (next_without_newline, _) = strip_newline(next_line);
1345 if next_without_newline.trim().is_empty() {
1346 return true;
1347 }
1348
1349 let (next_indent_cols, _) = leading_indent(next_without_newline);
1350 next_indent_cols >= content_col
1351 })
1352 .unwrap_or(true);
1353
1354 if blockquote_depth > 0 {
1355 self.containers.push(Container::Definition {
1356 content_col,
1357 plain_open: false,
1358 plain_buffer: TextBuffer::new(),
1359 });
1360 definition_pushed = true;
1361
1362 let marker_info = parse_blockquote_marker_info(content_line);
1363 for level in 0..blockquote_depth {
1364 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1365 if let Some(info) = marker_info.get(level) {
1366 blockquotes::emit_one_blockquote_marker(
1367 &mut self.builder,
1368 info.leading_spaces,
1369 info.has_trailing_space,
1370 );
1371 }
1372 self.containers.push(Container::BlockQuote {});
1373 }
1374
1375 if !inner_blockquote_content.trim().is_empty() {
1376 paragraphs::start_paragraph_if_needed(
1377 &mut self.containers,
1378 &mut self.builder,
1379 );
1380 paragraphs::append_paragraph_line(
1381 &mut self.containers,
1382 &mut self.builder,
1383 inner_blockquote_content,
1384 self.config,
1385 );
1386 }
1387 } else if let Some(marker_match) = try_parse_list_marker(
1388 content_slice,
1389 self.config,
1390 lists::open_list_hint_at_indent(
1391 &self.containers,
1392 leading_indent(content_slice).0,
1393 ),
1394 ) && should_start_list_from_first_line
1395 {
1396 self.containers.push(Container::Definition {
1397 content_col,
1398 plain_open: false,
1399 plain_buffer: TextBuffer::new(),
1400 });
1401 definition_pushed = true;
1402
1403 let (indent_cols, indent_bytes) = leading_indent(content_line);
1404 self.builder.start_node(SyntaxKind::LIST.into());
1405 self.containers.push(Container::List {
1406 marker: marker_match.marker.clone(),
1407 base_indent_cols: indent_cols,
1408 has_blank_between_items: false,
1409 });
1410
1411 let list_item = ListItemEmissionInput {
1412 content: content_line,
1413 marker_len: marker_match.marker_len,
1414 spaces_after_cols: marker_match.spaces_after_cols,
1415 spaces_after_bytes: marker_match.spaces_after_bytes,
1416 indent_cols,
1417 indent_bytes,
1418 virtual_marker_space: marker_match.virtual_marker_space,
1419 };
1420
1421 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1422 content_line,
1423 marker_match.marker_len,
1424 marker_match.spaces_after_bytes,
1425 ) {
1426 lists::add_list_item_with_nested_empty_list(
1427 &mut self.containers,
1428 &mut self.builder,
1429 &list_item,
1430 nested_marker,
1431 self.config,
1432 );
1433 lists::ListItemFinish::Done
1434 } else {
1435 lists::add_list_item(
1436 &mut self.containers,
1437 &mut self.builder,
1438 &list_item,
1439 self.config,
1440 )
1441 };
1442 extras = self.dispatch_bq_after_list_item(finish);
1443 } else if let Some(fence) =
1444 code_blocks::try_parse_fence_open(content_slice, self.config.dialect)
1445 {
1446 self.containers.push(Container::Definition {
1447 content_col,
1448 plain_open: false,
1449 plain_buffer: TextBuffer::new(),
1450 });
1451 definition_pushed = true;
1452
1453 let bq_depth = self.current_blockquote_depth();
1454 if let Some(indent_str) = indent_to_emit {
1455 self.builder
1456 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1457 }
1458 let fence_line = content[content_start..].to_string();
1459 let prefix = ContainerPrefix::from_scalars(
1463 bq_depth,
1464 0,
1465 bq_depth > 0,
1466 content_col,
1467 false,
1468 );
1469 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
1470 let new_pos = if self.config.extensions.tex_math_gfm
1471 && code_blocks::is_gfm_math_fence(&fence)
1472 {
1473 code_blocks::parse_fenced_math_block(
1474 &mut self.builder,
1475 &window,
1476 fence,
1477 Some(&fence_line),
1478 )
1479 } else {
1480 code_blocks::parse_fenced_code_block(
1481 &mut self.builder,
1482 &window,
1483 fence,
1484 Some(&fence_line),
1485 &self.diagnostics,
1486 )
1487 };
1488 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1489 } else {
1490 let (_, newline_str) = strip_newline(current_line);
1491 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1492 if content_without_newline.is_empty() {
1493 plain_buffer.push_line(newline_str);
1494 } else {
1495 let line_with_newline = if !newline_str.is_empty() {
1496 format!("{}{}", content_without_newline, newline_str)
1497 } else {
1498 content_without_newline.to_string()
1499 };
1500 plain_buffer.push_line(line_with_newline);
1501 }
1502 }
1503 }
1504
1505 if !definition_pushed {
1506 self.containers.push(Container::Definition {
1507 content_col,
1508 plain_open: *has_content,
1509 plain_buffer,
1510 });
1511 }
1512 }
1513 DefinitionPrepared::Term { blank_count } => {
1514 self.emit_buffered_plain_if_needed();
1515
1516 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1517 self.close_containers_to(self.containers.depth() - 1);
1518 }
1519
1520 if !definition_lists::in_definition_list(&self.containers) {
1521 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1522 self.containers.push(Container::DefinitionList {});
1523 }
1524
1525 while matches!(
1526 self.containers.last(),
1527 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1528 ) {
1529 self.close_containers_to(self.containers.depth() - 1);
1530 }
1531
1532 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1533 self.containers.push(Container::DefinitionItem {});
1534
1535 emit_term(&mut self.builder, content, self.config);
1536
1537 for i in 0..*blank_count {
1538 let blank_pos = self.pos + 1 + i;
1539 if blank_pos < self.lines.len() {
1540 let blank_line = self.lines[blank_pos];
1541 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1542 self.builder
1543 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1544 self.builder.finish_node();
1545 }
1546 }
1547 extras = *blank_count;
1548 }
1549 };
1550 extras
1551 }
1552
1553 fn blockquote_marker_info(
1555 &self,
1556 payload: Option<&BlockQuotePrepared>,
1557 line: &str,
1558 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1559 payload
1560 .map(|payload| payload.marker_info.clone())
1561 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1562 }
1563
1564 fn marker_info_for_line(
1570 &self,
1571 payload: Option<&BlockQuotePrepared>,
1572 raw_line: &str,
1573 marker_line: &str,
1574 shifted_prefix: &str,
1575 used_shifted: bool,
1576 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1577 let mut marker_info = if used_shifted {
1578 parse_blockquote_marker_info(marker_line)
1579 } else {
1580 self.blockquote_marker_info(payload, raw_line)
1581 };
1582 if used_shifted && !shifted_prefix.is_empty() {
1583 let (prefix_cols, _) = leading_indent(shifted_prefix);
1584 if let Some(first) = marker_info.first_mut() {
1585 first.leading_spaces += prefix_cols;
1586 }
1587 }
1588 marker_info
1589 }
1590
1591 fn shifted_blockquote_from_list<'b>(
1594 &self,
1595 line: &'b str,
1596 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1597 let list_content_col = self
1606 .containers
1607 .stack
1608 .iter()
1609 .rev()
1610 .find_map(|c| match c {
1611 Container::ListItem { content_col, .. } => Some(*content_col),
1612 _ => None,
1613 })
1614 .unwrap_or(0);
1615 let content_container_indent = self.content_container_indent_to_strip();
1616 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1624 return None;
1625 }
1626 let marker_col = list_content_col.saturating_add(content_container_indent);
1627 if marker_col == 0 {
1628 return None;
1629 }
1630
1631 let (indent_cols, _) = leading_indent(line);
1632 if indent_cols < marker_col {
1633 return None;
1634 }
1635
1636 let idx = byte_index_at_column(line, marker_col);
1637 if idx > line.len() {
1638 return None;
1639 }
1640
1641 let candidate = &line[idx..];
1642 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1643 if candidate_depth == 0 {
1644 return None;
1645 }
1646
1647 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1648 }
1649
1650 fn emit_blockquote_markers(
1651 &mut self,
1652 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1653 depth: usize,
1654 ) {
1655 for i in 0..depth {
1656 if let Some(info) = marker_info.get(i) {
1657 blockquotes::emit_one_blockquote_marker(
1658 &mut self.builder,
1659 info.leading_spaces,
1660 info.has_trailing_space,
1661 );
1662 }
1663 }
1664 }
1665
1666 fn current_blockquote_depth(&self) -> usize {
1667 blockquotes::current_blockquote_depth(&self.containers)
1668 }
1669
1670 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1678 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1679 return None;
1680 };
1681 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1682 }
1683
1684 fn emit_or_buffer_blockquote_marker(
1689 &mut self,
1690 leading_spaces: usize,
1691 has_trailing_space: bool,
1692 ) {
1693 if let Some(Container::ListItem {
1694 buffer,
1695 marker_only,
1696 ..
1697 }) = self.containers.stack.last_mut()
1698 {
1699 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1700 *marker_only = false;
1701 return;
1702 }
1703
1704 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1706 paragraphs::append_paragraph_marker(
1708 &mut self.containers,
1709 leading_spaces,
1710 has_trailing_space,
1711 );
1712 } else {
1713 blockquotes::emit_one_blockquote_marker(
1715 &mut self.builder,
1716 leading_spaces,
1717 has_trailing_space,
1718 );
1719 }
1720 }
1721
1722 fn parse_document_stack(&mut self) {
1723 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1724
1725 log::trace!("Starting document parse");
1726
1727 while self.pos < self.lines.len() {
1730 let line = self.lines[self.pos];
1731
1732 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1733
1734 match self.parse_line(line) {
1735 LineDispatch::Consumed(n) => self.pos += n,
1736 LineDispatch::Rejected => self.pos += 1,
1737 }
1738 }
1739
1740 self.close_containers_to(0);
1741 self.builder.finish_node(); }
1743
1744 fn parse_line(&mut self, line: &str) -> LineDispatch {
1748 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1751 let mut bq_marker_line = line;
1752 let mut shifted_bq_prefix = "";
1753 let mut used_shifted_bq = false;
1754 if bq_depth == 0
1755 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1756 self.shifted_blockquote_from_list(line)
1757 {
1758 bq_depth = candidate_depth;
1759 inner_content = candidate_inner;
1760 bq_marker_line = candidate_line;
1761 shifted_bq_prefix = candidate_prefix;
1762 used_shifted_bq = true;
1763 }
1764 let current_bq_depth = self.current_blockquote_depth();
1765
1766 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1767 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1768 let dispatcher_ctx = if current_bq_depth == 0 {
1769 Some(BlockContext {
1770 has_blank_before,
1771 has_blank_before_strict: has_blank_before,
1772 at_document_start: self.pos == 0,
1773 in_fenced_div: self.in_fenced_div(),
1774 blockquote_depth: current_bq_depth,
1775 config: self.config,
1776 diags: self.diagnostics.clone(),
1777 content_indent: 0,
1778 indent_to_emit: None,
1779 list_indent_info: None,
1780 in_list: lists::in_list(&self.containers),
1781 in_marker_only_list_item: matches!(
1782 self.containers.last(),
1783 Some(Container::ListItem {
1784 marker_only: true,
1785 ..
1786 })
1787 ),
1788 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1789 paragraph_open: self.is_paragraph_open(),
1790 next_line: if self.pos + 1 < self.lines.len() {
1791 Some(self.lines[self.pos + 1])
1792 } else {
1793 None
1794 },
1795 open_alpha_hint: lists::open_list_hint_at_indent(
1796 &self.containers,
1797 leading_indent(line).0,
1798 ),
1799 })
1800 } else {
1801 None
1802 };
1803
1804 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1805 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
1806 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
1807 self.block_registry
1808 .detect_prepared(dispatcher_ctx, &stripped)
1809 .and_then(|prepared| {
1810 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1811 blockquote_match = Some(prepared);
1812 blockquote_match.as_ref().and_then(|prepared| {
1813 prepared
1814 .payload
1815 .as_ref()
1816 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1817 .cloned()
1818 })
1819 } else {
1820 None
1821 }
1822 })
1823 } else {
1824 None
1825 };
1826
1827 log::trace!(
1828 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1829 self.pos,
1830 bq_depth,
1831 current_bq_depth,
1832 self.containers.depth(),
1833 line.trim_end()
1834 );
1835
1836 let inner_blank_in_blockquote = bq_depth > 0
1843 && is_blank_line(inner_content)
1844 && (current_bq_depth > 0
1845 || !self.config.extensions.blank_before_blockquote
1846 || blockquotes::can_start_blockquote(
1847 self.pos,
1848 &self.lines,
1849 self.config.extensions.fenced_divs,
1850 ));
1851 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1852
1853 if is_blank {
1854 if self.is_paragraph_open()
1855 && paragraphs::has_open_inline_math_environment(&self.containers)
1856 {
1857 paragraphs::append_paragraph_line(
1858 &mut self.containers,
1859 &mut self.builder,
1860 line,
1861 self.config,
1862 );
1863 return LineDispatch::consumed(1);
1864 }
1865
1866 self.close_paragraph_if_open();
1868
1869 self.emit_buffered_plain_if_needed();
1873
1874 if bq_depth > current_bq_depth {
1882 for _ in current_bq_depth..bq_depth {
1884 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1885 self.containers.push(Container::BlockQuote {});
1886 }
1887 } else if bq_depth < current_bq_depth {
1888 self.close_blockquotes_to_depth(bq_depth);
1890 }
1891
1892 let mut peek = self.pos + 1;
1899 while peek < self.lines.len() {
1900 let peek_line = self.lines[peek];
1901 if is_blank_line(peek_line) {
1902 peek += 1;
1903 continue;
1904 }
1905 if bq_depth > 0 {
1906 let (peek_bq, _) = count_blockquote_markers(peek_line);
1907 if peek_bq >= bq_depth {
1908 let peek_inner =
1909 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1910 if is_blank_line(peek_inner) {
1911 peek += 1;
1912 continue;
1913 }
1914 }
1915 }
1916 break;
1917 }
1918
1919 let levels_to_keep = if peek < self.lines.len() {
1921 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1922 self.current_blockquote_depth(),
1923 &self.containers,
1924 &self.lines,
1925 peek,
1926 self.lines[peek],
1927 )
1928 } else {
1929 0
1930 };
1931 log::trace!(
1932 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1933 self.containers.depth(),
1934 levels_to_keep,
1935 if peek < self.lines.len() {
1936 self.lines[peek]
1937 } else {
1938 "<EOF>"
1939 }
1940 );
1941
1942 while self.containers.depth() > levels_to_keep {
1946 match self.containers.last() {
1947 Some(Container::ListItem { .. }) => {
1948 log::trace!(
1950 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1951 levels_to_keep,
1952 self.containers.depth()
1953 );
1954 self.close_containers_to(self.containers.depth() - 1);
1955 }
1956 Some(Container::List { .. })
1957 | Some(Container::FootnoteDefinition { .. })
1958 | Some(Container::Alert { .. })
1959 | Some(Container::Paragraph { .. })
1960 | Some(Container::Definition { .. })
1961 | Some(Container::DefinitionItem { .. })
1962 | Some(Container::DefinitionList { .. }) => {
1963 log::trace!(
1964 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1965 self.containers.last(),
1966 self.containers.depth(),
1967 levels_to_keep
1968 );
1969
1970 self.close_containers_to(self.containers.depth() - 1);
1971 }
1972 _ => break,
1973 }
1974 }
1975
1976 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1980 self.emit_list_item_buffer_if_needed();
1981 }
1982
1983 if bq_depth > 0 {
1985 let marker_info = self.marker_info_for_line(
1986 blockquote_payload.as_ref(),
1987 line,
1988 bq_marker_line,
1989 shifted_bq_prefix,
1990 used_shifted_bq,
1991 );
1992 self.emit_blockquote_markers(&marker_info, bq_depth);
1993 }
1994
1995 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1996 self.builder
1997 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1998 self.builder.finish_node();
1999
2000 return LineDispatch::consumed(1);
2001 }
2002
2003 if bq_depth > current_bq_depth {
2005 if self.config.extensions.blank_before_blockquote
2008 && current_bq_depth == 0
2009 && !used_shifted_bq
2010 && !blockquote_payload
2011 .as_ref()
2012 .map(|payload| payload.can_start)
2013 .unwrap_or_else(|| {
2014 blockquotes::can_start_blockquote(
2015 self.pos,
2016 &self.lines,
2017 self.config.extensions.fenced_divs,
2018 )
2019 })
2020 {
2021 self.emit_list_item_buffer_if_needed();
2025 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2026 paragraphs::append_paragraph_line(
2027 &mut self.containers,
2028 &mut self.builder,
2029 line,
2030 self.config,
2031 );
2032 return LineDispatch::consumed(1);
2033 }
2034
2035 let can_nest = if current_bq_depth > 0 {
2038 if self.config.extensions.blank_before_blockquote {
2039 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2041 || (self.pos > 0 && {
2042 let prev_line = self.lines[self.pos - 1];
2043 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2044 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2045 })
2046 } else {
2047 true
2048 }
2049 } else {
2050 blockquote_payload
2051 .as_ref()
2052 .map(|payload| payload.can_nest)
2053 .unwrap_or(true)
2054 };
2055
2056 if !can_nest {
2057 let content_at_current_depth =
2060 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2061
2062 let marker_info = self.marker_info_for_line(
2064 blockquote_payload.as_ref(),
2065 line,
2066 bq_marker_line,
2067 shifted_bq_prefix,
2068 used_shifted_bq,
2069 );
2070 for i in 0..current_bq_depth {
2071 if let Some(info) = marker_info.get(i) {
2072 self.emit_or_buffer_blockquote_marker(
2073 info.leading_spaces,
2074 info.has_trailing_space,
2075 );
2076 }
2077 }
2078
2079 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2080 paragraphs::append_paragraph_line(
2082 &mut self.containers,
2083 &mut self.builder,
2084 content_at_current_depth,
2085 self.config,
2086 );
2087 return LineDispatch::consumed(1);
2088 } else {
2089 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2091 paragraphs::append_paragraph_line(
2092 &mut self.containers,
2093 &mut self.builder,
2094 content_at_current_depth,
2095 self.config,
2096 );
2097 return LineDispatch::consumed(1);
2098 }
2099 }
2100
2101 self.emit_list_item_buffer_if_needed();
2104
2105 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2107 self.close_containers_to(self.containers.depth() - 1);
2108 }
2109
2110 let marker_info = self.marker_info_for_line(
2112 blockquote_payload.as_ref(),
2113 line,
2114 bq_marker_line,
2115 shifted_bq_prefix,
2116 used_shifted_bq,
2117 );
2118
2119 if let (Some(dispatcher_ctx), Some(prepared)) =
2120 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2121 {
2122 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2123 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2124 let _ = self.block_registry.parse_prepared(
2125 prepared,
2126 dispatcher_ctx,
2127 &mut self.builder,
2128 &stripped,
2129 );
2130 for _ in 0..bq_depth {
2131 self.containers.push(Container::BlockQuote {});
2132 }
2133 } else {
2134 for level in 0..current_bq_depth {
2136 if let Some(info) = marker_info.get(level) {
2137 self.emit_or_buffer_blockquote_marker(
2138 info.leading_spaces,
2139 info.has_trailing_space,
2140 );
2141 }
2142 }
2143
2144 for level in current_bq_depth..bq_depth {
2146 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2147
2148 if let Some(info) = marker_info.get(level) {
2150 blockquotes::emit_one_blockquote_marker(
2151 &mut self.builder,
2152 info.leading_spaces,
2153 info.has_trailing_space,
2154 );
2155 }
2156
2157 self.containers.push(Container::BlockQuote {});
2158 }
2159 }
2160
2161 let prev_flag = self.dispatch_list_marker_consumed;
2174 if used_shifted_bq && !self.innermost_li_above_bq() {
2175 self.dispatch_list_marker_consumed = true;
2176 }
2177 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2178 self.dispatch_list_marker_consumed = prev_flag;
2179 return dispatch;
2180 } else if bq_depth < current_bq_depth {
2181 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2187 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2194 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2195 let interrupts_via_fence = is_commonmark
2196 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2197 let interrupts_via_div_close = self.config.extensions.fenced_divs
2202 && self.in_fenced_div()
2203 && fenced_divs::is_div_closing_fence(line);
2204 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2205 if bq_depth > 0 {
2206 let marker_info = self.marker_info_for_line(
2212 blockquote_payload.as_ref(),
2213 line,
2214 bq_marker_line,
2215 shifted_bq_prefix,
2216 used_shifted_bq,
2217 );
2218 for i in 0..bq_depth {
2219 if let Some(info) = marker_info.get(i) {
2220 paragraphs::append_paragraph_marker(
2221 &mut self.containers,
2222 info.leading_spaces,
2223 info.has_trailing_space,
2224 );
2225 }
2226 }
2227 paragraphs::append_paragraph_line(
2228 &mut self.containers,
2229 &mut self.builder,
2230 inner_content,
2231 self.config,
2232 );
2233 } else {
2234 paragraphs::append_paragraph_line(
2235 &mut self.containers,
2236 &mut self.builder,
2237 line,
2238 self.config,
2239 );
2240 }
2241 return LineDispatch::consumed(1);
2242 }
2243 }
2244 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2252 && lists::in_blockquote_list(&self.containers)
2253 && try_parse_list_marker(
2254 line,
2255 self.config,
2256 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2257 )
2258 .is_none()
2259 {
2260 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2261 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2262 let interrupts_via_fence = is_commonmark
2263 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2264 if !interrupts_via_hr && !interrupts_via_fence {
2265 if bq_depth > 0 {
2266 let marker_info = self.marker_info_for_line(
2267 blockquote_payload.as_ref(),
2268 line,
2269 bq_marker_line,
2270 shifted_bq_prefix,
2271 used_shifted_bq,
2272 );
2273 if let Some(Container::ListItem {
2274 buffer,
2275 marker_only,
2276 ..
2277 }) = self.containers.stack.last_mut()
2278 {
2279 for i in 0..bq_depth {
2280 if let Some(info) = marker_info.get(i) {
2281 buffer.push_blockquote_marker(
2282 info.leading_spaces,
2283 info.has_trailing_space,
2284 );
2285 }
2286 }
2287 buffer.push_text(inner_content);
2288 if !inner_content.trim().is_empty() {
2289 *marker_only = false;
2290 }
2291 }
2292 } else if let Some(Container::ListItem {
2293 buffer,
2294 marker_only,
2295 ..
2296 }) = self.containers.stack.last_mut()
2297 {
2298 buffer.push_text(line);
2299 if !line.trim().is_empty() {
2300 *marker_only = false;
2301 }
2302 }
2303 return LineDispatch::consumed(1);
2304 }
2305 }
2306 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2312 if lists::in_blockquote_list(&self.containers)
2315 && let Some(marker_match) = try_parse_list_marker(
2316 line,
2317 self.config,
2318 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2319 )
2320 {
2321 let (indent_cols, indent_bytes) = leading_indent(line);
2322 if let Some(level) = lists::find_matching_list_level(
2323 &self.containers,
2324 &marker_match.marker,
2325 indent_cols,
2326 self.config.dialect,
2327 ) {
2328 self.close_containers_to(level + 1);
2331
2332 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2334 self.close_containers_to(self.containers.depth() - 1);
2335 }
2336 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2337 self.close_containers_to(self.containers.depth() - 1);
2338 }
2339
2340 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2342 line,
2343 marker_match.marker_len,
2344 marker_match.spaces_after_bytes,
2345 ) {
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 lists::add_list_item_with_nested_empty_list(
2356 &mut self.containers,
2357 &mut self.builder,
2358 &list_item,
2359 nested_marker,
2360 self.config,
2361 );
2362 0
2363 } else {
2364 let list_item = ListItemEmissionInput {
2365 content: line,
2366 marker_len: marker_match.marker_len,
2367 spaces_after_cols: marker_match.spaces_after_cols,
2368 spaces_after_bytes: marker_match.spaces_after_bytes,
2369 indent_cols,
2370 indent_bytes,
2371 virtual_marker_space: marker_match.virtual_marker_space,
2372 };
2373 let finish = lists::add_list_item(
2374 &mut self.containers,
2375 &mut self.builder,
2376 &list_item,
2377 self.config,
2378 );
2379 self.dispatch_bq_after_list_item(finish)
2380 };
2381 return LineDispatch::consumed(1 + extras);
2382 }
2383 }
2384 }
2385
2386 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2388 self.close_containers_to(self.containers.depth() - 1);
2389 }
2390
2391 self.close_blockquotes_to_depth(bq_depth);
2393
2394 if bq_depth > 0 {
2396 let marker_info = self.marker_info_for_line(
2398 blockquote_payload.as_ref(),
2399 line,
2400 bq_marker_line,
2401 shifted_bq_prefix,
2402 used_shifted_bq,
2403 );
2404 for i in 0..bq_depth {
2405 if let Some(info) = marker_info.get(i) {
2406 self.emit_or_buffer_blockquote_marker(
2407 info.leading_spaces,
2408 info.has_trailing_space,
2409 );
2410 }
2411 }
2412 return self.parse_inner_content(inner_content, Some(inner_content));
2414 } else {
2415 return self.parse_inner_content(line, None);
2417 }
2418 } else if bq_depth > 0 {
2419 let mut list_item_continuation = false;
2421 let same_depth_marker_info = self.marker_info_for_line(
2422 blockquote_payload.as_ref(),
2423 line,
2424 bq_marker_line,
2425 shifted_bq_prefix,
2426 used_shifted_bq,
2427 );
2428 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2429
2430 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2442 if let Some(marker_match) = try_parse_list_marker(
2443 inner_content,
2444 self.config,
2445 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2446 ) {
2447 let inner_content_threshold =
2451 marker_match.marker_len + marker_match.spaces_after_cols;
2452 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2453 let sibling_list_level = if is_sibling_candidate {
2454 self.containers
2455 .stack
2456 .iter()
2457 .enumerate()
2458 .rev()
2459 .find_map(|(i, c)| match c {
2460 Container::List { marker, .. }
2461 if lists::markers_match(
2462 &marker_match.marker,
2463 marker,
2464 self.config.dialect,
2465 ) && self.containers.stack[..i]
2466 .iter()
2467 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2468 .count()
2469 == bq_depth =>
2470 {
2471 Some(i)
2472 }
2473 _ => None,
2474 })
2475 } else {
2476 None
2477 };
2478 if let Some(list_level) = sibling_list_level {
2479 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2485 Some(Container::List {
2486 base_indent_cols, ..
2487 }) => *base_indent_cols,
2488 _ => 0,
2489 };
2490
2491 self.emit_list_item_buffer_if_needed();
2493 self.close_containers_to(list_level + 1);
2496
2497 for i in 0..bq_depth {
2501 if let Some(info) = same_depth_marker_info.get(i) {
2502 self.emit_or_buffer_blockquote_marker(
2503 info.leading_spaces,
2504 info.has_trailing_space,
2505 );
2506 }
2507 }
2508
2509 let list_item = ListItemEmissionInput {
2511 content: inner_content,
2512 marker_len: marker_match.marker_len,
2513 spaces_after_cols: marker_match.spaces_after_cols,
2514 spaces_after_bytes: marker_match.spaces_after_bytes,
2515 indent_cols: sibling_base_indent_cols,
2516 indent_bytes: inner_indent_bytes,
2517 virtual_marker_space: marker_match.virtual_marker_space,
2518 };
2519 let finish = lists::add_list_item(
2520 &mut self.containers,
2521 &mut self.builder,
2522 &list_item,
2523 self.config,
2524 );
2525 let extras =
2526 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
2527 extras
2528 } else {
2529 self.maybe_open_indented_code_in_new_list_item();
2530 self.dispatch_bq_after_list_item(finish)
2531 };
2532 return LineDispatch::consumed(1 + extras);
2533 }
2534 }
2535
2536 if matches!(
2539 self.containers.last(),
2540 Some(Container::ListItem { content_col: _, .. })
2541 ) {
2542 let (indent_cols, _) = leading_indent(inner_content);
2543 let content_indent = self.content_container_indent_to_strip();
2544 let effective_indent = indent_cols.saturating_sub(content_indent);
2545 let content_col = match self.containers.last() {
2546 Some(Container::ListItem { content_col, .. }) => *content_col,
2547 _ => 0,
2548 };
2549
2550 let is_new_item_at_outer_level = if try_parse_list_marker(
2552 inner_content,
2553 self.config,
2554 lists::open_list_hint_at_indent(
2555 &self.containers,
2556 leading_indent(inner_content).0,
2557 ),
2558 )
2559 .is_some()
2560 {
2561 effective_indent < content_col
2562 } else {
2563 false
2564 };
2565
2566 if is_new_item_at_outer_level
2570 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2571 {
2572 log::trace!(
2573 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2574 is_new_item_at_outer_level,
2575 effective_indent,
2576 content_col
2577 );
2578 self.close_containers_to(self.containers.depth() - 1);
2579 } else {
2580 log::trace!(
2581 "Keeping ListItem: effective_indent={} >= content_col={}",
2582 effective_indent,
2583 content_col
2584 );
2585 list_item_continuation = true;
2586 }
2587 }
2588
2589 if list_item_continuation
2593 && code_blocks::try_parse_fence_open(inner_content, self.config.dialect).is_some()
2594 {
2595 list_item_continuation = false;
2596 }
2597
2598 let continuation_has_explicit_marker = list_item_continuation && {
2599 if has_explicit_same_depth_marker {
2600 for i in 0..bq_depth {
2601 if let Some(info) = same_depth_marker_info.get(i) {
2602 self.emit_or_buffer_blockquote_marker(
2603 info.leading_spaces,
2604 info.has_trailing_space,
2605 );
2606 }
2607 }
2608 true
2609 } else {
2610 false
2611 }
2612 };
2613
2614 if !list_item_continuation {
2615 let marker_info = self.marker_info_for_line(
2616 blockquote_payload.as_ref(),
2617 line,
2618 bq_marker_line,
2619 shifted_bq_prefix,
2620 used_shifted_bq,
2621 );
2622 for i in 0..bq_depth {
2623 if let Some(info) = marker_info.get(i) {
2624 self.emit_or_buffer_blockquote_marker(
2625 info.leading_spaces,
2626 info.has_trailing_space,
2627 );
2628 }
2629 }
2630 }
2631 let line_to_append = if list_item_continuation {
2632 if continuation_has_explicit_marker {
2633 Some(inner_content)
2634 } else {
2635 Some(line)
2636 }
2637 } else {
2638 Some(inner_content)
2639 };
2640 let prev_flag = self.dispatch_list_marker_consumed;
2646 if used_shifted_bq && !self.innermost_li_above_bq() {
2647 self.dispatch_list_marker_consumed = true;
2648 }
2649 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2650 self.dispatch_list_marker_consumed = prev_flag;
2651 return dispatch;
2652 }
2653
2654 if current_bq_depth > 0 {
2657 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2659 paragraphs::append_paragraph_line(
2660 &mut self.containers,
2661 &mut self.builder,
2662 line,
2663 self.config,
2664 );
2665 return LineDispatch::consumed(1);
2666 }
2667
2668 if lists::in_blockquote_list(&self.containers)
2670 && let Some(marker_match) = try_parse_list_marker(
2671 line,
2672 self.config,
2673 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2674 )
2675 {
2676 let (indent_cols, indent_bytes) = leading_indent(line);
2677 if let Some(level) = lists::find_matching_list_level(
2678 &self.containers,
2679 &marker_match.marker,
2680 indent_cols,
2681 self.config.dialect,
2682 ) {
2683 self.close_containers_to(level + 1);
2685
2686 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2688 self.close_containers_to(self.containers.depth() - 1);
2689 }
2690 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2691 self.close_containers_to(self.containers.depth() - 1);
2692 }
2693
2694 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2696 line,
2697 marker_match.marker_len,
2698 marker_match.spaces_after_bytes,
2699 ) {
2700 let list_item = ListItemEmissionInput {
2701 content: line,
2702 marker_len: marker_match.marker_len,
2703 spaces_after_cols: marker_match.spaces_after_cols,
2704 spaces_after_bytes: marker_match.spaces_after_bytes,
2705 indent_cols,
2706 indent_bytes,
2707 virtual_marker_space: marker_match.virtual_marker_space,
2708 };
2709 lists::add_list_item_with_nested_empty_list(
2710 &mut self.containers,
2711 &mut self.builder,
2712 &list_item,
2713 nested_marker,
2714 self.config,
2715 );
2716 0
2717 } else {
2718 let list_item = ListItemEmissionInput {
2719 content: line,
2720 marker_len: marker_match.marker_len,
2721 spaces_after_cols: marker_match.spaces_after_cols,
2722 spaces_after_bytes: marker_match.spaces_after_bytes,
2723 indent_cols,
2724 indent_bytes,
2725 virtual_marker_space: marker_match.virtual_marker_space,
2726 };
2727 let finish = lists::add_list_item(
2728 &mut self.containers,
2729 &mut self.builder,
2730 &list_item,
2731 self.config,
2732 );
2733 self.dispatch_bq_after_list_item(finish)
2734 };
2735 return LineDispatch::consumed(1 + extras);
2736 }
2737 }
2738 }
2739
2740 self.parse_inner_content(line, None)
2742 }
2743
2744 fn content_container_indent_to_strip(&self) -> usize {
2746 self.containers
2747 .stack
2748 .iter()
2749 .filter_map(|c| match c {
2750 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2751 Container::Definition { content_col, .. } => Some(*content_col),
2752 _ => None,
2753 })
2754 .sum()
2755 }
2756
2757 fn innermost_li_above_bq(&self) -> bool {
2764 for c in self.containers.stack.iter().rev() {
2765 match c {
2766 Container::ListItem { .. } => return true,
2767 Container::BlockQuote { .. } => return false,
2768 _ => continue,
2769 }
2770 }
2771 false
2772 }
2773
2774 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2780 log::trace!(
2781 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2782 self.pos,
2783 self.containers.depth(),
2784 self.containers.last(),
2785 content.trim_end()
2786 );
2787 let content_indent = self.content_container_indent_to_strip();
2792 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
2793
2794 if self.config.extensions.alerts
2795 && self.current_blockquote_depth() > 0
2796 && !self.in_active_alert()
2797 && !self.is_paragraph_open()
2798 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2799 {
2800 let (_, newline_str) = strip_newline(stripped_content);
2801 self.builder.start_node(SyntaxKind::ALERT.into());
2802 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2803 if !newline_str.is_empty() {
2804 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2805 }
2806 self.containers.push(Container::Alert {
2807 blockquote_depth: self.current_blockquote_depth(),
2808 });
2809 return LineDispatch::consumed(1);
2810 }
2811
2812 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2816 let is_definition_marker =
2817 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2818 && !stripped_content.starts_with(':');
2819 if content_indent == 0 && is_definition_marker {
2820 } else {
2822 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2823
2824 if policy.definition_plain_can_continue(
2825 stripped_content,
2826 content,
2827 content_indent,
2828 &BlockContext {
2829 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2830 has_blank_before_strict: self.pos == 0
2831 || is_blank_line(self.lines[self.pos - 1]),
2832 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2833 in_fenced_div: self.in_fenced_div(),
2834 blockquote_depth: self.current_blockquote_depth(),
2835 config: self.config,
2836 diags: self.diagnostics.clone(),
2837 content_indent,
2838 indent_to_emit: None,
2839 list_indent_info: None,
2840 in_list: lists::in_list(&self.containers),
2841 in_marker_only_list_item: matches!(
2842 self.containers.last(),
2843 Some(Container::ListItem {
2844 marker_only: true,
2845 ..
2846 })
2847 ),
2848 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2849 paragraph_open: self.is_paragraph_open(),
2850 next_line: if self.pos + 1 < self.lines.len() {
2851 Some(self.lines[self.pos + 1])
2852 } else {
2853 None
2854 },
2855 open_alpha_hint: lists::open_list_hint_at_indent(
2856 &self.containers,
2857 leading_indent(stripped_content).0,
2858 ),
2859 },
2860 &self.lines,
2861 self.pos,
2862 ) {
2863 let content_line = stripped_content;
2864 let (text_without_newline, newline_str) = strip_newline(content_line);
2865 let indent_prefix = if !text_without_newline.trim().is_empty() {
2866 indent_to_emit.unwrap_or("")
2867 } else {
2868 ""
2869 };
2870 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2871
2872 if let Some(Container::Definition {
2873 plain_open,
2874 plain_buffer,
2875 ..
2876 }) = self.containers.stack.last_mut()
2877 {
2878 let line_with_newline = if !newline_str.is_empty() {
2879 format!("{}{}", content_line, newline_str)
2880 } else {
2881 content_line
2882 };
2883 plain_buffer.push_line(line_with_newline);
2884 *plain_open = true;
2885 }
2886
2887 return LineDispatch::consumed(1);
2888 }
2889 }
2890 }
2891
2892 if content_indent > 0 {
2895 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2896 let current_bq_depth = self.current_blockquote_depth();
2897 let in_footnote_definition = self
2898 .containers
2899 .stack
2900 .iter()
2901 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2902
2903 if bq_depth > 0 {
2904 if in_footnote_definition
2905 && self.config.extensions.blank_before_blockquote
2906 && current_bq_depth == 0
2907 && !blockquotes::can_start_blockquote(
2908 self.pos,
2909 &self.lines,
2910 self.config.extensions.fenced_divs,
2911 )
2912 {
2913 } else {
2917 self.emit_buffered_plain_if_needed();
2920 self.emit_list_item_buffer_if_needed();
2921
2922 self.close_paragraph_if_open();
2925
2926 if bq_depth < current_bq_depth {
2927 self.close_blockquotes_to_depth(bq_depth);
2928 } else {
2929 let marker_info = parse_blockquote_marker_info(stripped_content);
2930
2931 if bq_depth > current_bq_depth {
2932 for level in current_bq_depth..bq_depth {
2934 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2935
2936 if level == current_bq_depth
2937 && let Some(indent_str) = indent_to_emit
2938 {
2939 self.builder
2940 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2941 }
2942
2943 if let Some(info) = marker_info.get(level) {
2944 blockquotes::emit_one_blockquote_marker(
2945 &mut self.builder,
2946 info.leading_spaces,
2947 info.has_trailing_space,
2948 );
2949 }
2950
2951 self.containers.push(Container::BlockQuote {});
2952 }
2953 } else {
2954 self.emit_blockquote_markers(&marker_info, bq_depth);
2956 }
2957 }
2958
2959 return self.parse_inner_content(inner_content, Some(inner_content));
2960 }
2961 }
2962 }
2963
2964 let content = stripped_content;
2966
2967 if self.is_paragraph_open()
2968 && (paragraphs::has_open_inline_math_environment(&self.containers)
2969 || paragraphs::has_open_display_math_dollars(&self.containers))
2970 {
2971 paragraphs::append_paragraph_line(
2972 &mut self.containers,
2973 &mut self.builder,
2974 line_to_append.unwrap_or(self.lines[self.pos]),
2975 self.config,
2976 );
2977 return LineDispatch::consumed(1);
2978 }
2979
2980 use super::blocks::lists;
2984 use super::blocks::paragraphs;
2985 let list_indent_info = if lists::in_list(&self.containers) {
2986 let content_col = paragraphs::current_content_col(&self.containers);
2987 if content_col > 0 {
2988 Some(super::block_dispatcher::ListIndentInfo { content_col })
2989 } else {
2990 None
2991 }
2992 } else {
2993 None
2994 };
2995
2996 let next_line = if self.pos + 1 < self.lines.len() {
2997 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
3000 } else {
3001 None
3002 };
3003
3004 let current_bq_depth = self.current_blockquote_depth();
3005 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
3006 && current_bq_depth < alert_bq_depth
3007 {
3008 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
3009 self.close_containers_to(self.containers.depth() - 1);
3010 }
3011 }
3012
3013 let dispatcher_ctx = BlockContext {
3014 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
3018 blockquote_depth: current_bq_depth,
3019 config: self.config,
3020 diags: self.diagnostics.clone(),
3021 content_indent,
3022 indent_to_emit,
3023 list_indent_info,
3024 in_list: lists::in_list(&self.containers),
3025 in_marker_only_list_item: matches!(
3026 self.containers.last(),
3027 Some(Container::ListItem {
3028 marker_only: true,
3029 ..
3030 })
3031 ),
3032 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3033 paragraph_open: self.is_paragraph_open(),
3034 next_line,
3035 open_alpha_hint: lists::open_list_hint_at_indent(
3036 &self.containers,
3037 leading_indent(content).0,
3038 ),
3039 };
3040
3041 let mut dispatcher_ctx = dispatcher_ctx;
3044
3045 let dispatcher_prefix =
3052 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3053
3054 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3058 return dispatch;
3059 }
3060
3061 let dispatcher_match = {
3064 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3065 self.block_registry
3066 .detect_prepared(&dispatcher_ctx, &stripped)
3067 };
3068
3069 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3075 let has_blank_before = if self.pos == 0 || after_metadata_block {
3076 true
3077 } else {
3078 let prev_line = self.lines[self.pos - 1];
3079 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3080 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3081 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3082 && fenced_divs::try_parse_div_fence_open(
3083 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3084 )
3085 .is_some();
3086
3087 let prev_line_blank = is_blank_line(prev_line);
3088 prev_line_blank
3089 || prev_is_fenced_div_open
3090 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3091 || !self.previous_block_requires_blank_before_heading()
3092 };
3093
3094 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3097
3098 let prev_line_blank = if self.pos > 0 {
3099 let prev_line = self.lines[self.pos - 1];
3100 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3101 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3102 } else {
3103 false
3104 };
3105 let has_blank_before_strict = at_document_start || prev_line_blank;
3106
3107 dispatcher_ctx.has_blank_before = has_blank_before;
3108 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3109 dispatcher_ctx.at_document_start = at_document_start;
3110
3111 let dispatcher_match =
3112 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3113 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3115 self.block_registry
3116 .detect_prepared(&dispatcher_ctx, &stripped)
3117 } else {
3118 dispatcher_match
3119 };
3120
3121 if has_blank_before {
3122 if let Some(env_name) = extract_environment_name(content)
3123 && is_inline_math_environment(env_name)
3124 {
3125 if !self.is_paragraph_open() {
3126 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3127 }
3128 paragraphs::append_paragraph_line(
3129 &mut self.containers,
3130 &mut self.builder,
3131 line_to_append.unwrap_or(self.lines[self.pos]),
3132 self.config,
3133 );
3134 return LineDispatch::consumed(1);
3135 }
3136
3137 if let Some(block_match) = dispatcher_match.as_ref() {
3138 let detection = block_match.detection;
3139
3140 match detection {
3141 BlockDetectionResult::YesCanInterrupt => {
3142 self.emit_list_item_buffer_if_needed();
3143 if self.is_paragraph_open() {
3144 self.close_containers_to(self.containers.depth() - 1);
3145 }
3146 }
3147 BlockDetectionResult::Yes => {
3148 self.prepare_for_block_element();
3149 }
3150 BlockDetectionResult::No => unreachable!(),
3151 }
3152
3153 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3154 self.close_containers_to_fenced_div();
3155 }
3156
3157 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3158 self.close_open_footnote_definition();
3159 }
3160
3161 let lines_consumed = {
3162 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3163 self.block_registry.parse_prepared(
3164 block_match,
3165 &dispatcher_ctx,
3166 &mut self.builder,
3167 &stripped,
3168 )
3169 };
3170
3171 if matches!(
3172 self.block_registry.parser_name(block_match),
3173 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3174 ) {
3175 self.after_metadata_block = true;
3176 }
3177
3178 let extras = match block_match.effect {
3179 BlockEffect::None => 0,
3180 BlockEffect::OpenFencedDiv => {
3181 self.containers.push(Container::FencedDiv {});
3182 0
3183 }
3184 BlockEffect::CloseFencedDiv => {
3185 self.close_fenced_div();
3186 0
3187 }
3188 BlockEffect::OpenFootnoteDefinition => {
3189 self.handle_footnote_open_effect(block_match, content)
3190 }
3191 BlockEffect::OpenList => {
3192 self.handle_list_open_effect(block_match, content, indent_to_emit)
3193 }
3194 BlockEffect::OpenDefinitionList => {
3195 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3196 }
3197 BlockEffect::OpenBlockQuote => {
3198 0
3200 }
3201 };
3202
3203 if lines_consumed == 0 {
3204 log::warn!(
3205 "block parser made no progress at line {} (parser={})",
3206 self.pos + 1,
3207 self.block_registry.parser_name(block_match)
3208 );
3209 return LineDispatch::Rejected;
3210 }
3211
3212 return LineDispatch::consumed(lines_consumed + extras);
3213 }
3214 } else if let Some(block_match) = dispatcher_match.as_ref() {
3215 let parser_name = self.block_registry.parser_name(block_match);
3218 match block_match.detection {
3219 BlockDetectionResult::YesCanInterrupt => {
3220 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3221 && self.is_paragraph_open()
3222 {
3223 if !self.is_paragraph_open() {
3225 paragraphs::start_paragraph_if_needed(
3226 &mut self.containers,
3227 &mut self.builder,
3228 );
3229 }
3230 paragraphs::append_paragraph_line(
3231 &mut self.containers,
3232 &mut self.builder,
3233 line_to_append.unwrap_or(self.lines[self.pos]),
3234 self.config,
3235 );
3236 return LineDispatch::consumed(1);
3237 }
3238
3239 if matches!(block_match.effect, BlockEffect::OpenList)
3240 && self.is_paragraph_open()
3241 && !lists::in_list(&self.containers)
3242 && (self.content_container_indent_to_strip() == 0
3243 || self.in_footnote_definition())
3244 {
3245 let allow_interrupt =
3254 self.config.dialect == crate::options::Dialect::CommonMark && {
3255 use super::block_dispatcher::ListPrepared;
3256 use super::blocks::lists::OrderedMarker;
3257 let prepared = block_match
3258 .payload
3259 .as_ref()
3260 .and_then(|p| p.downcast_ref::<ListPrepared>());
3261 match prepared.map(|p| &p.marker) {
3262 Some(ListMarker::Bullet(_)) => true,
3263 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3264 number,
3265 ..
3266 })) => number == "1",
3267 _ => false,
3268 }
3269 };
3270 if !allow_interrupt {
3271 paragraphs::append_paragraph_line(
3272 &mut self.containers,
3273 &mut self.builder,
3274 line_to_append.unwrap_or(self.lines[self.pos]),
3275 self.config,
3276 );
3277 return LineDispatch::consumed(1);
3278 }
3279 }
3280
3281 if matches!(block_match.effect, BlockEffect::OpenList)
3288 && self.try_lazy_list_continuation(block_match, content)
3289 {
3290 return LineDispatch::consumed(1);
3291 }
3292
3293 self.emit_list_item_buffer_if_needed();
3294 if self.is_paragraph_open() {
3295 if self.html_block_demotes_paragraph_to_plain(block_match) {
3296 self.close_paragraph_as_plain_if_open();
3297 } else {
3298 self.close_containers_to(self.containers.depth() - 1);
3299 }
3300 }
3301
3302 if self.config.dialect == crate::options::Dialect::CommonMark
3309 && !matches!(block_match.effect, BlockEffect::OpenList)
3310 {
3311 let (indent_cols, _) = leading_indent(content);
3312 self.close_lists_above_indent(indent_cols);
3313 }
3314 }
3315 BlockDetectionResult::Yes => {
3316 if parser_name == "setext_heading"
3328 && self.is_paragraph_open()
3329 && self.config.dialect == crate::options::Dialect::CommonMark
3330 {
3331 let text_line = self.lines[self.pos];
3332 let underline_line = self.lines[self.pos + 1];
3333 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3334 let level = if underline_char == '=' { 1 } else { 2 };
3335 self.emit_setext_heading_folding_paragraph(
3336 text_line,
3337 underline_line,
3338 level,
3339 );
3340 return LineDispatch::consumed(2);
3341 }
3342
3343 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3346 if !self.is_paragraph_open() {
3347 paragraphs::start_paragraph_if_needed(
3348 &mut self.containers,
3349 &mut self.builder,
3350 );
3351 }
3352 paragraphs::append_paragraph_line(
3353 &mut self.containers,
3354 &mut self.builder,
3355 line_to_append.unwrap_or(self.lines[self.pos]),
3356 self.config,
3357 );
3358 return LineDispatch::consumed(1);
3359 }
3360
3361 if parser_name == "reference_definition" && self.is_paragraph_open() {
3364 paragraphs::append_paragraph_line(
3365 &mut self.containers,
3366 &mut self.builder,
3367 line_to_append.unwrap_or(self.lines[self.pos]),
3368 self.config,
3369 );
3370 return LineDispatch::consumed(1);
3371 }
3372 }
3373 BlockDetectionResult::No => unreachable!(),
3374 }
3375
3376 if !matches!(block_match.detection, BlockDetectionResult::No) {
3377 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3378 self.close_containers_to_fenced_div();
3379 }
3380
3381 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3382 self.close_open_footnote_definition();
3383 }
3384
3385 let lines_consumed = {
3386 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3387 self.block_registry.parse_prepared(
3388 block_match,
3389 &dispatcher_ctx,
3390 &mut self.builder,
3391 &stripped,
3392 )
3393 };
3394
3395 let extras = match block_match.effect {
3396 BlockEffect::None => 0,
3397 BlockEffect::OpenFencedDiv => {
3398 self.containers.push(Container::FencedDiv {});
3399 0
3400 }
3401 BlockEffect::CloseFencedDiv => {
3402 self.close_fenced_div();
3403 0
3404 }
3405 BlockEffect::OpenFootnoteDefinition => {
3406 self.handle_footnote_open_effect(block_match, content)
3407 }
3408 BlockEffect::OpenList => {
3409 self.handle_list_open_effect(block_match, content, indent_to_emit)
3410 }
3411 BlockEffect::OpenDefinitionList => {
3412 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3413 }
3414 BlockEffect::OpenBlockQuote => {
3415 0
3417 }
3418 };
3419
3420 if lines_consumed == 0 {
3421 log::warn!(
3422 "block parser made no progress at line {} (parser={})",
3423 self.pos + 1,
3424 self.block_registry.parser_name(block_match)
3425 );
3426 return LineDispatch::Rejected;
3427 }
3428
3429 return LineDispatch::consumed(lines_consumed + extras);
3430 }
3431 }
3432
3433 if self.config.extensions.line_blocks
3435 && (has_blank_before || self.pos == 0)
3436 && try_parse_line_block_start(content).is_some()
3437 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3441 {
3442 log::trace!("Parsed line block at line {}", self.pos);
3443 self.close_paragraph_if_open();
3445
3446 let prefix = ContainerPrefix::default();
3452 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3453 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3454 if new_pos > self.pos {
3455 return LineDispatch::consumed(new_pos - self.pos);
3456 }
3457 }
3458
3459 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3462 log::trace!(
3463 "Inside ListItem - buffering content: {:?}",
3464 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3465 );
3466 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3468
3469 if let Some(Container::ListItem {
3471 buffer,
3472 marker_only,
3473 ..
3474 }) = self.containers.stack.last_mut()
3475 {
3476 buffer.push_text(line);
3477 if !is_blank_line(line) {
3478 *marker_only = false;
3479 }
3480 }
3481
3482 return LineDispatch::consumed(1);
3483 }
3484
3485 log::trace!(
3486 "Not in ListItem - creating paragraph for: {:?}",
3487 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3488 );
3489 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3491 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3494 paragraphs::append_paragraph_line(
3495 &mut self.containers,
3496 &mut self.builder,
3497 line,
3498 self.config,
3499 );
3500 LineDispatch::consumed(1)
3501 }
3502
3503 fn fenced_div_container_index(&self) -> Option<usize> {
3504 self.containers
3505 .stack
3506 .iter()
3507 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3508 }
3509
3510 fn close_containers_to_fenced_div(&mut self) {
3511 if let Some(index) = self.fenced_div_container_index() {
3512 self.close_containers_to(index + 1);
3513 }
3514 }
3515
3516 fn close_fenced_div(&mut self) {
3517 if let Some(index) = self.fenced_div_container_index() {
3518 self.close_containers_to(index);
3519 }
3520 }
3521
3522 fn in_fenced_div(&self) -> bool {
3523 self.containers
3524 .stack
3525 .iter()
3526 .any(|c| matches!(c, Container::FencedDiv { .. }))
3527 }
3528
3529 fn in_footnote_definition(&self) -> bool {
3537 self.containers
3538 .stack
3539 .iter()
3540 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3541 }
3542}
3543
3544fn emit_definition_plain_or_heading(
3551 builder: &mut GreenNodeBuilder<'static>,
3552 text: &str,
3553 config: &ParserOptions,
3554 suppress_footnote_refs: bool,
3555) {
3556 let line_without_newline = text
3557 .strip_suffix("\r\n")
3558 .or_else(|| text.strip_suffix('\n'));
3559 if let Some(line) = line_without_newline
3560 && !line.contains('\n')
3561 && !line.contains('\r')
3562 && let Some(level) = try_parse_atx_heading(line)
3563 {
3564 emit_atx_heading(builder, text, level, config);
3565 return;
3566 }
3567
3568 if let Some(first_nl) = text.find('\n') {
3570 let first_line = &text[..first_nl];
3571 let after_first = &text[first_nl + 1..];
3572 if !after_first.is_empty()
3573 && let Some(level) = try_parse_atx_heading(first_line)
3574 {
3575 let heading_bytes = &text[..first_nl + 1];
3576 emit_atx_heading(builder, heading_bytes, level, config);
3577 builder.start_node(SyntaxKind::PLAIN.into());
3578 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3579 builder.finish_node();
3580 return;
3581 }
3582 }
3583
3584 builder.start_node(SyntaxKind::PLAIN.into());
3585 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3586 builder.finish_node();
3587}
3588
3589fn footnote_first_line_term_lookahead(
3598 lines: &[&str],
3599 pos: usize,
3600 content_col: usize,
3601 table_captions_enabled: bool,
3602) -> Option<usize> {
3603 let mut check_pos = pos + 1;
3604 let mut blank_count = 0;
3605 while check_pos < lines.len() {
3606 let line = lines[check_pos];
3607 let (trimmed, _) = strip_newline(line);
3608 if trimmed.trim().is_empty() {
3609 blank_count += 1;
3610 check_pos += 1;
3611 continue;
3612 }
3613 let (line_indent_cols, _) = leading_indent(trimmed);
3614 if line_indent_cols < content_col {
3615 return None;
3616 }
3617 let strip_bytes = byte_index_at_column(trimmed, content_col);
3618 if strip_bytes > trimmed.len() {
3619 return None;
3620 }
3621 let stripped = &trimmed[strip_bytes..];
3622 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3623 if marker == ':'
3627 && table_captions_enabled
3628 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3629 {
3630 return None;
3631 }
3632 return Some(blank_count);
3633 }
3634 return None;
3635 }
3636 None
3637}