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]
- Reply, reply all and forward [#9] [#10] [#11]
- 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
@ -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
[#14]: https://github.com/soywod/himalaya/issues/14
[#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",
"rustyline",
"serde",
"terminal_size",
"toml",
]
@ -946,6 +947,16 @@ dependencies = [
"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]]
name = "textwrap"
version = "0.11.0"

View file

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

View file

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

View file

@ -379,7 +379,10 @@ impl DisplayRow for Msg {
let headers = parsed.get_headers();
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
.get_first_value("reply-to")
.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 date = headers.get_first_value("date").unwrap_or_default();
vec![
table::Cell::new(&[table::RED], &uid),
table::Cell::new(&[table::WHITE], &flags),
table::Cell::new(&[table::BLUE], &sender),
table::Cell::new(&[table::GREEN], &subject),
table::Cell::new(&[table::YELLOW], &date),
]
{
use crate::table::*;
vec![
Cell::new(&[RED], &uid),
Cell::new(&[WHITE], &flags),
Cell::new(&[BLUE], &sender),
FlexCell::new(&[GREEN], &subject),
Cell::new(&[YELLOW], &date),
]
}
}
}
}
}
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
fn cols() -> &'a [&'a str] {
&["uid", "flags", "sender", "subject", "date"]
fn header_row() -> Vec<table::Cell> {
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> {

View file

@ -1,4 +1,5 @@
use std::fmt;
use terminal_size::terminal_size;
#[derive(Clone, Debug)]
pub struct Style(u8, u8, u8);
@ -33,13 +34,15 @@ impl fmt::Display for Style {
pub struct Cell {
pub styles: Vec<Style>,
pub value: String,
pub flex: bool,
}
impl Cell {
pub fn new(styles: &[Style], value: &str) -> Cell {
Cell {
pub fn new(styles: &[Style], value: &str) -> Self {
Self {
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 {
let style_start = self
let style_begin = self
.styles
.iter()
.map(|style| format!("{}", style))
.map(|style| style.to_string())
.collect::<Vec<_>>()
.concat();
let style_end = "\x1b[0m";
let padding = if col_size == 0 {
"".to_string()
if col_size > 0 && self.printable_value_len() > col_size {
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 {
" ".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> {
fn cols() -> &'a [&'a str];
fn header_row() -> Vec<Cell>;
fn rows(&self) -> &Vec<T>;
fn to_table(&self) -> String {
let mut col_sizes = vec![];
let head = Self::header_row();
let head = Self::cols()
.iter()
.map(|col| {
let cell = Cell::new(&[BOLD, UNDERLINE, WHITE], &col.to_uppercase());
col_sizes.push(cell.printable_value_len());
cell
})
.collect::<Vec<_>>();
head.iter().for_each(|cell| {
col_sizes.push(cell.printable_value_len());
});
let mut body = self
let mut table = self
.rows()
.iter()
.map(|item| {
@ -97,13 +121,28 @@ pub trait DisplayTable<'a, T: DisplayRow + 'a> {
})
.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
.iter()
.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<_>>()
.join(&Cell::new(&[ext(8)], "|").render(0));