panache_parser/parser/blocks/
line_blocks.rs1use crate::options::ParserOptions;
2use crate::syntax::SyntaxKind;
3use rowan::GreenNodeBuilder;
4
5use crate::parser::utils::helpers::strip_newline;
6use crate::parser::utils::inline_emission;
7
8pub fn try_parse_line_block_start(line: &str) -> Option<()> {
11 let trimmed = line.trim_start();
12 if trimmed.starts_with("| ") || trimmed == "|" {
13 Some(())
14 } else {
15 None
16 }
17}
18
19pub fn parse_line_block(
22 lines: &[&str],
23 start_pos: usize,
24 builder: &mut GreenNodeBuilder<'static>,
25 config: &ParserOptions,
26) -> usize {
27 log::trace!("Parsing line block at line {}", start_pos + 1);
28
29 builder.start_node(SyntaxKind::LINE_BLOCK.into());
30
31 let mut pos = start_pos;
32
33 while pos < lines.len() {
34 let line = lines[pos];
35
36 if let Some(content_start) = parse_line_block_line_marker(line) {
38 builder.start_node(SyntaxKind::LINE_BLOCK_LINE.into());
40
41 builder.token(SyntaxKind::LINE_BLOCK_MARKER.into(), &line[..content_start]);
43
44 let content = &line[content_start..];
46
47 let (content_without_newline, newline_str) = strip_newline(content);
49
50 if !content_without_newline.is_empty() {
51 inline_emission::emit_inlines(builder, content_without_newline, config);
52 }
53
54 if !newline_str.is_empty() {
55 builder.token(SyntaxKind::NEWLINE.into(), newline_str);
56 }
57
58 builder.finish_node(); pos += 1;
60
61 while pos < lines.len() {
63 let next_line = lines[pos];
64
65 if next_line.starts_with(' ') && !next_line.trim_start().starts_with("| ") {
67 builder.start_node(SyntaxKind::LINE_BLOCK_LINE.into());
69
70 let (line_without_newline, newline_str) = strip_newline(next_line);
72
73 if !line_without_newline.is_empty() {
74 inline_emission::emit_inlines(builder, line_without_newline, config);
75 }
76
77 if !newline_str.is_empty() {
78 builder.token(SyntaxKind::NEWLINE.into(), newline_str);
79 }
80
81 builder.finish_node(); pos += 1;
83 } else {
84 break;
85 }
86 }
87 } else {
88 break;
90 }
91 }
92
93 builder.finish_node(); log::trace!("Parsed line block: lines {}-{}", start_pos + 1, pos);
96
97 pos
98}
99
100fn parse_line_block_line_marker(line: &str) -> Option<usize> {
103 let trimmed_start = line.len() - line.trim_start().len();
106 let after_indent = &line[trimmed_start..];
107
108 if after_indent.starts_with("| ") {
109 Some(trimmed_start + 2) } else if after_indent == "|" || after_indent == "|\n" {
111 Some(trimmed_start + 1) } else {
113 None
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_try_parse_line_block_start() {
123 assert!(try_parse_line_block_start("| Some text").is_some());
124 assert!(try_parse_line_block_start("| ").is_some());
125 assert!(try_parse_line_block_start("|").is_some()); assert!(try_parse_line_block_start(" | Some text").is_some());
127
128 assert!(try_parse_line_block_start("|No space").is_none());
130 assert!(try_parse_line_block_start("Regular text").is_none());
131 assert!(try_parse_line_block_start("").is_none());
132 }
133
134 #[test]
135 fn test_parse_line_block_marker() {
136 assert_eq!(parse_line_block_line_marker("| Some text"), Some(2));
137 assert_eq!(parse_line_block_line_marker("| "), Some(2));
138 assert_eq!(parse_line_block_line_marker("|"), Some(1)); assert_eq!(parse_line_block_line_marker(" | Indented"), Some(4));
140
141 assert_eq!(parse_line_block_line_marker("|No space"), None);
143 assert_eq!(parse_line_block_line_marker("Regular"), None);
144 }
145
146 #[test]
147 fn test_simple_line_block() {
148 let input = vec!["| Line one", "| Line two", "| Line three"];
149
150 let mut builder = GreenNodeBuilder::new();
151 let new_pos = parse_line_block(&input, 0, &mut builder, &ParserOptions::default());
152
153 assert_eq!(new_pos, 3);
154 }
155
156 #[test]
157 fn test_line_block_with_continuation() {
158 let input = vec![
159 "| This is a long line",
160 " that continues here",
161 "| Second line",
162 ];
163
164 let mut builder = GreenNodeBuilder::new();
165 let new_pos = parse_line_block(&input, 0, &mut builder, &ParserOptions::default());
166
167 assert_eq!(new_pos, 3);
168 }
169
170 #[test]
171 fn test_line_block_with_indentation() {
172 let input = vec!["| First line", "| Indented line", "| Back to normal"];
173
174 let mut builder = GreenNodeBuilder::new();
175 let new_pos = parse_line_block(&input, 0, &mut builder, &ParserOptions::default());
176
177 assert_eq!(new_pos, 3);
178 }
179
180 #[test]
181 fn test_line_block_stops_at_non_line_block() {
182 let input = vec!["| Line one", "| Line two", "Regular paragraph"];
183
184 let mut builder = GreenNodeBuilder::new();
185 let new_pos = parse_line_block(&input, 0, &mut builder, &ParserOptions::default());
186
187 assert_eq!(new_pos, 2); }
189}