pub use crate::parser::shared::{
opt_span, opt_span_range_inclusive as opt_span_range, to_parser_span,
to_parser_span_range_inclusive as to_parser_span_range, GrammarSpan,
};
#[cfg(test)]
use nom_locate::LocatedSpan;
pub fn dedent_list_item_content(content: &str, content_indent: usize) -> String {
let had_trailing_newline = content.ends_with('\n');
let mut result = content
.lines()
.map(|line| {
let mut expanded = String::with_capacity(line.len() * 2);
let mut column = content_indent;
for ch in line.chars() {
if ch == '\t' {
let spaces_to_add = 4 - (column % 4);
for _ in 0..spaces_to_add {
expanded.push(' ');
column += 1;
}
} else {
expanded.push(ch);
column += 1;
}
}
let mut spaces_to_strip = 0;
let mut chars = expanded.chars();
while spaces_to_strip < content_indent {
match chars.next() {
Some(' ') => spaces_to_strip += 1,
_ => break,
}
}
expanded[spaces_to_strip..].to_string()
})
.collect::<Vec<_>>()
.join("\n");
if had_trailing_newline {
result.push('\n');
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_test_to_parser_span() {
let input = "line1\nline2\nline3";
let span = LocatedSpan::new(input);
let parser_span = to_parser_span(span);
assert_eq!(parser_span.start.line, 1);
assert_eq!(parser_span.start.column, 1);
}
#[test]
fn test_to_parser_span_single_line_ascii() {
let input = LocatedSpan::new("**bold**");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 1);
assert_eq!(span.end.column, 9);
}
#[test]
fn test_to_parser_span_single_line_utf8() {
let input = LocatedSpan::new("Tëst");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 1);
assert_eq!(span.end.column, 6);
}
#[test]
fn test_to_parser_span_single_line_emoji() {
let input = LocatedSpan::new("🎨");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 1);
assert_eq!(span.end.column, 5);
}
#[test]
fn test_to_parser_span_multi_line_code_block() {
let input = LocatedSpan::new("```rust\nfn main() {}\n```");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 3);
assert_eq!(span.end.column, 4);
}
#[test]
fn test_to_parser_span_ends_with_newline() {
let input = LocatedSpan::new("line1\nline2\n");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 3);
assert_eq!(span.end.column, 1);
}
#[test]
fn test_to_parser_span_multi_line_utf8() {
let input = LocatedSpan::new("Line1\nTëst");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.line, 2);
assert_eq!(span.end.column, 6);
}
#[test]
fn test_to_parser_span_offset_correctness() {
let input = LocatedSpan::new("abc\ndef");
let span = to_parser_span(input);
assert_eq!(span.start.offset, 0);
assert_eq!(span.end.offset, 7);
}
#[test]
fn smoke_test_dedent_simple() {
let content = " Line 1\n Line 2\n";
let result = dedent_list_item_content(content, 2);
assert_eq!(result, "Line 1\nLine 2\n");
}
#[test]
fn smoke_test_dedent_preserves_extra_indent() {
let content = " Line 1\n Indented\n";
let result = dedent_list_item_content(content, 2);
assert_eq!(result, "Line 1\n Indented\n");
}
#[test]
fn smoke_test_dedent_preserves_blank_lines() {
let content = " Line 1\n\n Line 2\n";
let result = dedent_list_item_content(content, 2);
assert_eq!(result, "Line 1\n\nLine 2\n");
}
#[test]
fn smoke_test_dedent_with_tabs() {
let content = "\tLine 1\n\tLine 2\n";
let result = dedent_list_item_content(content, 4);
assert_eq!(result, "Line 1\nLine 2\n");
}
}