panache_parser/parser/utils/
list_item_buffer.rs1use crate::options::ParserOptions;
7use crate::parser::blocks::headings::{emit_atx_heading, try_parse_atx_heading};
8use crate::parser::blocks::horizontal_rules::{emit_horizontal_rule, try_parse_horizontal_rule};
9use crate::parser::utils::inline_emission;
10use crate::parser::utils::text_buffer::ParagraphBuffer;
11use crate::syntax::SyntaxKind;
12use rowan::GreenNodeBuilder;
13
14#[derive(Debug, Clone)]
16pub(crate) enum ListItemContent {
17 Text(String),
19 BlockquoteMarker {
21 leading_spaces: usize,
22 has_trailing_space: bool,
23 },
24}
25
26#[derive(Debug, Default, Clone)]
34pub(crate) struct ListItemBuffer {
35 segments: Vec<ListItemContent>,
37}
38
39impl ListItemBuffer {
40 pub(crate) fn new() -> Self {
42 Self {
43 segments: Vec::new(),
44 }
45 }
46
47 pub(crate) fn push_text(&mut self, text: impl Into<String>) {
49 let text = text.into();
50 if text.is_empty() {
51 return;
52 }
53 self.segments.push(ListItemContent::Text(text));
54 }
55
56 pub(crate) fn push_blockquote_marker(
57 &mut self,
58 leading_spaces: usize,
59 has_trailing_space: bool,
60 ) {
61 self.segments.push(ListItemContent::BlockquoteMarker {
62 leading_spaces,
63 has_trailing_space,
64 });
65 }
66
67 pub(crate) fn is_empty(&self) -> bool {
69 self.segments.is_empty()
70 }
71
72 pub(crate) fn segment_count(&self) -> usize {
74 self.segments.len()
75 }
76
77 pub(crate) fn first_text(&self) -> Option<&str> {
79 match self.segments.first()? {
80 ListItemContent::Text(t) => Some(t.as_str()),
81 ListItemContent::BlockquoteMarker { .. } => None,
82 }
83 }
84
85 pub(crate) fn has_blank_lines_between_content(&self) -> bool {
90 log::trace!(
91 "has_blank_lines_between_content: segments={} result=false",
92 self.segments.len()
93 );
94
95 false
96 }
97
98 fn get_text_for_parsing(&self) -> String {
100 let mut result = String::new();
101 for segment in &self.segments {
102 if let ListItemContent::Text(text) = segment {
103 result.push_str(text);
104 }
105 }
106 result
107 }
108
109 fn to_paragraph_buffer(&self) -> ParagraphBuffer {
110 let mut paragraph_buffer = ParagraphBuffer::new();
111 for segment in &self.segments {
112 match segment {
113 ListItemContent::Text(text) => paragraph_buffer.push_text(text),
114 ListItemContent::BlockquoteMarker {
115 leading_spaces,
116 has_trailing_space,
117 } => paragraph_buffer.push_marker(*leading_spaces, *has_trailing_space),
118 }
119 }
120 paragraph_buffer
121 }
122
123 pub(crate) fn emit_as_block(
128 &self,
129 builder: &mut GreenNodeBuilder<'static>,
130 use_paragraph: bool,
131 config: &ParserOptions,
132 ) {
133 if self.is_empty() {
134 return;
135 }
136
137 let text = self.get_text_for_parsing();
139
140 if !text.is_empty() {
141 let line_without_newline = text
142 .strip_suffix("\r\n")
143 .or_else(|| text.strip_suffix('\n'));
144 if let Some(line) = line_without_newline
145 && !line.contains('\n')
146 && !line.contains('\r')
147 {
148 if let Some(level) = try_parse_atx_heading(line) {
149 emit_atx_heading(builder, &text, level, config);
150 return;
151 }
152 if try_parse_horizontal_rule(line).is_some() {
153 emit_horizontal_rule(builder, &text);
154 return;
155 }
156 }
157 }
158
159 let block_kind = if use_paragraph {
160 SyntaxKind::PARAGRAPH
161 } else {
162 SyntaxKind::PLAIN
163 };
164
165 builder.start_node(block_kind.into());
166
167 let paragraph_buffer = self.to_paragraph_buffer();
168 if !paragraph_buffer.is_empty() {
169 paragraph_buffer.emit_with_inlines(builder, config);
170 } else if !text.is_empty() {
171 inline_emission::emit_inlines(builder, &text, config);
172 }
173
174 builder.finish_node(); }
176
177 pub(crate) fn clear(&mut self) {
179 self.segments.clear();
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_new_buffer_is_empty() {
189 let buffer = ListItemBuffer::new();
190 assert!(buffer.is_empty());
191 assert!(!buffer.has_blank_lines_between_content());
192 }
193
194 #[test]
195 fn test_push_single_text() {
196 let mut buffer = ListItemBuffer::new();
197 buffer.push_text("Hello, world!");
198 assert!(!buffer.is_empty());
199 assert!(!buffer.has_blank_lines_between_content());
200 assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
201 }
202
203 #[test]
204 fn test_push_multiple_text_segments() {
205 let mut buffer = ListItemBuffer::new();
206 buffer.push_text("Line 1\n");
207 buffer.push_text("Line 2\n");
208 buffer.push_text("Line 3");
209 assert_eq!(buffer.get_text_for_parsing(), "Line 1\nLine 2\nLine 3");
210 }
211
212 #[test]
213 fn test_clear_buffer() {
214 let mut buffer = ListItemBuffer::new();
215 buffer.push_text("Some text");
216 assert!(!buffer.is_empty());
217
218 buffer.clear();
219 assert!(buffer.is_empty());
220 assert_eq!(buffer.get_text_for_parsing(), "");
221 }
222
223 #[test]
224 fn test_empty_text_ignored() {
225 let mut buffer = ListItemBuffer::new();
226 buffer.push_text("");
227 assert!(buffer.is_empty());
228 }
229}