From c0806786d9c1beb330456e7adc1885591e942d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 9 Oct 2024 14:55:21 +0200 Subject: [PATCH] use himalaya shared config from pimalaya-tui --- Cargo.lock | 36 +++--- Cargo.toml | 15 +-- src/account/config.rs | 123 +++++++++++++-------- src/backend.rs | 18 +-- src/config.rs | 247 +++++++++++------------------------------- src/editor.rs | 2 +- src/main.rs | 184 +++++++++++++++++++------------ 7 files changed, 289 insertions(+), 336 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f821f01..02bdc5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,9 +510,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.27" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677207f6eaec43fcfd092a718c847fc38aa261d0e19b8ef6797e0ccbe789e738" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] @@ -581,9 +581,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -1143,7 +1143,7 @@ dependencies = [ [[package]] name = "email-lib" version = "0.25.0" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#124ff23ebd38c259e3fa377c043dbe4f010488bd" dependencies = [ "async-trait", "chrono", @@ -2779,7 +2779,7 @@ dependencies = [ [[package]] name = "oauth-lib" version = "0.1.1" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#124ff23ebd38c259e3fa377c043dbe4f010488bd" dependencies = [ "log", "oauth2", @@ -3054,8 +3054,7 @@ dependencies = [ [[package]] name = "pgp-lib" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6802b1ef0dfc50217185a1eda6ddd546b65ffa8b80f942aa1feda6536adf165" +source = "git+https://github.com/pimalaya/core#124ff23ebd38c259e3fa377c043dbe4f010488bd" dependencies = [ "async-recursion", "futures", @@ -3075,16 +3074,19 @@ dependencies = [ [[package]] name = "pimalaya-tui" version = "0.1.0" -source = "git+https://github.com/pimalaya/tui#c565ecdee52593451f02261f5e206b86372bef16" +source = "git+https://github.com/pimalaya/tui#ff25e31f455c338e3f438164d3b3df3e087c312e" dependencies = [ + "async-trait", "clap", "color-eyre", - "crossterm 0.25.0", + "comfy-table", + "crossterm 0.27.0", "dirs 4.0.0", "email-lib", "email_address", "inquire", "oauth-lib", + "process-lib", "secret-lib", "serde", "serde-toml-merge", @@ -3233,9 +3235,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -3243,7 +3245,7 @@ dependencies = [ [[package]] name = "process-lib" version = "0.4.2" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#124ff23ebd38c259e3fa377c043dbe4f010488bd" dependencies = [ "log", "serde", @@ -3693,9 +3695,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] diff --git a/Cargo.toml b/Cargo.toml index 4965c6b..76d40c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,17 @@ rustdoc-args = ["--cfg", "docsrs"] default = [ "imap", "maildir", - # "notmuch", + #"notmuch", "smtp", "sendmail", - # "keyring", - # "oauth2", + #"keyring", + #"oauth2", "wizard", - # "pgp-commands", - # "pgp-gpg", - # "pgp-native", + #"pgp-commands", + #"pgp-gpg", + #"pgp-native", ] imap = ["email-lib/imap", "pimalaya-tui/imap"] @@ -63,7 +63,7 @@ mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"] oauth-lib = { version = "=0.1.1", optional = true } once_cell = "1.16" petgraph = "0.6" -pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "config", "tracing"] } +pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "config", "tracing", "himalaya"] } process-lib = { version = "=0.4.2", features = ["derive"] } reedline = "0.35.0" secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true } @@ -82,5 +82,6 @@ imap-next = { git = "https://github.com/duesee/imap-next" } imap-client = { git = "https://github.com/pimalaya/imap-client" } oauth-lib = { git = "https://github.com/pimalaya/core" } process-lib = { git = "https://github.com/pimalaya/core" } +pgp-lib = { git = "https://github.com/pimalaya/core" } email-lib = { git = "https://github.com/pimalaya/core" } pimalaya-tui = { git = "https://github.com/pimalaya/tui" } diff --git a/src/account/config.rs b/src/account/config.rs index d79eff2..998614a 100644 --- a/src/account/config.rs +++ b/src/account/config.rs @@ -18,16 +18,12 @@ use email::notmuch::config::NotmuchConfig; use email::sendmail::config::SendmailConfig; #[cfg(feature = "smtp")] use email::smtp::config::SmtpConfig; -use process::Command; +use email::{account::config::AccountConfig, template::config::TemplateConfig}; +use pimalaya_tui::config::toml::himalaya::{ + BackendKind, EnvelopeConfig, FolderConfig, MessageConfig, +}; use serde::{Deserialize, Serialize}; -use crate::backend::BackendKind; - -// use crate::{ -// backend::BackendKind, envelope::config::EnvelopeConfig, flag::config::FlagConfig, -// folder::config::FolderConfig, message::config::MessageConfig, -// }; - /// Represents all existing kind of account config. #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] @@ -43,11 +39,11 @@ pub struct TomlAccountConfig { #[cfg(feature = "pgp")] pub pgp: Option, - // pub folder: Option, - // pub envelope: Option, - // pub flag: Option, + pub folder: Option, + pub envelope: Option, pub message: Option, - // pub template: Option, + pub template: Option, + #[cfg(feature = "imap")] pub imap: Option, #[cfg(feature = "maildir")] @@ -60,41 +56,82 @@ pub struct TomlAccountConfig { pub sendmail: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageConfig { - pub send: Option, -} +impl From for AccountConfig { + fn from(config: TomlAccountConfig) -> Self { + Self { + name: String::new(), + email: config.email, + display_name: config.display_name, + signature: config.signature, + signature_delim: config.signature_delim, + downloads_dir: config.downloads_dir, -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageSendConfig { - pub backend: Option, - pub save_copy: Option, - pub pre_hook: Option, -} + #[cfg(feature = "pgp")] + pgp: config.pgp, -#[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, + folder: config.folder.map(Into::into), + envelope: config.envelope.map(Into::into), + flag: None, + message: config.message.map(Into::into), + template: config.template, + } + } } -// impl ListAccountsTableConfig { -// pub fn preset(&self) -> &str { -// self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) -// } +impl TomlAccountConfig { + pub fn envelope_list_table_preset(&self) -> Option { + self.envelope.as_ref().and_then(|c| c.list_table_preset()) + } + + pub fn envelope_list_table_unseen_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_unseen_char()) + } + + pub fn envelope_list_table_replied_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_replied_char()) + } + + pub fn envelope_list_table_flagged_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_flagged_char()) + } -// pub fn name_color(&self) -> comfy_table::Color { -// map_color(self.name_color.unwrap_or(Color::Green)) -// } + pub fn envelope_list_table_attachment_char(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_attachment_char()) + } -// pub fn backends_color(&self) -> comfy_table::Color { -// map_color(self.backends_color.unwrap_or(Color::Blue)) -// } + pub fn envelope_list_table_id_color(&self) -> Option { + self.envelope.as_ref().and_then(|c| c.list_table_id_color()) + } -// pub fn default_color(&self) -> comfy_table::Color { -// map_color(self.default_color.unwrap_or(Color::Reset)) -// } -// } + pub fn envelope_list_table_flags_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_flags_color()) + } + + pub fn envelope_list_table_subject_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_subject_color()) + } + + pub fn envelope_list_table_sender_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_sender_color()) + } + + pub fn envelope_list_table_date_color(&self) -> Option { + self.envelope + .as_ref() + .and_then(|c| c.list_table_date_color()) + } +} diff --git a/src/backend.rs b/src/backend.rs index c27e7da..fa9b390 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -22,7 +22,7 @@ use email::{ }, AnyResult, }; -use serde::{Deserialize, Serialize}; +use pimalaya_tui::config::toml::himalaya::BackendKind; #[derive(Clone, Debug, Eq, PartialEq)] pub enum BackendConfig { @@ -38,22 +38,6 @@ pub enum BackendConfig { Sendmail(SendmailConfig), } -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum BackendKind { - None, - #[cfg(feature = "imap")] - Imap, - #[cfg(feature = "maildir")] - Maildir, - #[cfg(feature = "notmuch")] - Notmuch, - #[cfg(feature = "smtp")] - Smtp, - #[cfg(feature = "sendmail")] - Sendmail, -} - #[derive(BackendContext)] pub struct Context { #[cfg(feature = "imap")] diff --git a/src/config.rs b/src/config.rs index fb322b7..a147b0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,24 +1,16 @@ use std::{collections::HashMap, path::PathBuf}; -use color_eyre::{ - eyre::{bail, eyre}, - Result, -}; -use crossterm::style::Color; -use email::{ - account::config::AccountConfig, - message::{config::MessageConfig, send::config::MessageSendConfig}, -}; -use pimalaya_tui::config::TomlConfig; +use color_eyre::{eyre::eyre, Result}; +use email::{account::config::AccountConfig, config::Config}; +use pimalaya_tui::config::toml::himalaya::AccountsConfig; use serde::{Deserialize, Serialize}; -use shellexpand_utils::{canonicalize, expand}; -use crate::account::config::{ListAccountsTableConfig, TomlAccountConfig}; +use crate::account::config::TomlAccountConfig; -/// Represents the user config file. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +/// The structure representation of the user TOML configuration file. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct Config { +pub struct TomlConfig { #[serde(alias = "name")] pub display_name: Option, pub signature: Option, @@ -26,191 +18,80 @@ pub struct Config { pub downloads_dir: Option, pub accounts: HashMap, pub account: Option, + + pub repl: Option, } -impl TomlConfig for Config { - fn project_name() -> &'static str { - "himalaya" +impl TomlConfig { + pub fn repl_keybinds(&self) -> Option<&KeybindsStyle> { + self.repl.as_ref().and_then(|c| c.keybinds()) } } -impl Config { - /// Read and parse the TOML configuration from default paths. - pub async fn from_default_paths() -> Result { - match Self::first_valid_default_path() { - Some(path) => Self::from_paths(&[path]), - None => bail!("cannot find config file from default paths"), - } - } +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub enum KeybindsStyle { + #[default] + Emacs, + #[serde(alias = "vim")] + Vi, +} - /// Read and parse the TOML configuration at the optional given - /// path. - /// - /// If the given path exists, then read and parse the TOML - /// configuration from it. - /// - /// If the given path does not exist, then create it using the - /// wizard. - /// - /// If no path is given, then either read and parse the TOML - /// configuration at the first valid default path, otherwise - /// create it using the wizard. wizard. - pub async fn from_paths_or_default(paths: &[PathBuf]) -> Result { - match paths.len() { - 0 => Self::from_default_paths().await, - _ if paths[0].exists() => Self::from_paths(paths), - _ => bail!("cannot find config file from default paths"), - } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct ReplConfig { + pub keybinds: Option, +} + +impl ReplConfig { + pub fn keybinds(&self) -> Option<&KeybindsStyle> { + self.keybinds.as_ref() } +} - pub fn into_toml_account_config( - &self, - account_name: Option<&str>, - ) -> Result<(String, TomlAccountConfig)> { - #[allow(unused_mut)] - let (account_name, mut toml_account_config) = match account_name { - Some("default") | Some("") | None => self +impl From for Config { + fn from(config: TomlConfig) -> Self { + Self { + display_name: config.display_name, + signature: config.signature, + signature_delim: config.signature_delim, + downloads_dir: config.downloads_dir, + accounts: config .accounts - .iter() - .find_map(|(name, account)| { - account - .default - .filter(|default| *default) - .map(|_| (name.to_owned(), account.clone())) + .into_iter() + .map(|(name, config)| { + let mut config = AccountConfig::from(config); + config.name = name.clone(); + (name, config) }) - .ok_or_else(|| eyre!("cannot find default account")), - Some(name) => self - .accounts - .get(name) - .map(|account| (name.to_owned(), account.clone())) - .ok_or_else(|| eyre!("cannot find account {name}")), - }?; - - #[cfg(all(feature = "imap", feature = "keyring"))] - if let Some(imap_config) = toml_account_config.imap.as_mut() { - imap_config - .auth - .replace_undefined_keyring_entries(&account_name)?; - } - - #[cfg(all(feature = "smtp", feature = "keyring"))] - if let Some(smtp_config) = toml_account_config.smtp.as_mut() { - smtp_config - .auth - .replace_undefined_keyring_entries(&account_name)?; + .collect(), } - - Ok((account_name, toml_account_config)) - } - - /// Build account configurations from a given account name. - pub fn into_account_configs( - self, - account_name: Option<&str>, - ) -> Result<(TomlAccountConfig, AccountConfig)> { - let (account_name, toml_account_config) = self.into_toml_account_config(account_name)?; - - let config = email::config::Config { - display_name: self.display_name, - signature: self.signature, - signature_delim: self.signature_delim, - downloads_dir: self.downloads_dir, - - accounts: HashMap::from_iter(self.accounts.clone().into_iter().map( - |(name, config)| { - ( - name.clone(), - AccountConfig { - name, - email: config.email, - display_name: config.display_name, - signature: config.signature, - signature_delim: config.signature_delim, - downloads_dir: config.downloads_dir, - // folder: config.folder.map(|c| FolderConfig { - // aliases: c.alias, - // list: c.list.map(|c| c.remote), - // }), - folder: None, - // envelope: config.envelope.map(|c| EnvelopeConfig { - // list: c.list.map(|c| c.remote), - // thread: c.thread.map(|c| c.remote), - // }), - envelope: None, - // flag: None, - flag: None, - message: config.message.map(|c| MessageConfig { - send: c.send.map(|c| MessageSendConfig { - pre_hook: c.pre_hook, - save_copy: c.save_copy, - }), - ..Default::default() - }), - // template: config.template, - template: None, - #[cfg(feature = "pgp")] - pgp: config.pgp, - }, - ) - }, - )), - }; - - let account_config = config.account(account_name)?; - - Ok((toml_account_config, account_config)) } +} - 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()) - } +impl pimalaya_tui::config::toml::TomlConfig for TomlConfig { + type AccountConfig = TomlAccountConfig; - 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) + fn project_name() -> &'static str { + "himalaya" } - 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) + fn get_default_account_config(&self) -> Result<(String, Self::AccountConfig)> { + self.accounts + .iter() + .find_map(|(name, account)| { + account + .default + .filter(|default| *default) + .map(|_| (name.to_owned(), account.clone())) + }) + .ok_or_else(|| eyre!("cannot find default account")) } - 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) + fn get_account_config(&self, name: &str) -> Result<(String, Self::AccountConfig)> { + self.accounts + .get(name) + .map(|account| (name.to_owned(), account.clone())) + .ok_or_else(|| eyre!("cannot find account {name}")) } } - -/// Parse a configuration file path as [`PathBuf`]. -/// -/// The path is shell-expanded then canonicalized (if applicable). -pub fn path_parser(path: &str) -> Result { - expand::try_path(path) - .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/editor.rs b/src/editor.rs index 7b1a644..961375f 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -96,7 +96,7 @@ pub fn post_edit() -> Result { Ok(user_choice.clone()) } pub async fn edit_tpl_with_editor( - config: Arc, + #[cfg_attr(not(feature = "pgp"), allow(unused_variables))] config: Arc, backend: &Backend, mut tpl: Template, ) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index b136adc..bfc678e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,15 +36,20 @@ use email::{ copy::CopyMessages, delete::DeleteMessages, get::GetMessages, r#move::MoveMessages, Message, }, }; -use pimalaya_tui::{cli::tracing, prompt}; +use pimalaya_tui::{ + cli::tracing, + config::toml::{himalaya::BackendKind, TomlConfig as _}, + prompt, +}; use reedline::{ - default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultPrompt, DefaultPromptSegment, - Emacs, KeyCode, KeyModifiers, MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu, Signal, + default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, + ColumnarMenu, DefaultCompleter, DefaultPrompt, DefaultPromptSegment, Emacs, KeyCode, + KeyModifiers, MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu, Signal, Vi, }; use crate::{ - backend::{BackendKind, ContextBuilder}, - config::Config, + backend::ContextBuilder, + config::{KeybindsStyle, TomlConfig}, envelope::{Envelopes, EnvelopesTable}, id_mapper::IdMapper, }; @@ -56,50 +61,57 @@ async fn main() -> Result<()> { let cli = Cli::parse(); println!("Welcome to Himalaya REPL!"); - println!("Connecting…"); - - let config = Config::from_paths_or_default(&cli.config_paths).await?; - let (toml_account_config, account_config) = config.into_account_configs(None)?; - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - account_config.clone(), - ContextBuilder { - backend: toml_account_config.backend.unwrap_or(BackendKind::None), - sending_backend: toml_account_config - .message - .and_then(|c| c.send) - .and_then(|c| c.backend) - .unwrap_or(BackendKind::None), - - #[cfg(feature = "imap")] - imap: toml_account_config - .imap - .map(|imap| ImapContextBuilder::new(account_config.clone(), Arc::new(imap))), - #[cfg(feature = "maildir")] - maildir: toml_account_config.maildir.map(|maildir| { - MaildirContextBuilder::new(account_config.clone(), Arc::new(maildir)) - }), - #[cfg(feature = "notmuch")] - notmuch: toml_account_config.notmuch.map(|notmuch| { - NotmuchContextBuilder::new(account_config.clone(), Arc::new(notmuch)) - }), - #[cfg(feature = "smtp")] - smtp: toml_account_config - .smtp - .map(|smtp| SmtpContextBuilder::new(account_config.clone(), Arc::new(smtp))), - #[cfg(feature = "sendmail")] - sendmail: toml_account_config.sendmail.map(|sendmail| { - SendmailContextBuilder::new(account_config.clone(), Arc::new(sendmail)) - }), - }, - ) - .build() - .await?; + println!("Setting up backends…"); + + let toml_cfg = TomlConfig::from_paths_or_default(&cli.config_paths).await?; + let keybinds = toml_cfg.repl_keybinds().cloned().unwrap_or_default(); + let (toml_account_cfg, account_cfg) = toml_cfg.into_account_configs(None)?; + + let account_cfg = Arc::new(account_cfg); + + let backend = + BackendBuilder::new( + account_cfg.clone(), + ContextBuilder { + backend: toml_account_cfg + .backend + .clone() + .unwrap_or(BackendKind::None), + sending_backend: toml_account_cfg + .message + .as_ref() + .and_then(|c| c.send.as_ref()) + .and_then(|c| c.backend.clone()) + .unwrap_or(BackendKind::None), + + #[cfg(feature = "imap")] + imap: toml_account_cfg.imap.as_ref().map(|imap| { + ImapContextBuilder::new(account_cfg.clone(), Arc::new(imap.clone())) + }), + #[cfg(feature = "maildir")] + maildir: toml_account_cfg.maildir.as_ref().map(|maildir| { + MaildirContextBuilder::new(account_cfg.clone(), Arc::new(maildir.clone())) + }), + #[cfg(feature = "notmuch")] + notmuch: toml_account_cfg.notmuch.as_ref().map(|notmuch| { + NotmuchContextBuilder::new(account_cfg.clone(), Arc::new(notmuch.clone())) + }), + #[cfg(feature = "smtp")] + smtp: toml_account_cfg.smtp.as_ref().map(|smtp| { + SmtpContextBuilder::new(account_cfg.clone(), Arc::new(smtp.clone())) + }), + #[cfg(feature = "sendmail")] + sendmail: toml_account_cfg.sendmail.as_ref().map(|sendmail| { + SendmailContextBuilder::new(account_cfg.clone(), Arc::new(sendmail.clone())) + }), + }, + ) + .build() + .await?; println!(); - let mut mode = UnselectedMode::new(); + let mut mode = UnselectedMode::new(keybinds); let mut folder = Option::::None; @@ -142,8 +154,23 @@ async fn main() -> Result<()> { ) .await?; let envelopes = - Envelopes::try_from_lib(account_config.clone(), &id_mapper, envelopes)?; - let table = EnvelopesTable::from(envelopes); + Envelopes::try_from_lib(account_cfg.clone(), &id_mapper, envelopes)?; + let table = EnvelopesTable::from(envelopes) + .with_some_preset(toml_account_cfg.envelope_list_table_preset()) + .with_some_unseen_char(toml_account_cfg.envelope_list_table_unseen_char()) + .with_some_replied_char(toml_account_cfg.envelope_list_table_replied_char()) + .with_some_flagged_char(toml_account_cfg.envelope_list_table_flagged_char()) + .with_some_attachment_char( + toml_account_cfg.envelope_list_table_attachment_char(), + ) + .with_some_id_color(toml_account_cfg.envelope_list_table_id_color()) + .with_some_flags_color(toml_account_cfg.envelope_list_table_flags_color()) + .with_some_subject_color( + toml_account_cfg.envelope_list_table_subject_color(), + ) + .with_some_sender_color(toml_account_cfg.envelope_list_table_sender_color()) + .with_some_date_color(toml_account_cfg.envelope_list_table_date_color()); + println!("{table}"); } "read" => { @@ -162,7 +189,7 @@ async fn main() -> Result<()> { for email in emails.to_vec() { bodies.push_str(glue); - let tpl = email.to_read_tpl(&account_config, |tpl| tpl).await?; + let tpl = email.to_read_tpl(&account_cfg, |tpl| tpl).await?; bodies.push_str(&tpl); glue = "\n\n"; @@ -171,11 +198,11 @@ async fn main() -> Result<()> { println!("{bodies}"); } "write" => { - let tpl = Message::new_tpl_builder(account_config.clone()) + let tpl = Message::new_tpl_builder(account_cfg.clone()) .build() .await?; - editor::edit_tpl_with_editor(account_config.clone(), &backend, tpl).await?; + editor::edit_tpl_with_editor(account_cfg.clone(), &backend, tpl).await?; } "reply" => { let Some(folder) = folder.as_deref() else { @@ -191,12 +218,12 @@ async fn main() -> Result<()> { .await? .first() .ok_or(eyre!("cannot find message {id}"))? - .to_reply_tpl_builder(account_config.clone()) + .to_reply_tpl_builder(account_cfg.clone()) .with_reply_all(reply_all) .build() .await?; - editor::edit_tpl_with_editor(account_config.clone(), &backend, tpl).await?; + editor::edit_tpl_with_editor(account_cfg.clone(), &backend, tpl).await?; } "forward" => { let Some(folder) = folder.as_deref() else { @@ -211,11 +238,11 @@ async fn main() -> Result<()> { .await? .first() .ok_or(eyre!("cannot find message"))? - .to_forward_tpl_builder(account_config.clone()) + .to_forward_tpl_builder(account_cfg.clone()) .build() .await?; - editor::edit_tpl_with_editor(account_config.clone(), &backend, tpl).await?; + editor::edit_tpl_with_editor(account_cfg.clone(), &backend, tpl).await?; } "copy" => { let Some(source) = folder.as_deref() else { @@ -286,25 +313,46 @@ async fn main() -> Result<()> { struct UnselectedMode(Reedline); impl UnselectedMode { - pub fn new() -> impl DerefMut { + pub fn new(keybinds: KeybindsStyle) -> impl DerefMut { let commands = vec!["create".into(), "list".into(), "select".into()]; let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); let completion_menu = Box::new(ColumnarMenu::default().with_name("completion")); - let mut keybinds = default_emacs_keybindings(); - keybinds.add_binding( - KeyModifiers::NONE, - KeyCode::Tab, - ReedlineEvent::UntilFound(vec![ - ReedlineEvent::Menu("completion".to_string()), - ReedlineEvent::MenuNext, - ]), - ); - let reedline = Reedline::create() .with_completer(completer) - .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) - .with_edit_mode(Box::new(Emacs::new(keybinds))); + .with_menu(ReedlineMenu::EngineCompleter(completion_menu)); + + let reedline = match keybinds { + KeybindsStyle::Emacs => { + let mut keybinds = default_emacs_keybindings(); + + keybinds.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("completion".to_string()), + ReedlineEvent::MenuNext, + ]), + ); + + reedline.with_edit_mode(Box::new(Emacs::new(keybinds))) + } + KeybindsStyle::Vi => { + let mut keybinds = default_vi_insert_keybindings(); + + keybinds.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("completion".to_string()), + ReedlineEvent::MenuNext, + ]), + ); + + reedline + .with_edit_mode(Box::new(Vi::new(keybinds, default_vi_normal_keybindings()))) + } + }; Self(reedline) }