Skip to main content

rmut_session/
function.rs

1//! What the index can be told to do, and the doing of it.
2//!
3//! mutt names every operation (`<delete-message>`, `<group-reply>`)
4//! and binds keys to the names, which is what makes a keymap a
5//! configuration rather than a program. rmut had the names, but they
6//! lived in the terminal front end next to the dispatch, so the only
7//! way to reach `delete` was to press a key on a pty: 300 lines of
8//! mailbox logic that no test could name.
9//!
10//! The names and the dispatch live here now. A front end resolves
11//! whatever it has (a keystroke, a menu item, `:exec`) to a
12//! [`Function`], hands it to [`Session::run_function`], and reads the
13//! [`Outcome`]. Most functions finish inside the session; some stop
14//! to [`Ask`]; the rest come back as a [`FrontOp`], which is the
15//! honest list of what a session cannot do for itself because it owns
16//! neither a screen nor an editor.
17
18use crate::ask::{Ask, PatternOp};
19use crate::{ComposeKind, Session, ThreadOp};
20
21/// One thing the index can be told to do, under the name mutt gives
22/// it. A front end binds keys or menu items to these; `bind` and
23/// `:exec` name them straight out.
24#[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    /// mutt's next-unread-mailbox: open the next configured mailbox
110    /// holding new mail.
111    NextUnreadMailbox,
112    /// mutt's purge-message: delete, bypassing $trash.
113    PurgeMessage,
114    /// mutt's mark-message: a hotkey that jumps back here.
115    MarkMessage,
116    /// mutt's error-history: the recent complaints on a screen.
117    ErrorHistory,
118    /// mutt's what-key: say what the next keys are.
119    WhatKey,
120    /// mutt's list-action: RFC 2369 List-* actions of the message.
121    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    /// Every function, in the order the help screen and a menu bar
316    /// want them.
317    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    /// Which functions `;` (tag-prefix) can hand the tagged set to.
417    /// The rest say so rather than quietly acting on one message:
418    /// resend and edit open a draft or an editor, of which rmut has
419    /// one at a time.
420    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
441/// What came of running a function.
442pub enum Outcome {
443    /// The session did it. Whatever there was to say went out as a
444    /// [`Notice`](rmut_core::notice::Notice); whatever the front end
445    /// must do next is waiting in [`Session::take_request`].
446    Done,
447    /// It cannot finish without an answer. The front end collects one
448    /// however it likes and hands it back to [`Session::answer`].
449    Ask(Ask),
450    /// Only the front end can do this one.
451    Front(FrontOp),
452}
453
454impl From<Option<Ask>> for Outcome {
455    /// The `ask_*` helpers return `None` when the question does not
456    /// arise (nothing to act on, a read-only mailbox), having already
457    /// said why.
458    fn from(ask: Option<Ask>) -> Outcome {
459        match ask {
460            Some(ask) => Outcome::Ask(ask),
461            None => Outcome::Done,
462        }
463    }
464}
465
466/// The functions a session cannot carry out, because they are about
467/// the display rather than the mail: a menu to open, a screen to
468/// repaint, an editor to hand the terminal to. The session has done
469/// whatever checking it can (a mailbox with unsaved changes will not
470/// be left, an unconfigured query will not be prompted for) before
471/// handing one of these back, so a front end can act on it directly.
472pub enum FrontOp {
473    /// mutt's `x`: leave without writing anything back.
474    Exit,
475    /// Open this mailbox spec (mutt's next-unread-mailbox found it).
476    OpenMailbox(String),
477    /// Put these lines on a screen of their own: the error history,
478    /// oldest first.
479    ErrorHistory(Vec<String>),
480    /// Read keys and say what they are until Ctrl+G.
481    WhatKey,
482    /// Read the selected message, however messages are read.
483    OpenSelected,
484    /// Start a draft. The session asks for the recipients once the
485    /// front end has settled `$recall`.
486    Compose(ComposeKind),
487    /// mutt's resend-message: the selected message as a new draft.
488    Resend,
489    /// mutt's `e`: the selected message's own bytes, in an editor.
490    RawEdit,
491    /// The attachment menu for the selected message.
492    Attachments,
493    /// The folder browser.
494    Folders,
495    /// `$query_command`, which is configured: ask for the terms.
496    Query,
497    /// notmuch, which is not disabled: ask for the query.
498    Notmuch,
499    /// Somewhere else to open; the open mailbox is ready to be left.
500    ChangeMailbox { read_only: bool },
501    /// The `:` command line.
502    CommandPrompt,
503    /// mutt's `;`: the next function applies to the tagged set. There
504    /// are tagged messages, or this would have been a complaint.
505    TagPrefix,
506    /// The mailbox pane.
507    Sidebar(SidebarOp),
508    /// Move the cursor by where it sits on screen, which only the
509    /// front end knows: mutt's H, M and L.
510    PageMove(PageSpot),
511    /// mutt's help screen.
512    Help,
513    /// mutt's Ctrl+L: repaint.
514    Redraw,
515}
516
517/// What to do with the mailbox pane.
518#[derive(Clone, Copy, PartialEq, Eq, Debug)]
519pub enum SidebarOp {
520    Toggle,
521    Next,
522    Prev,
523    Open,
524}
525
526/// Where on the visible page the cursor should land.
527#[derive(Clone, Copy, PartialEq, Eq, Debug)]
528pub enum PageSpot {
529    Top,
530    Middle,
531    Bottom,
532}
533
534impl Session {
535    /// Do one thing, whatever asked for it: a key, a macro replay, a
536    /// `:exec`, a menu item.
537    ///
538    /// `tagged` is mutt's tag-prefix, and is only ever true for a
539    /// function that [takes it](Function::takes_tagged); `page` is how
540    /// many messages the front end is showing at once, which is all
541    /// the geometry the session needs to know.
542    pub fn run_function(&mut self, function: Function, tagged: bool, page: usize) -> Outcome {
543        use Function::*;
544        match function {
545            // ---- motion ----
546            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            // ---- marks ----
561            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                // A message still inside its $undo_send window is the
609                // most recent thing done, so it is what undo takes
610                // back first.
611                if !self.cancel_send() {
612                    self.undo_last();
613                }
614            }
615
616            // ---- threads ----
617            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            // ---- the mailbox ----
631            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            // ---- questions ----
659            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            // ---- off to the front end ----
680            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                // mutt writes "Tag-" on its message line and waits;
707                // rmut's one bottom line appends it to the status bar,
708                // which then stays readable.
709                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    /// One flag change, on the tagged set or on the message under the
739    /// cursor. mutt's $resolve (on by default) advances afterwards.
740    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}