Skip to main content

panache_parser/parser/blocks/
blockquotes.rs

1//! Blockquote parsing utilities.
2//!
3//! Re-exports marker parsing functions from marker_utils for backward compatibility.
4
5use crate::syntax::SyntaxKind;
6use rowan::GreenNodeBuilder;
7
8use crate::parser::utils::container_stack::{Container, ContainerStack};
9
10pub(crate) use crate::parser::utils::marker_utils::{
11    count_blockquote_markers, try_parse_blockquote_marker,
12};
13
14/// Check if we need a blank line before starting a new blockquote.
15/// Returns true if a blockquote can start here.
16///
17/// `fenced_divs_enabled` lets the first child of a fenced div start a
18/// blockquote without a preceding blank line: Pandoc treats the line right
19/// after a `::: {...}` opener like the start of the document, so a flush
20/// blockquote there is still a blockquote (see issue #310).
21pub(in crate::parser) fn can_start_blockquote(
22    pos: usize,
23    lines: &[&str],
24    fenced_divs_enabled: bool,
25) -> bool {
26    // At start of document, no blank line needed
27    if pos == 0 {
28        return true;
29    }
30    // After a blank line, can start blockquote
31    if crate::parser::utils::helpers::is_blank_line(lines[pos - 1]) {
32        return true;
33    }
34    // First child of a fenced div: the opener line is not blank, but Pandoc
35    // treats the start of a div like the start of the document.
36    if fenced_divs_enabled
37        && crate::parser::blocks::fenced_divs::try_parse_div_fence_open(lines[pos - 1]).is_some()
38    {
39        return true;
40    }
41    // If we're already in a blockquote, nested blockquotes need blank line too
42    // (blank_before_blockquote extension)
43    false
44}
45
46/// Get the current blockquote depth from the container stack.
47pub(in crate::parser) fn current_blockquote_depth(containers: &ContainerStack) -> usize {
48    containers
49        .stack
50        .iter()
51        .filter(|c| matches!(c, Container::BlockQuote { .. }))
52        .count()
53}
54
55/// Strip exactly n blockquote markers from a line, returning the rest.
56pub(in crate::parser) fn strip_n_blockquote_markers(line: &str, n: usize) -> &str {
57    let mut remaining = line;
58    for _ in 0..n {
59        if let Some((_, content_start)) = try_parse_blockquote_marker(remaining) {
60            remaining = &remaining[content_start..];
61        } else {
62            break;
63        }
64    }
65    remaining
66}
67
68/// Emit one blockquote marker with its whitespace.
69pub(in crate::parser) fn emit_one_blockquote_marker(
70    builder: &mut GreenNodeBuilder<'static>,
71    leading_spaces: usize,
72    has_trailing_space: bool,
73) {
74    if leading_spaces > 0 {
75        builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(leading_spaces));
76    }
77    builder.token(SyntaxKind::BLOCK_QUOTE_MARKER.into(), ">");
78    if has_trailing_space {
79        builder.token(SyntaxKind::WHITESPACE.into(), " ");
80    }
81}