Add option to highlight self in mailing list threads

Use under `listing` options such as:

globally
========

  [listing]
  highlight_self = true

per-account
===========

  [accounts.work]
  root_mailbox = '[Gmail]'
  format = "imap"
  subscribed_mailboxes = ["*"]
  listing.index_style = "compact"
  listing.highlight_self = true

per-mailbox
===========

  [accounts.work.mailboxes]
  "INBOX/Lists/project-devel" = { listing.highlight_self=true }

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/360/head
Manos Pitsidianakis 3 months ago
parent 3884c0da1f
commit e2cdebe89c
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -1589,6 +1589,15 @@ Flag to show if thread entry has been selected.
.It Ic attachment_flag Ar Option<String> .It Ic attachment_flag Ar Option<String>
Flag to show if thread entry contains attachments. Flag to show if thread entry contains attachments.
.LiteralStringValueRenders 📎\e\uu{FE0E} 📎︎ \" default value .LiteralStringValueRenders 📎\e\uu{FE0E} 📎︎ \" default value
.It Ic highlight_self_flag Ar Option<String>
Flag to show if any thread entry contains your address as a receiver.
Useful to make mailing list threads that CC you stand out.
.Pq Em "✸" \" default value
.It Ic highlight_self Ar boolean
Show
.Ic highlight_self_flag
or not.
.Pq Em false \" default value
.It Ic thread_subject_pack Ar boolean .It Ic thread_subject_pack Ar boolean
Should threads with differentiating Subjects show a list of those subjects on Should threads with differentiating Subjects show a list of those subjects on
the entry title? the entry title?

@ -62,7 +62,7 @@ use std::{
use indexmap::IndexMap; use indexmap::IndexMap;
use melib::{ use melib::{
conf::{AccountSettings, MailboxConf, ToggleFlag}, conf::{AccountSettings, ActionFlag, MailboxConf, ToggleFlag},
error::*, error::*,
}; };
use pager::PagerSettings; use pager::PagerSettings;
@ -679,16 +679,29 @@ mod default_vals {
None None
} }
pub(in crate::conf) fn internal_value_false<T: std::convert::From<super::ToggleFlag>>() -> T { pub(in crate::conf) fn internal_value_false<T: std::convert::From<melib::conf::ToggleFlag>>(
super::ToggleFlag::InternalVal(false).into() ) -> T {
melib::conf::ToggleFlag::InternalVal(false).into()
} }
pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T { pub(in crate::conf) fn internal_value_true<T: std::convert::From<melib::conf::ToggleFlag>>() -> T
super::ToggleFlag::InternalVal(true).into() {
melib::conf::ToggleFlag::InternalVal(true).into()
}
pub(in crate::conf) fn action_internal_value_false<T: std::convert::From<melib::ActionFlag>>(
) -> T {
melib::conf::ActionFlag::InternalVal(false).into()
} }
pub(in crate::conf) fn ask<T: std::convert::From<super::ToggleFlag>>() -> T { //pub(in crate::conf) fn action_internal_value_true<
super::ToggleFlag::Ask.into() // T: std::convert::From<melib::conf::ActionFlag>,
//>() -> T {
// melib::conf::ActionFlag::InternalVal(true).into()
//}
pub(in crate::conf) fn ask<T: std::convert::From<melib::conf::ActionFlag>>() -> T {
melib::conf::ActionFlag::Ask.into()
} }
} }
@ -1041,6 +1054,7 @@ mod dotaddressable {
impl DotAddressable for melib::LogLevel {} impl DotAddressable for melib::LogLevel {}
impl DotAddressable for PathBuf {} impl DotAddressable for PathBuf {}
impl DotAddressable for ToggleFlag {} impl DotAddressable for ToggleFlag {}
impl DotAddressable for ActionFlag {}
impl DotAddressable for SearchBackend {} impl DotAddressable for SearchBackend {}
impl DotAddressable for melib::SpecialUsageMailbox {} impl DotAddressable for melib::SpecialUsageMailbox {}
impl<T: DotAddressable> DotAddressable for Option<T> {} impl<T: DotAddressable> DotAddressable for Option<T> {}

@ -22,7 +22,7 @@
//! Configuration for composing email. //! Configuration for composing email.
use std::collections::HashMap; use std::collections::HashMap;
use melib::{email::HeaderName, ToggleFlag}; use melib::{conf::ActionFlag, email::HeaderName};
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer};
use super::{ use super::{
@ -92,7 +92,7 @@ pub struct ComposingSettings {
/// Forward emails as attachment? (Alternative is inline) /// Forward emails as attachment? (Alternative is inline)
/// Default: ask /// Default: ask
#[serde(default = "ask", alias = "forward-as-attachment")] #[serde(default = "ask", alias = "forward-as-attachment")]
pub forward_as_attachment: ToggleFlag, pub forward_as_attachment: ActionFlag,
/// Alternative lists of reply prefixes (etc. ["Re:", "RE:", ...]) to strip /// Alternative lists of reply prefixes (etc. ["Re:", "RE:", ...]) to strip
/// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:", /// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:",
/// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:", /// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:",
@ -126,7 +126,7 @@ impl Default for ComposingSettings {
wrap_header_preamble: None, wrap_header_preamble: None,
attribution_format_string: None, attribution_format_string: None,
attribution_use_posix_locale: true, attribution_use_posix_locale: true,
forward_as_attachment: ToggleFlag::Ask, forward_as_attachment: ActionFlag::Ask,
reply_prefix_list_to_strip: None, reply_prefix_list_to_strip: None,
reply_prefix: res(), reply_prefix: res(),
custom_compose_hooks: vec![], custom_compose_hooks: vec![],

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use melib::{search::Query, Error, Result}; use melib::{search::Query, Error, Result, ToggleFlag};
use super::{default_vals::*, DotAddressable, IndexStyle}; use super::{default_vals::*, DotAddressable, IndexStyle};
@ -130,6 +130,17 @@ pub struct ListingSettings {
#[serde(default)] #[serde(default)]
pub attachment_flag: Option<String>, pub attachment_flag: Option<String>,
/// Flag to show if any thread entry contains your address as a receiver.
/// Useful to make mailing list threads that CC you stand out.
/// Default: "✸"
#[serde(default)]
pub highlight_self_flag: Option<String>,
/// Show `highlight_self_flag` or not.
/// Default: false
#[serde(default)]
pub highlight_self: ToggleFlag,
/// Should threads with different Subjects show a list of those /// Should threads with different Subjects show a list of those
/// subjects on the entry title? /// subjects on the entry title?
/// Default: "true" /// Default: "true"
@ -185,6 +196,8 @@ impl Default for ListingSettings {
thread_snoozed_flag: None, thread_snoozed_flag: None,
selected_flag: None, selected_flag: None,
attachment_flag: None, attachment_flag: None,
highlight_self_flag: None,
highlight_self: ToggleFlag::Unset,
thread_subject_pack: true, thread_subject_pack: true,
threaded_repeat_identical_from_values: false, threaded_repeat_identical_from_values: false,
relative_menu_indices: true, relative_menu_indices: true,
@ -224,6 +237,8 @@ impl DotAddressable for ListingSettings {
"thread_snoozed_flag" => self.thread_snoozed_flag.lookup(field, tail), "thread_snoozed_flag" => self.thread_snoozed_flag.lookup(field, tail),
"selected_flag" => self.selected_flag.lookup(field, tail), "selected_flag" => self.selected_flag.lookup(field, tail),
"attachment_flag" => self.attachment_flag.lookup(field, tail), "attachment_flag" => self.attachment_flag.lookup(field, tail),
"highlight_self_flag" => self.highlight_self_flag.lookup(field, tail),
"highlight_self" => self.highlight_self.lookup(field, tail),
"thread_subject_pack" => self.thread_subject_pack.lookup(field, tail), "thread_subject_pack" => self.thread_subject_pack.lookup(field, tail),
"threaded_repeat_identical_from_values" => self "threaded_repeat_identical_from_values" => self
.threaded_repeat_identical_from_values .threaded_repeat_identical_from_values

@ -29,15 +29,15 @@ use melib::HeaderName;
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "sticky-headers" , alias = "headers-sticky" , alias = "headers_sticky")] # [serde (default)] pub sticky_headers : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { Self { pager_context : None , pager_stop : None , sticky_headers : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "sticky-headers" , alias = "headers-sticky" , alias = "headers_sticky")] # [serde (default)] pub sticky_headers : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { Self { pager_context : None , pager_stop : None , sticky_headers : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = " Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = " Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = " Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = " Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Should threads with different Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > , # [doc = " In threaded listing style, repeat identical From column values within a"] # [doc = " thread. Not repeating adds empty space in the From column which"] # [doc = " might result in less visual clutter."] # [doc = " Default: \"false\""] # [serde (default)] pub threaded_repeat_identical_from_values : Option < bool > , # [doc = " Show relative indices in menu mailboxes to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-menu-indices")] # [serde (default)] pub relative_menu_indices : Option < bool > , # [doc = " Show relative indices in listings to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-list-indices")] # [serde (default)] pub relative_list_indices : Option < bool > , # [doc = " Hide sidebar on launch. Default: \"false\""] # [serde (alias = "hide-sidebar-on-launch")] # [serde (default)] pub hide_sidebar_on_launch : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { Self { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , thread_subject_pack : None , threaded_repeat_identical_from_values : None , relative_menu_indices : None , relative_list_indices : None , hide_sidebar_on_launch : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = " Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = " Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = " Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = " Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Flag to show if any thread entry contains your address as a receiver."] # [doc = " Useful to make mailing list threads that CC you stand out."] # [doc = " Default: \"✸\""] # [serde (default)] pub highlight_self_flag : Option < Option < String > > , # [doc = " Show `highlight_self_flag` or not."] # [doc = " Default: false"] # [serde (default)] pub highlight_self : Option < ToggleFlag > , # [doc = " Should threads with different Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > , # [doc = " In threaded listing style, repeat identical From column values within a"] # [doc = " thread. Not repeating adds empty space in the From column which"] # [doc = " might result in less visual clutter."] # [doc = " Default: \"false\""] # [serde (default)] pub threaded_repeat_identical_from_values : Option < bool > , # [doc = " Show relative indices in menu mailboxes to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-menu-indices")] # [serde (default)] pub relative_menu_indices : Option < bool > , # [doc = " Show relative indices in listings to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-list-indices")] # [serde (default)] pub relative_list_indices : Option < bool > , # [doc = " Hide sidebar on launch. Default: \"false\""] # [serde (alias = "hide-sidebar-on-launch")] # [serde (default)] pub hide_sidebar_on_launch : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { Self { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , highlight_self_flag : None , highlight_self : None , thread_subject_pack : None , threaded_repeat_identical_from_values : None , relative_menu_indices : None , relative_list_indices : None , hide_sidebar_on_launch : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct NotificationsSettingsOverride { # [doc = " Enable notifications."] # [doc = " Default: True"] # [serde (default)] pub enable : Option < bool > , # [doc = " A command to pipe notifications through."] # [doc = " Default: None"] # [serde (default)] pub script : Option < Option < String > > , # [doc = " A command to pipe new mail notifications through (preferred over"] # [doc = " `script`). Default: None"] # [serde (default)] pub new_mail_script : Option < Option < String > > , # [doc = " A file location which has its size changed when new mail arrives (max"] # [doc = " 128 bytes). Can be used to trigger new mail notifications eg with"] # [doc = " `xbiff(1)`. Default: None"] # [serde (alias = "xbiff-file-path")] # [serde (default)] pub xbiff_file_path : Option < Option < String > > , # [serde (alias = "play-sound")] # [serde (default)] pub play_sound : Option < ToggleFlag > , # [serde (alias = "sound-file")] # [serde (default)] pub sound_file : Option < Option < String > > } impl Default for NotificationsSettingsOverride { fn default () -> Self { Self { enable : None , script : None , new_mail_script : None , xbiff_file_path : None , play_sound : None , sound_file : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct NotificationsSettingsOverride { # [doc = " Enable notifications."] # [doc = " Default: True"] # [serde (default)] pub enable : Option < bool > , # [doc = " A command to pipe notifications through."] # [doc = " Default: None"] # [serde (default)] pub script : Option < Option < String > > , # [doc = " A command to pipe new mail notifications through (preferred over"] # [doc = " `script`). Default: None"] # [serde (default)] pub new_mail_script : Option < Option < String > > , # [doc = " A file location which has its size changed when new mail arrives (max"] # [doc = " 128 bytes). Can be used to trigger new mail notifications eg with"] # [doc = " `xbiff(1)`. Default: None"] # [serde (alias = "xbiff-file-path")] # [serde (default)] pub xbiff_file_path : Option < Option < String > > , # [serde (alias = "play-sound")] # [serde (default)] pub play_sound : Option < ToggleFlag > , # [serde (alias = "sound-file")] # [serde (default)] pub sound_file : Option < Option < String > > } impl Default for NotificationsSettingsOverride { fn default () -> Self { Self { enable : None , script : None , new_mail_script : None , xbiff_file_path : None , play_sound : None , sound_file : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ShortcutsOverride { # [serde (default)] pub general : Option < GeneralShortcuts > , # [serde (default)] pub listing : Option < ListingShortcuts > , # [serde (default)] pub composing : Option < ComposingShortcuts > , # [serde (alias = "contact-list")] # [serde (default)] pub contact_list : Option < ContactListShortcuts > , # [serde (alias = "envelope-view")] # [serde (default)] pub envelope_view : Option < EnvelopeViewShortcuts > , # [serde (alias = "thread-view")] # [serde (default)] pub thread_view : Option < ThreadViewShortcuts > , # [serde (default)] pub pager : Option < PagerShortcuts > } impl Default for ShortcutsOverride { fn default () -> Self { Self { general : None , listing : None , composing : None , contact_list : None , envelope_view : None , thread_view : None , pager : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ShortcutsOverride { # [serde (default)] pub general : Option < GeneralShortcuts > , # [serde (default)] pub listing : Option < ListingShortcuts > , # [serde (default)] pub composing : Option < ComposingShortcuts > , # [serde (alias = "contact-list")] # [serde (default)] pub contact_list : Option < ContactListShortcuts > , # [serde (alias = "envelope-view")] # [serde (default)] pub envelope_view : Option < EnvelopeViewShortcuts > , # [serde (alias = "thread-view")] # [serde (default)] pub thread_view : Option < ThreadViewShortcuts > , # [serde (default)] pub pager : Option < PagerShortcuts > } impl Default for ShortcutsOverride { fn default () -> Self { Self { general : None , listing : None , composing : None , contact_list : None , envelope_view : None , thread_view : None , pager : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embedded editor (for terminal interfaces) instead of forking and"] # [doc = " waiting."] # [serde (alias = "embed")] # [serde (default)] pub embedded_pty : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Set User-Agent"] # [doc = " Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preamble when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preamble")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ToggleFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { Self { send_mail : None , editor_command : None , embedded_pty : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embedded editor (for terminal interfaces) instead of forking and"] # [doc = " waiting."] # [serde (alias = "embed")] # [serde (default)] pub embedded_pty : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Set User-Agent"] # [doc = " Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preamble when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preamble")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ActionFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { Self { send_mail : None , editor_command : None , embedded_pty : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct TagsSettingsOverride { # [serde (deserialize_with = "tag_color_de")] # [serde (default)] pub colors : Option < HashMap < TagHash , Color > > , # [serde (deserialize_with = "tag_set_de" , alias = "ignore-tags")] # [serde (default)] pub ignore_tags : Option < HashSet < TagHash > > } impl Default for TagsSettingsOverride { fn default () -> Self { Self { colors : None , ignore_tags : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct TagsSettingsOverride { # [serde (deserialize_with = "tag_color_de")] # [serde (default)] pub colors : Option < HashMap < TagHash , Color > > , # [serde (deserialize_with = "tag_set_de" , alias = "ignore-tags")] # [serde (default)] pub ignore_tags : Option < HashSet < TagHash > > } impl Default for TagsSettingsOverride { fn default () -> Self { Self { colors : None , ignore_tags : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PGPSettingsOverride { # [doc = " auto verify signed e-mail according to RFC3156"] # [doc = " Default: true"] # [serde (alias = "auto-verify-signatures")] # [serde (default)] pub auto_verify_signatures : Option < bool > , # [doc = " auto decrypt encrypted e-mail"] # [doc = " Default: true"] # [serde (alias = "auto-decrypt")] # [serde (default)] pub auto_decrypt : Option < bool > , # [doc = " always sign sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-sign")] # [serde (default)] pub auto_sign : Option < bool > , # [doc = " Auto encrypt sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-encrypt")] # [serde (default)] pub auto_encrypt : Option < bool > , # [doc = " Default: None"] # [serde (alias = "sign-key")] # [serde (default)] pub sign_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "decrypt-key")] # [serde (default)] pub decrypt_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "encrypt-key")] # [serde (default)] pub encrypt_key : Option < Option < String > > , # [doc = " Allow remote lookups"] # [doc = " Default: None"] # [serde (alias = "allow-remote-lookups")] # [serde (default)] pub allow_remote_lookup : Option < ToggleFlag > , # [doc = " Remote lookup mechanisms."] # [doc = " Default: \"local,wkd\""] # [cfg_attr (feature = "gpgme" , serde (alias = "remote-lookup-mechanisms"))] # [cfg (feature = "gpgme")] # [serde (default)] pub remote_lookup_mechanisms : Option < melib :: gpgme :: LocateKey > , # [cfg (not (feature = "gpgme"))] # [cfg_attr (not (feature = "gpgme") , serde (alias = "remote-lookup-mechanisms"))] # [serde (default)] pub remote_lookup_mechanisms : Option < String > } impl Default for PGPSettingsOverride { fn default () -> Self { Self { auto_verify_signatures : None , auto_decrypt : None , auto_sign : None , auto_encrypt : None , sign_key : None , decrypt_key : None , encrypt_key : None , allow_remote_lookup : None , remote_lookup_mechanisms : None } } } # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PGPSettingsOverride { # [doc = " auto verify signed e-mail according to RFC3156"] # [doc = " Default: true"] # [serde (alias = "auto-verify-signatures")] # [serde (default)] pub auto_verify_signatures : Option < ActionFlag > , # [doc = " auto decrypt encrypted e-mail"] # [doc = " Default: true"] # [serde (alias = "auto-decrypt")] # [serde (default)] pub auto_decrypt : Option < ActionFlag > , # [doc = " always sign sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-sign")] # [serde (default)] pub auto_sign : Option < ActionFlag > , # [doc = " Auto encrypt sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-encrypt")] # [serde (default)] pub auto_encrypt : Option < ActionFlag > , # [doc = " Default: None"] # [serde (alias = "sign-key")] # [serde (default)] pub sign_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "decrypt-key")] # [serde (default)] pub decrypt_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "encrypt-key")] # [serde (default)] pub encrypt_key : Option < Option < String > > , # [doc = " Allow remote lookups"] # [doc = " Default: False"] # [serde (alias = "allow-remote-lookups")] # [serde (default)] pub allow_remote_lookup : Option < ActionFlag > , # [doc = " Remote lookup mechanisms."] # [doc = " Default: \"local,wkd\""] # [cfg_attr (feature = "gpgme" , serde (alias = "remote-lookup-mechanisms"))] # [cfg (feature = "gpgme")] # [serde (default)] pub remote_lookup_mechanisms : Option < melib :: gpgme :: LocateKey > , # [cfg (not (feature = "gpgme"))] # [cfg_attr (not (feature = "gpgme") , serde (alias = "remote-lookup-mechanisms"))] # [serde (default)] pub remote_lookup_mechanisms : Option < String > } impl Default for PGPSettingsOverride { fn default () -> Self { Self { auto_verify_signatures : None , auto_decrypt : None , auto_sign : None , auto_encrypt : None , sign_key : None , decrypt_key : None , encrypt_key : None , allow_remote_lookup : None , remote_lookup_mechanisms : None } } }

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use melib::conf::ToggleFlag; use melib::conf::ActionFlag;
use super::default_vals::*; use super::default_vals::*;
@ -30,22 +30,22 @@ pub struct PGPSettings {
/// auto verify signed e-mail according to RFC3156 /// auto verify signed e-mail according to RFC3156
/// Default: true /// Default: true
#[serde(default = "true_val", alias = "auto-verify-signatures")] #[serde(default = "true_val", alias = "auto-verify-signatures")]
pub auto_verify_signatures: bool, pub auto_verify_signatures: ActionFlag,
/// auto decrypt encrypted e-mail /// auto decrypt encrypted e-mail
/// Default: true /// Default: true
#[serde(default = "true_val", alias = "auto-decrypt")] #[serde(default = "true_val", alias = "auto-decrypt")]
pub auto_decrypt: bool, pub auto_decrypt: ActionFlag,
/// always sign sent e-mail /// always sign sent e-mail
/// Default: false /// Default: false
#[serde(default = "false_val", alias = "auto-sign")] #[serde(default = "false_val", alias = "auto-sign")]
pub auto_sign: bool, pub auto_sign: ActionFlag,
/// Auto encrypt sent e-mail /// Auto encrypt sent e-mail
/// Default: false /// Default: false
#[serde(default = "false_val", alias = "auto-encrypt")] #[serde(default = "false_val", alias = "auto-encrypt")]
pub auto_encrypt: bool, pub auto_encrypt: ActionFlag,
// https://tools.ietf.org/html/rfc4880#section-12.2 // https://tools.ietf.org/html/rfc4880#section-12.2
/// Default: None /// Default: None
@ -61,9 +61,12 @@ pub struct PGPSettings {
pub encrypt_key: Option<String>, pub encrypt_key: Option<String>,
/// Allow remote lookups /// Allow remote lookups
/// Default: None /// Default: False
#[serde(default = "internal_value_false", alias = "allow-remote-lookups")] #[serde(
pub allow_remote_lookup: ToggleFlag, default = "action_internal_value_false",
alias = "allow-remote-lookups"
)]
pub allow_remote_lookup: ActionFlag,
/// Remote lookup mechanisms. /// Remote lookup mechanisms.
/// Default: "local,wkd" /// Default: "local,wkd"
@ -92,14 +95,14 @@ fn default_lookup_mechanism() -> melib::gpgme::LocateKey {
impl Default for PGPSettings { impl Default for PGPSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
auto_verify_signatures: true, auto_verify_signatures: true.into(),
auto_decrypt: true, auto_decrypt: true.into(),
auto_sign: false, auto_sign: false.into(),
auto_encrypt: false, auto_encrypt: false.into(),
sign_key: None, sign_key: None,
decrypt_key: None, decrypt_key: None,
encrypt_key: None, encrypt_key: None,
allow_remote_lookup: internal_value_false::<ToggleFlag>(), allow_remote_lookup: action_internal_value_false::<ActionFlag>(),
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
remote_lookup_mechanisms: default_lookup_mechanism(), remote_lookup_mechanisms: default_lookup_mechanism(),
#[cfg(not(feature = "gpgme"))] #[cfg(not(feature = "gpgme"))]

@ -62,6 +62,14 @@
clippy::cognitive_complexity, clippy::cognitive_complexity,
clippy::manual_clamp clippy::manual_clamp
)] )]
/* Source Code Annotation Tags:
*
* Global tags (in tagref format <https://github.com/stepchowfun/tagref>) for source code
* annotation:
*
* - tags from melib/src/lib.rs.
* - [tag:hardcoded_color_value] Replace hardcoded color values with user configurable ones.
*/
//! //!
//! This crate contains the frontend stuff of the application. The application //! This crate contains the frontend stuff of the application. The application
@ -93,8 +101,8 @@ static GLOBAL: System = System;
pub extern crate melib; pub extern crate melib;
pub use melib::{ pub use melib::{
error::*, log, AccountHash, Envelope, EnvelopeHash, EnvelopeRef, Flag, LogLevel, Mail, Mailbox, error::*, log, AccountHash, ActionFlag, Envelope, EnvelopeHash, EnvelopeRef, Flag, LogLevel,
MailboxHash, ThreadHash, ToggleFlag, Mail, Mailbox, MailboxHash, ThreadHash, ToggleFlag,
}; };
pub mod args; pub mod args;

@ -387,15 +387,11 @@ impl Composer {
to.extend(envelope.from().iter().cloned()); to.extend(envelope.from().iter().cloned());
} }
to.extend(envelope.to().iter().cloned()); to.extend(envelope.to().iter().cloned());
if let Ok(ours) = TryInto::<Address>::try_into( let ours = context.accounts[&coordinates.0]
context.accounts[&coordinates.0] .settings
.settings .account()
.account() .make_display_name();
.make_display_name() to.remove(&ours);
.as_str(),
) {
to.remove(&ours);
}
ret.draft.set_header(HeaderName::TO, { ret.draft.set_header(HeaderName::TO, {
let mut ret: String = let mut ret: String =
to.into_iter() to.into_iter()
@ -671,7 +667,7 @@ To: {}
} }
}; };
(addr, desc) (addr.to_string(), desc)
}) })
.map(AutoCompleteEntry::from) .map(AutoCompleteEntry::from)
.collect::<Vec<AutoCompleteEntry>>() .collect::<Vec<AutoCompleteEntry>>()
@ -688,7 +684,12 @@ To: {}
let theme_default = crate::conf::value(context, "theme_default"); let theme_default = crate::conf::value(context, "theme_default");
grid.clear_area(area, theme_default); grid.clear_area(area, theme_default);
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
if self.gpg_state.sign_mail.is_true() { if self
.gpg_state
.sign_mail
.unwrap_or(ActionFlag::False)
.is_true()
{
let key_list = self let key_list = self
.gpg_state .gpg_state
.sign_keys .sign_keys
@ -731,7 +732,12 @@ To: {}
); );
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
if self.gpg_state.encrypt_mail.is_true() { if self
.gpg_state
.encrypt_mail
.unwrap_or(ActionFlag::False)
.is_true()
{
let key_list = self let key_list = self
.gpg_state .gpg_state
.encrypt_keys .encrypt_keys
@ -867,10 +873,9 @@ impl Component for Composer {
if !self.initialized { if !self.initialized {
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
if self.gpg_state.sign_mail.is_unset() { if self.gpg_state.sign_mail.is_none() {
self.gpg_state.sign_mail = ToggleFlag::InternalVal(*account_settings!( self.gpg_state.sign_mail =
context[self.account_hash].pgp.auto_sign Some(*account_settings!(context[self.account_hash].pgp.auto_sign));
));
} }
if !self.draft.headers().contains_key(HeaderName::FROM) if !self.draft.headers().contains_key(HeaderName::FROM)
|| self.draft.headers()[HeaderName::FROM].is_empty() || self.draft.headers()[HeaderName::FROM].is_empty()
@ -880,7 +885,8 @@ impl Component for Composer {
context.accounts[&self.account_hash] context.accounts[&self.account_hash]
.settings .settings
.account() .account()
.make_display_name(), .make_display_name()
.to_string(),
); );
} }
self.pager.update_from_str(self.draft.body(), Some(77)); self.pager.update_from_str(self.draft.body(), Some(77));
@ -1457,12 +1463,20 @@ impl Component for Composer {
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
match self.cursor { match self.cursor {
Cursor::Sign => { Cursor::Sign => {
let is_true = self.gpg_state.sign_mail.is_true(); let is_true = self
self.gpg_state.sign_mail = ToggleFlag::from(!is_true); .gpg_state
.sign_mail
.unwrap_or(ActionFlag::False)
.is_true();
self.gpg_state.sign_mail = Some(ActionFlag::from(!is_true));
} }
Cursor::Encrypt => { Cursor::Encrypt => {
let is_true = self.gpg_state.encrypt_mail.is_true(); let is_true = self
self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true); .gpg_state
.encrypt_mail
.unwrap_or(ActionFlag::False)
.is_true();
self.gpg_state.encrypt_mail = Some(ActionFlag::from(!is_true));
} }
_ => {} _ => {}
}; };
@ -1686,7 +1700,7 @@ impl Component for Composer {
) )
}) { }) {
Ok(widget) => { Ok(widget) => {
self.gpg_state.sign_mail = ToggleFlag::from(true); self.gpg_state.sign_mail = Some(ActionFlag::from(true));
self.mode = ViewMode::SelectEncryptKey(false, widget); self.mode = ViewMode::SelectEncryptKey(false, widget);
} }
Err(err) => { Err(err) => {
@ -1727,7 +1741,7 @@ impl Component for Composer {
) )
}) { }) {
Ok(widget) => { Ok(widget) => {
self.gpg_state.encrypt_mail = ToggleFlag::from(true); self.gpg_state.encrypt_mail = Some(ActionFlag::from(true));
self.mode = ViewMode::SelectEncryptKey(true, widget); self.mode = ViewMode::SelectEncryptKey(true, widget);
} }
Err(err) => { Err(err) => {
@ -2166,15 +2180,23 @@ impl Component for Composer {
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
Action::Compose(ComposeAction::ToggleSign) => { Action::Compose(ComposeAction::ToggleSign) => {
let is_true = self.gpg_state.sign_mail.is_true(); let is_true = self
self.gpg_state.sign_mail = ToggleFlag::from(!is_true); .gpg_state
.sign_mail
.unwrap_or(ActionFlag::False)
.is_true();
self.gpg_state.sign_mail = Some(ActionFlag::from(!is_true));
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
Action::Compose(ComposeAction::ToggleEncrypt) => { Action::Compose(ComposeAction::ToggleEncrypt) => {
let is_true = self.gpg_state.encrypt_mail.is_true(); let is_true = self
self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true); .gpg_state
.encrypt_mail
.unwrap_or(ActionFlag::False)
.is_true();
self.gpg_state.encrypt_mail = Some(ActionFlag::from(!is_true));
self.set_dirty(true); self.set_dirty(true);
return true; return true;
} }
@ -2520,13 +2542,22 @@ pub fn send_draft_async(
>, >,
> = vec![]; > = vec![];
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
if gpg_state.sign_mail.is_true() && !gpg_state.encrypt_mail.is_true() { if gpg_state.sign_mail.unwrap_or(ActionFlag::False).is_true()
&& !gpg_state
.encrypt_mail
.unwrap_or(ActionFlag::False)
.is_true()
{
filters_stack.push(Box::new(crate::mail::pgp::sign_filter( filters_stack.push(Box::new(crate::mail::pgp::sign_filter(
gpg_state.sign_keys, gpg_state.sign_keys,
)?)); )?));
} else if gpg_state.encrypt_mail.is_true() { } else if gpg_state
.encrypt_mail
.unwrap_or(ActionFlag::False)
.is_true()
{
filters_stack.push(Box::new(crate::mail::pgp::encrypt_filter( filters_stack.push(Box::new(crate::mail::pgp::encrypt_filter(
if gpg_state.sign_mail.is_true() { if gpg_state.sign_mail.unwrap_or(ActionFlag::False).is_true() {
Some(gpg_state.sign_keys.clone()) Some(gpg_state.sign_keys.clone())
} else { } else {
None None

@ -29,7 +29,7 @@ pub enum KeySelection {
secret: bool, secret: bool,
local: bool, local: bool,
pattern: String, pattern: String,
allow_remote_lookup: ToggleFlag, allow_remote_lookup: ActionFlag,
}, },
Error { Error {
id: ComponentId, id: ComponentId,
@ -52,7 +52,7 @@ impl KeySelection {
secret: bool, secret: bool,
local: bool, local: bool,
pattern: String, pattern: String,
allow_remote_lookup: ToggleFlag, allow_remote_lookup: ActionFlag,
context: &Context, context: &Context,
) -> Result<Self> { ) -> Result<Self> {
use melib::gpgme::*; use melib::gpgme::*;
@ -247,8 +247,8 @@ impl Component for KeySelection {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GpgComposeState { pub struct GpgComposeState {
pub sign_mail: ToggleFlag, pub sign_mail: Option<ActionFlag>,
pub encrypt_mail: ToggleFlag, pub encrypt_mail: Option<ActionFlag>,
pub encrypt_keys: Vec<melib::gpgme::Key>, pub encrypt_keys: Vec<melib::gpgme::Key>,
pub encrypt_for_self: bool, pub encrypt_for_self: bool,
pub sign_keys: Vec<melib::gpgme::Key>, pub sign_keys: Vec<melib::gpgme::Key>,
@ -257,8 +257,8 @@ pub struct GpgComposeState {
impl Default for GpgComposeState { impl Default for GpgComposeState {
fn default() -> Self { fn default() -> Self {
Self { Self {
sign_mail: ToggleFlag::Unset, sign_mail: None,
encrypt_mail: ToggleFlag::Unset, encrypt_mail: None,
encrypt_keys: vec![], encrypt_keys: vec![],
encrypt_for_self: true, encrypt_for_self: true,
sign_keys: vec![], sign_keys: vec![],

@ -41,25 +41,11 @@ use crate::{
components::ExtendShortcutsMaps, components::ExtendShortcutsMaps,
}; };
// [ref:TODO]: emoji_text_presentation_selector should be printed along with the chars pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎", emoji_text_presentation_selector!());
// before it but not as a separate Cell pub const DEFAULT_SELECTED_FLAG: &str = concat!("☑️", emoji_text_presentation_selector!());
//macro_rules! emoji_text_presentation_selector { pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●", emoji_text_presentation_selector!());
// () => { pub const DEFAULT_SNOOZED_FLAG: &str = concat!("💤", emoji_text_presentation_selector!());
// "\u{FE0E}" pub const DEFAULT_HIGHLIGHT_SELF_FLAG: &str = concat!("✸", emoji_text_presentation_selector!());
// };
//}
//
//pub const DEFAULT_ATTACHMENT_FLAG: &str = concat!("📎",
// emoji_text_presentation_selector!()); pub const DEFAULT_SELECTED_FLAG: &str =
// concat!("☑️", emoji_text_presentation_selector!());
// pub const DEFAULT_UNSEEN_FLAG: &str = concat!("●",
// emoji_text_presentation_selector!()); pub const DEFAULT_SNOOZED_FLAG: &str =
// concat!("💤", emoji_text_presentation_selector!());
pub const DEFAULT_ATTACHMENT_FLAG: &str = "📎";
pub const DEFAULT_SELECTED_FLAG: &str = "☑️";
pub const DEFAULT_UNSEEN_FLAG: &str = "●";
pub const DEFAULT_SNOOZED_FLAG: &str = "💤";
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct RowsState<T> { pub struct RowsState<T> {
@ -401,6 +387,7 @@ pub struct EntryStrings {
pub flag: FlagString, pub flag: FlagString,
pub from: FromString, pub from: FromString,
pub tags: TagString, pub tags: TagString,
pub highlight_self: bool,
} }
#[macro_export] #[macro_export]

@ -324,6 +324,11 @@ impl MailListingTrait for CompactListing {
let mut from_address_list = Vec::new(); let mut from_address_list = Vec::new();
let mut from_address_set: std::collections::HashSet<Vec<u8>> = let mut from_address_set: std::collections::HashSet<Vec<u8>> =
std::collections::HashSet::new(); std::collections::HashSet::new();
let mut highlight_self: bool;
let my_address: Address = context.accounts[&self.cursor_pos.0]
.settings
.account
.make_display_name();
'items_for_loop: for thread in items { 'items_for_loop: for thread in items {
let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()];
let root_env_hash = if let Some(h) = thread_node.message().or_else(|| { let root_env_hash = if let Some(h) = thread_node.message().or_else(|| {
@ -373,6 +378,7 @@ impl MailListingTrait for CompactListing {
tags.clear(); tags.clear();
from_address_list.clear(); from_address_list.clear();
from_address_set.clear(); from_address_set.clear();
highlight_self = false;
for (envelope, show_subject) in threads for (envelope, show_subject) in threads
.thread_iter(thread) .thread_iter(thread)
.filter_map(|(_, h)| { .filter_map(|(_, h)| {
@ -399,6 +405,11 @@ impl MailListingTrait for CompactListing {
} }
} }
highlight_self |= envelope
.to()
.iter()
.chain(envelope.cc().iter())
.any(|a| a == &my_address);
for addr in envelope.from().iter() { for addr in envelope.from().iter() {
if from_address_set.contains(addr.address_spec_raw()) { if from_address_set.contains(addr.address_spec_raw()) {
continue; continue;
@ -407,6 +418,15 @@ impl MailListingTrait for CompactListing {
from_address_list.push(addr.clone()); from_address_list.push(addr.clone());
} }
} }
if !mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.highlight_self
)
.is_true()
{
highlight_self = false;
}
let row_attr = row_attr!( let row_attr = row_attr!(
self.color_cache, self.color_cache,
@ -425,6 +445,7 @@ impl MailListingTrait for CompactListing {
&threads, &threads,
&other_subjects, &other_subjects,
&tags, &tags,
highlight_self,
thread, thread,
); );
row_widths row_widths
@ -937,6 +958,7 @@ impl CompactListing {
threads: &Threads, threads: &Threads,
other_subjects: &IndexSet<String>, other_subjects: &IndexSet<String>,
tags: &IndexSet<TagHash>, tags: &IndexSet<TagHash>,
highlight_self: bool,
hash: ThreadHash, hash: ThreadHash,
) -> EntryStrings { ) -> EntryStrings {
let thread = threads.thread_ref(hash); let thread = threads.thread_ref(hash);
@ -1096,6 +1118,7 @@ impl CompactListing {
)), )),
from: FromString(Address::display_name_slice(from)), from: FromString(Address::display_name_slice(from)),
tags: TagString(tags_string, colors), tags: TagString(tags_string, colors),
highlight_self,
} }
} }
@ -1140,6 +1163,11 @@ impl CompactListing {
let mut from_address_list = Vec::new(); let mut from_address_list = Vec::new();
let mut from_address_set: std::collections::HashSet<Vec<u8>> = let mut from_address_set: std::collections::HashSet<Vec<u8>> =
std::collections::HashSet::new(); std::collections::HashSet::new();
let mut highlight_self: bool = false;
let my_address: Address = context.accounts[&self.cursor_pos.0]
.settings
.account
.make_display_name();
for (envelope, show_subject) in threads for (envelope, show_subject) in threads
.thread_iter(thread_hash) .thread_iter(thread_hash)
.filter_map(|(_, h)| { .filter_map(|(_, h)| {
@ -1164,6 +1192,11 @@ impl CompactListing {
tags.insert(t); tags.insert(t);
} }
} }
highlight_self |= envelope
.to()
.iter()
.chain(envelope.cc().iter())
.any(|a| a == &my_address);
for addr in envelope.from().iter() { for addr in envelope.from().iter() {
if from_address_set.contains(addr.address_spec_raw()) { if from_address_set.contains(addr.address_spec_raw()) {
continue; continue;
@ -1172,6 +1205,15 @@ impl CompactListing {
from_address_list.push(addr.clone()); from_address_list.push(addr.clone());
} }
} }
if !mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.highlight_self
)
.is_true()
{
highlight_self = false;
}
let strings = self.make_entry_string( let strings = self.make_entry_string(
&envelope, &envelope,
@ -1181,6 +1223,7 @@ impl CompactListing {
&threads, &threads,
&other_subjects, &other_subjects,
&tags, &tags,
highlight_self,
thread_hash, thread_hash,
); );
drop(envelope); drop(envelope);
@ -1458,7 +1501,34 @@ impl CompactListing {
) )
}; };
let x = { let x = {
let area = columns[3].area().nth_row(idx).skip_cols(x); let mut area = columns[3].area().nth_row(idx).skip_cols(x);
if strings.highlight_self {
// [ref:hardcoded_color_value]: add highlight_self theme attr
let x = columns[3]
.grid_mut()
.write_string(
mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.highlight_self_flag
)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(super::DEFAULT_HIGHLIGHT_SELF_FLAG),
Color::BLUE,
row_attr.bg,
row_attr.attrs | Attr::FORCE_TEXT,
area,
None,
)
.0;
for row in columns[3].grid().bounds_iter(area.nth_row(0).take_cols(x)) {
for c in row {
columns[3].grid_mut()[c].set_keep_fg(true);
}
}
area = area.skip_cols(x + 1);
}
columns[3] columns[3]
.grid_mut() .grid_mut()
.write_string( .write_string(

@ -713,7 +713,7 @@ impl ConversationsListing {
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(super) fn make_entry_string( fn make_entry_string(
&self, &self,
root_envelope: &Envelope, root_envelope: &Envelope,
context: &Context, context: &Context,
@ -795,6 +795,7 @@ impl ConversationsListing {
)), )),
from: FromString(Address::display_name_slice(from)), from: FromString(Address::display_name_slice(from)),
tags: TagString(tags_string, colors), tags: TagString(tags_string, colors),
highlight_self: false,
} }
} }

@ -785,6 +785,7 @@ impl PlainListing {
)), )),
from: FromString(Address::display_name_slice(e.from())), from: FromString(Address::display_name_slice(e.from())),
tags: TagString(tags, colors), tags: TagString(tags, colors),
highlight_self: false,
} }
} }

@ -953,6 +953,7 @@ impl ThreadListing {
)), )),
from: FromString(Address::display_name_slice(e.from())), from: FromString(Address::display_name_slice(e.from())),
tags: TagString(tags, colors), tags: TagString(tags, colors),
highlight_self: false,
} }
} }

@ -675,7 +675,8 @@ impl Component for MailView {
context.accounts[&coordinates.0] context.accounts[&coordinates.0]
.settings .settings
.account() .account()
.make_display_name(), .make_display_name()
.to_string(),
); );
/* Manually drop stuff because borrowck doesn't do it /* Manually drop stuff because borrowck doesn't do it
* on its own */ * on its own */

@ -257,7 +257,7 @@ impl EnvelopeView {
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
{ {
if view_settings.auto_verify_signatures { if view_settings.auto_verify_signatures.is_true() {
let verify_fut = crate::mail::pgp::verify(a.clone()); let verify_fut = crate::mail::pgp::verify(a.clone());
let handle = main_loop_handler let handle = main_loop_handler
.job_executor .job_executor
@ -317,7 +317,7 @@ impl EnvelopeView {
} }
#[cfg(feature = "gpgme")] #[cfg(feature = "gpgme")]
{ {
if view_settings.auto_decrypt { if view_settings.auto_decrypt.is_true() {
let decrypt_fut = crate::mail::pgp::decrypt(a.raw().to_vec()); let decrypt_fut = crate::mail::pgp::decrypt(a.raw().to_vec());
let handle = main_loop_handler let handle = main_loop_handler
.job_executor .job_executor

@ -21,7 +21,10 @@
use std::fmt::Write as IoWrite; use std::fmt::Write as IoWrite;
use melib::{attachment_types::Charset, error::*, pgp::DecryptionMetadata, Attachment, Result}; use melib::{
attachment_types::Charset, conf::ActionFlag, error::*, pgp::DecryptionMetadata, Attachment,
Result,
};
use crate::{ use crate::{
conf::shortcuts::EnvelopeViewShortcuts, conf::shortcuts::EnvelopeViewShortcuts,
@ -43,8 +46,8 @@ pub struct ViewSettings {
pub sticky_headers: bool, pub sticky_headers: bool,
pub show_date_in_my_timezone: bool, pub show_date_in_my_timezone: bool,
pub show_extra_headers: Vec<String>, pub show_extra_headers: Vec<String>,
pub auto_verify_signatures: bool, pub auto_verify_signatures: ActionFlag,
pub auto_decrypt: bool, pub auto_decrypt: ActionFlag,
} }
impl Default for ViewSettings { impl Default for ViewSettings {
@ -61,8 +64,8 @@ impl Default for ViewSettings {
sticky_headers: false, sticky_headers: false,
show_date_in_my_timezone: false, show_date_in_my_timezone: false,
show_extra_headers: vec![], show_extra_headers: vec![],
auto_verify_signatures: true, auto_verify_signatures: ActionFlag::InternalVal(true),
auto_decrypt: true, auto_decrypt: ActionFlag::InternalVal(true),
} }
} }
} }
@ -184,7 +187,13 @@ impl ViewOptions {
.collect::<Vec<Link>>(); .collect::<Vec<Link>>();
} }
for (lidx, l) in links.iter().enumerate().rev() { for (lidx, l) in links.iter().enumerate().rev() {
text.insert_str(l.start, &format!("[{}]", lidx)); let mut start = l.start;
while start < text.len() && !text.is_char_boundary(start) {
start += 1;
}
if start < text.len() {
text.insert_str(start, &format!("[{}]", lidx));
}
} }
} }

@ -58,6 +58,13 @@ pub enum Alignment {
Center, Center,
} }
#[macro_export]
macro_rules! emoji_text_presentation_selector {
() => {
'\u{FE0E}'
};
}
/* /*
* CSI events we use * CSI events we use
*/ */

@ -650,6 +650,7 @@ impl CellBuffer {
let upper_left = area.upper_left(); let upper_left = area.upper_left();
let bottom_right = area.bottom_right(); let bottom_right = area.bottom_right();
let (mut x, mut y) = upper_left; let (mut x, mut y) = upper_left;
let mut prev_coords = upper_left;
if y == get_y(bounds) || x == get_x(bounds) { if y == get_y(bounds) || x == get_x(bounds) {
if self.growable { if self.growable {
if !self.resize( if !self.resize(
@ -684,10 +685,17 @@ impl CellBuffer {
} }
} }
for c in s.chars() { for c in s.chars() {
if c == crate::emoji_text_presentation_selector!() {
let prev_attrs = self[prev_coords].attrs();
self[prev_coords].set_attrs(prev_attrs | Attr::FORCE_TEXT);
continue;
}
if c == '\r' { if c == '\r' {
continue; continue;
} }
if c == '\n' { if c == '\n' {
prev_coords = (x, y);
y += 1; y += 1;
if let Some(_x) = line_break { if let Some(_x) = line_break {
x = _x + get_x(upper_left); x = _x + get_x(upper_left);
@ -710,6 +718,7 @@ impl CellBuffer {
} }
break; break;
} }
prev_coords = (x, y);
if c == '\t' { if c == '\t' {
self[(x, y)].set_ch(' '); self[(x, y)].set_ch(' ');
x += 1; x += 1;
@ -996,7 +1005,10 @@ impl Cell {
self.attrs self.attrs
} }
pub fn set_attrs(&mut self, newattrs: Attr) -> &mut Self { pub fn set_attrs(&mut self, mut newattrs: Attr) -> &mut Self {
if self.attrs.intersects(Attr::FORCE_TEXT) {
newattrs |= Attr::FORCE_TEXT;
}
if !self.keep_attrs { if !self.keep_attrs {
self.attrs = newattrs; self.attrs = newattrs;
} }
@ -1079,14 +1091,15 @@ bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Attr: u8 { pub struct Attr: u8 {
/// Terminal default. /// Terminal default.
const DEFAULT = 0b000_0000; const DEFAULT = 0;
const BOLD = 0b000_0001; const BOLD = 1;
const DIM = 0b000_0010; const DIM = Self::BOLD.bits() << 1;
const ITALICS = 0b000_0100; const ITALICS = Self::DIM.bits() << 1;
const UNDERLINE = 0b000_1000; const UNDERLINE = Self::ITALICS.bits() << 1;
const BLINK = 0b001_0000; const BLINK = Self::UNDERLINE.bits() << 1;
const REVERSE = 0b010_0000; const REVERSE = Self::BLINK.bits() << 1;
const HIDDEN = 0b100_0000; const HIDDEN = Self::REVERSE.bits() << 1;
const FORCE_TEXT = Self::HIDDEN.bits() << 1;
} }
} }
@ -1107,6 +1120,7 @@ impl std::fmt::Display for Attr {
Self::BLINK => write!(f, "Blink"), Self::BLINK => write!(f, "Blink"),
Self::REVERSE => write!(f, "Reverse"), Self::REVERSE => write!(f, "Reverse"),
Self::HIDDEN => write!(f, "Hidden"), Self::HIDDEN => write!(f, "Hidden"),
Self::FORCE_TEXT => write!(f, "ForceTextRepresentation"),
combination => { combination => {
let mut ctr = 0; let mut ctr = 0;
if combination.intersects(Self::BOLD) { if combination.intersects(Self::BOLD) {
@ -1154,6 +1168,12 @@ impl std::fmt::Display for Attr {
} }
Self::HIDDEN.fmt(f)?; Self::HIDDEN.fmt(f)?;
} }
if combination.intersects(Self::FORCE_TEXT) {
if ctr > 0 {
write!(f, "|")?;
}
Self::FORCE_TEXT.fmt(f)?;
}
write!(f, "") write!(f, "")
} }
} }
@ -1196,6 +1216,7 @@ impl Attr {
"Blink" => Ok(Self::BLINK), "Blink" => Ok(Self::BLINK),
"Reverse" => Ok(Self::REVERSE), "Reverse" => Ok(Self::REVERSE),
"Hidden" => Ok(Self::HIDDEN), "Hidden" => Ok(Self::HIDDEN),
"ForceTextRepresentation" => Ok(Self::FORCE_TEXT),
combination if combination.contains('|') => { combination if combination.contains('|') => {
let mut ret = Self::DEFAULT; let mut ret = Self::DEFAULT;
for c in combination.trim().split('|') { for c in combination.trim().split('|') {

@ -405,6 +405,9 @@ impl Screen<Tty> {
} }
if !c.empty() { if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap(); write!(stdout, "{}", c.ch()).unwrap();
if c.attrs().intersects(Attr::FORCE_TEXT) {
_ = write!(stdout, "\u{FE0E}");
}
} }
} }
} }
@ -432,6 +435,9 @@ impl Screen<Tty> {
} }
if !c.empty() { if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap(); write!(stdout, "{}", c.ch()).unwrap();
if c.attrs().intersects(Attr::FORCE_TEXT) {
_ = write!(stdout, "\u{FE0E}");
}
} }
} }
} }

@ -23,7 +23,7 @@
//! [`backends`](./backends/index.html) //! [`backends`](./backends/index.html)
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::SpecialUsageMailbox, backends::SpecialUsageMailbox,
@ -55,8 +55,8 @@ pub struct AccountSettings {
impl AccountSettings { impl AccountSettings {
/// Create the account's display name from fields /// Create the account's display name from fields
/// [`AccountSettings::identity`] and [`AccountSettings::display_name`]. /// [`AccountSettings::identity`] and [`AccountSettings::display_name`].
pub fn make_display_name(&self) -> String { pub fn make_display_name(&self) -> Address {
Address::new(self.display_name.clone(), self.identity.clone()).to_string() Address::new(self.display_name.clone(), self.identity.clone())
} }
pub fn order(&self) -> Option<(SortField, SortOrder)> { pub fn order(&self) -> Option<(SortField, SortOrder)> {
@ -184,117 +184,196 @@ pub const fn none<T>() -> Option<T> {
None None
} }
macro_rules! named_unit_variant { pub use config_field_types::*;
($variant:ident) => {
pub mod $variant { pub mod config_field_types {
pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> use serde::{Deserialize, Deserializer, Serialize, Serializer};
where
D: serde::Deserializer<'de>, macro_rules! named_unit_variant {
{ ($variant:ident) => {
struct V; pub mod $variant {
impl<'de> serde::de::Visitor<'de> for V { pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
type Value = (); where
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { D: serde::Deserializer<'de>,
f.write_str(concat!("\"", stringify!($variant), "\"")) {
} struct V;
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> { impl<'de> serde::de::Visitor<'de> for V {
if value == stringify!($variant) { type Value = ();
Ok(()) fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
} else { f.write_str(concat!("\"", stringify!($variant), "\""))
Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) }
fn visit_str<E: serde::de::Error>(
self,
value: &str,
) -> Result<Self::Value, E> {
if value == stringify!($variant) {
Ok(())
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
}
} }
} }
deserializer.deserialize_str(V)
} }
deserializer.deserialize_str(V) }
};
}
pub mod strings {
named_unit_variant!(ask);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ToggleFlag {
#[default]
Unset,
InternalVal(bool),
False,
True,
}
impl From<bool> for ToggleFlag {
fn from(val: bool) -> Self {
if val {
Self::True
} else {
Self::False
} }
} }
}; }
}
pub mod strings { impl ToggleFlag {
named_unit_variant!(ask); pub fn is_unset(&self) -> bool {
} Self::Unset == *self
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub fn is_internal(&self) -> bool {
pub enum ToggleFlag { matches!(self, Self::InternalVal(_))
#[default] }
Unset,
InternalVal(bool),
False,
True,
Ask,
}
impl From<bool> for ToggleFlag { pub fn is_false(&self) -> bool {
fn from(val: bool) -> Self { matches!(self, Self::False | Self::InternalVal(false))
if val { }
Self::True
} else { pub fn is_true(&self) -> bool {
Self::False matches!(self, Self::True | Self::InternalVal(true))
} }
} }
}
impl ToggleFlag { impl Serialize for ToggleFlag {
pub fn is_unset(&self) -> bool { fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
Self::Unset == *self where
S: Serializer,
{
match self {
Self::Unset | Self::InternalVal(_) => serializer.serialize_none(),
Self::False => serializer.serialize_bool(false),
Self::True => serializer.serialize_bool(true),
}
}
} }
pub fn is_internal(&self) -> bool { impl<'de> Deserialize<'de> for ToggleFlag {
matches!(self, Self::InternalVal(_)) fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum InnerToggleFlag {
Bool(bool),
}
let s = <InnerToggleFlag>::deserialize(deserializer);
Ok(
match s.map_err(|err| {
serde::de::Error::custom(format!(
r#"expected one of "true", "false", found `{}`"#,
err
))
})? {
InnerToggleFlag::Bool(true) => Self::True,
InnerToggleFlag::Bool(false) => Self::False,
},
)
}
} }
pub fn is_ask(&self) -> bool { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
matches!(self, Self::Ask) pub enum ActionFlag {
InternalVal(bool),
False,
True,
#[default]
Ask,
} }
pub fn is_false(&self) -> bool { impl From<bool> for ActionFlag {
matches!(self, Self::False | Self::InternalVal(false)) fn from(val: bool) -> Self {
if val {
Self::True
} else {
Self::False
}
}
} }
pub fn is_true(&self) -> bool { impl ActionFlag {
matches!(self, Self::True | Self::InternalVal(true)) pub fn is_internal(&self) -> bool {
matches!(self, Self::InternalVal(_))
}
pub fn is_ask(&self) -> bool {
matches!(self, Self::Ask)
}
pub fn is_false(&self) -> bool {
matches!(self, Self::False | Self::InternalVal(false))
}
pub fn is_true(&self) -> bool {
matches!(self, Self::True | Self::InternalVal(true))
}
} }
}
impl Serialize for ToggleFlag { impl Serialize for ActionFlag {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
Self::Unset | Self::InternalVal(_) => serializer.serialize_none(), Self::InternalVal(_) => serializer.serialize_none(),
Self::False => serializer.serialize_bool(false), Self::False => serializer.serialize_bool(false),
Self::True => serializer.serialize_bool(true), Self::True => serializer.serialize_bool(true),
Self::Ask => serializer.serialize_str("ask"), Self::Ask => serializer.serialize_str("ask"),
}
} }
} }
}
impl<'de> Deserialize<'de> for ToggleFlag { impl<'de> Deserialize<'de> for ActionFlag {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum InnerToggleFlag { enum InnerActionFlag {
Bool(bool), Bool(bool),
#[serde(with = "strings::ask")] #[serde(with = "strings::ask")]
Ask, Ask,
}
let s = <InnerActionFlag>::deserialize(deserializer);
Ok(
match s.map_err(|err| {
serde::de::Error::custom(format!(
r#"expected one of "true", "false", "ask", found `{}`"#,
err
))
})? {
InnerActionFlag::Bool(true) => Self::True,
InnerActionFlag::Bool(false) => Self::False,
InnerActionFlag::Ask => Self::Ask,
},
)
} }
let s = <InnerToggleFlag>::deserialize(deserializer);
Ok(
match s.map_err(|err| {
serde::de::Error::custom(format!(
r#"expected one of "true", "false", "ask", found `{}`"#,
err
))
})? {
InnerToggleFlag::Bool(true) => Self::True,
InnerToggleFlag::Bool(false) => Self::False,
InnerToggleFlag::Ask => Self::Ask,
},
)
} }
} }

@ -1186,7 +1186,7 @@ impl JmapType {
let store = Arc::new(Store { let store = Arc::new(Store {
account_name: Arc::new(s.name.clone()), account_name: Arc::new(s.name.clone()),
account_hash, account_hash,
main_identity: s.make_display_name(), main_identity: s.make_display_name().to_string(),
extra_identities: s.extra_identities.clone(), extra_identities: s.extra_identities.clone(),
online_status, online_status,
event_consumer, event_consumer,

@ -68,7 +68,6 @@
* Global tags (in tagref format <https://github.com/stepchowfun/tagref>) for source code * Global tags (in tagref format <https://github.com/stepchowfun/tagref>) for source code
* annotation: * annotation:
* *
* - [tag:hardcoded_color_value] Replace hardcoded color values with user configurable ones.
* - [tag:needs_unit_test] * - [tag:needs_unit_test]
* - [tag:needs_user_doc] * - [tag:needs_user_doc]
* - [tag:needs_dev_doc] * - [tag:needs_dev_doc]

Loading…
Cancel
Save