panache_parser/parser/blocks/
paragraphs.rs1use crate::options::ParserOptions;
7use rowan::GreenNodeBuilder;
8
9use crate::parser::blocks::raw_blocks::{extract_environment_name, is_inline_math_environment};
10use crate::parser::utils::container_stack::{Container, ContainerStack};
11use crate::parser::utils::helpers::trim_end_newlines;
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 {
20 return;
21 }
22
23 let run_len = trimmed.bytes().take_while(|b| *b == b'$').count();
24 if run_len < 2 {
25 return;
26 }
27
28 let rest = &trimmed[run_len..];
29 let is_pure_dollars = rest.is_empty();
30 let is_quarto_equation_attr_closer = rest.starts_with(char::is_whitespace) && {
31 let attr = rest.trim_start();
32 attr.starts_with('{') && attr.ends_with('}')
33 };
34
35 if let Some(open_len) = *open_display_math_dollar_count {
36 if (is_pure_dollars || is_quarto_equation_attr_closer) && run_len >= open_len {
37 *open_display_math_dollar_count = None;
38 }
39 } else if is_pure_dollars {
40 *open_display_math_dollar_count = Some(run_len);
41 }
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(
65 containers: &mut ContainerStack,
66 builder: &mut GreenNodeBuilder<'static>,
67) {
68 if !matches!(containers.last(), Some(Container::Paragraph { .. })) {
69 let start_checkpoint = builder.checkpoint();
70 containers.push(Container::Paragraph {
71 buffer: ParagraphBuffer::new(),
72 open_inline_math_envs: Vec::new(),
73 open_display_math_dollar_count: None,
74 start_checkpoint,
75 });
76 }
77}
78
79pub(in crate::parser) fn append_paragraph_line(
81 containers: &mut ContainerStack,
82 _builder: &mut GreenNodeBuilder<'static>,
83 line: &str,
84 _config: &ParserOptions,
85) {
86 if let Some(Container::Paragraph {
89 buffer,
90 open_inline_math_envs,
91 open_display_math_dollar_count,
92 ..
93 }) = containers.stack.last_mut()
94 {
95 buffer.push_text(line);
96
97 let line_no_newline = trim_end_newlines(line);
98 update_display_math_dollar_state(line_no_newline, open_display_math_dollar_count);
103 if let Some(env_name) = extract_environment_name(line_no_newline)
104 && is_inline_math_environment(env_name)
105 {
106 open_inline_math_envs.push(env_name.to_string());
107 return;
108 }
109
110 if let Some(end_name) = extract_end_environment_name(line_no_newline)
111 && open_inline_math_envs
112 .last()
113 .is_some_and(|open| open == end_name)
114 {
115 open_inline_math_envs.pop();
116 }
117 }
118}
119
120pub(in crate::parser) fn append_paragraph_marker(
126 containers: &mut ContainerStack,
127 leading_spaces: usize,
128 has_trailing_space: bool,
129) {
130 if let Some(Container::Paragraph { buffer, .. }) = containers.stack.last_mut() {
131 buffer.push_marker(leading_spaces, has_trailing_space);
132 }
133}
134
135pub(in crate::parser) fn has_open_inline_math_environment(containers: &ContainerStack) -> bool {
136 matches!(
137 containers.last(),
138 Some(Container::Paragraph {
139 open_inline_math_envs,
140 ..
141 }) if !open_inline_math_envs.is_empty()
142 )
143}
144
145pub(in crate::parser) fn has_open_display_math_dollars(containers: &ContainerStack) -> bool {
146 matches!(
147 containers.last(),
148 Some(Container::Paragraph {
149 open_display_math_dollar_count,
150 ..
151 }) if open_display_math_dollar_count.is_some()
152 )
153}
154
155pub(in crate::parser) fn current_content_col(containers: &ContainerStack) -> usize {
157 containers
158 .stack
159 .iter()
160 .rev()
161 .find_map(|c| match c {
162 Container::ListItem { content_col, .. } => Some(*content_col),
163 Container::FootnoteDefinition { content_col, .. } => Some(*content_col),
164 _ => None,
165 })
166 .unwrap_or(0)
167}