Repository: zhiburt/tabled (rust)

Issue:
Add option to wrap when `MaxWidth` is configured
It would be really nice to be able to wrap wide columns onto newlines, rather than truncating them. Something like:

```
    let expected = concat!(
        "| id | destribution |    link    |\n",
        "|----+--------------+------------|\n",
        "| 0  |    Fedora    | https://ge |\n",
        "|    |              | tfedora.or |\n",
        "|    |              |     g/     |\n",
        "| 2  |   OpenSUSE   | https://ww |\n",
        "|    |              | w.opensuse |\n",
        "|    |              |   .org/    |\n",
        "| 3  |  Endeavouro  | https://en |\n",
        "|    |      s       | deavouros. |\n",
        "|    |              |    com/    |\n",
    );
```

when the max width is set to 10.

---

I had a go at this, and have the above test passing, but it required changing the public API of `MaxWidth` and didn't work great with type inference. Happy to push it up so you can take a look though.

Pull request description:
Allow wrapping in maxwidth continue #23
closes #23
closes #22 

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index efd22d0..fcbf6bb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,7 @@ color = ["papergrid/color", "ansi-cut"]
 [dependencies]
 tabled_derive = {path = "./tabled_derive"}
 papergrid = "0.1.22"
-ansi-cut = { version = "0.1.0", optional = true }
+ansi-cut = { version = "0.1.1", optional = true }
 
 [dev-dependencies]
 owo-colors = "1"
diff --git a/examples/color.rs b/examples/color.rs
index 10a09de..f8f6290 100644
--- a/examples/color.rs
+++ b/examples/color.rs
@@ -11,6 +11,7 @@ fn main() {
     use owo_colors::OwoColorize;
     use tabled::{Alignment, Column, Format, Head, Modify, Object, Row, Style, Table, Tabled};
 
+    #[allow(clippy::upper_case_acronyms)]
     #[derive(Tabled)]
     struct BSD {
         distribution: &'static str,
diff --git a/examples/width.rs b/examples/width.rs
index f1ef6e3..1b8ac4e 100644
--- a/examples/width.rs
+++ b/examples/width.rs
@@ -12,7 +12,7 @@ fn main() {
 
     let table = Table::new(&data)
         .with(Style::github_markdown())
-        .with(Modify::new(Full).with(MaxWidth(10, "...")));
+        .with(Modify::new(Full).with(MaxWidth::truncating(10, "...")));
 
     println!("{}", table);
 }
diff --git a/src/width.rs b/src/width.rs
index d4ade3e..c7f7c33 100644
--- a/src/width.rs
+++ b/src/width.rs
@@ -16,28 +16,61 @@ use papergrid::{Entity, Grid, Settings};
 ///
 /// let table = Table::new(&data)
 ///     .with(Style::github_markdown())
-///     .with(Modify::new(Full).with(MaxWidth(5, "...")));
+///     .with(Modify::new(Full).with(MaxWidth::truncating(5, "...")));
 /// ```
 ///
 /// While working with colors you must setup `colors` feature.
-pub struct MaxWidth<S>(pub usize, pub S)
+pub struct MaxWidth<S> {
+    width: usize,
+    wrap: Wrap<S>,
+}
+
+enum Wrap<S> {
+    Truncate(S),
+    Wrap,
+}
+
+impl<S> MaxWidth<S>
 where
-    S: AsRef<str>;
+    S: AsRef<str>,
+{
+    pub fn truncating(width: usize, suffix: S) -> Self {
+        Self {
+            width,
+            wrap: Wrap::Truncate(suffix),
+        }
+    }
+}
+
+impl MaxWidth<&'static str> {
+    pub fn wrapping(width: usize) -> Self {
+        Self {
+            width,
+            wrap: Wrap::Wrap,
+        }
+    }
+}
 
 impl<S: AsRef<str>> CellOption for MaxWidth<S> {
     fn change_cell(&mut self, grid: &mut Grid, row: usize, column: usize) {
-        let width = self.0;
-        let filler = self.1.as_ref();
-
         let content = grid.get_cell_content(row, column);
-        let striped_content = strip(content, width);
-
-        let old_content_length = content.len();
-        let new_content_length = striped_content.len();
-
-        if new_content_length != old_content_length {
-            let content = format!("{}{}", striped_content, filler);
-            grid.set(Entity::Cell(row, column), Settings::new().text(content))
+        match &self.wrap {
+            Wrap::Truncate(filler) => {
+                let striped_content = strip(content, self.width);
+                if striped_content.len() < content.len() {
+                    let new_content = format!("{}{}", striped_content, filler.as_ref());
+                    grid.set(Entity::Cell(row, column), Settings::new().text(new_content))
+                }
+            }
+            Wrap::Wrap => {
+                let wrapped_content = split(content, self.width);
+                if wrapped_content.len() != content.len() {
+                    grid.set(
+                        Entity::Cell(row, column),
+                        Settings::new().text(wrapped_content),
+                    )
+                }
+            }
         }
     }
 }
@@ -50,6 +83,26 @@ fn strip(s: &str, width: usize) -> String {
     #[cfg(feature = "color")]
     {
         let max_width = std::cmp::min(s.chars().count(), width);
-        ansi_cut::AnsiCut::cut(&s, ..max_width).to_string()
+        ansi_cut::AnsiCut::cut(&s, ..max_width)
+    }
+}
+
+fn split(s: &str, width: usize) -> String {
+    #[cfg(not(feature = "color"))]
+    {
+        s.chars()
+            .collect::<Vec<char>>()
+            .chunks(width)
+            .map(|chunk| chunk.iter().collect::<String>())
+            .collect::<Vec<String>>()
+            .join("\n")
+    }
+    #[cfg(feature = "color")]
+    {
+        if width == 0 {
+            s.to_string()
+        } else {
+            ansi_cut::chunks(s, width).join("\n")
+        }
     }
 }


Test patch:
diff --git a/tests/width_test.rs b/tests/width_test.rs
index c13cf9a..f6f6245 100644
--- a/tests/width_test.rs
+++ b/tests/width_test.rs
@@ -37,12 +37,89 @@ fn max_width() {
 
     let table = Table::new(&data)
         .with(Style::github_markdown())
-        .with(Modify::new(Column(1..).not(Row(..1))).with(MaxWidth(3, "...")))
+        .with(Modify::new(Column(1..).not(Row(..1))).with(MaxWidth::truncating(3, "...")))
         .to_string();
 
     assert_eq!(table, expected);
 }
 
+#[test]
+fn max_width_wrapped() {
+    let data = vec![
+        Linux {
+            id: 0,
+            destribution: "Fedora",
+            link: "https://getfedora.org/",
+        },
+        Linux {
+            id: 2,
+            destribution: "OpenSUSE",
+            link: "https://www.opensuse.org/",
+        },
+        Linux {
+            id: 3,
+            destribution: "Endeavouros",
+            link: "https://endeavouros.com/",
+        },
+    ];
+
+    let expected = concat!(
+        "| id | destribution |    link    |\n",
+        "|----+--------------+------------|\n",
+        "| 0  |    Fedora    | https://ge |\n",
+        "|    |              | tfedora.or |\n",
+        "|    |              |     g/     |\n",
+        "| 2  |   OpenSUSE   | https://ww |\n",
+        "|    |              | w.opensuse |\n",
+        "|    |              |   .org/    |\n",
+        "| 3  |  Endeavouro  | https://en |\n",
+        "|    |      s       | deavouros. |\n",
+        "|    |              |    com/    |\n",
+    );
+
+    let table = Table::new(&data)
+        .with(Style::github_markdown())
+        .with(Modify::new(Column(1..).not(Row(..1))).with(MaxWidth::wrapping(10)))
+        .to_string();
+
+    assert_eq!(table, expected);
+}
+
+#[cfg(feature = "color")]
+#[test]
+fn max_width_wrapped_collored() {
+    use owo_colors::OwoColorize;
+
+    let data = &[
+        "asd".red().to_string(),
+        "zxc2".blue().to_string(),
+        "asdasd".on_black().green().to_string(),
+    ];
+
+    let expected = concat!(
+        "| St |\n",
+        "| ri |\n",
+        "| ng |\n",
+        "|----|\n",
+        "| \u{1b}[31mas\u{1b}[0m |\n",
+        "| \u{1b}[31md\u{1b}[0m  |\n",
+        "| \u{1b}[34mzx\u{1b}[0m |\n",
+        "| \u{1b}[34mc2\u{1b}[0m |\n",
+        "| \u{1b}[32m\u{1b}[40mas\u{1b}[0m\u{1b}[0m |\n",
+        "| \u{1b}[32m\u{1b}[40mda\u{1b}[0m\u{1b}[0m |\n",
+        "| \u{1b}[32m\u{1b}[40msd\u{1b}[0m\u{1b}[0m |\n",
+    );
+
+    let table = Table::new(data)
+        .with(Style::github_markdown())
+        .with(Modify::new(Full).with(MaxWidth::wrapping(2)))
+        .to_string();
+
+    println!("{}", table);
+
+    assert_eq!(expected, table);
+}
+
 #[test]
 fn dont_change_content_if_width_is_less_then_max_width() {
     let data = vec![
@@ -73,7 +150,7 @@ fn dont_change_content_if_width_is_less_then_max_width() {
 
     let table = Table::new(&data)
         .with(Style::github_markdown())
-        .with(Modify::new(Full).with(MaxWidth(1000, "...")))
+        .with(Modify::new(Full).with(MaxWidth::truncating(1000, "...")))
         .to_string();
 
     assert_eq!(table, expected);
@@ -93,7 +170,7 @@ fn max_width_with_emoji() {
 
     let table = Table::new(data)
         .with(Style::github_markdown())
-        .with(Modify::new(Full).with(MaxWidth(3, "...")))
+        .with(Modify::new(Full).with(MaxWidth::truncating(3, "...")))
         .to_string();
 
     assert_eq!(table, _expected);
@@ -120,7 +197,7 @@ fn color_chars_are_stripped() {
 
     let table = Table::new(data)
         .with(Style::github_markdown())
-        .with(Modify::new(Full).with(MaxWidth(3, "...")))
+        .with(Modify::new(Full).with(MaxWidth::truncating(3, "...")))
         .to_string();
 
     println!("{}", table);