make table responsive

This commit is contained in:
Clément DOUIN 2021-01-16 19:38:03 +01:00
parent c3d5559234
commit 60af11bd47
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
6 changed files with 118 additions and 38 deletions

View file

@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Write new email [#8] - Write new email [#8]
- Reply, reply all and forward [#9] [#10] [#11] - Reply, reply all and forward [#9] [#10] [#11]
- Download attachments [#14] - Download attachments [#14]
- Merge `Email` with `Msg` [#21]
- List command with pagination [#19]
- Icon in table when attachment is present [#16]
[unreleased]: https://github.com/soywod/himalaya [unreleased]: https://github.com/soywod/himalaya
@ -37,3 +40,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#13]: https://github.com/soywod/himalaya/issues/13 [#13]: https://github.com/soywod/himalaya/issues/13
[#14]: https://github.com/soywod/himalaya/issues/14 [#14]: https://github.com/soywod/himalaya/issues/14
[#15]: https://github.com/soywod/himalaya/issues/15 [#15]: https://github.com/soywod/himalaya/issues/15
[#16]: https://github.com/soywod/himalaya/issues/16
[#19]: https://github.com/soywod/himalaya/issues/19
[#21]: https://github.com/soywod/himalaya/issues/21

11
Cargo.lock generated
View file

@ -280,6 +280,7 @@ dependencies = [
"rfc2047-decoder", "rfc2047-decoder",
"rustyline", "rustyline",
"serde", "serde",
"terminal_size",
"toml", "toml",
] ]
@ -946,6 +947,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "terminal_size"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"

View file

@ -14,4 +14,5 @@ native-tls = "0.2"
rfc2047-decoder = "0.1.2" rfc2047-decoder = "0.1.2"
rustyline = "7.1.0" rustyline = "7.1.0"
serde = { version = "1.0.118", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
terminal_size = "0.1.15"
toml = "0.5.8" toml = "0.5.8"

View file

@ -20,17 +20,25 @@ impl Mbox {
impl DisplayRow for Mbox { impl DisplayRow for Mbox {
fn to_row(&self) -> Vec<table::Cell> { fn to_row(&self) -> Vec<table::Cell> {
use crate::table::*;
vec![ vec![
table::Cell::new(&[table::BLUE], &self.delim), Cell::new(&[BLUE], &self.delim),
table::Cell::new(&[table::GREEN], &self.name), Cell::new(&[GREEN], &self.name),
table::Cell::new(&[table::YELLOW], &self.attributes.join(", ")), FlexCell::new(&[YELLOW], &self.attributes.join(", ")),
] ]
} }
} }
impl<'a> DisplayTable<'a, Mbox> for Vec<Mbox> { impl<'a> DisplayTable<'a, Mbox> for Vec<Mbox> {
fn cols() -> &'a [&'a str] { fn header_row() -> Vec<table::Cell> {
&["delim", "name", "attributes"] use crate::table::*;
vec![
Cell::new(&[BOLD, UNDERLINE, WHITE], "DELIM"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "NAME"),
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "ATTRIBUTES"),
]
} }
fn rows(&self) -> &Vec<Mbox> { fn rows(&self) -> &Vec<Mbox> {

View file

@ -379,7 +379,10 @@ impl DisplayRow for Msg {
let headers = parsed.get_headers(); let headers = parsed.get_headers();
let uid = &self.uid.to_string(); let uid = &self.uid.to_string();
let flags = String::new(); // TODO: render flags let flags = match self.extract_attachments().map(|vec| vec.is_empty()) {
Ok(false) => "",
_ => " ",
};
let sender = headers let sender = headers
.get_first_value("reply-to") .get_first_value("reply-to")
.or(headers.get_first_value("from")) .or(headers.get_first_value("from"))
@ -387,21 +390,33 @@ impl DisplayRow for Msg {
let subject = headers.get_first_value("subject").unwrap_or_default(); let subject = headers.get_first_value("subject").unwrap_or_default();
let date = headers.get_first_value("date").unwrap_or_default(); let date = headers.get_first_value("date").unwrap_or_default();
vec![ {
table::Cell::new(&[table::RED], &uid), use crate::table::*;
table::Cell::new(&[table::WHITE], &flags),
table::Cell::new(&[table::BLUE], &sender), vec![
table::Cell::new(&[table::GREEN], &subject), Cell::new(&[RED], &uid),
table::Cell::new(&[table::YELLOW], &date), Cell::new(&[WHITE], &flags),
] Cell::new(&[BLUE], &sender),
FlexCell::new(&[GREEN], &subject),
Cell::new(&[YELLOW], &date),
]
}
} }
} }
} }
} }
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> { impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
fn cols() -> &'a [&'a str] { fn header_row() -> Vec<table::Cell> {
&["uid", "flags", "sender", "subject", "date"] use crate::table::*;
vec![
Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "FLAGS"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"),
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"),
]
} }
fn rows(&self) -> &Vec<Msg> { fn rows(&self) -> &Vec<Msg> {

View file

@ -1,4 +1,5 @@
use std::fmt; use std::fmt;
use terminal_size::terminal_size;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Style(u8, u8, u8); pub struct Style(u8, u8, u8);
@ -33,13 +34,15 @@ impl fmt::Display for Style {
pub struct Cell { pub struct Cell {
pub styles: Vec<Style>, pub styles: Vec<Style>,
pub value: String, pub value: String,
pub flex: bool,
} }
impl Cell { impl Cell {
pub fn new(styles: &[Style], value: &str) -> Cell { pub fn new(styles: &[Style], value: &str) -> Self {
Cell { Self {
styles: styles.to_vec(), styles: styles.to_vec(),
value: value.to_string(), value: value.trim().to_string(),
flex: false,
} }
} }
@ -48,20 +51,45 @@ impl Cell {
} }
pub fn render(&self, col_size: usize) -> String { pub fn render(&self, col_size: usize) -> String {
let style_start = self let style_begin = self
.styles .styles
.iter() .iter()
.map(|style| format!("{}", style)) .map(|style| style.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.concat(); .concat();
let style_end = "\x1b[0m";
let padding = if col_size == 0 { if col_size > 0 && self.printable_value_len() > col_size {
"".to_string() let col_size = self
.value
.char_indices()
.map(|(i, _)| i)
.nth(col_size)
.unwrap()
- 2;
String::from(style_begin + &self.value[0..=col_size] + "" + style_end)
} else { } else {
" ".repeat(col_size - self.printable_value_len() + 1) let padding = if col_size == 0 {
}; "".to_string()
} else {
" ".repeat(col_size - self.printable_value_len() + 1)
};
String::from(style_start + &self.value + &padding + "\x1b[0m") String::from(style_begin + &self.value + &padding + style_end)
}
}
}
#[derive(Debug)]
pub struct FlexCell;
impl FlexCell {
pub fn new(styles: &[Style], value: &str) -> Cell {
Cell {
flex: true,
..Cell::new(styles, value)
}
} }
} }
@ -70,22 +98,18 @@ pub trait DisplayRow {
} }
pub trait DisplayTable<'a, T: DisplayRow + 'a> { pub trait DisplayTable<'a, T: DisplayRow + 'a> {
fn cols() -> &'a [&'a str]; fn header_row() -> Vec<Cell>;
fn rows(&self) -> &Vec<T>; fn rows(&self) -> &Vec<T>;
fn to_table(&self) -> String { fn to_table(&self) -> String {
let mut col_sizes = vec![]; let mut col_sizes = vec![];
let head = Self::header_row();
let head = Self::cols() head.iter().for_each(|cell| {
.iter() col_sizes.push(cell.printable_value_len());
.map(|col| { });
let cell = Cell::new(&[BOLD, UNDERLINE, WHITE], &col.to_uppercase());
col_sizes.push(cell.printable_value_len());
cell
})
.collect::<Vec<_>>();
let mut body = self let mut table = self
.rows() .rows()
.iter() .iter()
.map(|item| { .map(|item| {
@ -97,13 +121,28 @@ pub trait DisplayTable<'a, T: DisplayRow + 'a> {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
body.insert(0, head); table.insert(0, head);
body.iter().fold(String::new(), |output, row| { let term_width = terminal_size().map(|size| size.0 .0).unwrap_or(0) as usize;
let seps_width = 2 * col_sizes.len() - 1;
let table_width = col_sizes.iter().sum::<usize>() + seps_width;
let diff_width = if table_width < term_width {
0
} else {
table_width - term_width
};
table.iter().fold(String::new(), |output, row| {
let row_str = row let row_str = row
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, cell)| cell.render(col_sizes[i])) .map(|(i, cell)| {
if cell.flex && col_sizes[i] > diff_width {
cell.render(col_sizes[i] - diff_width)
} else {
cell.render(col_sizes[i])
}
})
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(&Cell::new(&[ext(8)], "|").render(0)); .join(&Cell::new(&[ext(8)], "|").render(0));