1use crate::ask::{Ask, PatternOp};
19use crate::{ComposeKind, Session, ThreadOp};
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
25pub enum Function {
26 Quit,
27 Abort,
28 Down,
29 Up,
30 PageDown,
31 PageUp,
32 First,
33 Last,
34 View,
35 Delete,
36 Undelete,
37 Flag,
38 ToggleNew,
39 MarkAllRead,
42 Sync,
43 Compose,
44 Reply,
45 GroupReply,
46 ListReply,
47 Forward,
48 Sort,
49 Limit,
50 Search,
51 SearchReverse,
52 SearchNext,
53 NextNew,
54 PrevNew,
55 ChangeMailbox,
56 ChangeMailboxReadOnly,
57 Folders,
58 Attachments,
59 FoldThread,
60 FoldAll,
61 Print,
62 Tag,
63 TagPrefix,
64 DeleteThread,
65 UndeleteThread,
66 TagThread,
67 DeleteSubthread,
68 UndeleteSubthread,
69 NextThread,
70 PrevThread,
71 BreakThread,
72 LinkThreads,
73 ReadThread,
74 ReadSubthread,
75 TagSubthread,
76 ParentMessage,
77 RootMessage,
78 EditLabel,
79 ShowVersion,
80 ShowLimit,
81 ToggleWrite,
82 DisplayAddress,
83 PageTop,
84 PageMiddle,
85 PageBottom,
86 Undo,
87 DeletePattern,
88 UndeletePattern,
89 TagPattern,
90 UntagPattern,
91 FetchMail,
92 Save,
93 Copy,
94 DecodeSave,
95 DecodeCopy,
96 Pipe,
97 Bounce,
98 Resend,
99 Edit,
100 SidebarToggle,
101 SidebarNext,
102 SidebarPrev,
103 SidebarOpen,
104 CreateAlias,
105 Query,
106 Notmuch,
107 EnterCommand,
108 Shell,
109 Redraw,
110 Suspend,
111 Help,
112 NextUnreadMailbox,
115 PurgeMessage,
117 MarkMessage,
119 ErrorHistory,
121 WhatKey,
123 ListAction,
125}
126
127impl Function {
128 pub fn name(self) -> &'static str {
129 use Function::*;
130 match self {
131 Quit => "quit",
132 Abort => "abort",
133 Down => "down",
134 Up => "up",
135 PageDown => "page-down",
136 PageUp => "page-up",
137 First => "first",
138 Last => "last",
139 View => "view",
140 Delete => "delete",
141 Undelete => "undelete",
142 Flag => "flag",
143 ToggleNew => "toggle-new",
144 MarkAllRead => "mark-all-read",
145 Sync => "sync",
146 Compose => "compose",
147 Reply => "reply",
148 GroupReply => "group-reply",
149 ListReply => "list-reply",
150 Forward => "forward",
151 Sort => "sort",
152 Limit => "limit",
153 Search => "search",
154 SearchReverse => "search-reverse",
155 SearchNext => "search-next",
156 NextNew => "next-new",
157 PrevNew => "previous-new",
158 ChangeMailbox => "change-mailbox",
159 ChangeMailboxReadOnly => "change-mailbox-readonly",
160 Folders => "folders",
161 Attachments => "attachments",
162 FoldThread => "fold-thread",
163 FoldAll => "fold-all",
164 Print => "print",
165 Tag => "tag",
166 TagPrefix => "tag-prefix",
167 DeleteThread => "delete-thread",
168 UndeleteThread => "undelete-thread",
169 TagThread => "tag-thread",
170 DeleteSubthread => "delete-subthread",
171 UndeleteSubthread => "undelete-subthread",
172 NextThread => "next-thread",
173 BreakThread => "break-thread",
174 LinkThreads => "link-threads",
175 ReadThread => "read-thread",
176 ReadSubthread => "read-subthread",
177 TagSubthread => "tag-subthread",
178 ParentMessage => "parent-message",
179 RootMessage => "root-message",
180 EditLabel => "edit-label",
181 ShowVersion => "show-version",
182 ShowLimit => "show-limit",
183 ToggleWrite => "toggle-write",
184 DisplayAddress => "display-address",
185 PageTop => "top-page",
186 PageMiddle => "middle-page",
187 PageBottom => "bottom-page",
188 PrevThread => "previous-thread",
189 Undo => "undo",
190 DeletePattern => "delete-pattern",
191 UndeletePattern => "undelete-pattern",
192 TagPattern => "tag-pattern",
193 UntagPattern => "untag-pattern",
194 FetchMail => "fetch-mail",
195 Save => "save",
196 Copy => "copy",
197 DecodeSave => "decode-save",
198 DecodeCopy => "decode-copy",
199 Pipe => "pipe",
200 Bounce => "bounce",
201 Resend => "resend",
202 Edit => "edit",
203 SidebarToggle => "sidebar-toggle",
204 SidebarNext => "sidebar-next",
205 SidebarPrev => "sidebar-prev",
206 SidebarOpen => "sidebar-open",
207 CreateAlias => "create-alias",
208 Query => "query",
209 Notmuch => "notmuch",
210 EnterCommand => "enter-command",
211 Shell => "shell-escape",
212 Redraw => "refresh",
213 Suspend => "suspend",
214 Help => "help",
215 NextUnreadMailbox => "next-unread-mailbox",
216 PurgeMessage => "purge-message",
217 MarkMessage => "mark-message",
218 ErrorHistory => "error-history",
219 WhatKey => "what-key",
220 ListAction => "list-action",
221 }
222 }
223
224 pub fn describe(self) -> &'static str {
225 use Function::*;
226 match self {
227 Quit => "quit (writes changes; asks before purging deletions)",
228 Abort => "quit without saving changes",
229 Down => "next message",
230 Up => "previous message",
231 PageDown => "page down",
232 PageUp => "page up",
233 First => "first message",
234 Last => "last message",
235 View => "view message",
236 Delete => "mark for deletion",
237 Undelete => "unmark deletion",
238 Flag => "toggle flagged mark",
239 ToggleNew => "toggle read/unread",
240 MarkAllRead => "mark every message in the mailbox read",
241 Sync => "write changes to the maildir",
242 Compose => "compose a new message",
243 Reply => "reply to sender",
244 GroupReply => "reply to all",
245 ListReply => "reply to the mailing list only",
246 Forward => "forward message",
247 Sort => "choose sort order",
248 Limit => "limit index by pattern",
249 Search => "search messages by pattern (the pager / searches its text)",
250 SearchReverse => "search backwards; n then repeats backwards too",
251 SearchNext => "repeat last search, the way it was going",
252 NextNew => "jump to the next new or unread message",
253 PrevNew => "jump to the previous new or unread message",
254 ChangeMailbox => "open a mailbox by path",
255 ChangeMailboxReadOnly => "open a mailbox read-only (Alt+c)",
256 Folders => "browse nearby mailboxes",
257 Attachments => "list message parts",
258 FoldThread => "fold/unfold current thread",
259 FoldAll => "fold/unfold all threads",
260 Print => "pipe message to the print command",
261 Tag => "toggle the tag on this message",
262 TagPrefix => "apply the next function to tagged messages",
263 DeleteThread => "mark the whole thread for deletion",
264 UndeleteThread => "unmark the whole thread",
265 TagThread => "tag/untag the whole thread",
266 DeleteSubthread => "mark this message and its replies for deletion",
267 UndeleteSubthread => "unmark this message and its replies",
268 NextThread => "jump to the next thread",
269 BreakThread => "break the thread in two at this message",
270 LinkThreads => "link the tagged messages under this one",
271 ReadThread => "mark the whole thread read",
272 ReadSubthread => "mark this message and its replies read",
273 TagSubthread => "tag this message and its replies",
274 ParentMessage => "jump to the parent message",
275 RootMessage => "jump to the thread's root message",
276 EditLabel => "add, change or clear the X-Label",
277 ShowVersion => "show the rmut version",
278 ShowLimit => "show the active limit pattern",
279 ToggleWrite => "toggle the mailbox's read-only state",
280 DisplayAddress => "show the sender's full address",
281 PageTop => "move to the top of the page",
282 PageMiddle => "move to the middle of the page",
283 PageBottom => "move to the bottom of the page",
284 PrevThread => "jump to the previous thread",
285 Undo => "cancel a held send, else undo the last delete/flag/tag/save",
286 DeletePattern => "delete every message matching a pattern",
287 UndeletePattern => "undelete every message matching a pattern",
288 TagPattern => "tag every message matching a pattern",
289 UntagPattern => "untag every message matching a pattern",
290 FetchMail => "check for new mail now",
291 Save => "save (copy + mark deleted) to a mailbox",
292 DecodeSave => "decode-save: the decoded message, original deleted",
293 DecodeCopy => "decode-copy: the decoded message",
294 Copy => "copy to a mailbox (original stays)",
295 Pipe => "pipe raw message to a shell command",
296 Bounce => "bounce (resend) message to new recipients",
297 Resend => "edit the message as a new draft",
298 Edit => "edit the raw message and replace it",
299 SidebarToggle => "show/hide the mailbox sidebar",
300 SidebarNext => "highlight the next sidebar mailbox",
301 SidebarPrev => "highlight the previous sidebar mailbox",
302 SidebarOpen => "open the highlighted sidebar mailbox",
303 CreateAlias => "add the sender to the alias file",
304 Query => "look up addresses with query_command",
305 Notmuch => "notmuch search into a read-only view",
306 EnterCommand => "run a config command (set/bind/macro/color/...)",
307 Shell => "run a shell command",
308 Redraw => "repaint the screen",
309 Suspend => "suspend rmut (fg brings it back)",
310 Help => "this help",
311 NextUnreadMailbox => "open the next mailbox holding new mail",
312 PurgeMessage => "mark for deletion, bypassing the trash",
313 MarkMessage => "bind a key that jumps back to this message",
314 ErrorHistory => "show the recent errors",
315 WhatKey => "say what a key is (Ctrl+G ends it)",
316 ListAction => "act on the message's List-* headers (subscribe, help, ...)",
317 }
318 }
319
320 pub fn all() -> &'static [Function] {
323 use Function::*;
324 &[
325 Quit,
326 Abort,
327 Down,
328 Up,
329 PageDown,
330 PageUp,
331 First,
332 Last,
333 View,
334 Delete,
335 Undelete,
336 Flag,
337 ToggleNew,
338 MarkAllRead,
339 Sync,
340 Compose,
341 Reply,
342 GroupReply,
343 ListReply,
344 Forward,
345 Sort,
346 Limit,
347 Search,
348 SearchReverse,
349 SearchNext,
350 NextNew,
351 PrevNew,
352 ChangeMailbox,
353 ChangeMailboxReadOnly,
354 Folders,
355 Attachments,
356 FoldThread,
357 FoldAll,
358 Print,
359 Tag,
360 TagPrefix,
361 DeleteThread,
362 UndeleteThread,
363 TagThread,
364 DeleteSubthread,
365 UndeleteSubthread,
366 NextThread,
367 PrevThread,
368 BreakThread,
369 LinkThreads,
370 ReadThread,
371 ReadSubthread,
372 TagSubthread,
373 ParentMessage,
374 RootMessage,
375 EditLabel,
376 ShowVersion,
377 ShowLimit,
378 ToggleWrite,
379 DisplayAddress,
380 PageTop,
381 PageMiddle,
382 PageBottom,
383 Undo,
384 DeletePattern,
385 UndeletePattern,
386 TagPattern,
387 UntagPattern,
388 FetchMail,
389 Save,
390 Copy,
391 DecodeSave,
392 DecodeCopy,
393 Pipe,
394 Bounce,
395 Resend,
396 Edit,
397 SidebarToggle,
398 SidebarNext,
399 SidebarPrev,
400 SidebarOpen,
401 CreateAlias,
402 Query,
403 Notmuch,
404 EnterCommand,
405 Shell,
406 Redraw,
407 Suspend,
408 Help,
409 NextUnreadMailbox,
410 PurgeMessage,
411 MarkMessage,
412 ErrorHistory,
413 WhatKey,
414 ListAction,
415 ]
416 }
417
418 pub fn from_name(name: &str) -> Option<Function> {
419 Function::all().iter().copied().find(|a| a.name() == name)
420 }
421
422 pub fn takes_tagged(self) -> bool {
427 use Function::*;
428 matches!(
429 self,
430 Delete
431 | Undelete
432 | Flag
433 | ToggleNew
434 | Tag
435 | Save
436 | Copy
437 | DecodeSave
438 | DecodeCopy
439 | Pipe
440 | Print
441 | Bounce
442 | EditLabel
443 )
444 }
445}
446
447pub enum Outcome {
449 Done,
453 Ask(Ask),
456 Front(FrontOp),
458}
459
460impl From<Option<Ask>> for Outcome {
461 fn from(ask: Option<Ask>) -> Outcome {
465 match ask {
466 Some(ask) => Outcome::Ask(ask),
467 None => Outcome::Done,
468 }
469 }
470}
471
472pub enum FrontOp {
479 Exit,
481 OpenMailbox(String),
483 ErrorHistory(Vec<String>),
486 WhatKey,
488 OpenSelected,
490 Compose(ComposeKind),
493 Resend,
495 RawEdit,
497 Attachments,
499 Folders,
501 Query,
503 Notmuch,
505 ChangeMailbox { read_only: bool },
507 CommandPrompt,
509 TagPrefix,
512 Sidebar(SidebarOp),
514 PageMove(PageSpot),
517 Help,
519 Redraw,
521}
522
523#[derive(Clone, Copy, PartialEq, Eq, Debug)]
525pub enum SidebarOp {
526 Toggle,
527 Next,
528 Prev,
529 Open,
530}
531
532#[derive(Clone, Copy, PartialEq, Eq, Debug)]
534pub enum PageSpot {
535 Top,
536 Middle,
537 Bottom,
538}
539
540impl Session {
541 pub fn run_function(&mut self, function: Function, tagged: bool, page: usize) -> Outcome {
549 use Function::*;
550 match function {
551 Down => self.select(self.sel.saturating_add(1)),
553 Up => self.select(self.sel.saturating_sub(1)),
554 PageDown => self.select(self.sel.saturating_add(page)),
555 PageUp => self.select(self.sel.saturating_sub(page)),
556 First => self.select(0),
557 Last => self.select(usize::MAX),
558 NextNew => self.jump_new(true),
559 PrevNew => self.jump_new(false),
560 NextThread => self.jump_thread(true),
561 PrevThread => self.jump_thread(false),
562 ParentMessage => self.jump_parent(false),
563 RootMessage => self.jump_parent(true),
564 SearchNext => self.search_next(),
565
566 Tag => {
568 if tagged {
569 let targets: Vec<usize> = self
573 .visible
574 .iter()
575 .copied()
576 .filter(|&i| self.msgs[i].env.tagged)
577 .collect();
578 if !targets.is_empty() {
579 self.push_undo("untag", &targets);
580 for &i in &targets {
581 self.msgs[i].env.tagged = false;
582 }
583 }
584 } else if let Some(&i) = self.visible.get(self.sel) {
585 self.push_undo("tag", &[i]);
586 self.msgs[i].env.tagged = !self.msgs[i].env.tagged;
587 self.select(self.sel.saturating_add(1));
588 }
589 }
590 Delete => {
591 let rules = self.delete_rules();
592 self.mark_selected(tagged, "delete", move |m| {
593 rules.mark(m);
594 })
595 }
596 Undelete => self.mark_selected(tagged, "undelete", |m| {
597 m.env.file.flags.deleted = false;
598 m.purge = false;
599 }),
600 PurgeMessage => {
601 let rules = self.delete_rules();
602 self.mark_selected(tagged, "purge", move |m| {
603 if rules.mark(m) {
604 m.purge = true;
605 }
606 })
607 }
608 NextUnreadMailbox => match self.next_unread_mailbox() {
609 Some(spec) => return Outcome::Front(FrontOp::OpenMailbox(spec)),
610 None => self.error("No mailboxes have new mail"),
611 },
612 MarkMessage => return self.ask_mark_message().into(),
613 ErrorHistory => {
614 if self.config.ui.error_history == 0 {
615 self.error("Error History is disabled.");
616 } else {
617 return Outcome::Front(FrontOp::ErrorHistory(self.error_history()));
618 }
619 }
620 WhatKey => return Outcome::Front(FrontOp::WhatKey),
621 ListAction => return self.ask_list_action().into(),
622 Flag => self.mark_selected(tagged, "flag", |m| {
623 m.env.file.flags.flagged = !m.env.file.flags.flagged
624 }),
625 ToggleNew => self.mark_selected(tagged, "toggle read", |m| {
626 m.env.file.flags.seen = !m.env.file.flags.seen;
627 m.env.file.is_new = false;
628 }),
629 MarkAllRead => self.mark_all_read(),
630 Undo => {
631 if !self.cancel_send() {
635 self.undo_last();
636 }
637 }
638
639 DeleteThread => self.thread_mark(false, ThreadOp::Delete),
641 UndeleteThread => self.thread_mark(false, ThreadOp::Undelete),
642 TagThread => self.thread_mark(false, ThreadOp::Tag),
643 ReadThread => self.thread_mark(false, ThreadOp::Read),
644 DeleteSubthread => self.thread_mark(true, ThreadOp::Delete),
645 UndeleteSubthread => self.thread_mark(true, ThreadOp::Undelete),
646 TagSubthread => self.thread_mark(true, ThreadOp::Tag),
647 ReadSubthread => self.thread_mark(true, ThreadOp::Read),
648 BreakThread => self.break_thread(),
649 LinkThreads => self.link_threads(),
650 FoldThread => self.toggle_collapse(false),
651 FoldAll => self.toggle_collapse(true),
652
653 Sync => {
655 if self.deleted_count() > 0 {
656 return self.ask_purge(false).into();
657 }
658 self.sync(true);
659 }
660 Quit => return self.leave().into(),
661 FetchMail => {
662 self.check_new_mail();
663 if self.notice().is_none() {
664 self.note("checked for new mail");
665 }
666 }
667 ToggleWrite => self.toggle_write(),
668 ShowLimit => self.show_limit(),
669 ShowVersion => self.note(concat!("rmut ", env!("CARGO_PKG_VERSION"))),
670 DisplayAddress => {
671 let from = self
672 .visible
673 .get(self.sel)
674 .map(|&i| self.msgs[i].env.from_full.clone());
675 match from {
676 Some(from) if !from.trim().is_empty() => self.note(from),
677 _ => self.note("(no From address)"),
678 }
679 }
680
681 Limit => return Outcome::Ask(self.ask_limit()),
683 Search => return Outcome::Ask(self.ask_search(false)),
684 SearchReverse => return Outcome::Ask(self.ask_search(true)),
685 Sort => return Outcome::Ask(self.ask_sort()),
686 Shell => return Outcome::Ask(self.ask_shell()),
687 DeletePattern => return self.ask_pattern(PatternOp::Delete).into(),
688 UndeletePattern => return self.ask_pattern(PatternOp::Undelete).into(),
689 TagPattern => return self.ask_pattern(PatternOp::Tag).into(),
690 UntagPattern => return self.ask_pattern(PatternOp::Untag).into(),
691 Save => return self.ask_copy(true, tagged).into(),
692 Copy => return self.ask_copy(false, tagged).into(),
693 DecodeSave => return self.ask_copy_decode(true, tagged, true).into(),
694 DecodeCopy => return self.ask_copy_decode(false, tagged, true).into(),
695 Pipe => return self.ask_pipe(tagged).into(),
696 Bounce => return self.ask_bounce(tagged).into(),
697 Print => return self.ask_print(tagged).into(),
698 EditLabel => return self.ask_edit_label(tagged).into(),
699 CreateAlias => return self.ask_alias().into(),
700 ListReply => return self.start_list_reply().into(),
701
702 Suspend => self.request_suspend(),
704 Abort => return Outcome::Front(FrontOp::Exit),
705 View => return Outcome::Front(FrontOp::OpenSelected),
706 Compose => return Outcome::Front(FrontOp::Compose(ComposeKind::New)),
707 Reply => return Outcome::Front(FrontOp::Compose(ComposeKind::Reply)),
708 GroupReply => return Outcome::Front(FrontOp::Compose(ComposeKind::GroupReply)),
709 Forward => return Outcome::Front(FrontOp::Compose(ComposeKind::Forward)),
710 Resend => return Outcome::Front(FrontOp::Resend),
711 Edit => return Outcome::Front(FrontOp::RawEdit),
712 Attachments => return Outcome::Front(FrontOp::Attachments),
713 Folders => return Outcome::Front(FrontOp::Folders),
714 EnterCommand => return Outcome::Front(FrontOp::CommandPrompt),
715 Help => return Outcome::Front(FrontOp::Help),
716 Redraw => return Outcome::Front(FrontOp::Redraw),
717 PageTop => return Outcome::Front(FrontOp::PageMove(PageSpot::Top)),
718 PageMiddle => return Outcome::Front(FrontOp::PageMove(PageSpot::Middle)),
719 PageBottom => return Outcome::Front(FrontOp::PageMove(PageSpot::Bottom)),
720 SidebarToggle => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Toggle)),
721 SidebarNext => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Next)),
722 SidebarPrev => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Prev)),
723 SidebarOpen => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Open)),
724 TagPrefix => {
725 if !self.msgs.iter().any(|m| m.env.tagged) {
726 self.note("no tagged messages");
727 return Outcome::Done;
728 }
729 self.note("Tag-");
733 return Outcome::Front(FrontOp::TagPrefix);
734 }
735 Query => {
736 if self.config.mail.query_command.is_none() {
737 self.error("no query_command configured");
738 return Outcome::Done;
739 }
740 return Outcome::Front(FrontOp::Query);
741 }
742 Notmuch => {
743 if self.config.mail.notmuch == Some(false) {
744 self.error("notmuch is disabled in the config");
745 return Outcome::Done;
746 }
747 return Outcome::Front(FrontOp::Notmuch);
748 }
749 ChangeMailbox | ChangeMailboxReadOnly => {
750 if !self.ready_to_leave() {
751 return Outcome::Done;
752 }
753 return Outcome::Front(FrontOp::ChangeMailbox {
754 read_only: function == ChangeMailboxReadOnly,
755 });
756 }
757 }
758 Outcome::Done
759 }
760
761 fn mark_selected(&mut self, tagged: bool, what: &'static str, f: impl Fn(&mut crate::Msg)) {
764 if self.deny_readonly() {
765 return;
766 }
767 if tagged {
768 self.each_tagged(what, f);
769 } else if let Some(&i) = self.visible.get(self.sel) {
770 self.push_undo(what, &[i]);
771 f(&mut self.msgs[i]);
772 self.msgs[i].dirty = true;
773 self.select(self.sel.saturating_add(1));
774 }
775 }
776}