| //! 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); |
| } |
| } |