Skip to main content

panache_parser/parser/blocks/
paragraphs.rs

1//! Paragraph handling utilities.
2//!
3//! Note: Most paragraph logic is in the main Parser since paragraphs
4//! are tightly integrated with container handling.
5
6use crate::config::Config;
7use crate::syntax::SyntaxKind;
8use rowan::GreenNodeBuilder;
9
10use crate::parser::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
11use crate::parser::utils::container_stack::{Container, ContainerStack};
12use crate::parser::utils::text_buffer::ParagraphBuffer;
13
14fn extract_end_environment_name(line: &str) -> Option<&str> {
15    let trimmed = line.trim_start();
16    if !trimmed.starts_with("\\end{") {
17        return None;
18    }
19    let rest = &trimmed[5..];
20    let close = rest.find('}')?;
21    let name = &rest[..close];
22    if name.is_empty() {
23        return None;
24    }
25    Some(name)
26}
27
28/// Start a paragraph if not already in one.
29pub(in crate::parser) fn start_paragraph_if_needed(
30    containers: &mut ContainerStack,
31    builder: &mut GreenNodeBuilder<'static>,
32) {
33    if !matches!(containers.last(), Some(Container::Paragraph { .. })) {
34        builder.start_node(SyntaxKind::PARAGRAPH.into());
35        containers.push(Container::Paragraph {
36            buffer: ParagraphBuffer::new(),
37            open_inline_math_envs: Vec::new(),
38        });
39    }
40}
41
42/// Append a line to the current paragraph (preserving losslessness).
43pub(in crate::parser) fn append_paragraph_line(
44    containers: &mut ContainerStack,
45    _builder: &mut GreenNodeBuilder<'static>,
46    line: &str,
47    _config: &Config,
48) {
49    // Buffer the line (with newline for losslessness)
50    // Works for ALL paragraphs including those in blockquotes
51    if let Some(Container::Paragraph {
52        buffer,
53        open_inline_math_envs,
54    }) = containers.stack.last_mut()
55    {
56        buffer.push_text(line);
57
58        let line_no_newline = line.trim_end_matches(&['\r', '\n'][..]);
59        if let Some(env_name) = extract_environment_name(line_no_newline)
60            && is_inline_math_environment(&env_name)
61        {
62            open_inline_math_envs.push(env_name);
63            return;
64        }
65
66        if let Some(end_name) = extract_end_environment_name(line_no_newline)
67            && open_inline_math_envs
68                .last()
69                .is_some_and(|open| open == end_name)
70        {
71            open_inline_math_envs.pop();
72        }
73    }
74}
75
76/// Buffer a blockquote marker in the current paragraph.
77///
78/// Called when processing blockquote continuation lines while a paragraph is open
79/// and using integrated inline parsing. The marker will be emitted at the correct
80/// position when the paragraph is closed.
81pub(in crate::parser) fn append_paragraph_marker(
82    containers: &mut ContainerStack,
83    leading_spaces: usize,
84    has_trailing_space: bool,
85) {
86    if let Some(Container::Paragraph { buffer, .. }) = containers.stack.last_mut() {
87        buffer.push_marker(leading_spaces, has_trailing_space);
88    }
89}
90
91pub(in crate::parser) fn has_open_inline_math_environment(containers: &ContainerStack) -> bool {
92    matches!(
93        containers.last(),
94        Some(Container::Paragraph {
95            open_inline_math_envs,
96            ..
97        }) if !open_inline_math_envs.is_empty()
98    )
99}
100
101/// Get the current content column from the container stack.
102pub(in crate::parser) fn current_content_col(containers: &ContainerStack) -> usize {
103    containers
104        .stack
105        .iter()
106        .rev()
107        .find_map(|c| match c {
108            Container::ListItem { content_col, .. } => Some(*content_col),
109            Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
110            _ => None,
111        })
112        .unwrap_or(0)
113}