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 { buffer, .. }) if !buffer.is_empty() => {
112 let buffer_clone = buffer.clone();
114
115 log::trace!(
116 "Closing ListItem with buffer (is_empty={}, segment_count={})",
117 buffer_clone.is_empty(),
118 buffer_clone.segment_count()
119 );
120
121 let parent_list_is_loose = self
125 .containers
126 .stack
127 .iter()
128 .rev()
129 .find_map(|c| match c {
130 Container::List {
131 has_blank_between_items,
132 ..
133 } => Some(*has_blank_between_items),
134 _ => None,
135 })
136 .unwrap_or(false);
137
138 let use_paragraph =
139 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
140
141 log::trace!(
142 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
143 use_paragraph,
144 parent_list_is_loose,
145 buffer_clone.has_blank_lines_between_content()
146 );
147
148 self.containers.stack.pop();
150 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
152 self.builder.finish_node(); }
154 Some(Container::ListItem { .. }) => {
156 log::trace!("Closing empty ListItem (no buffer content)");
157 self.containers.stack.pop();
159 self.builder.finish_node();
160 }
161 Some(Container::Paragraph {
163 buffer,
164 start_checkpoint,
165 ..
166 }) if !buffer.is_empty() => {
167 let buffer_clone = buffer.clone();
169 let checkpoint = *start_checkpoint;
170 self.containers.stack.pop();
172 self.builder
174 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
175 buffer_clone.emit_with_inlines(&mut self.builder, self.config);
176 self.builder.finish_node();
177 }
178 Some(Container::Paragraph {
180 start_checkpoint, ..
181 }) => {
182 let checkpoint = *start_checkpoint;
183 self.containers.stack.pop();
185 self.builder
186 .start_node_at(checkpoint, SyntaxKind::PARAGRAPH.into());
187 self.builder.finish_node();
188 }
189 Some(Container::Definition {
191 plain_open: true,
192 plain_buffer,
193 ..
194 }) if !plain_buffer.is_empty() => {
195 let text = plain_buffer.get_accumulated_text();
196 emit_definition_plain_or_heading(&mut self.builder, &text, self.config);
197
198 if let Some(Container::Definition {
200 plain_open,
201 plain_buffer,
202 ..
203 }) = self.containers.stack.last_mut()
204 {
205 plain_buffer.clear();
206 *plain_open = false;
207 }
208
209 self.containers.stack.pop();
211 self.builder.finish_node();
212 }
213 Some(Container::Definition {
215 plain_open: true, ..
216 }) => {
217 if let Some(Container::Definition {
219 plain_open,
220 plain_buffer,
221 ..
222 }) = self.containers.stack.last_mut()
223 {
224 plain_buffer.clear();
225 *plain_open = false;
226 }
227
228 self.containers.stack.pop();
230 self.builder.finish_node();
231 }
232 _ => {
234 self.containers.stack.pop();
235 self.builder.finish_node();
236 }
237 }
238 }
239 }
240
241 fn emit_buffered_plain_if_needed(&mut self) {
244 if let Some(Container::Definition {
246 plain_open: true,
247 plain_buffer,
248 ..
249 }) = self.containers.stack.last()
250 && !plain_buffer.is_empty()
251 {
252 let text = plain_buffer.get_accumulated_text();
253 emit_definition_plain_or_heading(&mut self.builder, &text, self.config);
254 }
255
256 if let Some(Container::Definition {
258 plain_open,
259 plain_buffer,
260 ..
261 }) = self.containers.stack.last_mut()
262 && *plain_open
263 {
264 plain_buffer.clear();
265 *plain_open = false;
266 }
267 }
268
269 fn close_blockquotes_to_depth(&mut self, target_depth: usize) {
274 let mut current = self.current_blockquote_depth();
275 while current > target_depth {
276 while !matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
277 if self.containers.depth() == 0 {
278 break;
279 }
280 self.close_containers_to(self.containers.depth() - 1);
281 }
282 if matches!(self.containers.last(), Some(Container::BlockQuote { .. })) {
283 self.close_containers_to(self.containers.depth() - 1);
284 current -= 1;
285 } else {
286 break;
287 }
288 }
289 }
290
291 fn active_alert_blockquote_depth(&self) -> Option<usize> {
292 self.containers.stack.iter().rev().find_map(|c| match c {
293 Container::Alert { blockquote_depth } => Some(*blockquote_depth),
294 _ => None,
295 })
296 }
297
298 fn in_active_alert(&self) -> bool {
299 self.active_alert_blockquote_depth().is_some()
300 }
301
302 fn previous_block_requires_blank_before_heading(&self) -> bool {
303 matches!(
304 self.containers.last(),
305 Some(Container::Paragraph { .. })
306 | Some(Container::ListItem { .. })
307 | Some(Container::Definition { .. })
308 | Some(Container::DefinitionItem { .. })
309 | Some(Container::FootnoteDefinition { .. })
310 )
311 }
312
313 fn alert_marker_from_content(content: &str) -> Option<&'static str> {
314 let (without_newline, _) = strip_newline(content);
315 let trimmed = without_newline.trim();
316 GITHUB_ALERT_MARKERS
317 .into_iter()
318 .find(|marker| *marker == trimmed)
319 }
320
321 fn emit_list_item_buffer_if_needed(&mut self) {
324 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut()
325 && !buffer.is_empty()
326 {
327 let buffer_clone = buffer.clone();
328 buffer.clear();
329 let use_paragraph = buffer_clone.has_blank_lines_between_content();
330 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
331 }
332 }
333
334 fn maybe_open_fenced_code_in_new_list_item(&mut self) {
346 let Some(Container::ListItem {
347 content_col,
348 buffer,
349 ..
350 }) = self.containers.stack.last()
351 else {
352 return;
353 };
354 let content_col = *content_col;
355 let Some(text) = buffer.first_text() else {
356 return;
357 };
358 if buffer.segment_count() != 1 {
359 return;
360 }
361 let text_owned = text.to_string();
362 let Some(fence) = code_blocks::try_parse_fence_open(&text_owned) else {
363 return;
364 };
365 let common_mark_dialect = self.config.dialect == crate::options::Dialect::CommonMark;
366 let has_info = !fence.info_string.trim().is_empty();
367 let bq_depth = self.current_blockquote_depth();
368 let has_matching_closer = self.has_matching_fence_closer(&fence, bq_depth, content_col);
369 if !(has_info || has_matching_closer || common_mark_dialect) {
370 return;
371 }
372 if (fence.fence_char == '`' && !self.config.extensions.backtick_code_blocks)
374 || (fence.fence_char == '~' && !self.config.extensions.fenced_code_blocks)
375 {
376 return;
377 }
378 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
379 buffer.clear();
380 }
381 let new_pos = code_blocks::parse_fenced_code_block(
382 &mut self.builder,
383 &self.lines,
384 self.pos,
385 fence,
386 bq_depth,
387 content_col,
388 Some(&text_owned),
389 );
390 self.pos = new_pos.saturating_sub(1);
394 }
395
396 fn maybe_open_indented_code_in_new_list_item(&mut self) {
407 let Some(Container::ListItem {
408 content_col,
409 buffer,
410 marker_only,
411 virtual_marker_space,
412 }) = self.containers.stack.last()
413 else {
414 return;
415 };
416 if *marker_only {
417 return;
418 }
419 if buffer.segment_count() != 1 {
420 return;
421 }
422 let Some(text) = buffer.first_text() else {
423 return;
424 };
425 let content_col = *content_col;
426 let virtual_marker_space = *virtual_marker_space;
427 let text_owned = text.to_string();
428
429 let mut iter = text_owned.split_inclusive('\n');
431 let line_with_nl = iter.next().unwrap_or("").to_string();
432 if iter.next().is_some() {
433 return;
434 }
435
436 let line_no_nl = line_with_nl
437 .strip_suffix("\r\n")
438 .or_else(|| line_with_nl.strip_suffix('\n'))
439 .unwrap_or(&line_with_nl);
440 let nl_suffix = &line_with_nl[line_no_nl.len()..];
441
442 let buffer_start_col = if virtual_marker_space {
443 content_col.saturating_sub(1)
444 } else {
445 content_col
446 };
447
448 let target = content_col + 4;
449 let (cols_walked, ws_bytes) =
450 super::utils::container_stack::leading_indent_from(line_no_nl, buffer_start_col);
451
452 if buffer_start_col + cols_walked < target {
453 return;
454 }
455 if ws_bytes >= line_no_nl.len() {
456 return;
457 }
458
459 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
460 buffer.clear();
461 }
462
463 self.builder.start_node(SyntaxKind::CODE_BLOCK.into());
464 self.builder.start_node(SyntaxKind::CODE_CONTENT.into());
465 if ws_bytes > 0 {
466 self.builder
467 .token(SyntaxKind::WHITESPACE.into(), &line_no_nl[..ws_bytes]);
468 }
469 let rest = &line_no_nl[ws_bytes..];
470 if !rest.is_empty() {
471 self.builder.token(SyntaxKind::TEXT.into(), rest);
472 }
473 if !nl_suffix.is_empty() {
474 self.builder.token(SyntaxKind::NEWLINE.into(), nl_suffix);
475 }
476 self.builder.finish_node();
477 self.builder.finish_node();
478 }
479
480 fn has_matching_fence_closer(
481 &self,
482 fence: &code_blocks::FenceInfo,
483 bq_depth: usize,
484 content_col: usize,
485 ) -> bool {
486 for raw_line in self.lines.iter().skip(self.pos + 1) {
487 let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
488 if line_bq_depth < bq_depth {
489 break;
490 }
491 let candidate = if content_col > 0 && !inner.is_empty() {
492 let idx = byte_index_at_column(inner, content_col);
493 if idx <= inner.len() {
494 &inner[idx..]
495 } else {
496 inner
497 }
498 } else {
499 inner
500 };
501 if code_blocks::is_closing_fence(candidate, fence) {
502 return true;
503 }
504 }
505 false
506 }
507
508 fn is_paragraph_open(&self) -> bool {
510 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
511 }
512
513 fn emit_setext_heading_folding_paragraph(
521 &mut self,
522 text_line: &str,
523 underline_line: &str,
524 level: usize,
525 ) {
526 let (buffered_text, checkpoint) = match self.containers.stack.last() {
527 Some(Container::Paragraph {
528 buffer,
529 start_checkpoint,
530 ..
531 }) => (buffer.get_text_for_parsing(), Some(*start_checkpoint)),
532 _ => (String::new(), None),
533 };
534
535 if checkpoint.is_some() {
536 self.containers.stack.pop();
537 }
538
539 let combined_text = if buffered_text.is_empty() {
540 text_line.to_string()
541 } else {
542 format!("{}{}", buffered_text, text_line)
543 };
544
545 let cp = checkpoint.expect(
546 "emit_setext_heading_folding_paragraph requires an open paragraph; \
547 single-line setext should go through the regular dispatcher path",
548 );
549 self.builder.start_node_at(cp, SyntaxKind::HEADING.into());
550 emit_setext_heading_body(
551 &mut self.builder,
552 &combined_text,
553 underline_line,
554 level,
555 self.config,
556 );
557 self.builder.finish_node();
558 }
559
560 fn try_fold_list_item_buffer_into_setext(&mut self, content: &str) -> bool {
578 let Some(Container::ListItem {
579 buffer,
580 content_col,
581 ..
582 }) = self.containers.stack.last()
583 else {
584 return false;
585 };
586 if buffer.segment_count() != 1 {
587 return false;
588 }
589 let Some(text_line) = buffer.first_text() else {
590 return false;
591 };
592
593 let content_col = *content_col;
598 let (underline_indent_cols, _) = leading_indent(content);
599 if underline_indent_cols < content_col {
600 return false;
601 }
602
603 let lines = [text_line, content];
604 let Some((level, _)) = try_parse_setext_heading(&lines, 0) else {
605 return false;
606 };
607
608 let (text_no_newline, _) = strip_newline(text_line);
609 if text_no_newline.trim().is_empty() {
610 return false;
611 }
612 if try_parse_horizontal_rule(text_no_newline).is_some() {
613 return false;
614 }
615
616 let text_owned = text_line.to_string();
617 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
618 buffer.clear();
619 }
620 emit_setext_heading(&mut self.builder, &text_owned, content, level, self.config);
621 self.pos += 1;
622 true
623 }
624
625 fn close_paragraph_if_open(&mut self) {
627 if self.is_paragraph_open() {
628 self.close_containers_to(self.containers.depth() - 1);
629 }
630 }
631
632 fn close_paragraph_as_plain_if_open(&mut self) {
643 if !self.is_paragraph_open() {
644 return;
645 }
646 let Some(Container::Paragraph {
647 buffer,
648 start_checkpoint,
649 ..
650 }) = self.containers.stack.last()
651 else {
652 return;
653 };
654 let buffer_clone = buffer.clone();
655 let checkpoint = *start_checkpoint;
656 self.containers.stack.pop();
657 self.builder
658 .start_node_at(checkpoint, SyntaxKind::PLAIN.into());
659 if !buffer_clone.is_empty() {
660 buffer_clone.emit_with_inlines(&mut self.builder, self.config);
661 }
662 self.builder.finish_node();
663 }
664
665 fn html_block_demotes_paragraph_to_plain(&self, block_match: &PreparedBlockMatch) -> bool {
674 if self.config.dialect != crate::options::Dialect::Pandoc {
675 return false;
676 }
677 if self.block_registry.parser_name(block_match) != "html_block" {
678 return false;
679 }
680 let html_block_type = block_match
681 .payload
682 .as_ref()
683 .and_then(|p| p.downcast_ref::<crate::parser::blocks::html_blocks::HtmlBlockType>());
684 matches!(
685 html_block_type,
686 Some(crate::parser::blocks::html_blocks::HtmlBlockType::BlockTag { .. })
687 )
688 }
689
690 fn prepare_for_block_element(&mut self) {
693 self.emit_list_item_buffer_if_needed();
694 self.close_paragraph_if_open();
695 }
696
697 fn close_open_footnote_definition(&mut self) {
701 while matches!(
702 self.containers.last(),
703 Some(Container::FootnoteDefinition { .. })
704 ) {
705 self.close_containers_to(self.containers.depth() - 1);
706 }
707 }
708
709 fn handle_footnote_open_effect(
710 &mut self,
711 block_match: &super::block_dispatcher::PreparedBlockMatch,
712 content: &str,
713 ) {
714 let content_start = block_match
715 .payload
716 .as_ref()
717 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
718 .map(|p| p.content_start)
719 .unwrap_or(0);
720
721 let content_col = 4;
722 self.containers
723 .push(Container::FootnoteDefinition { content_col });
724
725 if content_start == 0 {
726 return;
727 }
728 let first_line_content = &content[content_start..];
729 if first_line_content.trim().is_empty() {
730 let (_, newline_str) = strip_newline(content);
731 if !newline_str.is_empty() {
732 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
733 }
734 return;
735 }
736
737 if self.config.extensions.definition_lists
738 && let Some(blank_count) = footnote_first_line_term_lookahead(
739 &self.lines,
740 self.pos,
741 content_col,
742 self.config.extensions.table_captions,
743 )
744 {
745 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
746 self.containers.push(Container::DefinitionList {});
747 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
748 self.containers.push(Container::DefinitionItem {});
749 emit_term(&mut self.builder, first_line_content, self.config);
750 for i in 0..blank_count {
751 let blank_pos = self.pos + 1 + i;
752 if blank_pos < self.lines.len() {
753 let blank_line = self.lines[blank_pos];
754 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
755 self.builder
756 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
757 self.builder.finish_node();
758 }
759 }
760 self.pos += blank_count;
761 return;
762 }
763
764 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
765 paragraphs::append_paragraph_line(
766 &mut self.containers,
767 &mut self.builder,
768 first_line_content,
769 self.config,
770 );
771 }
772
773 fn try_lazy_list_continuation(
785 &mut self,
786 block_match: &super::block_dispatcher::PreparedBlockMatch,
787 content: &str,
788 ) -> bool {
789 use super::block_dispatcher::ListPrepared;
790
791 let Some(prepared) = block_match
792 .payload
793 .as_ref()
794 .and_then(|p| p.downcast_ref::<ListPrepared>())
795 else {
796 return false;
797 };
798
799 if prepared.indent_cols < 4 || !lists::in_list(&self.containers) {
800 return false;
801 }
802
803 let current_content_col = paragraphs::current_content_col(&self.containers);
804 if prepared.indent_cols >= current_content_col {
805 return false;
806 }
807
808 if lists::find_matching_list_level(
809 &self.containers,
810 &prepared.marker,
811 prepared.indent_cols,
812 self.config.dialect,
813 )
814 .is_some()
815 {
816 return false;
817 }
818
819 match self.containers.last() {
820 Some(Container::Paragraph { .. }) => {
821 paragraphs::append_paragraph_line(
822 &mut self.containers,
823 &mut self.builder,
824 content,
825 self.config,
826 );
827 true
828 }
829 Some(Container::ListItem { .. }) => {
830 if let Some(Container::ListItem {
831 buffer,
832 marker_only,
833 ..
834 }) = self.containers.stack.last_mut()
835 {
836 buffer.push_text(content);
837 if !content.trim().is_empty() {
838 *marker_only = false;
839 }
840 }
841 true
842 }
843 _ => false,
844 }
845 }
846
847 fn handle_list_open_effect(
848 &mut self,
849 block_match: &super::block_dispatcher::PreparedBlockMatch,
850 content: &str,
851 indent_to_emit: Option<&str>,
852 ) {
853 use super::block_dispatcher::ListPrepared;
854
855 let prepared = block_match
856 .payload
857 .as_ref()
858 .and_then(|p| p.downcast_ref::<ListPrepared>());
859 let Some(prepared) = prepared else {
860 return;
861 };
862
863 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
864 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
865 paragraphs::append_paragraph_line(
866 &mut self.containers,
867 &mut self.builder,
868 content,
869 self.config,
870 );
871 return;
872 }
873
874 if self.is_paragraph_open() {
875 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
876 paragraphs::append_paragraph_line(
877 &mut self.containers,
878 &mut self.builder,
879 content,
880 self.config,
881 );
882 return;
883 }
884 self.close_containers_to(self.containers.depth() - 1);
885 }
886
887 if matches!(
888 self.containers.last(),
889 Some(Container::Definition {
890 plain_open: true,
891 ..
892 })
893 ) {
894 self.emit_buffered_plain_if_needed();
895 }
896
897 let matched_level = lists::find_matching_list_level(
898 &self.containers,
899 &prepared.marker,
900 prepared.indent_cols,
901 self.config.dialect,
902 );
903 let list_item = ListItemEmissionInput {
904 content,
905 marker_len: prepared.marker_len,
906 spaces_after_cols: prepared.spaces_after_cols,
907 spaces_after_bytes: prepared.spaces_after,
908 indent_cols: prepared.indent_cols,
909 indent_bytes: prepared.indent_bytes,
910 virtual_marker_space: prepared.virtual_marker_space,
911 };
912 let current_content_col = paragraphs::current_content_col(&self.containers);
913 let deep_ordered_matched_level = matched_level
914 .and_then(|level| self.containers.stack.get(level).map(|c| (level, c)))
915 .and_then(|(level, container)| match container {
916 Container::List {
917 marker: list_marker,
918 base_indent_cols,
919 ..
920 } if matches!(
921 (&prepared.marker, list_marker),
922 (ListMarker::Ordered(_), ListMarker::Ordered(_))
923 ) && prepared.indent_cols >= 4
924 && *base_indent_cols >= 4
925 && prepared.indent_cols.abs_diff(*base_indent_cols) <= 3 =>
926 {
927 Some(level)
928 }
929 _ => None,
930 });
931
932 if deep_ordered_matched_level.is_none()
933 && current_content_col > 0
934 && prepared.indent_cols >= current_content_col
935 {
936 if let Some(level) = matched_level
937 && let Some(Container::List {
938 base_indent_cols, ..
939 }) = self.containers.stack.get(level)
940 && prepared.indent_cols == *base_indent_cols
941 {
942 let num_parent_lists = self.containers.stack[..level]
943 .iter()
944 .filter(|c| matches!(c, Container::List { .. }))
945 .count();
946
947 if num_parent_lists > 0 {
948 self.close_containers_to(level + 1);
949
950 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
951 self.close_containers_to(self.containers.depth() - 1);
952 }
953 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
954 self.close_containers_to(self.containers.depth() - 1);
955 }
956
957 if let Some(indent_str) = indent_to_emit {
958 self.builder
959 .token(SyntaxKind::WHITESPACE.into(), indent_str);
960 }
961
962 if let Some(nested_marker) = prepared.nested_marker {
963 lists::add_list_item_with_nested_empty_list(
964 &mut self.containers,
965 &mut self.builder,
966 &list_item,
967 nested_marker,
968 );
969 } else {
970 lists::add_list_item(
971 &mut self.containers,
972 &mut self.builder,
973 &list_item,
974 self.config,
975 );
976 }
977 self.maybe_open_fenced_code_in_new_list_item();
978 self.maybe_open_indented_code_in_new_list_item();
979 return;
980 }
981 }
982
983 self.emit_list_item_buffer_if_needed();
984
985 start_nested_list(
986 &mut self.containers,
987 &mut self.builder,
988 &prepared.marker,
989 &list_item,
990 indent_to_emit,
991 self.config,
992 );
993 self.maybe_open_fenced_code_in_new_list_item();
994 self.maybe_open_indented_code_in_new_list_item();
995 return;
996 }
997
998 if let Some(level) = matched_level {
999 self.close_containers_to(level + 1);
1000
1001 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1002 self.close_containers_to(self.containers.depth() - 1);
1003 }
1004 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1005 self.close_containers_to(self.containers.depth() - 1);
1006 }
1007
1008 if let Some(indent_str) = indent_to_emit {
1009 self.builder
1010 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1011 }
1012
1013 if let Some(nested_marker) = prepared.nested_marker {
1014 lists::add_list_item_with_nested_empty_list(
1015 &mut self.containers,
1016 &mut self.builder,
1017 &list_item,
1018 nested_marker,
1019 );
1020 } else {
1021 lists::add_list_item(
1022 &mut self.containers,
1023 &mut self.builder,
1024 &list_item,
1025 self.config,
1026 );
1027 }
1028 self.maybe_open_fenced_code_in_new_list_item();
1029 self.maybe_open_indented_code_in_new_list_item();
1030 return;
1031 }
1032
1033 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1034 self.close_containers_to(self.containers.depth() - 1);
1035 }
1036 while matches!(
1037 self.containers.last(),
1038 Some(Container::ListItem { .. } | Container::List { .. })
1039 ) {
1040 self.close_containers_to(self.containers.depth() - 1);
1041 }
1042
1043 self.builder.start_node(SyntaxKind::LIST.into());
1044 if let Some(indent_str) = indent_to_emit {
1045 self.builder
1046 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1047 }
1048 self.containers.push(Container::List {
1049 marker: prepared.marker.clone(),
1050 base_indent_cols: prepared.indent_cols,
1051 has_blank_between_items: false,
1052 });
1053
1054 if let Some(nested_marker) = prepared.nested_marker {
1055 lists::add_list_item_with_nested_empty_list(
1056 &mut self.containers,
1057 &mut self.builder,
1058 &list_item,
1059 nested_marker,
1060 );
1061 } else {
1062 lists::add_list_item(
1063 &mut self.containers,
1064 &mut self.builder,
1065 &list_item,
1066 self.config,
1067 );
1068 }
1069 self.maybe_open_fenced_code_in_new_list_item();
1070 self.maybe_open_indented_code_in_new_list_item();
1071 }
1072
1073 fn handle_definition_list_effect(
1074 &mut self,
1075 block_match: &super::block_dispatcher::PreparedBlockMatch,
1076 content: &str,
1077 indent_to_emit: Option<&str>,
1078 ) {
1079 use super::block_dispatcher::DefinitionPrepared;
1080
1081 let prepared = block_match
1082 .payload
1083 .as_ref()
1084 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
1085 let Some(prepared) = prepared else {
1086 return;
1087 };
1088
1089 match prepared {
1090 DefinitionPrepared::Definition {
1091 marker_char,
1092 indent,
1093 spaces_after,
1094 spaces_after_cols,
1095 has_content,
1096 } => {
1097 self.emit_buffered_plain_if_needed();
1098
1099 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1100 self.close_containers_to(self.containers.depth() - 1);
1101 }
1102 while matches!(self.containers.last(), Some(Container::List { .. })) {
1103 self.close_containers_to(self.containers.depth() - 1);
1104 }
1105
1106 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1107 self.close_containers_to(self.containers.depth() - 1);
1108 }
1109
1110 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1111 self.close_containers_to(self.containers.depth() - 1);
1112 }
1113
1114 if definition_lists::in_definition_list(&self.containers)
1118 && !matches!(
1119 self.containers.last(),
1120 Some(Container::DefinitionItem { .. })
1121 )
1122 {
1123 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1124 self.containers.push(Container::DefinitionItem {});
1125 }
1126
1127 if !definition_lists::in_definition_list(&self.containers) {
1128 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1129 self.containers.push(Container::DefinitionList {});
1130 }
1131
1132 if !matches!(
1133 self.containers.last(),
1134 Some(Container::DefinitionItem { .. })
1135 ) {
1136 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1137 self.containers.push(Container::DefinitionItem {});
1138 }
1139
1140 self.builder.start_node(SyntaxKind::DEFINITION.into());
1141
1142 if let Some(indent_str) = indent_to_emit {
1143 self.builder
1144 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1145 }
1146
1147 emit_definition_marker(&mut self.builder, *marker_char, *indent);
1148 let indent_bytes = byte_index_at_column(content, *indent);
1149 if *spaces_after > 0 {
1150 let space_start = indent_bytes + 1;
1151 let space_end = space_start + *spaces_after;
1152 if space_end <= content.len() {
1153 self.builder.token(
1154 SyntaxKind::WHITESPACE.into(),
1155 &content[space_start..space_end],
1156 );
1157 }
1158 }
1159
1160 if !*has_content {
1161 let current_line = self.lines[self.pos];
1162 let (_, newline_str) = strip_newline(current_line);
1163 if !newline_str.is_empty() {
1164 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1165 }
1166 }
1167
1168 let content_col = *indent + 1 + *spaces_after_cols;
1169 let content_start_bytes = indent_bytes + 1 + *spaces_after;
1170 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
1171 let mut plain_buffer = TextBuffer::new();
1172 let mut definition_pushed = false;
1173
1174 if *has_content {
1175 let current_line = self.lines[self.pos];
1176 let (trimmed_line, _) = strip_newline(current_line);
1177
1178 let content_start = content_start_bytes.min(trimmed_line.len());
1179 let content_slice = &trimmed_line[content_start..];
1180 let content_line = ¤t_line[content_start_bytes.min(current_line.len())..];
1181
1182 let (blockquote_depth, inner_blockquote_content) =
1183 count_blockquote_markers(content_line);
1184
1185 let should_start_list_from_first_line = self
1186 .lines
1187 .get(self.pos + 1)
1188 .map(|next_line| {
1189 let (next_without_newline, _) = strip_newline(next_line);
1190 if next_without_newline.trim().is_empty() {
1191 return true;
1192 }
1193
1194 let (next_indent_cols, _) = leading_indent(next_without_newline);
1195 next_indent_cols >= content_col
1196 })
1197 .unwrap_or(true);
1198
1199 if blockquote_depth > 0 {
1200 self.containers.push(Container::Definition {
1201 content_col,
1202 plain_open: false,
1203 plain_buffer: TextBuffer::new(),
1204 });
1205 definition_pushed = true;
1206
1207 let marker_info = parse_blockquote_marker_info(content_line);
1208 for level in 0..blockquote_depth {
1209 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1210 if let Some(info) = marker_info.get(level) {
1211 blockquotes::emit_one_blockquote_marker(
1212 &mut self.builder,
1213 info.leading_spaces,
1214 info.has_trailing_space,
1215 );
1216 }
1217 self.containers.push(Container::BlockQuote {});
1218 }
1219
1220 if !inner_blockquote_content.trim().is_empty() {
1221 paragraphs::start_paragraph_if_needed(
1222 &mut self.containers,
1223 &mut self.builder,
1224 );
1225 paragraphs::append_paragraph_line(
1226 &mut self.containers,
1227 &mut self.builder,
1228 inner_blockquote_content,
1229 self.config,
1230 );
1231 }
1232 } else if let Some(marker_match) =
1233 try_parse_list_marker(content_slice, self.config)
1234 && should_start_list_from_first_line
1235 {
1236 self.containers.push(Container::Definition {
1237 content_col,
1238 plain_open: false,
1239 plain_buffer: TextBuffer::new(),
1240 });
1241 definition_pushed = true;
1242
1243 let (indent_cols, indent_bytes) = leading_indent(content_line);
1244 self.builder.start_node(SyntaxKind::LIST.into());
1245 self.containers.push(Container::List {
1246 marker: marker_match.marker.clone(),
1247 base_indent_cols: indent_cols,
1248 has_blank_between_items: false,
1249 });
1250
1251 let list_item = ListItemEmissionInput {
1252 content: content_line,
1253 marker_len: marker_match.marker_len,
1254 spaces_after_cols: marker_match.spaces_after_cols,
1255 spaces_after_bytes: marker_match.spaces_after_bytes,
1256 indent_cols,
1257 indent_bytes,
1258 virtual_marker_space: marker_match.virtual_marker_space,
1259 };
1260
1261 if let Some(nested_marker) = is_content_nested_bullet_marker(
1262 content_line,
1263 marker_match.marker_len,
1264 marker_match.spaces_after_bytes,
1265 ) {
1266 lists::add_list_item_with_nested_empty_list(
1267 &mut self.containers,
1268 &mut self.builder,
1269 &list_item,
1270 nested_marker,
1271 );
1272 } else {
1273 lists::add_list_item(
1274 &mut self.containers,
1275 &mut self.builder,
1276 &list_item,
1277 self.config,
1278 );
1279 }
1280 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
1281 self.containers.push(Container::Definition {
1282 content_col,
1283 plain_open: false,
1284 plain_buffer: TextBuffer::new(),
1285 });
1286 definition_pushed = true;
1287
1288 let bq_depth = self.current_blockquote_depth();
1289 if let Some(indent_str) = indent_to_emit {
1290 self.builder
1291 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1292 }
1293 let fence_line = current_line[content_start..].to_string();
1294 let new_pos = if self.config.extensions.tex_math_gfm
1295 && code_blocks::is_gfm_math_fence(&fence)
1296 {
1297 code_blocks::parse_fenced_math_block(
1298 &mut self.builder,
1299 &self.lines,
1300 self.pos,
1301 fence,
1302 bq_depth,
1303 content_col,
1304 Some(&fence_line),
1305 )
1306 } else {
1307 code_blocks::parse_fenced_code_block(
1308 &mut self.builder,
1309 &self.lines,
1310 self.pos,
1311 fence,
1312 bq_depth,
1313 content_col,
1314 Some(&fence_line),
1315 )
1316 };
1317 self.pos = new_pos - 1;
1318 } else {
1319 let (_, newline_str) = strip_newline(current_line);
1320 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
1321 if content_without_newline.is_empty() {
1322 plain_buffer.push_line(newline_str);
1323 } else {
1324 let line_with_newline = if !newline_str.is_empty() {
1325 format!("{}{}", content_without_newline, newline_str)
1326 } else {
1327 content_without_newline.to_string()
1328 };
1329 plain_buffer.push_line(line_with_newline);
1330 }
1331 }
1332 }
1333
1334 if !definition_pushed {
1335 self.containers.push(Container::Definition {
1336 content_col,
1337 plain_open: *has_content,
1338 plain_buffer,
1339 });
1340 }
1341 }
1342 DefinitionPrepared::Term { blank_count } => {
1343 self.emit_buffered_plain_if_needed();
1344
1345 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1346 self.close_containers_to(self.containers.depth() - 1);
1347 }
1348
1349 if !definition_lists::in_definition_list(&self.containers) {
1350 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
1351 self.containers.push(Container::DefinitionList {});
1352 }
1353
1354 while matches!(
1355 self.containers.last(),
1356 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
1357 ) {
1358 self.close_containers_to(self.containers.depth() - 1);
1359 }
1360
1361 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
1362 self.containers.push(Container::DefinitionItem {});
1363
1364 emit_term(&mut self.builder, content, self.config);
1365
1366 for i in 0..*blank_count {
1367 let blank_pos = self.pos + 1 + i;
1368 if blank_pos < self.lines.len() {
1369 let blank_line = self.lines[blank_pos];
1370 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1371 self.builder
1372 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
1373 self.builder.finish_node();
1374 }
1375 }
1376 self.pos += *blank_count;
1377 }
1378 }
1379 }
1380
1381 fn blockquote_marker_info(
1383 &self,
1384 payload: Option<&BlockQuotePrepared>,
1385 line: &str,
1386 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1387 payload
1388 .map(|payload| payload.marker_info.clone())
1389 .unwrap_or_else(|| parse_blockquote_marker_info(line))
1390 }
1391
1392 fn marker_info_for_line(
1398 &self,
1399 payload: Option<&BlockQuotePrepared>,
1400 raw_line: &str,
1401 marker_line: &str,
1402 shifted_prefix: &str,
1403 used_shifted: bool,
1404 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
1405 let mut marker_info = if used_shifted {
1406 parse_blockquote_marker_info(marker_line)
1407 } else {
1408 self.blockquote_marker_info(payload, raw_line)
1409 };
1410 if used_shifted && !shifted_prefix.is_empty() {
1411 let (prefix_cols, _) = leading_indent(shifted_prefix);
1412 if let Some(first) = marker_info.first_mut() {
1413 first.leading_spaces += prefix_cols;
1414 }
1415 }
1416 marker_info
1417 }
1418
1419 fn shifted_blockquote_from_list<'b>(
1422 &self,
1423 line: &'b str,
1424 ) -> Option<(usize, &'b str, &'b str, &'b str)> {
1425 let list_content_col = paragraphs::current_content_col(&self.containers);
1426 let content_container_indent = self.content_container_indent_to_strip();
1427 let marker_col = list_content_col.saturating_add(content_container_indent);
1428 if marker_col == 0 {
1429 return None;
1430 }
1431
1432 let (indent_cols, _) = leading_indent(line);
1433 if indent_cols < marker_col {
1434 return None;
1435 }
1436
1437 let idx = byte_index_at_column(line, marker_col);
1438 if idx > line.len() {
1439 return None;
1440 }
1441
1442 let candidate = &line[idx..];
1443 let (candidate_depth, candidate_inner) = count_blockquote_markers(candidate);
1444 if candidate_depth == 0 {
1445 return None;
1446 }
1447
1448 Some((candidate_depth, candidate_inner, candidate, &line[..idx]))
1449 }
1450
1451 fn emit_blockquote_markers(
1452 &mut self,
1453 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
1454 depth: usize,
1455 ) {
1456 for i in 0..depth {
1457 if let Some(info) = marker_info.get(i) {
1458 blockquotes::emit_one_blockquote_marker(
1459 &mut self.builder,
1460 info.leading_spaces,
1461 info.has_trailing_space,
1462 );
1463 }
1464 }
1465 }
1466
1467 fn current_blockquote_depth(&self) -> usize {
1468 blockquotes::current_blockquote_depth(&self.containers)
1469 }
1470
1471 fn emit_or_buffer_blockquote_marker(
1476 &mut self,
1477 leading_spaces: usize,
1478 has_trailing_space: bool,
1479 ) {
1480 if let Some(Container::ListItem {
1481 buffer,
1482 marker_only,
1483 ..
1484 }) = self.containers.stack.last_mut()
1485 {
1486 buffer.push_blockquote_marker(leading_spaces, has_trailing_space);
1487 *marker_only = false;
1488 return;
1489 }
1490
1491 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1493 paragraphs::append_paragraph_marker(
1495 &mut self.containers,
1496 leading_spaces,
1497 has_trailing_space,
1498 );
1499 } else {
1500 blockquotes::emit_one_blockquote_marker(
1502 &mut self.builder,
1503 leading_spaces,
1504 has_trailing_space,
1505 );
1506 }
1507 }
1508
1509 fn parse_document_stack(&mut self) {
1510 self.builder.start_node(SyntaxKind::DOCUMENT.into());
1511
1512 log::trace!("Starting document parse");
1513
1514 while self.pos < self.lines.len() {
1517 let line = self.lines[self.pos];
1518
1519 log::trace!("Parsing line {}: {}", self.pos + 1, line);
1520
1521 if self.parse_line(line) {
1522 continue;
1523 }
1524 self.pos += 1;
1525 }
1526
1527 self.close_containers_to(0);
1528 self.builder.finish_node(); }
1530
1531 fn parse_line(&mut self, line: &str) -> bool {
1533 let (mut bq_depth, mut inner_content) = count_blockquote_markers(line);
1536 let mut bq_marker_line = line;
1537 let mut shifted_bq_prefix = "";
1538 let mut used_shifted_bq = false;
1539 if bq_depth == 0
1540 && let Some((candidate_depth, candidate_inner, candidate_line, candidate_prefix)) =
1541 self.shifted_blockquote_from_list(line)
1542 {
1543 bq_depth = candidate_depth;
1544 inner_content = candidate_inner;
1545 bq_marker_line = candidate_line;
1546 shifted_bq_prefix = candidate_prefix;
1547 used_shifted_bq = true;
1548 }
1549 let current_bq_depth = self.current_blockquote_depth();
1550
1551 let has_blank_before = self.pos == 0 || is_blank_line(self.lines[self.pos - 1]);
1552 let mut blockquote_match: Option<PreparedBlockMatch> = None;
1553 let dispatcher_ctx = if current_bq_depth == 0 {
1554 Some(BlockContext {
1555 content: line,
1556 has_blank_before,
1557 has_blank_before_strict: has_blank_before,
1558 at_document_start: self.pos == 0,
1559 in_fenced_div: self.in_fenced_div(),
1560 blockquote_depth: current_bq_depth,
1561 config: self.config,
1562 content_indent: 0,
1563 indent_to_emit: None,
1564 list_indent_info: None,
1565 in_list: lists::in_list(&self.containers),
1566 in_marker_only_list_item: matches!(
1567 self.containers.last(),
1568 Some(Container::ListItem {
1569 marker_only: true,
1570 ..
1571 })
1572 ),
1573 paragraph_open: self.is_paragraph_open(),
1574 next_line: if self.pos + 1 < self.lines.len() {
1575 Some(self.lines[self.pos + 1])
1576 } else {
1577 None
1578 },
1579 })
1580 } else {
1581 None
1582 };
1583
1584 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
1585 self.block_registry
1586 .detect_prepared(dispatcher_ctx, &self.lines, self.pos)
1587 .and_then(|prepared| {
1588 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
1589 blockquote_match = Some(prepared);
1590 blockquote_match.as_ref().and_then(|prepared| {
1591 prepared
1592 .payload
1593 .as_ref()
1594 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
1595 .cloned()
1596 })
1597 } else {
1598 None
1599 }
1600 })
1601 } else {
1602 None
1603 };
1604
1605 log::trace!(
1606 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1607 self.pos,
1608 bq_depth,
1609 current_bq_depth,
1610 self.containers.depth(),
1611 line.trim_end()
1612 );
1613
1614 let inner_blank_in_blockquote = bq_depth > 0
1621 && is_blank_line(inner_content)
1622 && (current_bq_depth > 0
1623 || !self.config.extensions.blank_before_blockquote
1624 || blockquotes::can_start_blockquote(self.pos, &self.lines));
1625 let is_blank = is_blank_line(line) || inner_blank_in_blockquote;
1626
1627 if is_blank {
1628 if self.is_paragraph_open()
1629 && paragraphs::has_open_inline_math_environment(&self.containers)
1630 {
1631 paragraphs::append_paragraph_line(
1632 &mut self.containers,
1633 &mut self.builder,
1634 line,
1635 self.config,
1636 );
1637 self.pos += 1;
1638 return true;
1639 }
1640
1641 self.close_paragraph_if_open();
1643
1644 self.emit_buffered_plain_if_needed();
1648
1649 if bq_depth > current_bq_depth {
1657 for _ in current_bq_depth..bq_depth {
1659 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1660 self.containers.push(Container::BlockQuote {});
1661 }
1662 } else if bq_depth < current_bq_depth {
1663 self.close_blockquotes_to_depth(bq_depth);
1665 }
1666
1667 let mut peek = self.pos + 1;
1674 while peek < self.lines.len() {
1675 let peek_line = self.lines[peek];
1676 if is_blank_line(peek_line) {
1677 peek += 1;
1678 continue;
1679 }
1680 if bq_depth > 0 {
1681 let (peek_bq, _) = count_blockquote_markers(peek_line);
1682 if peek_bq >= bq_depth {
1683 let peek_inner =
1684 blockquotes::strip_n_blockquote_markers(peek_line, bq_depth);
1685 if is_blank_line(peek_inner) {
1686 peek += 1;
1687 continue;
1688 }
1689 }
1690 }
1691 break;
1692 }
1693
1694 let levels_to_keep = if peek < self.lines.len() {
1696 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1697 self.current_blockquote_depth(),
1698 &self.containers,
1699 &self.lines,
1700 peek,
1701 self.lines[peek],
1702 )
1703 } else {
1704 0
1705 };
1706 log::trace!(
1707 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1708 self.containers.depth(),
1709 levels_to_keep,
1710 if peek < self.lines.len() {
1711 self.lines[peek]
1712 } else {
1713 "<EOF>"
1714 }
1715 );
1716
1717 while self.containers.depth() > levels_to_keep {
1721 match self.containers.last() {
1722 Some(Container::ListItem { .. }) => {
1723 log::trace!(
1725 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1726 levels_to_keep,
1727 self.containers.depth()
1728 );
1729 self.close_containers_to(self.containers.depth() - 1);
1730 }
1731 Some(Container::List { .. })
1732 | Some(Container::FootnoteDefinition { .. })
1733 | Some(Container::Alert { .. })
1734 | Some(Container::Paragraph { .. })
1735 | Some(Container::Definition { .. })
1736 | Some(Container::DefinitionItem { .. })
1737 | Some(Container::DefinitionList { .. }) => {
1738 log::trace!(
1739 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1740 self.containers.last(),
1741 self.containers.depth(),
1742 levels_to_keep
1743 );
1744
1745 self.close_containers_to(self.containers.depth() - 1);
1746 }
1747 _ => break,
1748 }
1749 }
1750
1751 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1755 self.emit_list_item_buffer_if_needed();
1756 }
1757
1758 if bq_depth > 0 {
1760 let marker_info = self.marker_info_for_line(
1761 blockquote_payload.as_ref(),
1762 line,
1763 bq_marker_line,
1764 shifted_bq_prefix,
1765 used_shifted_bq,
1766 );
1767 self.emit_blockquote_markers(&marker_info, bq_depth);
1768 }
1769
1770 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1771 self.builder
1772 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1773 self.builder.finish_node();
1774
1775 self.pos += 1;
1776 return true;
1777 }
1778
1779 if bq_depth > current_bq_depth {
1781 if self.config.extensions.blank_before_blockquote
1784 && current_bq_depth == 0
1785 && !used_shifted_bq
1786 && !blockquote_payload
1787 .as_ref()
1788 .map(|payload| payload.can_start)
1789 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1790 {
1791 self.emit_list_item_buffer_if_needed();
1795 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1796 paragraphs::append_paragraph_line(
1797 &mut self.containers,
1798 &mut self.builder,
1799 line,
1800 self.config,
1801 );
1802 self.pos += 1;
1803 return true;
1804 }
1805
1806 let can_nest = if current_bq_depth > 0 {
1809 if self.config.extensions.blank_before_blockquote {
1810 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1812 || (self.pos > 0 && {
1813 let prev_line = self.lines[self.pos - 1];
1814 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1815 prev_bq_depth >= current_bq_depth && is_blank_line(prev_inner)
1816 })
1817 } else {
1818 true
1819 }
1820 } else {
1821 blockquote_payload
1822 .as_ref()
1823 .map(|payload| payload.can_nest)
1824 .unwrap_or(true)
1825 };
1826
1827 if !can_nest {
1828 let content_at_current_depth =
1831 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
1832
1833 let marker_info = self.marker_info_for_line(
1835 blockquote_payload.as_ref(),
1836 line,
1837 bq_marker_line,
1838 shifted_bq_prefix,
1839 used_shifted_bq,
1840 );
1841 for i in 0..current_bq_depth {
1842 if let Some(info) = marker_info.get(i) {
1843 self.emit_or_buffer_blockquote_marker(
1844 info.leading_spaces,
1845 info.has_trailing_space,
1846 );
1847 }
1848 }
1849
1850 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1851 paragraphs::append_paragraph_line(
1853 &mut self.containers,
1854 &mut self.builder,
1855 content_at_current_depth,
1856 self.config,
1857 );
1858 self.pos += 1;
1859 return true;
1860 } else {
1861 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1863 paragraphs::append_paragraph_line(
1864 &mut self.containers,
1865 &mut self.builder,
1866 content_at_current_depth,
1867 self.config,
1868 );
1869 self.pos += 1;
1870 return true;
1871 }
1872 }
1873
1874 self.emit_list_item_buffer_if_needed();
1877
1878 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1880 self.close_containers_to(self.containers.depth() - 1);
1881 }
1882
1883 let marker_info = self.marker_info_for_line(
1885 blockquote_payload.as_ref(),
1886 line,
1887 bq_marker_line,
1888 shifted_bq_prefix,
1889 used_shifted_bq,
1890 );
1891
1892 if let (Some(dispatcher_ctx), Some(prepared)) =
1893 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
1894 {
1895 let _ = self.block_registry.parse_prepared(
1896 prepared,
1897 dispatcher_ctx,
1898 &mut self.builder,
1899 &self.lines,
1900 self.pos,
1901 );
1902 for _ in 0..bq_depth {
1903 self.containers.push(Container::BlockQuote {});
1904 }
1905 } else {
1906 for level in 0..current_bq_depth {
1908 if let Some(info) = marker_info.get(level) {
1909 self.emit_or_buffer_blockquote_marker(
1910 info.leading_spaces,
1911 info.has_trailing_space,
1912 );
1913 }
1914 }
1915
1916 for level in current_bq_depth..bq_depth {
1918 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1919
1920 if let Some(info) = marker_info.get(level) {
1922 blockquotes::emit_one_blockquote_marker(
1923 &mut self.builder,
1924 info.leading_spaces,
1925 info.has_trailing_space,
1926 );
1927 }
1928
1929 self.containers.push(Container::BlockQuote {});
1930 }
1931 }
1932
1933 return self.parse_inner_content(inner_content, Some(inner_content));
1936 } else if bq_depth < current_bq_depth {
1937 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1943 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
1950 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
1951 let interrupts_via_fence =
1952 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
1953 if !interrupts_via_hr && !interrupts_via_fence {
1954 if bq_depth > 0 {
1955 let marker_info = self.marker_info_for_line(
1961 blockquote_payload.as_ref(),
1962 line,
1963 bq_marker_line,
1964 shifted_bq_prefix,
1965 used_shifted_bq,
1966 );
1967 for i in 0..bq_depth {
1968 if let Some(info) = marker_info.get(i) {
1969 paragraphs::append_paragraph_marker(
1970 &mut self.containers,
1971 info.leading_spaces,
1972 info.has_trailing_space,
1973 );
1974 }
1975 }
1976 paragraphs::append_paragraph_line(
1977 &mut self.containers,
1978 &mut self.builder,
1979 inner_content,
1980 self.config,
1981 );
1982 } else {
1983 paragraphs::append_paragraph_line(
1984 &mut self.containers,
1985 &mut self.builder,
1986 line,
1987 self.config,
1988 );
1989 }
1990 self.pos += 1;
1991 return true;
1992 }
1993 }
1994 if matches!(self.containers.last(), Some(Container::ListItem { .. }))
2002 && lists::in_blockquote_list(&self.containers)
2003 && try_parse_list_marker(line, self.config).is_none()
2004 {
2005 let is_commonmark = self.config.dialect == crate::options::Dialect::CommonMark;
2006 let interrupts_via_hr = is_commonmark && try_parse_horizontal_rule(line).is_some();
2007 let interrupts_via_fence =
2008 is_commonmark && code_blocks::try_parse_fence_open(line).is_some();
2009 if !interrupts_via_hr && !interrupts_via_fence {
2010 if bq_depth > 0 {
2011 let marker_info = self.marker_info_for_line(
2012 blockquote_payload.as_ref(),
2013 line,
2014 bq_marker_line,
2015 shifted_bq_prefix,
2016 used_shifted_bq,
2017 );
2018 if let Some(Container::ListItem {
2019 buffer,
2020 marker_only,
2021 ..
2022 }) = self.containers.stack.last_mut()
2023 {
2024 for i in 0..bq_depth {
2025 if let Some(info) = marker_info.get(i) {
2026 buffer.push_blockquote_marker(
2027 info.leading_spaces,
2028 info.has_trailing_space,
2029 );
2030 }
2031 }
2032 buffer.push_text(inner_content);
2033 if !inner_content.trim().is_empty() {
2034 *marker_only = false;
2035 }
2036 }
2037 } else if let Some(Container::ListItem {
2038 buffer,
2039 marker_only,
2040 ..
2041 }) = self.containers.stack.last_mut()
2042 {
2043 buffer.push_text(line);
2044 if !line.trim().is_empty() {
2045 *marker_only = false;
2046 }
2047 }
2048 self.pos += 1;
2049 return true;
2050 }
2051 }
2052 if bq_depth == 0 && self.config.dialect != crate::options::Dialect::CommonMark {
2058 if lists::in_blockquote_list(&self.containers)
2061 && let Some(marker_match) = try_parse_list_marker(line, self.config)
2062 {
2063 let (indent_cols, indent_bytes) = leading_indent(line);
2064 if let Some(level) = lists::find_matching_list_level(
2065 &self.containers,
2066 &marker_match.marker,
2067 indent_cols,
2068 self.config.dialect,
2069 ) {
2070 self.close_containers_to(level + 1);
2073
2074 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2076 self.close_containers_to(self.containers.depth() - 1);
2077 }
2078 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2079 self.close_containers_to(self.containers.depth() - 1);
2080 }
2081
2082 if let Some(nested_marker) = is_content_nested_bullet_marker(
2084 line,
2085 marker_match.marker_len,
2086 marker_match.spaces_after_bytes,
2087 ) {
2088 let list_item = ListItemEmissionInput {
2089 content: line,
2090 marker_len: marker_match.marker_len,
2091 spaces_after_cols: marker_match.spaces_after_cols,
2092 spaces_after_bytes: marker_match.spaces_after_bytes,
2093 indent_cols,
2094 indent_bytes,
2095 virtual_marker_space: marker_match.virtual_marker_space,
2096 };
2097 lists::add_list_item_with_nested_empty_list(
2098 &mut self.containers,
2099 &mut self.builder,
2100 &list_item,
2101 nested_marker,
2102 );
2103 } else {
2104 let list_item = ListItemEmissionInput {
2105 content: line,
2106 marker_len: marker_match.marker_len,
2107 spaces_after_cols: marker_match.spaces_after_cols,
2108 spaces_after_bytes: marker_match.spaces_after_bytes,
2109 indent_cols,
2110 indent_bytes,
2111 virtual_marker_space: marker_match.virtual_marker_space,
2112 };
2113 lists::add_list_item(
2114 &mut self.containers,
2115 &mut self.builder,
2116 &list_item,
2117 self.config,
2118 );
2119 }
2120 self.pos += 1;
2121 return true;
2122 }
2123 }
2124 }
2125
2126 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2128 self.close_containers_to(self.containers.depth() - 1);
2129 }
2130
2131 self.close_blockquotes_to_depth(bq_depth);
2133
2134 if bq_depth > 0 {
2136 let marker_info = self.marker_info_for_line(
2138 blockquote_payload.as_ref(),
2139 line,
2140 bq_marker_line,
2141 shifted_bq_prefix,
2142 used_shifted_bq,
2143 );
2144 for i in 0..bq_depth {
2145 if let Some(info) = marker_info.get(i) {
2146 self.emit_or_buffer_blockquote_marker(
2147 info.leading_spaces,
2148 info.has_trailing_space,
2149 );
2150 }
2151 }
2152 return self.parse_inner_content(inner_content, Some(inner_content));
2154 } else {
2155 return self.parse_inner_content(line, None);
2157 }
2158 } else if bq_depth > 0 {
2159 let mut list_item_continuation = false;
2161 let same_depth_marker_info = self.marker_info_for_line(
2162 blockquote_payload.as_ref(),
2163 line,
2164 bq_marker_line,
2165 shifted_bq_prefix,
2166 used_shifted_bq,
2167 );
2168 let has_explicit_same_depth_marker = same_depth_marker_info.len() >= bq_depth;
2169
2170 let (inner_indent_cols_raw, inner_indent_bytes) = leading_indent(inner_content);
2182 if let Some(marker_match) = try_parse_list_marker(inner_content, self.config) {
2183 let inner_content_threshold =
2187 marker_match.marker_len + marker_match.spaces_after_cols;
2188 let is_sibling_candidate = inner_indent_cols_raw < inner_content_threshold;
2189 let sibling_list_level = if is_sibling_candidate {
2190 self.containers
2191 .stack
2192 .iter()
2193 .enumerate()
2194 .rev()
2195 .find_map(|(i, c)| match c {
2196 Container::List { marker, .. }
2197 if lists::markers_match(
2198 &marker_match.marker,
2199 marker,
2200 self.config.dialect,
2201 ) && self.containers.stack[..i]
2202 .iter()
2203 .filter(|x| matches!(x, Container::BlockQuote { .. }))
2204 .count()
2205 == bq_depth =>
2206 {
2207 Some(i)
2208 }
2209 _ => None,
2210 })
2211 } else {
2212 None
2213 };
2214 if let Some(list_level) = sibling_list_level {
2215 let sibling_base_indent_cols = match self.containers.stack.get(list_level) {
2221 Some(Container::List {
2222 base_indent_cols, ..
2223 }) => *base_indent_cols,
2224 _ => 0,
2225 };
2226
2227 self.emit_list_item_buffer_if_needed();
2229 self.close_containers_to(list_level + 1);
2232
2233 for i in 0..bq_depth {
2237 if let Some(info) = same_depth_marker_info.get(i) {
2238 self.emit_or_buffer_blockquote_marker(
2239 info.leading_spaces,
2240 info.has_trailing_space,
2241 );
2242 }
2243 }
2244
2245 let list_item = ListItemEmissionInput {
2247 content: inner_content,
2248 marker_len: marker_match.marker_len,
2249 spaces_after_cols: marker_match.spaces_after_cols,
2250 spaces_after_bytes: marker_match.spaces_after_bytes,
2251 indent_cols: sibling_base_indent_cols,
2252 indent_bytes: inner_indent_bytes,
2253 virtual_marker_space: marker_match.virtual_marker_space,
2254 };
2255 lists::add_list_item(
2256 &mut self.containers,
2257 &mut self.builder,
2258 &list_item,
2259 self.config,
2260 );
2261 self.maybe_open_fenced_code_in_new_list_item();
2262 self.maybe_open_indented_code_in_new_list_item();
2263 self.pos += 1;
2264 return true;
2265 }
2266 }
2267
2268 if matches!(
2271 self.containers.last(),
2272 Some(Container::ListItem { content_col: _, .. })
2273 ) {
2274 let (indent_cols, _) = leading_indent(inner_content);
2275 let content_indent = self.content_container_indent_to_strip();
2276 let effective_indent = indent_cols.saturating_sub(content_indent);
2277 let content_col = match self.containers.last() {
2278 Some(Container::ListItem { content_col, .. }) => *content_col,
2279 _ => 0,
2280 };
2281
2282 let is_new_item_at_outer_level =
2284 if try_parse_list_marker(inner_content, self.config).is_some() {
2285 effective_indent < content_col
2286 } else {
2287 false
2288 };
2289
2290 if is_new_item_at_outer_level
2294 || (effective_indent < content_col && !has_explicit_same_depth_marker)
2295 {
2296 log::trace!(
2297 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
2298 is_new_item_at_outer_level,
2299 effective_indent,
2300 content_col
2301 );
2302 self.close_containers_to(self.containers.depth() - 1);
2303 } else {
2304 log::trace!(
2305 "Keeping ListItem: effective_indent={} >= content_col={}",
2306 effective_indent,
2307 content_col
2308 );
2309 list_item_continuation = true;
2310 }
2311 }
2312
2313 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
2317 {
2318 list_item_continuation = false;
2319 }
2320
2321 let continuation_has_explicit_marker = list_item_continuation && {
2322 if has_explicit_same_depth_marker {
2323 for i in 0..bq_depth {
2324 if let Some(info) = same_depth_marker_info.get(i) {
2325 self.emit_or_buffer_blockquote_marker(
2326 info.leading_spaces,
2327 info.has_trailing_space,
2328 );
2329 }
2330 }
2331 true
2332 } else {
2333 false
2334 }
2335 };
2336
2337 if !list_item_continuation {
2338 let marker_info = self.marker_info_for_line(
2339 blockquote_payload.as_ref(),
2340 line,
2341 bq_marker_line,
2342 shifted_bq_prefix,
2343 used_shifted_bq,
2344 );
2345 for i in 0..bq_depth {
2346 if let Some(info) = marker_info.get(i) {
2347 self.emit_or_buffer_blockquote_marker(
2348 info.leading_spaces,
2349 info.has_trailing_space,
2350 );
2351 }
2352 }
2353 }
2354 let line_to_append = if list_item_continuation {
2355 if continuation_has_explicit_marker {
2356 Some(inner_content)
2357 } else {
2358 Some(line)
2359 }
2360 } else {
2361 Some(inner_content)
2362 };
2363 return self.parse_inner_content(inner_content, line_to_append);
2364 }
2365
2366 if current_bq_depth > 0 {
2369 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2371 paragraphs::append_paragraph_line(
2372 &mut self.containers,
2373 &mut self.builder,
2374 line,
2375 self.config,
2376 );
2377 self.pos += 1;
2378 return true;
2379 }
2380
2381 if lists::in_blockquote_list(&self.containers)
2383 && let Some(marker_match) = try_parse_list_marker(line, self.config)
2384 {
2385 let (indent_cols, indent_bytes) = leading_indent(line);
2386 if let Some(level) = lists::find_matching_list_level(
2387 &self.containers,
2388 &marker_match.marker,
2389 indent_cols,
2390 self.config.dialect,
2391 ) {
2392 self.close_containers_to(level + 1);
2394
2395 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
2397 self.close_containers_to(self.containers.depth() - 1);
2398 }
2399 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2400 self.close_containers_to(self.containers.depth() - 1);
2401 }
2402
2403 if let Some(nested_marker) = is_content_nested_bullet_marker(
2405 line,
2406 marker_match.marker_len,
2407 marker_match.spaces_after_bytes,
2408 ) {
2409 let list_item = ListItemEmissionInput {
2410 content: line,
2411 marker_len: marker_match.marker_len,
2412 spaces_after_cols: marker_match.spaces_after_cols,
2413 spaces_after_bytes: marker_match.spaces_after_bytes,
2414 indent_cols,
2415 indent_bytes,
2416 virtual_marker_space: marker_match.virtual_marker_space,
2417 };
2418 lists::add_list_item_with_nested_empty_list(
2419 &mut self.containers,
2420 &mut self.builder,
2421 &list_item,
2422 nested_marker,
2423 );
2424 } else {
2425 let list_item = ListItemEmissionInput {
2426 content: line,
2427 marker_len: marker_match.marker_len,
2428 spaces_after_cols: marker_match.spaces_after_cols,
2429 spaces_after_bytes: marker_match.spaces_after_bytes,
2430 indent_cols,
2431 indent_bytes,
2432 virtual_marker_space: marker_match.virtual_marker_space,
2433 };
2434 lists::add_list_item(
2435 &mut self.containers,
2436 &mut self.builder,
2437 &list_item,
2438 self.config,
2439 );
2440 }
2441 self.pos += 1;
2442 return true;
2443 }
2444 }
2445 }
2446
2447 self.parse_inner_content(line, None)
2449 }
2450
2451 fn content_container_indent_to_strip(&self) -> usize {
2453 self.containers
2454 .stack
2455 .iter()
2456 .filter_map(|c| match c {
2457 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
2458 Container::Definition { content_col, .. } => Some(*content_col),
2459 _ => None,
2460 })
2461 .sum()
2462 }
2463
2464 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> bool {
2470 log::trace!(
2471 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
2472 self.pos,
2473 self.containers.depth(),
2474 self.containers.last(),
2475 content.trim_end()
2476 );
2477 let content_indent = self.content_container_indent_to_strip();
2480 let (stripped_content, indent_to_emit) = if content_indent > 0 {
2481 let (indent_cols, _) = leading_indent(content);
2482 if indent_cols >= content_indent {
2483 let idx = byte_index_at_column(content, content_indent);
2484 (&content[idx..], Some(&content[..idx]))
2485 } else {
2486 let trimmed_start = content.trim_start();
2488 let ws_len = content.len() - trimmed_start.len();
2489 if ws_len > 0 {
2490 (trimmed_start, Some(&content[..ws_len]))
2491 } else {
2492 (content, None)
2493 }
2494 }
2495 } else {
2496 (content, None)
2497 };
2498
2499 if self.config.extensions.alerts
2500 && self.current_blockquote_depth() > 0
2501 && !self.in_active_alert()
2502 && !self.is_paragraph_open()
2503 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
2504 {
2505 let (_, newline_str) = strip_newline(stripped_content);
2506 self.builder.start_node(SyntaxKind::ALERT.into());
2507 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
2508 if !newline_str.is_empty() {
2509 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
2510 }
2511 self.containers.push(Container::Alert {
2512 blockquote_depth: self.current_blockquote_depth(),
2513 });
2514 self.pos += 1;
2515 return true;
2516 }
2517
2518 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
2522 let is_definition_marker =
2523 definition_lists::try_parse_definition_marker(stripped_content).is_some()
2524 && !stripped_content.starts_with(':');
2525 if content_indent == 0 && is_definition_marker {
2526 } else {
2528 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
2529
2530 if policy.definition_plain_can_continue(
2531 stripped_content,
2532 content,
2533 content_indent,
2534 &BlockContext {
2535 content: stripped_content,
2536 has_blank_before: self.pos == 0 || is_blank_line(self.lines[self.pos - 1]),
2537 has_blank_before_strict: self.pos == 0
2538 || is_blank_line(self.lines[self.pos - 1]),
2539 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
2540 in_fenced_div: self.in_fenced_div(),
2541 blockquote_depth: self.current_blockquote_depth(),
2542 config: self.config,
2543 content_indent,
2544 indent_to_emit: None,
2545 list_indent_info: None,
2546 in_list: lists::in_list(&self.containers),
2547 in_marker_only_list_item: matches!(
2548 self.containers.last(),
2549 Some(Container::ListItem {
2550 marker_only: true,
2551 ..
2552 })
2553 ),
2554 paragraph_open: self.is_paragraph_open(),
2555 next_line: if self.pos + 1 < self.lines.len() {
2556 Some(self.lines[self.pos + 1])
2557 } else {
2558 None
2559 },
2560 },
2561 &self.lines,
2562 self.pos,
2563 ) {
2564 let content_line = stripped_content;
2565 let (text_without_newline, newline_str) = strip_newline(content_line);
2566 let indent_prefix = if !text_without_newline.trim().is_empty() {
2567 indent_to_emit.unwrap_or("")
2568 } else {
2569 ""
2570 };
2571 let content_line = format!("{}{}", indent_prefix, text_without_newline);
2572
2573 if let Some(Container::Definition {
2574 plain_open,
2575 plain_buffer,
2576 ..
2577 }) = self.containers.stack.last_mut()
2578 {
2579 let line_with_newline = if !newline_str.is_empty() {
2580 format!("{}{}", content_line, newline_str)
2581 } else {
2582 content_line
2583 };
2584 plain_buffer.push_line(line_with_newline);
2585 *plain_open = true;
2586 }
2587
2588 self.pos += 1;
2589 return true;
2590 }
2591 }
2592 }
2593
2594 if content_indent > 0 {
2597 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
2598 let current_bq_depth = self.current_blockquote_depth();
2599 let in_footnote_definition = self
2600 .containers
2601 .stack
2602 .iter()
2603 .any(|container| matches!(container, Container::FootnoteDefinition { .. }));
2604
2605 if bq_depth > 0 {
2606 if in_footnote_definition
2607 && self.config.extensions.blank_before_blockquote
2608 && current_bq_depth == 0
2609 && !blockquotes::can_start_blockquote(self.pos, &self.lines)
2610 {
2611 } else {
2615 self.emit_buffered_plain_if_needed();
2618 self.emit_list_item_buffer_if_needed();
2619
2620 self.close_paragraph_if_open();
2623
2624 if bq_depth > current_bq_depth {
2625 let marker_info = parse_blockquote_marker_info(stripped_content);
2626
2627 for level in current_bq_depth..bq_depth {
2629 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
2630
2631 if level == current_bq_depth
2632 && let Some(indent_str) = indent_to_emit
2633 {
2634 self.builder
2635 .token(SyntaxKind::WHITESPACE.into(), indent_str);
2636 }
2637
2638 if let Some(info) = marker_info.get(level) {
2639 blockquotes::emit_one_blockquote_marker(
2640 &mut self.builder,
2641 info.leading_spaces,
2642 info.has_trailing_space,
2643 );
2644 }
2645
2646 self.containers.push(Container::BlockQuote {});
2647 }
2648 } else if bq_depth < current_bq_depth {
2649 self.close_blockquotes_to_depth(bq_depth);
2650 } else {
2651 let marker_info = parse_blockquote_marker_info(stripped_content);
2653 self.emit_blockquote_markers(&marker_info, bq_depth);
2654 }
2655
2656 return self.parse_inner_content(inner_content, Some(inner_content));
2657 }
2658 }
2659 }
2660
2661 let content = stripped_content;
2663
2664 if self.is_paragraph_open()
2665 && (paragraphs::has_open_inline_math_environment(&self.containers)
2666 || paragraphs::has_open_display_math_dollars(&self.containers))
2667 {
2668 paragraphs::append_paragraph_line(
2669 &mut self.containers,
2670 &mut self.builder,
2671 line_to_append.unwrap_or(self.lines[self.pos]),
2672 self.config,
2673 );
2674 self.pos += 1;
2675 return true;
2676 }
2677
2678 use super::blocks::lists;
2682 use super::blocks::paragraphs;
2683 let list_indent_info = if lists::in_list(&self.containers) {
2684 let content_col = paragraphs::current_content_col(&self.containers);
2685 if content_col > 0 {
2686 Some(super::block_dispatcher::ListIndentInfo { content_col })
2687 } else {
2688 None
2689 }
2690 } else {
2691 None
2692 };
2693
2694 let next_line = if self.pos + 1 < self.lines.len() {
2695 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
2698 } else {
2699 None
2700 };
2701
2702 let current_bq_depth = self.current_blockquote_depth();
2703 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
2704 && current_bq_depth < alert_bq_depth
2705 {
2706 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
2707 self.close_containers_to(self.containers.depth() - 1);
2708 }
2709 }
2710
2711 let dispatcher_ctx = BlockContext {
2712 content,
2713 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
2717 blockquote_depth: current_bq_depth,
2718 config: self.config,
2719 content_indent,
2720 indent_to_emit,
2721 list_indent_info,
2722 in_list: lists::in_list(&self.containers),
2723 in_marker_only_list_item: matches!(
2724 self.containers.last(),
2725 Some(Container::ListItem {
2726 marker_only: true,
2727 ..
2728 })
2729 ),
2730 paragraph_open: self.is_paragraph_open(),
2731 next_line,
2732 };
2733
2734 let mut dispatcher_ctx = dispatcher_ctx;
2737
2738 if self.try_fold_list_item_buffer_into_setext(stripped_content) {
2742 return true;
2743 }
2744
2745 let dispatcher_match =
2748 self.block_registry
2749 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos);
2750
2751 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
2757 let has_blank_before = if self.pos == 0 || after_metadata_block {
2758 true
2759 } else {
2760 let prev_line = self.lines[self.pos - 1];
2761 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2762 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
2763 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
2764 && fenced_divs::try_parse_div_fence_open(
2765 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
2766 )
2767 .is_some();
2768
2769 let prev_line_blank = is_blank_line(prev_line);
2770 prev_line_blank
2771 || prev_is_fenced_div_open
2772 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
2773 || !self.previous_block_requires_blank_before_heading()
2774 };
2775
2776 let at_document_start = self.pos == 0 && current_bq_depth == 0;
2779
2780 let prev_line_blank = if self.pos > 0 {
2781 let prev_line = self.lines[self.pos - 1];
2782 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
2783 is_blank_line(prev_line) || (prev_bq_depth > 0 && is_blank_line(prev_inner))
2784 } else {
2785 false
2786 };
2787 let has_blank_before_strict = at_document_start || prev_line_blank;
2788
2789 dispatcher_ctx.has_blank_before = has_blank_before;
2790 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
2791 dispatcher_ctx.at_document_start = at_document_start;
2792
2793 let dispatcher_match =
2794 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
2795 self.block_registry
2797 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos)
2798 } else {
2799 dispatcher_match
2800 };
2801
2802 if has_blank_before {
2803 if let Some(env_name) = extract_environment_name(content)
2804 && is_inline_math_environment(env_name)
2805 {
2806 if !self.is_paragraph_open() {
2807 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2808 }
2809 paragraphs::append_paragraph_line(
2810 &mut self.containers,
2811 &mut self.builder,
2812 line_to_append.unwrap_or(self.lines[self.pos]),
2813 self.config,
2814 );
2815 self.pos += 1;
2816 return true;
2817 }
2818
2819 if let Some(block_match) = dispatcher_match.as_ref() {
2820 let detection = block_match.detection;
2821
2822 match detection {
2823 BlockDetectionResult::YesCanInterrupt => {
2824 self.emit_list_item_buffer_if_needed();
2825 if self.is_paragraph_open() {
2826 self.close_containers_to(self.containers.depth() - 1);
2827 }
2828 }
2829 BlockDetectionResult::Yes => {
2830 self.prepare_for_block_element();
2831 }
2832 BlockDetectionResult::No => unreachable!(),
2833 }
2834
2835 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2836 self.close_containers_to_fenced_div();
2837 }
2838
2839 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
2840 self.close_open_footnote_definition();
2841 }
2842
2843 let lines_consumed = self.block_registry.parse_prepared(
2844 block_match,
2845 &dispatcher_ctx,
2846 &mut self.builder,
2847 &self.lines,
2848 self.pos,
2849 );
2850
2851 if matches!(
2852 self.block_registry.parser_name(block_match),
2853 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
2854 ) {
2855 self.after_metadata_block = true;
2856 }
2857
2858 match block_match.effect {
2859 BlockEffect::None => {}
2860 BlockEffect::OpenFencedDiv => {
2861 self.containers.push(Container::FencedDiv {});
2862 }
2863 BlockEffect::CloseFencedDiv => {
2864 self.close_fenced_div();
2865 }
2866 BlockEffect::OpenFootnoteDefinition => {
2867 self.handle_footnote_open_effect(block_match, content);
2868 }
2869 BlockEffect::OpenList => {
2870 self.handle_list_open_effect(block_match, content, indent_to_emit);
2871 }
2872 BlockEffect::OpenDefinitionList => {
2873 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2874 }
2875 BlockEffect::OpenBlockQuote => {
2876 }
2878 }
2879
2880 if lines_consumed == 0 {
2881 log::warn!(
2882 "block parser made no progress at line {} (parser={})",
2883 self.pos + 1,
2884 self.block_registry.parser_name(block_match)
2885 );
2886 return false;
2887 }
2888
2889 self.pos += lines_consumed;
2890 return true;
2891 }
2892 } else if let Some(block_match) = dispatcher_match.as_ref() {
2893 let parser_name = self.block_registry.parser_name(block_match);
2896 match block_match.detection {
2897 BlockDetectionResult::YesCanInterrupt => {
2898 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
2899 && self.is_paragraph_open()
2900 {
2901 if !self.is_paragraph_open() {
2903 paragraphs::start_paragraph_if_needed(
2904 &mut self.containers,
2905 &mut self.builder,
2906 );
2907 }
2908 paragraphs::append_paragraph_line(
2909 &mut self.containers,
2910 &mut self.builder,
2911 line_to_append.unwrap_or(self.lines[self.pos]),
2912 self.config,
2913 );
2914 self.pos += 1;
2915 return true;
2916 }
2917
2918 if matches!(block_match.effect, BlockEffect::OpenList)
2919 && self.is_paragraph_open()
2920 && !lists::in_list(&self.containers)
2921 && self.content_container_indent_to_strip() == 0
2922 {
2923 let allow_interrupt =
2929 self.config.dialect == crate::options::Dialect::CommonMark && {
2930 use super::block_dispatcher::ListPrepared;
2931 use super::blocks::lists::OrderedMarker;
2932 let prepared = block_match
2933 .payload
2934 .as_ref()
2935 .and_then(|p| p.downcast_ref::<ListPrepared>());
2936 match prepared.map(|p| &p.marker) {
2937 Some(ListMarker::Bullet(_)) => true,
2938 Some(ListMarker::Ordered(OrderedMarker::Decimal {
2939 number,
2940 ..
2941 })) => number == "1",
2942 _ => false,
2943 }
2944 };
2945 if !allow_interrupt {
2946 paragraphs::append_paragraph_line(
2947 &mut self.containers,
2948 &mut self.builder,
2949 line_to_append.unwrap_or(self.lines[self.pos]),
2950 self.config,
2951 );
2952 self.pos += 1;
2953 return true;
2954 }
2955 }
2956
2957 if matches!(block_match.effect, BlockEffect::OpenList)
2964 && self.try_lazy_list_continuation(block_match, content)
2965 {
2966 self.pos += 1;
2967 return true;
2968 }
2969
2970 self.emit_list_item_buffer_if_needed();
2971 if self.is_paragraph_open() {
2972 if self.html_block_demotes_paragraph_to_plain(block_match) {
2973 self.close_paragraph_as_plain_if_open();
2974 } else {
2975 self.close_containers_to(self.containers.depth() - 1);
2976 }
2977 }
2978
2979 if self.config.dialect == crate::options::Dialect::CommonMark
2986 && !matches!(block_match.effect, BlockEffect::OpenList)
2987 {
2988 let (indent_cols, _) = leading_indent(content);
2989 self.close_lists_above_indent(indent_cols);
2990 }
2991 }
2992 BlockDetectionResult::Yes => {
2993 if parser_name == "setext_heading"
3005 && self.is_paragraph_open()
3006 && self.config.dialect == crate::options::Dialect::CommonMark
3007 {
3008 let text_line = self.lines[self.pos];
3009 let underline_line = self.lines[self.pos + 1];
3010 let underline_char = underline_line.trim().chars().next().unwrap_or('=');
3011 let level = if underline_char == '=' { 1 } else { 2 };
3012 self.emit_setext_heading_folding_paragraph(
3013 text_line,
3014 underline_line,
3015 level,
3016 );
3017 self.pos += 2;
3018 return true;
3019 }
3020
3021 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
3024 if !self.is_paragraph_open() {
3025 paragraphs::start_paragraph_if_needed(
3026 &mut self.containers,
3027 &mut self.builder,
3028 );
3029 }
3030 paragraphs::append_paragraph_line(
3031 &mut self.containers,
3032 &mut self.builder,
3033 line_to_append.unwrap_or(self.lines[self.pos]),
3034 self.config,
3035 );
3036 self.pos += 1;
3037 return true;
3038 }
3039
3040 if parser_name == "reference_definition" && self.is_paragraph_open() {
3043 paragraphs::append_paragraph_line(
3044 &mut self.containers,
3045 &mut self.builder,
3046 line_to_append.unwrap_or(self.lines[self.pos]),
3047 self.config,
3048 );
3049 self.pos += 1;
3050 return true;
3051 }
3052 }
3053 BlockDetectionResult::No => unreachable!(),
3054 }
3055
3056 if !matches!(block_match.detection, BlockDetectionResult::No) {
3057 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
3058 self.close_containers_to_fenced_div();
3059 }
3060
3061 if matches!(block_match.effect, BlockEffect::OpenFootnoteDefinition) {
3062 self.close_open_footnote_definition();
3063 }
3064
3065 let lines_consumed = self.block_registry.parse_prepared(
3066 block_match,
3067 &dispatcher_ctx,
3068 &mut self.builder,
3069 &self.lines,
3070 self.pos,
3071 );
3072
3073 match block_match.effect {
3074 BlockEffect::None => {}
3075 BlockEffect::OpenFencedDiv => {
3076 self.containers.push(Container::FencedDiv {});
3077 }
3078 BlockEffect::CloseFencedDiv => {
3079 self.close_fenced_div();
3080 }
3081 BlockEffect::OpenFootnoteDefinition => {
3082 self.handle_footnote_open_effect(block_match, content);
3083 }
3084 BlockEffect::OpenList => {
3085 self.handle_list_open_effect(block_match, content, indent_to_emit);
3086 }
3087 BlockEffect::OpenDefinitionList => {
3088 self.handle_definition_list_effect(block_match, content, indent_to_emit);
3089 }
3090 BlockEffect::OpenBlockQuote => {
3091 }
3093 }
3094
3095 if lines_consumed == 0 {
3096 log::warn!(
3097 "block parser made no progress at line {} (parser={})",
3098 self.pos + 1,
3099 self.block_registry.parser_name(block_match)
3100 );
3101 return false;
3102 }
3103
3104 self.pos += lines_consumed;
3105 return true;
3106 }
3107 }
3108
3109 if self.config.extensions.line_blocks
3111 && (has_blank_before || self.pos == 0)
3112 && try_parse_line_block_start(content).is_some()
3113 && try_parse_line_block_start(self.lines[self.pos]).is_some()
3117 {
3118 log::trace!("Parsed line block at line {}", self.pos);
3119 self.close_paragraph_if_open();
3121
3122 let new_pos = parse_line_block(&self.lines, self.pos, &mut self.builder, self.config);
3123 if new_pos > self.pos {
3124 self.pos = new_pos;
3125 return true;
3126 }
3127 }
3128
3129 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
3132 log::trace!(
3133 "Inside ListItem - buffering content: {:?}",
3134 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3135 );
3136 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3138
3139 if let Some(Container::ListItem {
3141 buffer,
3142 marker_only,
3143 ..
3144 }) = self.containers.stack.last_mut()
3145 {
3146 buffer.push_text(line);
3147 if !is_blank_line(line) {
3148 *marker_only = false;
3149 }
3150 }
3151
3152 self.pos += 1;
3153 return true;
3154 }
3155
3156 log::trace!(
3157 "Not in ListItem - creating paragraph for: {:?}",
3158 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
3159 );
3160 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
3162 let line = line_to_append.unwrap_or(self.lines[self.pos]);
3165 paragraphs::append_paragraph_line(
3166 &mut self.containers,
3167 &mut self.builder,
3168 line,
3169 self.config,
3170 );
3171 self.pos += 1;
3172 true
3173 }
3174
3175 fn fenced_div_container_index(&self) -> Option<usize> {
3176 self.containers
3177 .stack
3178 .iter()
3179 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
3180 }
3181
3182 fn close_containers_to_fenced_div(&mut self) {
3183 if let Some(index) = self.fenced_div_container_index() {
3184 self.close_containers_to(index + 1);
3185 }
3186 }
3187
3188 fn close_fenced_div(&mut self) {
3189 if let Some(index) = self.fenced_div_container_index() {
3190 self.close_containers_to(index);
3191 }
3192 }
3193
3194 fn in_fenced_div(&self) -> bool {
3195 self.containers
3196 .stack
3197 .iter()
3198 .any(|c| matches!(c, Container::FencedDiv { .. }))
3199 }
3200}
3201
3202fn emit_definition_plain_or_heading(
3209 builder: &mut GreenNodeBuilder<'static>,
3210 text: &str,
3211 config: &ParserOptions,
3212) {
3213 let line_without_newline = text
3214 .strip_suffix("\r\n")
3215 .or_else(|| text.strip_suffix('\n'));
3216 if let Some(line) = line_without_newline
3217 && !line.contains('\n')
3218 && !line.contains('\r')
3219 && let Some(level) = try_parse_atx_heading(line)
3220 {
3221 emit_atx_heading(builder, text, level, config);
3222 return;
3223 }
3224
3225 if let Some(first_nl) = text.find('\n') {
3227 let first_line = &text[..first_nl];
3228 let after_first = &text[first_nl + 1..];
3229 if !after_first.is_empty()
3230 && let Some(level) = try_parse_atx_heading(first_line)
3231 {
3232 let heading_bytes = &text[..first_nl + 1];
3233 emit_atx_heading(builder, heading_bytes, level, config);
3234 builder.start_node(SyntaxKind::PLAIN.into());
3235 inline_emission::emit_inlines(builder, after_first, config);
3236 builder.finish_node();
3237 return;
3238 }
3239 }
3240
3241 builder.start_node(SyntaxKind::PLAIN.into());
3242 inline_emission::emit_inlines(builder, text, config);
3243 builder.finish_node();
3244}
3245
3246fn footnote_first_line_term_lookahead(
3255 lines: &[&str],
3256 pos: usize,
3257 content_col: usize,
3258 table_captions_enabled: bool,
3259) -> Option<usize> {
3260 let mut check_pos = pos + 1;
3261 let mut blank_count = 0;
3262 while check_pos < lines.len() {
3263 let line = lines[check_pos];
3264 let (trimmed, _) = strip_newline(line);
3265 if trimmed.trim().is_empty() {
3266 blank_count += 1;
3267 check_pos += 1;
3268 continue;
3269 }
3270 let (line_indent_cols, _) = leading_indent(trimmed);
3271 if line_indent_cols < content_col {
3272 return None;
3273 }
3274 let strip_bytes = byte_index_at_column(trimmed, content_col);
3275 if strip_bytes > trimmed.len() {
3276 return None;
3277 }
3278 let stripped = &trimmed[strip_bytes..];
3279 if let Some((marker, ..)) = definition_lists::try_parse_definition_marker(stripped) {
3280 if marker == ':'
3284 && table_captions_enabled
3285 && super::blocks::tables::is_caption_followed_by_table(lines, check_pos)
3286 {
3287 return None;
3288 }
3289 return Some(blank_count);
3290 }
3291 return None;
3292 }
3293 None
3294}