Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/stdx/src/lib.rs')
| -rw-r--r-- | crates/stdx/src/lib.rs | 76 |
1 files changed, 70 insertions, 6 deletions
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 7ab26b1890..275e0e5ac8 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -1,5 +1,6 @@ //! Missing batteries for standard libraries. +use std::borrow::Cow; use std::io as sio; use std::process::Command; use std::{cmp::Ordering, ops, time::Instant}; @@ -221,12 +222,7 @@ pub fn trim_indent(mut text: &str) -> String { if text.starts_with('\n') { text = &text[1..]; } - let indent = text - .lines() - .filter(|it| !it.trim().is_empty()) - .map(|it| it.len() - it.trim_start().len()) - .min() - .unwrap_or(0); + let indent = indent_of(text); text.split_inclusive('\n') .map( |line| { @@ -236,6 +232,43 @@ pub fn trim_indent(mut text: &str) -> String { .collect() } +#[must_use] +fn indent_of(text: &str) -> usize { + text.lines() + .filter(|it| !it.trim().is_empty()) + .map(|it| it.len() - it.trim_start().len()) + .min() + .unwrap_or(0) +} + +#[must_use] +pub fn dedent_by(spaces: usize, text: &str) -> String { + text.split_inclusive('\n') + .map(|line| { + let trimmed = line.trim_start_matches(' '); + if line.len() - trimmed.len() <= spaces { trimmed } else { &line[spaces..] } + }) + .collect() +} + +/// Indent non empty lines, including the first line +#[must_use] +pub fn indent_string(s: &str, indent_level: u8) -> String { + if indent_level == 0 || s.is_empty() { + return s.to_owned(); + } + let indent_str = " ".repeat(indent_level as usize); + s.split_inclusive("\n") + .map(|line| { + if line.trim_end().is_empty() { + Cow::Borrowed(line) + } else { + format!("{indent_str}{line}").into() + } + }) + .collect() +} + pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize> where F: FnMut(&T) -> Ordering, @@ -367,6 +400,37 @@ mod tests { } #[test] + fn test_dedent() { + assert_eq!(dedent_by(0, ""), ""); + assert_eq!(dedent_by(1, ""), ""); + assert_eq!(dedent_by(2, ""), ""); + assert_eq!(dedent_by(0, "foo"), "foo"); + assert_eq!(dedent_by(2, "foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), " foo"); + assert_eq!(dedent_by(2, " foo\nbar"), " foo\nbar"); + assert_eq!(dedent_by(2, "foo\n bar"), "foo\n bar"); + assert_eq!(dedent_by(2, "foo\n\n bar"), "foo\n\n bar"); + assert_eq!(dedent_by(2, "foo\n.\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n .\n bar"); + } + + #[test] + fn test_indent_of() { + assert_eq!(indent_of(""), 0); + assert_eq!(indent_of(" "), 0); + assert_eq!(indent_of(" x"), 1); + assert_eq!(indent_of(" x\n"), 1); + assert_eq!(indent_of(" x\ny"), 0); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 2); + assert_eq!(indent_of(" x\n y\n"), 2); + assert_eq!(indent_of(" x\n\n y\n"), 2); + } + + #[test] fn test_replace() { #[track_caller] fn test_replace(src: &str, from: char, to: &str, expected: &str) { |