1 /**
2  * @file
3  * Index functions
4  *
5  * @authors
6  * Copyright (C) 2021 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page index_functions Index functions
25  *
26  * Index functions
27  */
28 
29 #include "config.h"
30 #include <ctype.h>
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "mutt/lib.h"
36 #include "config/lib.h"
37 #include "email/lib.h"
38 #include "core/lib.h"
39 #include "alias/lib.h"
40 #include "gui/lib.h"
41 #include "mutt.h"
42 #include "functions.h"
43 #include "lib.h"
44 #include "attach/lib.h"
45 #include "menu/lib.h"
46 #include "ncrypt/lib.h"
47 #include "pager/lib.h"
48 #include "pattern/lib.h"
49 #include "progress/lib.h"
50 #include "question/lib.h"
51 #include "send/lib.h"
52 #include "browser.h"
53 #include "commands.h"
54 #include "context.h"
55 #include "hook.h"
56 #include "keymap.h"
57 #include "mutt_header.h"
58 #include "mutt_mailbox.h"
59 #include "mutt_thread.h"
60 #include "muttlib.h"
61 #include "mx.h"
62 #include "opcodes.h"
63 #include "options.h"
64 #include "private_data.h"
65 #include "protos.h"
66 #include "score.h"
67 #include "shared_data.h"
68 #include "sort.h"
69 #ifdef USE_AUTOCRYPT
70 #include "autocrypt/lib.h"
71 #endif
72 #ifdef USE_NOTMUCH
73 #include "notmuch/lib.h"
74 #endif
75 #ifdef USE_IMAP
76 #include "imap/lib.h"
77 #endif
78 #ifdef USE_SIDEBAR
79 #include "sidebar/lib.h"
80 #endif
81 #ifdef USE_NNTP
82 #include "nntp/lib.h"
83 #include "nntp/mdata.h" // IWYU pragma: keep
84 #endif
85 #ifdef USE_POP
86 #include "pop/lib.h"
87 #endif
88 #ifdef ENABLE_NLS
89 #include <libintl.h>
90 #endif
91 
92 static const char *Not_available_in_this_menu =
93     N_("Not available in this menu");
94 
95 // -----------------------------------------------------------------------------
96 
97 /**
98  * op_bounce_message - Remail a message to another user - Implements ::index_function_t - @ingroup index_function_api
99  */
op_bounce_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)100 static int op_bounce_message(struct IndexSharedData *shared,
101                              struct IndexPrivateData *priv, int op)
102 {
103   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
104   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
105   ci_bounce_message(shared->mailbox, &el);
106   emaillist_clear(&el);
107 
108   return IR_SUCCESS;
109 }
110 
111 /**
112  * op_check_stats - Calculate message statistics for all mailboxes - Implements ::index_function_t - @ingroup index_function_api
113  */
op_check_stats(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)114 static int op_check_stats(struct IndexSharedData *shared,
115                           struct IndexPrivateData *priv, int op)
116 {
117   mutt_check_stats(shared->mailbox);
118   return IR_SUCCESS;
119 }
120 
121 /**
122  * op_check_traditional - Check for classic PGP - Implements ::index_function_t - @ingroup index_function_api
123  */
op_check_traditional(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)124 static int op_check_traditional(struct IndexSharedData *shared,
125                                 struct IndexPrivateData *priv, int op)
126 {
127   if (!(WithCrypto & APPLICATION_PGP))
128     return IR_NOT_IMPL;
129   if (!shared->email)
130     return IR_NO_ACTION;
131 
132   if (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED))
133   {
134     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
135     el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
136     if (mutt_check_traditional_pgp(shared->mailbox, &el))
137       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
138     emaillist_clear(&el);
139   }
140 
141   if (priv->in_pager)
142     return IR_CONTINUE;
143 
144   return IR_SUCCESS;
145 }
146 
147 /**
148  * op_compose_to_sender - Compose new message to the current message sender - Implements ::index_function_t - @ingroup index_function_api
149  */
op_compose_to_sender(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)150 static int op_compose_to_sender(struct IndexSharedData *shared,
151                                 struct IndexPrivateData *priv, int op)
152 {
153   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
154   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
155   int rc = mutt_send_message(SEND_TO_SENDER, NULL, NULL, shared->mailbox, &el,
156                              shared->sub);
157   emaillist_clear(&el);
158   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
159 
160   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
161 }
162 
163 /**
164  * op_create_alias - Create an alias from a message sender - Implements ::index_function_t - @ingroup index_function_api
165  */
op_create_alias(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)166 static int op_create_alias(struct IndexSharedData *shared,
167                            struct IndexPrivateData *priv, int op)
168 {
169   struct AddressList *al = NULL;
170   if (shared->email && shared->email->env)
171     al = mutt_get_address(shared->email->env, NULL);
172   alias_create(al, shared->sub);
173   menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
174 
175   return IR_SUCCESS;
176 }
177 
178 /**
179  * op_delete - Delete the current entry - Implements ::index_function_t - @ingroup index_function_api
180  */
op_delete(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)181 static int op_delete(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
182 {
183   /* L10N: CHECK_ACL */
184   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't delete message")))
185     return IR_ERROR;
186 
187   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
188   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
189 
190   mutt_emails_set_flag(shared->mailbox, &el, MUTT_DELETE, true);
191   mutt_emails_set_flag(shared->mailbox, &el, MUTT_PURGE, (op == OP_PURGE_MESSAGE));
192   const bool c_delete_untag = cs_subset_bool(shared->sub, "delete_untag");
193   if (c_delete_untag)
194     mutt_emails_set_flag(shared->mailbox, &el, MUTT_TAG, false);
195   emaillist_clear(&el);
196 
197   if (priv->tag)
198   {
199     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
200   }
201   else
202   {
203     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
204     if (c_resolve)
205     {
206       int index = menu_get_index(priv->menu);
207       index = ci_next_undeleted(shared->mailbox, index);
208       if (index != -1)
209         menu_set_index(priv->menu, index);
210 
211       if (index == -1)
212       {
213         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
214       }
215       else if (priv->in_pager)
216       {
217         return IR_CONTINUE;
218       }
219     }
220     else
221       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
222   }
223 
224   return IR_SUCCESS;
225 }
226 
227 /**
228  * op_delete_thread - Delete all messages in thread - Implements ::index_function_t - @ingroup index_function_api
229  */
op_delete_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)230 static int op_delete_thread(struct IndexSharedData *shared,
231                             struct IndexPrivateData *priv, int op)
232 {
233   /* L10N: CHECK_ACL */
234   /* L10N: Due to the implementation details we do not know whether we
235      delete zero, 1, 12, ... messages. So in English we use
236      "messages". Your language might have other means to express this. */
237   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't delete messages")))
238     return IR_ERROR;
239   if (!shared->email)
240     return IR_NO_ACTION;
241 
242   int subthread = (op == OP_DELETE_SUBTHREAD);
243   int rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE, true, subthread);
244   if (rc == -1)
245     return IR_ERROR;
246   if (op == OP_PURGE_THREAD)
247   {
248     rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE, true, subthread);
249     if (rc == -1)
250       return IR_ERROR;
251   }
252 
253   const bool c_delete_untag = cs_subset_bool(shared->sub, "delete_untag");
254   if (c_delete_untag)
255     mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_TAG, false, subthread);
256   const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
257   if (c_resolve)
258   {
259     int index = menu_get_index(priv->menu);
260     index = ci_next_undeleted(shared->mailbox, index);
261     if (index != -1)
262       menu_set_index(priv->menu, index);
263   }
264   menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
265   return IR_SUCCESS;
266 }
267 
268 /**
269  * op_display_address - Display full address of sender - Implements ::index_function_t - @ingroup index_function_api
270  */
op_display_address(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)271 static int op_display_address(struct IndexSharedData *shared,
272                               struct IndexPrivateData *priv, int op)
273 {
274   if (!shared->email)
275     return IR_NO_ACTION;
276   mutt_display_address(shared->email->env);
277 
278   return IR_SUCCESS;
279 }
280 
281 /**
282  * op_display_message - Display a message - Implements ::index_function_t - @ingroup index_function_api
283  */
op_display_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)284 static int op_display_message(struct IndexSharedData *shared,
285                               struct IndexPrivateData *priv, int op)
286 {
287   if (!shared->email)
288     return IR_NO_ACTION;
289   /* toggle the weeding of headers so that a user can press the key
290    * again while reading the message.  */
291   if (op == OP_DISPLAY_HEADERS)
292     bool_str_toggle(shared->sub, "weed", NULL);
293 
294   OptNeedResort = false;
295 
296   if (mutt_using_threads() && shared->email->collapsed)
297   {
298     mutt_uncollapse_thread(shared->email);
299     mutt_set_vnum(shared->mailbox);
300     const bool c_uncollapse_jump =
301         cs_subset_bool(shared->sub, "uncollapse_jump");
302     if (c_uncollapse_jump)
303       menu_set_index(priv->menu, mutt_thread_next_unread(shared->email));
304   }
305 
306   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
307   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
308   {
309     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
310     el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
311     if (mutt_check_traditional_pgp(shared->mailbox, &el))
312       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
313     emaillist_clear(&el);
314   }
315   const int index = menu_get_index(priv->menu);
316   index_shared_data_set_email(shared, mutt_get_virt_email(shared->mailbox, index));
317 
318   const char *const c_pager = cs_subset_string(NeoMutt->sub, "pager");
319   if (c_pager && !mutt_str_equal(c_pager, "builtin"))
320   {
321     op = external_pager(shared->mailbox, shared->email, c_pager);
322   }
323   else
324   {
325     op = mutt_display_message(priv->win_index, priv->win_pager, priv->win_pbar,
326                               shared->mailbox, shared->email);
327   }
328 
329   window_set_focus(priv->win_index);
330   if (op < 0)
331   {
332     OptNeedResort = false;
333     return IR_ERROR;
334   }
335 
336   /* This is used to redirect a single operation back here afterwards.  If
337    * mutt_display_message() returns 0, then this flag and pager state will
338    * be cleaned up after this switch statement. */
339   priv->in_pager = true;
340   if (shared->mailbox)
341   {
342     update_index(priv->menu, shared->ctx, MX_STATUS_NEW_MAIL,
343                  shared->mailbox->msg_count, shared);
344   }
345 
346   return op;
347 }
348 
349 /**
350  * op_edit_label - Add, change, or delete a message's label - Implements ::index_function_t - @ingroup index_function_api
351  */
op_edit_label(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)352 static int op_edit_label(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
353 {
354   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
355   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
356   int num_changed = mutt_label_message(shared->mailbox, &el);
357   emaillist_clear(&el);
358 
359   if (num_changed > 0)
360   {
361     shared->mailbox->changed = true;
362     menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
363     /* L10N: This is displayed when the x-label on one or more
364        messages is edited. */
365     mutt_message(ngettext("%d label changed", "%d labels changed", num_changed), num_changed);
366     return IR_SUCCESS;
367   }
368 
369   /* L10N: This is displayed when editing an x-label, but no messages
370      were updated.  Possibly due to canceling at the prompt or if the new
371      label is the same as the old label. */
372   mutt_message(_("No labels changed"));
373   return IR_NO_ACTION;
374 }
375 
376 /**
377  * op_edit_raw_message - Edit the raw message (edit and edit-raw-message are synonyms) - Implements ::index_function_t - @ingroup index_function_api
378  */
op_edit_raw_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)379 static int op_edit_raw_message(struct IndexSharedData *shared,
380                                struct IndexPrivateData *priv, int op)
381 {
382   /* TODO split this into 3 cases? */
383   bool edit;
384   if (op == OP_EDIT_RAW_MESSAGE)
385   {
386     /* L10N: CHECK_ACL */
387     if (!check_acl(shared->mailbox, MUTT_ACL_INSERT, _("Can't edit message")))
388       return IR_ERROR;
389     edit = true;
390   }
391   else if (op == OP_EDIT_OR_VIEW_RAW_MESSAGE)
392     edit = !shared->mailbox->readonly && (shared->mailbox->rights & MUTT_ACL_INSERT);
393   else
394     edit = false;
395 
396   if (!shared->email)
397     return IR_NO_ACTION;
398   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
399   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
400   {
401     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
402     el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
403     if (mutt_check_traditional_pgp(shared->mailbox, &el))
404       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
405     emaillist_clear(&el);
406   }
407   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
408   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
409   mutt_ev_message(shared->mailbox, &el, edit ? EVM_EDIT : EVM_VIEW);
410   emaillist_clear(&el);
411   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
412 
413   return IR_SUCCESS;
414 }
415 
416 /**
417  * op_edit_type - Edit attachment content type - Implements ::index_function_t - @ingroup index_function_api
418  */
op_edit_type(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)419 static int op_edit_type(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
420 {
421   if (!shared->email)
422     return IR_NO_ACTION;
423   mutt_edit_content_type(shared->email, shared->email->body, NULL);
424   /* if we were in the pager, redisplay the message */
425   if (priv->in_pager)
426     return IR_CONTINUE;
427 
428   menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
429   return IR_SUCCESS;
430 }
431 
432 /**
433  * op_end_cond - End of conditional execution (noop) - Implements ::index_function_t - @ingroup index_function_api
434  */
op_end_cond(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)435 static int op_end_cond(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
436 {
437   return IR_SUCCESS;
438 }
439 
440 /**
441  * op_enter_command - Enter a neomuttrc command - Implements ::index_function_t - @ingroup index_function_api
442  */
op_enter_command(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)443 static int op_enter_command(struct IndexSharedData *shared,
444                             struct IndexPrivateData *priv, int op)
445 {
446   mutt_enter_command();
447   mutt_check_rescore(shared->mailbox);
448   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
449 
450   return IR_SUCCESS;
451 }
452 
453 /**
454  * op_exit - Exit this menu - Implements ::index_function_t - @ingroup index_function_api
455  */
op_exit(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)456 static int op_exit(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
457 {
458   if ((!priv->in_pager) && priv->attach_msg)
459     return IR_DONE;
460 
461   const enum QuadOption c_quit = cs_subset_quad(shared->sub, "quit");
462   if ((!priv->in_pager) &&
463       (query_quadoption(c_quit, _("Exit NeoMutt without saving?")) == MUTT_YES))
464   {
465     if (shared->ctx)
466     {
467       mx_fastclose_mailbox(shared->mailbox);
468       ctx_free(&shared->ctx);
469     }
470     return IR_DONE;
471   }
472 
473   return IR_NO_ACTION;
474 }
475 
476 /**
477  * op_extract_keys - Extract supported public keys - Implements ::index_function_t - @ingroup index_function_api
478  */
op_extract_keys(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)479 static int op_extract_keys(struct IndexSharedData *shared,
480                            struct IndexPrivateData *priv, int op)
481 {
482   if (!WithCrypto)
483     return IR_NOT_IMPL;
484   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
485   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
486   crypt_extract_keys_from_messages(shared->mailbox, &el);
487   emaillist_clear(&el);
488   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
489 
490   return IR_SUCCESS;
491 }
492 
493 /**
494  * op_flag_message - Toggle a message's 'important' flag - Implements ::index_function_t - @ingroup index_function_api
495  */
op_flag_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)496 static int op_flag_message(struct IndexSharedData *shared,
497                            struct IndexPrivateData *priv, int op)
498 {
499   /* L10N: CHECK_ACL */
500   if (!check_acl(shared->mailbox, MUTT_ACL_WRITE, _("Can't flag message")))
501     return IR_ERROR;
502 
503   struct Mailbox *m = shared->mailbox;
504   if (priv->tag)
505   {
506     for (size_t i = 0; i < m->msg_count; i++)
507     {
508       struct Email *e = m->emails[i];
509       if (!e)
510         break;
511       if (message_is_tagged(e))
512         mutt_set_flag(m, e, MUTT_FLAG, !e->flagged);
513     }
514 
515     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
516   }
517   else
518   {
519     if (!shared->email)
520       return IR_NO_ACTION;
521     mutt_set_flag(m, shared->email, MUTT_FLAG, !shared->email->flagged);
522     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
523     if (c_resolve)
524     {
525       int index = menu_get_index(priv->menu);
526       index = ci_next_undeleted(shared->mailbox, index);
527       if (index == -1)
528       {
529         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
530       }
531       else
532       {
533         menu_set_index(priv->menu, index);
534       }
535     }
536     else
537       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
538   }
539 
540   return IR_SUCCESS;
541 }
542 
543 /**
544  * op_forget_passphrase - Wipe passphrases from memory - Implements ::index_function_t - @ingroup index_function_api
545  */
op_forget_passphrase(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)546 static int op_forget_passphrase(struct IndexSharedData *shared,
547                                 struct IndexPrivateData *priv, int op)
548 {
549   crypt_forget_passphrase();
550   return IR_SUCCESS;
551 }
552 
553 /**
554  * op_forward_message - Forward a message with comments - Implements ::index_function_t - @ingroup index_function_api
555  */
op_forward_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)556 static int op_forward_message(struct IndexSharedData *shared,
557                               struct IndexPrivateData *priv, int op)
558 {
559   if (!shared->email)
560     return IR_NO_ACTION;
561   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
562   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
563   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
564   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
565   {
566     if (mutt_check_traditional_pgp(shared->mailbox, &el))
567       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
568   }
569   int rc =
570       mutt_send_message(SEND_FORWARD, NULL, NULL, shared->mailbox, &el, shared->sub);
571   emaillist_clear(&el);
572   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
573 
574   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
575 }
576 
577 /**
578  * op_group_reply - Reply to all recipients - Implements ::index_function_t - @ingroup index_function_api
579  */
op_group_reply(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)580 static int op_group_reply(struct IndexSharedData *shared,
581                           struct IndexPrivateData *priv, int op)
582 {
583   SendFlags replyflags = SEND_REPLY;
584   if (op == OP_GROUP_REPLY)
585     replyflags |= SEND_GROUP_REPLY;
586   else
587     replyflags |= SEND_GROUP_CHAT_REPLY;
588   if (!shared->email)
589     return IR_NO_ACTION;
590   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
591   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
592   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
593   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
594   {
595     if (mutt_check_traditional_pgp(shared->mailbox, &el))
596       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
597   }
598   int rc =
599       mutt_send_message(replyflags, NULL, NULL, shared->mailbox, &el, shared->sub);
600   emaillist_clear(&el);
601   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
602 
603   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
604 }
605 
606 /**
607  * op_help - This screen - Implements ::index_function_t - @ingroup index_function_api
608  */
op_help(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)609 static int op_help(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
610 {
611   mutt_help(MENU_MAIN);
612   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
613   return IR_SUCCESS;
614 }
615 
616 /**
617  * op_jump - Jump to an index number - Implements ::index_function_t - @ingroup index_function_api
618  */
op_jump(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)619 static int op_jump(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
620 {
621   char buf[PATH_MAX] = { 0 };
622   int msg_num = 0;
623   if (isdigit(LastKey))
624     mutt_unget_event(LastKey, 0);
625   if ((mutt_get_field(_("Jump to message: "), buf, sizeof(buf),
626                       MUTT_COMP_NO_FLAGS, false, NULL, NULL) != 0) ||
627       (buf[0] == '\0'))
628   {
629     mutt_error(_("Nothing to do"));
630   }
631   else if (mutt_str_atoi(buf, &msg_num) < 0)
632     mutt_error(_("Argument must be a message number"));
633   else if ((msg_num < 1) || (msg_num > shared->mailbox->msg_count))
634     mutt_error(_("Invalid message number"));
635   else if (!shared->mailbox->emails[msg_num - 1]->visible)
636     mutt_error(_("That message is not visible"));
637   else
638   {
639     struct Email *e = shared->mailbox->emails[msg_num - 1];
640 
641     if (mutt_messages_in_thread(shared->mailbox, e, MIT_POSITION) > 1)
642     {
643       mutt_uncollapse_thread(e);
644       mutt_set_vnum(shared->mailbox);
645     }
646     menu_set_index(priv->menu, e->vnum);
647   }
648 
649   if (priv->in_pager)
650     return IR_CONTINUE;
651 
652   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
653   return IR_SUCCESS;
654 }
655 
656 /**
657  * op_list_reply - Reply to specified mailing list - Implements ::index_function_t - @ingroup index_function_api
658  */
op_list_reply(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)659 static int op_list_reply(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
660 {
661   if (!shared->email)
662     return IR_NO_ACTION;
663   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
664   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
665   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
666   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
667   {
668     if (mutt_check_traditional_pgp(shared->mailbox, &el))
669       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
670   }
671   int rc = mutt_send_message(SEND_REPLY | SEND_LIST_REPLY, NULL, NULL,
672                              shared->mailbox, &el, shared->sub);
673   emaillist_clear(&el);
674   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
675 
676   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
677 }
678 
679 /**
680  * op_list_subscribe - Subscribe to a mailing list - Implements ::index_function_t - @ingroup index_function_api
681  */
op_list_subscribe(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)682 static int op_list_subscribe(struct IndexSharedData *shared,
683                              struct IndexPrivateData *priv, int op)
684 {
685   return mutt_send_list_subscribe(shared->mailbox, shared->email) ? IR_SUCCESS : IR_NO_ACTION;
686 }
687 
688 /**
689  * op_list_unsubscribe - Unsubscribe from mailing list - Implements ::index_function_t - @ingroup index_function_api
690  */
op_list_unsubscribe(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)691 static int op_list_unsubscribe(struct IndexSharedData *shared,
692                                struct IndexPrivateData *priv, int op)
693 {
694   return mutt_send_list_unsubscribe(shared->mailbox, shared->email) ? IR_SUCCESS : IR_NO_ACTION;
695 }
696 
697 /**
698  * op_mail - Compose a new mail message - Implements ::index_function_t - @ingroup index_function_api
699  */
op_mail(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)700 static int op_mail(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
701 {
702   int rc = mutt_send_message(SEND_NO_FLAGS, NULL, NULL, shared->mailbox, NULL,
703                              shared->sub);
704   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
705   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
706 }
707 
708 /**
709  * op_mailbox_list - List mailboxes with new mail - Implements ::index_function_t - @ingroup index_function_api
710  */
op_mailbox_list(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)711 static int op_mailbox_list(struct IndexSharedData *shared,
712                            struct IndexPrivateData *priv, int op)
713 {
714   mutt_mailbox_list();
715   return IR_SUCCESS;
716 }
717 
718 /**
719  * op_mail_key - Mail a PGP public key - Implements ::index_function_t - @ingroup index_function_api
720  */
op_mail_key(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)721 static int op_mail_key(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
722 {
723   if (!(WithCrypto & APPLICATION_PGP))
724     return IR_NOT_IMPL;
725   int rc = mutt_send_message(SEND_KEY, NULL, NULL, NULL, NULL, shared->sub);
726   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
727 
728   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
729 }
730 
731 /**
732  * op_main_break_thread - Break the thread in two - Implements ::index_function_t - @ingroup index_function_api
733  */
op_main_break_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)734 static int op_main_break_thread(struct IndexSharedData *shared,
735                                 struct IndexPrivateData *priv, int op)
736 {
737   /* L10N: CHECK_ACL */
738   if (!check_acl(shared->mailbox, MUTT_ACL_WRITE, _("Can't break thread")))
739     return IR_ERROR;
740   if (!shared->email)
741     return IR_NO_ACTION;
742 
743   if (!mutt_using_threads())
744     mutt_error(_("Threading is not enabled"));
745   else if (!STAILQ_EMPTY(&shared->email->env->in_reply_to) ||
746            !STAILQ_EMPTY(&shared->email->env->references))
747   {
748     {
749       mutt_break_thread(shared->email);
750       mutt_sort_headers(shared->mailbox, shared->ctx->threads, true,
751                         &shared->ctx->vsize);
752       menu_set_index(priv->menu, shared->email->vnum);
753     }
754 
755     shared->mailbox->changed = true;
756     mutt_message(_("Thread broken"));
757 
758     if (priv->in_pager)
759       return IR_CONTINUE;
760 
761     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
762   }
763   else
764   {
765     mutt_error(_("Thread can't be broken, message is not part of a thread"));
766   }
767 
768   return IR_SUCCESS;
769 }
770 
771 /**
772  * op_main_change_folder - Open a different folder - Implements ::index_function_t - @ingroup index_function_api
773  */
op_main_change_folder(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)774 static int op_main_change_folder(struct IndexSharedData *shared,
775                                  struct IndexPrivateData *priv, int op)
776 {
777   bool pager_return = true; /* return to display message in pager */
778   struct Buffer *folderbuf = mutt_buffer_pool_get();
779   mutt_buffer_alloc(folderbuf, PATH_MAX);
780 
781   char *cp = NULL;
782   bool read_only;
783   const bool c_read_only = cs_subset_bool(shared->sub, "read_only");
784   if (priv->attach_msg || c_read_only || (op == OP_MAIN_CHANGE_FOLDER_READONLY))
785   {
786     cp = _("Open mailbox in read-only mode");
787     read_only = true;
788   }
789   else
790   {
791     cp = _("Open mailbox");
792     read_only = false;
793   }
794 
795   const bool c_change_folder_next =
796       cs_subset_bool(shared->sub, "change_folder_next");
797   if (c_change_folder_next && shared->mailbox &&
798       !mutt_buffer_is_empty(&shared->mailbox->pathbuf))
799   {
800     mutt_buffer_strcpy(folderbuf, mailbox_path(shared->mailbox));
801     mutt_buffer_pretty_mailbox(folderbuf);
802   }
803   /* By default, fill buf with the next mailbox that contains unread mail */
804   mutt_mailbox_next(shared->ctx ? shared->mailbox : NULL, folderbuf);
805 
806   if (mutt_buffer_enter_fname(cp, folderbuf, true, shared->mailbox, false, NULL,
807                               NULL, MUTT_SEL_NO_FLAGS) == -1)
808   {
809     goto changefoldercleanup;
810   }
811 
812   /* Selected directory is okay, let's save it. */
813   mutt_browser_select_dir(mutt_buffer_string(folderbuf));
814 
815   if (mutt_buffer_is_empty(folderbuf))
816   {
817     msgwin_clear_text();
818     goto changefoldercleanup;
819   }
820 
821   struct Mailbox *m = mx_mbox_find2(mutt_buffer_string(folderbuf));
822   if (m)
823   {
824     change_folder_mailbox(priv->menu, m, &priv->oldcount, shared, read_only);
825     pager_return = false;
826   }
827   else
828   {
829     change_folder_string(priv->menu, folderbuf->data, folderbuf->dsize,
830                          &priv->oldcount, shared, &pager_return, read_only);
831   }
832 
833 changefoldercleanup:
834   mutt_buffer_pool_release(&folderbuf);
835   if (priv->in_pager && pager_return)
836     return IR_CONTINUE;
837 
838   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
839 
840   return IR_SUCCESS;
841 }
842 
843 /**
844  * op_main_collapse_all - Collapse/uncollapse all threads - Implements ::index_function_t - @ingroup index_function_api
845  */
op_main_collapse_all(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)846 static int op_main_collapse_all(struct IndexSharedData *shared,
847                                 struct IndexPrivateData *priv, int op)
848 {
849   if (!mutt_using_threads())
850   {
851     mutt_error(_("Threading is not enabled"));
852     return IR_ERROR;
853   }
854   collapse_all(shared->ctx, priv->menu, 1);
855 
856   return IR_SUCCESS;
857 }
858 
859 /**
860  * op_main_collapse_thread - Collapse/uncollapse current thread - Implements ::index_function_t - @ingroup index_function_api
861  */
op_main_collapse_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)862 static int op_main_collapse_thread(struct IndexSharedData *shared,
863                                    struct IndexPrivateData *priv, int op)
864 {
865   if (!mutt_using_threads())
866   {
867     mutt_error(_("Threading is not enabled"));
868     return IR_ERROR;
869   }
870 
871   if (!shared->email)
872     return IR_NO_ACTION;
873 
874   if (shared->email->collapsed)
875   {
876     int index = mutt_uncollapse_thread(shared->email);
877     mutt_set_vnum(shared->mailbox);
878     const bool c_uncollapse_jump =
879         cs_subset_bool(shared->sub, "uncollapse_jump");
880     if (c_uncollapse_jump)
881       index = mutt_thread_next_unread(shared->email);
882     menu_set_index(priv->menu, index);
883   }
884   else if (mutt_thread_can_collapse(shared->email))
885   {
886     menu_set_index(priv->menu, mutt_collapse_thread(shared->email));
887     mutt_set_vnum(shared->mailbox);
888   }
889   else
890   {
891     mutt_error(_("Thread contains unread or flagged messages"));
892     return IR_ERROR;
893   }
894 
895   menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
896 
897   return IR_SUCCESS;
898 }
899 
900 /**
901  * op_main_delete_pattern - Delete messages matching a pattern - Implements ::index_function_t - @ingroup index_function_api
902  */
op_main_delete_pattern(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)903 static int op_main_delete_pattern(struct IndexSharedData *shared,
904                                   struct IndexPrivateData *priv, int op)
905 {
906   /* L10N: CHECK_ACL */
907   /* L10N: Due to the implementation details we do not know whether we
908      delete zero, 1, 12, ... messages. So in English we use
909      "messages". Your language might have other means to express this.  */
910   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't delete messages")))
911     return IR_ERROR;
912 
913   mutt_pattern_func(shared->ctx, MUTT_DELETE, _("Delete messages matching: "));
914   menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
915 
916   return IR_SUCCESS;
917 }
918 
919 /**
920  * op_main_limit - Limit view to current thread - Implements ::index_function_t - @ingroup index_function_api
921  */
op_main_limit(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)922 static int op_main_limit(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
923 {
924   const bool lmt = ctx_has_limit(shared->ctx);
925   int old_index = shared->email ? shared->email->index : -1;
926   if (op == OP_TOGGLE_READ)
927   {
928     char buf2[1024];
929 
930     if (!lmt || !mutt_strn_equal(shared->ctx->pattern, "!~R!~D~s", 8))
931     {
932       snprintf(buf2, sizeof(buf2), "!~R!~D~s%s", lmt ? shared->ctx->pattern : ".*");
933     }
934     else
935     {
936       mutt_str_copy(buf2, shared->ctx->pattern + 8, sizeof(buf2));
937       if ((*buf2 == '\0') || mutt_strn_equal(buf2, ".*", 2))
938         snprintf(buf2, sizeof(buf2), "~A");
939     }
940     mutt_str_replace(&shared->ctx->pattern, buf2);
941     mutt_pattern_func(shared->ctx, MUTT_LIMIT, NULL);
942   }
943 
944   if (((op == OP_LIMIT_CURRENT_THREAD) &&
945        mutt_limit_current_thread(shared->ctx, shared->email)) ||
946       (op == OP_TOGGLE_READ) ||
947       ((op == OP_MAIN_LIMIT) &&
948        (mutt_pattern_func(shared->ctx, MUTT_LIMIT, _("Limit to messages matching: ")) == 0)))
949   {
950     int index = 0;
951     if (old_index >= 0)
952     {
953       /* try to find what used to be the current message */
954       for (size_t i = 0; i < shared->mailbox->vcount; i++)
955       {
956         struct Email *e = mutt_get_virt_email(shared->mailbox, i);
957         if (!e)
958           continue;
959         if (e->index == old_index)
960         {
961           index = i;
962           break;
963         }
964       }
965     }
966     menu_set_index(priv->menu, index);
967 
968     if ((shared->mailbox->msg_count != 0) && mutt_using_threads())
969     {
970       const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all");
971       if (c_collapse_all)
972         collapse_all(shared->ctx, priv->menu, 0);
973       mutt_draw_tree(shared->ctx->threads);
974     }
975     menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
976   }
977   if (lmt)
978     mutt_message(_("To view all messages, limit to \"all\""));
979 
980   return IR_SUCCESS;
981 }
982 
983 /**
984  * op_main_link_threads - Link tagged message to the current one - Implements ::index_function_t - @ingroup index_function_api
985  */
op_main_link_threads(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)986 static int op_main_link_threads(struct IndexSharedData *shared,
987                                 struct IndexPrivateData *priv, int op)
988 {
989   /* L10N: CHECK_ACL */
990   if (!check_acl(shared->mailbox, MUTT_ACL_WRITE, _("Can't link threads")))
991     return IR_ERROR;
992   if (!shared->email)
993     return IR_NO_ACTION;
994 
995   enum IndexRetval rc = IR_ERROR;
996 
997   if (!mutt_using_threads())
998     mutt_error(_("Threading is not enabled"));
999   else if (!shared->email->env->message_id)
1000     mutt_error(_("No Message-ID: header available to link thread"));
1001   else
1002   {
1003     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1004     el_add_tagged(&el, shared->ctx, NULL, true);
1005 
1006     if (mutt_link_threads(shared->email, &el, shared->mailbox))
1007     {
1008       mutt_sort_headers(shared->mailbox, shared->ctx->threads, true,
1009                         &shared->ctx->vsize);
1010       menu_set_index(priv->menu, shared->email->vnum);
1011 
1012       shared->mailbox->changed = true;
1013       mutt_message(_("Threads linked"));
1014       rc = IR_SUCCESS;
1015     }
1016     else
1017     {
1018       mutt_error(_("No thread linked"));
1019       rc = IR_NO_ACTION;
1020     }
1021 
1022     emaillist_clear(&el);
1023   }
1024 
1025   if (priv->in_pager)
1026     return IR_CONTINUE;
1027 
1028   menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1029   return rc;
1030 }
1031 
1032 /**
1033  * op_main_modify_tags - Modify (notmuch/imap) tags - Implements ::index_function_t - @ingroup index_function_api
1034  */
op_main_modify_tags(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1035 static int op_main_modify_tags(struct IndexSharedData *shared,
1036                                struct IndexPrivateData *priv, int op)
1037 {
1038   if (!shared->mailbox)
1039     return IR_ERROR;
1040   struct Mailbox *m = shared->mailbox;
1041   if (!mx_tags_is_supported(m))
1042   {
1043     mutt_message(_("Folder doesn't support tagging, aborting"));
1044     return IR_ERROR;
1045   }
1046   if (!shared->email)
1047     return IR_NO_ACTION;
1048   char *tags = NULL;
1049   if (!priv->tag)
1050     tags = driver_tags_get_with_hidden(&shared->email->tags);
1051   char buf[PATH_MAX] = { 0 };
1052   int rc = mx_tags_edit(m, tags, buf, sizeof(buf));
1053   FREE(&tags);
1054   if (rc < 0)
1055     return IR_ERROR;
1056   else if (rc == 0)
1057   {
1058     mutt_message(_("No tag specified, aborting"));
1059     return IR_ERROR;
1060   }
1061 
1062   if (priv->tag)
1063   {
1064     struct Progress *progress = NULL;
1065 
1066     if (m->verbose)
1067     {
1068       progress = progress_new(_("Update tags..."), MUTT_PROGRESS_WRITE, m->msg_tagged);
1069     }
1070 
1071 #ifdef USE_NOTMUCH
1072     if (m->type == MUTT_NOTMUCH)
1073       nm_db_longrun_init(m, true);
1074 #endif
1075     for (int px = 0, i = 0; i < m->msg_count; i++)
1076     {
1077       struct Email *e = m->emails[i];
1078       if (!e)
1079         break;
1080       if (!message_is_tagged(e))
1081         continue;
1082 
1083       if (m->verbose)
1084         progress_update(progress, ++px, -1);
1085       mx_tags_commit(m, e, buf);
1086       if (op == OP_MAIN_MODIFY_TAGS_THEN_HIDE)
1087       {
1088         bool still_queried = false;
1089 #ifdef USE_NOTMUCH
1090         if (m->type == MUTT_NOTMUCH)
1091           still_queried = nm_message_is_still_queried(m, e);
1092 #endif
1093         e->quasi_deleted = !still_queried;
1094         m->changed = true;
1095       }
1096     }
1097     progress_free(&progress);
1098 #ifdef USE_NOTMUCH
1099     if (m->type == MUTT_NOTMUCH)
1100       nm_db_longrun_done(m);
1101 #endif
1102     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1103   }
1104   else
1105   {
1106     if (mx_tags_commit(m, shared->email, buf))
1107     {
1108       mutt_message(_("Failed to modify tags, aborting"));
1109       return IR_ERROR;
1110     }
1111     if (op == OP_MAIN_MODIFY_TAGS_THEN_HIDE)
1112     {
1113       bool still_queried = false;
1114 #ifdef USE_NOTMUCH
1115       if (m->type == MUTT_NOTMUCH)
1116         still_queried = nm_message_is_still_queried(m, shared->email);
1117 #endif
1118       shared->email->quasi_deleted = !still_queried;
1119       m->changed = true;
1120     }
1121     if (priv->in_pager)
1122       return IR_CONTINUE;
1123 
1124     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
1125     if (c_resolve)
1126     {
1127       int index = menu_get_index(priv->menu);
1128       index = ci_next_undeleted(shared->mailbox, index);
1129       if (index == -1)
1130       {
1131         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
1132       }
1133       else
1134       {
1135         menu_set_index(priv->menu, index);
1136       }
1137     }
1138     else
1139       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
1140   }
1141 
1142   return IR_SUCCESS;
1143 }
1144 
1145 /**
1146  * op_main_next_new - Jump to the next new message - Implements ::index_function_t - @ingroup index_function_api
1147  */
op_main_next_new(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1148 static int op_main_next_new(struct IndexSharedData *shared,
1149                             struct IndexPrivateData *priv, int op)
1150 {
1151   int first_unread = -1;
1152   int first_new = -1;
1153 
1154   const int saved_current = menu_get_index(priv->menu);
1155   int mcur = saved_current;
1156   int index = -1;
1157   const bool threaded = mutt_using_threads();
1158   for (size_t i = 0; i != shared->mailbox->vcount; i++)
1159   {
1160     if ((op == OP_MAIN_NEXT_NEW) || (op == OP_MAIN_NEXT_UNREAD) ||
1161         (op == OP_MAIN_NEXT_NEW_THEN_UNREAD))
1162     {
1163       mcur++;
1164       if (mcur > (shared->mailbox->vcount - 1))
1165       {
1166         mcur = 0;
1167       }
1168     }
1169     else
1170     {
1171       mcur--;
1172       if (mcur < 0)
1173       {
1174         mcur = shared->mailbox->vcount - 1;
1175       }
1176     }
1177 
1178     struct Email *e = mutt_get_virt_email(shared->mailbox, mcur);
1179     if (!e)
1180       break;
1181     if (e->collapsed && threaded)
1182     {
1183       int unread = mutt_thread_contains_unread(e);
1184       if ((unread != 0) && (first_unread == -1))
1185         first_unread = mcur;
1186       if ((unread == 1) && (first_new == -1))
1187         first_new = mcur;
1188     }
1189     else if (!e->deleted && !e->read)
1190     {
1191       if (first_unread == -1)
1192         first_unread = mcur;
1193       if (!e->old && (first_new == -1))
1194         first_new = mcur;
1195     }
1196 
1197     if (((op == OP_MAIN_NEXT_UNREAD) || (op == OP_MAIN_PREV_UNREAD)) && (first_unread != -1))
1198     {
1199       break;
1200     }
1201     if (((op == OP_MAIN_NEXT_NEW) || (op == OP_MAIN_PREV_NEW) ||
1202          (op == OP_MAIN_NEXT_NEW_THEN_UNREAD) || (op == OP_MAIN_PREV_NEW_THEN_UNREAD)) &&
1203         (first_new != -1))
1204     {
1205       break;
1206     }
1207   }
1208   if (((op == OP_MAIN_NEXT_NEW) || (op == OP_MAIN_PREV_NEW) ||
1209        (op == OP_MAIN_NEXT_NEW_THEN_UNREAD) || (op == OP_MAIN_PREV_NEW_THEN_UNREAD)) &&
1210       (first_new != -1))
1211   {
1212     index = first_new;
1213   }
1214   else if (((op == OP_MAIN_NEXT_UNREAD) || (op == OP_MAIN_PREV_UNREAD) ||
1215             (op == OP_MAIN_NEXT_NEW_THEN_UNREAD) || (op == OP_MAIN_PREV_NEW_THEN_UNREAD)) &&
1216            (first_unread != -1))
1217   {
1218     index = first_unread;
1219   }
1220 
1221   if (index == -1)
1222   {
1223     menu_set_index(priv->menu, saved_current);
1224     if ((op == OP_MAIN_NEXT_NEW) || (op == OP_MAIN_PREV_NEW))
1225     {
1226       if (ctx_has_limit(shared->ctx))
1227         mutt_error(_("No new messages in this limited view"));
1228       else
1229         mutt_error(_("No new messages"));
1230     }
1231     else
1232     {
1233       if (ctx_has_limit(shared->ctx))
1234         mutt_error(_("No unread messages in this limited view"));
1235       else
1236         mutt_error(_("No unread messages"));
1237     }
1238     return IR_ERROR;
1239   }
1240   else
1241   {
1242     menu_set_index(priv->menu, index);
1243   }
1244 
1245   index = menu_get_index(priv->menu);
1246   if ((op == OP_MAIN_NEXT_NEW) || (op == OP_MAIN_NEXT_UNREAD) ||
1247       (op == OP_MAIN_NEXT_NEW_THEN_UNREAD))
1248   {
1249     if (saved_current > index)
1250     {
1251       mutt_message(_("Search wrapped to top"));
1252     }
1253   }
1254   else if (saved_current < index)
1255   {
1256     mutt_message(_("Search wrapped to bottom"));
1257   }
1258 
1259   if (priv->in_pager)
1260     return IR_CONTINUE;
1261 
1262   menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1263   return IR_SUCCESS;
1264 }
1265 
1266 /**
1267  * op_main_next_thread - Jump to the next thread - Implements ::index_function_t - @ingroup index_function_api
1268  */
op_main_next_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1269 static int op_main_next_thread(struct IndexSharedData *shared,
1270                                struct IndexPrivateData *priv, int op)
1271 {
1272   int index = -1;
1273   switch (op)
1274   {
1275     case OP_MAIN_NEXT_THREAD:
1276       index = mutt_next_thread(shared->email);
1277       break;
1278 
1279     case OP_MAIN_NEXT_SUBTHREAD:
1280       index = mutt_next_subthread(shared->email);
1281       break;
1282 
1283     case OP_MAIN_PREV_THREAD:
1284       index = mutt_previous_thread(shared->email);
1285       break;
1286 
1287     case OP_MAIN_PREV_SUBTHREAD:
1288       index = mutt_previous_subthread(shared->email);
1289       break;
1290   }
1291 
1292   if (index != -1)
1293     menu_set_index(priv->menu, index);
1294 
1295   if (index < 0)
1296   {
1297     if ((op == OP_MAIN_NEXT_THREAD) || (op == OP_MAIN_NEXT_SUBTHREAD))
1298       mutt_error(_("No more threads"));
1299     else
1300       mutt_error(_("You are on the first thread"));
1301   }
1302   else if (priv->in_pager)
1303   {
1304     return IR_CONTINUE;
1305   }
1306   else
1307     menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1308 
1309   return IR_SUCCESS;
1310 }
1311 
1312 /**
1313  * op_main_next_undeleted - Move to the next undeleted message - Implements ::index_function_t - @ingroup index_function_api
1314  */
op_main_next_undeleted(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1315 static int op_main_next_undeleted(struct IndexSharedData *shared,
1316                                   struct IndexPrivateData *priv, int op)
1317 {
1318   int index = menu_get_index(priv->menu);
1319   if (index >= (shared->mailbox->vcount - 1))
1320   {
1321     if (!priv->in_pager)
1322       mutt_message(_("You are on the last message"));
1323     return IR_ERROR;
1324   }
1325   index = ci_next_undeleted(shared->mailbox, index);
1326   if (index != -1)
1327     menu_set_index(priv->menu, index);
1328 
1329   if (index == -1)
1330   {
1331     if (!priv->in_pager)
1332       mutt_error(_("No undeleted messages"));
1333   }
1334   else if (priv->in_pager)
1335   {
1336     return IR_CONTINUE;
1337   }
1338   else
1339     menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1340 
1341   return IR_SUCCESS;
1342 }
1343 
1344 /**
1345  * op_main_next_unread_mailbox - Open next mailbox with unread mail - Implements ::index_function_t - @ingroup index_function_api
1346  */
op_main_next_unread_mailbox(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1347 static int op_main_next_unread_mailbox(struct IndexSharedData *shared,
1348                                        struct IndexPrivateData *priv, int op)
1349 {
1350   struct Mailbox *m = shared->mailbox;
1351 
1352   struct Buffer *folderbuf = mutt_buffer_pool_get();
1353   mutt_buffer_strcpy(folderbuf, mailbox_path(m));
1354   m = mutt_mailbox_next_unread(m, folderbuf);
1355   mutt_buffer_pool_release(&folderbuf);
1356 
1357   if (!m)
1358   {
1359     mutt_error(_("No mailboxes have new mail"));
1360     return IR_ERROR;
1361   }
1362 
1363   change_folder_mailbox(priv->menu, m, &priv->oldcount, shared, false);
1364   return IR_SUCCESS;
1365 }
1366 
1367 /**
1368  * op_main_prev_undeleted - Move to the previous undeleted message - Implements ::index_function_t - @ingroup index_function_api
1369  */
op_main_prev_undeleted(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1370 static int op_main_prev_undeleted(struct IndexSharedData *shared,
1371                                   struct IndexPrivateData *priv, int op)
1372 {
1373   int index = menu_get_index(priv->menu);
1374   if (index < 1)
1375   {
1376     mutt_message(_("You are on the first message"));
1377     return IR_ERROR;
1378   }
1379   index = ci_previous_undeleted(shared->mailbox, index);
1380   if (index != -1)
1381     menu_set_index(priv->menu, index);
1382 
1383   if (index == -1)
1384   {
1385     if (!priv->in_pager)
1386       mutt_error(_("No undeleted messages"));
1387   }
1388   else if (priv->in_pager)
1389   {
1390     return IR_CONTINUE;
1391   }
1392   else
1393     menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1394 
1395   return IR_SUCCESS;
1396 }
1397 
1398 /**
1399  * op_main_quasi_delete - Delete from NeoMutt, don't touch on disk - Implements ::index_function_t - @ingroup index_function_api
1400  */
op_main_quasi_delete(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1401 static int op_main_quasi_delete(struct IndexSharedData *shared,
1402                                 struct IndexPrivateData *priv, int op)
1403 {
1404   if (priv->tag)
1405   {
1406     struct Mailbox *m = shared->mailbox;
1407     for (size_t i = 0; i < m->msg_count; i++)
1408     {
1409       struct Email *e = m->emails[i];
1410       if (!e)
1411         break;
1412       if (message_is_tagged(e))
1413       {
1414         e->quasi_deleted = true;
1415         m->changed = true;
1416       }
1417     }
1418   }
1419   else
1420   {
1421     if (!shared->email)
1422       return IR_NO_ACTION;
1423     shared->email->quasi_deleted = true;
1424     shared->mailbox->changed = true;
1425   }
1426 
1427   return IR_SUCCESS;
1428 }
1429 
1430 /**
1431  * op_main_read_thread - Mark the current thread as read - Implements ::index_function_t - @ingroup index_function_api
1432  */
op_main_read_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1433 static int op_main_read_thread(struct IndexSharedData *shared,
1434                                struct IndexPrivateData *priv, int op)
1435 {
1436   /* L10N: CHECK_ACL */
1437   /* L10N: Due to the implementation details we do not know whether we
1438      mark zero, 1, 12, ... messages as read. So in English we use
1439      "messages". Your language might have other means to express this. */
1440   if (!check_acl(shared->mailbox, MUTT_ACL_SEEN, _("Can't mark messages as read")))
1441     return IR_ERROR;
1442 
1443   int rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_READ, true,
1444                                 (op != OP_MAIN_READ_THREAD));
1445   if (rc != -1)
1446   {
1447     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
1448     if (c_resolve)
1449     {
1450       int index = ((op == OP_MAIN_READ_THREAD) ? mutt_next_thread(shared->email) :
1451                                                  mutt_next_subthread(shared->email));
1452       if (index != -1)
1453         menu_set_index(priv->menu, index);
1454 
1455       if (priv->in_pager)
1456       {
1457         return IR_CONTINUE;
1458       }
1459     }
1460     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1461   }
1462 
1463   return IR_SUCCESS;
1464 }
1465 
1466 /**
1467  * op_main_root_message - Jump to root message in thread - Implements ::index_function_t - @ingroup index_function_api
1468  */
op_main_root_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1469 static int op_main_root_message(struct IndexSharedData *shared,
1470                                 struct IndexPrivateData *priv, int op)
1471 {
1472   int index = mutt_parent_message(shared->email, op == OP_MAIN_ROOT_MESSAGE);
1473   if (index != -1)
1474     menu_set_index(priv->menu, index);
1475 
1476   if (priv->in_pager)
1477   {
1478     return IR_CONTINUE;
1479   }
1480   else
1481     menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1482 
1483   return IR_SUCCESS;
1484 }
1485 
1486 /**
1487  * op_main_set_flag - Set a status flag on a message - Implements ::index_function_t - @ingroup index_function_api
1488  */
op_main_set_flag(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1489 static int op_main_set_flag(struct IndexSharedData *shared,
1490                             struct IndexPrivateData *priv, int op)
1491 {
1492   /* check_acl(MUTT_ACL_WRITE); */
1493   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1494   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
1495 
1496   if (mutt_change_flag(shared->mailbox, &el, (op == OP_MAIN_SET_FLAG)) == 0)
1497   {
1498     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
1499     if (priv->tag)
1500       menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1501     else if (c_resolve)
1502     {
1503       int index = menu_get_index(priv->menu);
1504       index = ci_next_undeleted(shared->mailbox, index);
1505       if (index == -1)
1506       {
1507         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
1508       }
1509       else
1510       {
1511         menu_set_index(priv->menu, index);
1512       }
1513     }
1514     else
1515       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
1516   }
1517   emaillist_clear(&el);
1518 
1519   return IR_SUCCESS;
1520 }
1521 
1522 /**
1523  * op_main_show_limit - Show currently active limit pattern - Implements ::index_function_t - @ingroup index_function_api
1524  */
op_main_show_limit(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1525 static int op_main_show_limit(struct IndexSharedData *shared,
1526                               struct IndexPrivateData *priv, int op)
1527 {
1528   if (!ctx_has_limit(shared->ctx))
1529     mutt_message(_("No limit pattern is in effect"));
1530   else
1531   {
1532     char buf2[256];
1533     /* L10N: ask for a limit to apply */
1534     snprintf(buf2, sizeof(buf2), _("Limit: %s"), shared->ctx->pattern);
1535     mutt_message("%s", buf2);
1536   }
1537 
1538   return IR_SUCCESS;
1539 }
1540 
1541 /**
1542  * op_main_sync_folder - Save changes to mailbox - Implements ::index_function_t - @ingroup index_function_api
1543  */
op_main_sync_folder(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1544 static int op_main_sync_folder(struct IndexSharedData *shared,
1545                                struct IndexPrivateData *priv, int op)
1546 {
1547   if (!shared->mailbox || (shared->mailbox->msg_count == 0) || shared->mailbox->readonly)
1548     return IR_NO_ACTION;
1549 
1550   int ovc = shared->mailbox->vcount;
1551   int oc = shared->mailbox->msg_count;
1552   struct Email *e = NULL;
1553 
1554   /* don't attempt to move the cursor if there are no visible messages in the current limit */
1555   int index = menu_get_index(priv->menu);
1556   if (index < shared->mailbox->vcount)
1557   {
1558     /* threads may be reordered, so figure out what header the cursor
1559      * should be on. */
1560     int newidx = index;
1561     if (!shared->email)
1562       return IR_NO_ACTION;
1563     if (shared->email->deleted)
1564       newidx = ci_next_undeleted(shared->mailbox, index);
1565     if (newidx < 0)
1566       newidx = ci_previous_undeleted(shared->mailbox, index);
1567     if (newidx >= 0)
1568       e = mutt_get_virt_email(shared->mailbox, newidx);
1569   }
1570 
1571   enum MxStatus check = mx_mbox_sync(shared->mailbox);
1572   if (check == MX_STATUS_OK)
1573   {
1574     if (e && (shared->mailbox->vcount != ovc))
1575     {
1576       for (size_t i = 0; i < shared->mailbox->vcount; i++)
1577       {
1578         struct Email *e2 = mutt_get_virt_email(shared->mailbox, i);
1579         if (e2 == e)
1580         {
1581           menu_set_index(priv->menu, i);
1582           break;
1583         }
1584       }
1585     }
1586     OptSearchInvalid = true;
1587   }
1588   else if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
1589   {
1590     update_index(priv->menu, shared->ctx, check, oc, shared);
1591   }
1592 
1593   /* do a sanity check even if mx_mbox_sync failed.  */
1594 
1595   index = menu_get_index(priv->menu);
1596   if ((index < 0) || (shared->mailbox && (index >= shared->mailbox->vcount)))
1597   {
1598     menu_set_index(priv->menu, ci_first_message(shared->mailbox));
1599   }
1600 
1601   /* check for a fatal error, or all messages deleted */
1602   if (shared->mailbox && mutt_buffer_is_empty(&shared->mailbox->pathbuf))
1603   {
1604     ctx_free(&shared->ctx);
1605   }
1606 
1607   /* if we were in the pager, redisplay the message */
1608   if (priv->in_pager)
1609   {
1610     return IR_CONTINUE;
1611   }
1612   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1613 
1614   return IR_SUCCESS;
1615 }
1616 
1617 /**
1618  * op_main_tag_pattern - Tag messages matching a pattern - Implements ::index_function_t - @ingroup index_function_api
1619  */
op_main_tag_pattern(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1620 static int op_main_tag_pattern(struct IndexSharedData *shared,
1621                                struct IndexPrivateData *priv, int op)
1622 {
1623   mutt_pattern_func(shared->ctx, MUTT_TAG, _("Tag messages matching: "));
1624   menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1625 
1626   return IR_SUCCESS;
1627 }
1628 
1629 /**
1630  * op_main_undelete_pattern - Undelete messages matching a pattern - Implements ::index_function_t - @ingroup index_function_api
1631  */
op_main_undelete_pattern(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1632 static int op_main_undelete_pattern(struct IndexSharedData *shared,
1633                                     struct IndexPrivateData *priv, int op)
1634 {
1635   /* L10N: CHECK_ACL */
1636   /* L10N: Due to the implementation details we do not know whether we
1637      undelete zero, 1, 12, ... messages. So in English we use
1638      "messages". Your language might have other means to express this. */
1639   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't undelete messages")))
1640     return IR_ERROR;
1641 
1642   if (mutt_pattern_func(shared->ctx, MUTT_UNDELETE, _("Undelete messages matching: ")) == 0)
1643   {
1644     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1645   }
1646 
1647   return IR_SUCCESS;
1648 }
1649 
1650 /**
1651  * op_main_untag_pattern - Untag messages matching a pattern - Implements ::index_function_t - @ingroup index_function_api
1652  */
op_main_untag_pattern(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1653 static int op_main_untag_pattern(struct IndexSharedData *shared,
1654                                  struct IndexPrivateData *priv, int op)
1655 {
1656   if (mutt_pattern_func(shared->ctx, MUTT_UNTAG, _("Untag messages matching: ")) == 0)
1657     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
1658 
1659   return IR_SUCCESS;
1660 }
1661 
1662 /**
1663  * op_mark_msg - Create a hotkey macro for the current message - Implements ::index_function_t - @ingroup index_function_api
1664  */
op_mark_msg(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1665 static int op_mark_msg(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1666 {
1667   if (!shared->email)
1668     return IR_NO_ACTION;
1669   if (shared->email->env->message_id)
1670   {
1671     char buf2[128] = { 0 };
1672 
1673     /* L10N: This is the prompt for <mark-message>.  Whatever they
1674        enter will be prefixed by $mark_macro_prefix and will become
1675        a macro hotkey to jump to the currently selected message. */
1676     if (!mutt_get_field(_("Enter macro stroke: "), buf2, sizeof(buf2),
1677                         MUTT_COMP_NO_FLAGS, false, NULL, NULL) &&
1678         buf2[0])
1679     {
1680       const char *const c_mark_macro_prefix =
1681           cs_subset_string(shared->sub, "mark_macro_prefix");
1682       char str[256];
1683       snprintf(str, sizeof(str), "%s%s", c_mark_macro_prefix, buf2);
1684 
1685       struct Buffer *msg_id = mutt_buffer_pool_get();
1686       mutt_file_sanitize_regex(msg_id, shared->email->env->message_id);
1687       char macro[256];
1688       snprintf(macro, sizeof(macro), "<search>~i '%s'\n", mutt_buffer_string(msg_id));
1689       mutt_buffer_pool_release(&msg_id);
1690 
1691       /* L10N: "message hotkey" is the key bindings menu description of a
1692          macro created by <mark-message>. */
1693       km_bind(str, MENU_MAIN, OP_MACRO, macro, _("message hotkey"));
1694 
1695       /* L10N: This is echoed after <mark-message> creates a new hotkey
1696          macro.  %s is the hotkey string ($mark_macro_prefix followed
1697          by whatever they typed at the prompt.) */
1698       snprintf(buf2, sizeof(buf2), _("Message bound to %s"), str);
1699       mutt_message(buf2);
1700       mutt_debug(LL_DEBUG1, "Mark: %s => %s\n", str, macro);
1701     }
1702   }
1703   else
1704   {
1705     /* L10N: This error is printed if <mark-message> can't find a
1706        Message-ID for the currently selected message in the index. */
1707     mutt_error(_("No message ID to macro"));
1708     return IR_ERROR;
1709   }
1710 
1711   return IR_SUCCESS;
1712 }
1713 
1714 /**
1715  * op_menu_move - Move to the bottom of the page - Implements ::index_function_t - @ingroup index_function_api
1716  */
op_menu_move(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1717 static int op_menu_move(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1718 {
1719   switch (op)
1720   {
1721     case OP_BOTTOM_PAGE:
1722       menu_bottom_page(priv->menu);
1723       return IR_SUCCESS;
1724     case OP_CURRENT_BOTTOM:
1725       menu_current_bottom(priv->menu);
1726       return IR_SUCCESS;
1727     case OP_CURRENT_MIDDLE:
1728       menu_current_middle(priv->menu);
1729       return IR_SUCCESS;
1730     case OP_CURRENT_TOP:
1731       menu_current_top(priv->menu);
1732       return IR_SUCCESS;
1733     case OP_FIRST_ENTRY:
1734       menu_first_entry(priv->menu);
1735       return IR_SUCCESS;
1736     case OP_HALF_DOWN:
1737       menu_half_down(priv->menu);
1738       return IR_SUCCESS;
1739     case OP_HALF_UP:
1740       menu_half_up(priv->menu);
1741       return IR_SUCCESS;
1742     case OP_LAST_ENTRY:
1743       menu_last_entry(priv->menu);
1744       return IR_SUCCESS;
1745     case OP_MIDDLE_PAGE:
1746       menu_middle_page(priv->menu);
1747       return IR_SUCCESS;
1748     case OP_NEXT_LINE:
1749       menu_next_line(priv->menu);
1750       return IR_SUCCESS;
1751     case OP_NEXT_PAGE:
1752       menu_next_page(priv->menu);
1753       return IR_SUCCESS;
1754     case OP_PREV_LINE:
1755       menu_prev_line(priv->menu);
1756       return IR_SUCCESS;
1757     case OP_PREV_PAGE:
1758       menu_prev_page(priv->menu);
1759       return IR_SUCCESS;
1760     case OP_TOP_PAGE:
1761       menu_top_page(priv->menu);
1762       return IR_SUCCESS;
1763   }
1764 
1765   return IR_ERROR;
1766 }
1767 
1768 /**
1769  * op_next_entry - Move to the next entry - Implements ::index_function_t - @ingroup index_function_api
1770  */
op_next_entry(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1771 static int op_next_entry(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1772 {
1773   const int index = menu_get_index(priv->menu) + 1;
1774   if (index >= shared->mailbox->vcount)
1775   {
1776     if (!priv->in_pager)
1777       mutt_message(_("You are on the last message"));
1778     return IR_ERROR;
1779   }
1780   menu_set_index(priv->menu, index);
1781   if (priv->in_pager)
1782     return IR_CONTINUE;
1783 
1784   menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1785   return IR_SUCCESS;
1786 }
1787 
1788 /**
1789  * op_pipe - Pipe message/attachment to a shell command - Implements ::index_function_t - @ingroup index_function_api
1790  */
op_pipe(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1791 static int op_pipe(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1792 {
1793   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1794   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
1795   mutt_pipe_message(shared->mailbox, &el);
1796   emaillist_clear(&el);
1797 
1798 #ifdef USE_IMAP
1799   /* in an IMAP folder index with imap_peek=no, piping could change
1800    * new or old messages status to read. Redraw what's needed.  */
1801   const bool c_imap_peek = cs_subset_bool(shared->sub, "imap_peek");
1802   if ((shared->mailbox->type == MUTT_IMAP) && !c_imap_peek)
1803   {
1804     menu_queue_redraw(priv->menu, (priv->tag ? MENU_REDRAW_INDEX : MENU_REDRAW_CURRENT));
1805   }
1806 #endif
1807 
1808   return IR_SUCCESS;
1809 }
1810 
1811 /**
1812  * op_prev_entry - Move to the previous entry - Implements ::index_function_t - @ingroup index_function_api
1813  */
op_prev_entry(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1814 static int op_prev_entry(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1815 {
1816   int index = menu_get_index(priv->menu);
1817   if (index < 1)
1818   {
1819     if (!priv->in_pager)
1820       mutt_message(_("You are on the first message"));
1821     return IR_ERROR;
1822   }
1823   menu_set_index(priv->menu, index - 1);
1824   if (priv->in_pager)
1825     return IR_CONTINUE;
1826 
1827   menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
1828   return IR_SUCCESS;
1829 }
1830 
1831 /**
1832  * op_print - Print the current entry - Implements ::index_function_t - @ingroup index_function_api
1833  */
op_print(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1834 static int op_print(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1835 {
1836   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1837   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
1838   mutt_print_message(shared->mailbox, &el);
1839   emaillist_clear(&el);
1840 
1841 #ifdef USE_IMAP
1842   /* in an IMAP folder index with imap_peek=no, printing could change
1843    * new or old messages status to read. Redraw what's needed.  */
1844   const bool c_imap_peek = cs_subset_bool(shared->sub, "imap_peek");
1845   if ((shared->mailbox->type == MUTT_IMAP) && !c_imap_peek)
1846   {
1847     menu_queue_redraw(priv->menu, (priv->tag ? MENU_REDRAW_INDEX : MENU_REDRAW_CURRENT));
1848   }
1849 #endif
1850 
1851   return IR_SUCCESS;
1852 }
1853 
1854 /**
1855  * op_query - Query external program for addresses - Implements ::index_function_t - @ingroup index_function_api
1856  */
op_query(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1857 static int op_query(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1858 {
1859   query_index(shared->sub);
1860   return IR_SUCCESS;
1861 }
1862 
1863 /**
1864  * op_quit - Save changes to mailbox and quit - Implements ::index_function_t - @ingroup index_function_api
1865  */
op_quit(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1866 static int op_quit(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1867 {
1868   if (priv->attach_msg)
1869     return IR_DONE;
1870 
1871   const enum QuadOption c_quit = cs_subset_quad(shared->sub, "quit");
1872   if (query_quadoption(c_quit, _("Quit NeoMutt?")) == MUTT_YES)
1873   {
1874     priv->oldcount = shared->mailbox ? shared->mailbox->msg_count : 0;
1875 
1876     mutt_startup_shutdown_hook(MUTT_SHUTDOWN_HOOK);
1877     mutt_debug(LL_NOTIFY, "NT_GLOBAL_SHUTDOWN\n");
1878     notify_send(NeoMutt->notify, NT_GLOBAL, NT_GLOBAL_SHUTDOWN, NULL);
1879 
1880     enum MxStatus check = MX_STATUS_OK;
1881     if (!shared->ctx || ((check = mx_mbox_close(shared->mailbox)) == MX_STATUS_OK))
1882     {
1883       ctx_free(&shared->ctx);
1884       if (shared->mailbox && (shared->mailbox->flags == MB_HIDDEN))
1885         mailbox_free(&shared->mailbox);
1886       return IR_DONE;
1887     }
1888 
1889     if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
1890     {
1891       update_index(priv->menu, shared->ctx, check, priv->oldcount, shared);
1892     }
1893 
1894     menu_queue_redraw(priv->menu, MENU_REDRAW_FULL); /* new mail arrived? */
1895     OptSearchInvalid = true;
1896   }
1897 
1898   return IR_NO_ACTION;
1899 }
1900 
1901 /**
1902  * op_recall_message - Recall a postponed message - Implements ::index_function_t - @ingroup index_function_api
1903  */
op_recall_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1904 static int op_recall_message(struct IndexSharedData *shared,
1905                              struct IndexPrivateData *priv, int op)
1906 {
1907   int rc = mutt_send_message(SEND_POSTPONED, NULL, NULL, shared->mailbox, NULL,
1908                              shared->sub);
1909   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1910   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
1911 }
1912 
1913 /**
1914  * op_redraw - Clear and redraw the screen - Implements ::index_function_t - @ingroup index_function_api
1915  */
op_redraw(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1916 static int op_redraw(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1917 {
1918   window_invalidate_all();
1919   mutt_window_reflow(NULL);
1920   clearok(stdscr, true);
1921   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1922   return IR_SUCCESS;
1923 }
1924 
1925 /**
1926  * op_reply - Reply to a message - Implements ::index_function_t - @ingroup index_function_api
1927  */
op_reply(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1928 static int op_reply(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1929 {
1930   if (!shared->email)
1931     return IR_NO_ACTION;
1932   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1933   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
1934   const bool c_pgp_auto_decode = cs_subset_bool(shared->sub, "pgp_auto_decode");
1935   if (c_pgp_auto_decode && (priv->tag || !(shared->email->security & PGP_TRADITIONAL_CHECKED)))
1936   {
1937     if (mutt_check_traditional_pgp(shared->mailbox, &el))
1938       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1939   }
1940   int rc =
1941       mutt_send_message(SEND_REPLY, NULL, NULL, shared->mailbox, &el, shared->sub);
1942   emaillist_clear(&el);
1943   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1944 
1945   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
1946 }
1947 
1948 /**
1949  * op_resend - Use the current message as a template for a new one - Implements ::index_function_t - @ingroup index_function_api
1950  */
op_resend(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1951 static int op_resend(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1952 {
1953   int rc = -1;
1954   if (priv->tag)
1955   {
1956     struct Mailbox *m = shared->mailbox;
1957     for (size_t i = 0; i < m->msg_count; i++)
1958     {
1959       struct Email *e = m->emails[i];
1960       if (!e)
1961         break;
1962       if (message_is_tagged(e))
1963         rc = mutt_resend_message(NULL, shared->mailbox, e, shared->sub);
1964     }
1965   }
1966   else
1967   {
1968     rc = mutt_resend_message(NULL, shared->mailbox, shared->email, shared->sub);
1969   }
1970 
1971   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1972   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
1973 }
1974 
1975 /**
1976  * op_save - Make decrypted copy - Implements ::index_function_t - @ingroup index_function_api
1977  */
op_save(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)1978 static int op_save(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
1979 {
1980   if (((op == OP_DECRYPT_COPY) || (op == OP_DECRYPT_SAVE)) && !WithCrypto)
1981     return IR_NOT_IMPL;
1982 
1983   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1984   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
1985 
1986   const enum MessageSaveOpt save_opt =
1987       ((op == OP_SAVE) || (op == OP_DECODE_SAVE) || (op == OP_DECRYPT_SAVE)) ? SAVE_MOVE : SAVE_COPY;
1988 
1989   enum MessageTransformOpt transform_opt =
1990       ((op == OP_DECODE_SAVE) || (op == OP_DECODE_COPY))   ? TRANSFORM_DECODE :
1991       ((op == OP_DECRYPT_SAVE) || (op == OP_DECRYPT_COPY)) ? TRANSFORM_DECRYPT :
1992                                                              TRANSFORM_NONE;
1993 
1994   const int rc = mutt_save_message(shared->mailbox, &el, save_opt, transform_opt);
1995   if ((rc == 0) && (save_opt == SAVE_MOVE))
1996   {
1997     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
1998     if (priv->tag)
1999       menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2000     else if (c_resolve)
2001     {
2002       int index = menu_get_index(priv->menu);
2003       index = ci_next_undeleted(shared->mailbox, index);
2004       if (index == -1)
2005       {
2006         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2007       }
2008       else
2009       {
2010         menu_set_index(priv->menu, index);
2011       }
2012     }
2013     else
2014       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2015   }
2016   emaillist_clear(&el);
2017 
2018   return (rc == -1) ? IR_ERROR : IR_SUCCESS;
2019 }
2020 
2021 /**
2022  * op_search - Search for a regular expression - Implements ::index_function_t - @ingroup index_function_api
2023  */
op_search(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2024 static int op_search(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2025 {
2026   // Initiating a search can happen on an empty mailbox, but
2027   // searching for next/previous/... needs to be on a message and
2028   // thus a non-empty mailbox
2029   int index = menu_get_index(priv->menu);
2030   index = mutt_search_command(shared->mailbox, priv->menu, index, op);
2031   if (index != -1)
2032     menu_set_index(priv->menu, index);
2033   else
2034     menu_queue_redraw(priv->menu, MENU_REDRAW_MOTION);
2035 
2036   return IR_SUCCESS;
2037 }
2038 
2039 /**
2040  * op_shell_escape - Invoke a command in a subshell - Implements ::index_function_t - @ingroup index_function_api
2041  */
op_shell_escape(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2042 static int op_shell_escape(struct IndexSharedData *shared,
2043                            struct IndexPrivateData *priv, int op)
2044 {
2045   if (mutt_shell_escape())
2046   {
2047     mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_FORCE);
2048   }
2049 
2050   return IR_SUCCESS;
2051 }
2052 
2053 /**
2054  * op_show_log_messages - Show log (and debug) messages - Implements ::index_function_t - @ingroup index_function_api
2055  */
op_show_log_messages(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2056 static int op_show_log_messages(struct IndexSharedData *shared,
2057                                 struct IndexPrivateData *priv, int op)
2058 {
2059   char tempfile[PATH_MAX];
2060   mutt_mktemp(tempfile, sizeof(tempfile));
2061 
2062   FILE *fp = mutt_file_fopen(tempfile, "a+");
2063   if (!fp)
2064   {
2065     mutt_perror("fopen");
2066     return IR_ERROR;
2067   }
2068 
2069   log_queue_save(fp);
2070   mutt_file_fclose(&fp);
2071 
2072   struct PagerData pdata = { 0 };
2073   struct PagerView pview = { &pdata };
2074 
2075   pdata.fname = tempfile;
2076 
2077   pview.banner = "messages";
2078   pview.flags = MUTT_PAGER_LOGS | MUTT_PAGER_BOTTOM;
2079   pview.mode = PAGER_MODE_OTHER;
2080 
2081   mutt_do_pager(&pview, NULL);
2082 
2083   return IR_SUCCESS;
2084 }
2085 
2086 /**
2087  * op_sort - Sort messages - Implements ::index_function_t - @ingroup index_function_api
2088  */
op_sort(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2089 static int op_sort(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2090 {
2091   if (!mutt_select_sort(op == OP_SORT_REVERSE))
2092     return IR_ERROR;
2093 
2094   if (shared->mailbox && (shared->mailbox->msg_count != 0))
2095   {
2096     resort_index(shared->ctx, priv->menu);
2097     OptSearchInvalid = true;
2098   }
2099   if (priv->in_pager)
2100     return IR_CONTINUE;
2101 
2102   return IR_SUCCESS;
2103 }
2104 
2105 /**
2106  * op_tag - Tag the current entry - Implements ::index_function_t - @ingroup index_function_api
2107  */
op_tag(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2108 static int op_tag(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2109 {
2110   const bool c_auto_tag = cs_subset_bool(shared->sub, "auto_tag");
2111   if (priv->tag && !c_auto_tag)
2112   {
2113     struct Mailbox *m = shared->mailbox;
2114     for (size_t i = 0; i < m->msg_count; i++)
2115     {
2116       struct Email *e = m->emails[i];
2117       if (!e)
2118         break;
2119       if (e->visible)
2120         mutt_set_flag(m, e, MUTT_TAG, false);
2121     }
2122     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2123     return IR_SUCCESS;
2124   }
2125 
2126   if (!shared->email)
2127     return IR_NO_ACTION;
2128 
2129   mutt_set_flag(shared->mailbox, shared->email, MUTT_TAG, !shared->email->tagged);
2130 
2131   const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
2132   const int index = menu_get_index(priv->menu) + 1;
2133   if (c_resolve && (index < shared->mailbox->vcount))
2134   {
2135     menu_set_index(priv->menu, index);
2136   }
2137   else
2138   {
2139     menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2140   }
2141 
2142   return IR_SUCCESS;
2143 }
2144 
2145 /**
2146  * op_tag_thread - Tag the current thread - Implements ::index_function_t - @ingroup index_function_api
2147  */
op_tag_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2148 static int op_tag_thread(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2149 {
2150   if (!shared->email)
2151     return IR_NO_ACTION;
2152 
2153   int rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_TAG,
2154                                 !shared->email->tagged, (op != OP_TAG_THREAD));
2155   if (rc != -1)
2156   {
2157     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
2158     if (c_resolve)
2159     {
2160       int index;
2161       if (op == OP_TAG_THREAD)
2162         index = mutt_next_thread(shared->email);
2163       else
2164         index = mutt_next_subthread(shared->email);
2165 
2166       if (index != -1)
2167         menu_set_index(priv->menu, index);
2168     }
2169     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2170   }
2171 
2172   return IR_SUCCESS;
2173 }
2174 
2175 /**
2176  * op_toggle_new - Toggle a message's 'new' flag - Implements ::index_function_t - @ingroup index_function_api
2177  */
op_toggle_new(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2178 static int op_toggle_new(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2179 {
2180   /* L10N: CHECK_ACL */
2181   if (!check_acl(shared->mailbox, MUTT_ACL_SEEN, _("Can't toggle new")))
2182     return IR_ERROR;
2183 
2184   struct Mailbox *m = shared->mailbox;
2185   if (priv->tag)
2186   {
2187     for (size_t i = 0; i < m->msg_count; i++)
2188     {
2189       struct Email *e = m->emails[i];
2190       if (!e)
2191         break;
2192       if (!message_is_tagged(e))
2193         continue;
2194 
2195       if (e->read || e->old)
2196         mutt_set_flag(m, e, MUTT_NEW, true);
2197       else
2198         mutt_set_flag(m, e, MUTT_READ, true);
2199     }
2200     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2201   }
2202   else
2203   {
2204     if (!shared->email)
2205       return IR_NO_ACTION;
2206     if (shared->email->read || shared->email->old)
2207       mutt_set_flag(m, shared->email, MUTT_NEW, true);
2208     else
2209       mutt_set_flag(m, shared->email, MUTT_READ, true);
2210 
2211     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
2212     if (c_resolve)
2213     {
2214       int index = menu_get_index(priv->menu);
2215       index = ci_next_undeleted(shared->mailbox, index);
2216       if (index == -1)
2217       {
2218         menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2219       }
2220       else
2221       {
2222         menu_set_index(priv->menu, index);
2223       }
2224     }
2225     else
2226       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2227   }
2228 
2229   return IR_SUCCESS;
2230 }
2231 
2232 /**
2233  * op_toggle_write - Toggle whether the mailbox will be rewritten - Implements ::index_function_t - @ingroup index_function_api
2234  */
op_toggle_write(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2235 static int op_toggle_write(struct IndexSharedData *shared,
2236                            struct IndexPrivateData *priv, int op)
2237 {
2238   if (mx_toggle_write(shared->mailbox) == 0)
2239   {
2240     if (priv->in_pager)
2241       return IR_CONTINUE;
2242   }
2243 
2244   return IR_SUCCESS;
2245 }
2246 
2247 /**
2248  * op_undelete - Undelete the current entry - Implements ::index_function_t - @ingroup index_function_api
2249  */
op_undelete(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2250 static int op_undelete(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2251 {
2252   /* L10N: CHECK_ACL */
2253   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't undelete message")))
2254     return IR_ERROR;
2255 
2256   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
2257   el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
2258 
2259   mutt_emails_set_flag(shared->mailbox, &el, MUTT_DELETE, false);
2260   mutt_emails_set_flag(shared->mailbox, &el, MUTT_PURGE, false);
2261   emaillist_clear(&el);
2262 
2263   if (priv->tag)
2264   {
2265     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2266   }
2267   else
2268   {
2269     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
2270     const int index = menu_get_index(priv->menu) + 1;
2271     if (c_resolve && (index < shared->mailbox->vcount))
2272     {
2273       menu_set_index(priv->menu, index);
2274     }
2275     else
2276       menu_queue_redraw(priv->menu, MENU_REDRAW_CURRENT);
2277   }
2278 
2279   return IR_SUCCESS;
2280 }
2281 
2282 /**
2283  * op_undelete_thread - Undelete all messages in thread - Implements ::index_function_t - @ingroup index_function_api
2284  */
op_undelete_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2285 static int op_undelete_thread(struct IndexSharedData *shared,
2286                               struct IndexPrivateData *priv, int op)
2287 {
2288   /* L10N: CHECK_ACL */
2289   /* L10N: Due to the implementation details we do not know whether we
2290      undelete zero, 1, 12, ... messages. So in English we use
2291      "messages". Your language might have other means to express this. */
2292   if (!check_acl(shared->mailbox, MUTT_ACL_DELETE, _("Can't undelete messages")))
2293     return IR_ERROR;
2294 
2295   int rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE,
2296                                 false, (op != OP_UNDELETE_THREAD));
2297   if (rc != -1)
2298   {
2299     rc = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE, false,
2300                               (op != OP_UNDELETE_THREAD));
2301   }
2302   if (rc != -1)
2303   {
2304     const bool c_resolve = cs_subset_bool(shared->sub, "resolve");
2305     if (c_resolve)
2306     {
2307       int index;
2308       if (op == OP_UNDELETE_THREAD)
2309         index = mutt_next_thread(shared->email);
2310       else
2311         index = mutt_next_subthread(shared->email);
2312 
2313       if (index != -1)
2314         menu_set_index(priv->menu, index);
2315     }
2316     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2317   }
2318 
2319   return IR_SUCCESS;
2320 }
2321 
2322 /**
2323  * op_version - Show the NeoMutt version number and date - Implements ::index_function_t - @ingroup index_function_api
2324  */
op_version(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2325 static int op_version(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2326 {
2327   mutt_message(mutt_make_version());
2328   return IR_SUCCESS;
2329 }
2330 
2331 /**
2332  * op_view_attachments - Show MIME attachments - Implements ::index_function_t - @ingroup index_function_api
2333  */
op_view_attachments(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2334 static int op_view_attachments(struct IndexSharedData *shared,
2335                                struct IndexPrivateData *priv, int op)
2336 {
2337   if (!shared->email)
2338     return IR_NO_ACTION;
2339 
2340   enum IndexRetval rc = IR_ERROR;
2341   struct Message *msg = mx_msg_open(shared->mailbox, shared->email->msgno);
2342   if (msg)
2343   {
2344     dlg_select_attachment(NeoMutt->sub, shared->mailbox, shared->email, msg->fp);
2345     if (shared->email->attach_del)
2346     {
2347       shared->mailbox->changed = true;
2348     }
2349     mx_msg_close(shared->mailbox, &msg);
2350     rc = IR_SUCCESS;
2351   }
2352   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2353   return rc;
2354 }
2355 
2356 /**
2357  * op_what_key - Display the keycode for a key press - Implements ::index_function_t - @ingroup index_function_api
2358  */
op_what_key(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2359 static int op_what_key(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2360 {
2361   mutt_what_key();
2362   return IR_SUCCESS;
2363 }
2364 
2365 // -----------------------------------------------------------------------------
2366 
2367 #ifdef USE_AUTOCRYPT
2368 /**
2369  * op_autocrypt_acct_menu - Manage autocrypt accounts - Implements ::index_function_t - @ingroup index_function_api
2370  */
op_autocrypt_acct_menu(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2371 static int op_autocrypt_acct_menu(struct IndexSharedData *shared,
2372                                   struct IndexPrivateData *priv, int op)
2373 {
2374   dlg_select_autocrypt_account();
2375   return IR_SUCCESS;
2376 }
2377 #endif
2378 
2379 #ifdef USE_IMAP
2380 /**
2381  * op_main_imap_fetch - Force retrieval of mail from IMAP server - Implements ::index_function_t - @ingroup index_function_api
2382  */
op_main_imap_fetch(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2383 static int op_main_imap_fetch(struct IndexSharedData *shared,
2384                               struct IndexPrivateData *priv, int op)
2385 {
2386   if (!shared->mailbox || (shared->mailbox->type != MUTT_IMAP))
2387     return IR_NO_ACTION;
2388 
2389   imap_check_mailbox(shared->mailbox, true);
2390   return IR_SUCCESS;
2391 }
2392 
2393 /**
2394  * op_main_imap_logout_all - Logout from all IMAP servers - Implements ::index_function_t - @ingroup index_function_api
2395  */
op_main_imap_logout_all(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2396 static int op_main_imap_logout_all(struct IndexSharedData *shared,
2397                                    struct IndexPrivateData *priv, int op)
2398 {
2399   if (shared->mailbox && (shared->mailbox->type == MUTT_IMAP))
2400   {
2401     const enum MxStatus check = mx_mbox_close(shared->mailbox);
2402     if (check == MX_STATUS_OK)
2403     {
2404       ctx_free(&shared->ctx);
2405     }
2406     else
2407     {
2408       if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
2409       {
2410         update_index(priv->menu, shared->ctx, check, priv->oldcount, shared);
2411       }
2412       OptSearchInvalid = true;
2413       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2414       return IR_ERROR;
2415     }
2416   }
2417   imap_logout_all();
2418   mutt_message(_("Logged out of IMAP servers"));
2419   OptSearchInvalid = true;
2420   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2421 
2422   return IR_SUCCESS;
2423 }
2424 #endif
2425 
2426 #ifdef USE_NNTP
2427 /**
2428  * op_catchup - Mark all articles in newsgroup as read - Implements ::index_function_t - @ingroup index_function_api
2429  */
op_catchup(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2430 static int op_catchup(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2431 {
2432   if (!shared->mailbox || (shared->mailbox->type != MUTT_NNTP))
2433     return IR_NO_ACTION;
2434 
2435   struct NntpMboxData *mdata = shared->mailbox->mdata;
2436   if (mutt_newsgroup_catchup(shared->mailbox, mdata->adata, mdata->group))
2437     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2438 
2439   return IR_SUCCESS;
2440 }
2441 
2442 /**
2443  * op_get_children - Get all children of the current message - Implements ::index_function_t - @ingroup index_function_api
2444  */
op_get_children(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2445 static int op_get_children(struct IndexSharedData *shared,
2446                            struct IndexPrivateData *priv, int op)
2447 {
2448   if (shared->mailbox->type != MUTT_NNTP)
2449     return IR_ERROR;
2450 
2451   if (!shared->email)
2452     return IR_NO_ACTION;
2453 
2454   char buf[PATH_MAX] = { 0 };
2455   int oldmsgcount = shared->mailbox->msg_count;
2456   int oldindex = shared->email->index;
2457   int rc = 0;
2458 
2459   if (!shared->email->env->message_id)
2460   {
2461     mutt_error(_("No Message-Id. Unable to perform operation."));
2462     return IR_ERROR;
2463   }
2464 
2465   mutt_message(_("Fetching message headers..."));
2466   if (!shared->mailbox->id_hash)
2467     shared->mailbox->id_hash = mutt_make_id_hash(shared->mailbox);
2468   mutt_str_copy(buf, shared->email->env->message_id, sizeof(buf));
2469 
2470   /* trying to find msgid of the root message */
2471   if (op == OP_RECONSTRUCT_THREAD)
2472   {
2473     struct ListNode *ref = NULL;
2474     STAILQ_FOREACH(ref, &shared->email->env->references, entries)
2475     {
2476       if (!mutt_hash_find(shared->mailbox->id_hash, ref->data))
2477       {
2478         rc = nntp_check_msgid(shared->mailbox, ref->data);
2479         if (rc < 0)
2480           return IR_ERROR;
2481       }
2482 
2483       /* the last msgid in References is the root message */
2484       if (!STAILQ_NEXT(ref, entries))
2485         mutt_str_copy(buf, ref->data, sizeof(buf));
2486     }
2487   }
2488 
2489   /* fetching all child messages */
2490   rc = nntp_check_children(shared->mailbox, buf);
2491 
2492   /* at least one message has been loaded */
2493   if (shared->mailbox->msg_count > oldmsgcount)
2494   {
2495     const int index = menu_get_index(priv->menu);
2496     struct Email *e_oldcur = mutt_get_virt_email(shared->mailbox, index);
2497     bool verbose = shared->mailbox->verbose;
2498 
2499     if (rc < 0)
2500       shared->mailbox->verbose = false;
2501     mutt_sort_headers(shared->mailbox, shared->ctx->threads,
2502                       (op == OP_RECONSTRUCT_THREAD), &shared->ctx->vsize);
2503     shared->mailbox->verbose = verbose;
2504 
2505     /* Similar to OP_MAIN_ENTIRE_THREAD, keep displaying the old message, but
2506      * update the index */
2507     if (priv->in_pager)
2508     {
2509       menu_set_index(priv->menu, e_oldcur->vnum);
2510       menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2511       return IR_CONTINUE;
2512     }
2513 
2514     /* if the root message was retrieved, move to it */
2515     struct Email *e = mutt_hash_find(shared->mailbox->id_hash, buf);
2516     if (e)
2517       menu_set_index(priv->menu, e->vnum);
2518     else
2519     {
2520       /* try to restore old position */
2521       for (int i = 0; i < shared->mailbox->msg_count; i++)
2522       {
2523         e = shared->mailbox->emails[i];
2524         if (!e)
2525           break;
2526         if (e->index == oldindex)
2527         {
2528           menu_set_index(priv->menu, e->vnum);
2529           /* as an added courtesy, recenter the menu
2530            * with the current entry at the middle of the screen */
2531           menu_current_middle(priv->menu);
2532         }
2533       }
2534     }
2535     menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2536   }
2537   else if (rc >= 0)
2538   {
2539     mutt_error(_("No deleted messages found in the thread"));
2540     /* Similar to OP_MAIN_ENTIRE_THREAD, keep displaying the old message, but
2541      * update the index */
2542     if (priv->in_pager)
2543     {
2544       return IR_CONTINUE;
2545     }
2546   }
2547 
2548   return IR_SUCCESS;
2549 }
2550 
2551 /**
2552  * op_get_message - Get parent of the current message - Implements ::index_function_t - @ingroup index_function_api
2553  */
op_get_message(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2554 static int op_get_message(struct IndexSharedData *shared,
2555                           struct IndexPrivateData *priv, int op)
2556 {
2557   if (shared->mailbox->type != MUTT_NNTP)
2558     return IR_SUCCESS;
2559 
2560   char buf[PATH_MAX] = { 0 };
2561   if (op == OP_GET_MESSAGE)
2562   {
2563     if ((mutt_get_field(_("Enter Message-Id: "), buf, sizeof(buf),
2564                         MUTT_COMP_NO_FLAGS, false, NULL, NULL) != 0) ||
2565         (buf[0] == '\0'))
2566     {
2567       return IR_ERROR;
2568     }
2569   }
2570   else
2571   {
2572     if (!shared->email || STAILQ_EMPTY(&shared->email->env->references))
2573     {
2574       mutt_error(_("Article has no parent reference"));
2575       return IR_ERROR;
2576     }
2577     mutt_str_copy(buf, STAILQ_FIRST(&shared->email->env->references)->data, sizeof(buf));
2578   }
2579 
2580   if (!shared->mailbox->id_hash)
2581     shared->mailbox->id_hash = mutt_make_id_hash(shared->mailbox);
2582   struct Email *e = mutt_hash_find(shared->mailbox->id_hash, buf);
2583   if (e)
2584   {
2585     if (e->vnum != -1)
2586     {
2587       menu_set_index(priv->menu, e->vnum);
2588     }
2589     else if (e->collapsed)
2590     {
2591       mutt_uncollapse_thread(e);
2592       mutt_set_vnum(shared->mailbox);
2593       menu_set_index(priv->menu, e->vnum);
2594     }
2595     else
2596       mutt_error(_("Message is not visible in limited view"));
2597   }
2598   else
2599   {
2600     mutt_message(_("Fetching %s from server..."), buf);
2601     int rc = nntp_check_msgid(shared->mailbox, buf);
2602     if (rc == 0)
2603     {
2604       e = shared->mailbox->emails[shared->mailbox->msg_count - 1];
2605       mutt_sort_headers(shared->mailbox, shared->ctx->threads, false,
2606                         &shared->ctx->vsize);
2607       menu_set_index(priv->menu, e->vnum);
2608       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2609     }
2610     else if (rc > 0)
2611       mutt_error(_("Article %s not found on the server"), buf);
2612   }
2613 
2614   return IR_SUCCESS;
2615 }
2616 
2617 /**
2618  * op_main_change_group - Open a different newsgroup - Implements ::index_function_t - @ingroup index_function_api
2619  */
op_main_change_group(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2620 static int op_main_change_group(struct IndexSharedData *shared,
2621                                 struct IndexPrivateData *priv, int op)
2622 {
2623   bool pager_return = true; /* return to display message in pager */
2624   struct Buffer *folderbuf = mutt_buffer_pool_get();
2625   mutt_buffer_alloc(folderbuf, PATH_MAX);
2626 
2627   OptNews = false;
2628   bool read_only;
2629   char *cp = NULL;
2630   const bool c_read_only = cs_subset_bool(shared->sub, "read_only");
2631   if (priv->attach_msg || c_read_only || (op == OP_MAIN_CHANGE_GROUP_READONLY))
2632   {
2633     cp = _("Open newsgroup in read-only mode");
2634     read_only = true;
2635   }
2636   else
2637   {
2638     cp = _("Open newsgroup");
2639     read_only = false;
2640   }
2641 
2642   const bool c_change_folder_next =
2643       cs_subset_bool(shared->sub, "change_folder_next");
2644   if (c_change_folder_next && shared->mailbox &&
2645       !mutt_buffer_is_empty(&shared->mailbox->pathbuf))
2646   {
2647     mutt_buffer_strcpy(folderbuf, mailbox_path(shared->mailbox));
2648     mutt_buffer_pretty_mailbox(folderbuf);
2649   }
2650 
2651   OptNews = true;
2652   const char *const c_news_server =
2653       cs_subset_string(shared->sub, "news_server");
2654   CurrentNewsSrv = nntp_select_server(shared->mailbox, c_news_server, false);
2655   if (!CurrentNewsSrv)
2656     goto changefoldercleanup2;
2657 
2658   nntp_mailbox(shared->mailbox, folderbuf->data, folderbuf->dsize);
2659 
2660   if (mutt_buffer_enter_fname(cp, folderbuf, true, shared->mailbox, false, NULL,
2661                               NULL, MUTT_SEL_NO_FLAGS) == -1)
2662   {
2663     goto changefoldercleanup2;
2664   }
2665 
2666   /* Selected directory is okay, let's save it. */
2667   mutt_browser_select_dir(mutt_buffer_string(folderbuf));
2668 
2669   if (mutt_buffer_is_empty(folderbuf))
2670   {
2671     msgwin_clear_text();
2672     goto changefoldercleanup2;
2673   }
2674 
2675   struct Mailbox *m = mx_mbox_find2(mutt_buffer_string(folderbuf));
2676   if (m)
2677   {
2678     change_folder_mailbox(priv->menu, m, &priv->oldcount, shared, read_only);
2679     pager_return = false;
2680   }
2681   else
2682   {
2683     change_folder_string(priv->menu, folderbuf->data, folderbuf->dsize,
2684                          &priv->oldcount, shared, &pager_return, read_only);
2685   }
2686   struct MuttWindow *dlg = dialog_find(priv->win_index);
2687   dlg->help_data = IndexNewsHelp;
2688 
2689 changefoldercleanup2:
2690   mutt_buffer_pool_release(&folderbuf);
2691   if (priv->in_pager && pager_return)
2692     return IR_CONTINUE;
2693 
2694   return IR_SUCCESS;
2695 }
2696 
2697 /**
2698  * op_post - Followup to newsgroup - Implements ::index_function_t - @ingroup index_function_api
2699  */
op_post(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2700 static int op_post(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2701 {
2702   if (!shared->email)
2703     return IR_NO_ACTION;
2704 
2705   const enum QuadOption c_followup_to_poster =
2706       cs_subset_quad(shared->sub, "followup_to_poster");
2707   if ((op != OP_FOLLOWUP) || !shared->email->env->followup_to ||
2708       !mutt_istr_equal(shared->email->env->followup_to, "poster") ||
2709       (query_quadoption(c_followup_to_poster,
2710                         _("Reply by mail as poster prefers?")) != MUTT_YES))
2711   {
2712     const enum QuadOption c_post_moderated =
2713         cs_subset_quad(shared->sub, "post_moderated");
2714     if (shared->mailbox && (shared->mailbox->type == MUTT_NNTP) &&
2715         !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
2716     {
2717       return IR_ERROR;
2718     }
2719     if (op == OP_POST)
2720       mutt_send_message(SEND_NEWS, NULL, NULL, shared->mailbox, NULL, shared->sub);
2721     else
2722     {
2723       struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
2724       el_add_tagged(&el, shared->ctx, shared->email, priv->tag);
2725       mutt_send_message(((op == OP_FOLLOWUP) ? SEND_REPLY : SEND_FORWARD) | SEND_NEWS,
2726                         NULL, NULL, shared->mailbox, &el, shared->sub);
2727       emaillist_clear(&el);
2728     }
2729     menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2730     return IR_SUCCESS;
2731   }
2732 
2733   return op_reply(shared, priv, OP_REPLY);
2734 }
2735 #endif
2736 
2737 #ifdef USE_NOTMUCH
2738 /**
2739  * op_main_entire_thread - Read entire thread of the current message - Implements ::index_function_t - @ingroup index_function_api
2740  */
op_main_entire_thread(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2741 static int op_main_entire_thread(struct IndexSharedData *shared,
2742                                  struct IndexPrivateData *priv, int op)
2743 {
2744   char buf[PATH_MAX] = { 0 };
2745   if (shared->mailbox->type != MUTT_NOTMUCH)
2746   {
2747     if (((shared->mailbox->type != MUTT_MH) && (shared->mailbox->type != MUTT_MAILDIR)) ||
2748         (!shared->email || !shared->email->env || !shared->email->env->message_id))
2749     {
2750       mutt_message(_("No virtual folder and no Message-Id, aborting"));
2751       return IR_ERROR;
2752     } // no virtual folder, but we have message-id, reconstruct thread on-the-fly
2753     strncpy(buf, "id:", sizeof(buf));
2754     int msg_id_offset = 0;
2755     if ((shared->email->env->message_id)[0] == '<')
2756       msg_id_offset = 1;
2757     mutt_str_cat(buf, sizeof(buf), (shared->email->env->message_id) + msg_id_offset);
2758     if (buf[strlen(buf) - 1] == '>')
2759       buf[strlen(buf) - 1] = '\0';
2760 
2761     change_folder_notmuch(priv->menu, buf, sizeof(buf), &priv->oldcount, shared, false);
2762 
2763     // If notmuch doesn't contain the message, we're left in an empty
2764     // vfolder. No messages are found, but nm_read_entire_thread assumes
2765     // a valid message-id and will throw a segfault.
2766     //
2767     // To prevent that, stay in the empty vfolder and print an error.
2768     if (shared->mailbox->msg_count == 0)
2769     {
2770       mutt_error(_("failed to find message in notmuch database. try "
2771                    "running 'notmuch new'."));
2772       return IR_ERROR;
2773     }
2774   }
2775   priv->oldcount = shared->mailbox->msg_count;
2776   int index = menu_get_index(priv->menu);
2777   struct Email *e_oldcur = mutt_get_virt_email(shared->mailbox, index);
2778   if (nm_read_entire_thread(shared->mailbox, e_oldcur) < 0)
2779   {
2780     mutt_message(_("Failed to read thread, aborting"));
2781     return IR_ERROR;
2782   }
2783 
2784   // nm_read_entire_thread() may modify msg_count and menu won't be updated.
2785   priv->menu->max = shared->mailbox->msg_count;
2786 
2787   if (priv->oldcount < shared->mailbox->msg_count)
2788   {
2789     /* nm_read_entire_thread() triggers mutt_sort_headers() if necessary */
2790     index = e_oldcur->vnum;
2791     if (e_oldcur->collapsed || shared->ctx->collapsed)
2792     {
2793       index = mutt_uncollapse_thread(e_oldcur);
2794       mutt_set_vnum(shared->mailbox);
2795     }
2796     menu_set_index(priv->menu, index);
2797     menu_queue_redraw(priv->menu, MENU_REDRAW_INDEX);
2798   }
2799   if (priv->in_pager)
2800     return IR_CONTINUE;
2801 
2802   return IR_SUCCESS;
2803 }
2804 
2805 /**
2806  * op_main_vfolder_from_query - Generate virtual folder from query - Implements ::index_function_t - @ingroup index_function_api
2807  */
op_main_vfolder_from_query(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2808 static int op_main_vfolder_from_query(struct IndexSharedData *shared,
2809                                       struct IndexPrivateData *priv, int op)
2810 {
2811   char buf[PATH_MAX] = { 0 };
2812   if ((mutt_get_field("Query: ", buf, sizeof(buf), MUTT_NM_QUERY, false, NULL, NULL) != 0) ||
2813       (buf[0] == '\0'))
2814   {
2815     mutt_message(_("No query, aborting"));
2816     return IR_NO_ACTION;
2817   }
2818 
2819   // Keep copy of user's query to name the mailbox
2820   char *query_unencoded = mutt_str_dup(buf);
2821 
2822   struct Mailbox *m_query =
2823       change_folder_notmuch(priv->menu, buf, sizeof(buf), &priv->oldcount,
2824                             shared, (op == OP_MAIN_VFOLDER_FROM_QUERY_READONLY));
2825   if (m_query)
2826   {
2827     FREE(&m_query->name);
2828     m_query->name = query_unencoded;
2829     query_unencoded = NULL;
2830   }
2831   else
2832   {
2833     FREE(&query_unencoded);
2834   }
2835 
2836   return IR_SUCCESS;
2837 }
2838 
2839 /**
2840  * op_main_windowed_vfolder - Shifts virtual folder time window - Implements ::index_function_t - @ingroup index_function_api
2841  */
op_main_windowed_vfolder(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2842 static int op_main_windowed_vfolder(struct IndexSharedData *shared,
2843                                     struct IndexPrivateData *priv, int op)
2844 {
2845   // Common guard clauses.
2846   if (!nm_query_window_available())
2847   {
2848     mutt_message(_("Windowed queries disabled"));
2849     return IR_ERROR;
2850   }
2851   const char *const c_nm_query_window_current_search =
2852       cs_subset_string(shared->sub, "nm_query_window_current_search");
2853   if (!c_nm_query_window_current_search)
2854   {
2855     mutt_message(_("No notmuch vfolder currently loaded"));
2856     return IR_ERROR;
2857   }
2858 
2859   // Call the specific operation.
2860   switch (op)
2861   {
2862     case OP_MAIN_WINDOWED_VFOLDER_BACKWARD:
2863       nm_query_window_backward();
2864       break;
2865     case OP_MAIN_WINDOWED_VFOLDER_FORWARD:
2866       nm_query_window_forward();
2867       break;
2868     case OP_MAIN_WINDOWED_VFOLDER_RESET:
2869       nm_query_window_reset();
2870       break;
2871   }
2872 
2873   // Common query window folder change.
2874   char buf[PATH_MAX] = { 0 };
2875   mutt_str_copy(buf, c_nm_query_window_current_search, sizeof(buf));
2876   change_folder_notmuch(priv->menu, buf, sizeof(buf), &priv->oldcount, shared, false);
2877 
2878   return IR_SUCCESS;
2879 }
2880 #endif
2881 
2882 #ifdef USE_POP
2883 /**
2884  * op_main_fetch_mail - Retrieve mail from POP server - Implements ::index_function_t - @ingroup index_function_api
2885  */
op_main_fetch_mail(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2886 static int op_main_fetch_mail(struct IndexSharedData *shared,
2887                               struct IndexPrivateData *priv, int op)
2888 {
2889   pop_fetch_mail();
2890   menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
2891   return IR_SUCCESS;
2892 }
2893 #endif
2894 
2895 #ifdef USE_SIDEBAR
2896 /**
2897  * op_sidebar_next - Move the highlight to the first mailbox - Implements ::index_function_t - @ingroup index_function_api
2898  */
op_sidebar_next(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2899 static int op_sidebar_next(struct IndexSharedData *shared,
2900                            struct IndexPrivateData *priv, int op)
2901 {
2902   struct MuttWindow *dlg = dialog_find(priv->win_index);
2903   struct MuttWindow *win_sidebar = window_find_child(dlg, WT_SIDEBAR);
2904   sb_change_mailbox(win_sidebar, op);
2905   return IR_SUCCESS;
2906 }
2907 
2908 /**
2909  * op_sidebar_open - Open highlighted mailbox - Implements ::index_function_t - @ingroup index_function_api
2910  */
op_sidebar_open(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2911 static int op_sidebar_open(struct IndexSharedData *shared,
2912                            struct IndexPrivateData *priv, int op)
2913 {
2914   struct MuttWindow *dlg = dialog_find(priv->win_index);
2915   struct MuttWindow *win_sidebar = window_find_child(dlg, WT_SIDEBAR);
2916   change_folder_mailbox(priv->menu, sb_get_highlight(win_sidebar),
2917                         &priv->oldcount, shared, false);
2918   return IR_SUCCESS;
2919 }
2920 
2921 /**
2922  * op_sidebar_toggle_visible - Make the sidebar (in)visible - Implements ::index_function_t - @ingroup index_function_api
2923  */
op_sidebar_toggle_visible(struct IndexSharedData * shared,struct IndexPrivateData * priv,int op)2924 static int op_sidebar_toggle_visible(struct IndexSharedData *shared,
2925                                      struct IndexPrivateData *priv, int op)
2926 {
2927   bool_str_toggle(shared->sub, "sidebar_visible", NULL);
2928   mutt_window_reflow(NULL);
2929   return IR_SUCCESS;
2930 }
2931 #endif
2932 
2933 // -----------------------------------------------------------------------------
2934 
2935 /**
2936  * prereq - Check the pre-requisites for a function
2937  * @param ctx    Mailbox
2938  * @param menu   Current Menu
2939  * @param checks Checks to perform, see #CheckFlags
2940  * @retval true The checks pass successfully
2941  */
prereq(struct Context * ctx,struct Menu * menu,CheckFlags checks)2942 bool prereq(struct Context *ctx, struct Menu *menu, CheckFlags checks)
2943 {
2944   bool result = true;
2945 
2946   if (checks & (CHECK_MSGCOUNT | CHECK_VISIBLE | CHECK_READONLY))
2947     checks |= CHECK_IN_MAILBOX;
2948 
2949   if ((checks & CHECK_IN_MAILBOX) && (!ctx || !ctx->mailbox))
2950   {
2951     mutt_error(_("No mailbox is open"));
2952     result = false;
2953   }
2954 
2955   if (result && (checks & CHECK_MSGCOUNT) && (ctx->mailbox->msg_count == 0))
2956   {
2957     mutt_error(_("There are no messages"));
2958     result = false;
2959   }
2960 
2961   int index = menu_get_index(menu);
2962   if (result && (checks & CHECK_VISIBLE) && (index >= ctx->mailbox->vcount))
2963   {
2964     mutt_error(_("No visible messages"));
2965     result = false;
2966   }
2967 
2968   if (result && (checks & CHECK_READONLY) && ctx->mailbox->readonly)
2969   {
2970     mutt_error(_("Mailbox is read-only"));
2971     result = false;
2972   }
2973 
2974   if (result && (checks & CHECK_ATTACH) && OptAttachMsg)
2975   {
2976     mutt_error(_("Function not permitted in attach-message mode"));
2977     result = false;
2978   }
2979 
2980   if (!result)
2981     mutt_flushinp();
2982 
2983   return result;
2984 }
2985 
2986 /**
2987  * index_function_dispatcher - Perform an Index function
2988  * @param win_index Window for the Index
2989  * @param op        Operation to perform, e.g. OP_MAIN_LIMIT
2990  * @retval num IndexRetval or opcode
2991  */
index_function_dispatcher(struct MuttWindow * win_index,int op)2992 int index_function_dispatcher(struct MuttWindow *win_index, int op)
2993 {
2994   if (!win_index)
2995   {
2996     mutt_error(_(Not_available_in_this_menu));
2997     return IR_ERROR;
2998   }
2999 
3000   struct IndexPrivateData *priv = win_index->parent->wdata;
3001   if (!priv)
3002     return IR_ERROR;
3003 
3004   struct MuttWindow *dlg = dialog_find(win_index);
3005   if (!dlg || !dlg->wdata)
3006     return IR_ERROR;
3007 
3008   struct IndexSharedData *shared = dlg->wdata;
3009 
3010   int rc = IR_UNKNOWN;
3011   for (size_t i = 0; IndexFunctions[i].op != OP_NULL; i++)
3012   {
3013     const struct IndexFunction *fn = &IndexFunctions[i];
3014     if (fn->op == op)
3015     {
3016       if (!prereq(shared->ctx, priv->menu, fn->flags))
3017       {
3018         rc = IR_ERROR;
3019         break;
3020       }
3021       rc = fn->function(shared, priv, op);
3022       break;
3023     }
3024   }
3025 
3026   return rc;
3027 }
3028 
3029 /**
3030  * IndexFunctions - All the NeoMutt functions that the Index supports
3031  */
3032 struct IndexFunction IndexFunctions[] = {
3033   // clang-format off
3034   { OP_BOTTOM_PAGE,                      op_menu_move,                      CHECK_NO_FLAGS },
3035   { OP_BOUNCE_MESSAGE,                   op_bounce_message,                 CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3036   { OP_CHECK_STATS,                      op_check_stats,                    CHECK_NO_FLAGS },
3037   { OP_CHECK_TRADITIONAL,                op_check_traditional,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3038   { OP_COMPOSE_TO_SENDER,                op_compose_to_sender,              CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3039   { OP_COPY_MESSAGE,                     op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3040   { OP_CREATE_ALIAS,                     op_create_alias,                   CHECK_NO_FLAGS },
3041   { OP_CURRENT_BOTTOM,                   op_menu_move,                      CHECK_NO_FLAGS },
3042   { OP_CURRENT_MIDDLE,                   op_menu_move,                      CHECK_NO_FLAGS },
3043   { OP_CURRENT_TOP,                      op_menu_move,                      CHECK_NO_FLAGS },
3044   { OP_DECODE_COPY,                      op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3045   { OP_DECODE_SAVE,                      op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3046   { OP_DECRYPT_COPY,                     op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3047   { OP_DECRYPT_SAVE,                     op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3048   { OP_DELETE,                           op_delete,                         CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3049   { OP_DELETE_SUBTHREAD,                 op_delete_thread,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3050   { OP_DELETE_THREAD,                    op_delete_thread,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3051   { OP_DISPLAY_ADDRESS,                  op_display_address,                CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3052   { OP_DISPLAY_HEADERS,                  op_display_message,                CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3053   { OP_DISPLAY_MESSAGE,                  op_display_message,                CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3054   { OP_EDIT_LABEL,                       op_edit_label,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3055   { OP_EDIT_OR_VIEW_RAW_MESSAGE,         op_edit_raw_message,               CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3056   { OP_EDIT_RAW_MESSAGE,                 op_edit_raw_message,               CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3057   { OP_EDIT_TYPE,                        op_edit_type,                      CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3058   { OP_END_COND,                         op_end_cond,                       CHECK_NO_FLAGS },
3059   { OP_ENTER_COMMAND,                    op_enter_command,                  CHECK_NO_FLAGS },
3060   { OP_EXIT,                             op_exit,                           CHECK_NO_FLAGS },
3061   { OP_EXTRACT_KEYS,                     op_extract_keys,                   CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3062   { OP_FIRST_ENTRY,                      op_menu_move,                      CHECK_NO_FLAGS },
3063   { OP_FLAG_MESSAGE,                     op_flag_message,                   CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3064   { OP_FORGET_PASSPHRASE,                op_forget_passphrase,              CHECK_NO_FLAGS },
3065   { OP_FORWARD_MESSAGE,                  op_forward_message,                CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3066   { OP_GROUP_CHAT_REPLY,                 op_group_reply,                    CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3067   { OP_GROUP_REPLY,                      op_group_reply,                    CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3068   { OP_HALF_DOWN,                        op_menu_move,                      CHECK_NO_FLAGS },
3069   { OP_HALF_UP,                          op_menu_move,                      CHECK_NO_FLAGS },
3070   { OP_HELP,                             op_help,                           CHECK_NO_FLAGS },
3071   { OP_JUMP,                             op_jump,                           CHECK_IN_MAILBOX },
3072   { OP_LAST_ENTRY,                       op_menu_move,                      CHECK_NO_FLAGS },
3073   { OP_LIMIT_CURRENT_THREAD,             op_main_limit,                     CHECK_IN_MAILBOX },
3074   { OP_LIST_REPLY,                       op_list_reply,                     CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3075   { OP_LIST_SUBSCRIBE,                   op_list_subscribe,                 CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3076   { OP_LIST_UNSUBSCRIBE,                 op_list_unsubscribe,               CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3077   { OP_MAIL,                             op_mail,                           CHECK_ATTACH },
3078   { OP_MAILBOX_LIST,                     op_mailbox_list,                   CHECK_NO_FLAGS },
3079   { OP_MAIL_KEY,                         op_mail_key,                       CHECK_ATTACH },
3080   { OP_MAIN_BREAK_THREAD,                op_main_break_thread,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3081   { OP_MAIN_CHANGE_FOLDER,               op_main_change_folder,             CHECK_NO_FLAGS },
3082   { OP_MAIN_CHANGE_FOLDER_READONLY,      op_main_change_folder,             CHECK_NO_FLAGS },
3083   { OP_MAIN_CLEAR_FLAG,                  op_main_set_flag,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3084   { OP_MAIN_COLLAPSE_ALL,                op_main_collapse_all,              CHECK_IN_MAILBOX },
3085   { OP_MAIN_COLLAPSE_THREAD,             op_main_collapse_thread,           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3086   { OP_MAIN_DELETE_PATTERN,              op_main_delete_pattern,            CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_READONLY },
3087   { OP_MAIN_LIMIT,                       op_main_limit,                     CHECK_IN_MAILBOX },
3088   { OP_MAIN_LINK_THREADS,                op_main_link_threads,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3089   { OP_MAIN_MODIFY_TAGS,                 op_main_modify_tags,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3090   { OP_MAIN_MODIFY_TAGS_THEN_HIDE,       op_main_modify_tags,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3091   { OP_MAIN_NEXT_NEW,                    op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3092   { OP_MAIN_NEXT_NEW_THEN_UNREAD,        op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3093   { OP_MAIN_NEXT_SUBTHREAD,              op_main_next_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3094   { OP_MAIN_NEXT_THREAD,                 op_main_next_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3095   { OP_MAIN_NEXT_UNDELETED,              op_main_next_undeleted,            CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3096   { OP_MAIN_NEXT_UNREAD,                 op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3097   { OP_MAIN_NEXT_UNREAD_MAILBOX,         op_main_next_unread_mailbox,       CHECK_IN_MAILBOX },
3098   { OP_MAIN_PARENT_MESSAGE,              op_main_root_message,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3099   { OP_MAIN_PREV_NEW,                    op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3100   { OP_MAIN_PREV_NEW_THEN_UNREAD,        op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3101   { OP_MAIN_PREV_SUBTHREAD,              op_main_next_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3102   { OP_MAIN_PREV_THREAD,                 op_main_next_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3103   { OP_MAIN_PREV_UNDELETED,              op_main_prev_undeleted,            CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3104   { OP_MAIN_PREV_UNREAD,                 op_main_next_new,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3105   { OP_MAIN_QUASI_DELETE,                op_main_quasi_delete,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3106   { OP_MAIN_READ_SUBTHREAD,              op_main_read_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3107   { OP_MAIN_READ_THREAD,                 op_main_read_thread,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3108   { OP_MAIN_ROOT_MESSAGE,                op_main_root_message,              CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3109   { OP_MAIN_SET_FLAG,                    op_main_set_flag,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3110   { OP_MAIN_SHOW_LIMIT,                  op_main_show_limit,                CHECK_IN_MAILBOX },
3111   { OP_MAIN_SYNC_FOLDER,                 op_main_sync_folder,               CHECK_NO_FLAGS },
3112   { OP_MAIN_TAG_PATTERN,                 op_main_tag_pattern,               CHECK_IN_MAILBOX },
3113   { OP_MAIN_UNDELETE_PATTERN,            op_main_undelete_pattern,          CHECK_IN_MAILBOX | CHECK_READONLY },
3114   { OP_MAIN_UNTAG_PATTERN,               op_main_untag_pattern,             CHECK_IN_MAILBOX },
3115   { OP_MARK_MSG,                         op_mark_msg,                       CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3116   { OP_MIDDLE_PAGE,                      op_menu_move,                      CHECK_NO_FLAGS },
3117   { OP_NEXT_ENTRY,                       op_next_entry,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3118   { OP_NEXT_LINE,                        op_menu_move,                      CHECK_NO_FLAGS },
3119   { OP_NEXT_PAGE,                        op_menu_move,                      CHECK_NO_FLAGS },
3120   { OP_PIPE,                             op_pipe,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3121   { OP_PREV_ENTRY,                       op_prev_entry,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3122   { OP_PREV_LINE,                        op_menu_move,                      CHECK_NO_FLAGS },
3123   { OP_PREV_PAGE,                        op_menu_move,                      CHECK_NO_FLAGS },
3124   { OP_PRINT,                            op_print,                          CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3125   { OP_PURGE_MESSAGE,                    op_delete,                         CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3126   { OP_PURGE_THREAD,                     op_delete_thread,                  CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3127   { OP_QUERY,                            op_query,                          CHECK_ATTACH },
3128   { OP_QUIT,                             op_quit,                           CHECK_NO_FLAGS },
3129   { OP_RECALL_MESSAGE,                   op_recall_message,                 CHECK_ATTACH },
3130   { OP_REDRAW,                           op_redraw,                         CHECK_NO_FLAGS },
3131   { OP_REPLY,                            op_reply,                          CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3132   { OP_RESEND,                           op_resend,                         CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3133   { OP_SAVE,                             op_save,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3134   { OP_SEARCH,                           op_search,                         CHECK_IN_MAILBOX },
3135   { OP_SEARCH_NEXT,                      op_search,                         CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3136   { OP_SEARCH_OPPOSITE,                  op_search,                         CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3137   { OP_SEARCH_REVERSE,                   op_search,                         CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3138   { OP_SHELL_ESCAPE,                     op_shell_escape,                   CHECK_NO_FLAGS },
3139   { OP_SHOW_LOG_MESSAGES,                op_show_log_messages,              CHECK_NO_FLAGS },
3140   { OP_SORT,                             op_sort,                           CHECK_NO_FLAGS },
3141   { OP_SORT_REVERSE,                     op_sort,                           CHECK_NO_FLAGS },
3142   { OP_TAG,                              op_tag,                            CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3143   { OP_TAG_SUBTHREAD,                    op_tag_thread,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3144   { OP_TAG_THREAD,                       op_tag_thread,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3145   { OP_TOGGLE_NEW,                       op_toggle_new,                     CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3146   { OP_TOGGLE_READ,                      op_main_limit,                     CHECK_IN_MAILBOX },
3147   { OP_TOGGLE_WRITE,                     op_toggle_write,                   CHECK_IN_MAILBOX },
3148   { OP_TOP_PAGE,                         op_menu_move,                      CHECK_NO_FLAGS },
3149   { OP_UNDELETE,                         op_undelete,                       CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3150   { OP_UNDELETE_SUBTHREAD,               op_undelete_thread,                CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3151   { OP_UNDELETE_THREAD,                  op_undelete_thread,                CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3152   { OP_VERSION,                          op_version,                        CHECK_NO_FLAGS },
3153   { OP_VIEW_ATTACHMENTS,                 op_view_attachments,               CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3154   { OP_VIEW_RAW_MESSAGE,                 op_edit_raw_message,               CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3155   { OP_WHAT_KEY,                         op_what_key,                       CHECK_NO_FLAGS },
3156 #ifdef USE_AUTOCRYPT
3157   { OP_AUTOCRYPT_ACCT_MENU,              op_autocrypt_acct_menu,            CHECK_NO_FLAGS },
3158 #endif
3159 #ifdef USE_IMAP
3160   { OP_MAIN_IMAP_FETCH,                  op_main_imap_fetch,                CHECK_NO_FLAGS },
3161   { OP_MAIN_IMAP_LOGOUT_ALL,             op_main_imap_logout_all,           CHECK_NO_FLAGS },
3162 #endif
3163 #ifdef USE_NNTP
3164   { OP_CATCHUP,                          op_catchup,                        CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY },
3165   { OP_FOLLOWUP,                         op_post,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3166   { OP_FORWARD_TO_GROUP,                 op_post,                           CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3167   { OP_GET_CHILDREN,                     op_get_children,                   CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3168   { OP_GET_MESSAGE,                      op_get_message,                    CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_READONLY },
3169   { OP_GET_PARENT,                       op_get_message,                    CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3170   { OP_MAIN_CHANGE_GROUP,                op_main_change_group,              CHECK_NO_FLAGS },
3171   { OP_MAIN_CHANGE_GROUP_READONLY,       op_main_change_group,              CHECK_NO_FLAGS },
3172   { OP_POST,                             op_post,                           CHECK_ATTACH | CHECK_IN_MAILBOX },
3173   { OP_RECONSTRUCT_THREAD,               op_get_children,                   CHECK_ATTACH | CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_READONLY | CHECK_VISIBLE },
3174 #endif
3175 #ifdef USE_NOTMUCH
3176   { OP_MAIN_CHANGE_VFOLDER,              op_main_change_folder,             CHECK_NO_FLAGS },
3177   { OP_MAIN_ENTIRE_THREAD,               op_main_entire_thread,             CHECK_IN_MAILBOX | CHECK_MSGCOUNT | CHECK_VISIBLE },
3178   { OP_MAIN_VFOLDER_FROM_QUERY,          op_main_vfolder_from_query,        CHECK_NO_FLAGS },
3179   { OP_MAIN_VFOLDER_FROM_QUERY_READONLY, op_main_vfolder_from_query,        CHECK_NO_FLAGS },
3180   { OP_MAIN_WINDOWED_VFOLDER_BACKWARD,   op_main_windowed_vfolder,          CHECK_IN_MAILBOX },
3181   { OP_MAIN_WINDOWED_VFOLDER_FORWARD,    op_main_windowed_vfolder,          CHECK_IN_MAILBOX },
3182   { OP_MAIN_WINDOWED_VFOLDER_RESET,      op_main_windowed_vfolder,          CHECK_IN_MAILBOX },
3183 #endif
3184 #ifdef USE_POP
3185   { OP_MAIN_FETCH_MAIL,                  op_main_fetch_mail,                CHECK_ATTACH },
3186 #endif
3187 #ifdef USE_SIDEBAR
3188   { OP_SIDEBAR_FIRST,                    op_sidebar_next,                   CHECK_NO_FLAGS },
3189   { OP_SIDEBAR_LAST,                     op_sidebar_next,                   CHECK_NO_FLAGS },
3190   { OP_SIDEBAR_NEXT,                     op_sidebar_next,                   CHECK_NO_FLAGS },
3191   { OP_SIDEBAR_NEXT_NEW,                 op_sidebar_next,                   CHECK_NO_FLAGS },
3192   { OP_SIDEBAR_OPEN,                     op_sidebar_open,                   CHECK_NO_FLAGS },
3193   { OP_SIDEBAR_PAGE_DOWN,                op_sidebar_next,                   CHECK_NO_FLAGS },
3194   { OP_SIDEBAR_PAGE_UP,                  op_sidebar_next,                   CHECK_NO_FLAGS },
3195   { OP_SIDEBAR_PREV,                     op_sidebar_next,                   CHECK_NO_FLAGS },
3196   { OP_SIDEBAR_PREV_NEW,                 op_sidebar_next,                   CHECK_NO_FLAGS },
3197   { OP_SIDEBAR_TOGGLE_VISIBLE,           op_sidebar_toggle_visible,         CHECK_NO_FLAGS },
3198 #endif
3199   { 0, NULL, CHECK_NO_FLAGS },
3200   // clang-format on
3201 };
3202