pub fn indent(s: &str, prefix: &str) -> String {
let mut result = String::new();
for line in s.lines() {
if line.chars().any(|c| !c.is_whitespace()) {
result.push_str(prefix);
result.push_str(line);
}
result.push('\n');
}
result
}
pub fn dedent(s: &str) -> String {
let mut prefix = "";
let mut lines = s.lines();
for line in &mut lines {
let mut whitespace_idx = line.len();
for (idx, ch) in line.char_indices() {
if !ch.is_whitespace() {
whitespace_idx = idx;
break;
}
}
if whitespace_idx < line.len() {
prefix = &line[..whitespace_idx];
break;
}
}
for line in &mut lines {
let mut whitespace_idx = line.len();
for ((idx, a), b) in line.char_indices().zip(prefix.chars()) {
if a != b {
whitespace_idx = idx;
break;
}
}
if whitespace_idx < line.len() && whitespace_idx < prefix.len() {
prefix = &line[..whitespace_idx];
}
}
let mut result = String::new();
for line in s.lines() {
if line.starts_with(&prefix) && line.chars().any(|c| !c.is_whitespace()) {
let (_, tail) = line.split_at(prefix.len());
result.push_str(tail);
}
result.push('\n');
}
if result.ends_with('\n') && !s.ends_with('\n') {
let new_len = result.len() - 1;
result.truncate(new_len);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn add_nl(lines: &[&str]) -> String {
lines.join("\n") + "\n"
}
#[test]
fn indent_empty() {
assert_eq!(indent("\n", " "), "\n");
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn indent_nonempty() {
let x = vec![" foo",
"bar",
" baz"];
let y = vec!["// foo",
"//bar",
"// baz"];
assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn indent_empty_line() {
let x = vec![" foo",
"bar",
"",
" baz"];
let y = vec!["// foo",
"//bar",
"",
"// baz"];
assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y));
}
#[test]
fn dedent_empty() {
assert_eq!(dedent(""), "");
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_multi_line() {
let x = vec![" foo",
" bar",
" baz"];
let y = vec![" foo",
"bar",
" baz"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_empty_line() {
let x = vec![" foo",
" bar",
" ",
" baz"];
let y = vec![" foo",
"bar",
"",
" baz"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_blank_line() {
let x = vec![" foo",
"",
" bar",
" foo",
" bar",
" baz"];
let y = vec!["foo",
"",
" bar",
" foo",
" bar",
" baz"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_whitespace_line() {
let x = vec![" foo",
" ",
" bar",
" foo",
" bar",
" baz"];
let y = vec!["foo",
"",
" bar",
" foo",
" bar",
" baz"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_mixed_whitespace() {
let x = vec!["\tfoo",
" bar"];
let y = vec!["\tfoo",
" bar"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_tabbed_whitespace() {
let x = vec!["\t\tfoo",
"\t\t\tbar"];
let y = vec!["foo",
"\tbar"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_mixed_tabbed_whitespace() {
let x = vec!["\t \tfoo",
"\t \t\tbar"];
let y = vec!["foo",
"\tbar"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_mixed_tabbed_whitespace2() {
let x = vec!["\t \tfoo",
"\t \tbar"];
let y = vec!["\tfoo",
" \tbar"];
assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn dedent_preserve_no_terminating_newline() {
let x = vec![" foo",
" bar"].join("\n");
let y = vec!["foo",
" bar"].join("\n");
assert_eq!(dedent(&x), y);
}
}