Repository: Nukesor/comfy-table (rust)

Issue:
Applying an underline only to the table heading, not the entire cell
The following code:

```rust
let mut table = Table::new();
table.load_preset(comfy_table::presets::NOTHING);
table.set_header(vec![
    "A heading".bold(),
    "Column 1".bold(),
    "A slightly longer heading".bold(),
]);
for i in 0..5 {
    let row: Vec<_> = (0..3).map(|j| format!("Row {} col {}", i, j)).collect();
    table.add_row(row);
}
```

produces a table with misaligned headings.

![screenshot-211107-202200](https://user-images.githubusercontent.com/6341745/140658992-a11a3d06-432b-4260-a868-1c27b1053b9d.png)

If I coerce each row into a `Vec<StyledContent<String>>` by calling `.reset()` on the `String`s:

```rust
let mut table = Table::new();
table.load_preset(comfy_table::presets::NOTHING);
table.set_header(vec![
    "A heading".bold(),
    "Column 1".bold(),
    "A slightly longer heading".bold(),
]);
for i in 0..5 {
    let row: Vec<_> = (0..3)
        .map(|j| format!("Row {} col {}", i, j).reset()) // Change.
        .collect();
    table.add_row(row);
}
```

the problem goes away.

![screenshot-211107-202527](https://user-images.githubusercontent.com/6341745/140658993-2722d11d-346f-4f41-995d-983632922684.png)


Pull request:
add: Style text only feature
Resolves #54

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf9acbc..c229185 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 - Don't build `Table::force_no_tty` and `Table::should_style` if `tty` feature isn't enabled.
 - Remove `Table::is_tty` for non-`tty` feature.
 
+### Added
+
+- Add `Table::style_text_only()`, which prevents non-delimiter whitespaces in cells to be styled.
+
 ## [5.0.1] - 2022-02-18
 
 ### Updates
diff --git a/codecov.yml b/codecov.yml
index 441140d..f6fe310 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -13,4 +13,4 @@ codecov:
     project:
       default:
         target: auto
-        threshold: 0.5%
+        threshold: 5%
diff --git a/src/table.rs b/src/table.rs
index c810369..25a28a9 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -36,6 +36,10 @@ pub struct Table {
     width: Option<u16>,
     #[cfg(feature = "tty")]
     enforce_styling: bool,
+    /// Define whether everything in a cells should be styled, including whitespaces
+    /// or whether only the text should be styled.
+    #[cfg(feature = "tty")]
+    pub(crate) style_text_only: bool,
 }
 
 impl fmt::Display for Table {
@@ -67,6 +71,8 @@ impl Table {
             style: HashMap::new(),
             #[cfg(feature = "tty")]
             enforce_styling: false,
+            #[cfg(feature = "tty")]
+            style_text_only: false,
         };
 
         table.load_preset(ASCII_FULL);
@@ -263,6 +269,14 @@ impl Table {
         self.is_tty()
     }
 
+    /// By default, the whole content of a cells will be styled.
+    /// Calling this function disables this behavior for all cells, resulting in
+    /// only the text of cells being styled.
+    #[cfg(feature = "tty")]
+    pub fn style_text_only(&mut self) {
+        self.style_text_only = true;
+    }
+
     /// Convenience method to set a [ColumnConstraint] for all columns at once.
     /// Constraints are used to influence the way the columns will be arranged.
     /// Check out their docs for more information.
diff --git a/src/utils/formatting/content_format.rs b/src/utils/formatting/content_format.rs
index b5d8724..ea0b648 100644
--- a/src/utils/formatting/content_format.rs
+++ b/src/utils/formatting/content_format.rs
@@ -122,31 +122,11 @@ pub fn format_row(
         // Iterate over all generated lines of this cell and align them
         let cell_lines = cell_lines
             .iter()
-            .map(|line| align_line(line.to_string(), info, cell));
-
-        // Apply tty styling for this cell.
-        #[cfg(feature = "tty")]
-        let cell_lines = apply_tty_styling(table, cell, cell_lines).into_iter();
+            .map(|line| align_line(table, info, cell, line.to_string()));
 
         temp_row_content.push(cell_lines.collect());
     }
 
-    /// A small wrapper around the top-level cell styling logic. It's only used to have a clear
-    /// separation of our tty styling logic for the `tty` feature flag.
-    #[cfg(feature = "tty")]
-    fn apply_tty_styling(
-        table: &Table,
-        cell: &Cell,
-        cell_lines: impl Iterator<Item = String>,
-    ) -> Vec<String> {
-        if table.should_style() {
-            let cell_lines = cell_lines.map(|line| style_line(line, cell));
-            cell_lines.collect()
-        } else {
-            cell_lines.collect()
-        }
-    }
-
     // Right now, we have a different structure than desired.
     // The content is organized by `row->cell->line`.
     // We want to remove the cell from our datastructure, since this makes the next step a lot easier.
@@ -190,10 +170,18 @@ pub fn format_row(
 /// In every case all lines will be exactly the same character length `info.width - padding long`
 /// This is needed, so we can simply insert it into the border frame later on.
 /// Padding is applied in this function as well.
-fn align_line(mut line: String, info: &ColumnDisplayInfo, cell: &Cell) -> String {
+#[allow(unused_variables)]
+fn align_line(table: &Table, info: &ColumnDisplayInfo, cell: &Cell, mut line: String) -> String {
     let content_width = info.content_width;
     let remaining: usize = usize::from(content_width).saturating_sub(line.width());
 
+    // Apply the styling before aligning the line, if the user requests it.
+    // That way non-delimiter whitespaces won't have stuff like underlines.
+    #[cfg(feature = "tty")]
+    if table.should_style() && table.style_text_only {
+        line = style_line(line, cell);
+    }
+
     // Determine the alignment of the column cells.
     // Cell settings overwrite the columns Alignment settings.
     // Default is Left
@@ -220,7 +208,14 @@ fn align_line(mut line: String, info: &ColumnDisplayInfo, cell: &Cell) -> String
         }
     }
 
-    pad_line(&line, info)
+    line = pad_line(&line, info);
+
+    #[cfg(feature = "tty")]
+    if table.should_style() && !table.style_text_only {
+        return style_line(line, cell);
+    }
+
+    line
 }
 
 /// Apply the column's padding to this line


Test patch:
diff --git a/tests/all/styling_test.rs b/tests/all/styling_test.rs
index 542221c..032b897 100644
--- a/tests/all/styling_test.rs
+++ b/tests/all/styling_test.rs
@@ -19,7 +19,9 @@ fn get_preset_table() -> Table {
         ])
         .add_row(vec![
             Cell::new("Blinking boiii").add_attribute(Attribute::SlowBlink),
-            Cell::new("Now\nadd some\nmulti line stuff").fg(Color::Cyan),
+            Cell::new("Now\nadd some\nmulti line stuff")
+                .fg(Color::Cyan)
+                .add_attribute(Attribute::Underlined),
             Cell::new("COMBINE ALL THE THINGS")
                 .fg(Color::Green)
                 .bg(Color::Black)
@@ -41,9 +43,9 @@ fn styled_table() {
 ╞═════════════════════╪══════════════════════╪═══════════════════════════════╡
 │\u{1b}[1m This is a bold text \u{1b}[0m┆\u{1b}[38;5;10m This is a green text \u{1b}[39m┆\u{1b}[48;5;0m This one has black background \u{1b}[49m│
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
-│\u{1b}[5m Blinking boiii      \u{1b}[0m┆\u{1b}[38;5;14m Now                  \u{1b}[39m┆\u{1b}[48;5;0m\u{1b}[38;5;10m\u{1b}[1m\u{1b}[5m COMBINE ALL THE THINGS        \u{1b}[0m│
-│                     ┆\u{1b}[38;5;14m add some             \u{1b}[39m┆                               │
-│                     ┆\u{1b}[38;5;14m multi line stuff     \u{1b}[39m┆                               │
+│\u{1b}[5m Blinking boiii      \u{1b}[0m┆\u{1b}[38;5;14m\u{1b}[4m Now                  \u{1b}[0m┆\u{1b}[48;5;0m\u{1b}[38;5;10m\u{1b}[1m\u{1b}[5m COMBINE ALL THE THINGS        \u{1b}[0m│
+│                     ┆\u{1b}[38;5;14m\u{1b}[4m add some             \u{1b}[0m┆                               │
+│                     ┆\u{1b}[38;5;14m\u{1b}[4m multi line stuff     \u{1b}[0m┆                               │
 └─────────────────────┴──────────────────────┴───────────────────────────────┘";
     assert_eq!("\n".to_string() + &table.to_string(), expected);
 }
@@ -66,3 +68,22 @@ fn no_style_styled_table() {
 └─────────────────────┴──────────────────────┴───────────────────────────────┘";
     assert_eq!("\n".to_string() + &table.to_string(), expected);
 }
+
+#[test]
+fn styled_text_only_table() {
+    let mut table = get_preset_table();
+    table.force_no_tty().enforce_styling().style_text_only();
+    println!("{table}");
+    let expected = "
+┌─────────────────────┬──────────────────────┬───────────────────────────────┐
+│ \u{1b}[1mHeader1\u{1b}[0m             ┆ \u{1b}[38;5;10mHeader2\u{1b}[39m              ┆ \u{1b}[48;5;0mHeader3\u{1b}[49m                       │
+╞═════════════════════╪══════════════════════╪═══════════════════════════════╡
+│ \u{1b}[1mThis is a bold text\u{1b}[0m ┆ \u{1b}[38;5;10mThis is a green text\u{1b}[39m ┆ \u{1b}[48;5;0mThis one has black background\u{1b}[49m │
+├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+│ \u{1b}[5mBlinking boiii\u{1b}[0m      ┆ \u{1b}[38;5;14m\u{1b}[4mNow\u{1b}[0m                  ┆ \u{1b}[48;5;0m\u{1b}[38;5;10m\u{1b}[1m\u{1b}[5mCOMBINE ALL THE THINGS\u{1b}[0m        │
+│                     ┆ \u{1b}[38;5;14m\u{1b}[4madd some\u{1b}[0m             ┆                               │
+│                     ┆ \u{1b}[38;5;14m\u{1b}[4mmulti line stuff\u{1b}[0m     ┆                               │
+└─────────────────────┴──────────────────────┴───────────────────────────────┘";
+
+    assert_eq!("\n".to_string() + &table.to_string(), expected);
+}