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}
110
111impl Function {
112 pub fn name(self) -> &'static str {
113 use Function::*;
114 match self {
115 Quit => "quit",
116 Abort => "abort",
117 Down => "down",
118 Up => "up",
119 PageDown => "page-down",
120 PageUp => "page-up",
121 First => "first",
122 Last => "last",
123 View => "view",
124 Delete => "delete",
125 Undelete => "undelete",
126 Flag => "flag",
127 ToggleNew => "toggle-new",
128 Sync => "sync",
129 Compose => "compose",
130 Reply => "reply",
131 GroupReply => "group-reply",
132 ListReply => "list-reply",
133 Forward => "forward",
134 Sort => "sort",
135 Limit => "limit",
136 Search => "search",
137 SearchReverse => "search-reverse",
138 SearchNext => "search-next",
139 NextNew => "next-new",
140 PrevNew => "previous-new",
141 ChangeMailbox => "change-mailbox",
142 ChangeMailboxReadOnly => "change-mailbox-readonly",
143 Folders => "folders",
144 Attachments => "attachments",
145 FoldThread => "fold-thread",
146 FoldAll => "fold-all",
147 Print => "print",
148 Tag => "tag",
149 TagPrefix => "tag-prefix",
150 DeleteThread => "delete-thread",
151 UndeleteThread => "undelete-thread",
152 TagThread => "tag-thread",
153 DeleteSubthread => "delete-subthread",
154 UndeleteSubthread => "undelete-subthread",
155 NextThread => "next-thread",
156 BreakThread => "break-thread",
157 LinkThreads => "link-threads",
158 ReadThread => "read-thread",
159 ReadSubthread => "read-subthread",
160 TagSubthread => "tag-subthread",
161 ParentMessage => "parent-message",
162 RootMessage => "root-message",
163 EditLabel => "edit-label",
164 ShowVersion => "show-version",
165 ShowLimit => "show-limit",
166 ToggleWrite => "toggle-write",
167 DisplayAddress => "display-address",
168 PageTop => "top-page",
169 PageMiddle => "middle-page",
170 PageBottom => "bottom-page",
171 PrevThread => "previous-thread",
172 Undo => "undo",
173 DeletePattern => "delete-pattern",
174 UndeletePattern => "undelete-pattern",
175 TagPattern => "tag-pattern",
176 UntagPattern => "untag-pattern",
177 FetchMail => "fetch-mail",
178 Save => "save",
179 Copy => "copy",
180 DecodeSave => "decode-save",
181 DecodeCopy => "decode-copy",
182 Pipe => "pipe",
183 Bounce => "bounce",
184 Resend => "resend",
185 Edit => "edit",
186 SidebarToggle => "sidebar-toggle",
187 SidebarNext => "sidebar-next",
188 SidebarPrev => "sidebar-prev",
189 SidebarOpen => "sidebar-open",
190 CreateAlias => "create-alias",
191 Query => "query",
192 Notmuch => "notmuch",
193 EnterCommand => "enter-command",
194 Shell => "shell-escape",
195 Redraw => "refresh",
196 Suspend => "suspend",
197 Help => "help",
198 }
199 }
200
201 pub fn describe(self) -> &'static str {
202 use Function::*;
203 match self {
204 Quit => "quit (writes changes; asks before purging deletions)",
205 Abort => "quit without saving changes",
206 Down => "next message",
207 Up => "previous message",
208 PageDown => "page down",
209 PageUp => "page up",
210 First => "first message",
211 Last => "last message",
212 View => "view message",
213 Delete => "mark for deletion",
214 Undelete => "unmark deletion",
215 Flag => "toggle flagged mark",
216 ToggleNew => "toggle read/unread",
217 Sync => "write changes to the maildir",
218 Compose => "compose a new message",
219 Reply => "reply to sender",
220 GroupReply => "reply to all",
221 ListReply => "reply to the mailing list only",
222 Forward => "forward message",
223 Sort => "choose sort order",
224 Limit => "limit index by pattern",
225 Search => "search messages by pattern (the pager / searches its text)",
226 SearchReverse => "search backwards; n then repeats backwards too",
227 SearchNext => "repeat last search, the way it was going",
228 NextNew => "jump to the next new or unread message",
229 PrevNew => "jump to the previous new or unread message",
230 ChangeMailbox => "open a mailbox by path",
231 ChangeMailboxReadOnly => "open a mailbox read-only (Alt+c)",
232 Folders => "browse nearby mailboxes",
233 Attachments => "list message parts",
234 FoldThread => "fold/unfold current thread",
235 FoldAll => "fold/unfold all threads",
236 Print => "pipe message to the print command",
237 Tag => "toggle the tag on this message",
238 TagPrefix => "apply the next function to tagged messages",
239 DeleteThread => "mark the whole thread for deletion",
240 UndeleteThread => "unmark the whole thread",
241 TagThread => "tag/untag the whole thread",
242 DeleteSubthread => "mark this message and its replies for deletion",
243 UndeleteSubthread => "unmark this message and its replies",
244 NextThread => "jump to the next thread",
245 BreakThread => "break the thread in two at this message",
246 LinkThreads => "link the tagged messages under this one",
247 ReadThread => "mark the whole thread read",
248 ReadSubthread => "mark this message and its replies read",
249 TagSubthread => "tag this message and its replies",
250 ParentMessage => "jump to the parent message",
251 RootMessage => "jump to the thread's root message",
252 EditLabel => "add, change or clear the X-Label",
253 ShowVersion => "show the rmut version",
254 ShowLimit => "show the active limit pattern",
255 ToggleWrite => "toggle the mailbox's read-only state",
256 DisplayAddress => "show the sender's full address",
257 PageTop => "move to the top of the page",
258 PageMiddle => "move to the middle of the page",
259 PageBottom => "move to the bottom of the page",
260 PrevThread => "jump to the previous thread",
261 Undo => "cancel a held send, else undo the last delete/flag/tag/save",
262 DeletePattern => "delete every message matching a pattern",
263 UndeletePattern => "undelete every message matching a pattern",
264 TagPattern => "tag every message matching a pattern",
265 UntagPattern => "untag every message matching a pattern",
266 FetchMail => "check for new mail now",
267 Save => "save (copy + mark deleted) to a mailbox",
268 DecodeSave => "decode-save: the decoded message, original deleted",
269 DecodeCopy => "decode-copy: the decoded message",
270 Copy => "copy to a mailbox (original stays)",
271 Pipe => "pipe raw message to a shell command",
272 Bounce => "bounce (resend) message to new recipients",
273 Resend => "edit the message as a new draft",
274 Edit => "edit the raw message and replace it",
275 SidebarToggle => "show/hide the mailbox sidebar",
276 SidebarNext => "highlight the next sidebar mailbox",
277 SidebarPrev => "highlight the previous sidebar mailbox",
278 SidebarOpen => "open the highlighted sidebar mailbox",
279 CreateAlias => "add the sender to the alias file",
280 Query => "look up addresses with query_command",
281 Notmuch => "notmuch search into a read-only view",
282 EnterCommand => "run a config command (set/bind/macro/color/...)",
283 Shell => "run a shell command",
284 Redraw => "repaint the screen",
285 Suspend => "suspend rmut (fg brings it back)",
286 Help => "this help",
287 }
288 }
289
290 pub fn all() -> &'static [Function] {
293 use Function::*;
294 &[
295 Quit,
296 Abort,
297 Down,
298 Up,
299 PageDown,
300 PageUp,
301 First,
302 Last,
303 View,
304 Delete,
305 Undelete,
306 Flag,
307 ToggleNew,
308 Sync,
309 Compose,
310 Reply,
311 GroupReply,
312 ListReply,
313 Forward,
314 Sort,
315 Limit,
316 Search,
317 SearchReverse,
318 SearchNext,
319 NextNew,
320 PrevNew,
321 ChangeMailbox,
322 ChangeMailboxReadOnly,
323 Folders,
324 Attachments,
325 FoldThread,
326 FoldAll,
327 Print,
328 Tag,
329 TagPrefix,
330 DeleteThread,
331 UndeleteThread,
332 TagThread,
333 DeleteSubthread,
334 UndeleteSubthread,
335 NextThread,
336 PrevThread,
337 BreakThread,
338 LinkThreads,
339 ReadThread,
340 ReadSubthread,
341 TagSubthread,
342 ParentMessage,
343 RootMessage,
344 EditLabel,
345 ShowVersion,
346 ShowLimit,
347 ToggleWrite,
348 DisplayAddress,
349 PageTop,
350 PageMiddle,
351 PageBottom,
352 Undo,
353 DeletePattern,
354 UndeletePattern,
355 TagPattern,
356 UntagPattern,
357 FetchMail,
358 Save,
359 Copy,
360 DecodeSave,
361 DecodeCopy,
362 Pipe,
363 Bounce,
364 Resend,
365 Edit,
366 SidebarToggle,
367 SidebarNext,
368 SidebarPrev,
369 SidebarOpen,
370 CreateAlias,
371 Query,
372 Notmuch,
373 EnterCommand,
374 Shell,
375 Redraw,
376 Suspend,
377 Help,
378 ]
379 }
380
381 pub fn from_name(name: &str) -> Option<Function> {
382 Function::all().iter().copied().find(|a| a.name() == name)
383 }
384
385 pub fn takes_tagged(self) -> bool {
390 use Function::*;
391 matches!(
392 self,
393 Delete
394 | Undelete
395 | Flag
396 | ToggleNew
397 | Tag
398 | Save
399 | Copy
400 | DecodeSave
401 | DecodeCopy
402 | Pipe
403 | Print
404 | Bounce
405 | EditLabel
406 )
407 }
408}
409
410pub enum Outcome {
412 Done,
416 Ask(Ask),
419 Front(FrontOp),
421}
422
423impl From<Option<Ask>> for Outcome {
424 fn from(ask: Option<Ask>) -> Outcome {
428 match ask {
429 Some(ask) => Outcome::Ask(ask),
430 None => Outcome::Done,
431 }
432 }
433}
434
435pub enum FrontOp {
442 Exit,
444 OpenSelected,
446 Compose(ComposeKind),
449 Resend,
451 RawEdit,
453 Attachments,
455 Folders,
457 Query,
459 Notmuch,
461 ChangeMailbox { read_only: bool },
463 CommandPrompt,
465 TagPrefix,
468 Sidebar(SidebarOp),
470 PageMove(PageSpot),
473 Help,
475 Redraw,
477}
478
479#[derive(Clone, Copy, PartialEq, Eq, Debug)]
481pub enum SidebarOp {
482 Toggle,
483 Next,
484 Prev,
485 Open,
486}
487
488#[derive(Clone, Copy, PartialEq, Eq, Debug)]
490pub enum PageSpot {
491 Top,
492 Middle,
493 Bottom,
494}
495
496impl Session {
497 pub fn run_function(&mut self, function: Function, tagged: bool, page: usize) -> Outcome {
505 use Function::*;
506 match function {
507 Down => self.select(self.sel.saturating_add(1)),
509 Up => self.select(self.sel.saturating_sub(1)),
510 PageDown => self.select(self.sel.saturating_add(page)),
511 PageUp => self.select(self.sel.saturating_sub(page)),
512 First => self.select(0),
513 Last => self.select(usize::MAX),
514 NextNew => self.jump_new(true),
515 PrevNew => self.jump_new(false),
516 NextThread => self.jump_thread(true),
517 PrevThread => self.jump_thread(false),
518 ParentMessage => self.jump_parent(false),
519 RootMessage => self.jump_parent(true),
520 SearchNext => self.search_next(),
521
522 Tag => {
524 if let Some(&i) = self.visible.get(self.sel) {
525 self.push_undo("tag", &[i]);
526 self.msgs[i].env.tagged = !self.msgs[i].env.tagged;
527 self.select(self.sel.saturating_add(1));
528 }
529 }
530 Delete => self.mark_selected(tagged, "delete", |m| m.env.file.flags.deleted = true),
531 Undelete => {
532 self.mark_selected(tagged, "undelete", |m| m.env.file.flags.deleted = false)
533 }
534 Flag => self.mark_selected(tagged, "flag", |m| {
535 m.env.file.flags.flagged = !m.env.file.flags.flagged
536 }),
537 ToggleNew => self.mark_selected(tagged, "toggle read", |m| {
538 m.env.file.flags.seen = !m.env.file.flags.seen;
539 m.env.file.is_new = false;
540 }),
541 Undo => {
542 if !self.cancel_send() {
546 self.undo_last();
547 }
548 }
549
550 DeleteThread => self.thread_mark(false, ThreadOp::Delete),
552 UndeleteThread => self.thread_mark(false, ThreadOp::Undelete),
553 TagThread => self.thread_mark(false, ThreadOp::Tag),
554 ReadThread => self.thread_mark(false, ThreadOp::Read),
555 DeleteSubthread => self.thread_mark(true, ThreadOp::Delete),
556 UndeleteSubthread => self.thread_mark(true, ThreadOp::Undelete),
557 TagSubthread => self.thread_mark(true, ThreadOp::Tag),
558 ReadSubthread => self.thread_mark(true, ThreadOp::Read),
559 BreakThread => self.break_thread(),
560 LinkThreads => self.link_threads(),
561 FoldThread => self.toggle_collapse(false),
562 FoldAll => self.toggle_collapse(true),
563
564 Sync => {
566 if self.deleted_count() > 0 {
567 return self.ask_purge(false).into();
568 }
569 self.sync(true);
570 }
571 Quit => return self.leave().into(),
572 FetchMail => {
573 self.check_new_mail();
574 if self.notice().is_none() {
575 self.note("checked for new mail");
576 }
577 }
578 ToggleWrite => self.toggle_write(),
579 ShowLimit => self.show_limit(),
580 ShowVersion => self.note(concat!("rmut ", env!("CARGO_PKG_VERSION"))),
581 DisplayAddress => {
582 let from = self
583 .visible
584 .get(self.sel)
585 .map(|&i| self.msgs[i].env.from_full.clone());
586 match from {
587 Some(from) if !from.trim().is_empty() => self.note(from),
588 _ => self.note("(no From address)"),
589 }
590 }
591
592 Limit => return Outcome::Ask(self.ask_limit()),
594 Search => return Outcome::Ask(self.ask_search(false)),
595 SearchReverse => return Outcome::Ask(self.ask_search(true)),
596 Sort => return Outcome::Ask(self.ask_sort()),
597 Shell => return Outcome::Ask(self.ask_shell()),
598 DeletePattern => return self.ask_pattern(PatternOp::Delete).into(),
599 UndeletePattern => return self.ask_pattern(PatternOp::Undelete).into(),
600 TagPattern => return self.ask_pattern(PatternOp::Tag).into(),
601 UntagPattern => return self.ask_pattern(PatternOp::Untag).into(),
602 Save => return self.ask_copy(true, tagged).into(),
603 Copy => return self.ask_copy(false, tagged).into(),
604 DecodeSave => return self.ask_copy_decode(true, tagged, true).into(),
605 DecodeCopy => return self.ask_copy_decode(false, tagged, true).into(),
606 Pipe => return self.ask_pipe(tagged).into(),
607 Bounce => return self.ask_bounce(tagged).into(),
608 Print => return self.ask_print(tagged).into(),
609 EditLabel => return self.ask_edit_label(tagged).into(),
610 CreateAlias => return self.ask_alias().into(),
611 ListReply => return self.start_list_reply().into(),
612
613 Suspend => self.request_suspend(),
615 Abort => return Outcome::Front(FrontOp::Exit),
616 View => return Outcome::Front(FrontOp::OpenSelected),
617 Compose => return Outcome::Front(FrontOp::Compose(ComposeKind::New)),
618 Reply => return Outcome::Front(FrontOp::Compose(ComposeKind::Reply)),
619 GroupReply => return Outcome::Front(FrontOp::Compose(ComposeKind::GroupReply)),
620 Forward => return Outcome::Front(FrontOp::Compose(ComposeKind::Forward)),
621 Resend => return Outcome::Front(FrontOp::Resend),
622 Edit => return Outcome::Front(FrontOp::RawEdit),
623 Attachments => return Outcome::Front(FrontOp::Attachments),
624 Folders => return Outcome::Front(FrontOp::Folders),
625 EnterCommand => return Outcome::Front(FrontOp::CommandPrompt),
626 Help => return Outcome::Front(FrontOp::Help),
627 Redraw => return Outcome::Front(FrontOp::Redraw),
628 PageTop => return Outcome::Front(FrontOp::PageMove(PageSpot::Top)),
629 PageMiddle => return Outcome::Front(FrontOp::PageMove(PageSpot::Middle)),
630 PageBottom => return Outcome::Front(FrontOp::PageMove(PageSpot::Bottom)),
631 SidebarToggle => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Toggle)),
632 SidebarNext => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Next)),
633 SidebarPrev => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Prev)),
634 SidebarOpen => return Outcome::Front(FrontOp::Sidebar(SidebarOp::Open)),
635 TagPrefix => {
636 if !self.msgs.iter().any(|m| m.env.tagged) {
637 self.note("no tagged messages");
638 return Outcome::Done;
639 }
640 self.note("Tag-");
644 return Outcome::Front(FrontOp::TagPrefix);
645 }
646 Query => {
647 if self.config.mail.query_command.is_none() {
648 self.error("no query_command configured");
649 return Outcome::Done;
650 }
651 return Outcome::Front(FrontOp::Query);
652 }
653 Notmuch => {
654 if self.config.mail.notmuch == Some(false) {
655 self.error("notmuch is disabled in the config");
656 return Outcome::Done;
657 }
658 return Outcome::Front(FrontOp::Notmuch);
659 }
660 ChangeMailbox | ChangeMailboxReadOnly => {
661 if !self.ready_to_leave() {
662 return Outcome::Done;
663 }
664 return Outcome::Front(FrontOp::ChangeMailbox {
665 read_only: function == ChangeMailboxReadOnly,
666 });
667 }
668 }
669 Outcome::Done
670 }
671
672 fn mark_selected(&mut self, tagged: bool, what: &'static str, f: impl Fn(&mut crate::Msg)) {
675 if self.deny_readonly() {
676 return;
677 }
678 if tagged {
679 self.each_tagged(what, f);
680 } else if let Some(&i) = self.visible.get(self.sel) {
681 self.push_undo(what, &[i]);
682 f(&mut self.msgs[i]);
683 self.msgs[i].dirty = true;
684 self.select(self.sel.saturating_add(1));
685 }
686 }
687}