diff --git a/CHANGELOG.md b/CHANGELOG.md index 67af44f..b717d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added `account.list.table.preset` global config option, `accounts..folder.list.table.preset` and `accounts..envelope.list.table.preset` account config options. + + These options customize the shape of tables, see examples at [`comfy_table::presets`](https://docs.rs/comfy-table/latest/comfy_table/presets/index.html). Defaults to `"|| |-||| "`, which corresponds to [`comfy_table::presets::ASCII_MARKDOWN`](https://docs.rs/comfy-table/latest/comfy_table/presets/constant.ASCII_MARKDOWN.html). + +- Added `account.list.table.name-color` config option to customize the color used for the accounts' `NAME` column (defaults to `green`). +- Added `account.list.table.backends-color` config option to customize the color used for the folders' `BACKENDS` column (defaults to `blue`). +- Added `account.list.table.default-color` config option to customize the color used for the folders' `DEFAULT` column (defaults to `reset`). +- Added `accounts..folder.list.table.name-color` account config option to customize the color used for the folders' `NAME` column (defaults to `blue`). +- Added `accounts..folder.list.table.desc-color` account config option to customize the color used for the folders' `DESC` column (defaults to `green`). +- Added `accounts..envelope.list.table.id-color` account config option to customize the color used for the envelopes' `ID` column (defaults to `red`). +- Added `accounts..envelope.list.table.flags-color` account config option to customize the color used for the envelopes' `FLAGS` column (defaults to `reset`). +- Added `accounts..envelope.list.table.subject-color` account config option to customize the color used for the envelopes' `SUBJECT` column (defaults to `green`). +- Added `accounts..envelope.list.table.sender-color` account config option to customize the color used for the envelopes' `FROM` column (defaults to `blue`). +- Added `accounts..envelope.list.table.date-color` account config option to customize the color used for the envelopes' `DATE` column (defaults to `dark_yellow`). +- Added `accounts..envelope.list.table.unseen-char` account config option to customize the char used for unseen envelopes (defaults to `*`). +- Added `accounts..envelope.list.table.replied-char` account config option to customize the char used for replied envelopes (defaults to `R`). +- Added `accounts..envelope.list.table.flagged-char` account config option to customize the char used for flagged envelopes (defaults to `!`). +- Added `accounts..envelope.list.table.attachment-char` account config option to customize the char used for envelopes with at least one attachment (defaults to `@`). + ## [1.0.0-beta.4] - 2024-04-16 ### Added diff --git a/Cargo.lock b/Cargo.lock index 5dba0b8..b2d3eb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,6 +418,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -885,6 +888,7 @@ dependencies = [ "libc", "mio 0.8.11", "parking_lot 0.12.3", + "serde", "signal-hook", "signal-hook-mio", "winapi", diff --git a/Cargo.toml b/Cargo.toml index bb4777f..157f689 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ clap_mangen = "0.2" color-eyre = "0.6.3" comfy-table = "7.1.1" console = "0.15.2" -crossterm = "0.27" +crossterm = { version = "0.27", features = ["serde"] } dirs = "4" email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] } email_address = "0.2.4" diff --git a/src/account/command/list.rs b/src/account/command/list.rs index d98531c..1840840 100644 --- a/src/account/command/list.rs +++ b/src/account/command/list.rs @@ -28,7 +28,12 @@ impl AccountListCommand { info!("executing list accounts command"); let accounts = Accounts::from(config.accounts.iter()); - let table = AccountsTable::from(accounts).with_some_width(self.table_max_width); + let table = AccountsTable::from(accounts) + .with_some_width(self.table_max_width) + .with_some_preset(config.account_list_table_preset()) + .with_some_name_color(config.account_list_table_name_color()) + .with_some_backends_color(config.account_list_table_backends_color()) + .with_some_default_color(config.account_list_table_default_color()); printer.out(table)?; Ok(()) diff --git a/src/account/config.rs b/src/account/config.rs index b64c7b0..6581130 100644 --- a/src/account/config.rs +++ b/src/account/config.rs @@ -3,6 +3,8 @@ //! This module contains the raw deserialized representation of an //! account in the accounts section of the user configuration file. +use comfy_table::presets; +use crossterm::style::Color; #[cfg(feature = "pgp")] use email::account::config::pgp::PgpConfig; #[cfg(feature = "imap")] @@ -21,7 +23,7 @@ use std::{collections::HashSet, path::PathBuf}; use crate::{ backend::BackendKind, envelope::config::EnvelopeConfig, flag::config::FlagConfig, - folder::config::FolderConfig, message::config::MessageConfig, + folder::config::FolderConfig, message::config::MessageConfig, ui::map_color, }; /// Represents all existing kind of account config. @@ -58,6 +60,110 @@ pub struct TomlAccountConfig { } impl TomlAccountConfig { + pub fn folder_list_table_preset(&self) -> Option { + self.folder + .as_ref() + .and_then(|folder| folder.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.preset.clone()) + } + + pub fn folder_list_table_name_color(&self) -> Option { + self.folder + .as_ref() + .and_then(|folder| folder.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.name_color) + } + + pub fn folder_list_table_desc_color(&self) -> Option { + self.folder + .as_ref() + .and_then(|folder| folder.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.desc_color) + } + + pub fn envelope_list_table_preset(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.preset.clone()) + } + + pub fn envelope_list_table_unseen_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.unseen_char) + } + + pub fn envelope_list_table_replied_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.replied_char) + } + + pub fn envelope_list_table_flagged_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.flagged_char) + } + + pub fn envelope_list_table_attachment_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.attachment_char) + } + + pub fn envelope_list_table_id_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.id_color) + } + + pub fn envelope_list_table_flags_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.flags_color) + } + + pub fn envelope_list_table_subject_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.subject_color) + } + + pub fn envelope_list_table_sender_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.sender_color) + } + + pub fn envelope_list_table_date_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|env| env.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.date_color) + } + pub fn add_folder_kind(&self) -> Option<&BackendKind> { self.folder .as_ref() @@ -228,3 +334,30 @@ impl TomlAccountConfig { used_backends } } + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ListAccountsTableConfig { + pub preset: Option, + pub name_color: Option, + pub backends_color: Option, + pub default_color: Option, +} + +impl ListAccountsTableConfig { + pub fn preset(&self) -> &str { + self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) + } + + pub fn name_color(&self) -> comfy_table::Color { + map_color(self.name_color.unwrap_or(Color::Green)) + } + + pub fn backends_color(&self) -> comfy_table::Color { + map_color(self.backends_color.unwrap_or(Color::Blue)) + } + + pub fn default_color(&self) -> comfy_table::Color { + map_color(self.default_color.unwrap_or(Color::Reset)) + } +} diff --git a/src/account/mod.rs b/src/account/mod.rs index 1662f14..4b13684 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -4,11 +4,12 @@ pub mod config; #[cfg(feature = "wizard")] pub(crate) mod wizard; -use comfy_table::{presets, Attribute, Cell, Color, ContentArrangement, Row, Table}; +use comfy_table::{Cell, ContentArrangement, Row, Table}; +use crossterm::style::Color; use serde::{Serialize, Serializer}; use std::{collections::hash_map::Iter, fmt, ops::Deref}; -use self::config::TomlAccountConfig; +use self::config::{ListAccountsTableConfig, TomlAccountConfig}; /// Represents the printable account. #[derive(Debug, Default, PartialEq, Eq, Serialize)] @@ -30,12 +31,12 @@ impl Account { } } - pub fn to_row(&self) -> Row { + pub fn to_row(&self, config: &ListAccountsTableConfig) -> Row { let mut row = Row::new(); - row.add_cell(Cell::new(&self.name).fg(Color::Green)); - row.add_cell(Cell::new(&self.backend).fg(Color::Blue)); - row.add_cell(Cell::new(if self.default { "yes" } else { "" }).fg(Color::White)); + row.add_cell(Cell::new(&self.name).fg(config.name_color())); + row.add_cell(Cell::new(&self.backend).fg(config.backends_color())); + row.add_cell(Cell::new(if self.default { "yes" } else { "" }).fg(config.default_color())); row } @@ -51,24 +52,6 @@ impl fmt::Display for Account { #[derive(Debug, Default, Serialize)] pub struct Accounts(Vec); -impl Accounts { - pub fn to_table(&self) -> Table { - let mut table = Table::new(); - - table - .load_preset(presets::NOTHING) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([ - Cell::new("NAME").add_attribute(Attribute::Reverse), - Cell::new("BACKENDS").add_attribute(Attribute::Reverse), - Cell::new("DEFAULT").add_attribute(Attribute::Reverse), - ])) - .add_rows(self.iter().map(Account::to_row)); - - table - } -} - impl Deref for Accounts { type Target = Vec; @@ -98,7 +81,7 @@ impl From> for Accounts { } #[cfg(feature = "notmuch")] - if account.imap.is_some() { + if account.notmuch.is_some() { if !backends.is_empty() { backends.push_str(", ") } @@ -135,6 +118,7 @@ impl From> for Accounts { pub struct AccountsTable { accounts: Accounts, width: Option, + config: ListAccountsTableConfig, } impl AccountsTable { @@ -142,6 +126,26 @@ impl AccountsTable { self.width = width; self } + + pub fn with_some_preset(mut self, preset: Option) -> Self { + self.config.preset = preset; + self + } + + pub fn with_some_name_color(mut self, color: Option) -> Self { + self.config.name_color = color; + self + } + + pub fn with_some_backends_color(mut self, color: Option) -> Self { + self.config.backends_color = color; + self + } + + pub fn with_some_default_color(mut self, color: Option) -> Self { + self.config.default_color = color; + self + } } impl From for AccountsTable { @@ -149,13 +153,28 @@ impl From for AccountsTable { Self { accounts, width: None, + config: Default::default(), } } } impl fmt::Display for AccountsTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = self.accounts.to_table(); + let mut table = Table::new(); + + table + .load_preset(self.config.preset()) + .set_content_arrangement(ContentArrangement::DynamicFullWidth) + .set_header(Row::from([ + Cell::new("NAME"), + Cell::new("BACKENDS"), + Cell::new("DEFAULT"), + ])) + .add_rows( + self.accounts + .iter() + .map(|account| account.to_row(&self.config)), + ); if let Some(width) = self.width { table.set_width(width); diff --git a/src/config/mod.rs b/src/config/mod.rs index 043db8b..bced744 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,6 +5,7 @@ use color_eyre::{ eyre::{bail, eyre, Context}, Result, }; +use crossterm::style::Color; use dirs::{config_dir, home_dir}; use email::{ account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig, @@ -17,7 +18,7 @@ use std::{collections::HashMap, fs, path::PathBuf, sync::Arc}; use toml::{self, Value}; use tracing::debug; -use crate::account::config::TomlAccountConfig; +use crate::account::config::{ListAccountsTableConfig, TomlAccountConfig}; #[cfg(feature = "wizard")] use crate::wizard_warn; @@ -31,9 +32,42 @@ pub struct TomlConfig { pub signature_delim: Option, pub downloads_dir: Option, pub accounts: HashMap, + pub account: Option, } impl TomlConfig { + pub fn account_list_table_preset(&self) -> Option { + self.account + .as_ref() + .and_then(|account| account.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.preset.clone()) + } + + pub fn account_list_table_name_color(&self) -> Option { + self.account + .as_ref() + .and_then(|account| account.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.name_color) + } + + pub fn account_list_table_backends_color(&self) -> Option { + self.account + .as_ref() + .and_then(|account| account.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.backends_color) + } + + pub fn account_list_table_default_color(&self) -> Option { + self.account + .as_ref() + .and_then(|account| account.list.as_ref()) + .and_then(|list| list.table.as_ref()) + .and_then(|table| table.default_color) + } + /// Read and parse the TOML configuration at the given paths. /// /// Returns an error if a configuration file cannot be read or if @@ -266,3 +300,15 @@ pub fn path_parser(path: &str) -> Result { .map(canonicalize::path) .map_err(|err| err.to_string()) } + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct AccountsConfig { + pub list: Option, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ListAccountsConfig { + pub table: Option, +} diff --git a/src/email/envelope/command/list.rs b/src/email/envelope/command/list.rs index 156133d..fd59ff2 100644 --- a/src/email/envelope/command/list.rs +++ b/src/email/envelope/command/list.rs @@ -188,7 +188,18 @@ impl ListEnvelopesCommand { }; let envelopes = backend.list_envelopes(folder, opts).await?; - let table = EnvelopesTable::from(envelopes).with_some_width(self.table_max_width); + let table = EnvelopesTable::from(envelopes) + .with_some_width(self.table_max_width) + .with_some_preset(toml_account_config.envelope_list_table_preset()) + .with_some_unseen_char(toml_account_config.envelope_list_table_unseen_char()) + .with_some_replied_char(toml_account_config.envelope_list_table_replied_char()) + .with_some_flagged_char(toml_account_config.envelope_list_table_flagged_char()) + .with_some_attachment_char(toml_account_config.envelope_list_table_attachment_char()) + .with_some_id_color(toml_account_config.envelope_list_table_id_color()) + .with_some_flags_color(toml_account_config.envelope_list_table_flags_color()) + .with_some_subject_color(toml_account_config.envelope_list_table_subject_color()) + .with_some_sender_color(toml_account_config.envelope_list_table_sender_color()) + .with_some_date_color(toml_account_config.envelope_list_table_date_color()); printer.out(table)?; Ok(()) diff --git a/src/email/envelope/config.rs b/src/email/envelope/config.rs index 3fda1ea..30804ab 100644 --- a/src/email/envelope/config.rs +++ b/src/email/envelope/config.rs @@ -1,7 +1,9 @@ +use comfy_table::presets; +use crossterm::style::Color; use serde::{Deserialize, Serialize}; use std::collections::HashSet; -use crate::backend::BackendKind; +use crate::{backend::BackendKind, ui::map_color}; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct EnvelopeConfig { @@ -27,8 +29,10 @@ impl EnvelopeConfig { } #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] pub struct ListEnvelopesConfig { pub backend: Option, + pub table: Option, #[serde(flatten)] pub remote: email::envelope::list::config::EnvelopeListConfig, @@ -46,6 +50,81 @@ impl ListEnvelopesConfig { } } +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ListEnvelopesTableConfig { + pub preset: Option, + + pub unseen_char: Option, + pub replied_char: Option, + pub flagged_char: Option, + pub attachment_char: Option, + + pub id_color: Option, + pub flags_color: Option, + pub subject_color: Option, + pub sender_color: Option, + pub date_color: Option, +} + +impl ListEnvelopesTableConfig { + pub fn preset(&self) -> &str { + self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) + } + + pub fn replied_char(&self, replied: bool) -> char { + if replied { + self.replied_char.unwrap_or('R') + } else { + ' ' + } + } + + pub fn flagged_char(&self, flagged: bool) -> char { + if flagged { + self.flagged_char.unwrap_or('!') + } else { + ' ' + } + } + + pub fn attachment_char(&self, attachment: bool) -> char { + if attachment { + self.attachment_char.unwrap_or('@') + } else { + ' ' + } + } + + pub fn unseen_char(&self, unseen: bool) -> char { + if unseen { + self.unseen_char.unwrap_or('*') + } else { + ' ' + } + } + + pub fn id_color(&self) -> comfy_table::Color { + map_color(self.id_color.unwrap_or(Color::Red)) + } + + pub fn flags_color(&self) -> comfy_table::Color { + map_color(self.flags_color.unwrap_or(Color::Reset)) + } + + pub fn subject_color(&self) -> comfy_table::Color { + map_color(self.subject_color.unwrap_or(Color::Green)) + } + + pub fn sender_color(&self) -> comfy_table::Color { + map_color(self.sender_color.unwrap_or(Color::Blue)) + } + + pub fn date_color(&self) -> comfy_table::Color { + map_color(self.date_color.unwrap_or(Color::DarkYellow)) + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct ThreadEnvelopesConfig { pub backend: Option, diff --git a/src/email/envelope/mod.rs b/src/email/envelope/mod.rs index 900abcc..19cef09 100644 --- a/src/email/envelope/mod.rs +++ b/src/email/envelope/mod.rs @@ -4,8 +4,12 @@ pub mod config; pub mod flag; use color_eyre::Result; -use comfy_table::{presets, Attribute, Cell, ContentArrangement, Row, Table}; -use crossterm::{cursor, style::Stylize, terminal}; +use comfy_table::{Attribute, Cell, ContentArrangement, Row, Table}; +use crossterm::{ + cursor, + style::{Color, Stylize}, + terminal, +}; use email::{account::config::AccountConfig, envelope::ThreadedEnvelope}; use petgraph::graphmap::DiGraphMap; use serde::{Serialize, Serializer}; @@ -16,6 +20,8 @@ use crate::{ flag::{Flag, Flags}, }; +use self::config::ListEnvelopesTableConfig; + #[derive(Clone, Debug, Default, Serialize)] pub struct Mailbox { pub name: Option, @@ -33,72 +39,11 @@ pub struct Envelope { pub has_attachment: bool, } -impl From for Row { - fn from(envelope: Envelope) -> Self { +impl Envelope { + fn to_row(&self, config: &ListEnvelopesTableConfig) -> Row { let mut all_attributes = vec![]; - let unseen = !envelope.flags.contains(&Flag::Seen); - if unseen { - all_attributes.push(Attribute::Bold) - } - - let flags = { - let mut flags = String::new(); - flags.push(if !unseen { ' ' } else { '✷' }); - flags.push(if envelope.flags.contains(&Flag::Answered) { - '↵' - } else { - ' ' - }); - flags.push(if envelope.flags.contains(&Flag::Flagged) { - '⚑' - } else { - ' ' - }); - flags - }; - - let mut row = Row::new(); - - row.add_cell( - Cell::new(envelope.id) - .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Red), - ) - .add_cell( - Cell::new(flags) - .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::White), - ) - .add_cell( - Cell::new(envelope.subject) - .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Green), - ) - .add_cell( - Cell::new(if let Some(name) = envelope.from.name { - name - } else { - envelope.from.addr - }) - .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Blue), - ) - .add_cell( - Cell::new(envelope.date) - .add_attributes(all_attributes) - .fg(comfy_table::Color::Yellow), - ); - - row - } -} - -impl From<&Envelope> for Row { - fn from(envelope: &Envelope) -> Self { - let mut all_attributes = vec![]; - - let unseen = !envelope.flags.contains(&Flag::Seen); + let unseen = !self.flags.contains(&Flag::Seen); if unseen { all_attributes.push(Attribute::Bold) } @@ -106,55 +51,45 @@ impl From<&Envelope> for Row { let flags = { let mut flags = String::new(); - flags.push(if !unseen { ' ' } else { '✷' }); - - flags.push(if envelope.flags.contains(&Flag::Answered) { - '↵' - } else { - ' ' - }); - - flags.push(if envelope.flags.contains(&Flag::Flagged) { - '⚑' - } else { - ' ' - }); - - flags.push(if envelope.has_attachment { '📎' } else { ' ' }); + flags.push(config.unseen_char(unseen)); + flags.push(config.replied_char(self.flags.contains(&Flag::Answered))); + flags.push(config.flagged_char(self.flags.contains(&Flag::Flagged))); + flags.push(config.attachment_char(self.has_attachment)); flags }; let mut row = Row::new(); + row.max_height(1); row.add_cell( - Cell::new(&envelope.id) + Cell::new(&self.id) .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Red), + .fg(config.id_color()), ) .add_cell( Cell::new(flags) .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::White), + .fg(config.flags_color()), ) .add_cell( - Cell::new(&envelope.subject) + Cell::new(&self.subject) .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Green), + .fg(config.subject_color()), ) .add_cell( - Cell::new(if let Some(name) = &envelope.from.name { + Cell::new(if let Some(name) = &self.from.name { name } else { - &envelope.from.addr + &self.from.addr }) .add_attributes(all_attributes.clone()) - .fg(comfy_table::Color::Blue), + .fg(config.sender_color()), ) .add_cell( - Cell::new(&envelope.date) + Cell::new(&self.date) .add_attributes(all_attributes) - .fg(comfy_table::Color::Yellow), + .fg(config.date_color()), ); row @@ -193,24 +128,6 @@ impl Envelopes { Ok(Envelopes(envelopes)) } - - pub fn to_table(&self) -> Table { - let mut table = Table::new(); - - table - .load_preset(presets::NOTHING) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([ - Cell::new("ID").add_attribute(Attribute::Reverse), - Cell::new("FLAGS").add_attribute(Attribute::Reverse), - Cell::new("SUBJECT").add_attribute(Attribute::Reverse), - Cell::new("FROM").add_attribute(Attribute::Reverse), - Cell::new("DATE").add_attribute(Attribute::Reverse), - ])) - .add_rows(self.iter().map(Row::from)); - - table - } } impl Deref for Envelopes { @@ -224,6 +141,7 @@ impl Deref for Envelopes { pub struct EnvelopesTable { envelopes: Envelopes, width: Option, + config: ListEnvelopesTableConfig, } impl EnvelopesTable { @@ -231,6 +149,56 @@ impl EnvelopesTable { self.width = width; self } + + pub fn with_some_preset(mut self, preset: Option) -> Self { + self.config.preset = preset; + self + } + + pub fn with_some_unseen_char(mut self, char: Option) -> Self { + self.config.unseen_char = char; + self + } + + pub fn with_some_replied_char(mut self, char: Option) -> Self { + self.config.replied_char = char; + self + } + + pub fn with_some_flagged_char(mut self, char: Option) -> Self { + self.config.flagged_char = char; + self + } + + pub fn with_some_attachment_char(mut self, char: Option) -> Self { + self.config.attachment_char = char; + self + } + + pub fn with_some_id_color(mut self, color: Option) -> Self { + self.config.id_color = color; + self + } + + pub fn with_some_flags_color(mut self, color: Option) -> Self { + self.config.flags_color = color; + self + } + + pub fn with_some_subject_color(mut self, color: Option) -> Self { + self.config.subject_color = color; + self + } + + pub fn with_some_sender_color(mut self, color: Option) -> Self { + self.config.sender_color = color; + self + } + + pub fn with_some_date_color(mut self, color: Option) -> Self { + self.config.date_color = color; + self + } } impl From for EnvelopesTable { @@ -238,13 +206,26 @@ impl From for EnvelopesTable { Self { envelopes, width: None, + config: Default::default(), } } } impl fmt::Display for EnvelopesTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = self.envelopes.to_table(); + let mut table = Table::new(); + + table + .load_preset(self.config.preset()) + .set_content_arrangement(ContentArrangement::DynamicFullWidth) + .set_header(Row::from([ + Cell::new("ID"), + Cell::new("FLAGS"), + Cell::new("SUBJECT"), + Cell::new("FROM"), + Cell::new("DATE"), + ])) + .add_rows(self.envelopes.iter().map(|env| env.to_row(&self.config))); if let Some(width) = self.width { table.set_width(width); diff --git a/src/folder/command/list.rs b/src/folder/command/list.rs index 6334375..8c40f08 100644 --- a/src/folder/command/list.rs +++ b/src/folder/command/list.rs @@ -47,7 +47,11 @@ impl FolderListCommand { .await?; let folders = Folders::from(backend.list_folders().await?); - let table = FoldersTable::from(folders).with_some_width(self.table_max_width); + let table = FoldersTable::from(folders) + .with_some_width(self.table_max_width) + .with_some_preset(toml_account_config.folder_list_table_preset()) + .with_some_name_color(toml_account_config.folder_list_table_name_color()) + .with_some_desc_color(toml_account_config.folder_list_table_desc_color()); printer.log(table)?; Ok(()) diff --git a/src/folder/config.rs b/src/folder/config.rs index da085c4..6ce8bf3 100644 --- a/src/folder/config.rs +++ b/src/folder/config.rs @@ -1,7 +1,9 @@ +use comfy_table::presets; +use crossterm::style::Color; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; -use crate::backend::BackendKind; +use crate::{backend::BackendKind, ui::map_color}; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct FolderConfig { @@ -60,8 +62,10 @@ impl FolderAddConfig { } #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] pub struct FolderListConfig { pub backend: Option, + pub table: Option, #[serde(flatten)] pub remote: email::folder::list::config::FolderListConfig, @@ -79,6 +83,28 @@ impl FolderListConfig { } } +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ListFoldersTableConfig { + pub preset: Option, + pub name_color: Option, + pub desc_color: Option, +} + +impl ListFoldersTableConfig { + pub fn preset(&self) -> &str { + self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) + } + + pub fn name_color(&self) -> comfy_table::Color { + map_color(self.name_color.unwrap_or(Color::Blue)) + } + + pub fn desc_color(&self) -> comfy_table::Color { + map_color(self.desc_color.unwrap_or(Color::Green)) + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct FolderExpungeConfig { pub backend: Option, diff --git a/src/folder/mod.rs b/src/folder/mod.rs index d370d8b..b00d79f 100644 --- a/src/folder/mod.rs +++ b/src/folder/mod.rs @@ -2,10 +2,13 @@ pub mod arg; pub mod command; pub mod config; -use comfy_table::{presets, Attribute, Cell, ContentArrangement, Row, Table}; +use comfy_table::{Cell, ContentArrangement, Row, Table}; +use crossterm::style::Color; use serde::{Serialize, Serializer}; use std::{fmt, ops::Deref}; +use self::config::ListFoldersTableConfig; + #[derive(Clone, Debug, Default, Serialize)] pub struct Folder { pub name: String, @@ -13,11 +16,12 @@ pub struct Folder { } impl Folder { - pub fn to_row(&self) -> Row { + pub fn to_row(&self, config: &ListFoldersTableConfig) -> Row { let mut row = Row::new(); + row.max_height(1); - row.add_cell(Cell::new(&self.name).fg(comfy_table::Color::Blue)); - row.add_cell(Cell::new(&self.desc).fg(comfy_table::Color::Green)); + row.add_cell(Cell::new(&self.name).fg(config.name_color())); + row.add_cell(Cell::new(&self.desc).fg(config.desc_color())); row } @@ -35,23 +39,6 @@ impl From for Folder { #[derive(Clone, Debug, Default, Serialize)] pub struct Folders(Vec); -impl Folders { - pub fn to_table(&self) -> Table { - let mut table = Table::new(); - - table - .load_preset(presets::NOTHING) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([ - Cell::new("NAME").add_attribute(Attribute::Reverse), - Cell::new("DESC").add_attribute(Attribute::Reverse), - ])) - .add_rows(self.iter().map(Folder::to_row)); - - table - } -} - impl Deref for Folders { type Target = Vec; @@ -69,6 +56,7 @@ impl From for Folders { pub struct FoldersTable { folders: Folders, width: Option, + config: ListFoldersTableConfig, } impl FoldersTable { @@ -76,6 +64,21 @@ impl FoldersTable { self.width = width; self } + + pub fn with_some_preset(mut self, preset: Option) -> Self { + self.config.preset = preset; + self + } + + pub fn with_some_name_color(mut self, color: Option) -> Self { + self.config.name_color = color; + self + } + + pub fn with_some_desc_color(mut self, color: Option) -> Self { + self.config.desc_color = color; + self + } } impl From for FoldersTable { @@ -83,13 +86,24 @@ impl From for FoldersTable { Self { folders, width: None, + config: Default::default(), } } } impl fmt::Display for FoldersTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = self.folders.to_table(); + let mut table = Table::new(); + + table + .load_preset(self.config.preset()) + .set_content_arrangement(ContentArrangement::DynamicFullWidth) + .set_header(Row::from([Cell::new("NAME"), Cell::new("DESC")])) + .add_rows( + self.folders + .iter() + .map(|folder| folder.to_row(&self.config)), + ); if let Some(width) = self.width { table.set_width(width); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1eb8655..a9a4f69 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,29 @@ +use crossterm::style::Color; + pub mod choice; pub mod editor; pub(crate) mod prompt; + +pub(crate) fn map_color(color: Color) -> comfy_table::Color { + match color { + Color::Reset => comfy_table::Color::Reset, + Color::Black => comfy_table::Color::Black, + Color::DarkGrey => comfy_table::Color::DarkGrey, + Color::Red => comfy_table::Color::Red, + Color::DarkRed => comfy_table::Color::DarkRed, + Color::Green => comfy_table::Color::Green, + Color::DarkGreen => comfy_table::Color::DarkGreen, + Color::Yellow => comfy_table::Color::Yellow, + Color::DarkYellow => comfy_table::Color::DarkYellow, + Color::Blue => comfy_table::Color::Blue, + Color::DarkBlue => comfy_table::Color::DarkBlue, + Color::Magenta => comfy_table::Color::Magenta, + Color::DarkMagenta => comfy_table::Color::DarkMagenta, + Color::Cyan => comfy_table::Color::Cyan, + Color::DarkCyan => comfy_table::Color::DarkCyan, + Color::White => comfy_table::Color::White, + Color::Grey => comfy_table::Color::Grey, + Color::Rgb { r, g, b } => comfy_table::Color::Rgb { r, g, b }, + Color::AnsiValue(n) => comfy_table::Color::AnsiValue(n), + } +}