Skip to content

Commit

Permalink
Merge pull request #140 from dsully/main
Browse files Browse the repository at this point in the history
Custom Styling Fix
  • Loading branch information
Nukesor committed Apr 5, 2024
2 parents 81e6ff4 + 3e8e175 commit 4dfab5c
Show file tree
Hide file tree
Showing 13 changed files with 65 additions and 47 deletions.
25 changes: 16 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Changed

## Fix

- Fix string width calculation with ANSI escape sequences by using ansi-str instead of console::measure_text_width().
- Fix typos.
- Fix compiler warnings in tests/all/property_test.rs

-

## [7.1.0] - 2023-10-21
Expand All @@ -28,7 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Fix a panic when working with extreme paddings, where `(padding.left + padding.right) > u16::MAX`.
- Fix a panic when working with extremely long content, where `(content_width + padding) > u16::MAX`.
- Properly enforce lower boundery constraints.
- Properly enforce lower boundary constraints.
Previously, "normal" columns were allocated before lower boundaries were respected.
This could lead to scenarios, where the table would grow beyond the specified size, when there was a lower boundary.
- Fix calculation of column widths for empty columns.
Expand All @@ -44,7 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Breaking

- The `Color` and `Attribute` enum are no longer re-exported from crossterm by default.
Previously, when updating comfy-table, crossterm needed to be upgraded as well, since the compile would otherwise fail due to type incompatibilies.
Previously, when updating comfy-table, crossterm needed to be upgraded as well, since the compile would otherwise fail due to type incompatibilities.

To fix this, these enums are now mirrored and internally mapped to their crossterm equivalents, which allows us to safely bump crossterm whenever a new version is released.
This change will only affect you if your projects explicitly use crossterm and comfy-table at the same time **and** feed crossterm's native types into comfy-table.
Expand Down Expand Up @@ -78,7 +84,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Fixed

- Disable unneded crossterm `bracketed-paste` feature.
- Disable unneeded crossterm `bracketed-paste` feature.

## [6.1.2] - 2022-10-27

Expand Down Expand Up @@ -172,7 +178,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
The `tty` feature flag is enabled by default.
Implemented by [roee88](https://github.com/roee88) in [#47](https://github.com/Nukesor/comfy-table/pull/47).


## [4.1.0] - 2021-08-09

### Added
Expand Down Expand Up @@ -243,6 +248,7 @@ pub enum Width {
```

Instead of the old

```
enum ColumnConstraints {
...,
Expand All @@ -263,31 +269,29 @@ enum ColumnConstraints {
Check the docs on the trait implementations for Cell, Row and Cells
- Add the `Cells` type, to allow super generic `Iterator -> Row` conversions.


## [2.1.0] - 2021-01-26

### Added

- `DynamicFullWidth` arrangement.
This mode is basically the same as the `Dynamic` arrangement mode, but it will always use the full available width, even if there isn't enough content to fill the space.


## [2.0.0] - 2021-01-16

### Added

**Dynamic arrangement**

A new logic to optimize space usage after splitting content has been added.\
If there is a lot of unused space after the content has been arranged, this space will now be redistributed ot the remaining columns.
If there is a lot of unused space after the content has been arranged, this space will now be redistributed to the remaining columns.
Or it will be removed if there are no other columns.

**This is considered a breaking change, since this can result in different table layouts!!**

This process is far from perfect, but the behavior is better than before.


Old behavior:

```
+-----------------------------------+-----------------------------------+------+
| Header1 | Header2 | Head |
Expand All @@ -298,6 +302,7 @@ Old behavior:
```

New behavior:

```
+-----------------------------------------+-----------------------------+------+
| Header1 | Header2 | Head |
Expand All @@ -308,6 +313,7 @@ New behavior:
```

Old behavior:

```
+------------------------------------------------+
| Header1 |
Expand All @@ -318,6 +324,7 @@ Old behavior:
```

New behavior:

```
+-------------------------------+
| Header1 |
Expand Down Expand Up @@ -353,7 +360,7 @@ New behavior:

### Added

- New ColumConstraint for hiding columns
- New ColumnConstraint for hiding columns

## [1.2.0] - 2020-10-27

Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ tty = ["crossterm"]
# - Text formatting still works, even if you roll your own ANSI escape sequences.
# - Rainbow text
# - Makes comfy-table 30-50% slower
custom_styling = ["console"]
custom_styling = ["ansi-str", "console"]
# With this flag, comfy_table re-exposes crossterm's "Attribute" and "Color" enum.
# By default, a mirrored type is exposed, which internally maps to the crossterm type.
#
Expand All @@ -69,6 +69,7 @@ integration_test = []
strum = "0.26"
strum_macros = "0.26"
unicode-width = "0.1"
ansi-str = { version = "0.8", optional = true }
console = { version = "0.15", optional = true }

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl Column {
self
}

/// Returns wheather the columns is hidden via [ColumnConstraint::Hidden].
/// Returns weather the columns is hidden via [ColumnConstraint::Hidden].
pub fn is_hidden(&self) -> bool {
matches!(self.constraint, Some(ColumnConstraint::Hidden))
}
Expand Down
2 changes: 1 addition & 1 deletion src/style/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum ColumnConstraint {
/// If the column has longer content and is allowed to grow, the column may take more space.
LowerBoundary(Width),
/// Specify a upper boundary, either fixed or as percentage of the total width.
/// A column with this constriant will be at most as wide as specified.
/// A column with this constraint will be at most as wide as specified.
/// The column may be smaller than that width.
UpperBoundary(Width),
/// Specify both, an upper and a lower boundary.
Expand Down
16 changes: 8 additions & 8 deletions src/utils/arrangement/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,22 +455,22 @@ fn longest_line_after_split(average_space: usize, column: &Column, table: &Table
let delimiter = delimiter(table, column, cell);

// Create a temporary ColumnDisplayInfo with the average space as width.
// That way we can simulate how the splitted text will look like.
// That way we can simulate how the split text will look like.
let info = ColumnDisplayInfo::new(column, average_space.try_into().unwrap_or(u16::MAX));

// Iterate over each line and split it into multiple lines, if necessary.
// Newlines added by the user will be preserved.
for line in cell.content.iter() {
if line.width() > average_space {
let mut splitted = split_line(line, &info, delimiter);
let mut parts = split_line(line, &info, delimiter);

#[cfg(feature = "debug")]
println!(
"dynamic::longest_line_after_split: Splitting line with width {}. Original:\n {}\nSplitted:\n {:?}",
line.width(), line, splitted
line.width(), line, parts
);

column_lines.append(&mut splitted);
column_lines.append(&mut parts);
} else {
column_lines.push(line.into());
}
Expand Down Expand Up @@ -499,7 +499,7 @@ fn use_full_width(infos: &mut DisplayInfos, remaining_width: usize) {
}

// Calculate the amount of average remaining space per column.
// Since we do integer division, there is most likely a little bit of non equally-divisable space.
// Since we do integer division, there is most likely a little bit of non equally-divisible space.
// We then try to distribute it as fair as possible (from left to right).
let average_space = remaining_width / visible_columns;
let mut excess = remaining_width - (average_space * visible_columns);
Expand All @@ -510,7 +510,7 @@ fn use_full_width(infos: &mut DisplayInfos, remaining_width: usize) {
continue;
}

// Distribute the non-divisable excess from left-to right until nothing is left.
// Distribute the non-divisible excess from left-to right until nothing is left.
let width = if excess > 0 {
excess -= 1;
(average_space + 1).try_into().unwrap_or(u16::MAX)
Expand All @@ -535,7 +535,7 @@ fn distribute_remaining_space(
remaining_columns: usize,
) {
// Calculate the amount of average remaining space per column.
// Since we do integer division, there is most likely a little bit of non equally-divisable space.
// Since we do integer division, there is most likely a little bit of non equally-divisible space.
// We then try to distribute it as fair as possible (from left to right).
let average_space = remaining_width / remaining_columns;
let mut excess = remaining_width - (average_space * remaining_columns);
Expand All @@ -546,7 +546,7 @@ fn distribute_remaining_space(
continue;
}

// Distribute the non-divisable excess from left-to right until nothing is left.
// Distribute the non-divisible excess from left-to right until nothing is left.
let width = if excess > 0 {
excess -= 1;
(average_space + 1).try_into().unwrap_or(u16::MAX)
Expand Down
8 changes: 4 additions & 4 deletions src/utils/formatting/content_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn format_row(
cell_iter.next();
continue;
}
// Each cell is devided into several lines devided by newline
// Each cell is divided into several lines divided by newline
// Every line that's too long will be split into multiple lines
let mut cell_lines = Vec::new();

Expand All @@ -92,8 +92,8 @@ pub fn format_row(
// Newlines added by the user will be preserved.
for line in cell.content.iter() {
if measure_text_width(line) > info.content_width.into() {
let mut splitted = split_line(line, info, delimiter);
cell_lines.append(&mut splitted);
let mut parts = split_line(line, info, delimiter);
cell_lines.append(&mut parts);
} else {
cell_lines.push(line.into());
}
Expand All @@ -117,7 +117,7 @@ pub fn format_row(
*last_line = stripped;
}

// Don't do anything if the collumn is smaller then 6 characters
// Don't do anything if the column is smaller then 6 characters
let width: usize = info.content_width.into();
if width >= 6 {
// Truncate the line if '...' doesn't fit
Expand Down
20 changes: 17 additions & 3 deletions src/utils/formatting/content_split/custom_styling.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use ansi_str::AnsiStr;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

const ANSI_RESET: &str = "\u{1b}[0m";

/// Returns printed length of string, takes into account escape codes
#[inline(always)]
pub fn measure_text_width(s: &str) -> usize {
console::measure_text_width(s)
s.ansi_strip().width()
}

/// Split the line by the given deliminator without breaking ansi codes that contain the delimiter
pub fn split_line_by_delimiter(line: &str, delimiter: char) -> Vec<String> {
let mut lines: Vec<String> = Vec::new();
let mut current_line = String::default();

// Iterate over line, spliting text with delimiter
// Iterate over line, splitting text with delimiter
let iter = console::AnsiCodeIterator::new(line);
for (str_slice, is_esc) in iter {
if is_esc {
Expand Down Expand Up @@ -55,7 +56,7 @@ pub fn split_long_word(allowed_width: usize, word: &str) -> (String, String) {
let mut escapes = Vec::new();

// Iterate over segments of the input string, each segment is either a singe escape code or block of text containing no escape codes.
// Add text and escape codes to the head buffer, keeping track of printable length and what ansi codes are active, untill there is no more room in allowed_width.
// Add text and escape codes to the head buffer, keeping track of printable length and what ansi codes are active, until there is no more room in allowed_width.
// If the str was split at a point with active escape-codes, add the ansi reset code to the end of head, and the list of active escape codes to the beginning of tail.
let mut iter = console::AnsiCodeIterator::new(word);
for (str_slice, is_esc) in iter.by_ref() {
Expand Down Expand Up @@ -158,6 +159,8 @@ pub fn fix_style_in_split_str(words: &mut [String]) {

#[cfg(test)]
mod test {
use unicode_width::UnicodeWidthStr;

#[test]
fn ansi_aware_split_test() {
use super::split_line_by_delimiter;
Expand All @@ -175,4 +178,15 @@ mod test {
]
)
}

#[test]
fn measure_text_width_osc8_test() {
use super::measure_text_width;

let text = "\x1b]8;;https://github.com\x1b\\This is a link\x1b]8;;\x1b";
let width = measure_text_width(text);

assert_eq!(text.width(), 41);
assert_eq!(width, 14);
}
}
2 changes: 1 addition & 1 deletion src/utils/formatting/content_split/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn split_line(line: &str, info: &ColumnDisplayInfo, delimiter: char) -> Vec<

// Some helper variables
// The length of the current line when combining it with the next element
// Add 1 for the delimiter if we are on a non-emtpy line.
// Add 1 for the delimiter if we are on a non-empty line.
let mut added_length = next_length + current_length;
if !current_line.is_empty() {
added_length += 1;
Expand Down
6 changes: 3 additions & 3 deletions src/utils/formatting/content_split/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub fn split_line_by_delimiter(line: &str, delimiter: char) -> Vec<String> {
/// wider display width than allowed.
pub fn split_long_word(allowed_width: usize, word: &str) -> (String, String) {
let mut current_width = 0;
let mut splitted = String::new();
let mut parts = String::new();

let mut char_iter = word.chars().peekable();
// Check if the string might be too long, one character at a time.
Expand All @@ -41,10 +41,10 @@ pub fn split_long_word(allowed_width: usize, word: &str) -> (String, String) {
let character_width = c.width().unwrap_or(1);

current_width += character_width;
splitted.push(c);
parts.push(c);
}

// Collect the remaining characters.
let remaining = char_iter.collect();
(splitted, remaining)
(parts, remaining)
}
2 changes: 1 addition & 1 deletion tests/all/constraints_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ fn unnecessary_max_min_constraints() {

#[test]
/// The user can specify constraints that result in bigger width than actually provided
/// This is allowed, but results in a wider table than acutally aimed for.
/// This is allowed, but results in a wider table than actually aimed for.
/// Anyway we still try to fit everything as good as possible, which of course breaks stuff.
fn constraints_bigger_than_table_width() {
let mut table = get_constraint_table();
Expand Down
4 changes: 2 additions & 2 deletions tests/all/content_arrangement_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use comfy_table::{ContentArrangement, Row, Table};

use super::assert_table_line_width;

/// Test the robustnes of the dynamic table arangement.
/// Test the robustness of the dynamic table arrangement.
#[test]
fn simple_dynamic_table() {
let mut table = Table::new();
Expand Down Expand Up @@ -229,7 +229,7 @@ fn dynamic_full_width() {
/// Test that a table is displayed in its full width, if the `table.width` is set to the exact
/// width the table has, if it's fully expanded.
///
/// The same should be the case for values that're larget than this width.
/// The same should be the case for values that are larger than this width.
#[test]
fn dynamic_exact_width() {
let header = vec!["a\n---\ni64", "b\n---\ni64", "b_squared\n---\nf64"];
Expand Down
2 changes: 1 addition & 1 deletion tests/all/custom_delimiter_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use comfy_table::*;

#[test]
/// Create a table with a custom delimiter on Table, Column and Cell level.
/// The first column should be splitted with the table's delimiter.
/// The first column should be split with the table's delimiter.
/// The first cell of the second column should be split with the custom column delimiter
/// The second cell of the second column should be split with the custom cell delimiter
fn full_custom_delimiters() {
Expand Down

0 comments on commit 4dfab5c

Please sign in to comment.