style: put back color inside table

This commit is contained in:
Clément DOUIN
2026-05-20 20:02:41 +02:00
parent e58fad97b6
commit 791a54ef15
24 changed files with 821 additions and 129 deletions
+212 -6
View File
@@ -30,15 +30,24 @@
use std::{collections::HashMap, env::temp_dir, path::PathBuf};
use comfy_table::{presets, ContentArrangement};
use comfy_table::{presets, Color as TableColor, ContentArrangement};
use crossterm::style::Color;
use dirs::download_dir;
use crate::config::{AccountConfig, ComposerConfig, Config, ReaderConfig, TableArrangementConfig};
use crate::config::{
AccountConfig, AttachmentListTableConfig, ComposerConfig, Config, EnvelopeListTableConfig,
MailboxListTableConfig, ReaderConfig, TableArrangementConfig,
};
const DEFAULT_DATETIME_FMT: &str = "%F %R%:z";
const DEFAULT_MAILBOX_ALIAS: &str = "inbox";
const DEFAULT_ENVELOPES_LIST_PAGE_SIZE: u32 = 25;
const DEFAULT_UNSEEN_CHAR: char = '*';
const DEFAULT_REPLIED_CHAR: char = 'R';
const DEFAULT_FLAGGED_CHAR: char = '!';
const DEFAULT_ATTACHMENT_CHAR: char = '@';
#[derive(Clone, Debug, Default)]
pub struct Account {
pub downloads_dir: Option<PathBuf>,
@@ -49,6 +58,13 @@ pub struct Account {
pub datetime_local_tz: Option<bool>,
pub envelopes_list_page_size: Option<u32>,
/// Per-column color + flag glyph overrides for `envelopes list`.
pub envelopes_list_table: EnvelopeListTableConfig,
/// Per-column color overrides for `mailboxes list`.
pub mailboxes_list_table: MailboxListTableConfig,
/// Per-column color overrides for `attachments list`.
pub attachments_list_table: AttachmentListTableConfig,
/// Mailbox aliases, keys lowercased. Populated from
/// `mailbox.alias` at the global and account levels; account
/// entries overwrite same-named global entries.
@@ -87,6 +103,19 @@ impl Account {
.envelopes_list_page_size
.or(self.envelopes_list_page_size),
envelopes_list_table: merge_envelope_table(
self.envelopes_list_table,
other.envelopes_list_table,
),
mailboxes_list_table: merge_mailbox_table(
self.mailboxes_list_table,
other.mailboxes_list_table,
),
attachments_list_table: merge_attachment_table(
self.attachments_list_table,
other.attachments_list_table,
),
mailbox_alias,
composer,
@@ -167,6 +196,175 @@ impl Account {
.get(DEFAULT_MAILBOX_ALIAS)
.map(String::as_str)
}
// ── envelopes list — flag glyphs ─────────────────────────────────────
pub fn envelopes_list_table_unseen_char(&self) -> char {
self.envelopes_list_table
.unseen_char
.unwrap_or(DEFAULT_UNSEEN_CHAR)
}
pub fn envelopes_list_table_replied_char(&self) -> char {
self.envelopes_list_table
.replied_char
.unwrap_or(DEFAULT_REPLIED_CHAR)
}
pub fn envelopes_list_table_flagged_char(&self) -> char {
self.envelopes_list_table
.flagged_char
.unwrap_or(DEFAULT_FLAGGED_CHAR)
}
pub fn envelopes_list_table_attachment_char(&self) -> char {
self.envelopes_list_table
.attachment_char
.unwrap_or(DEFAULT_ATTACHMENT_CHAR)
}
// ── envelopes list — column colors ───────────────────────────────────
//
// Defaults mirror pimalaya-tui v1.2.0
// (`ListEnvelopesTableConfig::{id,flags,subject,sender,date}_color`).
pub fn envelopes_list_table_id_color(&self) -> TableColor {
map_color_or(self.envelopes_list_table.id_color, Color::Red)
}
pub fn envelopes_list_table_flags_color(&self) -> TableColor {
map_color_or(self.envelopes_list_table.flags_color, Color::Reset)
}
pub fn envelopes_list_table_att_color(&self) -> TableColor {
// No v1 precedent for a standalone ATT column (v1 embedded the
// attachment glyph inside FLAGS); leave it neutral.
map_color_or(self.envelopes_list_table.att_color, Color::Reset)
}
pub fn envelopes_list_table_subject_color(&self) -> TableColor {
map_color_or(self.envelopes_list_table.subject_color, Color::Green)
}
pub fn envelopes_list_table_from_color(&self) -> TableColor {
map_color_or(self.envelopes_list_table.from_color, Color::Blue)
}
pub fn envelopes_list_table_to_color(&self) -> TableColor {
// `to` mirrors `from`'s default; v1 didn't surface a TO column.
map_color_or(self.envelopes_list_table.to_color, Color::Blue)
}
pub fn envelopes_list_table_date_color(&self) -> TableColor {
map_color_or(self.envelopes_list_table.date_color, Color::DarkYellow)
}
pub fn envelopes_list_table_size_color(&self) -> TableColor {
// New in v2, no v1 precedent.
map_color_or(self.envelopes_list_table.size_color, Color::Reset)
}
// ── mailboxes list — column colors ───────────────────────────────────
//
// `name` matches the v1 `folder.list.table.name-color` default
// (`pimalaya-tui::ListFoldersTableConfig::name_color`); the other
// columns are new in v2.
pub fn mailboxes_list_table_id_color(&self) -> TableColor {
map_color_or(self.mailboxes_list_table.id_color, Color::Reset)
}
pub fn mailboxes_list_table_name_color(&self) -> TableColor {
map_color_or(self.mailboxes_list_table.name_color, Color::Blue)
}
pub fn mailboxes_list_table_total_color(&self) -> TableColor {
map_color_or(self.mailboxes_list_table.total_color, Color::Reset)
}
pub fn mailboxes_list_table_unread_color(&self) -> TableColor {
map_color_or(self.mailboxes_list_table.unread_color, Color::Reset)
}
// ── attachments list — column colors ─────────────────────────────────
//
// No v1 precedent; defaults left neutral.
pub fn attachments_list_table_id_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.id_color, Color::Reset)
}
pub fn attachments_list_table_filename_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.filename_color, Color::Reset)
}
pub fn attachments_list_table_type_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.type_color, Color::Reset)
}
pub fn attachments_list_table_size_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.size_color, Color::Reset)
}
pub fn attachments_list_table_inline_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.inline_color, Color::Reset)
}
pub fn attachments_list_table_path_color(&self) -> TableColor {
map_color_or(self.attachments_list_table.path_color, Color::Reset)
}
}
/// Maps a [`crossterm::style::Color`] (deserialized from TOML) into a
/// [`comfy_table::Color`] used by the renderers, substituting
/// `fallback` when the TOML field is unset.
pub(crate) fn map_color_or(color: Option<Color>, fallback: Color) -> TableColor {
match color.unwrap_or(fallback) {
Color::Reset => TableColor::Reset,
Color::Black => TableColor::Black,
Color::DarkGrey => TableColor::DarkGrey,
Color::Red => TableColor::Red,
Color::DarkRed => TableColor::DarkRed,
Color::Green => TableColor::Green,
Color::DarkGreen => TableColor::DarkGreen,
Color::Yellow => TableColor::Yellow,
Color::DarkYellow => TableColor::DarkYellow,
Color::Blue => TableColor::Blue,
Color::DarkBlue => TableColor::DarkBlue,
Color::Magenta => TableColor::Magenta,
Color::DarkMagenta => TableColor::DarkMagenta,
Color::Cyan => TableColor::Cyan,
Color::DarkCyan => TableColor::DarkCyan,
Color::White => TableColor::White,
Color::Grey => TableColor::Grey,
Color::Rgb { r, g, b } => TableColor::Rgb { r, g, b },
Color::AnsiValue(n) => TableColor::AnsiValue(n),
}
}
fn merge_envelope_table(
base: EnvelopeListTableConfig,
over: EnvelopeListTableConfig,
) -> EnvelopeListTableConfig {
EnvelopeListTableConfig {
unseen_char: over.unseen_char.or(base.unseen_char),
replied_char: over.replied_char.or(base.replied_char),
flagged_char: over.flagged_char.or(base.flagged_char),
attachment_char: over.attachment_char.or(base.attachment_char),
id_color: over.id_color.or(base.id_color),
flags_color: over.flags_color.or(base.flags_color),
att_color: over.att_color.or(base.att_color),
subject_color: over.subject_color.or(base.subject_color),
from_color: over.from_color.or(base.from_color),
to_color: over.to_color.or(base.to_color),
date_color: over.date_color.or(base.date_color),
size_color: over.size_color.or(base.size_color),
}
}
fn merge_mailbox_table(
base: MailboxListTableConfig,
over: MailboxListTableConfig,
) -> MailboxListTableConfig {
MailboxListTableConfig {
id_color: over.id_color.or(base.id_color),
name_color: over.name_color.or(base.name_color),
total_color: over.total_color.or(base.total_color),
unread_color: over.unread_color.or(base.unread_color),
}
}
fn merge_attachment_table(
base: AttachmentListTableConfig,
over: AttachmentListTableConfig,
) -> AttachmentListTableConfig {
AttachmentListTableConfig {
id_color: over.id_color.or(base.id_color),
filename_color: over.filename_color.or(base.filename_color),
type_color: over.type_color.or(base.type_color),
size_color: over.size_color.or(base.size_color),
inline_color: over.inline_color.or(base.inline_color),
path_color: over.path_color.or(base.path_color),
}
}
/// Lowercases every key of `aliases`, leaving values untouched. Used at
@@ -184,13 +382,17 @@ impl From<Config> for Account {
fn from(config: Config) -> Self {
Self {
downloads_dir: config.downloads_dir,
table_preset: config.table_preset,
table_arrangement: config.table_arrangement,
table_preset: config.table.preset,
table_arrangement: config.table.arrangement,
datetime_fmt: config.envelope.list.datetime_fmt,
datetime_local_tz: config.envelope.list.datetime_local_tz,
envelopes_list_page_size: config.envelope.list.page_size,
envelopes_list_table: config.envelope.list.table,
mailboxes_list_table: config.mailbox.list.table,
attachments_list_table: config.attachment.list.table,
mailbox_alias: lowercase_alias_keys(config.mailbox.alias),
composer: config.message.composer,
@@ -203,13 +405,17 @@ impl From<AccountConfig> for Account {
fn from(config: AccountConfig) -> Self {
Self {
downloads_dir: config.downloads_dir,
table_preset: config.table_preset,
table_arrangement: config.table_arrangement,
table_preset: config.table.preset,
table_arrangement: config.table.arrangement,
datetime_fmt: config.envelope.list.datetime_fmt,
datetime_local_tz: config.envelope.list.datetime_local_tz,
envelopes_list_page_size: config.envelope.list.page_size,
envelopes_list_table: config.envelope.list.table,
mailboxes_list_table: config.mailbox.list.table,
attachments_list_table: config.attachment.list.table,
mailbox_alias: lowercase_alias_keys(config.mailbox.alias),
composer: HashMap::new(),
+33 -7
View File
@@ -19,12 +19,16 @@ use std::{fmt, path::PathBuf};
use anyhow::Result;
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use crossterm::style::Color as CrosstermColor;
use pimalaya_cli::printer::Printer;
use pimalaya_config::toml::TomlConfig;
use serde::Serialize;
use crate::config::{AccountConfig, Config, TableArrangementConfig};
use crate::{
account::context::map_color_or,
config::{AccountConfig, Config, TableArrangementConfig},
};
/// List all accounts declared in the configuration.
///
@@ -38,15 +42,25 @@ impl AccountListCommand {
let config = load_config(config_paths)?;
let preset = config
.table_preset
.table
.preset
.clone()
.unwrap_or_else(|| comfy_table::presets::UTF8_FULL_CONDENSED.to_string());
let arrangement = config
.table_arrangement
.table
.arrangement
.clone()
.unwrap_or(TableArrangementConfig::Dynamic)
.into();
let table_cfg = &config.account.list.table;
let colors = AccountColors {
// v1.2.0 defaults: name=Green, backends=Blue, default=Reset.
name: map_color_or(table_cfg.name_color, CrosstermColor::Green),
backends: map_color_or(table_cfg.backends_color, CrosstermColor::Blue),
default: map_color_or(table_cfg.default_color, CrosstermColor::Reset),
};
let mut accounts: Vec<AccountRow> = config
.accounts
.iter()
@@ -57,6 +71,7 @@ impl AccountListCommand {
let table = AccountsTable {
preset,
arrangement,
colors,
accounts,
};
@@ -64,6 +79,13 @@ impl AccountListCommand {
}
}
#[derive(Clone, Copy, Debug)]
struct AccountColors {
name: Color,
backends: Color,
default: Color,
}
fn load_config(paths: &[PathBuf]) -> Result<Config> {
match Config::from_paths_or_default(paths)? {
Some(config) => Ok(config),
@@ -111,6 +133,8 @@ pub struct AccountsTable {
pub preset: String,
#[serde(skip)]
pub arrangement: ContentArrangement,
#[serde(skip)]
colors: AccountColors,
pub accounts: Vec<AccountRow>,
}
@@ -129,9 +153,11 @@ impl fmt::Display for AccountsTable {
.add_rows(self.accounts.iter().map(|account| {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(&account.name));
row.add_cell(Cell::new(account.backends.join(", ")));
row.add_cell(Cell::new(if account.default { "yes" } else { "" }));
row.add_cell(Cell::new(&account.name).fg(self.colors.name));
row.add_cell(Cell::new(account.backends.join(", ")).fg(self.colors.backends));
row.add_cell(
Cell::new(if account.default { "yes" } else { "" }).fg(self.colors.default),
);
row
}));
+141 -12
View File
@@ -19,6 +19,7 @@ use std::{collections::HashMap, fs, path::Path, path::PathBuf};
use anyhow::{Context, Result};
use comfy_table::ContentArrangement;
use crossterm::style::Color;
use pimalaya_config::{
secret::Secret,
toml::{shell_expanded_string, TomlConfig},
@@ -38,14 +39,20 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Config {
pub downloads_dir: Option<PathBuf>,
pub table_preset: Option<String>,
pub table_arrangement: Option<TableArrangementConfig>,
#[serde(default)]
pub table: TableConfig,
#[serde(default)]
pub envelope: EnvelopeConfig,
#[serde(default)]
pub mailbox: MailboxConfig,
#[serde(default)]
pub message: MessageConfig,
#[serde(default)]
pub attachment: AttachmentConfig,
/// `account list` rendering options (global only — there is no
/// per-account override for the listing of accounts).
#[serde(default)]
pub account: AccountListingConfig,
pub accounts: HashMap<String, AccountConfig>,
}
@@ -98,8 +105,8 @@ pub struct AccountConfig {
pub default: bool,
pub downloads_dir: Option<PathBuf>,
pub table_preset: Option<String>,
pub table_arrangement: Option<TableArrangementConfig>,
#[serde(default)]
pub table: TableConfig,
#[serde(default)]
pub envelope: EnvelopeConfig,
@@ -107,6 +114,9 @@ pub struct AccountConfig {
#[serde(default)]
pub mailbox: MailboxConfig,
#[serde(default)]
pub attachment: AttachmentConfig,
#[allow(unused)]
pub imap: Option<ImapConfig>,
#[allow(unused)]
@@ -127,21 +137,88 @@ pub struct EnvelopeConfig {
/// Mailbox-level configuration.
///
/// Currently exposes user-defined aliases mapping a friendly name to a
/// backend-native id. Alias names are looked up case-insensitively at
/// resolution time, so `INBOX`, `Inbox` and `inbox` all hit the same
/// entry. Ids are stored verbatim. The entry `inbox` (case-insensitive)
/// acts as the implicit default mailbox when a shared command omits
/// `-m/--mailbox`.
/// Exposes user-defined aliases mapping a friendly name to a
/// backend-native id (looked up case-insensitively at resolution
/// time; the `inbox` alias acts as the implicit default mailbox when
/// a shared command omits `-m/--mailbox`) and the `mailboxes list`
/// rendering options.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct MailboxConfig {
#[serde(default, alias = "aliases")]
pub alias: HashMap<String, String>,
#[serde(default)]
pub list: MailboxListConfig,
}
/// `envelopes list` rendering options. Mirrors the pre-v2
/// `envelope.list.*` keys.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct MailboxListConfig {
#[serde(default)]
pub table: MailboxListTableConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct MailboxListTableConfig {
pub id_color: Option<Color>,
pub name_color: Option<Color>,
pub total_color: Option<Color>,
pub unread_color: Option<Color>,
}
/// `attachments list` rendering options.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AttachmentConfig {
#[serde(default)]
pub list: AttachmentListConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AttachmentListConfig {
#[serde(default)]
pub table: AttachmentListTableConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AttachmentListTableConfig {
pub id_color: Option<Color>,
pub filename_color: Option<Color>,
pub type_color: Option<Color>,
pub size_color: Option<Color>,
pub inline_color: Option<Color>,
pub path_color: Option<Color>,
}
/// `account list` rendering options. Top-level only — there is no
/// per-account override.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AccountListingConfig {
#[serde(default)]
pub list: AccountListingListConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AccountListingListConfig {
#[serde(default)]
pub table: AccountListingTableConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct AccountListingTableConfig {
pub name_color: Option<Color>,
pub backends_color: Option<Color>,
pub default_color: Option<Color>,
}
/// `envelopes list` rendering options under `envelope.list.*`.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct EnvelopeListConfig {
@@ -159,6 +236,43 @@ pub struct EnvelopeListConfig {
/// flag wins when passed; otherwise the merged account/global
/// config wins; otherwise the hard fallback (25) is used.
pub page_size: Option<u32>,
/// Per-column color overrides + flag glyph customization for the
/// rendered envelopes table. Keys mirror the v1.2.0 layout
/// (`envelope.list.table.id-color`, `envelope.list.table.unseen-char`,
/// etc.). Color values accept either a named [crossterm color]
/// (`"red"`, `"dark-magenta"`, …) or an `{ Rgb = { r = .., g = ..,
/// b = .. } }`/`{ AnsiValue = N }` table.
///
/// [crossterm color]: https://docs.rs/crossterm/latest/crossterm/style/enum.Color.html
#[serde(default)]
pub table: EnvelopeListTableConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct EnvelopeListTableConfig {
/// Single character used in the FLAGS column for messages that
/// lack `\Seen`. Defaults to `*` (v1.2.0 default).
pub unseen_char: Option<char>,
/// Single character used in the FLAGS column for messages with
/// `\Answered`. Defaults to `R`.
pub replied_char: Option<char>,
/// Single character used in the FLAGS column for messages with
/// `\Flagged`. Defaults to `!`.
pub flagged_char: Option<char>,
/// Single character used in the ATT column for messages with at
/// least one attachment. Defaults to `@`.
pub attachment_char: Option<char>,
pub id_color: Option<Color>,
pub flags_color: Option<Color>,
pub att_color: Option<Color>,
pub subject_color: Option<Color>,
pub from_color: Option<Color>,
pub to_color: Option<Color>,
pub date_color: Option<Color>,
pub size_color: Option<Color>,
}
/// Message-level configuration: user-defined composers and readers.
@@ -211,6 +325,21 @@ pub struct ReaderConfig {
pub default: bool,
}
/// Global / per-account table rendering knobs shared across every list
/// command (envelopes, mailboxes, attachments). The per-column color
/// blocks live under `*.list.table.*-color` (see [`EnvelopeListTableConfig`]
/// & co.).
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct TableConfig {
/// `comfy_table` preset string (chars for borders / corners /
/// separators). Defaults to `UTF8_FULL_CONDENSED`. See
/// <https://docs.rs/comfy-table/latest/comfy_table/presets/>.
pub preset: Option<String>,
/// Column-arrangement strategy. Defaults to `Dynamic`.
pub arrangement: Option<TableArrangementConfig>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub enum TableArrangementConfig {
+24 -6
View File
@@ -19,7 +19,7 @@ use std::{collections::BTreeMap, fmt, num::NonZeroU32};
use anyhow::{bail, Result};
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_imap::types::{
core::Vec1,
envelope::Address,
@@ -102,6 +102,12 @@ impl ImapEnvelopeListCommand {
let table = EnvelopesTable {
preset: client.account.table_preset().to_string(),
arrangement: client.account.table_arrangement(),
colors: EnvelopeColors {
id: client.account.envelopes_list_table_id_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
date: client.account.envelopes_list_table_date_color(),
},
envelopes: map_envelopes_table_entries(data),
};
@@ -109,12 +115,22 @@ impl ImapEnvelopeListCommand {
}
}
#[derive(Clone, Copy, Debug)]
struct EnvelopeColors {
id: Color,
subject: Color,
from: Color,
date: Color,
}
#[derive(Clone, Debug, Serialize)]
pub struct EnvelopesTable {
#[serde(skip)]
preset: String,
#[serde(skip)]
arrangement: ContentArrangement,
#[serde(skip)]
colors: EnvelopeColors,
envelopes: Vec<EnvelopesTableEntry>,
}
@@ -136,11 +152,13 @@ impl fmt::Display for EnvelopesTable {
for entry in &self.envelopes {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(entry.seq));
row.add_cell(Cell::new(entry.uid));
row.add_cell(Cell::new(&entry.subject));
row.add_cell(Cell::new(&entry.from));
row.add_cell(Cell::new(&entry.date));
// SEQ and UID share the same `id_color` since both are
// protocol-side identifiers for the row.
row.add_cell(Cell::new(entry.seq).fg(self.colors.id));
row.add_cell(Cell::new(entry.uid).fg(self.colors.id));
row.add_cell(Cell::new(&entry.subject).fg(self.colors.subject));
row.add_cell(Cell::new(&entry.from).fg(self.colors.from));
row.add_cell(Cell::new(&entry.date).fg(self.colors.date));
table.add_row(row);
}
+5 -2
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::{anyhow, bail, Result};
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_imap::types::{
core::{AString, Vec1},
datetime::NaiveDate,
@@ -88,6 +88,7 @@ impl ImapEnvelopeSearchCommand {
let table = SearchTable {
preset: client.account.table_preset().to_string(),
arrangement: client.account.table_arrangement(),
id_color: client.account.envelopes_list_table_id_color(),
ids: ids
.into_iter()
.map(|id| SearchResult { id: id.get() })
@@ -110,6 +111,8 @@ pub struct SearchTable {
preset: String,
#[serde(skip)]
arrangement: ContentArrangement,
#[serde(skip)]
id_color: Color,
uid_mode: bool,
ids: Vec<SearchResult>,
}
@@ -126,7 +129,7 @@ impl fmt::Display for SearchTable {
.set_header(Row::from([Cell::new(id_header)]));
for result in &self.ids {
table.add_row(Row::from([Cell::new(result.id)]));
table.add_row(Row::from([Cell::new(result.id).fg(self.id_color)]));
}
writeln!(f)?;
+12 -5
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::{bail, Result};
use clap::Parser;
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
use comfy_table::{presets, Cell, Color, ContentArrangement, Row, Table};
use io_imap::types::{
core::Vec1,
extensions::sort::{SortCriterion, SortKey},
@@ -82,7 +82,8 @@ impl ImapEnvelopeSortCommand {
let ids = client.sort(sort_criteria, search_criteria, !self.seq)?;
let table = SortResultsTable::new(ids, !self.seq);
let id_color = client.account.envelopes_list_table_id_color();
let table = SortResultsTable::new(ids, !self.seq, id_color);
printer.out(table)?;
Ok(())
@@ -108,12 +109,18 @@ fn parse_sort_key(s: &str) -> Result<SortKey> {
pub struct SortResultsTable {
ids: Vec<u32>,
uid_mode: bool,
#[serde(skip)]
id_color: Color,
}
impl SortResultsTable {
pub fn new(ids: Vec<std::num::NonZeroU32>, uid_mode: bool) -> Self {
pub fn new(ids: Vec<std::num::NonZeroU32>, uid_mode: bool, id_color: Color) -> Self {
let ids = ids.into_iter().map(|id| id.get()).collect();
Self { ids, uid_mode }
Self {
ids,
uid_mode,
id_color,
}
}
}
@@ -129,7 +136,7 @@ impl fmt::Display for SortResultsTable {
.set_header(Row::from([Cell::new(id_header)]));
for id in &self.ids {
table.add_row(Row::from([Cell::new(id)]));
table.add_row(Row::from([Cell::new(id).fg(self.id_color)]));
}
writeln!(f)?;
+6 -3
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use comfy_table::{Cell, Row, Table};
use comfy_table::{Cell, Color, Row, Table};
use io_email::mailbox::MailboxRole;
use io_imap::types::{core::QuotedChar, flag::FlagNameAttribute, mailbox::Mailbox};
use pimalaya_cli::printer::Printer;
@@ -60,6 +60,7 @@ impl ImapMailboxListCommand {
let table = MailboxesTable {
preset: client.account.table_preset().to_string(),
name_color: client.account.mailboxes_list_table_name_color(),
mailboxes: mailboxes.into_iter().map(From::from).collect(),
};
@@ -67,10 +68,12 @@ impl ImapMailboxListCommand {
}
}
#[derive(Clone, Debug, Default, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct MailboxesTable {
#[serde(skip)]
pub preset: String,
#[serde(skip)]
pub name_color: Color,
pub mailboxes: Vec<MailboxRow>,
}
@@ -99,7 +102,7 @@ impl fmt::Display for MailboxesTable {
.unwrap_or_default();
row.max_height(1)
.add_cell(Cell::new(&mbox.name))
.add_cell(Cell::new(&mbox.name).fg(self.name_color))
.add_cell(Cell::new(&mbox.delimiter))
.add_cell(Cell::new(role))
.add_cell(Cell::new(mbox.attributes.join(", ")));
+16 -1
View File
@@ -20,7 +20,10 @@ use clap::Parser;
use log::warn;
use pimalaya_cli::printer::Printer;
use crate::jmap::{client::JmapClient, email::query::EmailsTable};
use crate::jmap::{
client::JmapClient,
email::query::{EmailsChars, EmailsColors, EmailsTable},
};
/// Get JMAP emails by ID (Email/get).
///
@@ -43,6 +46,18 @@ impl JmapEmailGetCommand {
let table = EmailsTable {
preset: client.account.table_preset().to_string(),
arrangement: client.account.table_arrangement(),
colors: EmailsColors {
id: client.account.envelopes_list_table_id_color(),
flags: client.account.envelopes_list_table_flags_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
date: client.account.envelopes_list_table_date_color(),
},
chars: EmailsChars {
unseen: client.account.envelopes_list_table_unseen_char(),
flagged: client.account.envelopes_list_table_flagged_char(),
attachment: client.account.envelopes_list_table_attachment_char(),
},
emails: output.emails,
};
+44 -11
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_jmap::rfc8621::email::{
Email, EmailAddress, EmailComparator, EmailFilter, EmailSortProperty,
};
@@ -165,6 +165,18 @@ impl JmapEmailQueryCommand {
let table = EmailsTable {
preset: client.account.table_preset().to_string(),
arrangement: client.account.table_arrangement(),
colors: EmailsColors {
id: client.account.envelopes_list_table_id_color(),
flags: client.account.envelopes_list_table_flags_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
date: client.account.envelopes_list_table_date_color(),
},
chars: EmailsChars {
unseen: client.account.envelopes_list_table_unseen_char(),
flagged: client.account.envelopes_list_table_flagged_char(),
attachment: client.account.envelopes_list_table_attachment_char(),
},
emails: output.emails,
};
@@ -172,12 +184,32 @@ impl JmapEmailQueryCommand {
}
}
#[derive(Clone, Copy, Debug)]
pub struct EmailsColors {
pub id: Color,
pub flags: Color,
pub subject: Color,
pub from: Color,
pub date: Color,
}
#[derive(Clone, Copy, Debug)]
pub struct EmailsChars {
pub unseen: char,
pub flagged: char,
pub attachment: char,
}
#[derive(Clone, Debug, Serialize)]
pub struct EmailsTable {
#[serde(skip)]
pub preset: String,
#[serde(skip)]
pub arrangement: ContentArrangement,
#[serde(skip)]
pub colors: EmailsColors,
#[serde(skip)]
pub chars: EmailsChars,
pub emails: Vec<Email>,
}
@@ -200,24 +232,25 @@ impl fmt::Display for EmailsTable {
let mut flags = String::new();
let kw = e.keywords.as_ref();
if !kw.and_then(|k| k.get("$seen")).copied().unwrap_or(false) {
flags.push('U');
flags.push(self.chars.unseen);
}
if kw.and_then(|k| k.get("$flagged")).copied().unwrap_or(false) {
flags.push('F');
flags.push(self.chars.flagged);
}
if e.has_attachment.unwrap_or(false) {
flags.push('A');
flags.push(self.chars.attachment);
}
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(e.id.as_deref().unwrap_or("")));
row.add_cell(Cell::new(&flags));
row.add_cell(Cell::new(e.subject.as_deref().unwrap_or("")));
row.add_cell(Cell::new(format_addresses(
e.from.as_deref().unwrap_or(&[]),
)));
row.add_cell(Cell::new(e.received_at.as_deref().unwrap_or("")));
row.add_cell(Cell::new(e.id.as_deref().unwrap_or("")).fg(self.colors.id));
row.add_cell(Cell::new(&flags).fg(self.colors.flags));
row.add_cell(Cell::new(e.subject.as_deref().unwrap_or("")).fg(self.colors.subject));
row.add_cell(
Cell::new(format_addresses(e.from.as_deref().unwrap_or(&[])))
.fg(self.colors.from),
);
row.add_cell(Cell::new(e.received_at.as_deref().unwrap_or("")).fg(self.colors.date));
table.add_row(row);
}
+10 -1
View File
@@ -20,7 +20,10 @@ use clap::Parser;
use log::warn;
use pimalaya_cli::printer::Printer;
use crate::jmap::{client::JmapClient, mailbox::query::MailboxesTable};
use crate::jmap::{
client::JmapClient,
mailbox::query::{MailboxColors, MailboxesTable},
};
/// Get JMAP mailboxes by ID (Mailbox/get).
#[derive(Debug, Parser)]
@@ -40,6 +43,12 @@ impl JmapMailboxGetCommand {
let table = MailboxesTable {
preset: client.account.table_preset().to_string(),
colors: MailboxColors {
id: client.account.mailboxes_list_table_id_color(),
name: client.account.mailboxes_list_table_name_color(),
total: client.account.mailboxes_list_table_total_color(),
unread: client.account.mailboxes_list_table_unread_color(),
},
mailboxes: output.mailboxes,
};
+34 -5
View File
@@ -19,7 +19,7 @@ use std::{convert::Infallible, fmt, str::FromStr};
use anyhow::Result;
use clap::{Parser, ValueEnum};
use comfy_table::{Cell, Row, Table};
use comfy_table::{Cell, Color, Row, Table};
use io_jmap::rfc8621::mailbox::{
Mailbox, MailboxFilter, MailboxRole, MailboxSortComparator, MailboxSortProperty,
};
@@ -110,6 +110,12 @@ impl JmapMailboxQueryCommand {
let table = MailboxesTable {
preset: client.account.table_preset().to_string(),
colors: MailboxColors {
id: client.account.mailboxes_list_table_id_color(),
name: client.account.mailboxes_list_table_name_color(),
total: client.account.mailboxes_list_table_total_color(),
unread: client.account.mailboxes_list_table_unread_color(),
},
mailboxes: output.mailboxes,
};
@@ -117,10 +123,31 @@ impl JmapMailboxQueryCommand {
}
}
#[derive(Clone, Copy, Debug)]
pub struct MailboxColors {
pub id: Color,
pub name: Color,
pub total: Color,
pub unread: Color,
}
impl Default for MailboxColors {
fn default() -> Self {
Self {
id: Color::Reset,
name: Color::Reset,
total: Color::Reset,
unread: Color::Reset,
}
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct MailboxesTable {
#[serde(skip)]
pub preset: String,
#[serde(skip)]
pub colors: MailboxColors,
pub mailboxes: Vec<Mailbox>,
}
@@ -141,14 +168,16 @@ impl fmt::Display for MailboxesTable {
.add_rows(self.mailboxes.iter().map(|r| {
let mut row = Row::new();
row.max_height(1)
.add_cell(Cell::new(r.id.as_deref().unwrap_or("Unknown")))
.add_cell(Cell::new(r.name.as_deref().unwrap_or("Unknown")))
.add_cell(Cell::new(r.id.as_deref().unwrap_or("Unknown")).fg(self.colors.id))
.add_cell(
Cell::new(r.name.as_deref().unwrap_or("Unknown")).fg(self.colors.name),
)
.add_cell(match r.role.as_ref() {
Some(r) => Cell::new(r.to_string()),
None => Cell::new(""),
})
.add_cell(Cell::new(r.total_emails))
.add_cell(Cell::new(r.unread_emails))
.add_cell(Cell::new(r.total_emails).fg(self.colors.total))
.add_cell(Cell::new(r.unread_emails).fg(self.colors.unread))
.add_cell(Cell::new(if r.is_subscribed { "yes" } else { "" }));
row
}));
+21 -5
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_maildir::maildir::Maildir;
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -79,6 +79,12 @@ impl MaildirEnvelopeListCommand {
let table = EnvelopesTable {
preset: client.account.table_preset().to_string(),
arrangement: client.account.table_arrangement(),
colors: EnvelopeColors {
id: client.account.envelopes_list_table_id_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
date: client.account.envelopes_list_table_date_color(),
},
envelopes,
};
@@ -86,12 +92,22 @@ impl MaildirEnvelopeListCommand {
}
}
#[derive(Clone, Copy, Debug)]
struct EnvelopeColors {
id: Color,
subject: Color,
from: Color,
date: Color,
}
#[derive(Clone, Debug, Serialize)]
pub struct EnvelopesTable {
#[serde(skip)]
preset: String,
#[serde(skip)]
arrangement: ContentArrangement,
#[serde(skip)]
colors: EnvelopeColors,
envelopes: Vec<EnvelopesTableEntry>,
}
@@ -113,10 +129,10 @@ impl fmt::Display for EnvelopesTable {
let mut row = Row::new();
row.max_height(1)
.add_cell(Cell::new(&entry.id))
.add_cell(Cell::new(&entry.subject))
.add_cell(Cell::new(&entry.from))
.add_cell(Cell::new(&entry.date));
.add_cell(Cell::new(&entry.id).fg(self.colors.id))
.add_cell(Cell::new(&entry.subject).fg(self.colors.subject))
.add_cell(Cell::new(&entry.from).fg(self.colors.from))
.add_cell(Cell::new(&entry.date).fg(self.colors.date));
table.add_row(row);
}
+6 -3
View File
@@ -19,7 +19,7 @@ use std::{fmt, path::PathBuf};
use anyhow::Result;
use clap::Parser;
use comfy_table::{Cell, Row, Table};
use comfy_table::{Cell, Color, Row, Table};
use io_maildir::maildir::Maildir;
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -40,6 +40,7 @@ impl MaildirMailboxListCommand {
let table = MaildirsTable {
preset: client.account.table_preset().to_string(),
name_color: client.account.mailboxes_list_table_name_color(),
rows: maildirs.into_iter().map(From::from).collect(),
};
@@ -47,10 +48,12 @@ impl MaildirMailboxListCommand {
}
}
#[derive(Clone, Debug, Default, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct MaildirsTable {
#[serde(skip)]
pub preset: String,
#[serde(skip)]
pub name_color: Color,
#[serde(rename = "maildirs")]
pub rows: Vec<MaildirRow>,
}
@@ -66,7 +69,7 @@ impl fmt::Display for MaildirsTable {
let mut row = Row::new();
row.max_height(1)
.add_cell(Cell::new(&m.name))
.add_cell(Cell::new(&m.name).fg(self.name_color))
.add_cell(Cell::new(format!("{}", m.path.display())));
row
+9 -1
View File
@@ -27,7 +27,7 @@ use mail_parser::{MessageParser, MimeHeaders};
use pimalaya_cli::printer::Printer;
use crate::shared::{
attachments::list::{mime_string, Attachment, Attachments},
attachments::list::{mime_string, Attachment, AttachmentColors, Attachments},
client::EmailClient,
mailboxes::arg::MailboxArg,
};
@@ -130,6 +130,14 @@ impl AttachmentDownloadCommand {
arrangement: client.account.table_arrangement(),
with_inline: written.iter().any(|a| a.inline),
with_path: true,
colors: AttachmentColors {
id: client.account.attachments_list_table_id_color(),
filename: client.account.attachments_list_table_filename_color(),
r#type: client.account.attachments_list_table_type_color(),
size: client.account.attachments_list_table_size_color(),
inline: client.account.attachments_list_table_inline_color(),
path: client.account.attachments_list_table_path_color(),
},
attachments: written,
};
+31 -7
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::{bail, Result};
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use humansize::{format_size, BINARY};
use mail_parser::{MessageParser, MessagePart, MimeHeaders};
use pimalaya_cli::printer::Printer;
@@ -87,6 +87,14 @@ impl AttachmentListCommand {
arrangement: client.account.table_arrangement(),
with_inline: self.inline,
with_path: false,
colors: AttachmentColors {
id: client.account.attachments_list_table_id_color(),
filename: client.account.attachments_list_table_filename_color(),
r#type: client.account.attachments_list_table_type_color(),
size: client.account.attachments_list_table_size_color(),
inline: client.account.attachments_list_table_inline_color(),
path: client.account.attachments_list_table_path_color(),
},
attachments,
};
@@ -94,6 +102,16 @@ impl AttachmentListCommand {
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct AttachmentColors {
pub id: Color,
pub filename: Color,
pub r#type: Color,
pub size: Color,
pub inline: Color,
pub path: Color,
}
/// One row of the `attachments list` / `attachments download` output.
#[derive(Clone, Debug, Serialize)]
pub struct Attachment {
@@ -127,6 +145,8 @@ pub struct Attachments {
pub with_inline: bool,
#[serde(skip)]
pub with_path: bool,
#[serde(skip)]
pub(crate) colors: AttachmentColors,
pub attachments: Vec<Attachment>,
}
@@ -154,15 +174,19 @@ impl fmt::Display for Attachments {
.add_rows(self.attachments.iter().map(|a| {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(&a.id));
row.add_cell(Cell::new(a.filename.as_deref().unwrap_or("")));
row.add_cell(Cell::new(a.mime.as_deref().unwrap_or("")));
row.add_cell(Cell::new(format_size(a.size, BINARY)));
row.add_cell(Cell::new(&a.id).fg(self.colors.id));
row.add_cell(
Cell::new(a.filename.as_deref().unwrap_or("")).fg(self.colors.filename),
);
row.add_cell(Cell::new(a.mime.as_deref().unwrap_or("")).fg(self.colors.r#type));
row.add_cell(Cell::new(format_size(a.size, BINARY)).fg(self.colors.size));
if self.with_inline {
row.add_cell(Cell::new(if a.inline { "yes" } else { "no" }));
row.add_cell(
Cell::new(if a.inline { "yes" } else { "no" }).fg(self.colors.inline),
);
}
if self.with_path {
row.add_cell(Cell::new(a.path.as_deref().unwrap_or("")));
row.add_cell(Cell::new(a.path.as_deref().unwrap_or("")).fg(self.colors.path));
}
row
}));
+79 -29
View File
@@ -20,7 +20,7 @@ use std::{collections::BTreeSet, fmt};
use anyhow::Result;
use chrono::{DateTime, FixedOffset, Local};
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use humansize::{format_size, BINARY};
use io_email::{address::Address, envelope::Envelope, flag::Flag};
use pimalaya_cli::printer::Printer;
@@ -93,6 +93,22 @@ impl EnvelopeListCommand {
datetime_local_tz: client.account.datetime_local_tz(),
recipient: self.recipient,
with_attachment: self.has_attachment,
chars: FlagChars {
unseen: client.account.envelopes_list_table_unseen_char(),
replied: client.account.envelopes_list_table_replied_char(),
flagged: client.account.envelopes_list_table_flagged_char(),
attachment: client.account.envelopes_list_table_attachment_char(),
},
colors: EnvelopeColors {
id: client.account.envelopes_list_table_id_color(),
flags: client.account.envelopes_list_table_flags_color(),
att: client.account.envelopes_list_table_att_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
to: client.account.envelopes_list_table_to_color(),
date: client.account.envelopes_list_table_date_color(),
size: client.account.envelopes_list_table_size_color(),
},
envelopes,
};
@@ -100,6 +116,30 @@ impl EnvelopeListCommand {
}
}
/// Glyphs the FLAGS / ATT columns substitute in, sourced from the
/// merged account config (v1.2.0 defaults: `*`, `R`, `!`, `@`).
#[derive(Clone, Copy, Debug)]
pub(super) struct FlagChars {
pub unseen: char,
pub replied: char,
pub flagged: char,
pub attachment: char,
}
/// Per-column foreground colors for the envelopes table. `Color::Reset`
/// means "use the terminal default" (i.e. no override).
#[derive(Clone, Copy, Debug)]
pub(super) struct EnvelopeColors {
pub id: Color,
pub flags: Color,
pub att: Color,
pub subject: Color,
pub from: Color,
pub to: Color,
pub date: Color,
pub size: Color,
}
#[derive(Clone, Debug, Serialize)]
pub struct Envelopes {
#[serde(skip)]
@@ -116,6 +156,10 @@ pub struct Envelopes {
pub recipient: bool,
#[serde(skip)]
pub with_attachment: bool,
#[serde(skip)]
pub(super) chars: FlagChars,
#[serde(skip)]
pub(super) colors: EnvelopeColors,
pub envelopes: Vec<Envelope>,
}
@@ -139,22 +183,33 @@ impl fmt::Display for Envelopes {
.add_rows(self.envelopes.iter().map(|env| {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(&env.id));
row.add_cell(Cell::new(format_flags(&env.flags)));
row.add_cell(Cell::new(&env.id).fg(self.colors.id));
row.add_cell(Cell::new(format_flags(&env.flags, &self.chars)).fg(self.colors.flags));
if self.with_attachment {
row.add_cell(Cell::new(format_attachment(env.has_attachment)));
row.add_cell(
Cell::new(format_attachment(env.has_attachment, self.chars.attachment))
.fg(self.colors.att),
);
}
row.add_cell(Cell::new(&env.subject));
row.add_cell(Cell::new(&env.subject).fg(self.colors.subject));
let addresses = if self.recipient { &env.to } else { &env.from };
row.add_cell(Cell::new(format_addresses(addresses)));
let from_or_to_color = if self.recipient {
self.colors.to
} else {
self.colors.from
};
row.add_cell(Cell::new(format_addresses(addresses)).fg(from_or_to_color));
row.add_cell(Cell::new(format_date(
env.date,
&self.datetime_fmt,
self.datetime_local_tz,
)));
row.add_cell(Cell::new(format_size(env.size, BINARY)));
row.add_cell(
Cell::new(format_date(
env.date,
&self.datetime_fmt,
self.datetime_local_tz,
))
.fg(self.colors.date),
);
row.add_cell(Cell::new(format_size(env.size, BINARY)).fg(self.colors.size));
row
}));
@@ -167,39 +222,34 @@ impl fmt::Display for Envelopes {
}
}
/// 4-character flag widget: one slot per LCD variant. Unread (no
/// `Seen`) shows `N` in the first slot since unread is the
/// attention-grabbing case.
pub(super) fn format_flags(flags: &BTreeSet<Flag>) -> String {
let mut out = String::with_capacity(4);
/// 3-character flag widget: unseen, replied, flagged. Each slot is a
/// space when the flag is absent, otherwise the configured glyph
/// (v1.2.0 defaults: `*`, `R`, `!`).
pub(super) fn format_flags(flags: &BTreeSet<Flag>, chars: &FlagChars) -> String {
let mut out = String::with_capacity(3);
out.push(if flags.contains(&Flag::Seen) {
' '
} else {
'N'
chars.unseen
});
out.push(if flags.contains(&Flag::Answered) {
'r'
chars.replied
} else {
' '
});
out.push(if flags.contains(&Flag::Flagged) {
'*'
} else {
' '
});
out.push(if flags.contains(&Flag::Draft) {
'D'
chars.flagged
} else {
' '
});
out
}
pub(super) fn format_attachment(has: Option<bool>) -> &'static str {
pub(super) fn format_attachment(has: Option<bool>, glyph: char) -> String {
match has {
Some(true) => "@",
Some(false) => "",
None => "?",
Some(true) => glyph.to_string(),
Some(false) => String::new(),
None => "?".to_string(),
}
}
+21 -1
View File
@@ -23,7 +23,11 @@ use clap::Parser;
use io_email::search::{error::Error as SearchQueryError, query::SearchEmailsQuery};
use pimalaya_cli::printer::Printer;
use crate::shared::{client::EmailClient, envelopes::list::Envelopes, mailboxes::arg::MailboxArg};
use crate::shared::{
client::EmailClient,
envelopes::list::{EnvelopeColors, Envelopes, FlagChars},
mailboxes::arg::MailboxArg,
};
/// Search envelopes for the active account using the shared search
/// query DSL, regardless of the underlying backend (IMAP, JMAP or
@@ -102,6 +106,22 @@ impl EnvelopeSearchCommand {
datetime_local_tz: client.account.datetime_local_tz(),
recipient: self.recipient,
with_attachment: self.has_attachment,
chars: FlagChars {
unseen: client.account.envelopes_list_table_unseen_char(),
replied: client.account.envelopes_list_table_replied_char(),
flagged: client.account.envelopes_list_table_flagged_char(),
attachment: client.account.envelopes_list_table_attachment_char(),
},
colors: EnvelopeColors {
id: client.account.envelopes_list_table_id_color(),
flags: client.account.envelopes_list_table_flags_color(),
att: client.account.envelopes_list_table_att_color(),
subject: client.account.envelopes_list_table_subject_color(),
from: client.account.envelopes_list_table_from_color(),
to: client.account.envelopes_list_table_to_color(),
date: client.account.envelopes_list_table_date_color(),
size: client.account.envelopes_list_table_size_color(),
},
envelopes,
};
+21 -5
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use comfy_table::{Cell, ContentArrangement, Row, Table};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_email::mailbox::Mailbox;
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -56,6 +56,12 @@ impl MailboxListCommand {
arrangement: client.account.table_arrangement(),
max_width: self.max_width,
with_counts: self.counts,
colors: MailboxColors {
id: client.account.mailboxes_list_table_id_color(),
name: client.account.mailboxes_list_table_name_color(),
total: client.account.mailboxes_list_table_total_color(),
unread: client.account.mailboxes_list_table_unread_color(),
},
mailboxes,
};
@@ -63,6 +69,14 @@ impl MailboxListCommand {
}
}
#[derive(Clone, Copy, Debug)]
struct MailboxColors {
id: Color,
name: Color,
total: Color,
unread: Color,
}
#[derive(Clone, Debug, Serialize)]
pub struct Mailboxes {
#[serde(skip)]
@@ -73,6 +87,8 @@ pub struct Mailboxes {
pub max_width: Option<u16>,
#[serde(skip)]
pub with_counts: bool,
#[serde(skip)]
colors: MailboxColors,
pub mailboxes: Vec<Mailbox>,
}
@@ -93,11 +109,11 @@ impl fmt::Display for Mailboxes {
.add_rows(self.mailboxes.iter().map(|m| {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(&m.id));
row.add_cell(Cell::new(&m.name));
row.add_cell(Cell::new(&m.id).fg(self.colors.id));
row.add_cell(Cell::new(&m.name).fg(self.colors.name));
if self.with_counts {
row.add_cell(count_cell(m.total));
row.add_cell(count_cell(m.unread));
row.add_cell(count_cell(m.total).fg(self.colors.total));
row.add_cell(count_cell(m.unread).fg(self.colors.unread));
}
row
}));
+7 -6
View File
@@ -113,11 +113,12 @@ pub fn run_or_exit(target: &Path) -> Result<Config> {
let config = Config {
downloads_dir: None,
table_preset: None,
table_arrangement: None,
table: Default::default(),
envelope: Default::default(),
mailbox: Default::default(),
message: Default::default(),
attachment: Default::default(),
account: Default::default(),
accounts: HashMap::from([(account_name, account)]),
};
@@ -185,10 +186,10 @@ fn build_account_from_discovery(
Ok(AccountConfig {
default: true,
downloads_dir: None,
table_preset: None,
table_arrangement: None,
table: Default::default(),
envelope: Default::default(),
mailbox: Default::default(),
attachment: Default::default(),
imap: None,
jmap: Some(jmap_to_config(jmap)?),
maildir: None,
@@ -201,10 +202,10 @@ fn build_account_from_discovery(
Ok(AccountConfig {
default: true,
downloads_dir: None,
table_preset: None,
table_arrangement: None,
table: Default::default(),
envelope: Default::default(),
mailbox: Default::default(),
attachment: Default::default(),
imap: Some(imap_to_config(imap)?),
jmap: None,
maildir: None,
+12 -6
View File
@@ -94,8 +94,10 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re
.map(|a| a.default)
.unwrap_or(is_first_account);
let downloads_dir = existing.as_ref().and_then(|a| a.downloads_dir.clone());
let table_preset = existing.as_ref().and_then(|a| a.table_preset.clone());
let table_arrangement = existing.as_ref().and_then(|a| a.table_arrangement.clone());
let table = existing
.as_ref()
.map(|a| a.table.clone())
.unwrap_or_default();
let envelope = existing
.as_ref()
.map(|a| a.envelope.clone())
@@ -104,6 +106,10 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re
.as_ref()
.map(|a| a.mailbox.clone())
.unwrap_or_default();
let attachment = existing
.as_ref()
.map(|a| a.attachment.clone())
.unwrap_or_default();
let maildir = existing.as_ref().and_then(|a| a.maildir.clone());
let account = if jmap_defaults.is_some() {
@@ -111,10 +117,10 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re
AccountConfig {
default,
downloads_dir,
table_preset,
table_arrangement,
table,
envelope,
mailbox,
attachment,
imap: None,
jmap: Some(jmap_to_config(jmap)?),
maildir,
@@ -126,10 +132,10 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re
AccountConfig {
default,
downloads_dir,
table_preset,
table_arrangement,
table,
envelope,
mailbox,
attachment,
imap: Some(imap_to_config(imap)?),
jmap: None,
maildir,