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