Skip to content

Commit

Permalink
change: Handle multi-space utf-8 characters
Browse files Browse the repository at this point in the history
  • Loading branch information
Nukesor committed Aug 7, 2021
1 parent 704f683 commit 56d59c0
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 19 deletions.
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ edition = "2018"
maintenance = { status = "actively-developed" }

[dependencies]
crossterm = "^0.20"
strum = "^0.21"
strum_macros = "^0.21"
crossterm = "0.20"
strum = "0.21"
strum_macros = "0.21"
unicode-width = "0.1"

[dev-dependencies]
pretty_assertions = "0.7"
Expand Down
4 changes: 3 additions & 1 deletion src/row.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::slice::Iter;

use unicode_width::UnicodeWidthStr;

use crate::cell::{Cell, Cells};

/// Each row contains [Cells](crate::Cell) and can be added to a [Table](crate::Table).
Expand Down Expand Up @@ -65,7 +67,7 @@ impl Row {
// Each entry represents the longest string width for a cell.
cell.content
.iter()
.map(|string| string.chars().count())
.map(|string| string.width())
.max()
.unwrap_or(0)
})
Expand Down
6 changes: 4 additions & 2 deletions src/utils/arrangement/dynamic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::convert::TryInto;

use unicode_width::UnicodeWidthStr;

use super::constraints::get_max_constraint;
use super::constraints::get_min_constraint;
use super::helper::*;
Expand Down Expand Up @@ -423,7 +425,7 @@ fn get_longest_line_after_split(average_space: usize, column: &Column, table: &T
// 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.chars().count()) > average_space {
if line.width() > average_space {
let mut splitted = split_line(line, &info, delimiter);
column_lines.append(&mut splitted);
} else {
Expand All @@ -435,7 +437,7 @@ fn get_longest_line_after_split(average_space: usize, column: &Column, table: &T
// Get the longest line, default to length 0 if no lines exist.
column_lines
.iter()
.map(|line| line.chars().count())
.map(|line| line.width())
.max()
.unwrap_or(0)
}
Expand Down
11 changes: 6 additions & 5 deletions src/utils/formatting/content_format.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crossterm::style::{style, Stylize};
use unicode_width::UnicodeWidthStr;

use super::content_split::split_line;
use crate::cell::Cell;
Expand Down Expand Up @@ -85,7 +86,7 @@ pub fn format_row(
// 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.chars().count() > info.content_width.into() {
if line.width() > info.content_width.into() {
let mut splitted = split_line(line, info, delimiter);
cell_lines.append(&mut splitted);
} else {
Expand All @@ -108,9 +109,9 @@ pub fn format_row(
let width: usize = info.content_width.into();
if width >= 6 {
// Truncate the line if '...' doesn't fit
if last_line.chars().count() >= width - 3 {
let surplus = (last_line.chars().count() + 3) - width;
last_line.truncate(last_line.chars().count() - surplus);
if last_line.width() >= width - 3 {
let surplus = (last_line.width() + 3) - width;
last_line.truncate(last_line.width() - surplus);
}
last_line.push_str("...");
}
Expand Down Expand Up @@ -180,7 +181,7 @@ pub fn format_row(
/// Padding is applied in this function as well.
fn align_line(mut line: String, info: &ColumnDisplayInfo, cell: &Cell) -> String {
let content_width = info.content_width;
let remaining: usize = usize::from(content_width).saturating_sub(line.chars().count());
let remaining: usize = usize::from(content_width).saturating_sub(line.width());

// Determine the alignment of the column cells.
// Cell settings overwrite the columns Alignment settings.
Expand Down
10 changes: 6 additions & 4 deletions src/utils/formatting/content_split.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::iter::FromIterator;

use unicode_width::UnicodeWidthStr;

use crate::utils::ColumnDisplayInfo;

/// Split a line if it's longer than the allowed columns (width - padding).
Expand Down Expand Up @@ -28,8 +30,8 @@ pub fn split_line(line: &str, info: &ColumnDisplayInfo, delimiter: char) -> Vec<

let mut current_line = String::new();
while let Some(next) = elements.pop() {
let current_length = current_line.chars().count();
let next_length = next.chars().count();
let current_length = current_line.width();
let next_length = next.width();

// Some helper variables
// The length of the current line when combining it with the next element
Expand All @@ -39,7 +41,7 @@ pub fn split_line(line: &str, info: &ColumnDisplayInfo, delimiter: char) -> Vec<
added_length += 1;
}
// The remaining width for this column. If we are on a non-empty line, subtract 1 for the delimiter.
let mut remaining_width = content_width - current_line.chars().count();
let mut remaining_width = content_width - current_line.width();
if !current_line.is_empty() {
remaining_width = remaining_width.saturating_sub(1);
}
Expand Down Expand Up @@ -128,7 +130,7 @@ const MIN_FREE_CHARS: usize = 2;
/// Otherwise, we simply return the current line and basically don't do anything.
fn check_if_full(lines: &mut Vec<String>, content_width: usize, current_line: String) -> String {
// Already complete the current line, if there isn't space for more than two chars
if current_line.chars().count() > content_width.saturating_sub(MIN_FREE_CHARS) {
if current_line.width() > content_width.saturating_sub(MIN_FREE_CHARS) {
lines.push(current_line);
return String::new();
}
Expand Down
5 changes: 3 additions & 2 deletions tests/all/content_arrangement_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ use pretty_assertions::assert_eq;
use comfy_table::ColumnConstraint::*;
use comfy_table::Width::*;
use comfy_table::{ContentArrangement, Row, Table};
use unicode_width::UnicodeWidthStr;

fn assert_table_lines(table: &Table, count: usize) {
for line in table.lines() {
assert_eq!(line.chars().count(), count);
assert_eq!(line.width(), count);
}
}

Expand Down Expand Up @@ -66,7 +67,7 @@ fn simple_dynamic_table() {
| | stuff | |
+--------+-------+------+";
println!("{}", expected);
assert!(table.lines().all(|line| line.chars().count() == 25));
assert!(table.lines().all(|line| line.width() == 25));
assert_eq!("\n".to_string() + &table.to_string(), expected);
}

Expand Down
6 changes: 4 additions & 2 deletions tests/all/property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use comfy_table::presets::UTF8_FULL;
use comfy_table::ColumnConstraint::*;
use comfy_table::Width::*;
use comfy_table::*;
use unicode_width::UnicodeWidthStr;

/// Pick any of the three existing ContentArrangement types for the table.
fn content_arrangement() -> impl Strategy<Value = ContentArrangement> {
Expand Down Expand Up @@ -162,13 +163,14 @@ proptest! {

let mut line_iter = lines.iter();
let line_length = if let Some(line) = line_iter.next() {
line.chars().count()
line.width()
} else {
0
};

for line in line_iter {
if line.chars().count() != line_length {
if line.width() != line_length {
println!("{}", formatted);
return Err(TestCaseError::Fail("Each line of a printed table has to have the same length!".into()))
}
}
Expand Down

0 comments on commit 56d59c0

Please sign in to comment.