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::definition_lists;
12use super::blocks::fenced_divs;
13use super::blocks::headings::{
14 emit_atx_heading, emit_setext_heading, emit_setext_heading_body, try_parse_atx_heading,
15 try_parse_setext_heading,
16};
17use super::blocks::horizontal_rules::try_parse_horizontal_rule;
18use super::blocks::line_blocks;
19use super::blocks::lists;
20use super::blocks::paragraphs;
21use super::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
22use super::utils::container_stack;
23use super::utils::helpers::{is_blank_line, split_lines_inclusive, strip_newline};
24use super::utils::inline_emission;
25use super::utils::marker_utils;
26use super::utils::text_buffer;
27
28use super::blocks::blockquotes::strip_n_blockquote_markers;
29use super::utils::continuation::ContinuationPolicy;
30use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
31use definition_lists::{emit_definition_marker, emit_term};
32use line_blocks::{parse_line_block, try_parse_line_block_start};
33use lists::{
34 ListItemEmissionInput, ListMarker, is_content_nested_bullet_marker, start_nested_list,
35 try_parse_list_marker,
36};
37use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
38use text_buffer::TextBuffer;
39
40const GITHUB_ALERT_MARKERS: [&str; 5] = [
41 "[!TIP]",
42 "[!WARNING]",
43 "[!IMPORTANT]",
44 "[!CAUTION]",
45 "[!NOTE]",
46];
47
48pub struct Parser<'a> {
49 lines: Vec<&'a str>,
50 pos: usize,
51 builder: GreenNodeBuilder<'static>,
52 containers: ContainerStack,
53 config: &'a ParserOptions,
54 block_registry: BlockParserRegistry,
55 after_metadata_block: bool,
59}
60
61impl<'a> Parser<'a> {
62 pub fn new(input: &'a str, config: &'a ParserOptions) -> Self {
63 let lines = split_lines_inclusive(input);
65 Self {
66 lines,
67 pos: 0,
68 builder: GreenNodeBuilder::new(),
69 containers: ContainerStack::new(),
70 config,
71 block_registry: BlockParserRegistry::new(),
72 after_metadata_block: false,
73 }
74 }
75
76 pub fn parse(mut self) -> SyntaxNode {
77 self.parse_document_stack();
78
79 SyntaxNode::new_root(self.builder.finish())
80 }
81
82 fn close_lists_above_indent(&mut self, indent_cols: usize) {
93 while let Some(Container::ListItem { content_col, .. }) = self.containers.last() {
94 if indent_cols >= *content_col {
95 break;
96 }
97 self.close_containers_to(self.containers.depth() - 1);
98 if matches!(self.containers.last(), Some(Container::List { .. })) {
99 self.close_containers_to(self.containers.depth() - 1);
100 }
101 }
102 }
103
104 fn close_containers_to(&mut self, keep: usize) {
107 while self.containers.depth() > keep {
109 match self.containers.stack.last() {
110 Some(Container::ListItem {
112 buffer,
113 content_col,
114 ..
115 }) if !buffer.is_empty() => {
116 let buffer_clone = buffer.clone();
118 let item_content_col = *content_col;
119
120 log::trace!(
121 "Closing ListItem with buffer (is_empty={}, segment_count={})",
122 buffer_clone.is_empty(),
123 buffer_clone.segment_count()
124 );
125
126 let parent_list_is_loose = self
130 .containers
131 .stack
132 .iter()
133 .rev()
134 .find_map(|c| match c {
135 Container::List {
136 has_blank_between_items,
137 ..
138 } => Some(*has_blank_between_items),
139 _ => None,
140 })
141 .unwrap_or(false);
142
143 let use_paragraph =
144 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
145
146 log::trace!(
147 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
148 use_paragraph,
149 parent_list_is_loose,
150 buffer_clone.has_blank_lines_between_content()
151 );
152
153 let suppress_footnote_refs = self.in_footnote_definition();
154 self.containers.stack.pop();
156 buffer_clone.emit_as_block(
158 &mut self.builder,
159 use_paragraph,
160 self.config,
161 item_content_col,
162 suppress_footnote_refs,
163 );
164 self.builder.finish_node(); }
166 Some(Container::ListItem { .. }) => {
168 log::trace!("Closing empty ListItem (no buffer content)");
169 self.containers.stack.pop();
171 self.builder.finish_node();
172 }
173 Some(Container::Paragraph {
175 buffer,
176 start_checkpoint,
177 ..
178 }) if !buffer.is_empty() => {
179 let buffer_clone = buffer.clone();
181 let checkpoint = *start_checkpoint;
182 let suppress_footnote_refs = self.in_footnote_definition();
183 self.containers.stack.pop();
185 self.builder
187 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
188 buffer_clone.emit_with_inlines(
189 &mut self.builder,
190 self.config,
191 suppress_footnote_refs,
192 );
193 self.builder.finish_node();
194 }
195 Some(Container::Paragraph {
197 start_checkpoint, ..
198 }) => {
199 let checkpoint = *start_checkpoint;
200 self.containers.stack.pop();
202 self.builder
203 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
204 self.builder.finish_node();
205 }
206 Some(Container::Definition {
208 plain_open: true,
209 plain_buffer,
210 ..
211 }) if !plain_buffer.is_empty() => {
212 let text = plain_buffer.get_accumulated_text();
213 let suppress_footnote_refs = self.in_footnote_definition();
214 emit_definition_plain_or_heading(
215 &mut self.builder,
216 &text,
217 self.config,
218 suppress_footnote_refs,
219 );
220
221 if let Some(Container::Definition {
223 plain_open,
224 plain_buffer,
225 ..
226 }) = self.containers.stack.last_mut()
227 {
228 plain_buffer.clear();
229 *plain_open = false;
230 }
231
232 self.containers.stack.pop();
234 self.builder.finish_node();
235 }
236 Some(Container::Definition {
238 plain_open: true, ..
239 }) => {
240 if let Some(Container::Definition {
242 plain_open,
243 plain_buffer,
244 ..
245 }) = self.containers.stack.last_mut()
246 {
247 plain_buffer.clear();
248 *plain_open = false;
249 }
250
251 self.containers.stack.pop();
253 self.builder.finish_node();
254 }
255 _ => {
257 self.containers.stack.pop();
258 self.builder.finish_node();
259 }
260 }
261 }
262 }
263
264 fn emit_buffered_plain_if_needed(&mut self) {
267 if let Some(Container::Definition {
269 plain_open: true,
270 plain_buffer,
271 ..
272 }) = self.containers.stack.last()
273 && !plain_buffer.is_empty()
274 {
275 let text = plain_buffer.get_accumulated_text();
276 let suppress_footnote_refs = self.in_footnote_definition();
277 emit_definition_plain_or_heading(
278 &mut self.builder,
279 &text,
280 self.config,
281 suppress_footnote_refs,
282 );
283 }
284
285 if let Some(Container::Definition {
287 plain_open,
288 plain_buffer,
289 ..
290 }) = self.containers.stack.last_mut()
291 && *plain_open
292 {
293 plain_buffer.clear();
294 *plain_open = false;
295 }
296 }
297
298 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
303 let mut current = self.current_blockquote_depth();
304 while current > target_depth {
305 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
306 if self.containers.depth() == 0 {
307 break;
308 }
309 self.close_containers_to(self.containers.depth() - 1);
310 }
311 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
312 self.close_containers_to(self.containers.depth() - 1);
313 current -= 1;
314 } else {
315 break;
316 }
317 }
318 }
319
320 fn active_alert_blockquote_depth(&self) -> Option<usize> {
321 self.containers.stack.iter().rev().find_map(|c| match c {
322 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
323 _ => None,
324 })
325 }
326
327 fn in_active_alert(&self) -> bool {
328 self.active_alert_blockquote_depth().is_some()
329 }
330
331 fn previous_block_requires_blank_before_heading(&self) -> bool {
332 matches!(
333 self.containers.last(),
334 Some(Container::Paragraph { .. })
335 | Some(Container::ListItem { .. })
336 | Some(Container::Definition { .. })
337 | Some(Container::DefinitionItem { .. })
338 | Some(Container::FootnoteDefinition { .. })
339 )
340 }
341
342 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
343 let (without_newline, _) = strip_newline(content);
344 let trimmed = without_newline.trim();
345 GITHUB_ALERT_MARKERS
346 .into_iter()
347 .find(|marker| *marker == trimmed)
348 }
349
350 fn emit_list_item_buffer_if_needed(&mut self) {
353 if let Some(Container::ListItem {
354 buffer,
355 content_col,
356 ..
357 }) = self.containers.stack.last_mut()
358 && !buffer.is_empty()
359 {
360 let buffer_clone = buffer.clone();
361 let item_content_col = *content_col;
362 buffer.clear();
363 let use_paragraph = buffer_clone.has_blank_lines_between_content();
364 let suppress_footnote_refs = self.in_footnote_definition();
365 buffer_clone.emit_as_block(
366 &mut self.builder,
367 use_paragraph,
368 self.config,
369 item_content_col,
370 suppress_footnote_refs,
371 );
372 }
373 }
374
375 fn maybe_open_fenced_code_in_new_list_item(&mut self) {
387 let Some(Container::ListItem {
388 content_col,
389 buffer,
390 ..
391 }) = self.containers.stack.last()
392 else {
393 return;
394 };
395 let content_col = *content_col;
396 let Some(text) = buffer.first_text() else {
397 return;
398 };
399 if buffer.segment_count() != 1 {
400 return;
401 }
402 let text_owned = text.to_string();
403 let Some(fence) = code_blocks::try_parse_fence_open(&text_owned) else {
404 return;
405 };
406 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
407 let has_info = !fence.info_string.trim().is_empty();
408 let bq_depth = self.current_blockquote_depth();
409 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
410 if !(has_info || has_matching_closer || common_mark_dialect) {
411 return;
412 }
413 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
415 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
416 {
417 return;
418 }
419 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
420 buffer.clear();
421 }
422 let new_pos = code_blocks::parse_fenced_code_block(
423 &mut self.builder,
424 &self.lines,
425 self.pos,
426 fence,
427 bq_depth,
428 content_col,
429 Some(&text_owned),
430 );
431 self.pos = new_pos.saturating_sub(1);
435 }
436
437 fn maybe_open_indented_code_in_new_list_item(&mut self) {
448 let Some(Container::ListItem {
449 content_col,
450 buffer,
451 marker_only,
452 virtual_marker_space,
453 }) = self.containers.stack.last()
454 else {
455 return;
456 };
457 if *marker_only {
458 return;
459 }
460 if buffer.segment_count() != 1 {
461 return;
462 }
463 let Some(text) = buffer.first_text() else {
464 return;
465 };
466 let content_col = *content_col;
467 let virtual_marker_space = *virtual_marker_space;
468 let text_owned = text.to_string();
469
470 let mut iter = text_owned.split_inclusive('\n');
472 let line_with_nl = iter.next().unwrap_or("").to_string();
473 if iter.next().is_some() {
474 return;
475 }
476
477 let line_no_nl = line_with_nl
478 .strip_suffix("\r\n")
479 .or_else(|| line_with_nl.strip_suffix('\n'))
480 .unwrap_or(&line_with_nl);
481 let nl_suffix = &line_with_nl[line_no_nl.len()..];
482
483 let buffer_start_col = if virtual_marker_space {
484 content_col.saturating_sub(1)
485 } else {
486 content_col
487 };
488
489 let target = content_col + 4;
490 let (cols_walked, ws_bytes) =
491 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
492
493 if buffer_start_col + cols_walked < target {
494 return;
495 }
496 if ws_bytes >= line_no_nl.len() {
497 return;
498 }
499
500 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
501 buffer.clear();
502 }
503
504 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
505 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
506 if ws_bytes > 0 {
507 self.builder
508 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
509 }
510 let rest = &line_no_nl[ws_bytes..];
511 if !rest.is_empty() {
512 self.builder.token(SyntaxKind::TEXT.into(), rest);
513 }
514 if !nl_suffix.is_empty() {
515 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
516 }
517 self.builder.finish_node();
518 self.builder.finish_node();
519 }
520
521 fn has_matching_fence_closer(
522 &self,
523 fence: &code_blocks::FenceInfo,
524 bq_depth: usize,
525 content_col: usize,
526 ) -> bool {
527 for raw_line in self.lines.iter().skip(self.pos + 1) {
528 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
529 if line_bq_depth < bq_depth {
530 break;
531 }
532 let candidate = if content_col > 0 && !inner.is_empty() {
533 let idx = byte_index_at_column(inner, content_col);
534 if idx <= inner.len() {
535 &inner[idx..]
536 } else {
537 inner
538 }
539 } else {
540 inner
541 };
542 if code_blocks::is_closing_fence(candidate, fence) {
543 return true;
544 }
545 }
546 false
547 }
548
549 fn is_paragraph_open(&self) -> bool {
551 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
552 }
553
554 fn emit_setext_heading_folding_paragraph(
562 &mut self,
563 text_line: &str,
564 underline_line: &str,
565 level: usize,
566 ) {
567 let (buffered_text, checkpoint) = match self.containers.stack.last() {
568 Some(Container::Paragraph {
569 buffer,
570 start_checkpoint,
571 ..
572 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
573 _ => (String::new(), None),
574 };
575
576 if checkpoint.is_some() {
577 self.containers.stack.pop();
578 }
579
580 let combined_text = if buffered_text.is_empty() {
581 text_line.to_string()
582 } else {
583 format!("{}{}", buffered_text, text_line)
584 };
585
586 let cp = checkpoint.expect(
587 "emit_setext_heading_folding_paragraph requires an open paragraph; \
588 single-line setext should go through the regular dispatcher path",
589 );
590 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
591 emit_setext_heading_body(
592 &mut self.builder,
593 &combined_text,
594 underline_line,
595 level,
596 self.config,
597 );
598 self.builder.finish_node();
599 }
600
601 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> bool {
619 let Some(Container::ListItem {
620 buffer,
621 content_col,
622 ..
623 }) = self.containers.stack.last()
624 else {
625 return false;
626 };
627 if buffer.segment_count() != 1 {
628 return false;
629 }
630 let Some(text_line) = buffer.first_text() else {
631 return false;
632 };
633
634 let content_col = *content_col;
639 let (underline_indent_cols, _) = leading_indent(content);
640 if underline_indent_cols < content_col {
641 return false;
642 }
643
644 let lines = [text_line, content];
645 let Some((level, _)) = try_parse_setext_heading(&lines, 0) else {
646 return false;
647 };
648
649 let (text_no_newline, _) = strip_newline(text_line);
650 if text_no_newline.trim().is_empty() {
651 return false;
652 }
653 if try_parse_horizontal_rule(text_no_newline).is_some() {
654 return false;
655 }
656
657 let text_owned = text_line.to_string();
658 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
659 buffer.clear();
660 }
661 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
662 self.pos += 1;
663 true
664 }
665
666 fn close_paragraph_if_open(&mut self) {
668 if self.is_paragraph_open() {
669 self.close_containers_to(self.containers.depth() - 1);
670 }
671 }
672
673 fn close_paragraph_as_plain_if_open(&mut self) {
684 if !self.is_paragraph_open() {
685 return;
686 }
687 let Some(Container::Paragraph {
688 buffer,
689 start_checkpoint,
690 ..
691 }) = self.containers.stack.last()
692 else {
693 return;
694 };
695 let buffer_clone = buffer.clone();
696 let checkpoint = *start_checkpoint;
697 let suppress_footnote_refs = self.in_footnote_definition();
698 self.containers.stack.pop();
699 self.builder
700 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
701 if !buffer_clone.is_empty() {
702 buffer_clone.emit_with_inlines(&mut self.builder, self.config, suppress_footnote_refs);
703 }
704 self.builder.finish_node();
705 }
706
707 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
716 if self.config.dialect != crate::options::Dialect::Pandoc {
717 return false;
718 }
719 if self.block_registry.parser_name(block_match) != "html_block" {
720 return false;
721 }
722 let html_block_type = block_match
723 .payload
724 .as_ref()
725 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
726 matches!(
727 html_block_type,
728 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
729 )
730 }
731
732 fn prepare_for_block_element(&mut self) {
735 self.emit_list_item_buffer_if_needed();
736 self.close_paragraph_if_open();
737 }
738
739 fn close_open_footnote_definition(&mut self) {
743 while matches!(
744 self.containers.last(),
745 Some(Container::FootnoteDefinition { .. })
746 ) {
747 self.close_containers_to(self.containers.depth() - 1);
748 }
749 }
750
751 fn handle_footnote_open_effect(
752 &mut self,
753 block_match: &super::block_dispatcher::PreparedBlockMatch,
754 content: &str,
755 ) {
756 let content_start = block_match
757 .payload
758 .as_ref()
759 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
760 .map(|p| p.content_start)
761 .unwrap_or(0);
762
763 let content_col = 4;
764 self.containers
765 .push(Container::FootnoteDefinition { content_col });
766
767 if content_start == 0 {
768 return;
769 }
770 let first_line_content = &content[content_start..];
771 if first_line_content.trim().is_empty() {
772 let (_, newline_str) = strip_newline(content);
773 if !newline_str.is_empty() {
774 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
775 }
776 return;
777 }
778
779 if self.config.extensions.definition_lists
780 && let Some(blank_count) = footnote_first_line_term_lookahead(
781 &self.lines,
782 self.pos,
783 content_col,
784 self.config.extensions.table_captions,
785 )
786 {
787 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
788 self.containers.push(Container::DefinitionList {});
789 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
790 self.containers.push(Container::DefinitionItem {});
791 emit_term(&mut self.builder, first_line_content, self.config);
792 for i in 0..blank_count {
793 let blank_pos = self.pos + 1 + i;
794 if blank_pos < self.lines.len() {
795 let blank_line = self.lines[blank_pos];
796 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
797 self.builder
798 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
799 self.builder.finish_node();
800 }
801 }
802 self.pos += blank_count;
803 return;
804 }
805
806 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
807 paragraphs::append_paragraph_line(
808 &mut self.containers,
809 &mut self.builder,
810 first_line_content,
811 self.config,
812 );
813 }
814
815 fn try_lazy_list_continuation(
827 &mut self,
828 block_match: &super::block_dispatcher::PreparedBlockMatch,
829 content: &str,
830 ) -> bool {
831 use super::block_dispatcher::ListPrepared;
832
833 let Some(prepared) = block_match
834 .payload
835 .as_ref()
836 .and_then(|p| p.downcast_ref::<ListPrepared>())
837 else {
838 return false;
839 };
840
841 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
842 return false;
843 }
844
845 let current_content_col = paragraphs::current_content_col(&self.containers);
846 if prepared.indent_cols >= current_content_col {
847 return false;
848 }
849
850 if lists::find_matching_list_level(
851 &self.containers,
852 &prepared.marker,
853 prepared.indent_cols,
854 self.config.dialect,
855 )
856 .is_some()
857 {
858 return false;
859 }
860
861 match self.containers.last() {
862 Some(Container::Paragraph { .. }) => {
863 paragraphs::append_paragraph_line(
864 &mut self.containers,
865 &mut self.builder,
866 content,
867 self.config,
868 );
869 true
870 }
871 Some(Container::ListItem { .. }) => {
872 if let Some(Container::ListItem {
873 buffer,
874 marker_only,
875 ..
876 }) = self.containers.stack.last_mut()
877 {
878 buffer.push_text(content);
879 if !content.trim().is_empty() {
880 *marker_only = false;
881 }
882 }
883 true
884 }
885 _ => false,
886 }
887 }
888
889 fn handle_list_open_effect(
890 &mut self,
891 block_match: &super::block_dispatcher::PreparedBlockMatch,
892 content: &str,
893 indent_to_emit: Option<&str>,
894 ) {
895 use super::block_dispatcher::ListPrepared;
896
897 let prepared = block_match
898 .payload
899 .as_ref()
900 .and_then(|p| p.downcast_ref::<ListPrepared>());
901 let Some(prepared) = prepared else {
902 return;
903 };
904
905 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
906 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
907 paragraphs::append_paragraph_line(
908 &mut self.containers,
909 &mut self.builder,
910 content,
911 self.config,
912 );
913 return;
914 }
915
916 if self.is_paragraph_open() {
917 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
918 paragraphs::append_paragraph_line(
919 &mut self.containers,
920 &mut self.builder,
921 content,
922 self.config,
923 );
924 return;
925 }
926 self.close_containers_to(self.containers.depth() - 1);
927 }
928
929 if matches!(
930 self.containers.last(),
931 Some(Container::Definition {
932 plain_open: true,
933 ..
934 })
935 ) {
936 self.emit_buffered_plain_if_needed();
937 }
938
939 let matched_level = lists::find_matching_list_level(
940 &self.containers,
941 &prepared.marker,
942 prepared.indent_cols,
943 self.config.dialect,
944 );
945 let list_item = ListItemEmissionInput {
946 content,
947 marker_len: prepared.marker_len,
948 spaces_after_cols: prepared.spaces_after_cols,
949 spaces_after_bytes: prepared.spaces_after,
950 indent_cols: prepared.indent_cols,
951 indent_bytes: prepared.indent_bytes,
952 virtual_marker_space: prepared.virtual_marker_space,
953 };
954 let current_content_col = paragraphs::current_content_col(&self.containers);
955 let deep_ordered_matched_level = matched_level
956 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
957 .and_then(|(level, container)| match container {
958 Container::List {
959 marker: list_marker,
960 base_indent_cols,
961 ..
962 } if matches!(
963 (&prepared.marker, list_marker),
964 (ListMarker::Ordered(_), ListMarker::Ordered(_))
965 ) && prepared.indent_cols >= 4
966 && *base_indent_cols >= 4
967 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
968 {
969 Some(level)
970 }
971 _ => None,
972 });
973
974 if deep_ordered_matched_level.is_none()
975 && current_content_col > 0
976 && prepared.indent_cols >= current_content_col
977 {
978 if let Some(level) = matched_level
979 && let Some(Container::List {
980 base_indent_cols, ..
981 }) = self.containers.stack.get(level)
982 && prepared.indent_cols == *base_indent_cols
983 {
984 let num_parent_lists = self.containers.stack[..level]
985 .iter()
986 .filter(|c| matches!(c, Container::List { .. }))
987 .count();
988
989 if num_parent_lists > 0 {
990 self.close_containers_to(level + 1);
991
992 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
993 self.close_containers_to(self.containers.depth() - 1);
994 }
995 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
996 self.close_containers_to(self.containers.depth() - 1);
997 }
998
999 if let Some(indent_str) = indent_to_emit {
1000 self.builder
1001 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1002 }
1003
1004 if let Some(nested_marker) = prepared.nested_marker {
1005 lists::add_list_item_with_nested_empty_list(
1006 &mut self.containers,
1007 &mut self.builder,
1008 &list_item,
1009 nested_marker,
1010 );
1011 } else {
1012 lists::add_list_item(
1013 &mut self.containers,
1014 &mut self.builder,
1015 &list_item,
1016 self.config,
1017 );
1018 }
1019 self.maybe_open_fenced_code_in_new_list_item();
1020 self.maybe_open_indented_code_in_new_list_item();
1021 return;
1022 }
1023 }
1024
1025 self.emit_list_item_buffer_if_needed();
1026
1027 start_nested_list(
1028 &mut self.containers,
1029 &mut self.builder,
1030 &prepared.marker,
1031 &list_item,
1032 indent_to_emit,
1033 self.config,
1034 );
1035 self.maybe_open_fenced_code_in_new_list_item();
1036 self.maybe_open_indented_code_in_new_list_item();
1037 return;
1038 }
1039
1040 if let Some(level) = matched_level {
1041 self.close_containers_to(level + 1);
1042
1043 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1044 self.close_containers_to(self.containers.depth() - 1);
1045 }
1046 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1047 self.close_containers_to(self.containers.depth() - 1);
1048 }
1049
1050 if let Some(indent_str) = indent_to_emit {
1051 self.builder
1052 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1053 }
1054
1055 if let Some(nested_marker) = prepared.nested_marker {
1056 lists::add_list_item_with_nested_empty_list(
1057 &mut self.containers,
1058 &mut self.builder,
1059 &list_item,
1060 nested_marker,
1061 );
1062 } else {
1063 lists::add_list_item(
1064 &mut self.containers,
1065 &mut self.builder,
1066 &list_item,
1067 self.config,
1068 );
1069 }
1070 self.maybe_open_fenced_code_in_new_list_item();
1071 self.maybe_open_indented_code_in_new_list_item();
1072 return;
1073 }
1074
1075 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1076 self.close_containers_to(self.containers.depth() - 1);
1077 }
1078 while matches!(
1079 self.containers.last(),
1080 Some(Container::ListItem { .. } | Container::List { .. })
1081 ) {
1082 self.close_containers_to(self.containers.depth() - 1);
1083 }
1084
1085 self.builder.start_node(SyntaxKind::LIST.into());
1086 if let Some(indent_str) = indent_to_emit {
1087 self.builder
1088 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1089 }
1090 self.containers.push(Container::List {
1091 marker: prepared.marker.clone(),
1092 base_indent_cols: prepared.indent_cols,
1093 has_blank_between_items: false,
1094 });
1095
1096 if let Some(nested_marker) = prepared.nested_marker {
1097 lists::add_list_item_with_nested_empty_list(
1098 &mut self.containers,
1099 &mut self.builder,
1100 &list_item,
1101 nested_marker,
1102 );
1103 } else {
1104 lists::add_list_item(
1105 &mut self.containers,
1106 &mut self.builder,
1107 &list_item,
1108 self.config,
1109 );
1110 }
1111 self.maybe_open_fenced_code_in_new_list_item();
1112 self.maybe_open_indented_code_in_new_list_item();
1113 }
1114
1115 fn handle_definition_list_effect(
1116 &mut self,
1117 block_match: &super::block_dispatcher::PreparedBlockMatch,
1118 content: &str,
1119 indent_to_emit: Option<&str>,
1120 ) {
1121 use super::block_dispatcher::DefinitionPrepared;
1122
1123 let prepared = block_match
1124 .payload
1125 .as_ref()
1126 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1127 let Some(prepared) = prepared else {
1128 return;
1129 };
1130
1131 match prepared {
1132 DefinitionPrepared::Definition {
1133 marker_char,
1134 indent,
1135 spaces_after,
1136 spaces_after_cols,
1137 has_content,
1138 } => {
1139 self.emit_buffered_plain_if_needed();
1140
1141 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1142 self.close_containers_to(self.containers.depth() - 1);
1143 }
1144 while matches!(self.containers.last(), Some(Container::List { .. })) {
1145 self.close_containers_to(self.containers.depth() - 1);
1146 }
1147
1148 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1149 self.close_containers_to(self.containers.depth() - 1);
1150 }
1151
1152 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1153 self.close_containers_to(self.containers.depth() - 1);
1154 }
1155
1156 if definition_lists::in_definition_list(&self.containers)
1160 && !matches!(
1161 self.containers.last(),
1162 Some(Container::DefinitionItem { .. })
1163 )
1164 {
1165 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1166 self.containers.push(Container::DefinitionItem {});
1167 }
1168
1169 if !definition_lists::in_definition_list(&self.containers) {
1170 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1171 self.containers.push(Container::DefinitionList {});
1172 }
1173
1174 if !matches!(
1175 self.containers.last(),
1176 Some(Container::DefinitionItem { .. })
1177 ) {
1178 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1179 self.containers.push(Container::DefinitionItem {});
1180 }
1181
1182 self.builder.start_node(SyntaxKind::DEFINITION.into());
1183
1184 if let Some(indent_str) = indent_to_emit {
1185 self.builder
1186 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1187 }
1188
1189 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1190 let indent_bytes = byte_index_at_column(content, *indent);
1191 if *spaces_after > 0 {
1192 let space_start = indent_bytes + 1;
1193 let space_end = space_start + *spaces_after;
1194 if space_end <= content.len() {
1195 self.builder.token(
1196 SyntaxKind::WHITESPACE.into(),
1197 &content[space_start..space_end],
1198 );
1199 }
1200 }
1201
1202 if !*has_content {
1203 let current_line = self.lines[self.pos];
1204 let (_, newline_str) = strip_newline(current_line);
1205 if !newline_str.is_empty() {
1206 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1207 }
1208 }
1209
1210 let content_col = *indent + 1 + *spaces_after_cols;
1211 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1212 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1213 let mut plain_buffer = TextBuffer::new();
1214 let mut definition_pushed = false;
1215
1216 if *has_content {
1217 let current_line = self.lines[self.pos];
1218 let (trimmed_line, _) = strip_newline(current_line);
1219
1220 let content_start = content_start_bytes.min(trimmed_line.len());
1221 let content_slice = &trimmed_line[content_start..];
1222 let content_line = ¤t_line[content_start_bytes.min(current_line.len())..];
1223
1224 let (blockquote_depth, inner_blockquote_content) =
1225 count_blockquote_markers(content_line);
1226
1227 let should_start_list_from_first_line = self
1228 .lines
1229 .get(self.pos + 1)
1230 .map(|next_line| {
1231 let (next_without_newline, _) = strip_newline(next_line);
1232 if next_without_newline.trim().is_empty() {
1233 return true;
1234 }
1235
1236 let (next_indent_cols, _) = leading_indent(next_without_newline);
1237 next_indent_cols >= content_col
1238 })
1239 .unwrap_or(true);
1240
1241 if blockquote_depth > 0 {
1242 self.containers.push(Container::Definition {
1243 content_col,
1244 plain_open: false,
1245 plain_buffer: TextBuffer::new(),
1246 });
1247 definition_pushed = true;
1248
1249 let marker_info = parse_blockquote_marker_info(content_line);
1250 for level in 0..blockquote_depth {
1251 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1252 if let Some(info) = marker_info.get(level) {
1253 blockquotes::emit_one_blockquote_marker(
1254 &mut self.builder,
1255 info.leading_spaces,
1256 info.has_trailing_space,
1257 );
1258 }
1259 self.containers.push(Container::BlockQuote {});
1260 }
1261
1262 if !inner_blockquote_content.trim().is_empty() {
1263 paragraphs::start_paragraph_if_needed(
1264 &mut self.containers,
1265 &mut self.builder,
1266 );
1267 paragraphs::append_paragraph_line(
1268 &mut self.containers,
1269 &mut self.builder,
1270 inner_blockquote_content,
1271 self.config,
1272 );
1273 }
1274 } else if let Some(marker_match) =
1275 try_parse_list_marker(content_slice, self.config)
1276 && should_start_list_from_first_line
1277 {
1278 self.containers.push(Container::Definition {
1279 content_col,
1280 plain_open: false,
1281 plain_buffer: TextBuffer::new(),
1282 });
1283 definition_pushed = true;
1284
1285 let (indent_cols, indent_bytes) = leading_indent(content_line);
1286 self.builder.start_node(SyntaxKind::LIST.into());
1287 self.containers.push(Container::List {
1288 marker: marker_match.marker.clone(),
1289 base_indent_cols: indent_cols,
1290 has_blank_between_items: false,
1291 });
1292
1293 let list_item = ListItemEmissionInput {
1294 content: content_line,
1295 marker_len: marker_match.marker_len,
1296 spaces_after_cols: marker_match.spaces_after_cols,
1297 spaces_after_bytes: marker_match.spaces_after_bytes,
1298 indent_cols,
1299 indent_bytes,
1300 virtual_marker_space: marker_match.virtual_marker_space,
1301 };
1302
1303 if let Some(nested_marker) = is_content_nested_bullet_marker(
1304 content_line,
1305 marker_match.marker_len,
1306 marker_match.spaces_after_bytes,
1307 ) {
1308 lists::add_list_item_with_nested_empty_list(
1309 &mut self.containers,
1310 &mut self.builder,
1311 &list_item,
1312 nested_marker,
1313 );
1314 } else {
1315 lists::add_list_item(
1316 &mut self.containers,
1317 &mut self.builder,
1318 &list_item,
1319 self.config,
1320 );
1321 }
1322 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
1323 self.containers.push(Container::Definition {
1324 content_col,
1325 plain_open: false,
1326 plain_buffer: TextBuffer::new(),
1327 });
1328 definition_pushed = true;
1329
1330 let bq_depth = self.current_blockquote_depth();
1331 if let Some(indent_str) = indent_to_emit {
1332 self.builder
1333 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1334 }
1335 let fence_line = current_line[content_start..].to_string();
1336 let new_pos = if self.config.extensions.tex_math_gfm
1337 && code_blocks::is_gfm_math_fence(&fence)
1338 {
1339 code_blocks::parse_fenced_math_block(
1340 &mut self.builder,
1341 &self.lines,
1342 self.pos,
1343 fence,
1344 bq_depth,
1345 content_col,
1346 Some(&fence_line),
1347 )
1348 } else {
1349 code_blocks::parse_fenced_code_block(
1350 &mut self.builder,
1351 &self.lines,
1352 self.pos,
1353 fence,
1354 bq_depth,
1355 content_col,
1356 Some(&fence_line),
1357 )
1358 };
1359 self.pos = new_pos - 1;
1360 } else {
1361 let (_, newline_str) = strip_newline(current_line);
1362 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1363 if content_without_newline.is_empty() {
1364 plain_buffer.push_line(newline_str);
1365 } else {
1366 let line_with_newline = if !newline_str.is_empty() {
1367 format!("{}{}", content_without_newline, newline_str)
1368 } else {
1369 content_without_newline.to_string()
1370 };
1371 plain_buffer.push_line(line_with_newline);
1372 }
1373 }
1374 }
1375
1376 if !definition_pushed {
1377 self.containers.push(Container::Definition {
1378 content_col,
1379 plain_open: *has_content,
1380 plain_buffer,
1381 });
1382 }
1383 }
1384 DefinitionPrepared::Term { blank_count } => {
1385 self.emit_buffered_plain_if_needed();
1386
1387 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1388 self.close_containers_to(self.containers.depth() - 1);
1389 }
1390
1391 if !definition_lists::in_definition_list(&self.containers) {
1392 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1393 self.containers.push(Container::DefinitionList {});
1394 }
1395
1396 while matches!(
1397 self.containers.last(),
1398 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1399 ) {
1400 self.close_containers_to(self.containers.depth() - 1);
1401 }
1402
1403 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1404 self.containers.push(Container::DefinitionItem {});
1405
1406 emit_term(&mut self.builder, content, self.config);
1407
1408 for i in 0..*blank_count {
1409 let blank_pos = self.pos + 1 + i;
1410 if blank_pos < self.lines.len() {
1411 let blank_line = self.lines[blank_pos];
1412 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1413 self.builder
1414 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1415 self.builder.finish_node();
1416 }
1417 }
1418 self.pos += *blank_count;
1419 }
1420 }
1421 }
1422
1423 fn blockquote_marker_info(
1425 &self,
1426 payload: Option<&BlockQuotePrepared>,
1427 line: &str,
1428 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1429 payload
1430 .map(|payload| payload.marker_info.clone())
1431 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1432 }
1433
1434 fn marker_info_for_line(
1440 &self,
1441 payload: Option<&BlockQuotePrepared>,
1442 raw_line: &str,
1443 marker_line: &str,
1444 shifted_prefix: &str,
1445 used_shifted: bool,
1446 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1447 let mut marker_info = if used_shifted {
1448 parse_blockquote_marker_info(marker_line)
1449 } else {
1450 self.blockquote_marker_info(payload, raw_line)
1451 };
1452 if used_shifted && !shifted_prefix.is_empty() {
1453 let (prefix_cols, _) = leading_indent(shifted_prefix);
1454 if let Some(first) = marker_info.first_mut() {
1455 first.leading_spaces += prefix_cols;
1456 }
1457 }
1458 marker_info
1459 }
1460
1461 fn shifted_blockquote_from_list<'b>(
1464 &self,
1465 line: &'b str,
1466 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1467 let list_content_col = paragraphs::current_content_col(&self.containers);
1468 let content_container_indent = self.content_container_indent_to_strip();
1469 let marker_col = list_content_col.saturating_add(content_container_indent);
1470 if marker_col == 0 {
1471 return None;
1472 }
1473
1474 let (indent_cols, _) = leading_indent(line);
1475 if indent_cols < marker_col {
1476 return None;
1477 }
1478
1479 let idx = byte_index_at_column(line, marker_col);
1480 if idx > line.len() {
1481 return None;
1482 }
1483
1484 let candidate = &line[idx..];
1485 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1486 if candidate_depth == 0 {
1487 return None;
1488 }
1489
1490 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1491 }
1492
1493 fn emit_blockquote_markers(
1494 &mut self,
1495 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1496 depth: usize,
1497 ) {
1498 for i in 0..depth {
1499 if let Some(info) = marker_info.get(i) {
1500 blockquotes::emit_one_blockquote_marker(
1501 &mut self.builder,
1502 info.leading_spaces,
1503 info.has_trailing_space,
1504 );
1505 }
1506 }
1507 }
1508
1509 fn current_blockquote_depth(&self) -> usize {
1510 blockquotes::current_blockquote_depth(&self.containers)
1511 }
1512
1513 fn list_item_unclosed_html_block_tag(&self) -> Option<String> {
1521 let Container::ListItem { buffer, .. } = self.containers.stack.last()? else {
1522 return None;
1523 };
1524 buffer.unclosed_pandoc_matched_pair_tag(self.config)
1525 }
1526
1527 fn emit_or_buffer_blockquote_marker(
1532 &mut self,
1533 leading_spaces: usize,
1534 has_trailing_space: bool,
1535 ) {
1536 if let Some(Container::ListItem {
1537 buffer,
1538 marker_only,
1539 ..
1540 }) = self.containers.stack.last_mut()
1541 {
1542 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1543 *marker_only = false;
1544 return;
1545 }
1546
1547 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1549 paragraphs::append_paragraph_marker(
1551 &mut self.containers,
1552 leading_spaces,
1553 has_trailing_space,
1554 );
1555 } else {
1556 blockquotes::emit_one_blockquote_marker(
1558 &mut self.builder,
1559 leading_spaces,
1560 has_trailing_space,
1561 );
1562 }
1563 }
1564
1565 fn parse_document_stack(&mut self) {
1566 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1567
1568 log::trace!("Starting document parse");
1569
1570 while self.pos < self.lines.len() {
1573 let line = self.lines[self.pos];
1574
1575 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1576
1577 if self.parse_line(line) {
1578 continue;
1579 }
1580 self.pos += 1;
1581 }
1582
1583 self.close_containers_to(0);
1584 self.builder.finish_node(); }
1586
1587 fn parse_line(&mut self, line: &str) -> bool {
1589 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1592 let mut bq_marker_line = line;
1593 let mut shifted_bq_prefix = "";
1594 let mut used_shifted_bq = false;
1595 if bq_depth == 0
1596 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1597 self.shifted_blockquote_from_list(line)
1598 {
1599 bq_depth = candidate_depth;
1600 inner_content = candidate_inner;
1601 bq_marker_line = candidate_line;
1602 shifted_bq_prefix = candidate_prefix;
1603 used_shifted_bq = true;
1604 }
1605 let current_bq_depth = self.current_blockquote_depth();
1606
1607 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1608 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1609 let dispatcher_ctx = if current_bq_depth == 0 {
1610 Some(BlockContext {
1611 content: line,
1612 has_blank_before,
1613 has_blank_before_strict: has_blank_before,
1614 at_document_start: self.pos == 0,
1615 in_fenced_div: self.in_fenced_div(),
1616 blockquote_depth: current_bq_depth,
1617 config: self.config,
1618 content_indent: 0,
1619 indent_to_emit: None,
1620 list_indent_info: None,
1621 in_list: lists::in_list(&self.containers),
1622 in_marker_only_list_item: matches!(
1623 self.containers.last(),
1624 Some(Container::ListItem {
1625 marker_only: true,
1626 ..
1627 })
1628 ),
1629 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
1630 paragraph_open: self.is_paragraph_open(),
1631 next_line: if self.pos + 1 < self.lines.len() {
1632 Some(self.lines[self.pos + 1])
1633 } else {
1634 None
1635 },
1636 })
1637 } else {
1638 None
1639 };
1640
1641 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1642 self.block_registry
1643 .detect_prepared(dispatcher_ctx, &self.lines, self.pos)
1644 .and_then(|prepared| {
1645 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1646 blockquote_match = Some(prepared);
1647 blockquote_match.as_ref().and_then(|prepared| {
1648 prepared
1649 .payload
1650 .as_ref()
1651 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1652 .cloned()
1653 })
1654 } else {
1655 None
1656 }
1657 })
1658 } else {
1659 None
1660 };
1661
1662 log::trace!(
1663 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1664 self.pos,
1665 bq_depth,
1666 current_bq_depth,
1667 self.containers.depth(),
1668 line.trim_end()
1669 );
1670
1671 let inner_blank_in_blockquote = bq_depth > 0
1678 && is_blank_line(inner_content)
1679 && (current_bq_depth > 0
1680 || !self.config.extensions.blank_before_blockquote
1681 || blockquotes::can_start_blockquote(self.pos, &self.lines));
1682 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1683
1684 if is_blank {
1685 if self.is_paragraph_open()
1686 && paragraphs::has_open_inline_math_environment(&self.containers)
1687 {
1688 paragraphs::append_paragraph_line(
1689 &mut self.containers,
1690 &mut self.builder,
1691 line,
1692 self.config,
1693 );
1694 self.pos += 1;
1695 return true;
1696 }
1697
1698 self.close_paragraph_if_open();
1700
1701 self.emit_buffered_plain_if_needed();
1705
1706 if bq_depth > current_bq_depth {
1714 for _ in current_bq_depth..bq_depth {
1716 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1717 self.containers.push(Container::BlockQuote {});
1718 }
1719 } else if bq_depth < current_bq_depth {
1720 self.close_blockquotes_to_depth(bq_depth);
1722 }
1723
1724 let mut peek = self.pos + 1;
1731 while peek < self.lines.len() {
1732 let peek_line = self.lines[peek];
1733 if is_blank_line(peek_line) {
1734 peek += 1;
1735 continue;
1736 }
1737 if bq_depth > 0 {
1738 let (peek_bq, _) = count_blockquote_markers(peek_line);
1739 if peek_bq >= bq_depth {
1740 let peek_inner =
1741 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1742 if is_blank_line(peek_inner) {
1743 peek += 1;
1744 continue;
1745 }
1746 }
1747 }
1748 break;
1749 }
1750
1751 let levels_to_keep = if peek < self.lines.len() {
1753 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1754 self.current_blockquote_depth(),
1755 &self.containers,
1756 &self.lines,
1757 peek,
1758 self.lines[peek],
1759 )
1760 } else {
1761 0
1762 };
1763 log::trace!(
1764 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1765 self.containers.depth(),
1766 levels_to_keep,
1767 if peek < self.lines.len() {
1768 self.lines[peek]
1769 } else {
1770 "<EOF>"
1771 }
1772 );
1773
1774 while self.containers.depth() > levels_to_keep {
1778 match self.containers.last() {
1779 Some(Container::ListItem { .. }) => {
1780 log::trace!(
1782 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1783 levels_to_keep,
1784 self.containers.depth()
1785 );
1786 self.close_containers_to(self.containers.depth() - 1);
1787 }
1788 Some(Container::List { .. })
1789 | Some(Container::FootnoteDefinition { .. })
1790 | Some(Container::Alert { .. })
1791 | Some(Container::Paragraph { .. })
1792 | Some(Container::Definition { .. })
1793 | Some(Container::DefinitionItem { .. })
1794 | Some(Container::DefinitionList { .. }) => {
1795 log::trace!(
1796 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1797 self.containers.last(),
1798 self.containers.depth(),
1799 levels_to_keep
1800 );
1801
1802 self.close_containers_to(self.containers.depth() - 1);
1803 }
1804 _ => break,
1805 }
1806 }
1807
1808 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1812 self.emit_list_item_buffer_if_needed();
1813 }
1814
1815 if bq_depth > 0 {
1817 let marker_info = self.marker_info_for_line(
1818 blockquote_payload.as_ref(),
1819 line,
1820 bq_marker_line,
1821 shifted_bq_prefix,
1822 used_shifted_bq,
1823 );
1824 self.emit_blockquote_markers(&marker_info, bq_depth);
1825 }
1826
1827 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1828 self.builder
1829 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1830 self.builder.finish_node();
1831
1832 self.pos += 1;
1833 return true;
1834 }
1835
1836 if bq_depth > current_bq_depth {
1838 if self.config.extensions.blank_before_blockquote
1841 && current_bq_depth == 0
1842 && !used_shifted_bq
1843 && !blockquote_payload
1844 .as_ref()
1845 .map(|payload| payload.can_start)
1846 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1847 {
1848 self.emit_list_item_buffer_if_needed();
1852 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1853 paragraphs::append_paragraph_line(
1854 &mut self.containers,
1855 &mut self.builder,
1856 line,
1857 self.config,
1858 );
1859 self.pos += 1;
1860 return true;
1861 }
1862
1863 let can_nest = if current_bq_depth > 0 {
1866 if self.config.extensions.blank_before_blockquote {
1867 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1869 || (self.pos > 0 && {
1870 let prev_line = self.lines[self.pos - 1];
1871 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1872 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
1873 })
1874 } else {
1875 true
1876 }
1877 } else {
1878 blockquote_payload
1879 .as_ref()
1880 .map(|payload| payload.can_nest)
1881 .unwrap_or(true)
1882 };
1883
1884 if !can_nest {
1885 let content_at_current_depth =
1888 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
1889
1890 let marker_info = self.marker_info_for_line(
1892 blockquote_payload.as_ref(),
1893 line,
1894 bq_marker_line,
1895 shifted_bq_prefix,
1896 used_shifted_bq,
1897 );
1898 for i in 0..current_bq_depth {
1899 if let Some(info) = marker_info.get(i) {
1900 self.emit_or_buffer_blockquote_marker(
1901 info.leading_spaces,
1902 info.has_trailing_space,
1903 );
1904 }
1905 }
1906
1907 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1908 paragraphs::append_paragraph_line(
1910 &mut self.containers,
1911 &mut self.builder,
1912 content_at_current_depth,
1913 self.config,
1914 );
1915 self.pos += 1;
1916 return true;
1917 } else {
1918 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1920 paragraphs::append_paragraph_line(
1921 &mut self.containers,
1922 &mut self.builder,
1923 content_at_current_depth,
1924 self.config,
1925 );
1926 self.pos += 1;
1927 return true;
1928 }
1929 }
1930
1931 self.emit_list_item_buffer_if_needed();
1934
1935 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1937 self.close_containers_to(self.containers.depth() - 1);
1938 }
1939
1940 let marker_info = self.marker_info_for_line(
1942 blockquote_payload.as_ref(),
1943 line,
1944 bq_marker_line,
1945 shifted_bq_prefix,
1946 used_shifted_bq,
1947 );
1948
1949 if let (Some(dispatcher_ctx), Some(prepared)) =
1950 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
1951 {
1952 let _ = self.block_registry.parse_prepared(
1953 prepared,
1954 dispatcher_ctx,
1955 &mut self.builder,
1956 &self.lines,
1957 self.pos,
1958 );
1959 for _ in 0..bq_depth {
1960 self.containers.push(Container::BlockQuote {});
1961 }
1962 } else {
1963 for level in 0..current_bq_depth {
1965 if let Some(info) = marker_info.get(level) {
1966 self.emit_or_buffer_blockquote_marker(
1967 info.leading_spaces,
1968 info.has_trailing_space,
1969 );
1970 }
1971 }
1972
1973 for level in current_bq_depth..bq_depth {
1975 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1976
1977 if let Some(info) = marker_info.get(level) {
1979 blockquotes::emit_one_blockquote_marker(
1980 &mut self.builder,
1981 info.leading_spaces,
1982 info.has_trailing_space,
1983 );
1984 }
1985
1986 self.containers.push(Container::BlockQuote {});
1987 }
1988 }
1989
1990 return self.parse_inner_content(inner_content, Some(inner_content));
1993 } else if bq_depth < current_bq_depth {
1994 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2000 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2007 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2008 let interrupts_via_fence =
2009 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2010 if !interrupts_via_hr && !interrupts_via_fence {
2011 if bq_depth > 0 {
2012 let marker_info = self.marker_info_for_line(
2018 blockquote_payload.as_ref(),
2019 line,
2020 bq_marker_line,
2021 shifted_bq_prefix,
2022 used_shifted_bq,
2023 );
2024 for i in 0..bq_depth {
2025 if let Some(info) = marker_info.get(i) {
2026 paragraphs::append_paragraph_marker(
2027 &mut self.containers,
2028 info.leading_spaces,
2029 info.has_trailing_space,
2030 );
2031 }
2032 }
2033 paragraphs::append_paragraph_line(
2034 &mut self.containers,
2035 &mut self.builder,
2036 inner_content,
2037 self.config,
2038 );
2039 } else {
2040 paragraphs::append_paragraph_line(
2041 &mut self.containers,
2042 &mut self.builder,
2043 line,
2044 self.config,
2045 );
2046 }
2047 self.pos += 1;
2048 return true;
2049 }
2050 }
2051 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2059 && lists::in_blockquote_list(&self.containers)
2060 && try_parse_list_marker(line, self.config).is_none()
2061 {
2062 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2063 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2064 let interrupts_via_fence =
2065 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2066 if !interrupts_via_hr && !interrupts_via_fence {
2067 if bq_depth > 0 {
2068 let marker_info = self.marker_info_for_line(
2069 blockquote_payload.as_ref(),
2070 line,
2071 bq_marker_line,
2072 shifted_bq_prefix,
2073 used_shifted_bq,
2074 );
2075 if let Some(Container::ListItem {
2076 buffer,
2077 marker_only,
2078 ..
2079 }) = self.containers.stack.last_mut()
2080 {
2081 for i in 0..bq_depth {
2082 if let Some(info) = marker_info.get(i) {
2083 buffer.push_blockquote_marker(
2084 info.leading_spaces,
2085 info.has_trailing_space,
2086 );
2087 }
2088 }
2089 buffer.push_text(inner_content);
2090 if !inner_content.trim().is_empty() {
2091 *marker_only = false;
2092 }
2093 }
2094 } else if let Some(Container::ListItem {
2095 buffer,
2096 marker_only,
2097 ..
2098 }) = self.containers.stack.last_mut()
2099 {
2100 buffer.push_text(line);
2101 if !line.trim().is_empty() {
2102 *marker_only = false;
2103 }
2104 }
2105 self.pos += 1;
2106 return true;
2107 }
2108 }
2109 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2115 if lists::in_blockquote_list(&self.containers)
2118 && let Some(marker_match) = try_parse_list_marker(line, self.config)
2119 {
2120 let (indent_cols, indent_bytes) = leading_indent(line);
2121 if let Some(level) = lists::find_matching_list_level(
2122 &self.containers,
2123 &marker_match.marker,
2124 indent_cols,
2125 self.config.dialect,
2126 ) {
2127 self.close_containers_to(level + 1);
2130
2131 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2133 self.close_containers_to(self.containers.depth() - 1);
2134 }
2135 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2136 self.close_containers_to(self.containers.depth() - 1);
2137 }
2138
2139 if let Some(nested_marker) = is_content_nested_bullet_marker(
2141 line,
2142 marker_match.marker_len,
2143 marker_match.spaces_after_bytes,
2144 ) {
2145 let list_item = ListItemEmissionInput {
2146 content: line,
2147 marker_len: marker_match.marker_len,
2148 spaces_after_cols: marker_match.spaces_after_cols,
2149 spaces_after_bytes: marker_match.spaces_after_bytes,
2150 indent_cols,
2151 indent_bytes,
2152 virtual_marker_space: marker_match.virtual_marker_space,
2153 };
2154 lists::add_list_item_with_nested_empty_list(
2155 &mut self.containers,
2156 &mut self.builder,
2157 &list_item,
2158 nested_marker,
2159 );
2160 } else {
2161 let list_item = ListItemEmissionInput {
2162 content: line,
2163 marker_len: marker_match.marker_len,
2164 spaces_after_cols: marker_match.spaces_after_cols,
2165 spaces_after_bytes: marker_match.spaces_after_bytes,
2166 indent_cols,
2167 indent_bytes,
2168 virtual_marker_space: marker_match.virtual_marker_space,
2169 };
2170 lists::add_list_item(
2171 &mut self.containers,
2172 &mut self.builder,
2173 &list_item,
2174 self.config,
2175 );
2176 }
2177 self.pos += 1;
2178 return true;
2179 }
2180 }
2181 }
2182
2183 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2185 self.close_containers_to(self.containers.depth() - 1);
2186 }
2187
2188 self.close_blockquotes_to_depth(bq_depth);
2190
2191 if bq_depth > 0 {
2193 let marker_info = self.marker_info_for_line(
2195 blockquote_payload.as_ref(),
2196 line,
2197 bq_marker_line,
2198 shifted_bq_prefix,
2199 used_shifted_bq,
2200 );
2201 for i in 0..bq_depth {
2202 if let Some(info) = marker_info.get(i) {
2203 self.emit_or_buffer_blockquote_marker(
2204 info.leading_spaces,
2205 info.has_trailing_space,
2206 );
2207 }
2208 }
2209 return self.parse_inner_content(inner_content, Some(inner_content));
2211 } else {
2212 return self.parse_inner_content(line, None);
2214 }
2215 } else if bq_depth > 0 {
2216 let mut list_item_continuation = false;
2218 let same_depth_marker_info = self.marker_info_for_line(
2219 blockquote_payload.as_ref(),
2220 line,
2221 bq_marker_line,
2222 shifted_bq_prefix,
2223 used_shifted_bq,
2224 );
2225 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2226
2227 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2239 if let Some(marker_match) = try_parse_list_marker(inner_content, self.config) {
2240 let inner_content_threshold =
2244 marker_match.marker_len + marker_match.spaces_after_cols;
2245 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2246 let sibling_list_level = if is_sibling_candidate {
2247 self.containers
2248 .stack
2249 .iter()
2250 .enumerate()
2251 .rev()
2252 .find_map(|(i, c)| match c {
2253 Container::List { marker, .. }
2254 if lists::markers_match(
2255 &marker_match.marker,
2256 marker,
2257 self.config.dialect,
2258 ) && self.containers.stack[..i]
2259 .iter()
2260 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2261 .count()
2262 == bq_depth =>
2263 {
2264 Some(i)
2265 }
2266 _ => None,
2267 })
2268 } else {
2269 None
2270 };
2271 if let Some(list_level) = sibling_list_level {
2272 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2278 Some(Container::List {
2279 base_indent_cols, ..
2280 }) => *base_indent_cols,
2281 _ => 0,
2282 };
2283
2284 self.emit_list_item_buffer_if_needed();
2286 self.close_containers_to(list_level + 1);
2289
2290 for i in 0..bq_depth {
2294 if let Some(info) = same_depth_marker_info.get(i) {
2295 self.emit_or_buffer_blockquote_marker(
2296 info.leading_spaces,
2297 info.has_trailing_space,
2298 );
2299 }
2300 }
2301
2302 let list_item = ListItemEmissionInput {
2304 content: inner_content,
2305 marker_len: marker_match.marker_len,
2306 spaces_after_cols: marker_match.spaces_after_cols,
2307 spaces_after_bytes: marker_match.spaces_after_bytes,
2308 indent_cols: sibling_base_indent_cols,
2309 indent_bytes: inner_indent_bytes,
2310 virtual_marker_space: marker_match.virtual_marker_space,
2311 };
2312 lists::add_list_item(
2313 &mut self.containers,
2314 &mut self.builder,
2315 &list_item,
2316 self.config,
2317 );
2318 self.maybe_open_fenced_code_in_new_list_item();
2319 self.maybe_open_indented_code_in_new_list_item();
2320 self.pos += 1;
2321 return true;
2322 }
2323 }
2324
2325 if matches!(
2328 self.containers.last(),
2329 Some(Container::ListItem { content_col: _, .. })
2330 ) {
2331 let (indent_cols, _) = leading_indent(inner_content);
2332 let content_indent = self.content_container_indent_to_strip();
2333 let effective_indent = indent_cols.saturating_sub(content_indent);
2334 let content_col = match self.containers.last() {
2335 Some(Container::ListItem { content_col, .. }) => *content_col,
2336 _ => 0,
2337 };
2338
2339 let is_new_item_at_outer_level =
2341 if try_parse_list_marker(inner_content, self.config).is_some() {
2342 effective_indent < content_col
2343 } else {
2344 false
2345 };
2346
2347 if is_new_item_at_outer_level
2351 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2352 {
2353 log::trace!(
2354 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2355 is_new_item_at_outer_level,
2356 effective_indent,
2357 content_col
2358 );
2359 self.close_containers_to(self.containers.depth() - 1);
2360 } else {
2361 log::trace!(
2362 "Keeping ListItem: effective_indent={} >= content_col={}",
2363 effective_indent,
2364 content_col
2365 );
2366 list_item_continuation = true;
2367 }
2368 }
2369
2370 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
2374 {
2375 list_item_continuation = false;
2376 }
2377
2378 let continuation_has_explicit_marker = list_item_continuation && {
2379 if has_explicit_same_depth_marker {
2380 for i in 0..bq_depth {
2381 if let Some(info) = same_depth_marker_info.get(i) {
2382 self.emit_or_buffer_blockquote_marker(
2383 info.leading_spaces,
2384 info.has_trailing_space,
2385 );
2386 }
2387 }
2388 true
2389 } else {
2390 false
2391 }
2392 };
2393
2394 if !list_item_continuation {
2395 let marker_info = self.marker_info_for_line(
2396 blockquote_payload.as_ref(),
2397 line,
2398 bq_marker_line,
2399 shifted_bq_prefix,
2400 used_shifted_bq,
2401 );
2402 for i in 0..bq_depth {
2403 if let Some(info) = marker_info.get(i) {
2404 self.emit_or_buffer_blockquote_marker(
2405 info.leading_spaces,
2406 info.has_trailing_space,
2407 );
2408 }
2409 }
2410 }
2411 let line_to_append = if list_item_continuation {
2412 if continuation_has_explicit_marker {
2413 Some(inner_content)
2414 } else {
2415 Some(line)
2416 }
2417 } else {
2418 Some(inner_content)
2419 };
2420 return self.parse_inner_content(inner_content, line_to_append);
2421 }
2422
2423 if current_bq_depth > 0 {
2426 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2428 paragraphs::append_paragraph_line(
2429 &mut self.containers,
2430 &mut self.builder,
2431 line,
2432 self.config,
2433 );
2434 self.pos += 1;
2435 return true;
2436 }
2437
2438 if lists::in_blockquote_list(&self.containers)
2440 && let Some(marker_match) = try_parse_list_marker(line, self.config)
2441 {
2442 let (indent_cols, indent_bytes) = leading_indent(line);
2443 if let Some(level) = lists::find_matching_list_level(
2444 &self.containers,
2445 &marker_match.marker,
2446 indent_cols,
2447 self.config.dialect,
2448 ) {
2449 self.close_containers_to(level + 1);
2451
2452 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2454 self.close_containers_to(self.containers.depth() - 1);
2455 }
2456 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2457 self.close_containers_to(self.containers.depth() - 1);
2458 }
2459
2460 if let Some(nested_marker) = is_content_nested_bullet_marker(
2462 line,
2463 marker_match.marker_len,
2464 marker_match.spaces_after_bytes,
2465 ) {
2466 let list_item = ListItemEmissionInput {
2467 content: line,
2468 marker_len: marker_match.marker_len,
2469 spaces_after_cols: marker_match.spaces_after_cols,
2470 spaces_after_bytes: marker_match.spaces_after_bytes,
2471 indent_cols,
2472 indent_bytes,
2473 virtual_marker_space: marker_match.virtual_marker_space,
2474 };
2475 lists::add_list_item_with_nested_empty_list(
2476 &mut self.containers,
2477 &mut self.builder,
2478 &list_item,
2479 nested_marker,
2480 );
2481 } else {
2482 let list_item = ListItemEmissionInput {
2483 content: line,
2484 marker_len: marker_match.marker_len,
2485 spaces_after_cols: marker_match.spaces_after_cols,
2486 spaces_after_bytes: marker_match.spaces_after_bytes,
2487 indent_cols,
2488 indent_bytes,
2489 virtual_marker_space: marker_match.virtual_marker_space,
2490 };
2491 lists::add_list_item(
2492 &mut self.containers,
2493 &mut self.builder,
2494 &list_item,
2495 self.config,
2496 );
2497 }
2498 self.pos += 1;
2499 return true;
2500 }
2501 }
2502 }
2503
2504 self.parse_inner_content(line, None)
2506 }
2507
2508 fn content_container_indent_to_strip(&self) -> usize {
2510 self.containers
2511 .stack
2512 .iter()
2513 .filter_map(|c| match c {
2514 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2515 Container::Definition { content_col, .. } => Some(*content_col),
2516 _ => None,
2517 })
2518 .sum()
2519 }
2520
2521 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> bool {
2527 log::trace!(
2528 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2529 self.pos,
2530 self.containers.depth(),
2531 self.containers.last(),
2532 content.trim_end()
2533 );
2534 let content_indent = self.content_container_indent_to_strip();
2537 let (stripped_content, indent_to_emit) = if content_indent > 0 {
2538 let (indent_cols, _) = leading_indent(content);
2539 if indent_cols >= content_indent {
2540 let idx = byte_index_at_column(content, content_indent);
2541 (&content[idx..], Some(&content[..idx]))
2542 } else {
2543 let trimmed_start = content.trim_start();
2545 let ws_len = content.len() - trimmed_start.len();
2546 if ws_len > 0 {
2547 (trimmed_start, Some(&content[..ws_len]))
2548 } else {
2549 (content, None)
2550 }
2551 }
2552 } else {
2553 (content, None)
2554 };
2555
2556 if self.config.extensions.alerts
2557 && self.current_blockquote_depth() > 0
2558 && !self.in_active_alert()
2559 && !self.is_paragraph_open()
2560 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2561 {
2562 let (_, newline_str) = strip_newline(stripped_content);
2563 self.builder.start_node(SyntaxKind::ALERT.into());
2564 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2565 if !newline_str.is_empty() {
2566 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2567 }
2568 self.containers.push(Container::Alert {
2569 blockquote_depth: self.current_blockquote_depth(),
2570 });
2571 self.pos += 1;
2572 return true;
2573 }
2574
2575 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2579 let is_definition_marker =
2580 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2581 && !stripped_content.starts_with(':');
2582 if content_indent == 0 && is_definition_marker {
2583 } else {
2585 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2586
2587 if policy.definition_plain_can_continue(
2588 stripped_content,
2589 content,
2590 content_indent,
2591 &BlockContext {
2592 content: stripped_content,
2593 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2594 has_blank_before_strict: self.pos == 0
2595 || is_blank_line(self.lines[self.pos - 1]),
2596 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2597 in_fenced_div: self.in_fenced_div(),
2598 blockquote_depth: self.current_blockquote_depth(),
2599 config: self.config,
2600 content_indent,
2601 indent_to_emit: None,
2602 list_indent_info: None,
2603 in_list: lists::in_list(&self.containers),
2604 in_marker_only_list_item: matches!(
2605 self.containers.last(),
2606 Some(Container::ListItem {
2607 marker_only: true,
2608 ..
2609 })
2610 ),
2611 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2612 paragraph_open: self.is_paragraph_open(),
2613 next_line: if self.pos + 1 < self.lines.len() {
2614 Some(self.lines[self.pos + 1])
2615 } else {
2616 None
2617 },
2618 },
2619 &self.lines,
2620 self.pos,
2621 ) {
2622 let content_line = stripped_content;
2623 let (text_without_newline, newline_str) = strip_newline(content_line);
2624 let indent_prefix = if !text_without_newline.trim().is_empty() {
2625 indent_to_emit.unwrap_or("")
2626 } else {
2627 ""
2628 };
2629 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2630
2631 if let Some(Container::Definition {
2632 plain_open,
2633 plain_buffer,
2634 ..
2635 }) = self.containers.stack.last_mut()
2636 {
2637 let line_with_newline = if !newline_str.is_empty() {
2638 format!("{}{}", content_line, newline_str)
2639 } else {
2640 content_line
2641 };
2642 plain_buffer.push_line(line_with_newline);
2643 *plain_open = true;
2644 }
2645
2646 self.pos += 1;
2647 return true;
2648 }
2649 }
2650 }
2651
2652 if content_indent > 0 {
2655 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2656 let current_bq_depth = self.current_blockquote_depth();
2657 let in_footnote_definition = self
2658 .containers
2659 .stack
2660 .iter()
2661 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2662
2663 if bq_depth > 0 {
2664 if in_footnote_definition
2665 && self.config.extensions.blank_before_blockquote
2666 && current_bq_depth == 0
2667 && !blockquotes::can_start_blockquote(self.pos, &self.lines)
2668 {
2669 } else {
2673 self.emit_buffered_plain_if_needed();
2676 self.emit_list_item_buffer_if_needed();
2677
2678 self.close_paragraph_if_open();
2681
2682 if bq_depth > current_bq_depth {
2683 let marker_info = parse_blockquote_marker_info(stripped_content);
2684
2685 for level in current_bq_depth..bq_depth {
2687 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2688
2689 if level == current_bq_depth
2690 && let Some(indent_str) = indent_to_emit
2691 {
2692 self.builder
2693 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2694 }
2695
2696 if let Some(info) = marker_info.get(level) {
2697 blockquotes::emit_one_blockquote_marker(
2698 &mut self.builder,
2699 info.leading_spaces,
2700 info.has_trailing_space,
2701 );
2702 }
2703
2704 self.containers.push(Container::BlockQuote {});
2705 }
2706 } else if bq_depth < current_bq_depth {
2707 self.close_blockquotes_to_depth(bq_depth);
2708 } else {
2709 let marker_info = parse_blockquote_marker_info(stripped_content);
2711 self.emit_blockquote_markers(&marker_info, bq_depth);
2712 }
2713
2714 return self.parse_inner_content(inner_content, Some(inner_content));
2715 }
2716 }
2717 }
2718
2719 let content = stripped_content;
2721
2722 if self.is_paragraph_open()
2723 && (paragraphs::has_open_inline_math_environment(&self.containers)
2724 || paragraphs::has_open_display_math_dollars(&self.containers))
2725 {
2726 paragraphs::append_paragraph_line(
2727 &mut self.containers,
2728 &mut self.builder,
2729 line_to_append.unwrap_or(self.lines[self.pos]),
2730 self.config,
2731 );
2732 self.pos += 1;
2733 return true;
2734 }
2735
2736 use super::blocks::lists;
2740 use super::blocks::paragraphs;
2741 let list_indent_info = if lists::in_list(&self.containers) {
2742 let content_col = paragraphs::current_content_col(&self.containers);
2743 if content_col > 0 {
2744 Some(super::block_dispatcher::ListIndentInfo { content_col })
2745 } else {
2746 None
2747 }
2748 } else {
2749 None
2750 };
2751
2752 let next_line = if self.pos + 1 < self.lines.len() {
2753 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2756 } else {
2757 None
2758 };
2759
2760 let current_bq_depth = self.current_blockquote_depth();
2761 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2762 && current_bq_depth < alert_bq_depth
2763 {
2764 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2765 self.close_containers_to(self.containers.depth() - 1);
2766 }
2767 }
2768
2769 let dispatcher_ctx = BlockContext {
2770 content,
2771 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2775 blockquote_depth: current_bq_depth,
2776 config: self.config,
2777 content_indent,
2778 indent_to_emit,
2779 list_indent_info,
2780 in_list: lists::in_list(&self.containers),
2781 in_marker_only_list_item: matches!(
2782 self.containers.last(),
2783 Some(Container::ListItem {
2784 marker_only: true,
2785 ..
2786 })
2787 ),
2788 list_item_unclosed_html_block_tag: self.list_item_unclosed_html_block_tag(),
2789 paragraph_open: self.is_paragraph_open(),
2790 next_line,
2791 };
2792
2793 let mut dispatcher_ctx = dispatcher_ctx;
2796
2797 if self.try_fold_list_item_buffer_into_setext(stripped_content) {
2801 return true;
2802 }
2803
2804 let dispatcher_match =
2807 self.block_registry
2808 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos);
2809
2810 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
2816 let has_blank_before = if self.pos == 0 || after_metadata_block {
2817 true
2818 } else {
2819 let prev_line = self.lines[self.pos - 1];
2820 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2821 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
2822 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
2823 && fenced_divs::try_parse_div_fence_open(
2824 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
2825 )
2826 .is_some();
2827
2828 let prev_line_blank = is_blank_line(prev_line);
2829 prev_line_blank
2830 || prev_is_fenced_div_open
2831 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2832 || !self.previous_block_requires_blank_before_heading()
2833 };
2834
2835 let at_document_start = self.pos == 0 && current_bq_depth == 0;
2838
2839 let prev_line_blank = if self.pos > 0 {
2840 let prev_line = self.lines[self.pos - 1];
2841 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2842 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
2843 } else {
2844 false
2845 };
2846 let has_blank_before_strict = at_document_start || prev_line_blank;
2847
2848 dispatcher_ctx.has_blank_before = has_blank_before;
2849 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
2850 dispatcher_ctx.at_document_start = at_document_start;
2851
2852 let dispatcher_match =
2853 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
2854 self.block_registry
2856 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos)
2857 } else {
2858 dispatcher_match
2859 };
2860
2861 if has_blank_before {
2862 if let Some(env_name) = extract_environment_name(content)
2863 && is_inline_math_environment(env_name)
2864 {
2865 if !self.is_paragraph_open() {
2866 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2867 }
2868 paragraphs::append_paragraph_line(
2869 &mut self.containers,
2870 &mut self.builder,
2871 line_to_append.unwrap_or(self.lines[self.pos]),
2872 self.config,
2873 );
2874 self.pos += 1;
2875 return true;
2876 }
2877
2878 if let Some(block_match) = dispatcher_match.as_ref() {
2879 let detection = block_match.detection;
2880
2881 match detection {
2882 BlockDetectionResult::YesCanInterrupt => {
2883 self.emit_list_item_buffer_if_needed();
2884 if self.is_paragraph_open() {
2885 self.close_containers_to(self.containers.depth() - 1);
2886 }
2887 }
2888 BlockDetectionResult::Yes => {
2889 self.prepare_for_block_element();
2890 }
2891 BlockDetectionResult::No => unreachable!(),
2892 }
2893
2894 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2895 self.close_containers_to_fenced_div();
2896 }
2897
2898 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2899 self.close_open_footnote_definition();
2900 }
2901
2902 let lines_consumed = self.block_registry.parse_prepared(
2903 block_match,
2904 &dispatcher_ctx,
2905 &mut self.builder,
2906 &self.lines,
2907 self.pos,
2908 );
2909
2910 if matches!(
2911 self.block_registry.parser_name(block_match),
2912 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
2913 ) {
2914 self.after_metadata_block = true;
2915 }
2916
2917 match block_match.effect {
2918 BlockEffect::None => {}
2919 BlockEffect::OpenFencedDiv => {
2920 self.containers.push(Container::FencedDiv {});
2921 }
2922 BlockEffect::CloseFencedDiv => {
2923 self.close_fenced_div();
2924 }
2925 BlockEffect::OpenFootnoteDefinition => {
2926 self.handle_footnote_open_effect(block_match, content);
2927 }
2928 BlockEffect::OpenList => {
2929 self.handle_list_open_effect(block_match, content, indent_to_emit);
2930 }
2931 BlockEffect::OpenDefinitionList => {
2932 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2933 }
2934 BlockEffect::OpenBlockQuote => {
2935 }
2937 }
2938
2939 if lines_consumed == 0 {
2940 log::warn!(
2941 "block parser made no progress at line {} (parser={})",
2942 self.pos + 1,
2943 self.block_registry.parser_name(block_match)
2944 );
2945 return false;
2946 }
2947
2948 self.pos += lines_consumed;
2949 return true;
2950 }
2951 } else if let Some(block_match) = dispatcher_match.as_ref() {
2952 let parser_name = self.block_registry.parser_name(block_match);
2955 match block_match.detection {
2956 BlockDetectionResult::YesCanInterrupt => {
2957 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
2958 && self.is_paragraph_open()
2959 {
2960 if !self.is_paragraph_open() {
2962 paragraphs::start_paragraph_if_needed(
2963 &mut self.containers,
2964 &mut self.builder,
2965 );
2966 }
2967 paragraphs::append_paragraph_line(
2968 &mut self.containers,
2969 &mut self.builder,
2970 line_to_append.unwrap_or(self.lines[self.pos]),
2971 self.config,
2972 );
2973 self.pos += 1;
2974 return true;
2975 }
2976
2977 if matches!(block_match.effect, BlockEffect::OpenList)
2978 && self.is_paragraph_open()
2979 && !lists::in_list(&self.containers)
2980 && self.content_container_indent_to_strip() == 0
2981 {
2982 let allow_interrupt =
2988 self.config.dialect == crate::options::Dialect::CommonMark && {
2989 use super::block_dispatcher::ListPrepared;
2990 use super::blocks::lists::OrderedMarker;
2991 let prepared = block_match
2992 .payload
2993 .as_ref()
2994 .and_then(|p| p.downcast_ref::<ListPrepared>());
2995 match prepared.map(|p| &p.marker) {
2996 Some(ListMarker::Bullet(_)) => true,
2997 Some(ListMarker::Ordered(OrderedMarker::Decimal {
2998 number,
2999 ..
3000 })) => number == "1",
3001 _ => false,
3002 }
3003 };
3004 if !allow_interrupt {
3005 paragraphs::append_paragraph_line(
3006 &mut self.containers,
3007 &mut self.builder,
3008 line_to_append.unwrap_or(self.lines[self.pos]),
3009 self.config,
3010 );
3011 self.pos += 1;
3012 return true;
3013 }
3014 }
3015
3016 if matches!(block_match.effect, BlockEffect::OpenList)
3023 && self.try_lazy_list_continuation(block_match, content)
3024 {
3025 self.pos += 1;
3026 return true;
3027 }
3028
3029 self.emit_list_item_buffer_if_needed();
3030 if self.is_paragraph_open() {
3031 if self.html_block_demotes_paragraph_to_plain(block_match) {
3032 self.close_paragraph_as_plain_if_open();
3033 } else {
3034 self.close_containers_to(self.containers.depth() - 1);
3035 }
3036 }
3037
3038 if self.config.dialect == crate::options::Dialect::CommonMark
3045 && !matches!(block_match.effect, BlockEffect::OpenList)
3046 {
3047 let (indent_cols, _) = leading_indent(content);
3048 self.close_lists_above_indent(indent_cols);
3049 }
3050 }
3051 BlockDetectionResult::Yes => {
3052 if parser_name == "setext_heading"
3064 && self.is_paragraph_open()
3065 && self.config.dialect == crate::options::Dialect::CommonMark
3066 {
3067 let text_line = self.lines[self.pos];
3068 let underline_line = self.lines[self.pos + 1];
3069 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3070 let level = if underline_char == '=' { 1 } else { 2 };
3071 self.emit_setext_heading_folding_paragraph(
3072 text_line,
3073 underline_line,
3074 level,
3075 );
3076 self.pos += 2;
3077 return true;
3078 }
3079
3080 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3083 if !self.is_paragraph_open() {
3084 paragraphs::start_paragraph_if_needed(
3085 &mut self.containers,
3086 &mut self.builder,
3087 );
3088 }
3089 paragraphs::append_paragraph_line(
3090 &mut self.containers,
3091 &mut self.builder,
3092 line_to_append.unwrap_or(self.lines[self.pos]),
3093 self.config,
3094 );
3095 self.pos += 1;
3096 return true;
3097 }
3098
3099 if parser_name == "reference_definition" && self.is_paragraph_open() {
3102 paragraphs::append_paragraph_line(
3103 &mut self.containers,
3104 &mut self.builder,
3105 line_to_append.unwrap_or(self.lines[self.pos]),
3106 self.config,
3107 );
3108 self.pos += 1;
3109 return true;
3110 }
3111 }
3112 BlockDetectionResult::No => unreachable!(),
3113 }
3114
3115 if !matches!(block_match.detection, BlockDetectionResult::No) {
3116 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3117 self.close_containers_to_fenced_div();
3118 }
3119
3120 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3121 self.close_open_footnote_definition();
3122 }
3123
3124 let lines_consumed = self.block_registry.parse_prepared(
3125 block_match,
3126 &dispatcher_ctx,
3127 &mut self.builder,
3128 &self.lines,
3129 self.pos,
3130 );
3131
3132 match block_match.effect {
3133 BlockEffect::None => {}
3134 BlockEffect::OpenFencedDiv => {
3135 self.containers.push(Container::FencedDiv {});
3136 }
3137 BlockEffect::CloseFencedDiv => {
3138 self.close_fenced_div();
3139 }
3140 BlockEffect::OpenFootnoteDefinition => {
3141 self.handle_footnote_open_effect(block_match, content);
3142 }
3143 BlockEffect::OpenList => {
3144 self.handle_list_open_effect(block_match, content, indent_to_emit);
3145 }
3146 BlockEffect::OpenDefinitionList => {
3147 self.handle_definition_list_effect(block_match, content, indent_to_emit);
3148 }
3149 BlockEffect::OpenBlockQuote => {
3150 }
3152 }
3153
3154 if lines_consumed == 0 {
3155 log::warn!(
3156 "block parser made no progress at line {} (parser={})",
3157 self.pos + 1,
3158 self.block_registry.parser_name(block_match)
3159 );
3160 return false;
3161 }
3162
3163 self.pos += lines_consumed;
3164 return true;
3165 }
3166 }
3167
3168 if self.config.extensions.line_blocks
3170 && (has_blank_before || self.pos == 0)
3171 && try_parse_line_block_start(content).is_some()
3172 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3176 {
3177 log::trace!("Parsed line block at line {}", self.pos);
3178 self.close_paragraph_if_open();
3180
3181 let new_pos = parse_line_block(&self.lines, self.pos, &mut self.builder, self.config);
3182 if new_pos > self.pos {
3183 self.pos = new_pos;
3184 return true;
3185 }
3186 }
3187
3188 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3191 log::trace!(
3192 "Inside ListItem - buffering content: {:?}",
3193 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3194 );
3195 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3197
3198 if let Some(Container::ListItem {
3200 buffer,
3201 marker_only,
3202 ..
3203 }) = self.containers.stack.last_mut()
3204 {
3205 buffer.push_text(line);
3206 if !is_blank_line(line) {
3207 *marker_only = false;
3208 }
3209 }
3210
3211 self.pos += 1;
3212 return true;
3213 }
3214
3215 log::trace!(
3216 "Not in ListItem - creating paragraph for: {:?}",
3217 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3218 );
3219 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3221 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3224 paragraphs::append_paragraph_line(
3225 &mut self.containers,
3226 &mut self.builder,
3227 line,
3228 self.config,
3229 );
3230 self.pos += 1;
3231 true
3232 }
3233
3234 fn fenced_div_container_index(&self) -> Option<usize> {
3235 self.containers
3236 .stack
3237 .iter()
3238 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3239 }
3240
3241 fn close_containers_to_fenced_div(&mut self) {
3242 if let Some(index) = self.fenced_div_container_index() {
3243 self.close_containers_to(index + 1);
3244 }
3245 }
3246
3247 fn close_fenced_div(&mut self) {
3248 if let Some(index) = self.fenced_div_container_index() {
3249 self.close_containers_to(index);
3250 }
3251 }
3252
3253 fn in_fenced_div(&self) -> bool {
3254 self.containers
3255 .stack
3256 .iter()
3257 .any(|c| matches!(c, Container::FencedDiv { .. }))
3258 }
3259
3260 fn in_footnote_definition(&self) -> bool {
3268 self.containers
3269 .stack
3270 .iter()
3271 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
3272 }
3273}
3274
3275fn emit_definition_plain_or_heading(
3282 builder: &mut GreenNodeBuilder<'static>,
3283 text: &str,
3284 config: &ParserOptions,
3285 suppress_footnote_refs: bool,
3286) {
3287 let line_without_newline = text
3288 .strip_suffix("\r\n")
3289 .or_else(|| text.strip_suffix('\n'));
3290 if let Some(line) = line_without_newline
3291 && !line.contains('\n')
3292 && !line.contains('\r')
3293 && let Some(level) = try_parse_atx_heading(line)
3294 {
3295 emit_atx_heading(builder, text, level, config);
3296 return;
3297 }
3298
3299 if let Some(first_nl) = text.find('\n') {
3301 let first_line = &text[..first_nl];
3302 let after_first = &text[first_nl + 1..];
3303 if !after_first.is_empty()
3304 && let Some(level) = try_parse_atx_heading(first_line)
3305 {
3306 let heading_bytes = &text[..first_nl + 1];
3307 emit_atx_heading(builder, heading_bytes, level, config);
3308 builder.start_node(SyntaxKind::PLAIN.into());
3309 inline_emission::emit_inlines(builder, after_first, config, suppress_footnote_refs);
3310 builder.finish_node();
3311 return;
3312 }
3313 }
3314
3315 builder.start_node(SyntaxKind::PLAIN.into());
3316 inline_emission::emit_inlines(builder, text, config, suppress_footnote_refs);
3317 builder.finish_node();
3318}
3319
3320fn footnote_first_line_term_lookahead(
3329 lines: &[&str],
3330 pos: usize,
3331 content_col: usize,
3332 table_captions_enabled: bool,
3333) -> Option<usize> {
3334 let mut check_pos = pos + 1;
3335 let mut blank_count = 0;
3336 while check_pos < lines.len() {
3337 let line = lines[check_pos];
3338 let (trimmed, _) = strip_newline(line);
3339 if trimmed.trim().is_empty() {
3340 blank_count += 1;
3341 check_pos += 1;
3342 continue;
3343 }
3344 let (line_indent_cols, _) = leading_indent(trimmed);
3345 if line_indent_cols < content_col {
3346 return None;
3347 }
3348 let strip_bytes = byte_index_at_column(trimmed, content_col);
3349 if strip_bytes > trimmed.len() {
3350 return None;
3351 }
3352 let stripped = &trimmed[strip_bytes..];
3353 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3354 if marker == ':'
3358 && table_captions_enabled
3359 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3360 {
3361 return None;
3362 }
3363 return Some(blank_count);
3364 }
3365 return None;
3366 }
3367 None
3368}