diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/textwrap/src/indentation.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/textwrap/src/indentation.rs')
-rw-r--r-- | third_party/rust/textwrap/src/indentation.rs | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/third_party/rust/textwrap/src/indentation.rs b/third_party/rust/textwrap/src/indentation.rs new file mode 100644 index 0000000000..5d90c06156 --- /dev/null +++ b/third_party/rust/textwrap/src/indentation.rs @@ -0,0 +1,347 @@ +//! Functions related to adding and removing indentation from lines of +//! text. +//! +//! The functions here can be used to uniformly indent or dedent +//! (unindent) word wrapped lines of text. + +/// Indent each line by the given prefix. +/// +/// # Examples +/// +/// ``` +/// use textwrap::indent; +/// +/// assert_eq!(indent("First line.\nSecond line.\n", " "), +/// " First line.\n Second line.\n"); +/// ``` +/// +/// When indenting, trailing whitespace is stripped from the prefix. +/// This means that empty lines remain empty afterwards: +/// +/// ``` +/// use textwrap::indent; +/// +/// assert_eq!(indent("First line.\n\n\nSecond line.\n", " "), +/// " First line.\n\n\n Second line.\n"); +/// ``` +/// +/// Notice how `"\n\n\n"` remained as `"\n\n\n"`. +/// +/// This feature is useful when you want to indent text and have a +/// space between your prefix and the text. In this case, you _don't_ +/// want a trailing space on empty lines: +/// +/// ``` +/// use textwrap::indent; +/// +/// assert_eq!(indent("foo = 123\n\nprint(foo)\n", "# "), +/// "# foo = 123\n#\n# print(foo)\n"); +/// ``` +/// +/// Notice how `"\n\n"` became `"\n#\n"` instead of `"\n# \n"` which +/// would have trailing whitespace. +/// +/// Leading and trailing whitespace coming from the text itself is +/// kept unchanged: +/// +/// ``` +/// use textwrap::indent; +/// +/// assert_eq!(indent(" \t Foo ", "->"), "-> \t Foo "); +/// ``` +pub fn indent(s: &str, prefix: &str) -> String { + // We know we'll need more than s.len() bytes for the output, but + // without counting '\n' characters (which is somewhat slow), we + // don't know exactly how much. However, we can preemptively do + // the first doubling of the output size. + let mut result = String::with_capacity(2 * s.len()); + let trimmed_prefix = prefix.trim_end(); + for (idx, line) in s.split_terminator('\n').enumerate() { + if idx > 0 { + result.push('\n'); + } + if line.trim().is_empty() { + result.push_str(trimmed_prefix); + } else { + result.push_str(prefix); + } + result.push_str(line); + } + if s.ends_with('\n') { + // split_terminator will have eaten the final '\n'. + result.push('\n'); + } + result +} + +/// Removes common leading whitespace from each line. +/// +/// This function will look at each non-empty line and determine the +/// maximum amount of whitespace that can be removed from all lines: +/// +/// ``` +/// use textwrap::dedent; +/// +/// assert_eq!(dedent(" +/// 1st line +/// 2nd line +/// 3rd line +/// "), " +/// 1st line +/// 2nd line +/// 3rd line +/// "); +/// ``` +pub fn dedent(s: &str) -> String { + let mut prefix = ""; + let mut lines = s.lines(); + + // We first search for a non-empty line to find a prefix. + 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; + } + } + + // Check if the line had anything but whitespace + if whitespace_idx < line.len() { + prefix = &line[..whitespace_idx]; + break; + } + } + + // We then continue looking through the remaining lines to + // possibly shorten the prefix. + 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; + } + } + + // Check if the line had anything but whitespace and if we + // have found a shorter prefix + if whitespace_idx < line.len() && whitespace_idx < prefix.len() { + prefix = &line[..whitespace_idx]; + } + } + + // We now go over the lines a second time to build the result. + 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::*; + + #[test] + fn indent_empty() { + assert_eq!(indent("\n", " "), "\n"); + } + + #[test] + #[rustfmt::skip] + fn indent_nonempty() { + let text = [ + " foo\n", + "bar\n", + " baz\n", + ].join(""); + let expected = [ + "// foo\n", + "// bar\n", + "// baz\n", + ].join(""); + assert_eq!(indent(&text, "// "), expected); + } + + #[test] + #[rustfmt::skip] + fn indent_empty_line() { + let text = [ + " foo", + "bar", + "", + " baz", + ].join("\n"); + let expected = [ + "// foo", + "// bar", + "//", + "// baz", + ].join("\n"); + assert_eq!(indent(&text, "// "), expected); + } + + #[test] + fn dedent_empty() { + assert_eq!(dedent(""), ""); + } + + #[test] + #[rustfmt::skip] + fn dedent_multi_line() { + let x = [ + " foo", + " bar", + " baz", + ].join("\n"); + let y = [ + " foo", + "bar", + " baz" + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_empty_line() { + let x = [ + " foo", + " bar", + " ", + " baz" + ].join("\n"); + let y = [ + " foo", + "bar", + "", + " baz" + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_blank_line() { + let x = [ + " foo", + "", + " bar", + " foo", + " bar", + " baz", + ].join("\n"); + let y = [ + "foo", + "", + " bar", + " foo", + " bar", + " baz", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_whitespace_line() { + let x = [ + " foo", + " ", + " bar", + " foo", + " bar", + " baz", + ].join("\n"); + let y = [ + "foo", + "", + " bar", + " foo", + " bar", + " baz", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_mixed_whitespace() { + let x = [ + "\tfoo", + " bar", + ].join("\n"); + let y = [ + "\tfoo", + " bar", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_tabbed_whitespace() { + let x = [ + "\t\tfoo", + "\t\t\tbar", + ].join("\n"); + let y = [ + "foo", + "\tbar", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_mixed_tabbed_whitespace() { + let x = [ + "\t \tfoo", + "\t \t\tbar", + ].join("\n"); + let y = [ + "foo", + "\tbar", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_mixed_tabbed_whitespace2() { + let x = [ + "\t \tfoo", + "\t \tbar", + ].join("\n"); + let y = [ + "\tfoo", + " \tbar", + ].join("\n"); + assert_eq!(dedent(&x), y); + } + + #[test] + #[rustfmt::skip] + fn dedent_preserve_no_terminating_newline() { + let x = [ + " foo", + " bar", + ].join("\n"); + let y = [ + "foo", + " bar", + ].join("\n"); + assert_eq!(dedent(&x), y); + } +} |