marco_core/parser/blocks/
shared.rs1pub use crate::parser::shared::{
5 opt_span, opt_span_range_inclusive as opt_span_range, to_parser_span,
6 to_parser_span_range_inclusive as to_parser_span_range, GrammarSpan,
7};
8#[cfg(test)]
11use nom_locate::LocatedSpan;
12
13pub fn dedent_list_item_content(content: &str, content_indent: usize) -> String {
31 let had_trailing_newline = content.ends_with('\n');
32
33 let mut result = content
34 .lines()
35 .map(|line| {
36 let mut expanded = String::with_capacity(line.len() * 2);
39 let mut column = content_indent; for ch in line.chars() {
42 if ch == '\t' {
43 let spaces_to_add = 4 - (column % 4);
45 for _ in 0..spaces_to_add {
46 expanded.push(' ');
47 column += 1;
48 }
49 } else {
50 expanded.push(ch);
51 column += 1;
52 }
53 }
54
55 let mut spaces_to_strip = 0;
57 let mut chars = expanded.chars();
58 while spaces_to_strip < content_indent {
59 match chars.next() {
60 Some(' ') => spaces_to_strip += 1,
61 _ => break,
62 }
63 }
64
65 expanded[spaces_to_strip..].to_string()
67 })
68 .collect::<Vec<_>>()
69 .join("\n");
70
71 if had_trailing_newline {
73 result.push('\n');
74 }
75
76 result
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn smoke_test_to_parser_span() {
85 let input = "line1\nline2\nline3";
86 let span = LocatedSpan::new(input);
87 let parser_span = to_parser_span(span);
88 assert_eq!(parser_span.start.line, 1);
89 assert_eq!(parser_span.start.column, 1);
90 }
91
92 #[test]
93 fn test_to_parser_span_single_line_ascii() {
94 let input = LocatedSpan::new("**bold**");
96 let span = to_parser_span(input);
97
98 assert_eq!(span.start.line, 1);
100 assert_eq!(span.start.column, 1);
101
102 assert_eq!(span.end.line, 1);
104 assert_eq!(span.end.column, 9);
105 }
106
107 #[test]
108 fn test_to_parser_span_single_line_utf8() {
109 let input = LocatedSpan::new("Tëst");
112 let span = to_parser_span(input);
113
114 assert_eq!(span.start.line, 1);
115 assert_eq!(span.start.column, 1);
116
117 assert_eq!(span.end.line, 1);
119 assert_eq!(span.end.column, 6);
120 }
121
122 #[test]
123 fn test_to_parser_span_single_line_emoji() {
124 let input = LocatedSpan::new("🎨");
126 let span = to_parser_span(input);
127
128 assert_eq!(span.start.line, 1);
129 assert_eq!(span.start.column, 1);
130
131 assert_eq!(span.end.line, 1);
133 assert_eq!(span.end.column, 5);
134 }
135
136 #[test]
137 fn test_to_parser_span_multi_line_code_block() {
138 let input = LocatedSpan::new("```rust\nfn main() {}\n```");
141 let span = to_parser_span(input);
142
143 assert_eq!(span.start.line, 1);
145 assert_eq!(span.start.column, 1);
146
147 assert_eq!(span.end.line, 3);
149
150 assert_eq!(span.end.column, 4);
152 }
153
154 #[test]
155 fn test_to_parser_span_ends_with_newline() {
156 let input = LocatedSpan::new("line1\nline2\n");
158 let span = to_parser_span(input);
159
160 assert_eq!(span.start.line, 1);
161 assert_eq!(span.start.column, 1);
162
163 assert_eq!(span.end.line, 3);
165 assert_eq!(span.end.column, 1);
166 }
167
168 #[test]
169 fn test_to_parser_span_multi_line_utf8() {
170 let input = LocatedSpan::new("Line1\nTëst");
173 let span = to_parser_span(input);
174
175 assert_eq!(span.start.line, 1);
176 assert_eq!(span.start.column, 1);
177
178 assert_eq!(span.end.line, 2);
180
181 assert_eq!(span.end.column, 6);
183 }
184
185 #[test]
186 fn test_to_parser_span_offset_correctness() {
187 let input = LocatedSpan::new("abc\ndef");
189 let span = to_parser_span(input);
190
191 assert_eq!(span.start.offset, 0);
193
194 assert_eq!(span.end.offset, 7);
196 }
197
198 #[test]
199 fn smoke_test_dedent_simple() {
200 let content = " Line 1\n Line 2\n";
201 let result = dedent_list_item_content(content, 2);
202 assert_eq!(result, "Line 1\nLine 2\n");
203 }
204
205 #[test]
206 fn smoke_test_dedent_preserves_extra_indent() {
207 let content = " Line 1\n Indented\n";
208 let result = dedent_list_item_content(content, 2);
209 assert_eq!(result, "Line 1\n Indented\n");
210 }
211
212 #[test]
213 fn smoke_test_dedent_preserves_blank_lines() {
214 let content = " Line 1\n\n Line 2\n";
215 let result = dedent_list_item_content(content, 2);
216 assert_eq!(result, "Line 1\n\nLine 2\n");
217 }
218
219 #[test]
220 fn smoke_test_dedent_with_tabs() {
221 let content = "\tLine 1\n\tLine 2\n";
222 let result = dedent_list_item_content(content, 4);
223 assert_eq!(result, "Line 1\nLine 2\n");
224 }
225}