panache_parser/parser/utils/
text_buffer.rs1use super::inline_emission;
7use crate::options::ParserOptions;
8use crate::parser::inlines::sink::MarkerInjectingSink;
9use rowan::GreenNodeBuilder;
10
11#[derive(Debug, Default, Clone)]
16pub(crate) struct TextBuffer {
17 lines: Vec<String>,
19}
20
21impl TextBuffer {
22 pub(crate) fn new() -> Self {
24 Self { lines: Vec::new() }
25 }
26
27 pub(crate) fn push_line(&mut self, text: impl Into<String>) {
31 self.lines.push(text.into());
32 }
33
34 pub(crate) fn get_accumulated_text(&self) -> String {
39 self.lines.concat()
40 }
41
42 pub(crate) fn clear(&mut self) {
44 self.lines.clear();
45 }
46
47 pub(crate) fn is_empty(&self) -> bool {
49 self.lines.is_empty()
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_new_buffer_is_empty() {
59 let buffer = TextBuffer::new();
60 assert!(buffer.is_empty());
61 assert!(buffer.is_empty());
62 assert_eq!(buffer.get_accumulated_text(), "");
63 }
64
65 #[test]
66 fn test_push_single_line() {
67 let mut buffer = TextBuffer::new();
68 buffer.push_line("Hello, world!");
69 assert!(!buffer.is_empty());
70 assert_eq!(buffer.get_accumulated_text(), "Hello, world!");
71 }
72
73 #[test]
74 fn test_push_multiple_lines() {
75 let mut buffer = TextBuffer::new();
76 buffer.push_line("Line 1\n");
77 buffer.push_line("Line 2\n");
78 buffer.push_line("Line 3");
79 assert_eq!(buffer.get_accumulated_text(), "Line 1\nLine 2\nLine 3");
80 }
81
82 #[test]
83 fn test_clear_buffer() {
84 let mut buffer = TextBuffer::new();
85 buffer.push_line("Line 1");
86 buffer.push_line("Line 2");
87 buffer.clear();
88 assert!(buffer.is_empty());
89 assert_eq!(buffer.get_accumulated_text(), "");
90 }
91
92 #[test]
93 fn test_reuse_after_clear() {
94 let mut buffer = TextBuffer::new();
95
96 buffer.push_line("First paragraph\n");
98 buffer.push_line("continues here");
99 assert_eq!(
100 buffer.get_accumulated_text(),
101 "First paragraph\ncontinues here"
102 );
103
104 buffer.clear();
106 buffer.push_line("Second paragraph\n");
107 buffer.push_line("also continues");
108 assert_eq!(
109 buffer.get_accumulated_text(),
110 "Second paragraph\nalso continues"
111 );
112 }
113
114 #[test]
115 fn test_empty_lines() {
116 let mut buffer = TextBuffer::new();
117 buffer.push_line("\n");
118 buffer.push_line("Non-empty\n");
119 buffer.push_line("");
120 assert!(!buffer.is_empty());
121 assert_eq!(buffer.get_accumulated_text(), "\nNon-empty\n");
122 }
123
124 #[test]
125 fn test_whitespace_preserved() {
126 let mut buffer = TextBuffer::new();
127 buffer.push_line(" Leading spaces\n");
128 buffer.push_line("Trailing spaces \n");
129 buffer.push_line("\tTab at start");
130 assert_eq!(
131 buffer.get_accumulated_text(),
132 " Leading spaces\nTrailing spaces \n\tTab at start"
133 );
134 }
135
136 #[test]
137 fn test_default_is_empty() {
138 let buffer = TextBuffer::default();
139 assert!(buffer.is_empty());
140 assert_eq!(buffer.get_accumulated_text(), "");
141 }
142}
143
144#[derive(Debug, Clone)]
150pub(crate) enum ParagraphSegment {
151 Text(String),
153 BlockquoteMarker {
155 leading_spaces: usize,
156 has_trailing_space: bool,
157 },
158}
159
160#[derive(Debug, Default, Clone)]
165pub(crate) struct ParagraphBuffer {
166 segments: Vec<ParagraphSegment>,
168}
169
170impl ParagraphBuffer {
171 pub(crate) fn new() -> Self {
173 Self {
174 segments: Vec::new(),
175 }
176 }
177
178 pub(crate) fn push_text(&mut self, text: &str) {
182 if text.is_empty() {
183 return;
184 }
185 match self.segments.last_mut() {
186 Some(ParagraphSegment::Text(existing)) => {
187 existing.push_str(text);
188 }
189 _ => {
190 self.segments.push(ParagraphSegment::Text(text.to_string()));
191 }
192 }
193 }
194
195 pub(crate) fn push_marker(&mut self, leading_spaces: usize, has_trailing_space: bool) {
197 self.segments.push(ParagraphSegment::BlockquoteMarker {
198 leading_spaces,
199 has_trailing_space,
200 });
201 }
202
203 pub(crate) fn get_text_for_parsing(&self) -> String {
205 let mut result = String::new();
206 for segment in &self.segments {
207 if let ParagraphSegment::Text(text) = segment {
208 result.push_str(text);
209 }
210 }
211 result
212 }
213
214 fn get_marker_positions(&self) -> Vec<(usize, usize, bool)> {
218 let mut positions = Vec::new();
219 let mut byte_offset = 0;
220
221 for segment in &self.segments {
222 match segment {
223 ParagraphSegment::Text(text) => {
224 byte_offset += text.len();
225 }
226 ParagraphSegment::BlockquoteMarker {
227 leading_spaces,
228 has_trailing_space,
229 } => {
230 positions.push((byte_offset, *leading_spaces, *has_trailing_space));
231 }
232 }
233 }
234 positions
235 }
236
237 pub(crate) fn emit_with_inlines(
244 &self,
245 builder: &mut GreenNodeBuilder<'static>,
246 config: &ParserOptions,
247 suppress_footnote_refs: bool,
248 ) {
249 let text = self.get_text_for_parsing();
250 if text.is_empty() && self.segments.is_empty() {
251 return;
252 }
253
254 let marker_positions = self.get_marker_positions();
255
256 if marker_positions.is_empty() {
257 inline_emission::emit_inlines(builder, &text, config, suppress_footnote_refs);
259 } else {
260 self.emit_with_markers(
262 builder,
263 &text,
264 &marker_positions,
265 config,
266 suppress_footnote_refs,
267 );
268 }
269 }
270
271 fn emit_with_markers(
279 &self,
280 builder: &mut GreenNodeBuilder<'static>,
281 text: &str,
282 marker_positions: &[(usize, usize, bool)],
283 config: &ParserOptions,
284 suppress_footnote_refs: bool,
285 ) {
286 let mut sink = MarkerInjectingSink::new(builder, marker_positions);
287 inline_emission::emit_inlines(&mut sink, text, config, suppress_footnote_refs);
288 sink.finish();
289 }
290
291 pub(crate) fn is_empty(&self) -> bool {
293 self.segments.is_empty()
294 }
295}
296
297#[cfg(test)]
298mod paragraph_buffer_tests {
299 use super::*;
300
301 #[test]
302 fn test_new_buffer_is_empty() {
303 let buffer = ParagraphBuffer::new();
304 assert!(buffer.is_empty());
305 assert_eq!(buffer.get_text_for_parsing(), "");
306 }
307
308 #[test]
309 fn test_push_text_single() {
310 let mut buffer = ParagraphBuffer::new();
311 buffer.push_text("Hello, world!");
312 assert!(!buffer.is_empty());
313 assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
314 }
315
316 #[test]
317 fn test_push_text_concatenates() {
318 let mut buffer = ParagraphBuffer::new();
319 buffer.push_text("Hello");
320 buffer.push_text(", ");
321 buffer.push_text("world!");
322 assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
323 assert_eq!(buffer.segments.len(), 1);
325 }
326
327 #[test]
328 fn test_push_marker_separates_text() {
329 let mut buffer = ParagraphBuffer::new();
330 buffer.push_text("Line 1\n");
331 buffer.push_marker(0, true);
332 buffer.push_text("Line 2\n");
333 assert_eq!(buffer.segments.len(), 3);
335 assert_eq!(buffer.get_text_for_parsing(), "Line 1\nLine 2\n");
336 }
337
338 #[test]
339 fn test_marker_positions() {
340 let mut buffer = ParagraphBuffer::new();
341 buffer.push_text("Line 1\n"); buffer.push_marker(0, true);
343 buffer.push_text("Line 2\n"); let positions = buffer.get_marker_positions();
346 assert_eq!(positions.len(), 1);
347 assert_eq!(positions[0], (7, 0, true)); }
349
350 #[test]
351 fn test_multiple_markers() {
352 let mut buffer = ParagraphBuffer::new();
353 buffer.push_text("A\n"); buffer.push_marker(0, true);
355 buffer.push_text("B\n"); buffer.push_marker(1, false);
357 buffer.push_text("C");
358
359 let positions = buffer.get_marker_positions();
360 assert_eq!(positions.len(), 2);
361 assert_eq!(positions[0], (2, 0, true)); assert_eq!(positions[1], (4, 1, false)); }
364
365 #[test]
366 fn test_empty_text_ignored() {
367 let mut buffer = ParagraphBuffer::new();
368 buffer.push_text("");
369 assert!(buffer.is_empty());
370 }
371}