diff --git a/meli/docs/meli.conf.5 b/meli/docs/meli.conf.5 index 891c98d4..c248169a 100644 --- a/meli/docs/meli.conf.5 +++ b/meli/docs/meli.conf.5 @@ -1589,6 +1589,15 @@ Flag to show if thread entry has been selected. .It Ic attachment_flag Ar Option Flag to show if thread entry contains attachments. .LiteralStringValueRenders 📎\e\uu{FE0E} 📎︎ \" default value +.It Ic highlight_self_flag Ar Option +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 Should threads with differentiating Subjects show a list of those subjects on the entry title? diff --git a/meli/src/conf.rs b/meli/src/conf.rs index b9fd770a..4a4734f1 100644 --- a/meli/src/conf.rs +++ b/meli/src/conf.rs @@ -62,7 +62,7 @@ use std::{ use indexmap::IndexMap; use melib::{ - conf::{AccountSettings, MailboxConf, ToggleFlag}, + conf::{AccountSettings, ActionFlag, MailboxConf, ToggleFlag}, error::*, }; use pager::PagerSettings; @@ -679,16 +679,29 @@ mod default_vals { None } - pub(in crate::conf) fn internal_value_false>() -> T { - super::ToggleFlag::InternalVal(false).into() + pub(in crate::conf) fn internal_value_false>( + ) -> T { + melib::conf::ToggleFlag::InternalVal(false).into() } - pub(in crate::conf) fn internal_value_true>() -> T { - super::ToggleFlag::InternalVal(true).into() + pub(in crate::conf) fn internal_value_true>() -> T + { + melib::conf::ToggleFlag::InternalVal(true).into() + } + + pub(in crate::conf) fn action_internal_value_false>( + ) -> T { + melib::conf::ActionFlag::InternalVal(false).into() } - pub(in crate::conf) fn ask>() -> T { - super::ToggleFlag::Ask.into() + //pub(in crate::conf) fn action_internal_value_true< + // T: std::convert::From, + //>() -> T { + // melib::conf::ActionFlag::InternalVal(true).into() + //} + + pub(in crate::conf) fn ask>() -> T { + melib::conf::ActionFlag::Ask.into() } } @@ -1041,6 +1054,7 @@ mod dotaddressable { impl DotAddressable for melib::LogLevel {} impl DotAddressable for PathBuf {} impl DotAddressable for ToggleFlag {} + impl DotAddressable for ActionFlag {} impl DotAddressable for SearchBackend {} impl DotAddressable for melib::SpecialUsageMailbox {} impl DotAddressable for Option {} diff --git a/meli/src/conf/composing.rs b/meli/src/conf/composing.rs index 505ea6a9..1e5044a4 100644 --- a/meli/src/conf/composing.rs +++ b/meli/src/conf/composing.rs @@ -22,7 +22,7 @@ //! Configuration for composing email. use std::collections::HashMap; -use melib::{email::HeaderName, ToggleFlag}; +use melib::{conf::ActionFlag, email::HeaderName}; use serde::{de, Deserialize, Deserializer}; use super::{ @@ -92,7 +92,7 @@ pub struct ComposingSettings { /// Forward emails as attachment? (Alternative is inline) /// Default: ask #[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 /// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:", /// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:", @@ -126,7 +126,7 @@ impl Default for ComposingSettings { wrap_header_preamble: None, attribution_format_string: None, attribution_use_posix_locale: true, - forward_as_attachment: ToggleFlag::Ask, + forward_as_attachment: ActionFlag::Ask, reply_prefix_list_to_strip: None, reply_prefix: res(), custom_compose_hooks: vec![], diff --git a/meli/src/conf/listing.rs b/meli/src/conf/listing.rs index 4d08ff4d..d0da035b 100644 --- a/meli/src/conf/listing.rs +++ b/meli/src/conf/listing.rs @@ -19,7 +19,7 @@ * along with meli. If not, see . */ -use melib::{search::Query, Error, Result}; +use melib::{search::Query, Error, Result, ToggleFlag}; use super::{default_vals::*, DotAddressable, IndexStyle}; @@ -130,6 +130,17 @@ pub struct ListingSettings { #[serde(default)] pub attachment_flag: Option, + /// 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, + + /// Show `highlight_self_flag` or not. + /// Default: false + #[serde(default)] + pub highlight_self: ToggleFlag, + /// Should threads with different Subjects show a list of those /// subjects on the entry title? /// Default: "true" @@ -185,6 +196,8 @@ impl Default for ListingSettings { thread_snoozed_flag: None, selected_flag: None, attachment_flag: None, + highlight_self_flag: None, + highlight_self: ToggleFlag::Unset, thread_subject_pack: true, threaded_repeat_identical_from_values: false, relative_menu_indices: true, @@ -224,6 +237,8 @@ impl DotAddressable for ListingSettings { "thread_snoozed_flag" => self.thread_snoozed_flag.lookup(field, tail), "selected_flag" => self.selected_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), "threaded_repeat_identical_from_values" => self .threaded_repeat_identical_from_values diff --git a/meli/src/conf/overrides.rs b/meli/src/conf/overrides.rs index 6ede703c..5746ef69 100644 --- a/meli/src/conf/overrides.rs +++ b/meli/src/conf/overrides.rs @@ -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 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 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 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 } } } diff --git a/meli/src/conf/pgp.rs b/meli/src/conf/pgp.rs index 637d1e75..52600e3d 100644 --- a/meli/src/conf/pgp.rs +++ b/meli/src/conf/pgp.rs @@ -19,7 +19,7 @@ * along with meli. If not, see . */ -use melib::conf::ToggleFlag; +use melib::conf::ActionFlag; use super::default_vals::*; @@ -30,22 +30,22 @@ pub struct PGPSettings { /// auto verify signed e-mail according to RFC3156 /// Default: true #[serde(default = "true_val", alias = "auto-verify-signatures")] - pub auto_verify_signatures: bool, + pub auto_verify_signatures: ActionFlag, /// auto decrypt encrypted e-mail /// Default: true #[serde(default = "true_val", alias = "auto-decrypt")] - pub auto_decrypt: bool, + pub auto_decrypt: ActionFlag, /// always sign sent e-mail /// Default: false #[serde(default = "false_val", alias = "auto-sign")] - pub auto_sign: bool, + pub auto_sign: ActionFlag, /// Auto encrypt sent e-mail /// Default: false #[serde(default = "false_val", alias = "auto-encrypt")] - pub auto_encrypt: bool, + pub auto_encrypt: ActionFlag, // https://tools.ietf.org/html/rfc4880#section-12.2 /// Default: None @@ -61,9 +61,12 @@ pub struct PGPSettings { pub encrypt_key: Option, /// Allow remote lookups - /// Default: None - #[serde(default = "internal_value_false", alias = "allow-remote-lookups")] - pub allow_remote_lookup: ToggleFlag, + /// Default: False + #[serde( + default = "action_internal_value_false", + alias = "allow-remote-lookups" + )] + pub allow_remote_lookup: ActionFlag, /// Remote lookup mechanisms. /// Default: "local,wkd" @@ -92,14 +95,14 @@ fn default_lookup_mechanism() -> melib::gpgme::LocateKey { impl Default for PGPSettings { fn default() -> Self { Self { - auto_verify_signatures: true, - auto_decrypt: true, - auto_sign: false, - auto_encrypt: false, + auto_verify_signatures: true.into(), + auto_decrypt: true.into(), + auto_sign: false.into(), + auto_encrypt: false.into(), sign_key: None, decrypt_key: None, encrypt_key: None, - allow_remote_lookup: internal_value_false::(), + allow_remote_lookup: action_internal_value_false::(), #[cfg(feature = "gpgme")] remote_lookup_mechanisms: default_lookup_mechanism(), #[cfg(not(feature = "gpgme"))] diff --git a/meli/src/lib.rs b/meli/src/lib.rs index 41f4c0c8..4b06cd28 100644 --- a/meli/src/lib.rs +++ b/meli/src/lib.rs @@ -62,6 +62,14 @@ clippy::cognitive_complexity, clippy::manual_clamp )] +/* Source Code Annotation Tags: + * + * Global tags (in tagref format ) 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 @@ -93,8 +101,8 @@ static GLOBAL: System = System; pub extern crate melib; pub use melib::{ - error::*, log, AccountHash, Envelope, EnvelopeHash, EnvelopeRef, Flag, LogLevel, Mail, Mailbox, - MailboxHash, ThreadHash, ToggleFlag, + error::*, log, AccountHash, ActionFlag, Envelope, EnvelopeHash, EnvelopeRef, Flag, LogLevel, + Mail, Mailbox, MailboxHash, ThreadHash, ToggleFlag, }; pub mod args; diff --git a/meli/src/mail/compose.rs b/meli/src/mail/compose.rs index c55f5bed..c09794de 100644 --- a/meli/src/mail/compose.rs +++ b/meli/src/mail/compose.rs @@ -387,15 +387,11 @@ impl Composer { to.extend(envelope.from().iter().cloned()); } to.extend(envelope.to().iter().cloned()); - if let Ok(ours) = TryInto::
::try_into( - context.accounts[&coordinates.0] - .settings - .account() - .make_display_name() - .as_str(), - ) { - to.remove(&ours); - } + let ours = context.accounts[&coordinates.0] + .settings + .account() + .make_display_name(); + to.remove(&ours); ret.draft.set_header(HeaderName::TO, { let mut ret: String = to.into_iter() @@ -671,7 +667,7 @@ To: {} } }; - (addr, desc) + (addr.to_string(), desc) }) .map(AutoCompleteEntry::from) .collect::>() @@ -688,7 +684,12 @@ To: {} let theme_default = crate::conf::value(context, "theme_default"); grid.clear_area(area, theme_default); #[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 .gpg_state .sign_keys @@ -731,7 +732,12 @@ To: {} ); } #[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 .gpg_state .encrypt_keys @@ -867,10 +873,9 @@ impl Component for Composer { if !self.initialized { #[cfg(feature = "gpgme")] - if self.gpg_state.sign_mail.is_unset() { - self.gpg_state.sign_mail = ToggleFlag::InternalVal(*account_settings!( - context[self.account_hash].pgp.auto_sign - )); + if self.gpg_state.sign_mail.is_none() { + self.gpg_state.sign_mail = + Some(*account_settings!(context[self.account_hash].pgp.auto_sign)); } if !self.draft.headers().contains_key(HeaderName::FROM) || self.draft.headers()[HeaderName::FROM].is_empty() @@ -880,7 +885,8 @@ impl Component for Composer { context.accounts[&self.account_hash] .settings .account() - .make_display_name(), + .make_display_name() + .to_string(), ); } self.pager.update_from_str(self.draft.body(), Some(77)); @@ -1457,12 +1463,20 @@ impl Component for Composer { #[cfg(feature = "gpgme")] match self.cursor { Cursor::Sign => { - let is_true = self.gpg_state.sign_mail.is_true(); - self.gpg_state.sign_mail = ToggleFlag::from(!is_true); + let is_true = self + .gpg_state + .sign_mail + .unwrap_or(ActionFlag::False) + .is_true(); + self.gpg_state.sign_mail = Some(ActionFlag::from(!is_true)); } Cursor::Encrypt => { - let is_true = self.gpg_state.encrypt_mail.is_true(); - self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true); + let is_true = self + .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) => { - self.gpg_state.sign_mail = ToggleFlag::from(true); + self.gpg_state.sign_mail = Some(ActionFlag::from(true)); self.mode = ViewMode::SelectEncryptKey(false, widget); } Err(err) => { @@ -1727,7 +1741,7 @@ impl Component for Composer { ) }) { 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); } Err(err) => { @@ -2166,15 +2180,23 @@ impl Component for Composer { } #[cfg(feature = "gpgme")] Action::Compose(ComposeAction::ToggleSign) => { - let is_true = self.gpg_state.sign_mail.is_true(); - self.gpg_state.sign_mail = ToggleFlag::from(!is_true); + let is_true = self + .gpg_state + .sign_mail + .unwrap_or(ActionFlag::False) + .is_true(); + self.gpg_state.sign_mail = Some(ActionFlag::from(!is_true)); self.set_dirty(true); return true; } #[cfg(feature = "gpgme")] Action::Compose(ComposeAction::ToggleEncrypt) => { - let is_true = self.gpg_state.encrypt_mail.is_true(); - self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true); + let is_true = self + .gpg_state + .encrypt_mail + .unwrap_or(ActionFlag::False) + .is_true(); + self.gpg_state.encrypt_mail = Some(ActionFlag::from(!is_true)); self.set_dirty(true); return true; } @@ -2520,13 +2542,22 @@ pub fn send_draft_async( >, > = vec![]; #[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( 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( - if gpg_state.sign_mail.is_true() { + if gpg_state.sign_mail.unwrap_or(ActionFlag::False).is_true() { Some(gpg_state.sign_keys.clone()) } else { None diff --git a/meli/src/mail/compose/gpg.rs b/meli/src/mail/compose/gpg.rs index 6bcc2e85..77ff10f2 100644 --- a/meli/src/mail/compose/gpg.rs +++ b/meli/src/mail/compose/gpg.rs @@ -29,7 +29,7 @@ pub enum KeySelection { secret: bool, local: bool, pattern: String, - allow_remote_lookup: ToggleFlag, + allow_remote_lookup: ActionFlag, }, Error { id: ComponentId, @@ -52,7 +52,7 @@ impl KeySelection { secret: bool, local: bool, pattern: String, - allow_remote_lookup: ToggleFlag, + allow_remote_lookup: ActionFlag, context: &Context, ) -> Result { use melib::gpgme::*; @@ -247,8 +247,8 @@ impl Component for KeySelection { #[derive(Clone, Debug)] pub struct GpgComposeState { - pub sign_mail: ToggleFlag, - pub encrypt_mail: ToggleFlag, + pub sign_mail: Option, + pub encrypt_mail: Option, pub encrypt_keys: Vec, pub encrypt_for_self: bool, pub sign_keys: Vec, @@ -257,8 +257,8 @@ pub struct GpgComposeState { impl Default for GpgComposeState { fn default() -> Self { Self { - sign_mail: ToggleFlag::Unset, - encrypt_mail: ToggleFlag::Unset, + sign_mail: None, + encrypt_mail: None, encrypt_keys: vec![], encrypt_for_self: true, sign_keys: vec![], diff --git a/meli/src/mail/listing.rs b/meli/src/mail/listing.rs index 76e72fd2..a88d356c 100644 --- a/meli/src/mail/listing.rs +++ b/meli/src/mail/listing.rs @@ -41,25 +41,11 @@ use crate::{ components::ExtendShortcutsMaps, }; -// [ref:TODO]: emoji_text_presentation_selector should be printed along with the chars -// before it but not as a separate Cell -//macro_rules! emoji_text_presentation_selector { -// () => { -// "\u{FE0E}" -// }; -//} -// -//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 = "💤"; +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_HIGHLIGHT_SELF_FLAG: &str = concat!("✸", emoji_text_presentation_selector!()); #[derive(Debug, Default)] pub struct RowsState { @@ -401,6 +387,7 @@ pub struct EntryStrings { pub flag: FlagString, pub from: FromString, pub tags: TagString, + pub highlight_self: bool, } #[macro_export] diff --git a/meli/src/mail/listing/compact.rs b/meli/src/mail/listing/compact.rs index 3f84e0ab..1efe7007 100644 --- a/meli/src/mail/listing/compact.rs +++ b/meli/src/mail/listing/compact.rs @@ -324,6 +324,11 @@ impl MailListingTrait for CompactListing { let mut from_address_list = Vec::new(); let mut from_address_set: std::collections::HashSet> = 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 { let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()]; let root_env_hash = if let Some(h) = thread_node.message().or_else(|| { @@ -373,6 +378,7 @@ impl MailListingTrait for CompactListing { tags.clear(); from_address_list.clear(); from_address_set.clear(); + highlight_self = false; for (envelope, show_subject) in threads .thread_iter(thread) .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() { if from_address_set.contains(addr.address_spec_raw()) { continue; @@ -407,6 +418,15 @@ impl MailListingTrait for CompactListing { 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!( self.color_cache, @@ -425,6 +445,7 @@ impl MailListingTrait for CompactListing { &threads, &other_subjects, &tags, + highlight_self, thread, ); row_widths @@ -937,6 +958,7 @@ impl CompactListing { threads: &Threads, other_subjects: &IndexSet, tags: &IndexSet, + highlight_self: bool, hash: ThreadHash, ) -> EntryStrings { let thread = threads.thread_ref(hash); @@ -1096,6 +1118,7 @@ impl CompactListing { )), from: FromString(Address::display_name_slice(from)), tags: TagString(tags_string, colors), + highlight_self, } } @@ -1140,6 +1163,11 @@ impl CompactListing { let mut from_address_list = Vec::new(); let mut from_address_set: std::collections::HashSet> = 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 .thread_iter(thread_hash) .filter_map(|(_, h)| { @@ -1164,6 +1192,11 @@ impl CompactListing { tags.insert(t); } } + highlight_self |= envelope + .to() + .iter() + .chain(envelope.cc().iter()) + .any(|a| a == &my_address); for addr in envelope.from().iter() { if from_address_set.contains(addr.address_spec_raw()) { continue; @@ -1172,6 +1205,15 @@ impl CompactListing { 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( &envelope, @@ -1181,6 +1223,7 @@ impl CompactListing { &threads, &other_subjects, &tags, + highlight_self, thread_hash, ); drop(envelope); @@ -1458,7 +1501,34 @@ impl CompactListing { ) }; 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] .grid_mut() .write_string( diff --git a/meli/src/mail/listing/conversations.rs b/meli/src/mail/listing/conversations.rs index 447d365b..d7b1ca96 100644 --- a/meli/src/mail/listing/conversations.rs +++ b/meli/src/mail/listing/conversations.rs @@ -713,7 +713,7 @@ impl ConversationsListing { } #[allow(clippy::too_many_arguments)] - pub(super) fn make_entry_string( + fn make_entry_string( &self, root_envelope: &Envelope, context: &Context, @@ -795,6 +795,7 @@ impl ConversationsListing { )), from: FromString(Address::display_name_slice(from)), tags: TagString(tags_string, colors), + highlight_self: false, } } diff --git a/meli/src/mail/listing/plain.rs b/meli/src/mail/listing/plain.rs index fbc43825..2e028d66 100644 --- a/meli/src/mail/listing/plain.rs +++ b/meli/src/mail/listing/plain.rs @@ -785,6 +785,7 @@ impl PlainListing { )), from: FromString(Address::display_name_slice(e.from())), tags: TagString(tags, colors), + highlight_self: false, } } diff --git a/meli/src/mail/listing/thread.rs b/meli/src/mail/listing/thread.rs index 18475bd7..96c5ce03 100644 --- a/meli/src/mail/listing/thread.rs +++ b/meli/src/mail/listing/thread.rs @@ -953,6 +953,7 @@ impl ThreadListing { )), from: FromString(Address::display_name_slice(e.from())), tags: TagString(tags, colors), + highlight_self: false, } } diff --git a/meli/src/mail/view.rs b/meli/src/mail/view.rs index 9a1c1555..1f3dbad7 100644 --- a/meli/src/mail/view.rs +++ b/meli/src/mail/view.rs @@ -675,7 +675,8 @@ impl Component for MailView { context.accounts[&coordinates.0] .settings .account() - .make_display_name(), + .make_display_name() + .to_string(), ); /* Manually drop stuff because borrowck doesn't do it * on its own */ diff --git a/meli/src/mail/view/envelope.rs b/meli/src/mail/view/envelope.rs index 79408cdd..a9fb0587 100644 --- a/meli/src/mail/view/envelope.rs +++ b/meli/src/mail/view/envelope.rs @@ -257,7 +257,7 @@ impl EnvelopeView { } #[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 handle = main_loop_handler .job_executor @@ -317,7 +317,7 @@ impl EnvelopeView { } #[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 handle = main_loop_handler .job_executor diff --git a/meli/src/mail/view/types.rs b/meli/src/mail/view/types.rs index 6a13b726..78d02a01 100644 --- a/meli/src/mail/view/types.rs +++ b/meli/src/mail/view/types.rs @@ -21,7 +21,10 @@ 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::{ conf::shortcuts::EnvelopeViewShortcuts, @@ -43,8 +46,8 @@ pub struct ViewSettings { pub sticky_headers: bool, pub show_date_in_my_timezone: bool, pub show_extra_headers: Vec, - pub auto_verify_signatures: bool, - pub auto_decrypt: bool, + pub auto_verify_signatures: ActionFlag, + pub auto_decrypt: ActionFlag, } impl Default for ViewSettings { @@ -61,8 +64,8 @@ impl Default for ViewSettings { sticky_headers: false, show_date_in_my_timezone: false, show_extra_headers: vec![], - auto_verify_signatures: true, - auto_decrypt: true, + auto_verify_signatures: ActionFlag::InternalVal(true), + auto_decrypt: ActionFlag::InternalVal(true), } } } @@ -184,7 +187,13 @@ impl ViewOptions { .collect::>(); } 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)); + } } } diff --git a/meli/src/terminal.rs b/meli/src/terminal.rs index 78095af6..9aec80e8 100644 --- a/meli/src/terminal.rs +++ b/meli/src/terminal.rs @@ -58,6 +58,13 @@ pub enum Alignment { Center, } +#[macro_export] +macro_rules! emoji_text_presentation_selector { + () => { + '\u{FE0E}' + }; +} + /* * CSI events we use */ diff --git a/meli/src/terminal/cells.rs b/meli/src/terminal/cells.rs index 09e9c7b8..52ee045c 100644 --- a/meli/src/terminal/cells.rs +++ b/meli/src/terminal/cells.rs @@ -650,6 +650,7 @@ impl CellBuffer { let upper_left = area.upper_left(); let bottom_right = area.bottom_right(); let (mut x, mut y) = upper_left; + let mut prev_coords = upper_left; if y == get_y(bounds) || x == get_x(bounds) { if self.growable { if !self.resize( @@ -684,10 +685,17 @@ impl CellBuffer { } } 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' { continue; } if c == '\n' { + prev_coords = (x, y); y += 1; if let Some(_x) = line_break { x = _x + get_x(upper_left); @@ -710,6 +718,7 @@ impl CellBuffer { } break; } + prev_coords = (x, y); if c == '\t' { self[(x, y)].set_ch(' '); x += 1; @@ -996,7 +1005,10 @@ impl Cell { 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 { self.attrs = newattrs; } @@ -1079,14 +1091,15 @@ bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attr: u8 { /// Terminal default. - const DEFAULT = 0b000_0000; - const BOLD = 0b000_0001; - const DIM = 0b000_0010; - const ITALICS = 0b000_0100; - const UNDERLINE = 0b000_1000; - const BLINK = 0b001_0000; - const REVERSE = 0b010_0000; - const HIDDEN = 0b100_0000; + const DEFAULT = 0; + const BOLD = 1; + const DIM = Self::BOLD.bits() << 1; + const ITALICS = Self::DIM.bits() << 1; + const UNDERLINE = Self::ITALICS.bits() << 1; + const BLINK = Self::UNDERLINE.bits() << 1; + const REVERSE = Self::BLINK.bits() << 1; + 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::REVERSE => write!(f, "Reverse"), Self::HIDDEN => write!(f, "Hidden"), + Self::FORCE_TEXT => write!(f, "ForceTextRepresentation"), combination => { let mut ctr = 0; if combination.intersects(Self::BOLD) { @@ -1154,6 +1168,12 @@ impl std::fmt::Display for Attr { } Self::HIDDEN.fmt(f)?; } + if combination.intersects(Self::FORCE_TEXT) { + if ctr > 0 { + write!(f, "|")?; + } + Self::FORCE_TEXT.fmt(f)?; + } write!(f, "") } } @@ -1196,6 +1216,7 @@ impl Attr { "Blink" => Ok(Self::BLINK), "Reverse" => Ok(Self::REVERSE), "Hidden" => Ok(Self::HIDDEN), + "ForceTextRepresentation" => Ok(Self::FORCE_TEXT), combination if combination.contains('|') => { let mut ret = Self::DEFAULT; for c in combination.trim().split('|') { diff --git a/meli/src/terminal/screen.rs b/meli/src/terminal/screen.rs index 15f434f2..126cad41 100644 --- a/meli/src/terminal/screen.rs +++ b/meli/src/terminal/screen.rs @@ -405,6 +405,9 @@ impl Screen { } if !c.empty() { write!(stdout, "{}", c.ch()).unwrap(); + if c.attrs().intersects(Attr::FORCE_TEXT) { + _ = write!(stdout, "\u{FE0E}"); + } } } } @@ -432,6 +435,9 @@ impl Screen { } if !c.empty() { write!(stdout, "{}", c.ch()).unwrap(); + if c.attrs().intersects(Attr::FORCE_TEXT) { + _ = write!(stdout, "\u{FE0E}"); + } } } } diff --git a/melib/src/conf.rs b/melib/src/conf.rs index 9bf44236..53b8b8bc 100644 --- a/melib/src/conf.rs +++ b/melib/src/conf.rs @@ -23,7 +23,7 @@ //! [`backends`](./backends/index.html) use std::{collections::HashMap, path::Path}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use crate::{ backends::SpecialUsageMailbox, @@ -55,8 +55,8 @@ pub struct AccountSettings { impl AccountSettings { /// Create the account's display name from fields /// [`AccountSettings::identity`] and [`AccountSettings::display_name`]. - pub fn make_display_name(&self) -> String { - Address::new(self.display_name.clone(), self.identity.clone()).to_string() + pub fn make_display_name(&self) -> Address { + Address::new(self.display_name.clone(), self.identity.clone()) } pub fn order(&self) -> Option<(SortField, SortOrder)> { @@ -184,117 +184,196 @@ pub const fn none() -> Option { None } -macro_rules! named_unit_variant { - ($variant:ident) => { - pub mod $variant { - pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> serde::de::Visitor<'de> for V { - type Value = (); - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(concat!("\"", stringify!($variant), "\"")) - } - fn visit_str(self, value: &str) -> Result { - if value == stringify!($variant) { - Ok(()) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) +pub use config_field_types::*; + +pub mod config_field_types { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + macro_rules! named_unit_variant { + ($variant:ident) => { + pub mod $variant { + pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = (); + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(concat!("\"", stringify!($variant), "\"")) + } + fn visit_str( + self, + value: &str, + ) -> Result { + 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 for ToggleFlag { + fn from(val: bool) -> Self { + if val { + Self::True + } else { + Self::False } } - }; -} + } -pub mod strings { - named_unit_variant!(ask); -} + impl ToggleFlag { + pub fn is_unset(&self) -> bool { + Self::Unset == *self + } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub enum ToggleFlag { - #[default] - Unset, - InternalVal(bool), - False, - True, - Ask, -} + pub fn is_internal(&self) -> bool { + matches!(self, Self::InternalVal(_)) + } -impl From for ToggleFlag { - fn from(val: bool) -> Self { - if val { - Self::True - } else { - Self::False + 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 ToggleFlag { - pub fn is_unset(&self) -> bool { - Self::Unset == *self + impl Serialize for ToggleFlag { + fn serialize(&self, serializer: S) -> std::result::Result + 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 { - matches!(self, Self::InternalVal(_)) + impl<'de> Deserialize<'de> for ToggleFlag { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum InnerToggleFlag { + Bool(bool), + } + let s = ::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 { - matches!(self, Self::Ask) + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum ActionFlag { + InternalVal(bool), + False, + True, + #[default] + Ask, } - pub fn is_false(&self) -> bool { - matches!(self, Self::False | Self::InternalVal(false)) + impl From for ActionFlag { + fn from(val: bool) -> Self { + if val { + Self::True + } else { + Self::False + } + } } - pub fn is_true(&self) -> bool { - matches!(self, Self::True | Self::InternalVal(true)) + impl ActionFlag { + 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 { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - match self { - Self::Unset | Self::InternalVal(_) => serializer.serialize_none(), - Self::False => serializer.serialize_bool(false), - Self::True => serializer.serialize_bool(true), - Self::Ask => serializer.serialize_str("ask"), + impl Serialize for ActionFlag { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + match self { + Self::InternalVal(_) => serializer.serialize_none(), + Self::False => serializer.serialize_bool(false), + Self::True => serializer.serialize_bool(true), + Self::Ask => serializer.serialize_str("ask"), + } } } -} -impl<'de> Deserialize<'de> for ToggleFlag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum InnerToggleFlag { - Bool(bool), - #[serde(with = "strings::ask")] - Ask, + impl<'de> Deserialize<'de> for ActionFlag { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum InnerActionFlag { + Bool(bool), + #[serde(with = "strings::ask")] + Ask, + } + let s = ::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 = ::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, - }, - ) } } diff --git a/melib/src/jmap/mod.rs b/melib/src/jmap/mod.rs index e8987bf3..a0a1db1d 100644 --- a/melib/src/jmap/mod.rs +++ b/melib/src/jmap/mod.rs @@ -1186,7 +1186,7 @@ impl JmapType { let store = Arc::new(Store { account_name: Arc::new(s.name.clone()), account_hash, - main_identity: s.make_display_name(), + main_identity: s.make_display_name().to_string(), extra_identities: s.extra_identities.clone(), online_status, event_consumer, diff --git a/melib/src/lib.rs b/melib/src/lib.rs index 9dc0e6d6..25541343 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -68,7 +68,6 @@ * Global tags (in tagref format ) for source code * annotation: * - * - [tag:hardcoded_color_value] Replace hardcoded color values with user configurable ones. * - [tag:needs_unit_test] * - [tag:needs_user_doc] * - [tag:needs_dev_doc]