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