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