summaryrefslogtreecommitdiffstats
path: root/third_party/rust/textwrap/src/indentation.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/textwrap/src/indentation.rs
parentInitial commit. (diff)
downloadfirefox-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.rs347
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);
+ }
+}