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::utils::inline_emission;
9use crate::parser::utils::text_buffer::ParagraphBuffer;
10use crate::syntax::SyntaxKind;
11use rowan::GreenNodeBuilder;
12
13#[derive(Debug, Clone)]
15pub(crate) enum ListItemContent {
16 Text(String),
18 BlockquoteMarker {
20 leading_spaces: usize,
21 has_trailing_space: bool,
22 },
23}
24
25#[derive(Debug, Default, Clone)]
33pub(crate) struct ListItemBuffer {
34 segments: Vec<ListItemContent>,
36}
37
38impl ListItemBuffer {
39 pub(crate) fn new() -> Self {
41 Self {
42 segments: Vec::new(),
43 }
44 }
45
46 pub(crate) fn push_text(&mut self, text: impl Into<String>) {
48 let text = text.into();
49 if text.is_empty() {
50 return;
51 }
52 self.segments.push(ListItemContent::Text(text));
53 }
54
55 pub(crate) fn push_blockquote_marker(
56 &mut self,
57 leading_spaces: usize,
58 has_trailing_space: bool,
59 ) {
60 self.segments.push(ListItemContent::BlockquoteMarker {
61 leading_spaces,
62 has_trailing_space,
63 });
64 }
65
66 pub(crate) fn is_empty(&self) -> bool {
68 self.segments.is_empty()
69 }
70
71 pub(crate) fn segment_count(&self) -> usize {
73 self.segments.len()
74 }
75
76 pub(crate) fn has_blank_lines_between_content(&self) -> bool {
81 log::trace!(
82 "has_blank_lines_between_content: segments={} result=false",
83 self.segments.len()
84 );
85
86 false
87 }
88
89 fn get_text_for_parsing(&self) -> String {
91 let mut result = String::new();
92 for segment in &self.segments {
93 if let ListItemContent::Text(text) = segment {
94 result.push_str(text);
95 }
96 }
97 result
98 }
99
100 fn to_paragraph_buffer(&self) -> ParagraphBuffer {
101 let mut paragraph_buffer = ParagraphBuffer::new();
102 for segment in &self.segments {
103 match segment {
104 ListItemContent::Text(text) => paragraph_buffer.push_text(text),
105 ListItemContent::BlockquoteMarker {
106 leading_spaces,
107 has_trailing_space,
108 } => paragraph_buffer.push_marker(*leading_spaces, *has_trailing_space),
109 }
110 }
111 paragraph_buffer
112 }
113
114 pub(crate) fn emit_as_block(
119 &self,
120 builder: &mut GreenNodeBuilder<'static>,
121 use_paragraph: bool,
122 config: &ParserOptions,
123 ) {
124 if self.is_empty() {
125 return;
126 }
127
128 let text = self.get_text_for_parsing();
130
131 if !text.is_empty() {
132 let line_without_newline = text
133 .strip_suffix("\r\n")
134 .or_else(|| text.strip_suffix('\n'));
135 if let Some(line) = line_without_newline
136 && !line.contains('\n')
137 && !line.contains('\r')
138 && let Some(level) = try_parse_atx_heading(line)
139 {
140 emit_atx_heading(builder, &text, level, config);
141 return;
142 }
143 }
144
145 let block_kind = if use_paragraph {
146 SyntaxKind::PARAGRAPH
147 } else {
148 SyntaxKind::PLAIN
149 };
150
151 builder.start_node(block_kind.into());
152
153 let paragraph_buffer = self.to_paragraph_buffer();
154 if !paragraph_buffer.is_empty() {
155 paragraph_buffer.emit_with_inlines(builder, config);
156 } else if !text.is_empty() {
157 inline_emission::emit_inlines(builder, &text, config);
158 }
159
160 builder.finish_node(); }
162
163 pub(crate) fn clear(&mut self) {
165 self.segments.clear();
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_new_buffer_is_empty() {
175 let buffer = ListItemBuffer::new();
176 assert!(buffer.is_empty());
177 assert!(!buffer.has_blank_lines_between_content());
178 }
179
180 #[test]
181 fn test_push_single_text() {
182 let mut buffer = ListItemBuffer::new();
183 buffer.push_text("Hello, world!");
184 assert!(!buffer.is_empty());
185 assert!(!buffer.has_blank_lines_between_content());
186 assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
187 }
188
189 #[test]
190 fn test_push_multiple_text_segments() {
191 let mut buffer = ListItemBuffer::new();
192 buffer.push_text("Line 1\n");
193 buffer.push_text("Line 2\n");
194 buffer.push_text("Line 3");
195 assert_eq!(buffer.get_text_for_parsing(), "Line 1\nLine 2\nLine 3");
196 }
197
198 #[test]
199 fn test_clear_buffer() {
200 let mut buffer = ListItemBuffer::new();
201 buffer.push_text("Some text");
202 assert!(!buffer.is_empty());
203
204 buffer.clear();
205 assert!(buffer.is_empty());
206 assert_eq!(buffer.get_text_for_parsing(), "");
207 }
208
209 #[test]
210 fn test_empty_text_ignored() {
211 let mut buffer = ListItemBuffer::new();
212 buffer.push_text("");
213 assert!(buffer.is_empty());
214 }
215}