panache_parser/parser/blocks/
paragraphs.rs1use crate::options::ParserOptions;
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 update_display_math_dollar_state(
15 line_no_newline: &str,
16 open_display_math_dollar_count: &mut Option<usize>,
17) {
18 let trimmed = line_no_newline.trim();
19 if trimmed.len() < 2 || !trimmed.as_bytes().iter().all(|b| *b == b'$') {
20 return;
21 }
22
23 let run_len = trimmed.len();
24 if run_len < 2 {
25 return;
26 }
27
28 if let Some(open_len) = *open_display_math_dollar_count {
29 if run_len >= open_len {
30 *open_display_math_dollar_count = None;
31 }
32 } else {
33 *open_display_math_dollar_count = Some(run_len);
34 }
35}
36
37fn is_inside_footnote_definition(containers: &ContainerStack) -> bool {
38 containers
39 .stack
40 .iter()
41 .any(|c| matches!(c, Container::FootnoteDefinition { .. }))
42}
43
44fn extract_end_environment_name(line: &str) -> Option<&str> {
45 let trimmed = line.trim_start();
46 if !trimmed.starts_with("\\end{") {
47 return None;
48 }
49 let rest = &trimmed[5..];
50 let close = rest.find('}')?;
51 let name = &rest[..close];
52 if name.is_empty() {
53 return None;
54 }
55 Some(name)
56}
57
58pub(in crate::parser) fn start_paragraph_if_needed(
60 containers: &mut ContainerStack,
61 builder: &mut GreenNodeBuilder<'static>,
62) {
63 if !matches!(containers.last(), Some(Container::Paragraph { .. })) {
64 builder.start_node(SyntaxKind::PARAGRAPH.into());
65 containers.push(Container::Paragraph {
66 buffer: ParagraphBuffer::new(),
67 open_inline_math_envs: Vec::new(),
68 open_display_math_dollar_count: None,
69 });
70 }
71}
72
73pub(in crate::parser) fn append_paragraph_line(
75 containers: &mut ContainerStack,
76 _builder: &mut GreenNodeBuilder<'static>,
77 line: &str,
78 _config: &ParserOptions,
79) {
80 let in_footnote = is_inside_footnote_definition(containers);
81
82 if let Some(Container::Paragraph {
85 buffer,
86 open_inline_math_envs,
87 open_display_math_dollar_count,
88 }) = containers.stack.last_mut()
89 {
90 buffer.push_text(line);
91
92 let line_no_newline = line.trim_end_matches(&['\r', '\n'][..]);
93 if in_footnote {
94 update_display_math_dollar_state(line_no_newline, open_display_math_dollar_count);
95 } else {
96 *open_display_math_dollar_count = None;
97 }
98 if let Some(env_name) = extract_environment_name(line_no_newline)
99 && is_inline_math_environment(&env_name)
100 {
101 open_inline_math_envs.push(env_name);
102 return;
103 }
104
105 if let Some(end_name) = extract_end_environment_name(line_no_newline)
106 && open_inline_math_envs
107 .last()
108 .is_some_and(|open| open == end_name)
109 {
110 open_inline_math_envs.pop();
111 }
112 }
113}
114
115pub(in crate::parser) fn append_paragraph_marker(
121 containers: &mut ContainerStack,
122 leading_spaces: usize,
123 has_trailing_space: bool,
124) {
125 if let Some(Container::Paragraph { buffer, .. }) = containers.stack.last_mut() {
126 buffer.push_marker(leading_spaces, has_trailing_space);
127 }
128}
129
130pub(in crate::parser) fn has_open_inline_math_environment(containers: &ContainerStack) -> bool {
131 matches!(
132 containers.last(),
133 Some(Container::Paragraph {
134 open_inline_math_envs,
135 open_display_math_dollar_count,
136 ..
137 }) if !open_inline_math_envs.is_empty() || open_display_math_dollar_count.is_some()
138 )
139}
140
141pub(in crate::parser) fn current_content_col(containers: &ContainerStack) -> usize {
143 containers
144 .stack
145 .iter()
146 .rev()
147 .find_map(|c| match c {
148 Container::ListItem { content_col, .. } => Some(*content_col),
149 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
150 _ => None,
151 })
152 .unwrap_or(0)
153}