1use crate::config::Config;
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::{emit_atx_heading, try_parse_atx_heading};
14use super::blocks::line_blocks;
15use super::blocks::lists;
16use super::blocks::paragraphs;
17use super::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
18use super::utils::container_stack;
19use super::utils::helpers::{split_lines_inclusive, strip_newline};
20use super::utils::inline_emission;
21use super::utils::marker_utils;
22use super::utils::text_buffer;
23
24use super::blocks::blockquotes::strip_n_blockquote_markers;
25use super::utils::continuation::ContinuationPolicy;
26use container_stack::{Container, ContainerStack, byte_index_at_column, leading_indent};
27use definition_lists::{emit_definition_marker, emit_term};
28use line_blocks::{parse_line_block, try_parse_line_block_start};
29use lists::{
30 ListItemEmissionInput, is_content_nested_bullet_marker, start_nested_list,
31 try_parse_list_marker,
32};
33use marker_utils::{count_blockquote_markers, parse_blockquote_marker_info};
34use text_buffer::TextBuffer;
35
36const GITHUB_ALERT_MARKERS: [&str; 5] = [
37 "[!TIP]",
38 "[!WARNING]",
39 "[!IMPORTANT]",
40 "[!CAUTION]",
41 "[!NOTE]",
42];
43
44#[cfg(debug_assertions)]
45fn init_logger() {
46 let _ = env_logger::builder().is_test(true).try_init();
47}
48
49pub struct Parser<'a> {
50 lines: Vec<&'a str>,
51 pos: usize,
52 builder: GreenNodeBuilder<'static>,
53 containers: ContainerStack,
54 config: &'a Config,
55 block_registry: BlockParserRegistry,
56 after_metadata_block: bool,
60}
61
62impl<'a> Parser<'a> {
63 pub fn new(input: &'a str, config: &'a Config) -> Self {
64 let lines = split_lines_inclusive(input);
66 Self {
67 lines,
68 pos: 0,
69 builder: GreenNodeBuilder::new(),
70 containers: ContainerStack::new(),
71 config,
72 block_registry: BlockParserRegistry::new(),
73 after_metadata_block: false,
74 }
75 }
76
77 pub fn parse(mut self) -> SyntaxNode {
78 #[cfg(debug_assertions)]
79 {
80 init_logger();
81 }
82
83 self.parse_document_stack();
84
85 SyntaxNode::new_root(self.builder.finish())
86 }
87
88 fn close_containers_to(&mut self, keep: usize) {
91 while self.containers.depth() > keep {
93 match self.containers.stack.last() {
94 Some(Container::ListItem { buffer, .. }) if !buffer.is_empty() => {
96 let buffer_clone = buffer.clone();
98
99 log::debug!(
100 "Closing ListItem with buffer (is_empty={}, segment_count={})",
101 buffer_clone.is_empty(),
102 buffer_clone.segment_count()
103 );
104
105 let parent_list_is_loose = self
109 .containers
110 .stack
111 .iter()
112 .rev()
113 .find_map(|c| match c {
114 Container::List {
115 has_blank_between_items,
116 ..
117 } => Some(*has_blank_between_items),
118 _ => None,
119 })
120 .unwrap_or(false);
121
122 let use_paragraph =
123 parent_list_is_loose || buffer_clone.has_blank_lines_between_content();
124
125 log::debug!(
126 "Emitting ListItem buffer: use_paragraph={} (parent_list_is_loose={}, item_has_blanks={})",
127 use_paragraph,
128 parent_list_is_loose,
129 buffer_clone.has_blank_lines_between_content()
130 );
131
132 self.containers.stack.pop();
134 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
136 self.builder.finish_node(); }
138 Some(Container::ListItem { .. }) => {
140 log::debug!("Closing empty ListItem (no buffer content)");
141 self.containers.stack.pop();
143 self.builder.finish_node();
144 }
145 Some(Container::Paragraph { buffer, .. }) if !buffer.is_empty() => {
147 let buffer_clone = buffer.clone();
149 self.containers.stack.pop();
151 buffer_clone.emit_with_inlines(&mut self.builder, self.config);
153 self.builder.finish_node();
154 }
155 Some(Container::Paragraph { .. }) => {
157 self.containers.stack.pop();
159 self.builder.finish_node();
160 }
161 Some(Container::Definition {
163 plain_open: true,
164 plain_buffer,
165 ..
166 }) if !plain_buffer.is_empty() => {
167 let text = plain_buffer.get_accumulated_text();
168 let line_without_newline = text
169 .strip_suffix("\r\n")
170 .or_else(|| text.strip_suffix('\n'));
171 if let Some(line) = line_without_newline
172 && !line.contains('\n')
173 && !line.contains('\r')
174 && let Some(level) = try_parse_atx_heading(line)
175 {
176 emit_atx_heading(&mut self.builder, &text, level, self.config);
177 } else {
178 self.builder.start_node(SyntaxKind::PLAIN.into());
180 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
181 self.builder.finish_node();
182 }
183
184 if let Some(Container::Definition {
186 plain_open,
187 plain_buffer,
188 ..
189 }) = self.containers.stack.last_mut()
190 {
191 plain_buffer.clear();
192 *plain_open = false;
193 }
194
195 self.containers.stack.pop();
197 self.builder.finish_node();
198 }
199 Some(Container::Definition {
201 plain_open: true, ..
202 }) => {
203 if let Some(Container::Definition {
205 plain_open,
206 plain_buffer,
207 ..
208 }) = self.containers.stack.last_mut()
209 {
210 plain_buffer.clear();
211 *plain_open = false;
212 }
213
214 self.containers.stack.pop();
216 self.builder.finish_node();
217 }
218 _ => {
220 self.containers.stack.pop();
221 self.builder.finish_node();
222 }
223 }
224 }
225 }
226
227 fn emit_buffered_plain_if_needed(&mut self) {
230 if let Some(Container::Definition {
232 plain_open: true,
233 plain_buffer,
234 ..
235 }) = self.containers.stack.last()
236 && !plain_buffer.is_empty()
237 {
238 let text = plain_buffer.get_accumulated_text();
239 let line_without_newline = text
240 .strip_suffix("\r\n")
241 .or_else(|| text.strip_suffix('\n'));
242 if let Some(line) = line_without_newline
243 && !line.contains('\n')
244 && !line.contains('\r')
245 && let Some(level) = try_parse_atx_heading(line)
246 {
247 emit_atx_heading(&mut self.builder, &text, level, self.config);
248 } else {
249 self.builder.start_node(SyntaxKind::PLAIN.into());
251 inline_emission::emit_inlines(&mut self.builder, &text, self.config);
252 self.builder.finish_node();
253 }
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 alert_marker_from_content(content: &str) -> Option<&'static str> {
303 let (without_newline, _) = strip_newline(content);
304 let trimmed = without_newline.trim();
305 GITHUB_ALERT_MARKERS
306 .into_iter()
307 .find(|marker| *marker == trimmed)
308 }
309
310 fn emit_list_item_buffer_if_needed(&mut self) {
313 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut()
314 && !buffer.is_empty()
315 {
316 let buffer_clone = buffer.clone();
317 buffer.clear();
318 let use_paragraph = buffer_clone.has_blank_lines_between_content();
319 buffer_clone.emit_as_block(&mut self.builder, use_paragraph, self.config);
320 }
321 }
322
323 fn is_paragraph_open(&self) -> bool {
325 matches!(self.containers.last(), Some(Container::Paragraph { .. }))
326 }
327
328 fn close_paragraph_if_open(&mut self) {
330 if self.is_paragraph_open() {
331 self.close_containers_to(self.containers.depth() - 1);
332 }
333 }
334
335 fn prepare_for_block_element(&mut self) {
338 self.emit_list_item_buffer_if_needed();
339 self.close_paragraph_if_open();
340 }
341
342 fn handle_footnote_open_effect(
343 &mut self,
344 block_match: &super::block_dispatcher::PreparedBlockMatch,
345 content: &str,
346 ) {
347 let content_start = block_match
348 .payload
349 .as_ref()
350 .and_then(|p| p.downcast_ref::<super::block_dispatcher::FootnoteDefinitionPrepared>())
351 .map(|p| p.content_start)
352 .unwrap_or(0);
353
354 while matches!(
355 self.containers.last(),
356 Some(Container::FootnoteDefinition { .. })
357 ) {
358 self.close_containers_to(self.containers.depth() - 1);
359 }
360
361 let content_col = 4;
362 self.containers
363 .push(Container::FootnoteDefinition { content_col });
364
365 if content_start > 0 {
366 let first_line_content = &content[content_start..];
367 if !first_line_content.trim().is_empty() {
368 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
369 paragraphs::append_paragraph_line(
370 &mut self.containers,
371 &mut self.builder,
372 first_line_content,
373 self.config,
374 );
375 } else {
376 let (_, newline_str) = strip_newline(content);
377 if !newline_str.is_empty() {
378 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
379 }
380 }
381 }
382 }
383
384 fn handle_list_open_effect(
385 &mut self,
386 block_match: &super::block_dispatcher::PreparedBlockMatch,
387 content: &str,
388 indent_to_emit: Option<&str>,
389 ) {
390 use super::block_dispatcher::ListPrepared;
391
392 let prepared = block_match
393 .payload
394 .as_ref()
395 .and_then(|p| p.downcast_ref::<ListPrepared>());
396 let Some(prepared) = prepared else {
397 return;
398 };
399
400 if prepared.indent_cols >= 4 && !lists::in_list(&self.containers) {
401 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
402 paragraphs::append_paragraph_line(
403 &mut self.containers,
404 &mut self.builder,
405 content,
406 self.config,
407 );
408 return;
409 }
410
411 if self.is_paragraph_open() {
412 if !block_match.detection.eq(&BlockDetectionResult::Yes) {
413 paragraphs::append_paragraph_line(
414 &mut self.containers,
415 &mut self.builder,
416 content,
417 self.config,
418 );
419 return;
420 }
421 self.close_containers_to(self.containers.depth() - 1);
422 }
423
424 if matches!(
425 self.containers.last(),
426 Some(Container::Definition {
427 plain_open: true,
428 ..
429 })
430 ) {
431 self.emit_buffered_plain_if_needed();
432 }
433
434 let matched_level = lists::find_matching_list_level(
435 &self.containers,
436 &prepared.marker,
437 prepared.indent_cols,
438 );
439 let list_item = ListItemEmissionInput {
440 content,
441 marker_len: prepared.marker_len,
442 spaces_after_cols: prepared.spaces_after_cols,
443 spaces_after_bytes: prepared.spaces_after,
444 indent_cols: prepared.indent_cols,
445 indent_bytes: prepared.indent_bytes,
446 };
447 let current_content_col = paragraphs::current_content_col(&self.containers);
448
449 if current_content_col > 0 && prepared.indent_cols >= current_content_col {
450 if let Some(level) = matched_level
451 && let Some(Container::List {
452 base_indent_cols, ..
453 }) = self.containers.stack.get(level)
454 && prepared.indent_cols == *base_indent_cols
455 {
456 let num_parent_lists = self.containers.stack[..level]
457 .iter()
458 .filter(|c| matches!(c, Container::List { .. }))
459 .count();
460
461 if num_parent_lists > 0 {
462 self.close_containers_to(level + 1);
463
464 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
465 self.close_containers_to(self.containers.depth() - 1);
466 }
467 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
468 self.close_containers_to(self.containers.depth() - 1);
469 }
470
471 if let Some(indent_str) = indent_to_emit {
472 self.builder
473 .token(SyntaxKind::WHITESPACE.into(), indent_str);
474 }
475
476 if let Some(nested_marker) = prepared.nested_marker {
477 lists::add_list_item_with_nested_empty_list(
478 &mut self.containers,
479 &mut self.builder,
480 &list_item,
481 nested_marker,
482 );
483 } else {
484 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
485 }
486 return;
487 }
488 }
489
490 self.emit_list_item_buffer_if_needed();
491
492 start_nested_list(
493 &mut self.containers,
494 &mut self.builder,
495 &prepared.marker,
496 &list_item,
497 indent_to_emit,
498 );
499 return;
500 }
501
502 if let Some(level) = matched_level {
503 self.close_containers_to(level + 1);
504
505 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
506 self.close_containers_to(self.containers.depth() - 1);
507 }
508 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
509 self.close_containers_to(self.containers.depth() - 1);
510 }
511
512 if let Some(indent_str) = indent_to_emit {
513 self.builder
514 .token(SyntaxKind::WHITESPACE.into(), indent_str);
515 }
516
517 if let Some(nested_marker) = prepared.nested_marker {
518 lists::add_list_item_with_nested_empty_list(
519 &mut self.containers,
520 &mut self.builder,
521 &list_item,
522 nested_marker,
523 );
524 } else {
525 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
526 }
527 return;
528 }
529
530 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
531 self.close_containers_to(self.containers.depth() - 1);
532 }
533 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
534 self.close_containers_to(self.containers.depth() - 1);
535 }
536 while matches!(self.containers.last(), Some(Container::List { .. })) {
537 self.close_containers_to(self.containers.depth() - 1);
538 }
539
540 self.builder.start_node(SyntaxKind::LIST.into());
541 if let Some(indent_str) = indent_to_emit {
542 self.builder
543 .token(SyntaxKind::WHITESPACE.into(), indent_str);
544 }
545 self.containers.push(Container::List {
546 marker: prepared.marker.clone(),
547 base_indent_cols: prepared.indent_cols,
548 has_blank_between_items: false,
549 });
550
551 if let Some(nested_marker) = prepared.nested_marker {
552 lists::add_list_item_with_nested_empty_list(
553 &mut self.containers,
554 &mut self.builder,
555 &list_item,
556 nested_marker,
557 );
558 } else {
559 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
560 }
561 }
562
563 fn handle_definition_list_effect(
564 &mut self,
565 block_match: &super::block_dispatcher::PreparedBlockMatch,
566 content: &str,
567 indent_to_emit: Option<&str>,
568 ) {
569 use super::block_dispatcher::DefinitionPrepared;
570
571 let prepared = block_match
572 .payload
573 .as_ref()
574 .and_then(|p| p.downcast_ref::<DefinitionPrepared>());
575 let Some(prepared) = prepared else {
576 return;
577 };
578
579 match prepared {
580 DefinitionPrepared::Definition {
581 marker_char,
582 indent,
583 spaces_after,
584 spaces_after_cols,
585 has_content,
586 } => {
587 self.emit_buffered_plain_if_needed();
588
589 while matches!(self.containers.last(), Some(Container::ListItem { .. })) {
590 self.close_containers_to(self.containers.depth() - 1);
591 }
592 while matches!(self.containers.last(), Some(Container::List { .. })) {
593 self.close_containers_to(self.containers.depth() - 1);
594 }
595
596 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
597 self.close_containers_to(self.containers.depth() - 1);
598 }
599
600 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
601 self.close_containers_to(self.containers.depth() - 1);
602 }
603
604 if definition_lists::in_definition_list(&self.containers)
608 && !matches!(
609 self.containers.last(),
610 Some(Container::DefinitionItem { .. })
611 )
612 {
613 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
614 self.containers.push(Container::DefinitionItem {});
615 }
616
617 if !definition_lists::in_definition_list(&self.containers) {
618 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
619 self.containers.push(Container::DefinitionList {});
620 }
621
622 if !matches!(
623 self.containers.last(),
624 Some(Container::DefinitionItem { .. })
625 ) {
626 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
627 self.containers.push(Container::DefinitionItem {});
628 }
629
630 self.builder.start_node(SyntaxKind::DEFINITION.into());
631
632 if let Some(indent_str) = indent_to_emit {
633 self.builder
634 .token(SyntaxKind::WHITESPACE.into(), indent_str);
635 }
636
637 emit_definition_marker(&mut self.builder, *marker_char, *indent);
638 let indent_bytes = byte_index_at_column(content, *indent);
639 if *spaces_after > 0 {
640 let space_start = indent_bytes + 1;
641 let space_end = space_start + *spaces_after;
642 if space_end <= content.len() {
643 self.builder.token(
644 SyntaxKind::WHITESPACE.into(),
645 &content[space_start..space_end],
646 );
647 }
648 }
649
650 if !*has_content {
651 let current_line = self.lines[self.pos];
652 let (_, newline_str) = strip_newline(current_line);
653 if !newline_str.is_empty() {
654 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
655 }
656 }
657
658 let content_col = *indent + 1 + *spaces_after_cols;
659 let content_start_bytes = indent_bytes + 1 + *spaces_after;
660 let after_marker_and_spaces = content.get(content_start_bytes..).unwrap_or("");
661 let mut plain_buffer = TextBuffer::new();
662 let mut definition_pushed = false;
663
664 if *has_content {
665 let current_line = self.lines[self.pos];
666 let (trimmed_line, _) = strip_newline(current_line);
667
668 let content_start = content_start_bytes.min(trimmed_line.len());
669 let content_slice = &trimmed_line[content_start..];
670 let content_line = ¤t_line[content_start_bytes.min(current_line.len())..];
671
672 let (blockquote_depth, inner_blockquote_content) =
673 count_blockquote_markers(content_line);
674
675 let should_start_list_from_first_line = self
676 .lines
677 .get(self.pos + 1)
678 .map(|next_line| {
679 let (next_without_newline, _) = strip_newline(next_line);
680 if next_without_newline.trim().is_empty() {
681 return false;
682 }
683
684 let (next_indent_cols, _) = leading_indent(next_without_newline);
685 next_indent_cols >= content_col
686 })
687 .unwrap_or(false);
688
689 if blockquote_depth > 0 {
690 self.containers.push(Container::Definition {
691 content_col,
692 plain_open: false,
693 plain_buffer: TextBuffer::new(),
694 });
695 definition_pushed = true;
696
697 let marker_info = parse_blockquote_marker_info(content_line);
698 for level in 0..blockquote_depth {
699 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
700 if let Some(info) = marker_info.get(level) {
701 blockquotes::emit_one_blockquote_marker(
702 &mut self.builder,
703 info.leading_spaces,
704 info.has_trailing_space,
705 );
706 }
707 self.containers.push(Container::BlockQuote {});
708 }
709
710 if !inner_blockquote_content.trim().is_empty() {
711 paragraphs::start_paragraph_if_needed(
712 &mut self.containers,
713 &mut self.builder,
714 );
715 paragraphs::append_paragraph_line(
716 &mut self.containers,
717 &mut self.builder,
718 inner_blockquote_content,
719 self.config,
720 );
721 }
722 } else if let Some(marker_match) =
723 try_parse_list_marker(content_slice, self.config)
724 && should_start_list_from_first_line
725 {
726 self.containers.push(Container::Definition {
727 content_col,
728 plain_open: false,
729 plain_buffer: TextBuffer::new(),
730 });
731 definition_pushed = true;
732
733 let (indent_cols, indent_bytes) = leading_indent(content_line);
734 self.builder.start_node(SyntaxKind::LIST.into());
735 self.containers.push(Container::List {
736 marker: marker_match.marker.clone(),
737 base_indent_cols: indent_cols,
738 has_blank_between_items: false,
739 });
740
741 let list_item = ListItemEmissionInput {
742 content: content_line,
743 marker_len: marker_match.marker_len,
744 spaces_after_cols: marker_match.spaces_after_cols,
745 spaces_after_bytes: marker_match.spaces_after_bytes,
746 indent_cols,
747 indent_bytes,
748 };
749
750 if let Some(nested_marker) = is_content_nested_bullet_marker(
751 content_line,
752 marker_match.marker_len,
753 marker_match.spaces_after_bytes,
754 ) {
755 lists::add_list_item_with_nested_empty_list(
756 &mut self.containers,
757 &mut self.builder,
758 &list_item,
759 nested_marker,
760 );
761 } else {
762 lists::add_list_item(
763 &mut self.containers,
764 &mut self.builder,
765 &list_item,
766 );
767 }
768 } else if let Some(fence) = code_blocks::try_parse_fence_open(content_slice) {
769 self.containers.push(Container::Definition {
770 content_col,
771 plain_open: false,
772 plain_buffer: TextBuffer::new(),
773 });
774 definition_pushed = true;
775
776 let bq_depth = self.current_blockquote_depth();
777 if let Some(indent_str) = indent_to_emit {
778 self.builder
779 .token(SyntaxKind::WHITESPACE.into(), indent_str);
780 }
781 let fence_line = current_line[content_start..].to_string();
782 let new_pos = if self.config.extensions.tex_math_gfm
783 && code_blocks::is_gfm_math_fence(&fence)
784 {
785 code_blocks::parse_fenced_math_block(
786 &mut self.builder,
787 &self.lines,
788 self.pos,
789 fence,
790 bq_depth,
791 content_col,
792 Some(&fence_line),
793 )
794 } else {
795 code_blocks::parse_fenced_code_block(
796 &mut self.builder,
797 &self.lines,
798 self.pos,
799 fence,
800 bq_depth,
801 content_col,
802 Some(&fence_line),
803 )
804 };
805 self.pos = new_pos - 1;
806 } else {
807 let (_, newline_str) = strip_newline(current_line);
808 let (content_without_newline, _) = strip_newline(after_marker_and_spaces);
809 if content_without_newline.is_empty() {
810 plain_buffer.push_line(newline_str);
811 } else {
812 let line_with_newline = if !newline_str.is_empty() {
813 format!("{}{}", content_without_newline, newline_str)
814 } else {
815 content_without_newline.to_string()
816 };
817 plain_buffer.push_line(line_with_newline);
818 }
819 }
820 }
821
822 if !definition_pushed {
823 self.containers.push(Container::Definition {
824 content_col,
825 plain_open: *has_content,
826 plain_buffer,
827 });
828 }
829 }
830 DefinitionPrepared::Term { blank_count } => {
831 self.emit_buffered_plain_if_needed();
832
833 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
834 self.close_containers_to(self.containers.depth() - 1);
835 }
836
837 if !definition_lists::in_definition_list(&self.containers) {
838 self.builder.start_node(SyntaxKind::DEFINITION_LIST.into());
839 self.containers.push(Container::DefinitionList {});
840 }
841
842 while matches!(
843 self.containers.last(),
844 Some(Container::Definition { .. }) | Some(Container::DefinitionItem { .. })
845 ) {
846 self.close_containers_to(self.containers.depth() - 1);
847 }
848
849 self.builder.start_node(SyntaxKind::DEFINITION_ITEM.into());
850 self.containers.push(Container::DefinitionItem {});
851
852 emit_term(&mut self.builder, content, self.config);
853
854 for i in 0..*blank_count {
855 let blank_pos = self.pos + 1 + i;
856 if blank_pos < self.lines.len() {
857 let blank_line = self.lines[blank_pos];
858 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
859 self.builder
860 .token(SyntaxKind::BLANK_LINE.into(), blank_line);
861 self.builder.finish_node();
862 }
863 }
864 self.pos += *blank_count;
865 }
866 }
867 }
868
869 fn blockquote_marker_info(
871 &self,
872 payload: Option<&BlockQuotePrepared>,
873 line: &str,
874 ) -> Vec<marker_utils::BlockQuoteMarkerInfo> {
875 payload
876 .map(|payload| payload.marker_info.clone())
877 .unwrap_or_else(|| parse_blockquote_marker_info(line))
878 }
879
880 fn emit_blockquote_markers(
881 &mut self,
882 marker_info: &[marker_utils::BlockQuoteMarkerInfo],
883 depth: usize,
884 ) {
885 for i in 0..depth {
886 if let Some(info) = marker_info.get(i) {
887 blockquotes::emit_one_blockquote_marker(
888 &mut self.builder,
889 info.leading_spaces,
890 info.has_trailing_space,
891 );
892 }
893 }
894 }
895
896 fn current_blockquote_depth(&self) -> usize {
897 blockquotes::current_blockquote_depth(&self.containers)
898 }
899
900 fn emit_or_buffer_blockquote_marker(
905 &mut self,
906 leading_spaces: usize,
907 has_trailing_space: bool,
908 ) {
909 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
911 paragraphs::append_paragraph_marker(
913 &mut self.containers,
914 leading_spaces,
915 has_trailing_space,
916 );
917 } else {
918 blockquotes::emit_one_blockquote_marker(
920 &mut self.builder,
921 leading_spaces,
922 has_trailing_space,
923 );
924 }
925 }
926
927 fn parse_document_stack(&mut self) {
928 self.builder.start_node(SyntaxKind::DOCUMENT.into());
929
930 log::debug!("Starting document parse");
931
932 while self.pos < self.lines.len() {
935 let line = self.lines[self.pos];
936
937 log::debug!("Parsing line {}: {}", self.pos + 1, line);
938
939 if self.parse_line(line) {
940 continue;
941 }
942 self.pos += 1;
943 }
944
945 self.close_containers_to(0);
946 self.builder.finish_node(); }
948
949 fn parse_line(&mut self, line: &str) -> bool {
951 let (bq_depth, inner_content) = count_blockquote_markers(line);
953 let current_bq_depth = self.current_blockquote_depth();
954
955 let has_blank_before = self.pos == 0 || self.lines[self.pos - 1].trim().is_empty();
956 let mut blockquote_match: Option<PreparedBlockMatch> = None;
957 let dispatcher_ctx = if current_bq_depth == 0 {
958 Some(BlockContext {
959 content: line,
960 has_blank_before,
961 has_blank_before_strict: has_blank_before,
962 at_document_start: self.pos == 0,
963 in_fenced_div: self.in_fenced_div(),
964 blockquote_depth: current_bq_depth,
965 config: self.config,
966 content_indent: 0,
967 indent_to_emit: None,
968 list_indent_info: None,
969 in_list: lists::in_list(&self.containers),
970 next_line: if self.pos + 1 < self.lines.len() {
971 Some(self.lines[self.pos + 1])
972 } else {
973 None
974 },
975 })
976 } else {
977 None
978 };
979
980 let blockquote_payload = if let Some(dispatcher_ctx) = dispatcher_ctx.as_ref() {
981 self.block_registry
982 .detect_prepared(dispatcher_ctx, &self.lines, self.pos)
983 .and_then(|prepared| {
984 if matches!(prepared.effect, BlockEffect::OpenBlockQuote) {
985 blockquote_match = Some(prepared);
986 blockquote_match.as_ref().and_then(|prepared| {
987 prepared
988 .payload
989 .as_ref()
990 .and_then(|payload| payload.downcast_ref::<BlockQuotePrepared>())
991 .cloned()
992 })
993 } else {
994 None
995 }
996 })
997 } else {
998 None
999 };
1000
1001 log::debug!(
1002 "parse_line [{}]: bq_depth={}, current_bq={}, depth={}, line={:?}",
1003 self.pos,
1004 bq_depth,
1005 current_bq_depth,
1006 self.containers.depth(),
1007 line.trim_end()
1008 );
1009
1010 let is_blank = line.trim_end_matches('\n').trim().is_empty()
1015 || (bq_depth > 0 && inner_content.trim_end_matches('\n').trim().is_empty());
1016
1017 if is_blank {
1018 if self.is_paragraph_open()
1019 && paragraphs::has_open_inline_math_environment(&self.containers)
1020 {
1021 paragraphs::append_paragraph_line(
1022 &mut self.containers,
1023 &mut self.builder,
1024 line,
1025 self.config,
1026 );
1027 self.pos += 1;
1028 return true;
1029 }
1030
1031 self.close_paragraph_if_open();
1033
1034 self.emit_buffered_plain_if_needed();
1038
1039 if bq_depth > current_bq_depth {
1045 for _ in current_bq_depth..bq_depth {
1047 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1048 self.containers.push(Container::BlockQuote {});
1049 }
1050 } else if bq_depth < current_bq_depth {
1051 self.close_blockquotes_to_depth(bq_depth);
1053 }
1054
1055 let mut peek = self.pos + 1;
1057 while peek < self.lines.len() && self.lines[peek].trim().is_empty() {
1058 peek += 1;
1059 }
1060
1061 let levels_to_keep = if peek < self.lines.len() {
1063 ContinuationPolicy::new(self.config, &self.block_registry).compute_levels_to_keep(
1064 self.current_blockquote_depth(),
1065 &self.containers,
1066 &self.lines,
1067 peek,
1068 self.lines[peek],
1069 )
1070 } else {
1071 0
1072 };
1073 log::trace!(
1074 "Blank line: depth={}, levels_to_keep={}, next='{}'",
1075 self.containers.depth(),
1076 levels_to_keep,
1077 if peek < self.lines.len() {
1078 self.lines[peek]
1079 } else {
1080 "<EOF>"
1081 }
1082 );
1083
1084 while self.containers.depth() > levels_to_keep {
1088 match self.containers.last() {
1089 Some(Container::ListItem { .. }) => {
1090 log::debug!(
1092 "Closing ListItem at blank line (levels_to_keep={} < depth={})",
1093 levels_to_keep,
1094 self.containers.depth()
1095 );
1096 self.close_containers_to(self.containers.depth() - 1);
1097 }
1098 Some(Container::List { .. })
1099 | Some(Container::FootnoteDefinition { .. })
1100 | Some(Container::Alert { .. })
1101 | Some(Container::Paragraph { .. })
1102 | Some(Container::Definition { .. })
1103 | Some(Container::DefinitionItem { .. })
1104 | Some(Container::DefinitionList { .. }) => {
1105 log::debug!(
1106 "Closing {:?} at blank line (depth {} > levels_to_keep {})",
1107 self.containers.last(),
1108 self.containers.depth(),
1109 levels_to_keep
1110 );
1111
1112 self.close_containers_to(self.containers.depth() - 1);
1113 }
1114 _ => break,
1115 }
1116 }
1117
1118 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1122 self.emit_list_item_buffer_if_needed();
1123 }
1124
1125 if bq_depth > 0 {
1127 let marker_info = self.blockquote_marker_info(blockquote_payload.as_ref(), line);
1128 self.emit_blockquote_markers(&marker_info, bq_depth);
1129 }
1130
1131 self.builder.start_node(SyntaxKind::BLANK_LINE.into());
1132 self.builder
1133 .token(SyntaxKind::BLANK_LINE.into(), inner_content);
1134 self.builder.finish_node();
1135
1136 self.pos += 1;
1137 return true;
1138 }
1139
1140 if bq_depth > current_bq_depth {
1142 if self.config.extensions.blank_before_blockquote
1145 && current_bq_depth == 0
1146 && !blockquote_payload
1147 .as_ref()
1148 .map(|payload| payload.can_start)
1149 .unwrap_or_else(|| blockquotes::can_start_blockquote(self.pos, &self.lines))
1150 {
1151 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1153 paragraphs::append_paragraph_line(
1154 &mut self.containers,
1155 &mut self.builder,
1156 line,
1157 self.config,
1158 );
1159 self.pos += 1;
1160 return true;
1161 }
1162
1163 let can_nest = if current_bq_depth > 0 {
1166 if self.config.extensions.blank_before_blockquote {
1167 matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1169 || (self.pos > 0 && {
1170 let prev_line = self.lines[self.pos - 1];
1171 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1172 prev_bq_depth >= current_bq_depth && prev_inner.trim().is_empty()
1173 })
1174 } else {
1175 true
1176 }
1177 } else {
1178 blockquote_payload
1179 .as_ref()
1180 .map(|payload| payload.can_nest)
1181 .unwrap_or(true)
1182 };
1183
1184 if !can_nest {
1185 let content_at_current_depth =
1188 blockquotes::strip_n_blockquote_markers(line, current_bq_depth);
1189
1190 let marker_info = self.blockquote_marker_info(blockquote_payload.as_ref(), line);
1192 for i in 0..current_bq_depth {
1193 if let Some(info) = marker_info.get(i) {
1194 self.emit_or_buffer_blockquote_marker(
1195 info.leading_spaces,
1196 info.has_trailing_space,
1197 );
1198 }
1199 }
1200
1201 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1202 paragraphs::append_paragraph_line(
1204 &mut self.containers,
1205 &mut self.builder,
1206 content_at_current_depth,
1207 self.config,
1208 );
1209 self.pos += 1;
1210 return true;
1211 } else {
1212 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1214 paragraphs::append_paragraph_line(
1215 &mut self.containers,
1216 &mut self.builder,
1217 content_at_current_depth,
1218 self.config,
1219 );
1220 self.pos += 1;
1221 return true;
1222 }
1223 }
1224
1225 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1227 self.close_containers_to(self.containers.depth() - 1);
1228 }
1229
1230 let marker_info = self.blockquote_marker_info(blockquote_payload.as_ref(), line);
1232
1233 if let (Some(dispatcher_ctx), Some(prepared)) =
1234 (dispatcher_ctx.as_ref(), blockquote_match.as_ref())
1235 {
1236 let _ = self.block_registry.parse_prepared(
1237 prepared,
1238 dispatcher_ctx,
1239 &mut self.builder,
1240 &self.lines,
1241 self.pos,
1242 );
1243 for _ in 0..bq_depth {
1244 self.containers.push(Container::BlockQuote {});
1245 }
1246 } else {
1247 for level in 0..current_bq_depth {
1249 if let Some(info) = marker_info.get(level) {
1250 self.emit_or_buffer_blockquote_marker(
1251 info.leading_spaces,
1252 info.has_trailing_space,
1253 );
1254 }
1255 }
1256
1257 for level in current_bq_depth..bq_depth {
1259 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1260
1261 if let Some(info) = marker_info.get(level) {
1263 blockquotes::emit_one_blockquote_marker(
1264 &mut self.builder,
1265 info.leading_spaces,
1266 info.has_trailing_space,
1267 );
1268 }
1269
1270 self.containers.push(Container::BlockQuote {});
1271 }
1272 }
1273
1274 return self.parse_inner_content(inner_content, Some(inner_content));
1277 } else if bq_depth < current_bq_depth {
1278 if bq_depth == 0 {
1281 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1283 paragraphs::append_paragraph_line(
1284 &mut self.containers,
1285 &mut self.builder,
1286 line,
1287 self.config,
1288 );
1289 self.pos += 1;
1290 return true;
1291 }
1292
1293 if lists::in_blockquote_list(&self.containers)
1296 && let Some(marker_match) = try_parse_list_marker(line, self.config)
1297 {
1298 let (indent_cols, indent_bytes) = leading_indent(line);
1299 if let Some(level) = lists::find_matching_list_level(
1300 &self.containers,
1301 &marker_match.marker,
1302 indent_cols,
1303 ) {
1304 self.close_containers_to(level + 1);
1307
1308 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1310 self.close_containers_to(self.containers.depth() - 1);
1311 }
1312 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1313 self.close_containers_to(self.containers.depth() - 1);
1314 }
1315
1316 if let Some(nested_marker) = is_content_nested_bullet_marker(
1318 line,
1319 marker_match.marker_len,
1320 marker_match.spaces_after_bytes,
1321 ) {
1322 let list_item = ListItemEmissionInput {
1323 content: line,
1324 marker_len: marker_match.marker_len,
1325 spaces_after_cols: marker_match.spaces_after_cols,
1326 spaces_after_bytes: marker_match.spaces_after_bytes,
1327 indent_cols,
1328 indent_bytes,
1329 };
1330 lists::add_list_item_with_nested_empty_list(
1331 &mut self.containers,
1332 &mut self.builder,
1333 &list_item,
1334 nested_marker,
1335 );
1336 } else {
1337 let list_item = ListItemEmissionInput {
1338 content: line,
1339 marker_len: marker_match.marker_len,
1340 spaces_after_cols: marker_match.spaces_after_cols,
1341 spaces_after_bytes: marker_match.spaces_after_bytes,
1342 indent_cols,
1343 indent_bytes,
1344 };
1345 lists::add_list_item(
1346 &mut self.containers,
1347 &mut self.builder,
1348 &list_item,
1349 );
1350 }
1351 self.pos += 1;
1352 return true;
1353 }
1354 }
1355 }
1356
1357 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1359 self.close_containers_to(self.containers.depth() - 1);
1360 }
1361
1362 self.close_blockquotes_to_depth(bq_depth);
1364
1365 if bq_depth > 0 {
1367 let marker_info = parse_blockquote_marker_info(line);
1369 for i in 0..bq_depth {
1370 if let Some(info) = marker_info.get(i) {
1371 self.emit_or_buffer_blockquote_marker(
1372 info.leading_spaces,
1373 info.has_trailing_space,
1374 );
1375 }
1376 }
1377 return self.parse_inner_content(inner_content, Some(inner_content));
1379 } else {
1380 return self.parse_inner_content(line, None);
1382 }
1383 } else if bq_depth > 0 {
1384 let mut list_item_continuation = false;
1386
1387 if matches!(
1390 self.containers.last(),
1391 Some(Container::ListItem { content_col: _, .. })
1392 ) {
1393 let (indent_cols, _) = leading_indent(inner_content);
1394 let content_indent = self.content_container_indent_to_strip();
1395 let effective_indent = indent_cols.saturating_sub(content_indent);
1396 let content_col = match self.containers.last() {
1397 Some(Container::ListItem { content_col, .. }) => *content_col,
1398 _ => 0,
1399 };
1400
1401 let is_new_item_at_outer_level =
1403 if try_parse_list_marker(inner_content, self.config).is_some() {
1404 effective_indent < content_col
1405 } else {
1406 false
1407 };
1408
1409 if is_new_item_at_outer_level || effective_indent < content_col {
1413 log::debug!(
1414 "Closing ListItem: is_new_item={}, effective_indent={} < content_col={}",
1415 is_new_item_at_outer_level,
1416 effective_indent,
1417 content_col
1418 );
1419 self.close_containers_to(self.containers.depth() - 1);
1420 } else {
1421 log::debug!(
1422 "Keeping ListItem: effective_indent={} >= content_col={}",
1423 effective_indent,
1424 content_col
1425 );
1426 list_item_continuation = true;
1427 }
1428 }
1429
1430 if list_item_continuation && code_blocks::try_parse_fence_open(inner_content).is_some()
1434 {
1435 list_item_continuation = false;
1436 }
1437
1438 if !list_item_continuation {
1439 let marker_info = parse_blockquote_marker_info(line);
1440 for i in 0..bq_depth {
1441 if let Some(info) = marker_info.get(i) {
1442 self.emit_or_buffer_blockquote_marker(
1443 info.leading_spaces,
1444 info.has_trailing_space,
1445 );
1446 }
1447 }
1448 }
1449 let line_to_append = if list_item_continuation {
1452 Some(line)
1453 } else {
1454 Some(inner_content)
1455 };
1456 return self.parse_inner_content(inner_content, line_to_append);
1457 }
1458
1459 if current_bq_depth > 0 {
1462 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1464 paragraphs::append_paragraph_line(
1465 &mut self.containers,
1466 &mut self.builder,
1467 line,
1468 self.config,
1469 );
1470 self.pos += 1;
1471 return true;
1472 }
1473
1474 if lists::in_blockquote_list(&self.containers)
1476 && let Some(marker_match) = try_parse_list_marker(line, self.config)
1477 {
1478 let (indent_cols, indent_bytes) = leading_indent(line);
1479 if let Some(level) = lists::find_matching_list_level(
1480 &self.containers,
1481 &marker_match.marker,
1482 indent_cols,
1483 ) {
1484 self.close_containers_to(level + 1);
1486
1487 if matches!(self.containers.last(), Some(Container::Paragraph { .. })) {
1489 self.close_containers_to(self.containers.depth() - 1);
1490 }
1491 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
1492 self.close_containers_to(self.containers.depth() - 1);
1493 }
1494
1495 if let Some(nested_marker) = is_content_nested_bullet_marker(
1497 line,
1498 marker_match.marker_len,
1499 marker_match.spaces_after_bytes,
1500 ) {
1501 let list_item = ListItemEmissionInput {
1502 content: line,
1503 marker_len: marker_match.marker_len,
1504 spaces_after_cols: marker_match.spaces_after_cols,
1505 spaces_after_bytes: marker_match.spaces_after_bytes,
1506 indent_cols,
1507 indent_bytes,
1508 };
1509 lists::add_list_item_with_nested_empty_list(
1510 &mut self.containers,
1511 &mut self.builder,
1512 &list_item,
1513 nested_marker,
1514 );
1515 } else {
1516 let list_item = ListItemEmissionInput {
1517 content: line,
1518 marker_len: marker_match.marker_len,
1519 spaces_after_cols: marker_match.spaces_after_cols,
1520 spaces_after_bytes: marker_match.spaces_after_bytes,
1521 indent_cols,
1522 indent_bytes,
1523 };
1524 lists::add_list_item(&mut self.containers, &mut self.builder, &list_item);
1525 }
1526 self.pos += 1;
1527 return true;
1528 }
1529 }
1530 }
1531
1532 self.parse_inner_content(line, None)
1534 }
1535
1536 fn content_container_indent_to_strip(&self) -> usize {
1538 self.containers
1539 .stack
1540 .iter()
1541 .filter_map(|c| match c {
1542 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
1543 Container::Definition { content_col, .. } => Some(*content_col),
1544 _ => None,
1545 })
1546 .sum()
1547 }
1548
1549 fn parse_inner_content(&mut self, content: &str, line_to_append: Option<&str>) -> bool {
1555 log::debug!(
1556 "parse_inner_content [{}]: depth={}, last={:?}, content={:?}",
1557 self.pos,
1558 self.containers.depth(),
1559 self.containers.last(),
1560 content.trim_end()
1561 );
1562 let content_indent = self.content_container_indent_to_strip();
1565 let (stripped_content, indent_to_emit) = if content_indent > 0 {
1566 let (indent_cols, _) = leading_indent(content);
1567 if indent_cols >= content_indent {
1568 let idx = byte_index_at_column(content, content_indent);
1569 (&content[idx..], Some(&content[..idx]))
1570 } else {
1571 let trimmed_start = content.trim_start();
1573 let ws_len = content.len() - trimmed_start.len();
1574 if ws_len > 0 {
1575 (trimmed_start, Some(&content[..ws_len]))
1576 } else {
1577 (content, None)
1578 }
1579 }
1580 } else {
1581 (content, None)
1582 };
1583
1584 if self.config.extensions.alerts
1585 && self.current_blockquote_depth() > 0
1586 && !self.in_active_alert()
1587 && !self.is_paragraph_open()
1588 && let Some(marker) = Self::alert_marker_from_content(stripped_content)
1589 {
1590 let (_, newline_str) = strip_newline(stripped_content);
1591 self.builder.start_node(SyntaxKind::ALERT.into());
1592 self.builder.token(SyntaxKind::ALERT_MARKER.into(), marker);
1593 if !newline_str.is_empty() {
1594 self.builder.token(SyntaxKind::NEWLINE.into(), newline_str);
1595 }
1596 self.containers.push(Container::Alert {
1597 blockquote_depth: self.current_blockquote_depth(),
1598 });
1599 self.pos += 1;
1600 return true;
1601 }
1602
1603 if matches!(self.containers.last(), Some(Container::Definition { .. })) {
1607 let is_definition_marker =
1608 definition_lists::try_parse_definition_marker(stripped_content).is_some()
1609 && !stripped_content.starts_with(':');
1610 if content_indent == 0 && is_definition_marker {
1611 } else {
1613 let policy = ContinuationPolicy::new(self.config, &self.block_registry);
1614
1615 if policy.definition_plain_can_continue(
1616 stripped_content,
1617 content,
1618 content_indent,
1619 &BlockContext {
1620 content: stripped_content,
1621 has_blank_before: self.pos == 0
1622 || self.lines[self.pos - 1].trim().is_empty(),
1623 has_blank_before_strict: self.pos == 0
1624 || self.lines[self.pos - 1].trim().is_empty(),
1625 at_document_start: self.pos == 0 && self.current_blockquote_depth() == 0,
1626 in_fenced_div: self.in_fenced_div(),
1627 blockquote_depth: self.current_blockquote_depth(),
1628 config: self.config,
1629 content_indent,
1630 indent_to_emit: None,
1631 list_indent_info: None,
1632 in_list: lists::in_list(&self.containers),
1633 next_line: if self.pos + 1 < self.lines.len() {
1634 Some(self.lines[self.pos + 1])
1635 } else {
1636 None
1637 },
1638 },
1639 &self.lines,
1640 self.pos,
1641 ) {
1642 let content_line = stripped_content;
1643 let (text_without_newline, newline_str) = strip_newline(content_line);
1644 let indent_prefix = if !text_without_newline.trim().is_empty() {
1645 indent_to_emit.unwrap_or("")
1646 } else {
1647 ""
1648 };
1649 let content_line = format!("{}{}", indent_prefix, text_without_newline);
1650
1651 if let Some(Container::Definition {
1652 plain_open,
1653 plain_buffer,
1654 ..
1655 }) = self.containers.stack.last_mut()
1656 {
1657 let line_with_newline = if !newline_str.is_empty() {
1658 format!("{}{}", content_line, newline_str)
1659 } else {
1660 content_line
1661 };
1662 plain_buffer.push_line(line_with_newline);
1663 *plain_open = true;
1664 }
1665
1666 self.pos += 1;
1667 return true;
1668 }
1669 }
1670 }
1671
1672 if content_indent > 0 {
1675 let (bq_depth, inner_content) = count_blockquote_markers(stripped_content);
1676 let current_bq_depth = self.current_blockquote_depth();
1677
1678 if bq_depth > 0 {
1679 self.emit_buffered_plain_if_needed();
1682 self.emit_list_item_buffer_if_needed();
1683
1684 self.close_paragraph_if_open();
1687
1688 if bq_depth > current_bq_depth {
1689 let marker_info = parse_blockquote_marker_info(stripped_content);
1690
1691 for level in current_bq_depth..bq_depth {
1693 self.builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
1694
1695 if level == current_bq_depth
1696 && let Some(indent_str) = indent_to_emit
1697 {
1698 self.builder
1699 .token(SyntaxKind::WHITESPACE.into(), indent_str);
1700 }
1701
1702 if let Some(info) = marker_info.get(level) {
1703 blockquotes::emit_one_blockquote_marker(
1704 &mut self.builder,
1705 info.leading_spaces,
1706 info.has_trailing_space,
1707 );
1708 }
1709
1710 self.containers.push(Container::BlockQuote {});
1711 }
1712 } else if bq_depth < current_bq_depth {
1713 self.close_blockquotes_to_depth(bq_depth);
1714 } else {
1715 let marker_info = parse_blockquote_marker_info(stripped_content);
1717 self.emit_blockquote_markers(&marker_info, bq_depth);
1718 }
1719
1720 return self.parse_inner_content(inner_content, Some(inner_content));
1721 }
1722 }
1723
1724 let content = stripped_content;
1726
1727 if self.is_paragraph_open()
1728 && paragraphs::has_open_inline_math_environment(&self.containers)
1729 {
1730 paragraphs::append_paragraph_line(
1731 &mut self.containers,
1732 &mut self.builder,
1733 line_to_append.unwrap_or(self.lines[self.pos]),
1734 self.config,
1735 );
1736 self.pos += 1;
1737 return true;
1738 }
1739
1740 use super::blocks::lists;
1744 use super::blocks::paragraphs;
1745 let list_indent_info = if lists::in_list(&self.containers) {
1746 let content_col = paragraphs::current_content_col(&self.containers);
1747 if content_col > 0 {
1748 Some(super::block_dispatcher::ListIndentInfo { content_col })
1749 } else {
1750 None
1751 }
1752 } else {
1753 None
1754 };
1755
1756 let next_line = if self.pos + 1 < self.lines.len() {
1757 Some(count_blockquote_markers(self.lines[self.pos + 1]).1)
1760 } else {
1761 None
1762 };
1763
1764 let current_bq_depth = self.current_blockquote_depth();
1765 if let Some(alert_bq_depth) = self.active_alert_blockquote_depth()
1766 && current_bq_depth < alert_bq_depth
1767 {
1768 while matches!(self.containers.last(), Some(Container::Alert { .. })) {
1769 self.close_containers_to(self.containers.depth() - 1);
1770 }
1771 }
1772
1773 let dispatcher_ctx = BlockContext {
1774 content,
1775 has_blank_before: false, has_blank_before_strict: false, at_document_start: false, in_fenced_div: self.in_fenced_div(),
1779 blockquote_depth: current_bq_depth,
1780 config: self.config,
1781 content_indent,
1782 indent_to_emit,
1783 list_indent_info,
1784 in_list: lists::in_list(&self.containers),
1785 next_line,
1786 };
1787
1788 let mut dispatcher_ctx = dispatcher_ctx;
1791
1792 let dispatcher_match =
1795 self.block_registry
1796 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos);
1797
1798 let after_metadata_block = std::mem::replace(&mut self.after_metadata_block, false);
1804 let has_blank_before = if self.pos == 0 || after_metadata_block {
1805 true
1806 } else {
1807 let prev_line = self.lines[self.pos - 1];
1808 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1809 let (prev_inner_no_nl, _) = strip_newline(prev_inner);
1810 let prev_is_fenced_div_open = self.config.extensions.fenced_divs
1811 && fenced_divs::try_parse_div_fence_open(
1812 strip_n_blockquote_markers(prev_inner_no_nl, prev_bq_depth).trim_start(),
1813 )
1814 .is_some();
1815
1816 prev_line.trim().is_empty()
1817 || prev_is_fenced_div_open
1818 || matches!(self.containers.last(), Some(Container::BlockQuote { .. }))
1819 };
1820
1821 let at_document_start = self.pos == 0 && current_bq_depth == 0;
1824
1825 let prev_line_blank = if self.pos > 0 {
1826 let prev_line = self.lines[self.pos - 1];
1827 let (prev_bq_depth, prev_inner) = count_blockquote_markers(prev_line);
1828 prev_line.trim().is_empty() || (prev_bq_depth > 0 && prev_inner.trim().is_empty())
1829 } else {
1830 false
1831 };
1832 let has_blank_before_strict = at_document_start || prev_line_blank;
1833
1834 dispatcher_ctx.has_blank_before = has_blank_before;
1835 dispatcher_ctx.has_blank_before_strict = has_blank_before_strict;
1836 dispatcher_ctx.at_document_start = at_document_start;
1837
1838 let dispatcher_match =
1839 if dispatcher_ctx.has_blank_before || dispatcher_ctx.at_document_start {
1840 self.block_registry
1842 .detect_prepared(&dispatcher_ctx, &self.lines, self.pos)
1843 } else {
1844 dispatcher_match
1845 };
1846
1847 if has_blank_before {
1848 if let Some(env_name) = extract_environment_name(content)
1849 && is_inline_math_environment(&env_name)
1850 {
1851 if !self.is_paragraph_open() {
1852 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
1853 }
1854 paragraphs::append_paragraph_line(
1855 &mut self.containers,
1856 &mut self.builder,
1857 line_to_append.unwrap_or(self.lines[self.pos]),
1858 self.config,
1859 );
1860 self.pos += 1;
1861 return true;
1862 }
1863
1864 if let Some(block_match) = dispatcher_match.as_ref() {
1865 let detection = block_match.detection;
1866
1867 match detection {
1868 BlockDetectionResult::YesCanInterrupt => {
1869 self.emit_list_item_buffer_if_needed();
1870 if self.is_paragraph_open() {
1871 self.close_containers_to(self.containers.depth() - 1);
1872 }
1873 }
1874 BlockDetectionResult::Yes => {
1875 self.prepare_for_block_element();
1876 }
1877 BlockDetectionResult::No => unreachable!(),
1878 }
1879
1880 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
1881 self.close_containers_to_fenced_div();
1882 }
1883
1884 let lines_consumed = self.block_registry.parse_prepared(
1885 block_match,
1886 &dispatcher_ctx,
1887 &mut self.builder,
1888 &self.lines,
1889 self.pos,
1890 );
1891
1892 if matches!(
1893 self.block_registry.parser_name(block_match),
1894 "yaml_metadata" | "pandoc_title_block" | "mmd_title_block"
1895 ) {
1896 self.after_metadata_block = true;
1897 }
1898
1899 match block_match.effect {
1900 BlockEffect::None => {}
1901 BlockEffect::OpenFencedDiv => {
1902 self.containers.push(Container::FencedDiv {});
1903 }
1904 BlockEffect::CloseFencedDiv => {
1905 self.close_fenced_div();
1906 }
1907 BlockEffect::OpenFootnoteDefinition => {
1908 self.handle_footnote_open_effect(block_match, content);
1909 }
1910 BlockEffect::OpenList => {
1911 self.handle_list_open_effect(block_match, content, indent_to_emit);
1912 }
1913 BlockEffect::OpenDefinitionList => {
1914 self.handle_definition_list_effect(block_match, content, indent_to_emit);
1915 }
1916 BlockEffect::OpenBlockQuote => {
1917 }
1919 }
1920
1921 if lines_consumed == 0 {
1922 log::warn!(
1923 "block parser made no progress at line {} (parser={})",
1924 self.pos + 1,
1925 self.block_registry.parser_name(block_match)
1926 );
1927 return false;
1928 }
1929
1930 self.pos += lines_consumed;
1931 return true;
1932 }
1933 } else if let Some(block_match) = dispatcher_match.as_ref() {
1934 let parser_name = self.block_registry.parser_name(block_match);
1937 match block_match.detection {
1938 BlockDetectionResult::YesCanInterrupt => {
1939 if matches!(block_match.effect, BlockEffect::OpenFencedDiv)
1940 && self.is_paragraph_open()
1941 {
1942 if !self.is_paragraph_open() {
1944 paragraphs::start_paragraph_if_needed(
1945 &mut self.containers,
1946 &mut self.builder,
1947 );
1948 }
1949 paragraphs::append_paragraph_line(
1950 &mut self.containers,
1951 &mut self.builder,
1952 line_to_append.unwrap_or(self.lines[self.pos]),
1953 self.config,
1954 );
1955 self.pos += 1;
1956 return true;
1957 }
1958
1959 if matches!(block_match.effect, BlockEffect::OpenList)
1960 && self.is_paragraph_open()
1961 && !lists::in_list(&self.containers)
1962 && self.content_container_indent_to_strip() == 0
1963 {
1964 paragraphs::append_paragraph_line(
1966 &mut self.containers,
1967 &mut self.builder,
1968 line_to_append.unwrap_or(self.lines[self.pos]),
1969 self.config,
1970 );
1971 self.pos += 1;
1972 return true;
1973 }
1974
1975 self.emit_list_item_buffer_if_needed();
1976 if self.is_paragraph_open() {
1977 self.close_containers_to(self.containers.depth() - 1);
1978 }
1979 }
1980 BlockDetectionResult::Yes => {
1981 if parser_name == "fenced_div_open" && self.is_paragraph_open() {
1984 if !self.is_paragraph_open() {
1985 paragraphs::start_paragraph_if_needed(
1986 &mut self.containers,
1987 &mut self.builder,
1988 );
1989 }
1990 paragraphs::append_paragraph_line(
1991 &mut self.containers,
1992 &mut self.builder,
1993 line_to_append.unwrap_or(self.lines[self.pos]),
1994 self.config,
1995 );
1996 self.pos += 1;
1997 return true;
1998 }
1999 }
2000 BlockDetectionResult::No => unreachable!(),
2001 }
2002
2003 if !matches!(block_match.detection, BlockDetectionResult::No) {
2004 if matches!(block_match.effect, BlockEffect::CloseFencedDiv) {
2005 self.close_containers_to_fenced_div();
2006 }
2007
2008 let lines_consumed = self.block_registry.parse_prepared(
2009 block_match,
2010 &dispatcher_ctx,
2011 &mut self.builder,
2012 &self.lines,
2013 self.pos,
2014 );
2015
2016 match block_match.effect {
2017 BlockEffect::None => {}
2018 BlockEffect::OpenFencedDiv => {
2019 self.containers.push(Container::FencedDiv {});
2020 }
2021 BlockEffect::CloseFencedDiv => {
2022 self.close_fenced_div();
2023 }
2024 BlockEffect::OpenFootnoteDefinition => {
2025 self.handle_footnote_open_effect(block_match, content);
2026 }
2027 BlockEffect::OpenList => {
2028 self.handle_list_open_effect(block_match, content, indent_to_emit);
2029 }
2030 BlockEffect::OpenDefinitionList => {
2031 self.handle_definition_list_effect(block_match, content, indent_to_emit);
2032 }
2033 BlockEffect::OpenBlockQuote => {
2034 }
2036 }
2037
2038 if lines_consumed == 0 {
2039 log::warn!(
2040 "block parser made no progress at line {} (parser={})",
2041 self.pos + 1,
2042 self.block_registry.parser_name(block_match)
2043 );
2044 return false;
2045 }
2046
2047 self.pos += lines_consumed;
2048 return true;
2049 }
2050 }
2051
2052 if self.config.extensions.line_blocks
2054 && (has_blank_before || self.pos == 0)
2055 && try_parse_line_block_start(content).is_some()
2056 && try_parse_line_block_start(self.lines[self.pos]).is_some()
2060 {
2061 log::debug!("Parsed line block at line {}", self.pos);
2062 self.close_paragraph_if_open();
2064
2065 let new_pos = parse_line_block(&self.lines, self.pos, &mut self.builder, self.config);
2066 if new_pos > self.pos {
2067 self.pos = new_pos;
2068 return true;
2069 }
2070 }
2071
2072 if matches!(self.containers.last(), Some(Container::ListItem { .. })) {
2075 log::debug!(
2076 "Inside ListItem - buffering content: {:?}",
2077 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2078 );
2079 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2081
2082 if let Some(Container::ListItem { buffer, .. }) = self.containers.stack.last_mut() {
2084 buffer.push_text(line);
2085 }
2086
2087 self.pos += 1;
2088 return true;
2089 }
2090
2091 log::debug!(
2092 "Not in ListItem - creating paragraph for: {:?}",
2093 line_to_append.unwrap_or(self.lines[self.pos]).trim_end()
2094 );
2095 paragraphs::start_paragraph_if_needed(&mut self.containers, &mut self.builder);
2097 let line = line_to_append.unwrap_or(self.lines[self.pos]);
2100 paragraphs::append_paragraph_line(
2101 &mut self.containers,
2102 &mut self.builder,
2103 line,
2104 self.config,
2105 );
2106 self.pos += 1;
2107 true
2108 }
2109
2110 fn fenced_div_container_index(&self) -> Option<usize> {
2111 self.containers
2112 .stack
2113 .iter()
2114 .rposition(|c| matches!(c, Container::FencedDiv { .. }))
2115 }
2116
2117 fn close_containers_to_fenced_div(&mut self) {
2118 if let Some(index) = self.fenced_div_container_index() {
2119 self.close_containers_to(index + 1);
2120 }
2121 }
2122
2123 fn close_fenced_div(&mut self) {
2124 if let Some(index) = self.fenced_div_container_index() {
2125 self.close_containers_to(index);
2126 }
2127 }
2128
2129 fn in_fenced_div(&self) -> bool {
2130 self.containers
2131 .stack
2132 .iter()
2133 .any(|c| matches!(c, Container::FencedDiv { .. }))
2134 }
2135}