panache_parser/parser/blocks/
indented_code.rs1use crate::syntax::SyntaxKind;
10use rowan::GreenNodeBuilder;
11
12use crate::parser::utils::helpers::strip_newline;
13
14pub(crate) fn is_indented_code_line(content: &str) -> bool {
18 if content.is_empty() {
19 return false;
20 }
21 let (cols, _) = leading_indent(content);
22 cols >= 4
23}
24
25pub(crate) fn parse_indented_code_block(
42 builder: &mut GreenNodeBuilder<'static>,
43 lines: &[&str],
44 start_pos: usize,
45 bq_depth: usize,
46 base_indent: usize,
47) -> usize {
48 use super::blockquotes::{
49 count_blockquote_markers, emit_one_blockquote_marker, strip_n_blockquote_markers,
50 };
51 use crate::parser::utils::marker_utils::parse_blockquote_marker_info;
52
53 builder.start_node(SyntaxKind::CODE_BLOCK.into());
54 builder.start_node(SyntaxKind::CODE_CONTENT.into());
55
56 let mut current_pos = start_pos;
57 let code_indent = base_indent + 4;
59
60 while current_pos < lines.len() {
61 let line = lines[current_pos];
62
63 let (line_bq_depth, _) = count_blockquote_markers(line);
65 let inner = if bq_depth > 0 {
66 strip_n_blockquote_markers(line, bq_depth)
67 } else {
68 line
69 };
70
71 if line_bq_depth < bq_depth {
73 break;
74 }
75
76 if inner.trim().is_empty() {
78 let mut look_pos = current_pos + 1;
80 let mut continues = false;
81 while look_pos < lines.len() {
82 let (look_bq_depth, look_inner) = count_blockquote_markers(lines[look_pos]);
83 if look_bq_depth < bq_depth {
84 break;
85 }
86 if look_inner.trim_end_matches('\n').trim().is_empty() {
87 look_pos += 1;
88 continue;
89 }
90 let (look_indent, _) = leading_indent(look_inner);
91 if look_indent >= code_indent {
92 continues = true;
93 }
94 break;
95 }
96 if !continues {
97 break;
98 }
99 if bq_depth > 0 && current_pos > start_pos {
100 let marker_info = parse_blockquote_marker_info(line);
101 for i in 0..bq_depth {
102 if let Some(info) = marker_info.get(i) {
103 emit_one_blockquote_marker(
104 builder,
105 info.leading_spaces,
106 info.has_trailing_space,
107 );
108 }
109 }
110 }
111 let (blank_content, newline_str) = strip_newline(inner);
112 if !blank_content.is_empty() {
113 builder.token(SyntaxKind::WHITESPACE.into(), blank_content);
114 }
115 builder.token(SyntaxKind::TEXT.into(), "");
116 builder.token(
117 SyntaxKind::NEWLINE.into(),
118 if newline_str.is_empty() {
119 "\n"
120 } else {
121 newline_str
122 },
123 );
124 current_pos += 1;
125 continue;
126 }
127
128 let (indent_cols, indent_bytes) = leading_indent(inner);
130 if indent_cols < code_indent {
131 break;
132 }
133
134 if bq_depth > 0 && current_pos > start_pos {
135 let marker_info = parse_blockquote_marker_info(line);
136 for i in 0..bq_depth {
137 if let Some(info) = marker_info.get(i) {
138 emit_one_blockquote_marker(
139 builder,
140 info.leading_spaces,
141 info.has_trailing_space,
142 );
143 }
144 }
145 }
146
147 if indent_bytes > 0 {
150 let indent_str = &inner[..indent_bytes];
151 builder.token(SyntaxKind::WHITESPACE.into(), indent_str);
152 }
153
154 let content = &inner[indent_bytes..];
156
157 let (content_without_newline, newline_str) = strip_newline(content);
159
160 if !content_without_newline.is_empty() {
161 builder.token(SyntaxKind::TEXT.into(), content_without_newline);
162 }
163
164 if !newline_str.is_empty() {
165 builder.token(SyntaxKind::NEWLINE.into(), newline_str);
166 }
167
168 current_pos += 1;
169 }
170
171 builder.finish_node(); builder.finish_node(); current_pos
175}
176
177use crate::parser::utils::container_stack::leading_indent;
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_is_indented_code_line() {
185 assert!(is_indented_code_line(" code"));
186 assert!(is_indented_code_line(" code"));
187 assert!(is_indented_code_line("\tcode"));
188 assert!(!is_indented_code_line(" not enough"));
189 assert!(!is_indented_code_line(""));
190 assert!(!is_indented_code_line("no indent"));
191 }
192
193 #[test]
194 fn test_parse_simple_code_block() {
195 let input = vec![" code line 1", " code line 2"];
196 let mut builder = GreenNodeBuilder::new();
197 let new_pos = parse_indented_code_block(&mut builder, &input, 0, 0, 0);
198 assert_eq!(new_pos, 2);
199 }
200
201 #[test]
202 fn test_parse_code_block_with_blank_line() {
203 let input = vec![" code line 1", "", " code line 2"];
204 let mut builder = GreenNodeBuilder::new();
205 let new_pos = parse_indented_code_block(&mut builder, &input, 0, 0, 0);
206 assert_eq!(new_pos, 3);
207 }
208
209 #[test]
210 fn test_parse_code_block_stops_at_unindented() {
211 let input = vec![" code line 1", " code line 2", "not code"];
212 let mut builder = GreenNodeBuilder::new();
213 let new_pos = parse_indented_code_block(&mut builder, &input, 0, 0, 0);
214 assert_eq!(new_pos, 2);
215 }
216
217 #[test]
218 fn test_parse_code_block_with_tab() {
219 let input = vec!["\tcode with tab", "\tanother line"];
220 let mut builder = GreenNodeBuilder::new();
221 let new_pos = parse_indented_code_block(&mut builder, &input, 0, 0, 0);
222 assert_eq!(new_pos, 2);
223 }
224}