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 self.config.flavor,
516 );
517 Some(new_pos.saturating_sub(self.pos).saturating_sub(1))
518 }
519
520 fn maybe_open_indented_code_in_new_list_item(&mut self) {
531 let Some(Container::ListItem {
532 content_col,
533 buffer,
534 marker_only,
535 virtual_marker_space,
536 }) = self.containers.stack.last()
537 else {
538 return;
539 };
540 if *marker_only {
541 return;
542 }
543 if buffer.segment_count() != 1 {
544 return;
545 }
546 let Some(text) = buffer.first_text() else {
547 return;
548 };
549 let content_col = *content_col;
550 let virtual_marker_space = *virtual_marker_space;
551 let text_owned = text.to_string();
552
553 let mut iter = text_owned.split_inclusive('\n');
555 let line_with_nl = iter.next().unwrap_or("").to_string();
556 if iter.next().is_some() {
557 return;
558 }
559
560 let line_no_nl = line_with_nl
561 .strip_suffix("\r\n")
562 .or_else(|| line_with_nl.strip_suffix('\n'))
563 .unwrap_or(&line_with_nl);
564 let nl_suffix = &line_with_nl[line_no_nl.len()..];
565
566 let buffer_start_col = if virtual_marker_space {
567 content_col.saturating_sub(1)
568 } else {
569 content_col
570 };
571
572 let target = content_col + 4;
573 let (cols_walked, ws_bytes) =
574 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
575
576 if buffer_start_col + cols_walked < target {
577 return;
578 }
579 if ws_bytes >= line_no_nl.len() {
580 return;
581 }
582
583 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
584 buffer.clear();
585 }
586
587 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
588 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
589 if ws_bytes > 0 {
590 self.builder
591 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
592 }
593 let rest = &line_no_nl[ws_bytes..];
594 if !rest.is_empty() {
595 self.builder.token(SyntaxKind::TEXT.into(), rest);
596 }
597 if !nl_suffix.is_empty() {
598 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
599 }
600 self.builder.finish_node();
601 self.builder.finish_node();
602 }
603
604 fn has_matching_fence_closer(
605 &self,
606 fence: &code_blocks::FenceInfo,
607 bq_depth: usize,
608 content_col: usize,
609 ) -> bool {
610 for raw_line in self.lines.iter().skip(self.pos + 1) {
611 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
612 if line_bq_depth < bq_depth {
613 break;
614 }
615 let candidate = if content_col > 0 && !inner.is_empty() {
616 let idx = byte_index_at_column(inner, content_col);
617 if idx <= inner.len() {
618 &inner[idx..]
619 } else {
620 inner
621 }
622 } else {
623 inner
624 };
625 if code_blocks::is_closing_fence(candidate, fence) {
626 return true;
627 }
628 }
629 false
630 }
631
632 fn is_paragraph_open(&self) -> bool {
634 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
635 }
636
637 fn emit_setext_heading_folding_paragraph(
645 &mut self,
646 text_line: &str,
647 underline_line: &str,
648 level: usize,
649 ) {
650 let (buffered_text, checkpoint) = match self.containers.stack.last() {
651 Some(Container::Paragraph {
652 buffer,
653 start_checkpoint,
654 ..
655 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
656 _ => (String::new(), None),
657 };
658
659 if checkpoint.is_some() {
660 self.containers.stack.pop();
661 }
662
663 let combined_text = if buffered_text.is_empty() {
664 text_line.to_string()
665 } else {
666 format!("{}{}", buffered_text, text_line)
667 };
668
669 let cp = checkpoint.expect(
670 "emit_setext_heading_folding_paragraph requires an open paragraph; \
671 single-line setext should go through the regular dispatcher path",
672 );
673 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
674 emit_setext_heading_body(
675 &mut self.builder,
676 &combined_text,
677 underline_line,
678 level,
679 self.config,
680 );
681 self.builder.finish_node();
682 }
683
684 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> Option<LineDispatch> {
702 let Some(Container::ListItem {
703 buffer,
704 content_col,
705 ..
706 }) = self.containers.stack.last()
707 else {
708 return None;
709 };
710 if buffer.segment_count() != 1 {
711 return None;
712 }
713 let text_line = buffer.first_text()?;
714
715 let content_col = *content_col;
720 let (underline_indent_cols, _) = leading_indent(content);
721 if underline_indent_cols < content_col {
722 return None;
723 }
724
725 let lines = [text_line, content];
726 let (level, _) = try_parse_setext_heading(&lines, 0)?;
727
728 let (text_no_newline, _) = strip_newline(text_line);
729 if text_no_newline.trim().is_empty() {
730 return None;
731 }
732 if try_parse_horizontal_rule(text_no_newline).is_some() {
733 return None;
734 }
735
736 let text_owned = text_line.to_string();
737 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
738 buffer.clear();
739 }
740 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
741 Some(LineDispatch::consumed(1))
742 }
743
744 fn close_paragraph_if_open(&mut self) {
746 if self.is_paragraph_open() {
747 self.close_containers_to(self.containers.depth() - 1);
748 }
749 }
750
751 fn close_paragraph_as_plain_if_open(&mut self) {
762 if !self.is_paragraph_open() {
763 return;
764 }
765 let Some(Container::Paragraph {
766 buffer,
767 start_checkpoint,
768 ..
769 }) = self.containers.stack.last()
770 else {
771 return;
772 };
773 let buffer_clone = buffer.clone();
774 let checkpoint = *start_checkpoint;
775 let suppress_footnote_refs = self.in_footnote_definition();
776 self.containers.stack.pop();
777 self.builder
778 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
779 if !buffer_clone.is_empty() {
780 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
781 }
782 self.builder.finish_node();
783 }
784
785 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
794 if self.config.dialect != crate::options::Dialect::Pandoc {
795 return false;
796 }
797 if self.block_registry.parser_name(block_match) != "html_block" {
798 return false;
799 }
800 let html_block_type = block_match
801 .payload
802 .as_ref()
803 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
804 matches!(
805 html_block_type,
806 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
807 )
808 }
809
810 fn prepare_for_block_element(&mut self) {
813 self.emit_list_item_buffer_if_needed();
814 self.close_paragraph_if_open();
815 }
816
817 fn close_open_footnote_definition(&mut self) {
821 while matches!(
822 self.containers.last(),
823 Some(Container::FootnoteDefinition { .. })
824 ) {
825 self.close_containers_to(self.containers.depth() - 1);
826 }
827 }
828
829 fn handle_footnote_open_effect(
833 &mut self,
834 block_match: &super::block_dispatcher::PreparedBlockMatch,
835 content: &str,
836 ) -> usize {
837 let content_start = block_match
838 .payload
839 .as_ref()
840 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
841 .map(|p| p.content_start)
842 .unwrap_or(0);
843
844 let content_col = 4;
845 self.containers
846 .push(Container::FootnoteDefinition { content_col });
847
848 if content_start == 0 {
849 return 0;
850 }
851 let first_line_content = &content[content_start..];
852 if first_line_content.trim().is_empty() {
853 let (_, newline_str) = strip_newline(content);
854 if !newline_str.is_empty() {
855 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
856 }
857 return 0;
858 }
859
860 if self.config.extensions.definition_lists
861 && let Some(blank_count) = footnote_first_line_term_lookahead(
862 &self.lines,
863 self.pos,
864 content_col,
865 self.config.extensions.table_captions,
866 )
867 {
868 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
869 self.containers.push(Container::DefinitionList {});
870 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
871 self.containers.push(Container::DefinitionItem {});
872 emit_term(&mut self.builder, first_line_content, self.config);
873 for i in 0..blank_count {
874 let blank_pos = self.pos + 1 + i;
875 if blank_pos < self.lines.len() {
876 let blank_line = self.lines[blank_pos];
877 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
878 self.builder
879 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
880 self.builder.finish_node();
881 }
882 }
883 return blank_count;
884 }
885
886 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
887 paragraphs::append_paragraph_line(
888 &mut self.containers,
889 &mut self.builder,
890 first_line_content,
891 self.config,
892 );
893 0
894 }
895
896 fn try_lazy_list_continuation(
908 &mut self,
909 block_match: &super::block_dispatcher::PreparedBlockMatch,
910 content: &str,
911 ) -> bool {
912 use super::block_dispatcher::ListPrepared;
913
914 let Some(prepared) = block_match
915 .payload
916 .as_ref()
917 .and_then(|p| p.downcast_ref::<ListPrepared>())
918 else {
919 return false;
920 };
921
922 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
923 return false;
924 }
925
926 let current_content_col = paragraphs::current_content_col(&self.containers);
927 if prepared.indent_cols >= current_content_col {
928 return false;
929 }
930
931 if lists::find_matching_list_level(
932 &self.containers,
933 &prepared.marker,
934 prepared.indent_cols,
935 self.config.dialect,
936 )
937 .is_some()
938 {
939 return false;
940 }
941
942 match self.containers.last() {
943 Some(Container::Paragraph { .. }) => {
944 paragraphs::append_paragraph_line(
945 &mut self.containers,
946 &mut self.builder,
947 content,
948 self.config,
949 );
950 true
951 }
952 Some(Container::ListItem { .. }) => {
953 if let Some(Container::ListItem {
954 buffer,
955 marker_only,
956 ..
957 }) = self.containers.stack.last_mut()
958 {
959 buffer.push_text(content);
960 if !content.trim().is_empty() {
961 *marker_only = false;
962 }
963 }
964 true
965 }
966 _ => false,
967 }
968 }
969
970 fn handle_list_open_effect(
976 &mut self,
977 block_match: &super::block_dispatcher::PreparedBlockMatch,
978 content: &str,
979 indent_to_emit: Option<&str>,
980 ) -> usize {
981 use super::block_dispatcher::ListPrepared;
982
983 let prepared = block_match
984 .payload
985 .as_ref()
986 .and_then(|p| p.downcast_ref::<ListPrepared>());
987 let Some(prepared) = prepared else {
988 return 0;
989 };
990
991 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
992 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
993 paragraphs::append_paragraph_line(
994 &mut self.containers,
995 &mut self.builder,
996 content,
997 self.config,
998 );
999 return 0;
1000 }
1001
1002 if self.is_paragraph_open() {
1003 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
1004 paragraphs::append_paragraph_line(
1005 &mut self.containers,
1006 &mut self.builder,
1007 content,
1008 self.config,
1009 );
1010 return 0;
1011 }
1012 self.close_containers_to(self.containers.depth() - 1);
1013 }
1014
1015 if matches!(
1016 self.containers.last(),
1017 Some(Container::Definition {
1018 plain_open: true,
1019 ..
1020 })
1021 ) {
1022 self.emit_buffered_plain_if_needed();
1023 }
1024
1025 let matched_level = lists::find_matching_list_level(
1026 &self.containers,
1027 &prepared.marker,
1028 prepared.indent_cols,
1029 self.config.dialect,
1030 );
1031 let list_item = ListItemEmissionInput {
1032 content,
1033 marker_len: prepared.marker_len,
1034 spaces_after_cols: prepared.spaces_after_cols,
1035 spaces_after_bytes: prepared.spaces_after,
1036 indent_cols: prepared.indent_cols,
1037 indent_bytes: prepared.indent_bytes,
1038 virtual_marker_space: prepared.virtual_marker_space,
1039 };
1040 let current_content_col = paragraphs::current_content_col(&self.containers);
1041 let deep_ordered_matched_level = matched_level
1042 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
1043 .and_then(|(level, container)| match container {
1044 Container::List {
1045 marker: list_marker,
1046 base_indent_cols,
1047 ..
1048 } if matches!(
1049 (&prepared.marker, list_marker),
1050 (ListMarker::Ordered(_), ListMarker::Ordered(_))
1051 ) && prepared.indent_cols >= 4
1052 && *base_indent_cols >= 4
1053 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
1054 {
1055 Some(level)
1056 }
1057 _ => None,
1058 });
1059
1060 if deep_ordered_matched_level.is_none()
1061 && current_content_col > 0
1062 && prepared.indent_cols >= current_content_col
1063 {
1064 if let Some(level) = matched_level
1065 && let Some(Container::List {
1066 base_indent_cols, ..
1067 }) = self.containers.stack.get(level)
1068 && prepared.indent_cols == *base_indent_cols
1069 {
1070 let num_parent_lists = self.containers.stack[..level]
1071 .iter()
1072 .filter(|c| matches!(c, Container::List { .. }))
1073 .count();
1074
1075 if num_parent_lists > 0 {
1076 self.close_containers_to(level + 1);
1077
1078 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1079 self.close_containers_to(self.containers.depth() - 1);
1080 }
1081 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1082 self.close_containers_to(self.containers.depth() - 1);
1083 }
1084
1085 if let Some(indent_str) = indent_to_emit {
1086 self.builder
1087 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1088 }
1089
1090 let finish = if let Some(nested_marker) = prepared.nested_marker {
1091 lists::add_list_item_with_nested_empty_list(
1092 &mut self.containers,
1093 &mut self.builder,
1094 &list_item,
1095 nested_marker,
1096 self.config,
1097 );
1098 lists::ListItemFinish::Done
1099 } else {
1100 lists::add_list_item(
1101 &mut self.containers,
1102 &mut self.builder,
1103 &list_item,
1104 self.config,
1105 )
1106 };
1107 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1108 return extras;
1109 }
1110 self.maybe_open_indented_code_in_new_list_item();
1111 return self.dispatch_bq_after_list_item(finish);
1112 }
1113 }
1114
1115 self.emit_list_item_buffer_if_needed();
1116
1117 let finish = start_nested_list(
1118 &mut self.containers,
1119 &mut self.builder,
1120 &prepared.marker,
1121 &list_item,
1122 indent_to_emit,
1123 self.config,
1124 );
1125 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1126 return extras;
1127 }
1128 self.maybe_open_indented_code_in_new_list_item();
1129 return self.dispatch_bq_after_list_item(finish);
1130 }
1131
1132 if let Some(level) = matched_level {
1133 self.close_containers_to(level + 1);
1134
1135 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1136 self.close_containers_to(self.containers.depth() - 1);
1137 }
1138 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1139 self.close_containers_to(self.containers.depth() - 1);
1140 }
1141
1142 if let Some(indent_str) = indent_to_emit {
1143 self.builder
1144 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1145 }
1146
1147 let finish = if let Some(nested_marker) = prepared.nested_marker {
1148 lists::add_list_item_with_nested_empty_list(
1149 &mut self.containers,
1150 &mut self.builder,
1151 &list_item,
1152 nested_marker,
1153 self.config,
1154 );
1155 lists::ListItemFinish::Done
1156 } else {
1157 lists::add_list_item(
1158 &mut self.containers,
1159 &mut self.builder,
1160 &list_item,
1161 self.config,
1162 )
1163 };
1164 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1165 return extras;
1166 }
1167 self.maybe_open_indented_code_in_new_list_item();
1168 return self.dispatch_bq_after_list_item(finish);
1169 }
1170
1171 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1172 self.close_containers_to(self.containers.depth() - 1);
1173 }
1174 while matches!(
1175 self.containers.last(),
1176 Some(Container::ListItem { .. } | Container::List { .. })
1177 ) {
1178 self.close_containers_to(self.containers.depth() - 1);
1179 }
1180
1181 self.builder.start_node(SyntaxKind::LIST.into());
1182 if let Some(indent_str) = indent_to_emit {
1183 self.builder
1184 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1185 }
1186 self.containers.push(Container::List {
1187 marker: prepared.marker.clone(),
1188 base_indent_cols: prepared.indent_cols,
1189 has_blank_between_items: false,
1190 });
1191
1192 let finish = if let Some(nested_marker) = prepared.nested_marker {
1193 lists::add_list_item_with_nested_empty_list(
1194 &mut self.containers,
1195 &mut self.builder,
1196 &list_item,
1197 nested_marker,
1198 self.config,
1199 );
1200 lists::ListItemFinish::Done
1201 } else {
1202 lists::add_list_item(
1203 &mut self.containers,
1204 &mut self.builder,
1205 &list_item,
1206 self.config,
1207 )
1208 };
1209 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
1210 return extras;
1211 }
1212 self.maybe_open_indented_code_in_new_list_item();
1213 self.dispatch_bq_after_list_item(finish)
1214 }
1215
1216 fn handle_definition_list_effect(
1223 &mut self,
1224 block_match: &super::block_dispatcher::PreparedBlockMatch,
1225 content: &str,
1226 indent_to_emit: Option<&str>,
1227 ) -> usize {
1228 use super::block_dispatcher::DefinitionPrepared;
1229
1230 let prepared = block_match
1231 .payload
1232 .as_ref()
1233 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1234 let Some(prepared) = prepared else {
1235 return 0;
1236 };
1237
1238 let mut extras: usize = 0;
1239 match prepared {
1240 DefinitionPrepared::Definition {
1241 marker_char,
1242 indent,
1243 spaces_after,
1244 spaces_after_cols,
1245 has_content,
1246 } => {
1247 self.emit_buffered_plain_if_needed();
1248
1249 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1250 self.close_containers_to(self.containers.depth() - 1);
1251 }
1252 while matches!(self.containers.last(), Some(Container::List { .. })) {
1253 self.close_containers_to(self.containers.depth() - 1);
1254 }
1255
1256 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1257 self.close_containers_to(self.containers.depth() - 1);
1258 }
1259
1260 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1261 self.close_containers_to(self.containers.depth() - 1);
1262 }
1263
1264 if definition_lists::in_definition_list(&self.containers)
1268 && !matches!(
1269 self.containers.last(),
1270 Some(Container::DefinitionItem { .. })
1271 )
1272 {
1273 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1274 self.containers.push(Container::DefinitionItem {});
1275 }
1276
1277 if !definition_lists::in_definition_list(&self.containers) {
1278 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1279 self.containers.push(Container::DefinitionList {});
1280 }
1281
1282 if !matches!(
1283 self.containers.last(),
1284 Some(Container::DefinitionItem { .. })
1285 ) {
1286 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1287 self.containers.push(Container::DefinitionItem {});
1288 }
1289
1290 self.builder.start_node(SyntaxKind::DEFINITION.into());
1291
1292 if let Some(indent_str) = indent_to_emit {
1293 self.builder
1294 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1295 }
1296
1297 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1298 let indent_bytes = byte_index_at_column(content, *indent);
1299 if *spaces_after > 0 {
1300 let space_start = indent_bytes + 1;
1301 let space_end = space_start + *spaces_after;
1302 if space_end <= content.len() {
1303 self.builder.token(
1304 SyntaxKind::WHITESPACE.into(),
1305 &content[space_start..space_end],
1306 );
1307 }
1308 }
1309
1310 if !*has_content {
1311 let current_line = self.lines[self.pos];
1312 let (_, newline_str) = strip_newline(current_line);
1313 if !newline_str.is_empty() {
1314 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1315 }
1316 }
1317
1318 let content_col = *indent + 1 + *spaces_after_cols;
1319 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1320 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1321 let mut plain_buffer = TextBuffer::new();
1322 let mut definition_pushed = false;
1323
1324 if *has_content {
1325 let current_line = self.lines[self.pos];
1326 let (trimmed_content, _) = strip_newline(content);
1327
1328 let content_start = content_start_bytes.min(trimmed_content.len());
1335 let content_slice = &trimmed_content[content_start..];
1336 let content_line = &content[content_start_bytes.min(content.len())..];
1337
1338 let (blockquote_depth, inner_blockquote_content) =
1339 count_blockquote_markers(content_line);
1340
1341 let should_start_list_from_first_line = self
1342 .lines
1343 .get(self.pos + 1)
1344 .map(|next_line| {
1345 let (next_without_newline, _) = strip_newline(next_line);
1346 if next_without_newline.trim().is_empty() {
1347 return true;
1348 }
1349
1350 let (next_indent_cols, _) = leading_indent(next_without_newline);
1351 next_indent_cols >= content_col
1352 })
1353 .unwrap_or(true);
1354
1355 if blockquote_depth > 0 {
1356 self.containers.push(Container::Definition {
1357 content_col,
1358 plain_open: false,
1359 plain_buffer: TextBuffer::new(),
1360 });
1361 definition_pushed = true;
1362
1363 let marker_info = parse_blockquote_marker_info(content_line);
1364 for level in 0..blockquote_depth {
1365 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1366 if let Some(info) = marker_info.get(level) {
1367 blockquotes::emit_one_blockquote_marker(
1368 &mut self.builder,
1369 info.leading_spaces,
1370 info.has_trailing_space,
1371 );
1372 }
1373 self.containers.push(Container::BlockQuote {});
1374 }
1375
1376 if !inner_blockquote_content.trim().is_empty() {
1377 paragraphs::start_paragraph_if_needed(
1378 &mut self.containers,
1379 &mut self.builder,
1380 );
1381 paragraphs::append_paragraph_line(
1382 &mut self.containers,
1383 &mut self.builder,
1384 inner_blockquote_content,
1385 self.config,
1386 );
1387 }
1388 } else if let Some(marker_match) = try_parse_list_marker(
1389 content_slice,
1390 self.config,
1391 lists::open_list_hint_at_indent(
1392 &self.containers,
1393 leading_indent(content_slice).0,
1394 ),
1395 ) && should_start_list_from_first_line
1396 {
1397 self.containers.push(Container::Definition {
1398 content_col,
1399 plain_open: false,
1400 plain_buffer: TextBuffer::new(),
1401 });
1402 definition_pushed = true;
1403
1404 let (indent_cols, indent_bytes) = leading_indent(content_line);
1405 self.builder.start_node(SyntaxKind::LIST.into());
1406 self.containers.push(Container::List {
1407 marker: marker_match.marker.clone(),
1408 base_indent_cols: indent_cols,
1409 has_blank_between_items: false,
1410 });
1411
1412 let list_item = ListItemEmissionInput {
1413 content: content_line,
1414 marker_len: marker_match.marker_len,
1415 spaces_after_cols: marker_match.spaces_after_cols,
1416 spaces_after_bytes: marker_match.spaces_after_bytes,
1417 indent_cols,
1418 indent_bytes,
1419 virtual_marker_space: marker_match.virtual_marker_space,
1420 };
1421
1422 let finish = if let Some(nested_marker) = is_content_nested_bullet_marker(
1423 content_line,
1424 marker_match.marker_len,
1425 marker_match.spaces_after_bytes,
1426 ) {
1427 lists::add_list_item_with_nested_empty_list(
1428 &mut self.containers,
1429 &mut self.builder,
1430 &list_item,
1431 nested_marker,
1432 self.config,
1433 );
1434 lists::ListItemFinish::Done
1435 } else {
1436 lists::add_list_item(
1437 &mut self.containers,
1438 &mut self.builder,
1439 &list_item,
1440 self.config,
1441 )
1442 };
1443 extras = self.dispatch_bq_after_list_item(finish);
1444 } else if let Some(fence) =
1445 code_blocks::try_parse_fence_open(content_slice, self.config.dialect)
1446 {
1447 self.containers.push(Container::Definition {
1448 content_col,
1449 plain_open: false,
1450 plain_buffer: TextBuffer::new(),
1451 });
1452 definition_pushed = true;
1453
1454 let bq_depth = self.current_blockquote_depth();
1455 if let Some(indent_str) = indent_to_emit {
1456 self.builder
1457 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1458 }
1459 let fence_line = content[content_start..].to_string();
1460 let prefix = ContainerPrefix::from_scalars(
1464 bq_depth,
1465 0,
1466 bq_depth > 0,
1467 content_col,
1468 false,
1469 );
1470 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
1471 let new_pos = if self.config.extensions.tex_math_gfm
1472 && code_blocks::is_gfm_math_fence(&fence)
1473 {
1474 code_blocks::parse_fenced_math_block(
1475 &mut self.builder,
1476 &window,
1477 fence,
1478 Some(&fence_line),
1479 )
1480 } else {
1481 code_blocks::parse_fenced_code_block(
1482 &mut self.builder,
1483 &window,
1484 fence,
1485 Some(&fence_line),
1486 &self.diagnostics,
1487 self.config.flavor,
1488 )
1489 };
1490 extras = new_pos.saturating_sub(self.pos).saturating_sub(1);
1491 } else {
1492 let (_, newline_str) = strip_newline(current_line);
1493 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1494 if content_without_newline.is_empty() {
1495 plain_buffer.push_line(newline_str);
1496 } else {
1497 let line_with_newline = if !newline_str.is_empty() {
1498 format!("{}{}", content_without_newline, newline_str)
1499 } else {
1500 content_without_newline.to_string()
1501 };
1502 plain_buffer.push_line(line_with_newline);
1503 }
1504 }
1505 }
1506
1507 if !definition_pushed {
1508 self.containers.push(Container::Definition {
1509 content_col,
1510 plain_open: *has_content,
1511 plain_buffer,
1512 });
1513 }
1514 }
1515 DefinitionPrepared::Term { blank_count } => {
1516 self.emit_buffered_plain_if_needed();
1517
1518 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1519 self.close_containers_to(self.containers.depth() - 1);
1520 }
1521
1522 if !definition_lists::in_definition_list(&self.containers) {
1523 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1524 self.containers.push(Container::DefinitionList {});
1525 }
1526
1527 while matches!(
1528 self.containers.last(),
1529 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1530 ) {
1531 self.close_containers_to(self.containers.depth() - 1);
1532 }
1533
1534 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1535 self.containers.push(Container::DefinitionItem {});
1536
1537 emit_term(&mut self.builder, content, self.config);
1538
1539 for i in 0..*blank_count {
1540 let blank_pos = self.pos + 1 + i;
1541 if blank_pos < self.lines.len() {
1542 let blank_line = self.lines[blank_pos];
1543 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1544 self.builder
1545 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1546 self.builder.finish_node();
1547 }
1548 }
1549 extras = *blank_count;
1550 }
1551 };
1552 extras
1553 }
1554
1555 fn blockquote_marker_info(
1557 &self,
1558 payload: Option<&BlockQuotePrepared>,
1559 line: &str,
1560 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1561 payload
1562 .map(|payload| payload.marker_info.clone())
1563 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1564 }
1565
1566 fn marker_info_for_line(
1572 &self,
1573 payload: Option<&BlockQuotePrepared>,
1574 raw_line: &str,
1575 marker_line: &str,
1576 shifted_prefix: &str,
1577 used_shifted: bool,
1578 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1579 let mut marker_info = if used_shifted {
1580 parse_blockquote_marker_info(marker_line)
1581 } else {
1582 self.blockquote_marker_info(payload, raw_line)
1583 };
1584 if used_shifted && !shifted_prefix.is_empty() {
1585 let (prefix_cols, _) = leading_indent(shifted_prefix);
1586 if let Some(first) = marker_info.first_mut() {
1587 first.leading_spaces += prefix_cols;
1588 }
1589 }
1590 marker_info
1591 }
1592
1593 fn shifted_blockquote_from_list<'b>(
1596 &self,
1597 line: &'b str,
1598 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1599 let list_content_col = self
1608 .containers
1609 .stack
1610 .iter()
1611 .rev()
1612 .find_map(|c| match c {
1613 Container::ListItem { content_col, .. } => Some(*content_col),
1614 _ => None,
1615 })
1616 .unwrap_or(0);
1617 let content_container_indent = self.content_container_indent_to_strip();
1618 if list_content_col == 0 && self.current_blockquote_depth() == 0 {
1626 return None;
1627 }
1628 let marker_col = list_content_col.saturating_add(content_container_indent);
1629 if marker_col == 0 {
1630 return None;
1631 }
1632
1633 let (indent_cols, _) = leading_indent(line);
1634 if indent_cols < marker_col {
1635 return None;
1636 }
1637
1638 let idx = byte_index_at_column(line, marker_col);
1639 if idx > line.len() {
1640 return None;
1641 }
1642
1643 let candidate = &line[idx..];
1644 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1645 if candidate_depth == 0 {
1646 return None;
1647 }
1648
1649 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1650 }
1651
1652 fn emit_blockquote_markers(
1653 &mut self,
1654 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1655 depth: usize,
1656 ) {
1657 for i in 0..depth {
1658 if let Some(info) = marker_info.get(i) {
1659 blockquotes::emit_one_blockquote_marker(
1660 &mut self.builder,
1661 info.leading_spaces,
1662 info.has_trailing_space,
1663 );
1664 }
1665 }
1666 }
1667
1668 fn current_blockquote_depth(&self) -> usize {
1669 blockquotes::current_blockquote_depth(&self.containers)
1670 }
1671
1672 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1680 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1681 return None;
1682 };
1683 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1684 }
1685
1686 fn emit_or_buffer_blockquote_marker(
1691 &mut self,
1692 leading_spaces: usize,
1693 has_trailing_space: bool,
1694 ) {
1695 if let Some(Container::ListItem {
1696 buffer,
1697 marker_only,
1698 ..
1699 }) = self.containers.stack.last_mut()
1700 {
1701 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1702 *marker_only = false;
1703 return;
1704 }
1705
1706 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1708 paragraphs::append_paragraph_marker(
1710 &mut self.containers,
1711 leading_spaces,
1712 has_trailing_space,
1713 );
1714 } else {
1715 blockquotes::emit_one_blockquote_marker(
1717 &mut self.builder,
1718 leading_spaces,
1719 has_trailing_space,
1720 );
1721 }
1722 }
1723
1724 fn parse_document_stack(&mut self) {
1725 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1726
1727 log::trace!("Starting document parse");
1728
1729 while self.pos < self.lines.len() {
1732 let line = self.lines[self.pos];
1733
1734 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1735
1736 match self.parse_line(line) {
1737 LineDispatch::Consumed(n) => self.pos += n,
1738 LineDispatch::Rejected => self.pos += 1,
1739 }
1740 }
1741
1742 self.close_containers_to(0);
1743 self.builder.finish_node(); }
1745
1746 fn parse_line(&mut self, line: &str) -> LineDispatch {
1750 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1753 let mut bq_marker_line = line;
1754 let mut shifted_bq_prefix = "";
1755 let mut used_shifted_bq = false;
1756 if bq_depth == 0
1757 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1758 self.shifted_blockquote_from_list(line)
1759 {
1760 bq_depth = candidate_depth;
1761 inner_content = candidate_inner;
1762 bq_marker_line = candidate_line;
1763 shifted_bq_prefix = candidate_prefix;
1764 used_shifted_bq = true;
1765 }
1766 let current_bq_depth = self.current_blockquote_depth();
1767
1768 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1769 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1770 let dispatcher_ctx = if current_bq_depth == 0 {
1771 Some(BlockContext {
1772 has_blank_before,
1773 has_blank_before_strict: has_blank_before,
1774 at_document_start: self.pos == 0,
1775 in_fenced_div: self.in_fenced_div(),
1776 blockquote_depth: current_bq_depth,
1777 config: self.config,
1778 diags: self.diagnostics.clone(),
1779 content_indent: 0,
1780 indent_to_emit: None,
1781 list_indent_info: None,
1782 in_list: lists::in_list(&self.containers),
1783 in_marker_only_list_item: matches!(
1784 self.containers.last(),
1785 Some(Container::ListItem {
1786 marker_only: true,
1787 ..
1788 })
1789 ),
1790 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1791 paragraph_open: self.is_paragraph_open(),
1792 next_line: if self.pos + 1 < self.lines.len() {
1793 Some(self.lines[self.pos + 1])
1794 } else {
1795 None
1796 },
1797 open_alpha_hint: lists::open_list_hint_at_indent(
1798 &self.containers,
1799 leading_indent(line).0,
1800 ),
1801 })
1802 } else {
1803 None
1804 };
1805
1806 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1807 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
1808 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
1809 self.block_registry
1810 .detect_prepared(dispatcher_ctx, &stripped)
1811 .and_then(|prepared| {
1812 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1813 blockquote_match = Some(prepared);
1814 blockquote_match.as_ref().and_then(|prepared| {
1815 prepared
1816 .payload
1817 .as_ref()
1818 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1819 .cloned()
1820 })
1821 } else {
1822 None
1823 }
1824 })
1825 } else {
1826 None
1827 };
1828
1829 log::trace!(
1830 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1831 self.pos,
1832 bq_depth,
1833 current_bq_depth,
1834 self.containers.depth(),
1835 line.trim_end()
1836 );
1837
1838 let inner_blank_in_blockquote = bq_depth > 0
1845 && is_blank_line(inner_content)
1846 && (current_bq_depth > 0
1847 || !self.config.extensions.blank_before_blockquote
1848 || blockquotes::can_start_blockquote(
1849 self.pos,
1850 &self.lines,
1851 self.config.extensions.fenced_divs,
1852 ));
1853 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1854
1855 if is_blank {
1856 if self.is_paragraph_open()
1857 && paragraphs::has_open_inline_math_environment(&self.containers)
1858 {
1859 paragraphs::append_paragraph_line(
1860 &mut self.containers,
1861 &mut self.builder,
1862 line,
1863 self.config,
1864 );
1865 return LineDispatch::consumed(1);
1866 }
1867
1868 self.close_paragraph_if_open();
1870
1871 self.emit_buffered_plain_if_needed();
1875
1876 if bq_depth > current_bq_depth {
1884 for _ in current_bq_depth..bq_depth {
1886 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1887 self.containers.push(Container::BlockQuote {});
1888 }
1889 } else if bq_depth < current_bq_depth {
1890 self.close_blockquotes_to_depth(bq_depth);
1892 }
1893
1894 let mut peek = self.pos + 1;
1901 while peek < self.lines.len() {
1902 let peek_line = self.lines[peek];
1903 if is_blank_line(peek_line) {
1904 peek += 1;
1905 continue;
1906 }
1907 if bq_depth > 0 {
1908 let (peek_bq, _) = count_blockquote_markers(peek_line);
1909 if peek_bq >= bq_depth {
1910 let peek_inner =
1911 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1912 if is_blank_line(peek_inner) {
1913 peek += 1;
1914 continue;
1915 }
1916 }
1917 }
1918 break;
1919 }
1920
1921 let levels_to_keep = if peek < self.lines.len() {
1923 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1924 self.current_blockquote_depth(),
1925 &self.containers,
1926 &self.lines,
1927 peek,
1928 self.lines[peek],
1929 )
1930 } else {
1931 0
1932 };
1933 log::trace!(
1934 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1935 self.containers.depth(),
1936 levels_to_keep,
1937 if peek < self.lines.len() {
1938 self.lines[peek]
1939 } else {
1940 "<EOF>"
1941 }
1942 );
1943
1944 while self.containers.depth() > levels_to_keep {
1948 match self.containers.last() {
1949 Some(Container::ListItem { .. }) => {
1950 log::trace!(
1952 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1953 levels_to_keep,
1954 self.containers.depth()
1955 );
1956 self.close_containers_to(self.containers.depth() - 1);
1957 }
1958 Some(Container::List { .. })
1959 | Some(Container::FootnoteDefinition { .. })
1960 | Some(Container::Alert { .. })
1961 | Some(Container::Paragraph { .. })
1962 | Some(Container::Definition { .. })
1963 | Some(Container::DefinitionItem { .. })
1964 | Some(Container::DefinitionList { .. }) => {
1965 log::trace!(
1966 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1967 self.containers.last(),
1968 self.containers.depth(),
1969 levels_to_keep
1970 );
1971
1972 self.close_containers_to(self.containers.depth() - 1);
1973 }
1974 _ => break,
1975 }
1976 }
1977
1978 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1982 self.emit_list_item_buffer_if_needed();
1983 }
1984
1985 if bq_depth > 0 {
1987 let marker_info = self.marker_info_for_line(
1988 blockquote_payload.as_ref(),
1989 line,
1990 bq_marker_line,
1991 shifted_bq_prefix,
1992 used_shifted_bq,
1993 );
1994 self.emit_blockquote_markers(&marker_info, bq_depth);
1995 }
1996
1997 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1998 self.builder
1999 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
2000 self.builder.finish_node();
2001
2002 return LineDispatch::consumed(1);
2003 }
2004
2005 if bq_depth > current_bq_depth {
2007 if self.config.extensions.blank_before_blockquote
2010 && current_bq_depth == 0
2011 && !used_shifted_bq
2012 && !blockquote_payload
2013 .as_ref()
2014 .map(|payload| payload.can_start)
2015 .unwrap_or_else(|| {
2016 blockquotes::can_start_blockquote(
2017 self.pos,
2018 &self.lines,
2019 self.config.extensions.fenced_divs,
2020 )
2021 })
2022 {
2023 self.emit_list_item_buffer_if_needed();
2027 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2028 paragraphs::append_paragraph_line(
2029 &mut self.containers,
2030 &mut self.builder,
2031 line,
2032 self.config,
2033 );
2034 return LineDispatch::consumed(1);
2035 }
2036
2037 let can_nest = if current_bq_depth > 0 {
2040 if self.config.extensions.blank_before_blockquote {
2041 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2043 || (self.pos > 0 && {
2044 let prev_line = self.lines[self.pos - 1];
2045 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2046 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
2047 })
2048 } else {
2049 true
2050 }
2051 } else {
2052 blockquote_payload
2053 .as_ref()
2054 .map(|payload| payload.can_nest)
2055 .unwrap_or(true)
2056 };
2057
2058 if !can_nest {
2059 let content_at_current_depth =
2062 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
2063
2064 let marker_info = self.marker_info_for_line(
2066 blockquote_payload.as_ref(),
2067 line,
2068 bq_marker_line,
2069 shifted_bq_prefix,
2070 used_shifted_bq,
2071 );
2072 for i in 0..current_bq_depth {
2073 if let Some(info) = marker_info.get(i) {
2074 self.emit_or_buffer_blockquote_marker(
2075 info.leading_spaces,
2076 info.has_trailing_space,
2077 );
2078 }
2079 }
2080
2081 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2082 paragraphs::append_paragraph_line(
2084 &mut self.containers,
2085 &mut self.builder,
2086 content_at_current_depth,
2087 self.config,
2088 );
2089 return LineDispatch::consumed(1);
2090 } else {
2091 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2093 paragraphs::append_paragraph_line(
2094 &mut self.containers,
2095 &mut self.builder,
2096 content_at_current_depth,
2097 self.config,
2098 );
2099 return LineDispatch::consumed(1);
2100 }
2101 }
2102
2103 self.emit_list_item_buffer_if_needed();
2106
2107 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2109 self.close_containers_to(self.containers.depth() - 1);
2110 }
2111
2112 let marker_info = self.marker_info_for_line(
2114 blockquote_payload.as_ref(),
2115 line,
2116 bq_marker_line,
2117 shifted_bq_prefix,
2118 used_shifted_bq,
2119 );
2120
2121 if let (Some(dispatcher_ctx), Some(prepared)) =
2122 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
2123 {
2124 let prefix = ContainerPrefix::from_ctx(dispatcher_ctx);
2125 let stripped = StrippedLines::new(&self.lines, self.pos, &prefix);
2126 let _ = self.block_registry.parse_prepared(
2127 prepared,
2128 dispatcher_ctx,
2129 &mut self.builder,
2130 &stripped,
2131 );
2132 for _ in 0..bq_depth {
2133 self.containers.push(Container::BlockQuote {});
2134 }
2135 } else {
2136 for level in 0..current_bq_depth {
2138 if let Some(info) = marker_info.get(level) {
2139 self.emit_or_buffer_blockquote_marker(
2140 info.leading_spaces,
2141 info.has_trailing_space,
2142 );
2143 }
2144 }
2145
2146 for level in current_bq_depth..bq_depth {
2148 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2149
2150 if let Some(info) = marker_info.get(level) {
2152 blockquotes::emit_one_blockquote_marker(
2153 &mut self.builder,
2154 info.leading_spaces,
2155 info.has_trailing_space,
2156 );
2157 }
2158
2159 self.containers.push(Container::BlockQuote {});
2160 }
2161 }
2162
2163 let prev_flag = self.dispatch_list_marker_consumed;
2176 if used_shifted_bq && !self.innermost_li_above_bq() {
2177 self.dispatch_list_marker_consumed = true;
2178 }
2179 let dispatch = self.parse_inner_content(inner_content, Some(inner_content));
2180 self.dispatch_list_marker_consumed = prev_flag;
2181 return dispatch;
2182 } else if bq_depth < current_bq_depth {
2183 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2189 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2196 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2197 let interrupts_via_fence = is_commonmark
2198 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2199 let interrupts_via_div_close = self.config.extensions.fenced_divs
2204 && self.in_fenced_div()
2205 && fenced_divs::is_div_closing_fence(line);
2206 if !interrupts_via_hr && !interrupts_via_fence && !interrupts_via_div_close {
2207 if bq_depth > 0 {
2208 let marker_info = self.marker_info_for_line(
2214 blockquote_payload.as_ref(),
2215 line,
2216 bq_marker_line,
2217 shifted_bq_prefix,
2218 used_shifted_bq,
2219 );
2220 for i in 0..bq_depth {
2221 if let Some(info) = marker_info.get(i) {
2222 paragraphs::append_paragraph_marker(
2223 &mut self.containers,
2224 info.leading_spaces,
2225 info.has_trailing_space,
2226 );
2227 }
2228 }
2229 paragraphs::append_paragraph_line(
2230 &mut self.containers,
2231 &mut self.builder,
2232 inner_content,
2233 self.config,
2234 );
2235 } else {
2236 paragraphs::append_paragraph_line(
2237 &mut self.containers,
2238 &mut self.builder,
2239 line,
2240 self.config,
2241 );
2242 }
2243 return LineDispatch::consumed(1);
2244 }
2245 }
2246 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2254 && lists::in_blockquote_list(&self.containers)
2255 && try_parse_list_marker(
2256 line,
2257 self.config,
2258 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2259 )
2260 .is_none()
2261 {
2262 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2263 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2264 let interrupts_via_fence = is_commonmark
2265 && code_blocks::try_parse_fence_open(line, self.config.dialect).is_some();
2266 if !interrupts_via_hr && !interrupts_via_fence {
2267 if bq_depth > 0 {
2268 let marker_info = self.marker_info_for_line(
2269 blockquote_payload.as_ref(),
2270 line,
2271 bq_marker_line,
2272 shifted_bq_prefix,
2273 used_shifted_bq,
2274 );
2275 if let Some(Container::ListItem {
2276 buffer,
2277 marker_only,
2278 ..
2279 }) = self.containers.stack.last_mut()
2280 {
2281 for i in 0..bq_depth {
2282 if let Some(info) = marker_info.get(i) {
2283 buffer.push_blockquote_marker(
2284 info.leading_spaces,
2285 info.has_trailing_space,
2286 );
2287 }
2288 }
2289 buffer.push_text(inner_content);
2290 if !inner_content.trim().is_empty() {
2291 *marker_only = false;
2292 }
2293 }
2294 } else if let Some(Container::ListItem {
2295 buffer,
2296 marker_only,
2297 ..
2298 }) = self.containers.stack.last_mut()
2299 {
2300 buffer.push_text(line);
2301 if !line.trim().is_empty() {
2302 *marker_only = false;
2303 }
2304 }
2305 return LineDispatch::consumed(1);
2306 }
2307 }
2308 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2314 if lists::in_blockquote_list(&self.containers)
2317 && let Some(marker_match) = try_parse_list_marker(
2318 line,
2319 self.config,
2320 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2321 )
2322 {
2323 let (indent_cols, indent_bytes) = leading_indent(line);
2324 if let Some(level) = lists::find_matching_list_level(
2325 &self.containers,
2326 &marker_match.marker,
2327 indent_cols,
2328 self.config.dialect,
2329 ) {
2330 self.close_containers_to(level + 1);
2333
2334 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2336 self.close_containers_to(self.containers.depth() - 1);
2337 }
2338 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2339 self.close_containers_to(self.containers.depth() - 1);
2340 }
2341
2342 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2344 line,
2345 marker_match.marker_len,
2346 marker_match.spaces_after_bytes,
2347 ) {
2348 let list_item = ListItemEmissionInput {
2349 content: line,
2350 marker_len: marker_match.marker_len,
2351 spaces_after_cols: marker_match.spaces_after_cols,
2352 spaces_after_bytes: marker_match.spaces_after_bytes,
2353 indent_cols,
2354 indent_bytes,
2355 virtual_marker_space: marker_match.virtual_marker_space,
2356 };
2357 lists::add_list_item_with_nested_empty_list(
2358 &mut self.containers,
2359 &mut self.builder,
2360 &list_item,
2361 nested_marker,
2362 self.config,
2363 );
2364 0
2365 } else {
2366 let list_item = ListItemEmissionInput {
2367 content: line,
2368 marker_len: marker_match.marker_len,
2369 spaces_after_cols: marker_match.spaces_after_cols,
2370 spaces_after_bytes: marker_match.spaces_after_bytes,
2371 indent_cols,
2372 indent_bytes,
2373 virtual_marker_space: marker_match.virtual_marker_space,
2374 };
2375 let finish = lists::add_list_item(
2376 &mut self.containers,
2377 &mut self.builder,
2378 &list_item,
2379 self.config,
2380 );
2381 self.dispatch_bq_after_list_item(finish)
2382 };
2383 return LineDispatch::consumed(1 + extras);
2384 }
2385 }
2386 }
2387
2388 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2390 self.close_containers_to(self.containers.depth() - 1);
2391 }
2392
2393 self.close_blockquotes_to_depth(bq_depth);
2395
2396 if bq_depth > 0 {
2398 let marker_info = self.marker_info_for_line(
2400 blockquote_payload.as_ref(),
2401 line,
2402 bq_marker_line,
2403 shifted_bq_prefix,
2404 used_shifted_bq,
2405 );
2406 for i in 0..bq_depth {
2407 if let Some(info) = marker_info.get(i) {
2408 self.emit_or_buffer_blockquote_marker(
2409 info.leading_spaces,
2410 info.has_trailing_space,
2411 );
2412 }
2413 }
2414 return self.parse_inner_content(inner_content, Some(inner_content));
2416 } else {
2417 return self.parse_inner_content(line, None);
2419 }
2420 } else if bq_depth > 0 {
2421 let mut list_item_continuation = false;
2423 let same_depth_marker_info = self.marker_info_for_line(
2424 blockquote_payload.as_ref(),
2425 line,
2426 bq_marker_line,
2427 shifted_bq_prefix,
2428 used_shifted_bq,
2429 );
2430 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2431
2432 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2444 if let Some(marker_match) = try_parse_list_marker(
2445 inner_content,
2446 self.config,
2447 lists::open_list_hint_at_indent(&self.containers, inner_indent_cols_raw),
2448 ) {
2449 let inner_content_threshold =
2453 marker_match.marker_len + marker_match.spaces_after_cols;
2454 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2455 let sibling_list_level = if is_sibling_candidate {
2456 self.containers
2457 .stack
2458 .iter()
2459 .enumerate()
2460 .rev()
2461 .find_map(|(i, c)| match c {
2462 Container::List { marker, .. }
2463 if lists::markers_match(
2464 &marker_match.marker,
2465 marker,
2466 self.config.dialect,
2467 ) && self.containers.stack[..i]
2468 .iter()
2469 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2470 .count()
2471 == bq_depth =>
2472 {
2473 Some(i)
2474 }
2475 _ => None,
2476 })
2477 } else {
2478 None
2479 };
2480 if let Some(list_level) = sibling_list_level {
2481 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2487 Some(Container::List {
2488 base_indent_cols, ..
2489 }) => *base_indent_cols,
2490 _ => 0,
2491 };
2492
2493 self.emit_list_item_buffer_if_needed();
2495 self.close_containers_to(list_level + 1);
2498
2499 for i in 0..bq_depth {
2503 if let Some(info) = same_depth_marker_info.get(i) {
2504 self.emit_or_buffer_blockquote_marker(
2505 info.leading_spaces,
2506 info.has_trailing_space,
2507 );
2508 }
2509 }
2510
2511 let list_item = ListItemEmissionInput {
2513 content: inner_content,
2514 marker_len: marker_match.marker_len,
2515 spaces_after_cols: marker_match.spaces_after_cols,
2516 spaces_after_bytes: marker_match.spaces_after_bytes,
2517 indent_cols: sibling_base_indent_cols,
2518 indent_bytes: inner_indent_bytes,
2519 virtual_marker_space: marker_match.virtual_marker_space,
2520 };
2521 let finish = lists::add_list_item(
2522 &mut self.containers,
2523 &mut self.builder,
2524 &list_item,
2525 self.config,
2526 );
2527 let extras =
2528 if let Some(extras) = self.maybe_open_fenced_code_in_new_list_item() {
2529 extras
2530 } else {
2531 self.maybe_open_indented_code_in_new_list_item();
2532 self.dispatch_bq_after_list_item(finish)
2533 };
2534 return LineDispatch::consumed(1 + extras);
2535 }
2536 }
2537
2538 if matches!(
2541 self.containers.last(),
2542 Some(Container::ListItem { content_col: _, .. })
2543 ) {
2544 let (indent_cols, _) = leading_indent(inner_content);
2545 let content_indent = self.content_container_indent_to_strip();
2546 let effective_indent = indent_cols.saturating_sub(content_indent);
2547 let content_col = match self.containers.last() {
2548 Some(Container::ListItem { content_col, .. }) => *content_col,
2549 _ => 0,
2550 };
2551
2552 let is_new_item_at_outer_level = if try_parse_list_marker(
2554 inner_content,
2555 self.config,
2556 lists::open_list_hint_at_indent(
2557 &self.containers,
2558 leading_indent(inner_content).0,
2559 ),
2560 )
2561 .is_some()
2562 {
2563 effective_indent < content_col
2564 } else {
2565 false
2566 };
2567
2568 if is_new_item_at_outer_level
2572 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2573 {
2574 log::trace!(
2575 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2576 is_new_item_at_outer_level,
2577 effective_indent,
2578 content_col
2579 );
2580 self.close_containers_to(self.containers.depth() - 1);
2581 } else {
2582 log::trace!(
2583 "Keeping ListItem: effective_indent={} >= content_col={}",
2584 effective_indent,
2585 content_col
2586 );
2587 list_item_continuation = true;
2588 }
2589 }
2590
2591 if list_item_continuation
2595 && code_blocks::try_parse_fence_open(inner_content, self.config.dialect).is_some()
2596 {
2597 list_item_continuation = false;
2598 }
2599
2600 let continuation_has_explicit_marker = list_item_continuation && {
2601 if has_explicit_same_depth_marker {
2602 for i in 0..bq_depth {
2603 if let Some(info) = same_depth_marker_info.get(i) {
2604 self.emit_or_buffer_blockquote_marker(
2605 info.leading_spaces,
2606 info.has_trailing_space,
2607 );
2608 }
2609 }
2610 true
2611 } else {
2612 false
2613 }
2614 };
2615
2616 if !list_item_continuation {
2617 let marker_info = self.marker_info_for_line(
2618 blockquote_payload.as_ref(),
2619 line,
2620 bq_marker_line,
2621 shifted_bq_prefix,
2622 used_shifted_bq,
2623 );
2624 for i in 0..bq_depth {
2625 if let Some(info) = marker_info.get(i) {
2626 self.emit_or_buffer_blockquote_marker(
2627 info.leading_spaces,
2628 info.has_trailing_space,
2629 );
2630 }
2631 }
2632 }
2633 let line_to_append = if list_item_continuation {
2634 if continuation_has_explicit_marker {
2635 Some(inner_content)
2636 } else {
2637 Some(line)
2638 }
2639 } else {
2640 Some(inner_content)
2641 };
2642 let prev_flag = self.dispatch_list_marker_consumed;
2648 if used_shifted_bq && !self.innermost_li_above_bq() {
2649 self.dispatch_list_marker_consumed = true;
2650 }
2651 let dispatch = self.parse_inner_content(inner_content, line_to_append);
2652 self.dispatch_list_marker_consumed = prev_flag;
2653 return dispatch;
2654 }
2655
2656 if current_bq_depth > 0 {
2659 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2661 paragraphs::append_paragraph_line(
2662 &mut self.containers,
2663 &mut self.builder,
2664 line,
2665 self.config,
2666 );
2667 return LineDispatch::consumed(1);
2668 }
2669
2670 if lists::in_blockquote_list(&self.containers)
2672 && let Some(marker_match) = try_parse_list_marker(
2673 line,
2674 self.config,
2675 lists::open_list_hint_at_indent(&self.containers, leading_indent(line).0),
2676 )
2677 {
2678 let (indent_cols, indent_bytes) = leading_indent(line);
2679 if let Some(level) = lists::find_matching_list_level(
2680 &self.containers,
2681 &marker_match.marker,
2682 indent_cols,
2683 self.config.dialect,
2684 ) {
2685 self.close_containers_to(level + 1);
2687
2688 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2690 self.close_containers_to(self.containers.depth() - 1);
2691 }
2692 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2693 self.close_containers_to(self.containers.depth() - 1);
2694 }
2695
2696 let extras = if let Some(nested_marker) = is_content_nested_bullet_marker(
2698 line,
2699 marker_match.marker_len,
2700 marker_match.spaces_after_bytes,
2701 ) {
2702 let list_item = ListItemEmissionInput {
2703 content: line,
2704 marker_len: marker_match.marker_len,
2705 spaces_after_cols: marker_match.spaces_after_cols,
2706 spaces_after_bytes: marker_match.spaces_after_bytes,
2707 indent_cols,
2708 indent_bytes,
2709 virtual_marker_space: marker_match.virtual_marker_space,
2710 };
2711 lists::add_list_item_with_nested_empty_list(
2712 &mut self.containers,
2713 &mut self.builder,
2714 &list_item,
2715 nested_marker,
2716 self.config,
2717 );
2718 0
2719 } else {
2720 let list_item = ListItemEmissionInput {
2721 content: line,
2722 marker_len: marker_match.marker_len,
2723 spaces_after_cols: marker_match.spaces_after_cols,
2724 spaces_after_bytes: marker_match.spaces_after_bytes,
2725 indent_cols,
2726 indent_bytes,
2727 virtual_marker_space: marker_match.virtual_marker_space,
2728 };
2729 let finish = lists::add_list_item(
2730 &mut self.containers,
2731 &mut self.builder,
2732 &list_item,
2733 self.config,
2734 );
2735 self.dispatch_bq_after_list_item(finish)
2736 };
2737 return LineDispatch::consumed(1 + extras);
2738 }
2739 }
2740 }
2741
2742 self.parse_inner_content(line, None)
2744 }
2745
2746 fn content_container_indent_to_strip(&self) -> usize {
2748 self.containers
2749 .stack
2750 .iter()
2751 .filter_map(|c| match c {
2752 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2753 Container::Definition { content_col, .. } => Some(*content_col),
2754 _ => None,
2755 })
2756 .sum()
2757 }
2758
2759 fn innermost_li_above_bq(&self) -> bool {
2766 for c in self.containers.stack.iter().rev() {
2767 match c {
2768 Container::ListItem { .. } => return true,
2769 Container::BlockQuote { .. } => return false,
2770 _ => continue,
2771 }
2772 }
2773 false
2774 }
2775
2776 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> LineDispatch {
2782 log::trace!(
2783 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2784 self.pos,
2785 self.containers.depth(),
2786 self.containers.last(),
2787 content.trim_end()
2788 );
2789 let content_indent = self.content_container_indent_to_strip();
2794 let (stripped_content, indent_to_emit) = strip_content_indent(content, content_indent);
2795
2796 if self.config.extensions.alerts
2797 && self.current_blockquote_depth() > 0
2798 && !self.in_active_alert()
2799 && !self.is_paragraph_open()
2800 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2801 {
2802 let (_, newline_str) = strip_newline(stripped_content);
2803 self.builder.start_node(SyntaxKind::ALERT.into());
2804 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2805 if !newline_str.is_empty() {
2806 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2807 }
2808 self.containers.push(Container::Alert {
2809 blockquote_depth: self.current_blockquote_depth(),
2810 });
2811 return LineDispatch::consumed(1);
2812 }
2813
2814 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2818 let is_definition_marker =
2819 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2820 && !stripped_content.starts_with(':');
2821 if content_indent == 0 && is_definition_marker {
2822 } else {
2824 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2825
2826 if policy.definition_plain_can_continue(
2827 stripped_content,
2828 content,
2829 content_indent,
2830 &BlockContext {
2831 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2832 has_blank_before_strict: self.pos == 0
2833 || is_blank_line(self.lines[self.pos - 1]),
2834 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2835 in_fenced_div: self.in_fenced_div(),
2836 blockquote_depth: self.current_blockquote_depth(),
2837 config: self.config,
2838 diags: self.diagnostics.clone(),
2839 content_indent,
2840 indent_to_emit: None,
2841 list_indent_info: None,
2842 in_list: lists::in_list(&self.containers),
2843 in_marker_only_list_item: matches!(
2844 self.containers.last(),
2845 Some(Container::ListItem {
2846 marker_only: true,
2847 ..
2848 })
2849 ),
2850 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2851 paragraph_open: self.is_paragraph_open(),
2852 next_line: if self.pos + 1 < self.lines.len() {
2853 Some(self.lines[self.pos + 1])
2854 } else {
2855 None
2856 },
2857 open_alpha_hint: lists::open_list_hint_at_indent(
2858 &self.containers,
2859 leading_indent(stripped_content).0,
2860 ),
2861 },
2862 &self.lines,
2863 self.pos,
2864 ) {
2865 let content_line = stripped_content;
2866 let (text_without_newline, newline_str) = strip_newline(content_line);
2867 let indent_prefix = if !text_without_newline.trim().is_empty() {
2868 indent_to_emit.unwrap_or("")
2869 } else {
2870 ""
2871 };
2872 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2873
2874 if let Some(Container::Definition {
2875 plain_open,
2876 plain_buffer,
2877 ..
2878 }) = self.containers.stack.last_mut()
2879 {
2880 let line_with_newline = if !newline_str.is_empty() {
2881 format!("{}{}", content_line, newline_str)
2882 } else {
2883 content_line
2884 };
2885 plain_buffer.push_line(line_with_newline);
2886 *plain_open = true;
2887 }
2888
2889 return LineDispatch::consumed(1);
2890 }
2891 }
2892 }
2893
2894 if content_indent > 0 {
2897 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2898 let current_bq_depth = self.current_blockquote_depth();
2899 let in_footnote_definition = self
2900 .containers
2901 .stack
2902 .iter()
2903 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2904
2905 if bq_depth > 0 {
2906 if in_footnote_definition
2907 && self.config.extensions.blank_before_blockquote
2908 && current_bq_depth == 0
2909 && !blockquotes::can_start_blockquote(
2910 self.pos,
2911 &self.lines,
2912 self.config.extensions.fenced_divs,
2913 )
2914 {
2915 } else {
2919 self.emit_buffered_plain_if_needed();
2922 self.emit_list_item_buffer_if_needed();
2923
2924 self.close_paragraph_if_open();
2927
2928 if bq_depth < current_bq_depth {
2929 self.close_blockquotes_to_depth(bq_depth);
2930 } else {
2931 let marker_info = parse_blockquote_marker_info(stripped_content);
2932
2933 if bq_depth > current_bq_depth {
2934 for level in current_bq_depth..bq_depth {
2936 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2937
2938 if level == current_bq_depth
2939 && let Some(indent_str) = indent_to_emit
2940 {
2941 self.builder
2942 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2943 }
2944
2945 if let Some(info) = marker_info.get(level) {
2946 blockquotes::emit_one_blockquote_marker(
2947 &mut self.builder,
2948 info.leading_spaces,
2949 info.has_trailing_space,
2950 );
2951 }
2952
2953 self.containers.push(Container::BlockQuote {});
2954 }
2955 } else {
2956 self.emit_blockquote_markers(&marker_info, bq_depth);
2958 }
2959 }
2960
2961 return self.parse_inner_content(inner_content, Some(inner_content));
2962 }
2963 }
2964 }
2965
2966 let content = stripped_content;
2968
2969 if self.is_paragraph_open()
2970 && (paragraphs::has_open_inline_math_environment(&self.containers)
2971 || paragraphs::has_open_display_math_dollars(&self.containers))
2972 {
2973 paragraphs::append_paragraph_line(
2974 &mut self.containers,
2975 &mut self.builder,
2976 line_to_append.unwrap_or(self.lines[self.pos]),
2977 self.config,
2978 );
2979 return LineDispatch::consumed(1);
2980 }
2981
2982 use super::blocks::lists;
2986 use super::blocks::paragraphs;
2987 let list_indent_info = if lists::in_list(&self.containers) {
2988 let content_col = paragraphs::current_content_col(&self.containers);
2989 if content_col > 0 {
2990 Some(super::block_dispatcher::ListIndentInfo { content_col })
2991 } else {
2992 None
2993 }
2994 } else {
2995 None
2996 };
2997
2998 let next_line = if self.pos + 1 < self.lines.len() {
2999 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
3002 } else {
3003 None
3004 };
3005
3006 let current_bq_depth = self.current_blockquote_depth();
3007 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
3008 && current_bq_depth < alert_bq_depth
3009 {
3010 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
3011 self.close_containers_to(self.containers.depth() - 1);
3012 }
3013 }
3014
3015 let dispatcher_ctx = BlockContext {
3016 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
3020 blockquote_depth: current_bq_depth,
3021 config: self.config,
3022 diags: self.diagnostics.clone(),
3023 content_indent,
3024 indent_to_emit,
3025 list_indent_info,
3026 in_list: lists::in_list(&self.containers),
3027 in_marker_only_list_item: matches!(
3028 self.containers.last(),
3029 Some(Container::ListItem {
3030 marker_only: true,
3031 ..
3032 })
3033 ),
3034 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
3035 paragraph_open: self.is_paragraph_open(),
3036 next_line,
3037 open_alpha_hint: lists::open_list_hint_at_indent(
3038 &self.containers,
3039 leading_indent(content).0,
3040 ),
3041 };
3042
3043 let mut dispatcher_ctx = dispatcher_ctx;
3046
3047 let dispatcher_prefix =
3054 ContainerPrefix::from_stack(&self.containers.stack, self.dispatch_list_marker_consumed);
3055
3056 if let Some(dispatch) = self.try_fold_list_item_buffer_into_setext(stripped_content) {
3060 return dispatch;
3061 }
3062
3063 let dispatcher_match = {
3066 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3067 self.block_registry
3068 .detect_prepared(&dispatcher_ctx, &stripped)
3069 };
3070
3071 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
3077 let has_blank_before = if self.pos == 0 || after_metadata_block {
3078 true
3079 } else {
3080 let prev_line = self.lines[self.pos - 1];
3081 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3082 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
3083 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
3084 && fenced_divs::try_parse_div_fence_open(
3085 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
3086 )
3087 .is_some();
3088
3089 let prev_line_blank = is_blank_line(prev_line);
3090 prev_line_blank
3091 || prev_is_fenced_div_open
3092 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
3093 || !self.previous_block_requires_blank_before_heading()
3094 };
3095
3096 let at_document_start = self.pos == 0 && current_bq_depth == 0;
3099
3100 let prev_line_blank = if self.pos > 0 {
3101 let prev_line = self.lines[self.pos - 1];
3102 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
3103 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
3104 } else {
3105 false
3106 };
3107 let has_blank_before_strict = at_document_start || prev_line_blank;
3108
3109 dispatcher_ctx.has_blank_before = has_blank_before;
3110 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
3111 dispatcher_ctx.at_document_start = at_document_start;
3112
3113 let dispatcher_match =
3114 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
3115 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3117 self.block_registry
3118 .detect_prepared(&dispatcher_ctx, &stripped)
3119 } else {
3120 dispatcher_match
3121 };
3122
3123 if has_blank_before {
3124 if let Some(env_name) = extract_environment_name(content)
3125 && is_inline_math_environment(env_name)
3126 {
3127 if !self.is_paragraph_open() {
3128 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3129 }
3130 paragraphs::append_paragraph_line(
3131 &mut self.containers,
3132 &mut self.builder,
3133 line_to_append.unwrap_or(self.lines[self.pos]),
3134 self.config,
3135 );
3136 return LineDispatch::consumed(1);
3137 }
3138
3139 if let Some(block_match) = dispatcher_match.as_ref() {
3140 let detection = block_match.detection;
3141
3142 match detection {
3143 BlockDetectionResult::YesCanInterrupt => {
3144 self.emit_list_item_buffer_if_needed();
3145 if self.is_paragraph_open() {
3146 self.close_containers_to(self.containers.depth() - 1);
3147 }
3148 }
3149 BlockDetectionResult::Yes => {
3150 self.prepare_for_block_element();
3151 }
3152 BlockDetectionResult::No => unreachable!(),
3153 }
3154
3155 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3156 self.close_containers_to_fenced_div();
3157 }
3158
3159 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3160 self.close_open_footnote_definition();
3161 }
3162
3163 let lines_consumed = {
3164 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3165 self.block_registry.parse_prepared(
3166 block_match,
3167 &dispatcher_ctx,
3168 &mut self.builder,
3169 &stripped,
3170 )
3171 };
3172
3173 if matches!(
3174 self.block_registry.parser_name(block_match),
3175 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
3176 ) {
3177 self.after_metadata_block = true;
3178 }
3179
3180 let extras = match block_match.effect {
3181 BlockEffect::None => 0,
3182 BlockEffect::OpenFencedDiv => {
3183 self.containers.push(Container::FencedDiv {});
3184 0
3185 }
3186 BlockEffect::CloseFencedDiv => {
3187 self.close_fenced_div();
3188 0
3189 }
3190 BlockEffect::OpenFootnoteDefinition => {
3191 self.handle_footnote_open_effect(block_match, content)
3192 }
3193 BlockEffect::OpenList => {
3194 self.handle_list_open_effect(block_match, content, indent_to_emit)
3195 }
3196 BlockEffect::OpenDefinitionList => {
3197 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3198 }
3199 BlockEffect::OpenBlockQuote => {
3200 0
3202 }
3203 };
3204
3205 if lines_consumed == 0 {
3206 log::warn!(
3207 "block parser made no progress at line {} (parser={})",
3208 self.pos + 1,
3209 self.block_registry.parser_name(block_match)
3210 );
3211 return LineDispatch::Rejected;
3212 }
3213
3214 return LineDispatch::consumed(lines_consumed + extras);
3215 }
3216 } else if let Some(block_match) = dispatcher_match.as_ref() {
3217 let parser_name = self.block_registry.parser_name(block_match);
3220 match block_match.detection {
3221 BlockDetectionResult::YesCanInterrupt => {
3222 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
3223 && self.is_paragraph_open()
3224 {
3225 if !self.is_paragraph_open() {
3227 paragraphs::start_paragraph_if_needed(
3228 &mut self.containers,
3229 &mut self.builder,
3230 );
3231 }
3232 paragraphs::append_paragraph_line(
3233 &mut self.containers,
3234 &mut self.builder,
3235 line_to_append.unwrap_or(self.lines[self.pos]),
3236 self.config,
3237 );
3238 return LineDispatch::consumed(1);
3239 }
3240
3241 if matches!(block_match.effect, BlockEffect::OpenList)
3242 && self.is_paragraph_open()
3243 && !lists::in_list(&self.containers)
3244 && (self.content_container_indent_to_strip() == 0
3245 || self.in_footnote_definition())
3246 {
3247 let allow_interrupt =
3256 self.config.dialect == crate::options::Dialect::CommonMark && {
3257 use super::block_dispatcher::ListPrepared;
3258 use super::blocks::lists::OrderedMarker;
3259 let prepared = block_match
3260 .payload
3261 .as_ref()
3262 .and_then(|p| p.downcast_ref::<ListPrepared>());
3263 match prepared.map(|p| &p.marker) {
3264 Some(ListMarker::Bullet(_)) => true,
3265 Some(ListMarker::Ordered(OrderedMarker::Decimal {
3266 number,
3267 ..
3268 })) => number == "1",
3269 _ => false,
3270 }
3271 };
3272 if !allow_interrupt {
3273 paragraphs::append_paragraph_line(
3274 &mut self.containers,
3275 &mut self.builder,
3276 line_to_append.unwrap_or(self.lines[self.pos]),
3277 self.config,
3278 );
3279 return LineDispatch::consumed(1);
3280 }
3281 }
3282
3283 if matches!(block_match.effect, BlockEffect::OpenList)
3290 && self.try_lazy_list_continuation(block_match, content)
3291 {
3292 return LineDispatch::consumed(1);
3293 }
3294
3295 self.emit_list_item_buffer_if_needed();
3296 if self.is_paragraph_open() {
3297 if self.html_block_demotes_paragraph_to_plain(block_match) {
3298 self.close_paragraph_as_plain_if_open();
3299 } else {
3300 self.close_containers_to(self.containers.depth() - 1);
3301 }
3302 }
3303
3304 if self.config.dialect == crate::options::Dialect::CommonMark
3311 && !matches!(block_match.effect, BlockEffect::OpenList)
3312 {
3313 let (indent_cols, _) = leading_indent(content);
3314 self.close_lists_above_indent(indent_cols);
3315 }
3316 }
3317 BlockDetectionResult::Yes => {
3318 if parser_name == "setext_heading"
3330 && self.is_paragraph_open()
3331 && self.config.dialect == crate::options::Dialect::CommonMark
3332 {
3333 let text_line = self.lines[self.pos];
3334 let underline_line = self.lines[self.pos + 1];
3335 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3336 let level = if underline_char == '=' { 1 } else { 2 };
3337 self.emit_setext_heading_folding_paragraph(
3338 text_line,
3339 underline_line,
3340 level,
3341 );
3342 return LineDispatch::consumed(2);
3343 }
3344
3345 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3348 if !self.is_paragraph_open() {
3349 paragraphs::start_paragraph_if_needed(
3350 &mut self.containers,
3351 &mut self.builder,
3352 );
3353 }
3354 paragraphs::append_paragraph_line(
3355 &mut self.containers,
3356 &mut self.builder,
3357 line_to_append.unwrap_or(self.lines[self.pos]),
3358 self.config,
3359 );
3360 return LineDispatch::consumed(1);
3361 }
3362
3363 if parser_name == "reference_definition" && self.is_paragraph_open() {
3366 paragraphs::append_paragraph_line(
3367 &mut self.containers,
3368 &mut self.builder,
3369 line_to_append.unwrap_or(self.lines[self.pos]),
3370 self.config,
3371 );
3372 return LineDispatch::consumed(1);
3373 }
3374 }
3375 BlockDetectionResult::No => unreachable!(),
3376 }
3377
3378 if !matches!(block_match.detection, BlockDetectionResult::No) {
3379 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3380 self.close_containers_to_fenced_div();
3381 }
3382
3383 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3384 self.close_open_footnote_definition();
3385 }
3386
3387 let lines_consumed = {
3388 let stripped = StrippedLines::new(&self.lines, self.pos, &dispatcher_prefix);
3389 self.block_registry.parse_prepared(
3390 block_match,
3391 &dispatcher_ctx,
3392 &mut self.builder,
3393 &stripped,
3394 )
3395 };
3396
3397 let extras = match block_match.effect {
3398 BlockEffect::None => 0,
3399 BlockEffect::OpenFencedDiv => {
3400 self.containers.push(Container::FencedDiv {});
3401 0
3402 }
3403 BlockEffect::CloseFencedDiv => {
3404 self.close_fenced_div();
3405 0
3406 }
3407 BlockEffect::OpenFootnoteDefinition => {
3408 self.handle_footnote_open_effect(block_match, content)
3409 }
3410 BlockEffect::OpenList => {
3411 self.handle_list_open_effect(block_match, content, indent_to_emit)
3412 }
3413 BlockEffect::OpenDefinitionList => {
3414 self.handle_definition_list_effect(block_match, content, indent_to_emit)
3415 }
3416 BlockEffect::OpenBlockQuote => {
3417 0
3419 }
3420 };
3421
3422 if lines_consumed == 0 {
3423 log::warn!(
3424 "block parser made no progress at line {} (parser={})",
3425 self.pos + 1,
3426 self.block_registry.parser_name(block_match)
3427 );
3428 return LineDispatch::Rejected;
3429 }
3430
3431 return LineDispatch::consumed(lines_consumed + extras);
3432 }
3433 }
3434
3435 if self.config.extensions.line_blocks
3437 && (has_blank_before || self.pos == 0)
3438 && try_parse_line_block_start(content).is_some()
3439 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3443 {
3444 log::trace!("Parsed line block at line {}", self.pos);
3445 self.close_paragraph_if_open();
3447
3448 let prefix = ContainerPrefix::default();
3454 let window = StrippedLines::new(&self.lines, self.pos, &prefix);
3455 let new_pos = parse_line_block(&window, &mut self.builder, self.config);
3456 if new_pos > self.pos {
3457 return LineDispatch::consumed(new_pos - self.pos);
3458 }
3459 }
3460
3461 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3464 log::trace!(
3465 "Inside ListItem - buffering content: {:?}",
3466 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3467 );
3468 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3470
3471 if let Some(Container::ListItem {
3473 buffer,
3474 marker_only,
3475 ..
3476 }) = self.containers.stack.last_mut()
3477 {
3478 buffer.push_text(line);
3479 if !is_blank_line(line) {
3480 *marker_only = false;
3481 }
3482 }
3483
3484 return LineDispatch::consumed(1);
3485 }
3486
3487 log::trace!(
3488 "Not in ListItem - creating paragraph for: {:?}",
3489 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3490 );
3491 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3493 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3496 paragraphs::append_paragraph_line(
3497 &mut self.containers,
3498 &mut self.builder,
3499 line,
3500 self.config,
3501 );
3502 LineDispatch::consumed(1)
3503 }
3504
3505 fn fenced_div_container_index(&self) -> Option<usize> {
3506 self.containers
3507 .stack
3508 .iter()
3509 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3510 }
3511
3512 fn close_containers_to_fenced_div(&mut self) {
3513 if let Some(index) = self.fenced_div_container_index() {
3514 self.close_containers_to(index + 1);
3515 }
3516 }
3517
3518 fn close_fenced_div(&mut self) {
3519 if let Some(index) = self.fenced_div_container_index() {
3520 self.close_containers_to(index);
3521 }
3522 }
3523
3524 fn in_fenced_div(&self) -> bool {
3525 self.containers
3526 .stack
3527 .iter()
3528 .any(|c| matches!(c, Container::FencedDiv { .. }))
3529 }
3530
3531 fn in_footnote_definition(&self) -> bool {
3539 self.containers
3540 .stack
3541 .iter()
3542 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3543 }
3544}
3545
3546fn emit_definition_plain_or_heading(
3553 builder: &mut GreenNodeBuilder<'static>,
3554 text: &str,
3555 config: &ParserOptions,
3556 suppress_footnote_refs: bool,
3557) {
3558 let line_without_newline = text
3559 .strip_suffix("\r\n")
3560 .or_else(|| text.strip_suffix('\n'));
3561 if let Some(line) = line_without_newline
3562 && !line.contains('\n')
3563 && !line.contains('\r')
3564 && let Some(level) = try_parse_atx_heading(line)
3565 {
3566 emit_atx_heading(builder, text, level, config);
3567 return;
3568 }
3569
3570 if let Some(first_nl) = text.find('\n') {
3572 let first_line = &text[..first_nl];
3573 let after_first = &text[first_nl + 1..];
3574 if !after_first.is_empty()
3575 && let Some(level) = try_parse_atx_heading(first_line)
3576 {
3577 let heading_bytes = &text[..first_nl + 1];
3578 emit_atx_heading(builder, heading_bytes, level, config);
3579 builder.start_node(SyntaxKind::PLAIN.into());
3580 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3581 builder.finish_node();
3582 return;
3583 }
3584 }
3585
3586 builder.start_node(SyntaxKind::PLAIN.into());
3587 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3588 builder.finish_node();
3589}
3590
3591fn footnote_first_line_term_lookahead(
3600 lines: &[&str],
3601 pos: usize,
3602 content_col: usize,
3603 table_captions_enabled: bool,
3604) -> Option<usize> {
3605 let mut check_pos = pos + 1;
3606 let mut blank_count = 0;
3607 while check_pos < lines.len() {
3608 let line = lines[check_pos];
3609 let (trimmed, _) = strip_newline(line);
3610 if trimmed.trim().is_empty() {
3611 blank_count += 1;
3612 check_pos += 1;
3613 continue;
3614 }
3615 let (line_indent_cols, _) = leading_indent(trimmed);
3616 if line_indent_cols < content_col {
3617 return None;
3618 }
3619 let strip_bytes = byte_index_at_column(trimmed, content_col);
3620 if strip_bytes > trimmed.len() {
3621 return None;
3622 }
3623 let stripped = &trimmed[strip_bytes..];
3624 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3625 if marker == ':'
3629 && table_captions_enabled
3630 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3631 {
3632 return None;
3633 }
3634 return Some(blank_count);
3635 }
3636 return None;
3637 }
3638 None
3639}