diff --git a/meli/docs/meli.conf.5 b/meli/docs/meli.conf.5 index 019c4d32..25ad29cc 100644 --- a/meli/docs/meli.conf.5 +++ b/meli/docs/meli.conf.5 @@ -217,6 +217,11 @@ Default values are shown in parentheses. The backend-specific path of the root_mailbox, usually .Sy INBOX Ns \&. +.It Ic default_mailbox Ar String +.Pq Em optional +The mailbox that is the default to open or view for this account. +Must be a valid mailbox path. +If not specified, the default will be the root mailbox. .It Ic format Ar String Op maildir mbox imap notmuch jmap The format of the mail backend. .It Ic subscribed_mailboxes Ar [String,] diff --git a/meli/src/accounts.rs b/meli/src/accounts.rs index a64cbb74..ec67bef8 100644 --- a/meli/src/accounts.rs +++ b/meli/src/accounts.rs @@ -350,9 +350,19 @@ impl Account { .keys() .cloned() .collect::>(); + let mut default_mailbox = self + .settings + .conf + .default_mailbox + .clone() + .into_iter() + .collect::>(); for f in ref_mailboxes.values_mut() { if let Some(conf) = self.settings.mailbox_confs.get_mut(f.path()) { mailbox_conf_hash_set.remove(f.path()); + if default_mailbox.remove(f.path()) { + self.settings.default_mailbox = Some(f.hash()); + } conf.mailbox_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal { Some(f.special_usage()) } else { @@ -447,6 +457,23 @@ impl Account { ))); } + match self.settings.conf.default_mailbox { + Some(ref v) if !default_mailbox.is_empty() => { + let err = Error::new(format!( + "Account `{}` has default mailbox set as `{}` but it doesn't exist.", + &self.name, v + )) + .set_kind(ErrorKind::Configuration); + self.is_online.set_err(err.clone()); + self.main_loop_handler + .send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange( + self.hash, None, + ))); + return Err(err); + } + _ => {} + } + let mut tree: Vec = Vec::new(); for (h, f) in ref_mailboxes.iter() { if !f.is_subscribed() { @@ -1394,6 +1421,12 @@ impl Account { } } + pub fn default_mailbox(&self) -> Option { + self.settings + .default_mailbox + .or_else(|| Some(*self.mailboxes_order.first()?)) + } + pub fn mailbox_by_path(&self, path: &str) -> Result { if let Some((mailbox_hash, _)) = self .mailbox_entries diff --git a/meli/src/conf.rs b/meli/src/conf.rs index c906f5cf..2c4a9e16 100644 --- a/meli/src/conf.rs +++ b/meli/src/conf.rs @@ -165,6 +165,12 @@ use crate::conf::deserializers::extra_settings; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct FileAccount { pub root_mailbox: String, + /// The mailbox that is the default to open / view for this account. Must be + /// a valid mailbox path. + /// + /// If not specified, the default is [`Self::root_mailbox`]. + #[serde(default = "none", skip_serializing_if = "Option::is_none")] + pub default_mailbox: Option, pub format: String, pub identity: String, #[serde(default)] @@ -234,6 +240,7 @@ pub struct FileSettings { #[derive(Clone, Debug, Default, Serialize)] pub struct AccountConf { pub account: AccountSettings, + pub default_mailbox: Option, pub sent_mailbox: Option, pub conf: FileAccount, pub conf_override: MailUIConf, @@ -286,6 +293,7 @@ impl From for AccountConf { let mailbox_confs = x.mailboxes.clone(); Self { account: acc, + default_mailbox: None, sent_mailbox: None, conf_override: x.conf_override.clone(), conf: x, @@ -538,6 +546,7 @@ This is required so that you don't accidentally start meli and find out later th mailboxes, extra, manual_refresh, + default_mailbox: _, refresh_command: _, search_backend: _, conf_override: _, diff --git a/meli/src/mail/listing.rs b/meli/src/mail/listing.rs index 8595f0bf..eb3f9713 100644 --- a/meli/src/mail/listing.rs +++ b/meli/src/mail/listing.rs @@ -475,6 +475,18 @@ struct AccountMenuEntry { entries: SmallVec<[MailboxMenuEntry; 16]>, } +impl AccountMenuEntry { + fn entry_by_hash(&self, needle: MailboxHash) -> Option { + self.entries.iter().enumerate().find_map(|(i, e)| { + if e.mailbox_hash == needle { + Some(i) + } else { + None + } + }) + } +} + pub trait MailListingTrait: ListingTrait { fn as_component(&self) -> &dyn Component where @@ -1568,7 +1580,15 @@ impl Component for Listing { k if shortcut!(k == shortcuts[Shortcuts::LISTING]["next_account"]) => { if self.cursor_pos.account + amount < self.accounts.len() { self.cursor_pos.account += amount; - self.cursor_pos.menu = MenuEntryCursor::Mailbox(0); + let _new_val = self.cursor_pos.account; + self.cursor_pos.menu = if let Some(idx) = context.accounts[_new_val] + .default_mailbox() + .and_then(|h| self.accounts[_new_val].entry_by_hash(h)) + { + MenuEntryCursor::Mailbox(idx) + } else { + MenuEntryCursor::Status + }; } else { return true; } @@ -1576,7 +1596,15 @@ impl Component for Listing { k if shortcut!(k == shortcuts[Shortcuts::LISTING]["prev_account"]) => { if self.cursor_pos.account >= amount { self.cursor_pos.account -= amount; - self.cursor_pos.menu = MenuEntryCursor::Mailbox(0); + let _new_val = self.cursor_pos.account; + self.cursor_pos.menu = if let Some(idx) = context.accounts[_new_val] + .default_mailbox() + .and_then(|h| self.accounts[_new_val].entry_by_hash(h)) + { + MenuEntryCursor::Mailbox(idx) + } else { + MenuEntryCursor::Status + }; } else { return true; } @@ -2076,9 +2104,17 @@ impl Component for Listing { } => { if *account > 0 { *account -= 1; - self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox( - self.accounts[*account].entries.len().saturating_sub(1), - ); + self.menu_cursor_pos.menu = + if self.accounts[*account].entries.is_empty() { + MenuEntryCursor::Status + } else { + MenuEntryCursor::Mailbox( + self.accounts[*account] + .entries + .len() + .saturating_sub(1), + ) + }; } else { return true; } @@ -2111,7 +2147,12 @@ impl Component for Listing { } if !self.accounts[*account].entries.is_empty() && *menu == MenuEntryCursor::Status => { - *menu = MenuEntryCursor::Mailbox(0); + if let Some(idx) = context.accounts[*account] + .default_mailbox() + .and_then(|h| self.accounts[*account].entry_by_hash(h)) + { + *menu = MenuEntryCursor::Mailbox(idx); + } } // If current account has no mailboxes, go to next account CursorPos { @@ -2250,15 +2291,32 @@ impl Component for Listing { { if self.menu_cursor_pos.account + amount >= self.accounts.len() { // Go to last mailbox. - self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox( - self.accounts[self.menu_cursor_pos.account] - .entries - .len() - .saturating_sub(1), - ); + self.menu_cursor_pos.menu = if self.accounts + [self.menu_cursor_pos.account] + .entries + .is_empty() + { + MenuEntryCursor::Status + } else { + MenuEntryCursor::Mailbox( + self.accounts[self.menu_cursor_pos.account] + .entries + .len() + .saturating_sub(1), + ) + }; } else if self.menu_cursor_pos.account + amount < self.accounts.len() { self.menu_cursor_pos.account += amount; - self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox(0); + let _new_val = self.menu_cursor_pos.account; + self.menu_cursor_pos.menu = if let Some(idx) = context.accounts + [_new_val] + .default_mailbox() + .and_then(|h| self.accounts[_new_val].entry_by_hash(h)) + { + MenuEntryCursor::Mailbox(idx) + } else { + MenuEntryCursor::Status + }; } else { return true; } @@ -2268,7 +2326,16 @@ impl Component for Listing { { if self.menu_cursor_pos.account >= amount { self.menu_cursor_pos.account -= amount; - self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox(0); + let _new_val = self.menu_cursor_pos.account; + self.menu_cursor_pos.menu = if let Some(idx) = context.accounts + [_new_val] + .default_mailbox() + .and_then(|h| self.accounts[_new_val].entry_by_hash(h)) + { + MenuEntryCursor::Mailbox(idx) + } else { + MenuEntryCursor::Status + }; } else { return true; } @@ -2294,12 +2361,24 @@ impl Component for Listing { menu: MenuEntryCursor::Mailbox(0) } ) { + // Can't go anywhere upwards, we're on top already. return true; } - if self.menu_cursor_pos.menu == MenuEntryCursor::Mailbox(0) { - self.menu_cursor_pos.account = 0; - } else { - self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox(0); + match ( + self.menu_cursor_pos.menu, + context.accounts[self.menu_cursor_pos.account] + .default_mailbox() + .and_then(|h| { + self.accounts[self.menu_cursor_pos.account].entry_by_hash(h) + }), + ) { + (MenuEntryCursor::Mailbox(0), _) => { + self.menu_cursor_pos.account = 0; + } + (MenuEntryCursor::Mailbox(_), Some(v)) => { + self.menu_cursor_pos.menu = MenuEntryCursor::Mailbox(v); + } + _ => return true, } if self.show_menu_scrollbar != ShowMenuScrollbar::Never { self.menu_scrollbar_show_timer.rearm(); @@ -2337,11 +2416,20 @@ impl Component for Listing { self.accounts[*account].entries.len().saturating_sub(1) ) { *account = self.accounts.len().saturating_sub(1); - *menu = MenuEntryCursor::Mailbox(0); - } else { + *menu = if let Some(idx) = context.accounts[*account] + .default_mailbox() + .and_then(|h| self.accounts[*account].entry_by_hash(h)) + { + MenuEntryCursor::Mailbox(idx) + } else { + MenuEntryCursor::Status + }; + } else if !self.accounts[*account].entries.is_empty() { *menu = MenuEntryCursor::Mailbox( self.accounts[*account].entries.len().saturating_sub(1), ); + } else { + *menu = MenuEntryCursor::Status; } if self.show_menu_scrollbar != ShowMenuScrollbar::Never { self.menu_scrollbar_show_timer.rearm();