implemented wrapping of cell contents in activity lists

This commit is contained in:
Nikolas Schmidt-Voigt 2021-10-05 17:01:39 +02:00
parent 3386e94095
commit e12a143265
4 changed files with 109 additions and 17 deletions

61
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
@ -43,6 +52,7 @@ dependencies = [
"clap",
"nu-ansi-term",
"term_size",
"textwrap 0.14.2",
"thiserror",
]
@ -75,7 +85,7 @@ dependencies = [
"atty",
"bitflags",
"strsim",
"textwrap",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
]
@ -120,6 +130,12 @@ version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "nu-ansi-term"
version = "0.34.0"
@ -174,6 +190,29 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "strsim"
version = "0.8.0"
@ -211,6 +250,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.24"
@ -242,6 +292,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]]
name = "unicode-width"
version = "0.1.8"

View file

@ -12,4 +12,5 @@ clap = "~2.33"
thiserror = "1.0"
anyhow = "1.0.42"
nu-ansi-term = "0.34.0"
term_size = "1.0.0-beta1"
term_size = "1.0.0-beta1"
textwrap = "0.14.2"

View file

@ -1,3 +1,4 @@
pub static FORMAT_DATETIME: &str = "%F %R";
pub static FORMAT_TIME: &str = "%R";
pub static FORMAT_DATE: &str = "%F";
pub static DEFAULT_WIDTH : usize = 80;

View file

@ -1,8 +1,11 @@
use std::cmp;
use std::fmt;
use std::str;
use std::borrow::Cow;
use nu_ansi_term::Style;
use textwrap;
use crate::conf;
pub enum Wrap {
Wrap,
@ -74,6 +77,11 @@ impl Table {
.collect()
}
/* Calculates the widths for the columns in a table
If the width of the longest line in the table exceeds the maximum width for the output
all the wrapable columns shrink to an acceptable size.
*/
fn get_column_width(&self, max_width: usize) -> Vec<usize> {
let mut max_column_width = self.get_max_column_width();
@ -82,10 +90,11 @@ impl Table {
let mut number_of_wrappable_columns : usize = columns_wrap.iter().filter(|w| matches!(w, Wrap::Wrap)).count();
if width <= max_width || number_of_wrappable_columns == 0 {
// we do or can not wrap
// we do not need to or can not wrap
return max_column_width;
}
// the total width of the columns that we may not wrap
let unwrapable_width : usize = max_column_width.iter().zip(columns_wrap.iter())
.filter(|(_, wrap)| matches!(wrap, Wrap::NoWrap))
.map(|(width, _)| width)
@ -96,14 +105,14 @@ impl Table {
return max_column_width;
}
let mut available_width_for_wrappable_columns = max_width - unwrapable_width;
// we start with a width of 0 for all the wrapable columns
let mut column_width : Vec<usize> = max_column_width.iter().zip(columns_wrap.iter())
.map(|(width, wrap)| if matches!(wrap, Wrap::NoWrap) { width.clone() } else { 0 })
.collect();
// then we distribute the available width to the wrappable columns
let mut available_width_for_wrappable_columns = max_width - unwrapable_width;
while available_width_for_wrappable_columns > 0 && number_of_wrappable_columns > 0 {
// the maximum additional width we give each column in this round
@ -123,7 +132,7 @@ impl Table {
available_width_for_wrappable_columns -= *max_width - *width;
*width = *max_width;
// this row won't need any more width
// this column won't need any more width
number_of_wrappable_columns -= 1;
}
}
@ -157,7 +166,11 @@ impl Table {
impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let column_width = self.get_column_width(1000); // TODO
let terminal_width = term_size::dimensions_stdout().map(|d| d.0)
.unwrap_or(conf::DEFAULT_WIDTH);
let column_width = self.get_column_width(terminal_width - self.columns.len());
let labels : Vec<&String> = self.columns.iter().map(|c| &c.label).collect();
@ -211,15 +224,33 @@ fn write_cells<T: AsRef<str> + std::fmt::Display>(
column_width: &[usize],
style: Option<Style>,
) -> fmt::Result {
let cells_with_width: Vec<(Option<&usize>, &str)> = cells
let wrapped_cells : Vec<Vec<Cow<str>>> = cells
.iter()
.map(|cell| cell.as_ref())
.enumerate()
.map(|(i, cell)| (column_width.get(i), cell))
.map(|(i, c)| match column_width.get(i) {
Some(s) => textwrap::wrap(c.as_ref(), textwrap::Options::new(*s)),
None => {
let mut lines = Vec::new();
lines.push(Cow::from(c.as_ref()));
lines
}
})
.collect();
for (width, cell) in cells_with_width {
write_with_width_and_style(f, cell, width, style)?;
let most_lines : usize = wrapped_cells.iter().map(|c| c.len()).max().unwrap_or(1);
for line in 0..most_lines {
for (width, wrapped_cell) in column_width.iter().zip(wrapped_cells.iter()) {
match wrapped_cell.get(line) {
Some(c) => write_with_width_and_style(f, c, width, style)?,
None => write!(f, "{} ", "\u{a0}".repeat(*width))?
}
}
let is_last_line = line + 1 < most_lines;
if is_last_line { writeln!(f)?; }
}
Ok(())
@ -228,17 +259,17 @@ fn write_cells<T: AsRef<str> + std::fmt::Display>(
fn write_with_width_and_style(
f: &mut fmt::Formatter<'_>,
content: &str,
opt_width: Option<&usize>,
width: &usize,
opt_style: Option<Style>,
) -> fmt::Result {
let content_length = content.chars().count();
let style_prefix = opt_style.map_or("".to_string(), |style| style.prefix().to_string());
let style_suffix = opt_style.map_or("".to_string(), |style| style.suffix().to_string());
let width = opt_width.unwrap_or(&content_length);
// cells are filled with non-breaking white space. Contrary to normal spaces non-breaking white
// space will be styled (e.g. underlined)
write!(
f,
"{prefix}{content:<width$}{suffix} ",
"{prefix}{content:\u{a0}<width$}{suffix} ",
prefix = style_prefix,
content = content,
width = width,
@ -371,7 +402,7 @@ mod tests {
assert_eq!(
format!("{}", t),
"\u{1b}[4ma \u{1b}[0m \u{1b}[4mb \u{1b}[0m \u{1b}[4mc \u{1b}[0m \nabc defg \na b cdef \n"
"\u{1b}[4ma\u{a0}\u{a0}\u{1b}[0m \u{1b}[4mb\u{a0}\u{a0}\u{a0}\u{1b}[0m \u{1b}[4mc\u{a0}\u{a0}\u{a0}\u{1b}[0m \nabc defg \na\u{a0}\u{a0} b\u{a0}\u{a0}\u{a0} cdef \n"
);
}