use crate::ask::{Ask, PatternOp};
use crate::{ComposeKind, Session, ThreadOp};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Function {
Quit,
Abort,
Down,
Up,
PageDown,
PageUp,
First,
Last,
View,
Delete,
Undelete,
Flag,
ToggleNew,
MarkAllRead,
Sync,
Compose,
Reply,
GroupReply,
ListReply,
Forward,
Sort,
Limit,
Search,
SearchReverse,
SearchNext,
NextNew,
PrevNew,
ChangeMailbox,
ChangeMailboxReadOnly,
Folders,
Attachments,
FoldThread,
FoldAll,
Print,
Tag,
TagPrefix,
DeleteThread,
UndeleteThread,
TagThread,
DeleteSubthread,
UndeleteSubthread,
NextThread,
PrevThread,
BreakThread,
LinkThreads,
ReadThread,
ReadSubthread,
TagSubthread,
ParentMessage,
RootMessage,
EditLabel,
ShowVersion,
ShowLimit,
ToggleWrite,
DisplayAddress,
PageTop,
PageMiddle,
PageBottom,
Undo,
DeletePattern,
UndeletePattern,
TagPattern,
UntagPattern,
FetchMail,
Save,
Copy,
DecodeSave,
DecodeCopy,
Pipe,
Bounce,
Resend,
Edit,
SidebarToggle,
SidebarNext,
SidebarPrev,
SidebarOpen,
CreateAlias,
Query,
Notmuch,
EnterCommand,
Shell,
Redraw,
Suspend,
Help,
NextUnreadMailbox,
PurgeMessage,
MarkMessage,
ErrorHistory,
WhatKey,
ListAction,
}
impl Function {
pub fn name(self) -> &'static str {
use Function::*;
match self {
Quit => "quit",
Abort => "abort",
Down => "down",
Up => "up",
PageDown => "page-down",
PageUp => "page-up",
First => "first",
Last => "last",
View => "view",
Delete => "delete",
Undelete => "undelete",
Flag => "flag",
ToggleNew => "toggle-new",
MarkAllRead => "mark-all-read",
Sync => "sync",
Compose => "compose",
Reply => "reply",
GroupReply => "group-reply",
ListReply => "list-reply",
Forward => "forward",
Sort => "sort",
Limit => "limit",
Search => "search",
SearchReverse => "search-reverse",
SearchNext => "search-next",
NextNew => "next-new",
PrevNew => "previous-new",
ChangeMailbox => "change-mailbox",
ChangeMailboxReadOnly => "change-mailbox-readonly",
Folders => "folders",
Attachments => "attachments",
FoldThread => "fold-thread",
FoldAll => "fold-all",
Print => "print",
Tag => "tag",
TagPrefix => "tag-prefix",
DeleteThread => "delete-thread",
UndeleteThread => "undelete-thread",
TagThread => "tag-thread",
DeleteSubthread => "delete-subthread",
UndeleteSubthread => "undelete-subthread",
NextThread => "next-thread",
BreakThread => "break-thread",
LinkThreads => "link-threads",
ReadThread => "read-thread",
ReadSubthread => "read-subthread",
TagSubthread => "tag-subthread",
ParentMessage => "parent-message",
RootMessage => "root-message",
EditLabel => "edit-label",
ShowVersion => "show-version",
ShowLimit => "show-limit",
ToggleWrite => "toggle-write",
DisplayAddress => "display-address",
PageTop => "top-page",
PageMiddle => "middle-page",
PageBottom => "bottom-page",
PrevThread => "previous-thread",
Undo => "undo",
DeletePattern => "delete-pattern",
UndeletePattern => "undelete-pattern",
TagPattern => "tag-pattern",
UntagPattern => "untag-pattern",
FetchMail => "fetch-mail",
Save => "save",
Copy => "copy",
DecodeSave => "decode-save",
DecodeCopy => "decode-copy",
Pipe => "pipe",
Bounce => "bounce",
Resend => "resend",
Edit => "edit",
SidebarToggle => "sidebar-toggle",
SidebarNext => "sidebar-next",
SidebarPrev => "sidebar-prev",
SidebarOpen => "sidebar-open",
CreateAlias => "create-alias",
Query => "query",
Notmuch => "notmuch",
EnterCommand => "enter-command",
Shell => "shell-escape",
Redraw => "refresh",
Suspend => "suspend",
Help => "help",
NextUnreadMailbox => "next-unread-mailbox",
PurgeMessage => "purge-message",
MarkMessage => "mark-message",
ErrorHistory => "error-history",
WhatKey => "what-key",
ListAction => "list-action",
}
}
pub fn describe(self) -> &'static str {
use Function::*;
match self {
Quit => "quit (writes changes; asks before purging deletions)",
Abort => "quit without saving changes",
Down => "next message",
Up => "previous message",
PageDown => "page down",
PageUp => "page up",
First => "first message",
Last => "last message",
View => "view message",
Delete => "mark for deletion",
Undelete => "unmark deletion",
Flag => "toggle flagged mark",
ToggleNew => "toggle read/unread",
MarkAllRead => "mark every message in the mailbox read",
Sync => "write changes to the maildir",
Compose => "compose a new message",
Reply => "reply to sender",
GroupReply => "reply to all",
ListReply => "reply to the mailing list only",
Forward => "forward message",
Sort => "choose sort order",
Limit => "limit index by pattern",
Search => "search messages by pattern (the pager / searches its text)",
SearchReverse => "search backwards; n then repeats backwards too",
SearchNext => "repeat last search, the way it was going",
NextNew => "jump to the next new or unread message",
PrevNew => "jump to the previous new or unread message",
ChangeMailbox => "open a mailbox by path",
ChangeMailboxReadOnly => "open a mailbox read-only (Alt+c)",
Folders => "browse nearby mailboxes",
Attachments => "list message parts",
FoldThread => "fold/unfold current thread",
FoldAll => "fold/unfold all threads",
Print => "pipe message to the print command",
Tag => "toggle the tag on this message",
TagPrefix => "apply the next function to tagged messages",
DeleteThread => "mark the whole thread for deletion",
UndeleteThread => "unmark the whole thread",
TagThread => "tag/untag the whole thread",
DeleteSubthread => "mark this message and its replies for deletion",
UndeleteSubthread => "unmark this message and its replies",
NextThread => "jump to the next thread",
BreakThread => "break the thread in two at this message",
LinkThreads => "link the tagged messages under this one",
ReadThread => "mark the whole thread read",
ReadSubthread => "mark this message and its replies read",
TagSubthread => "tag this message and its replies",
ParentMessage => "jump to the parent message",
RootMessage => "jump to the thread's root message",
EditLabel => "add, change or clear the X-Label",
ShowVersion => "show the rmut version",
ShowLimit => "show the active limit pattern",
ToggleWrite => "toggle the mailbox's read-only state",
DisplayAddress => "show the sender's full address",
PageTop => "move to the top of the page",
PageMiddle => "move to the middle of the page",
PageBottom => "move to the bottom of the page",
PrevThread => "jump to the previous thread",
Undo => "cancel a held send, else undo the last delete/flag/tag/save",
DeletePattern => "delete every message matching a pattern",
UndeletePattern => "undelete every message matching a pattern",
TagPattern => "tag every message matching a pattern",
UntagPattern => "untag every message matching a pattern",
FetchMail => "check for new mail now",
Save => "save (copy + mark deleted) to a mailbox",
DecodeSave => "decode-save: the decoded message, original deleted",
DecodeCopy => "decode-copy: the decoded message",
Copy => "copy to a mailbox (original stays)",
Pipe => "pipe raw message to a shell command",
Bounce => "bounce (resend) message to new recipients",
Resend => "edit the message as a new draft",
Edit => "edit the raw message and replace it",
SidebarToggle => "show/hide the mailbox sidebar",
SidebarNext => "highlight the next sidebar mailbox",
SidebarPrev => "highlight the previous sidebar mailbox",
SidebarOpen => "open the highlighted sidebar mailbox",
CreateAlias => "add the sender to the alias file",
Query => "look up addresses with query_command",
Notmuch => "notmuch search into a read-only view",
EnterCommand => "run a config command (set/bind/macro/color/...)",
Shell => "run a shell command",
Redraw => "repaint the screen",
Suspend => "suspend rmut (fg brings it back)",
Help => "this help",
NextUnreadMailbox => "open the next mailbox holding new mail",
PurgeMessage => "mark for deletion, bypassing the trash",
MarkMessage => "bind a key that jumps back to this message",
ErrorHistory => "show the recent errors",
WhatKey => "say what a key is (Ctrl+G ends it)",
ListAction => "act on the message's List-* headers (subscribe, help, ...)",
}
}
pub fn all() -> &'static [Function] {
use Function::*;
&[
Quit,
Abort,
Down,
Up,
PageDown,
PageUp,
First,
Last,
View,
Delete,
Undelete,
Flag,
ToggleNew,
MarkAllRead,
Sync,
Compose,
Reply,
GroupReply,
ListReply,
Forward,
Sort,
Limit,
Search,
SearchReverse,
SearchNext,
NextNew,
PrevNew,
ChangeMailbox,
ChangeMailboxReadOnly,
Folders,
Attachments,
FoldThread,
FoldAll,
Print,
Tag,
TagPrefix,
DeleteThread,
UndeleteThread,
TagThread,
DeleteSubthread,
UndeleteSubthread,
NextThread,
PrevThread,
BreakThread,
LinkThreads,
ReadThread,
ReadSubthread,
TagSubthread,
ParentMessage,
RootMessage,
EditLabel,
ShowVersion,
ShowLimit,
ToggleWrite,
DisplayAddress,
PageTop,
PageMiddle,
PageBottom,
Undo,
DeletePattern,
UndeletePattern,
TagPattern,
UntagPattern,
FetchMail,
Save,
Copy,
DecodeSave,
DecodeCopy,
Pipe,
Bounce,
Resend,
Edit,
SidebarToggle,
SidebarNext,
SidebarPrev,
SidebarOpen,
CreateAlias,
Query,
Notmuch,
EnterCommand,
Shell,
Redraw,
Suspend,
Help,
NextUnreadMailbox,
PurgeMessage,
MarkMessage,
ErrorHistory,
WhatKey,
ListAction,
]
}
pub fn from_name(name: &str) -> Option<Function> {
Function::all().iter().copied().find(|a| a.name() == name)
}
pub fn takes_tagged(self) -> bool {
use Function::*;
matches!(
self,
Delete
| Undelete
| Flag
| ToggleNew
| Tag
| Save
| Copy
| DecodeSave
| DecodeCopy
| Pipe
| Print
| Bounce
| EditLabel
)
}
}
pub enum Outcome {
Done,
Ask(Ask),
Front(FrontOp),
}
impl From<Option<Ask>> for Outcome {
fn from(ask: Option<Ask>) -> Outcome {
match ask {
Some(ask) => Outcome::Ask(ask),
None => Outcome::Done,
}
}
}
pub enum FrontOp {
Exit,
OpenMailbox(String),
ErrorHistory(Vec<String>),
WhatKey,
OpenSelected,
Compose(ComposeKind),
Resend,
RawEdit,
Attachments,
Folders,
Query,
Notmuch,
ChangeMailbox { read_only: bool },
CommandPrompt,
TagPrefix,
Sidebar(SidebarOp),
PageMove(PageSpot),
Help,
Redraw,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SidebarOp {
Toggle,
Next,
Prev,
Open,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PageSpot {
Top,
Middle,
Bottom,
}
impl Session {
pub fn run_function(&mut self, function: Function, tagged: bool, page: usize) -> Outcome {
use Function::*;
match function {
Down => self.select(self.sel.saturating_add(1)),
Up => self.select(self.sel.saturating_sub(1)),
PageDown => self.select(self.sel.saturating_add(page)),
PageUp => self.select(self.sel.saturating_sub(page)),
First => self.select(0),
Last => self.select(usize::MAX),
NextNew => self.jump_new(true),
PrevNew => self.jump_new(false),
NextThread => self.jump_thread(true),
PrevThread => self.jump_thread(false),
ParentMessage => self.jump_parent(false),
RootMessage => self.jump_parent(true),
SearchNext => self.search_next(),
Tag => {
if tagged {
let targets: Vec<usize> = self
.visible
.iter()
.copied()
.filter(|&i| self.msgs[i].env.tagged)
.collect();
if !targets.is_empty() {
self.push_undo("untag", &targets);
for &i in &targets {
self.msgs[i].env.tagged = false;
}
}
} else if let Some(&i) = self.visible.get(self.sel) {
self.push_undo("tag", &[i]);
self.msgs[i].env.tagged = !self.msgs[i].env.tagged;
self.select(self.sel.saturating_add(1));
}
}
Delete => {
let rules = self.delete_rules();
self.mark_selected(tagged, "delete", move |m| {
rules.mark(m);
})
}
Undelete => self.mark_selected(tagged, "undelete", |m| {
m.env.file.flags.deleted = false;
m.purge = false;
}),
PurgeMessage => {
let rules = self.delete_rules();
self.mark_selected(tagged, "purge", move |m| {
if rules.mark(m) {
m.purge = true;
}
})
}
NextUnreadMailbox => match self.next_unread_mailbox() {
Some(spec) => return Outcome::Front(FrontOp::OpenMailbox(spec)),
None => self.error("No mailboxes have new mail"),
},
MarkMessage => return self.ask_mark_message().into(),
ErrorHistory => {
if self.config.ui.error_history == 0 {
self.error("Error History is disabled.");
} else {
return Outcome::Front(FrontOp::ErrorHistory(self.error_history()));
}
}
WhatKey => return Outcome::Front(FrontOp::WhatKey),
ListAction => return self.ask_list_action().into(),
Flag => self.mark_selected(tagged, "flag", |m| {
m.env.file.flags.flagged = !m.env.file.flags.flagged
}),
ToggleNew => self.mark_selected(tagged, "toggle read", |m| {
m.env.file.flags.seen = !m.env.file.flags.seen;
m.env.file.is_new = false;
}),
MarkAllRead => self.mark_all_read(),
Undo => {
if !self.cancel_send() {
self.undo_last();
}
}
DeleteThread => self.thread_mark(false, ThreadOp::Delete),
UndeleteThread => self.thread_mark(false, ThreadOp::Undelete),
TagThread => self.thread_mark(false, ThreadOp::Tag),
ReadThread => self.thread_mark(false, ThreadOp::Read),
DeleteSubthread => self.thread_mark(true, ThreadOp::Delete),
UndeleteSubthread => self.thread_mark(true, ThreadOp::Undelete),
TagSubthread => self.thread_mark(true, ThreadOp::Tag),
ReadSubthread => self.thread_mark(true, ThreadOp::Read),
BreakThread => self.break_thread(),
LinkThreads => self.link_threads(),
FoldThread => self.toggle_collapse(false),
FoldAll => self.toggle_collapse(true),
Sync => {
if self.deleted_count() > 0 {
return self.ask_purge(false).into();
}
self.sync(true);
}
Quit => return self.leave().into(),
FetchMail => {
self.check_new_mail();
if self.notice().is_none() {
self.note("checked for new mail");
}
}
ToggleWrite => self.toggle_write(),
ShowLimit => self.show_limit(),
ShowVersion => self.note(concat!("rmut ", env!("CARGO_PKG_VERSION"))),
DisplayAddress => {
let from = self
.visible
.get(self.sel)
.map(|&i| self.msgs[i].env.from_full.clone());
match from {
Some(from) if !from.trim().is_empty() => self.note(from),
_ => self.note("(no From address)"),
}
}
Limit => return Outcome::Ask(self.ask_limit()),
Search => return Outcome::Ask(self.ask_search(false)),
SearchReverse => return Outcome::Ask(self.ask_search(true)),
Sort => return Outcome::Ask(self.ask_sort()),
Shell => return Outcome::Ask(self.ask_shell()),
DeletePattern => return self.ask_pattern(PatternOp::Delete).into(),
UndeletePattern => return self.ask_pattern(PatternOp::Undelete).into(),
TagPattern => return self.ask_pattern(PatternOp::Tag).into(),
UntagPattern => return self.ask_pattern(PatternOp::Untag).into(),
Save => return self.ask_copy(true, tagged).into(),
Copy => return self.ask_copy(false, tagged).into(),
DecodeSave => return self.ask_copy_decode(true, tagged, true).into(),
DecodeCopy => return self.ask_copy_decode(false, tagged, true).into(),
Pipe => return self.ask_pipe(tagged).into(),
Bounce => return self.ask_bounce(tagged).into(),
Print => return self.ask_print(tagged).into(),
EditLabel => return self.ask_edit_label(tagged).into(),
CreateAlias => return self.ask_alias().into(),
ListReply => return self.start_list_reply().into(),
Suspend => self.request_suspend(),
Abort => return Outcome::Front(FrontOp::Exit),
View => return Outcome::Front(FrontOp::OpenSelected),
Compose => return Outcome::Front(FrontOp::Compose(ComposeKind::New)),
Reply => return Outcome::Front(FrontOp::Compose(ComposeKind::Reply)),
GroupReply => return Outcome::Front(FrontOp::Compose(ComposeKind::GroupReply)),
Forward => return Outcome::Front(FrontOp::Compose(ComposeKind::Forward)),
Resend => return Outcome::Front(FrontOp::Resend),
Edit => return Outcome::Front(FrontOp::RawEdit),
Attachments => return Outcome::Front(FrontOp::Attachments),
Folders => return Outcome::Front(FrontOp::Folders),
EnterCommand => return Outcome::Front(FrontOp::CommandPrompt),
Help => return Outcome::Front(FrontOp::Help),
Redraw => return Outcome::Front(FrontOp::Redraw),
PageTop => return Outcome::Front(FrontOp::PageMove(PageSpot::Top)),
PageMiddle => return Outcome::Front(FrontOp::PageMove(PageSpot::Middle)),
PageBottom => return Outcome::Front(FrontOp::PageMove(PageSpot::Bottom)),
SidebarToggle => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Toggle)),
SidebarNext => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Next)),
SidebarPrev => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Prev)),
SidebarOpen => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Open)),
TagPrefix => {
if !self.msgs.iter().any(|m| m.env.tagged) {
self.note("no tagged messages");
return Outcome::Done;
}
self.note("Tag-");
return Outcome::Front(FrontOp::TagPrefix);
}
Query => {
if self.config.mail.query_command.is_none() {
self.error("no query_command configured");
return Outcome::Done;
}
return Outcome::Front(FrontOp::Query);
}
Notmuch => {
if self.config.mail.notmuch == Some(false) {
self.error("notmuch is disabled in the config");
return Outcome::Done;
}
return Outcome::Front(FrontOp::Notmuch);
}
ChangeMailbox | ChangeMailboxReadOnly => {
if !self.ready_to_leave() {
return Outcome::Done;
}
return Outcome::Front(FrontOp::ChangeMailbox {
read_only: function == ChangeMailboxReadOnly,
});
}
}
Outcome::Done
}
fn mark_selected(&mut self, tagged: bool, what: &'static str, f: impl Fn(&mut crate::Msg)) {
if self.deny_readonly() {
return;
}
if tagged {
self.each_tagged(what, f);
} else if let Some(&i) = self.visible.get(self.sel) {
self.push_undo(what, &[i]);
f(&mut self.msgs[i]);
self.msgs[i].dirty = true;
self.select(self.sel.saturating_add(1));
}
}
}