1 /**
2  * @file
3  * Pager 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 pager_functions Pager functions
25  *
26  * Pager functions
27  */
28 
29 #include "config.h"
30 #include <stddef.h>
31 #include <assert.h>
32 #include <inttypes.h> // IWYU pragma: keep
33 #include <stdbool.h>
34 #include <stdint.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include "mutt/lib.h"
38 #include "config/lib.h"
39 #include "email/lib.h"
40 #include "core/lib.h"
41 #include "alias/lib.h"
42 #include "gui/lib.h"
43 #include "mutt.h"
44 #include "functions.h"
45 #include "lib.h"
46 #include "attach/lib.h"
47 #include "color/lib.h"
48 #include "index/lib.h"
49 #include "menu/lib.h"
50 #include "ncrypt/lib.h"
51 #include "question/lib.h"
52 #include "send/lib.h"
53 #include "commands.h"
54 #include "context.h"
55 #include "display.h"
56 #include "keymap.h"
57 #include "mutt_globals.h"
58 #include "mutt_header.h"
59 #include "mutt_mailbox.h"
60 #include "muttlib.h"
61 #include "opcodes.h"
62 #include "options.h"
63 #include "private_data.h"
64 #include "protos.h"
65 #include "recvcmd.h"
66 #ifdef USE_SIDEBAR
67 #include "sidebar/lib.h"
68 #endif
69 #ifdef USE_NNTP
70 #include "nntp/lib.h"
71 #include "nntp/mdata.h" // IWYU pragma: keep
72 #endif
73 #ifdef ENABLE_NLS
74 #include <libintl.h>
75 #endif
76 
77 static const char *Not_available_in_this_menu =
78     N_("Not available in this menu");
79 static const char *Mailbox_is_read_only = N_("Mailbox is read-only");
80 static const char *Function_not_permitted_in_attach_message_mode =
81     N_("Function not permitted in attach-message mode");
82 
83 static int op_search_next(struct IndexSharedData *shared,
84                           struct PagerPrivateData *priv, int op);
85 
86 /**
87  * assert_pager_mode - Check that pager is in correct mode
88  * @param test   Test condition
89  * @retval true  Expected mode is set
90  * @retval false Pager is is some other mode
91  *
92  * @note On failure, the input will be flushed and an error message displayed
93  */
assert_pager_mode(bool test)94 static inline bool assert_pager_mode(bool test)
95 {
96   if (test)
97     return true;
98 
99   mutt_flushinp();
100   mutt_error(_(Not_available_in_this_menu));
101   return false;
102 }
103 
104 /**
105  * assert_mailbox_writable - Checks that mailbox is writable
106  * @param mailbox mailbox to check
107  * @retval true  Mailbox is writable
108  * @retval false Mailbox is not writable
109  *
110  * @note On failure, the input will be flushed and an error message displayed
111  */
assert_mailbox_writable(struct Mailbox * mailbox)112 static inline bool assert_mailbox_writable(struct Mailbox *mailbox)
113 {
114   assert(mailbox);
115   if (mailbox->readonly)
116   {
117     mutt_flushinp();
118     mutt_error(_(Mailbox_is_read_only));
119     return false;
120   }
121   return true;
122 }
123 
124 /**
125  * assert_attach_msg_mode - Check that attach message mode is on
126  * @param attach_msg Globally-named boolean pseudo-option
127  * @retval true  Attach message mode in on
128  * @retval false Attach message mode is off
129  *
130  * @note On true, the input will be flushed and an error message displayed
131  */
assert_attach_msg_mode(bool attach_msg)132 static inline bool assert_attach_msg_mode(bool attach_msg)
133 {
134   if (attach_msg)
135   {
136     mutt_flushinp();
137     mutt_error(_(Function_not_permitted_in_attach_message_mode));
138     return true;
139   }
140   return false;
141 }
142 
143 /**
144  * assert_mailbox_permissions - Checks that mailbox is has requested acl flags set
145  * @param m      Mailbox to check
146  * @param acl    AclFlags required to be set on a given mailbox
147  * @param action String to augment error message
148  * @retval true  Mailbox has necessary flags set
149  * @retval false Mailbox does not have necessary flags set
150  *
151  * @note On failure, the input will be flushed and an error message displayed
152  */
assert_mailbox_permissions(struct Mailbox * m,AclFlags acl,char * action)153 static inline bool assert_mailbox_permissions(struct Mailbox *m, AclFlags acl, char *action)
154 {
155   assert(m);
156   assert(action);
157   if (m->rights & acl)
158   {
159     return true;
160   }
161   mutt_flushinp();
162   /* L10N: %s is one of the CHECK_ACL entries below. */
163   mutt_error(_("%s: Operation not permitted by ACL"), action);
164   return false;
165 }
166 
167 /**
168  * up_n_lines - Reposition the pager's view up by n lines
169  * @param nlines Number of lines to move
170  * @param info   Line info array
171  * @param cur    Current line number
172  * @param hiding true if lines have been hidden
173  * @retval num New current line number
174  */
up_n_lines(int nlines,struct Line * info,int cur,bool hiding)175 static int up_n_lines(int nlines, struct Line *info, int cur, bool hiding)
176 {
177   while ((cur > 0) && (nlines > 0))
178   {
179     cur--;
180     if (!hiding || (info[cur].color != MT_COLOR_QUOTED))
181       nlines--;
182   }
183 
184   return cur;
185 }
186 
187 /**
188  * jump_to_bottom - Make sure the bottom line is displayed
189  * @param priv   Private Pager data
190  * @param pview PagerView
191  * @retval true Something changed
192  * @retval false Bottom was already displayed
193  */
jump_to_bottom(struct PagerPrivateData * priv,struct PagerView * pview)194 bool jump_to_bottom(struct PagerPrivateData *priv, struct PagerView *pview)
195 {
196   if (!(priv->lines[priv->cur_line].offset < (priv->st.st_size - 1)))
197   {
198     return false;
199   }
200 
201   int line_num = priv->cur_line;
202   /* make sure the types are defined to the end of file */
203   while (display_line(priv->fp, &priv->bytes_read, &priv->lines, line_num,
204                       &priv->lines_used, &priv->lines_max,
205                       priv->has_types | (pview->flags & MUTT_PAGER_NOWRAP),
206                       &priv->quote_list, &priv->q_level, &priv->force_redraw,
207                       &priv->search_re, priv->pview->win_pager) == 0)
208   {
209     line_num++;
210   }
211   priv->top_line = up_n_lines(priv->pview->win_pager->state.rows, priv->lines,
212                               priv->lines_used, priv->hide_quoted);
213   notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
214   return true;
215 }
216 
217 /**
218  * op_bounce_message - Remail a message to another user - Implements ::pager_function_t - @ingroup pager_function_api
219  */
op_bounce_message(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)220 static int op_bounce_message(struct IndexSharedData *shared,
221                              struct PagerPrivateData *priv, int op)
222 {
223   struct PagerView *pview = priv->pview;
224 
225   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
226   {
227     return IR_NOT_IMPL;
228   }
229   if (assert_attach_msg_mode(OptAttachMsg))
230     return IR_ERROR;
231   if (pview->mode == PAGER_MODE_ATTACH_E)
232   {
233     mutt_attach_bounce(shared->mailbox, pview->pdata->fp, pview->pdata->actx,
234                        pview->pdata->body);
235   }
236   else
237   {
238     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
239     emaillist_add_email(&el, shared->email);
240     ci_bounce_message(shared->mailbox, &el);
241     emaillist_clear(&el);
242   }
243   return IR_SUCCESS;
244 }
245 
246 /**
247  * op_check_stats - Calculate message statistics for all mailboxes - Implements ::pager_function_t - @ingroup pager_function_api
248  */
op_check_stats(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)249 static int op_check_stats(struct IndexSharedData *shared,
250                           struct PagerPrivateData *priv, int op)
251 {
252   mutt_check_stats(shared->mailbox);
253   return IR_SUCCESS;
254 }
255 
256 /**
257  * op_check_traditional - Check for classic PGP - Implements ::pager_function_t - @ingroup pager_function_api
258  */
op_check_traditional(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)259 static int op_check_traditional(struct IndexSharedData *shared,
260                                 struct PagerPrivateData *priv, int op)
261 {
262   struct PagerView *pview = priv->pview;
263 
264   if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
265     return IR_NOT_IMPL;
266   if (!(WithCrypto & APPLICATION_PGP))
267     return IR_NO_ACTION;
268   if (!(shared->email->security & PGP_TRADITIONAL_CHECKED))
269   {
270     priv->rc = OP_CHECK_TRADITIONAL;
271     return IR_DONE;
272   }
273   return IR_SUCCESS;
274 }
275 
276 /**
277  * op_compose_to_sender - Compose new message to the current message sender - Implements ::pager_function_t - @ingroup pager_function_api
278  */
op_compose_to_sender(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)279 static int op_compose_to_sender(struct IndexSharedData *shared,
280                                 struct PagerPrivateData *priv, int op)
281 {
282   struct PagerView *pview = priv->pview;
283 
284   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
285   {
286     return IR_NOT_IMPL;
287   }
288   if (assert_attach_msg_mode(OptAttachMsg))
289     return IR_ERROR;
290   if (pview->mode == PAGER_MODE_ATTACH_E)
291   {
292     mutt_attach_mail_sender(pview->pdata->actx, pview->pdata->body);
293   }
294   else
295   {
296     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
297     emaillist_add_email(&el, shared->email);
298 
299     mutt_send_message(SEND_TO_SENDER, NULL, NULL, shared->mailbox, &el, NeoMutt->sub);
300     emaillist_clear(&el);
301   }
302   pager_queue_redraw(priv, MENU_REDRAW_FULL);
303   return IR_SUCCESS;
304 }
305 
306 /**
307  * op_copy_message - Copy a message to a file/mailbox - Implements ::pager_function_t - @ingroup pager_function_api
308  */
op_copy_message(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)309 static int op_copy_message(struct IndexSharedData *shared,
310                            struct PagerPrivateData *priv, int op)
311 {
312   if (!(WithCrypto != 0) && (op == OP_DECRYPT_COPY))
313     return IR_DONE;
314 
315   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
316     return IR_NOT_IMPL;
317   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
318   emaillist_add_email(&el, shared->email);
319 
320   const enum MessageSaveOpt save_opt =
321       ((op == OP_SAVE) || (op == OP_DECODE_SAVE) || (op == OP_DECRYPT_SAVE)) ? SAVE_MOVE : SAVE_COPY;
322 
323   enum MessageTransformOpt transform_opt =
324       ((op == OP_DECODE_SAVE) || (op == OP_DECODE_COPY))   ? TRANSFORM_DECODE :
325       ((op == OP_DECRYPT_SAVE) || (op == OP_DECRYPT_COPY)) ? TRANSFORM_DECRYPT :
326                                                              TRANSFORM_NONE;
327 
328   const int rc2 = mutt_save_message(shared->mailbox, &el, save_opt, transform_opt);
329   emaillist_clear(&el);
330 
331   if ((rc2 == 0) && (save_opt == SAVE_MOVE))
332   {
333     const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
334     if (c_resolve)
335     {
336       priv->rc = OP_MAIN_NEXT_UNDELETED;
337       return IR_DONE;
338     }
339     else
340       pager_queue_redraw(priv, MENU_REDRAW_INDEX);
341   }
342   return IR_SUCCESS;
343 }
344 
345 /**
346  * op_create_alias - Create an alias from a message sender - Implements ::pager_function_t - @ingroup pager_function_api
347  */
op_create_alias(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)348 static int op_create_alias(struct IndexSharedData *shared,
349                            struct PagerPrivateData *priv, int op)
350 {
351   struct PagerView *pview = priv->pview;
352 
353   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
354   {
355     return IR_NOT_IMPL;
356   }
357   struct AddressList *al = NULL;
358   if (pview->mode == PAGER_MODE_ATTACH_E)
359     al = mutt_get_address(pview->pdata->body->email->env, NULL);
360   else
361     al = mutt_get_address(shared->email->env, NULL);
362   alias_create(al, NeoMutt->sub);
363   return IR_SUCCESS;
364 }
365 
366 /**
367  * op_delete - Delete the current entry - Implements ::pager_function_t - @ingroup pager_function_api
368  */
op_delete(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)369 static int op_delete(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
370 {
371   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
372     return IR_NOT_IMPL;
373   if (!assert_mailbox_writable(shared->mailbox))
374     return IR_NO_ACTION;
375   /* L10N: CHECK_ACL */
376   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_DELETE, _("Can't delete message")))
377   {
378     return IR_ERROR;
379   }
380 
381   mutt_set_flag(shared->mailbox, shared->email, MUTT_DELETE, true);
382   mutt_set_flag(shared->mailbox, shared->email, MUTT_PURGE, (op == OP_PURGE_MESSAGE));
383   const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
384   if (c_delete_untag)
385     mutt_set_flag(shared->mailbox, shared->email, MUTT_TAG, false);
386   pager_queue_redraw(priv, MENU_REDRAW_INDEX);
387   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
388   if (c_resolve)
389   {
390     priv->rc = OP_MAIN_NEXT_UNDELETED;
391     return IR_DONE;
392   }
393   return IR_SUCCESS;
394 }
395 
396 /**
397  * op_delete_thread - Delete all messages in thread - Implements ::pager_function_t - @ingroup pager_function_api
398  */
op_delete_thread(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)399 static int op_delete_thread(struct IndexSharedData *shared,
400                             struct PagerPrivateData *priv, int op)
401 {
402   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
403     return IR_NOT_IMPL;
404   if (!assert_mailbox_writable(shared->mailbox))
405     return IR_NO_ACTION;
406   /* L10N: CHECK_ACL */
407   /* L10N: Due to the implementation details we do not know whether we
408      delete zero, 1, 12, ... messages. So in English we use
409      "messages". Your language might have other means to express this.  */
410   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_DELETE, _("Can't delete messages")))
411   {
412     return IR_ERROR;
413   }
414 
415   int subthread = (op == OP_DELETE_SUBTHREAD);
416   int r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE, 1, subthread);
417   if (r == -1)
418     return IR_ERROR;
419   if (op == OP_PURGE_THREAD)
420   {
421     r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE, true, subthread);
422     if (r == -1)
423       return IR_ERROR;
424   }
425 
426   const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
427   if (c_delete_untag)
428     mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_TAG, 0, subthread);
429 
430   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
431   if (c_resolve)
432   {
433     priv->rc = OP_MAIN_NEXT_UNDELETED;
434   }
435 
436   if (!c_resolve && (cs_subset_number(NeoMutt->sub, "pager_index_lines") != 0))
437     pager_queue_redraw(priv, MENU_REDRAW_FULL);
438   else
439     pager_queue_redraw(priv, MENU_REDRAW_INDEX);
440 
441   if (c_resolve)
442     return IR_DONE;
443 
444   return IR_SUCCESS;
445 }
446 
447 /**
448  * op_display_address - Display full address of sender - Implements ::pager_function_t - @ingroup pager_function_api
449  */
op_display_address(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)450 static int op_display_address(struct IndexSharedData *shared,
451                               struct PagerPrivateData *priv, int op)
452 {
453   struct PagerView *pview = priv->pview;
454 
455   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
456   {
457     return IR_NOT_IMPL;
458   }
459   if (pview->mode == PAGER_MODE_ATTACH_E)
460     mutt_display_address(pview->pdata->body->email->env);
461   else
462     mutt_display_address(shared->email->env);
463   return IR_SUCCESS;
464 }
465 
466 /**
467  * op_edit_label - Add, change, or delete a message's label - Implements ::pager_function_t - @ingroup pager_function_api
468  */
op_edit_label(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)469 static int op_edit_label(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
470 {
471   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
472     return IR_NOT_IMPL;
473 
474   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
475   emaillist_add_email(&el, shared->email);
476   priv->rc = mutt_label_message(shared->mailbox, &el);
477   emaillist_clear(&el);
478 
479   if (priv->rc > 0)
480   {
481     shared->mailbox->changed = true;
482     pager_queue_redraw(priv, MENU_REDRAW_FULL);
483     mutt_message(ngettext("%d label changed", "%d labels changed", priv->rc), priv->rc);
484   }
485   else
486   {
487     mutt_message(_("No labels changed"));
488   }
489   return IR_SUCCESS;
490 }
491 
492 /**
493  * op_enter_command - Enter a neomuttrc command - Implements ::pager_function_t - @ingroup pager_function_api
494  */
op_enter_command(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)495 static int op_enter_command(struct IndexSharedData *shared,
496                             struct PagerPrivateData *priv, int op)
497 {
498   mutt_enter_command();
499   pager_queue_redraw(priv, MENU_REDRAW_FULL);
500 
501   if (OptNeedResort)
502   {
503     OptNeedResort = false;
504     if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
505       return IR_NOT_IMPL;
506     OptNeedResort = true;
507   }
508 
509   if ((priv->redraw & MENU_REDRAW_FLOW) && (priv->pview->flags & MUTT_PAGER_RETWINCH))
510   {
511     priv->rc = OP_REFORMAT_WINCH;
512     return IR_DONE;
513   }
514 
515   return IR_SUCCESS;
516 }
517 
518 /**
519  * op_exit - Exit this menu - Implements ::pager_function_t - @ingroup pager_function_api
520  */
op_exit(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)521 static int op_exit(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
522 {
523   priv->rc = -1;
524   return IR_DONE;
525 }
526 
527 /**
528  * op_extract_keys - Extract supported public keys - Implements ::pager_function_t - @ingroup pager_function_api
529  */
op_extract_keys(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)530 static int op_extract_keys(struct IndexSharedData *shared,
531                            struct PagerPrivateData *priv, int op)
532 {
533   if (!WithCrypto)
534     return IR_DONE;
535 
536   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
537     return IR_NOT_IMPL;
538 
539   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
540   emaillist_add_email(&el, shared->email);
541   crypt_extract_keys_from_messages(shared->mailbox, &el);
542   emaillist_clear(&el);
543   pager_queue_redraw(priv, MENU_REDRAW_FULL);
544   return IR_SUCCESS;
545 }
546 
547 /**
548  * op_flag_message - Toggle a message's 'important' flag - Implements ::pager_function_t - @ingroup pager_function_api
549  */
op_flag_message(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)550 static int op_flag_message(struct IndexSharedData *shared,
551                            struct PagerPrivateData *priv, int op)
552 {
553   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
554     return IR_NOT_IMPL;
555   if (!assert_mailbox_writable(shared->mailbox))
556     return IR_NO_ACTION;
557   /* L10N: CHECK_ACL */
558   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_WRITE, "Can't flag message"))
559     return IR_ERROR;
560 
561   mutt_set_flag(shared->mailbox, shared->email, MUTT_FLAG, !shared->email->flagged);
562   pager_queue_redraw(priv, MENU_REDRAW_INDEX);
563   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
564   if (c_resolve)
565   {
566     priv->rc = OP_MAIN_NEXT_UNDELETED;
567     return IR_DONE;
568   }
569   return IR_SUCCESS;
570 }
571 
572 /**
573  * op_forget_passphrase - Wipe passphrases from memory - Implements ::pager_function_t - @ingroup pager_function_api
574  */
op_forget_passphrase(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)575 static int op_forget_passphrase(struct IndexSharedData *shared,
576                                 struct PagerPrivateData *priv, int op)
577 {
578   crypt_forget_passphrase();
579   return IR_SUCCESS;
580 }
581 
582 /**
583  * op_forward_message - Forward a message with comments - Implements ::pager_function_t - @ingroup pager_function_api
584  */
op_forward_message(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)585 static int op_forward_message(struct IndexSharedData *shared,
586                               struct PagerPrivateData *priv, int op)
587 {
588   struct PagerView *pview = priv->pview;
589 
590   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
591   {
592     return IR_NOT_IMPL;
593   }
594   if (assert_attach_msg_mode(OptAttachMsg))
595     return IR_ERROR;
596   if (pview->mode == PAGER_MODE_ATTACH_E)
597   {
598     mutt_attach_forward(pview->pdata->fp, shared->email, pview->pdata->actx,
599                         pview->pdata->body, SEND_NO_FLAGS);
600   }
601   else
602   {
603     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
604     emaillist_add_email(&el, shared->email);
605 
606     mutt_send_message(SEND_FORWARD, NULL, NULL, shared->mailbox, &el, NeoMutt->sub);
607     emaillist_clear(&el);
608   }
609   pager_queue_redraw(priv, MENU_REDRAW_FULL);
610   return IR_SUCCESS;
611 }
612 
613 /**
614  * op_half_down - Scroll down 1/2 page - Implements ::pager_function_t - @ingroup pager_function_api
615  */
op_half_down(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)616 static int op_half_down(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
617 {
618   const bool c_pager_stop = cs_subset_bool(NeoMutt->sub, "pager_stop");
619   if (priv->lines[priv->cur_line].offset < (priv->st.st_size - 1))
620   {
621     priv->top_line = up_n_lines(priv->pview->win_pager->state.rows / 2,
622                                 priv->lines, priv->cur_line, priv->hide_quoted);
623     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
624   }
625   else if (c_pager_stop)
626   {
627     /* emulate "less -q" and don't go on to the next message. */
628     mutt_message(_("Bottom of message is shown"));
629   }
630   else
631   {
632     /* end of the current message, so display the next message. */
633     priv->rc = OP_MAIN_NEXT_UNDELETED;
634     return IR_DONE;
635   }
636   return IR_SUCCESS;
637 }
638 
639 /**
640  * op_half_up - Scroll up 1/2 page - Implements ::pager_function_t - @ingroup pager_function_api
641  */
op_half_up(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)642 static int op_half_up(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
643 {
644   if (priv->top_line)
645   {
646     priv->top_line = up_n_lines(priv->pview->win_pager->state.rows / 2 +
647                                     (priv->pview->win_pager->state.rows % 2),
648                                 priv->lines, priv->top_line, priv->hide_quoted);
649     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
650   }
651   else
652     mutt_message(_("Top of message is shown"));
653   return IR_SUCCESS;
654 }
655 
656 /**
657  * op_help - This screen - Implements ::pager_function_t - @ingroup pager_function_api
658  */
op_help(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)659 static int op_help(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
660 {
661   if (priv->pview->mode == PAGER_MODE_HELP)
662   {
663     /* don't let the user enter the help-menu from the help screen! */
664     mutt_error(_("Help is currently being shown"));
665     return IR_ERROR;
666   }
667   mutt_help(MENU_PAGER);
668   pager_queue_redraw(priv, MENU_REDRAW_FULL);
669   return IR_SUCCESS;
670 }
671 
672 /**
673  * op_mail - Compose a new mail message - Implements ::pager_function_t - @ingroup pager_function_api
674  */
op_mail(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)675 static int op_mail(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
676 {
677   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
678     return IR_NOT_IMPL;
679   if (assert_attach_msg_mode(OptAttachMsg))
680     return IR_ERROR;
681 
682   mutt_send_message(SEND_NO_FLAGS, NULL, NULL, shared->mailbox, NULL, NeoMutt->sub);
683   pager_queue_redraw(priv, MENU_REDRAW_FULL);
684   return IR_SUCCESS;
685 }
686 
687 /**
688  * op_mailbox_list - List mailboxes with new mail - Implements ::pager_function_t - @ingroup pager_function_api
689  */
op_mailbox_list(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)690 static int op_mailbox_list(struct IndexSharedData *shared,
691                            struct PagerPrivateData *priv, int op)
692 {
693   mutt_mailbox_list();
694   return IR_SUCCESS;
695 }
696 
697 /**
698  * op_mail_key - Mail a PGP public key - Implements ::pager_function_t - @ingroup pager_function_api
699  */
op_mail_key(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)700 static int op_mail_key(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
701 {
702   if (!(WithCrypto & APPLICATION_PGP))
703     return IR_DONE;
704   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
705     return IR_NOT_IMPL;
706   if (assert_attach_msg_mode(OptAttachMsg))
707     return IR_ERROR;
708   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
709   emaillist_add_email(&el, shared->email);
710 
711   mutt_send_message(SEND_KEY, NULL, NULL, shared->mailbox, &el, NeoMutt->sub);
712   emaillist_clear(&el);
713   pager_queue_redraw(priv, MENU_REDRAW_FULL);
714   return IR_SUCCESS;
715 }
716 
717 /**
718  * op_main_set_flag - Set a status flag on a message - Implements ::pager_function_t - @ingroup pager_function_api
719  */
op_main_set_flag(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)720 static int op_main_set_flag(struct IndexSharedData *shared,
721                             struct PagerPrivateData *priv, int op)
722 {
723   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
724     return IR_NOT_IMPL;
725   if (!assert_mailbox_writable(shared->mailbox))
726     return IR_NO_ACTION;
727 
728   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
729   emaillist_add_email(&el, shared->email);
730 
731   if (mutt_change_flag(shared->mailbox, &el, (op == OP_MAIN_SET_FLAG)) == 0)
732     pager_queue_redraw(priv, MENU_REDRAW_INDEX);
733   emaillist_clear(&el);
734 
735   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
736   if (shared->email->deleted && c_resolve)
737   {
738     priv->rc = OP_MAIN_NEXT_UNDELETED;
739     return IR_DONE;
740   }
741   return IR_SUCCESS;
742 }
743 
744 /**
745  * op_next_line - Scroll down one line - Implements ::pager_function_t - @ingroup pager_function_api
746  */
op_next_line(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)747 static int op_next_line(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
748 {
749   if (priv->lines[priv->cur_line].offset < (priv->st.st_size - 1))
750   {
751     priv->top_line++;
752     if (priv->hide_quoted)
753     {
754       while ((priv->lines[priv->top_line].color == MT_COLOR_QUOTED) &&
755              (priv->top_line < priv->lines_used))
756       {
757         priv->top_line++;
758       }
759     }
760     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
761   }
762   else
763   {
764     mutt_message(_("Bottom of message is shown"));
765   }
766   return IR_SUCCESS;
767 }
768 
769 /**
770  * op_next_page - Move to the next page - Implements ::pager_function_t - @ingroup pager_function_api
771  */
op_next_page(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)772 static int op_next_page(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
773 {
774   const bool c_pager_stop = cs_subset_bool(NeoMutt->sub, "pager_stop");
775   if (priv->lines[priv->cur_line].offset < (priv->st.st_size - 1))
776   {
777     const short c_pager_context =
778         cs_subset_number(NeoMutt->sub, "pager_context");
779     priv->top_line =
780         up_n_lines(c_pager_context, priv->lines, priv->cur_line, priv->hide_quoted);
781     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
782   }
783   else if (c_pager_stop)
784   {
785     /* emulate "less -q" and don't go on to the next message. */
786     mutt_message(_("Bottom of message is shown"));
787   }
788   else
789   {
790     /* end of the current message, so display the next message. */
791     priv->rc = OP_MAIN_NEXT_UNDELETED;
792     return IR_DONE;
793   }
794   return IR_SUCCESS;
795 }
796 
797 /**
798  * op_pager_bottom - Jump to the bottom of the message - Implements ::pager_function_t - @ingroup pager_function_api
799  */
op_pager_bottom(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)800 static int op_pager_bottom(struct IndexSharedData *shared,
801                            struct PagerPrivateData *priv, int op)
802 {
803   if (!jump_to_bottom(priv, priv->pview))
804     mutt_message(_("Bottom of message is shown"));
805 
806   return IR_SUCCESS;
807 }
808 
809 /**
810  * op_pager_hide_quoted - Toggle display of quoted text - Implements ::pager_function_t - @ingroup pager_function_api
811  */
op_pager_hide_quoted(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)812 static int op_pager_hide_quoted(struct IndexSharedData *shared,
813                                 struct PagerPrivateData *priv, int op)
814 {
815   if (!priv->has_types)
816     return IR_NO_ACTION;
817 
818   priv->hide_quoted ^= MUTT_HIDE;
819   if (priv->hide_quoted && (priv->lines[priv->top_line].color == MT_COLOR_QUOTED))
820   {
821     priv->top_line = up_n_lines(1, priv->lines, priv->top_line, priv->hide_quoted);
822   }
823   else
824   {
825     pager_queue_redraw(priv, MENU_REDRAW_BODY);
826   }
827   notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
828   return IR_SUCCESS;
829 }
830 
831 /**
832  * op_pager_skip_headers - Jump to first line after headers - Implements ::pager_function_t - @ingroup pager_function_api
833  */
op_pager_skip_headers(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)834 static int op_pager_skip_headers(struct IndexSharedData *shared,
835                                  struct PagerPrivateData *priv, int op)
836 {
837   struct PagerView *pview = priv->pview;
838 
839   if (!priv->has_types)
840     return IR_NO_ACTION;
841 
842   int dretval = 0;
843   int new_topline = 0;
844 
845   while (((new_topline < priv->lines_used) ||
846           (0 == (dretval = display_line(
847                      priv->fp, &priv->bytes_read, &priv->lines, new_topline, &priv->lines_used,
848                      &priv->lines_max, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
849                      &priv->quote_list, &priv->q_level, &priv->force_redraw,
850                      &priv->search_re, priv->pview->win_pager)))) &&
851          simple_color_is_header(priv->lines[new_topline].color))
852   {
853     new_topline++;
854   }
855 
856   if (dretval < 0)
857   {
858     /* L10N: Displayed if <skip-headers> is invoked in the pager, but
859        there is no text past the headers.
860        (I don't think this is actually possible in Mutt's code, but
861        display some kind of message in case it somehow occurs.) */
862     mutt_warning(_("No text past headers"));
863     return IR_NO_ACTION;
864   }
865   priv->top_line = new_topline;
866   notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
867   return IR_SUCCESS;
868 }
869 
870 /**
871  * op_pager_skip_quoted - Skip beyond quoted text - Implements ::pager_function_t - @ingroup pager_function_api
872  */
op_pager_skip_quoted(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)873 static int op_pager_skip_quoted(struct IndexSharedData *shared,
874                                 struct PagerPrivateData *priv, int op)
875 {
876   struct PagerView *pview = priv->pview;
877 
878   if (!priv->has_types)
879     return IR_NO_ACTION;
880 
881   const short c_skip_quoted_context =
882       cs_subset_number(NeoMutt->sub, "pager_skip_quoted_context");
883   int dretval = 0;
884   int new_topline = priv->top_line;
885   int num_quoted = 0;
886 
887   /* In a header? Skip all the email headers, and done */
888   if (simple_color_is_header(priv->lines[new_topline].color))
889   {
890     while (((new_topline < priv->lines_used) ||
891             (0 == (dretval = display_line(
892                        priv->fp, &priv->bytes_read, &priv->lines, new_topline, &priv->lines_used,
893                        &priv->lines_max, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
894                        &priv->quote_list, &priv->q_level, &priv->force_redraw,
895                        &priv->search_re, priv->pview->win_pager)))) &&
896            simple_color_is_header(priv->lines[new_topline].color))
897     {
898       new_topline++;
899     }
900     priv->top_line = new_topline;
901     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
902     return IR_SUCCESS;
903   }
904 
905   /* Already in the body? Skip past previous "context" quoted lines */
906   if (c_skip_quoted_context > 0)
907   {
908     while (((new_topline < priv->lines_used) ||
909             (0 == (dretval = display_line(
910                        priv->fp, &priv->bytes_read, &priv->lines, new_topline, &priv->lines_used,
911                        &priv->lines_max, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
912                        &priv->quote_list, &priv->q_level, &priv->force_redraw,
913                        &priv->search_re, priv->pview->win_pager)))) &&
914            (priv->lines[new_topline].color == MT_COLOR_QUOTED))
915     {
916       new_topline++;
917       num_quoted++;
918     }
919 
920     if (dretval < 0)
921     {
922       mutt_error(_("No more unquoted text after quoted text"));
923       return IR_NO_ACTION;
924     }
925   }
926 
927   if (num_quoted <= c_skip_quoted_context)
928   {
929     num_quoted = 0;
930 
931     while (((new_topline < priv->lines_used) ||
932             (0 == (dretval = display_line(
933                        priv->fp, &priv->bytes_read, &priv->lines, new_topline, &priv->lines_used,
934                        &priv->lines_max, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
935                        &priv->quote_list, &priv->q_level, &priv->force_redraw,
936                        &priv->search_re, priv->pview->win_pager)))) &&
937            (priv->lines[new_topline].color != MT_COLOR_QUOTED))
938     {
939       new_topline++;
940     }
941 
942     if (dretval < 0)
943     {
944       mutt_error(_("No more quoted text"));
945       return IR_NO_ACTION;
946     }
947 
948     while (((new_topline < priv->lines_used) ||
949             (0 == (dretval = display_line(
950                        priv->fp, &priv->bytes_read, &priv->lines, new_topline, &priv->lines_used,
951                        &priv->lines_max, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
952                        &priv->quote_list, &priv->q_level, &priv->force_redraw,
953                        &priv->search_re, priv->pview->win_pager)))) &&
954            (priv->lines[new_topline].color == MT_COLOR_QUOTED))
955     {
956       new_topline++;
957       num_quoted++;
958     }
959 
960     if (dretval < 0)
961     {
962       mutt_error(_("No more unquoted text after quoted text"));
963       return IR_NO_ACTION;
964     }
965   }
966   priv->top_line = new_topline - MIN(c_skip_quoted_context, num_quoted);
967   notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
968   return IR_SUCCESS;
969 }
970 
971 /**
972  * op_pager_top - Jump to the top of the message - Implements ::pager_function_t - @ingroup pager_function_api
973  */
op_pager_top(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)974 static int op_pager_top(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
975 {
976   if (priv->top_line)
977     priv->top_line = 0;
978   else
979     mutt_message(_("Top of message is shown"));
980   return IR_SUCCESS;
981 }
982 
983 /**
984  * op_pipe - Pipe message/attachment to a shell command - Implements ::pager_function_t - @ingroup pager_function_api
985  */
op_pipe(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)986 static int op_pipe(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
987 {
988   struct PagerView *pview = priv->pview;
989 
990   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH)))
991   {
992     return IR_NOT_IMPL;
993   }
994   if (pview->mode == PAGER_MODE_ATTACH)
995   {
996     mutt_pipe_attachment_list(pview->pdata->actx, pview->pdata->fp, false,
997                               pview->pdata->body, false);
998   }
999   else
1000   {
1001     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1002     el_add_tagged(&el, shared->ctx, shared->email, false);
1003     mutt_pipe_message(shared->mailbox, &el);
1004     emaillist_clear(&el);
1005   }
1006   return IR_SUCCESS;
1007 }
1008 
1009 /**
1010  * op_prev_line - Scroll up one line - Implements ::pager_function_t - @ingroup pager_function_api
1011  */
op_prev_line(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1012 static int op_prev_line(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1013 {
1014   if (priv->top_line)
1015   {
1016     priv->top_line = up_n_lines(1, priv->lines, priv->top_line, priv->hide_quoted);
1017     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
1018   }
1019   else
1020   {
1021     mutt_message(_("Top of message is shown"));
1022   }
1023   return IR_SUCCESS;
1024 }
1025 
1026 /**
1027  * op_prev_page - Move to the previous page - Implements ::pager_function_t - @ingroup pager_function_api
1028  */
op_prev_page(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1029 static int op_prev_page(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1030 {
1031   if (priv->top_line == 0)
1032   {
1033     mutt_message(_("Top of message is shown"));
1034   }
1035   else
1036   {
1037     const short c_pager_context =
1038         cs_subset_number(NeoMutt->sub, "pager_context");
1039     priv->top_line = up_n_lines(priv->pview->win_pager->state.rows - c_pager_context,
1040                                 priv->lines, priv->top_line, priv->hide_quoted);
1041     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
1042   }
1043   return IR_SUCCESS;
1044 }
1045 
1046 /**
1047  * op_print - Print the current entry - Implements ::pager_function_t - @ingroup pager_function_api
1048  */
op_print(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1049 static int op_print(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1050 {
1051   struct PagerView *pview = priv->pview;
1052 
1053   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH)))
1054   {
1055     return IR_NOT_IMPL;
1056   }
1057   if (pview->mode == PAGER_MODE_ATTACH)
1058   {
1059     mutt_print_attachment_list(pview->pdata->actx, pview->pdata->fp, false,
1060                                pview->pdata->body);
1061   }
1062   else
1063   {
1064     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1065     el_add_tagged(&el, shared->ctx, shared->email, false);
1066     mutt_print_message(shared->mailbox, &el);
1067     emaillist_clear(&el);
1068   }
1069   return IR_SUCCESS;
1070 }
1071 
1072 /**
1073  * op_quit - Save changes to mailbox and quit - Implements ::pager_function_t - @ingroup pager_function_api
1074  */
op_quit(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1075 static int op_quit(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1076 {
1077   const enum QuadOption c_quit = cs_subset_quad(NeoMutt->sub, "quit");
1078   if (query_quadoption(c_quit, _("Quit NeoMutt?")) == MUTT_YES)
1079   {
1080     /* avoid prompting again in the index menu */
1081     cs_subset_str_native_set(NeoMutt->sub, "quit", MUTT_YES, NULL);
1082     return IR_DONE;
1083   }
1084   return IR_SUCCESS;
1085 }
1086 
1087 /**
1088  * op_recall_message - Recall a postponed message - Implements ::pager_function_t - @ingroup pager_function_api
1089  */
op_recall_message(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1090 static int op_recall_message(struct IndexSharedData *shared,
1091                              struct PagerPrivateData *priv, int op)
1092 {
1093   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1094     return IR_NOT_IMPL;
1095   if (assert_attach_msg_mode(OptAttachMsg))
1096     return IR_ERROR;
1097   struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1098   emaillist_add_email(&el, shared->email);
1099 
1100   mutt_send_message(SEND_POSTPONED, NULL, NULL, shared->mailbox, &el, NeoMutt->sub);
1101   emaillist_clear(&el);
1102   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1103   return IR_SUCCESS;
1104 }
1105 
1106 /**
1107  * op_redraw - Clear and redraw the screen - Implements ::pager_function_t - @ingroup pager_function_api
1108  */
op_redraw(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1109 static int op_redraw(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1110 {
1111   window_invalidate_all();
1112   mutt_window_reflow(NULL);
1113   clearok(stdscr, true);
1114   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1115   return IR_SUCCESS;
1116 }
1117 
1118 /**
1119  * op_reply - Reply to a message - Implements ::pager_function_t - @ingroup pager_function_api
1120  */
op_reply(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1121 static int op_reply(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1122 {
1123   struct PagerView *pview = priv->pview;
1124 
1125   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
1126   {
1127     return IR_NOT_IMPL;
1128   }
1129   if (assert_attach_msg_mode(OptAttachMsg))
1130     return IR_ERROR;
1131 
1132   SendFlags replyflags = SEND_REPLY;
1133   if (op == OP_GROUP_REPLY)
1134     replyflags |= SEND_GROUP_REPLY;
1135   else if (op == OP_GROUP_CHAT_REPLY)
1136     replyflags |= SEND_GROUP_CHAT_REPLY;
1137   else if (op == OP_LIST_REPLY)
1138     replyflags |= SEND_LIST_REPLY;
1139 
1140   if (pview->mode == PAGER_MODE_ATTACH_E)
1141   {
1142     mutt_attach_reply(pview->pdata->fp, shared->mailbox, shared->email,
1143                       pview->pdata->actx, pview->pdata->body, replyflags);
1144   }
1145   else
1146   {
1147     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1148     emaillist_add_email(&el, shared->email);
1149     mutt_send_message(replyflags, NULL, NULL, shared->mailbox, &el, NeoMutt->sub);
1150     emaillist_clear(&el);
1151   }
1152   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1153   return IR_SUCCESS;
1154 }
1155 
1156 /**
1157  * op_list_subscribe - Subscribe to a mailing list - Implements ::pager_function_t - @ingroup pager_function_api
1158  */
op_list_subscribe(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1159 static int op_list_subscribe(struct IndexSharedData *shared,
1160                              struct PagerPrivateData *priv, int op)
1161 {
1162   const int rc = mutt_send_list_subscribe(shared->mailbox, shared->email) ? IR_SUCCESS : IR_NO_ACTION;
1163   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1164   return rc;
1165 }
1166 
1167 /**
1168  * op_list_unsubscribe - Unsubscribe from mailing list - Implements ::pager_function_t - @ingroup pager_function_api
1169  */
op_list_unsubscribe(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1170 static int op_list_unsubscribe(struct IndexSharedData *shared,
1171                                struct PagerPrivateData *priv, int op)
1172 {
1173   const int rc = mutt_send_list_unsubscribe(shared->mailbox, shared->email) ?
1174                      IR_SUCCESS :
1175                      IR_NO_ACTION;
1176   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1177   return rc;
1178 }
1179 
1180 /**
1181  * op_resend - Use the current message as a template for a new one - Implements ::pager_function_t - @ingroup pager_function_api
1182  */
op_resend(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1183 static int op_resend(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1184 {
1185   struct PagerView *pview = priv->pview;
1186 
1187   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
1188   {
1189     return IR_NOT_IMPL;
1190   }
1191   if (assert_attach_msg_mode(OptAttachMsg))
1192     return IR_ERROR;
1193   if (pview->mode == PAGER_MODE_ATTACH_E)
1194   {
1195     mutt_attach_resend(pview->pdata->fp, shared->mailbox, pview->pdata->actx,
1196                        pview->pdata->body);
1197   }
1198   else
1199   {
1200     mutt_resend_message(NULL, shared->mailbox, shared->email, NeoMutt->sub);
1201   }
1202   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1203   return IR_SUCCESS;
1204 }
1205 
1206 /**
1207  * op_save - Save message/attachment to a mailbox/file - Implements ::pager_function_t - @ingroup pager_function_api
1208  */
op_save(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1209 static int op_save(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1210 {
1211   if ((op == OP_DECRYPT_SAVE) && !WithCrypto)
1212     return IR_DONE;
1213 
1214   struct PagerView *pview = priv->pview;
1215 
1216   if (pview->mode == PAGER_MODE_ATTACH)
1217   {
1218     mutt_save_attachment_list(pview->pdata->actx, pview->pdata->fp, false,
1219                               pview->pdata->body, shared->email, NULL);
1220     return IR_SUCCESS;
1221   }
1222 
1223   return op_copy_message(shared, priv, op);
1224 }
1225 
1226 /**
1227  * op_search - Search for a regular expression - Implements ::pager_function_t - @ingroup pager_function_api
1228  */
op_search(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1229 static int op_search(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1230 {
1231   struct PagerView *pview = priv->pview;
1232 
1233   char buf[1024] = { 0 };
1234   mutt_str_copy(buf, priv->search_str, sizeof(buf));
1235   if (mutt_get_field(
1236           ((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ? _("Search for: ") : _("Reverse search for: "),
1237           buf, sizeof(buf), MUTT_CLEAR | MUTT_PATTERN, false, NULL, NULL) != 0)
1238   {
1239     return IR_NO_ACTION;
1240   }
1241 
1242   if (strcmp(buf, priv->search_str) == 0)
1243   {
1244     if (priv->search_compiled)
1245     {
1246       /* do an implicit search-next */
1247       if (op == OP_SEARCH)
1248         op = OP_SEARCH_NEXT;
1249       else
1250         op = OP_SEARCH_OPPOSITE;
1251 
1252       priv->wrapped = false;
1253       op_search_next(shared, priv, op);
1254     }
1255   }
1256 
1257   if (buf[0] == '\0')
1258     return IR_NO_ACTION;
1259 
1260   mutt_str_copy(priv->search_str, buf, sizeof(priv->search_str));
1261 
1262   /* leave search_back alone if op == OP_SEARCH_NEXT */
1263   if (op == OP_SEARCH)
1264     priv->search_back = false;
1265   else if (op == OP_SEARCH_REVERSE)
1266     priv->search_back = true;
1267 
1268   if (priv->search_compiled)
1269   {
1270     regfree(&priv->search_re);
1271     for (size_t i = 0; i < priv->lines_used; i++)
1272     {
1273       FREE(&(priv->lines[i].search));
1274       priv->lines[i].search_arr_size = -1;
1275     }
1276   }
1277 
1278   uint16_t rflags = mutt_mb_is_lower(priv->search_str) ? REG_ICASE : 0;
1279   int err = REG_COMP(&priv->search_re, priv->search_str, REG_NEWLINE | rflags);
1280   if (err != 0)
1281   {
1282     regerror(err, &priv->search_re, buf, sizeof(buf));
1283     mutt_error("%s", buf);
1284     for (size_t i = 0; i < priv->lines_max; i++)
1285     {
1286       /* cleanup */
1287       FREE(&(priv->lines[i].search));
1288       priv->lines[i].search_arr_size = -1;
1289     }
1290     priv->search_flag = 0;
1291     priv->search_compiled = false;
1292   }
1293   else
1294   {
1295     priv->search_compiled = true;
1296     /* update the search pointers */
1297     int line_num = 0;
1298     while (display_line(priv->fp, &priv->bytes_read, &priv->lines, line_num,
1299                         &priv->lines_used, &priv->lines_max,
1300                         MUTT_SEARCH | (pview->flags & MUTT_PAGER_NSKIP) |
1301                             (pview->flags & MUTT_PAGER_NOWRAP) | priv->has_types,
1302                         &priv->quote_list, &priv->q_level, &priv->force_redraw,
1303                         &priv->search_re, priv->pview->win_pager) == 0)
1304     {
1305       line_num++;
1306     }
1307 
1308     if (!priv->search_back)
1309     {
1310       /* searching forward */
1311       int i;
1312       for (i = priv->top_line; i < priv->lines_used; i++)
1313       {
1314         if ((!priv->hide_quoted || (priv->lines[i].color != MT_COLOR_QUOTED)) &&
1315             !priv->lines[i].cont_line && (priv->lines[i].search_arr_size > 0))
1316         {
1317           break;
1318         }
1319       }
1320 
1321       if (i < priv->lines_used)
1322         priv->top_line = i;
1323     }
1324     else
1325     {
1326       /* searching backward */
1327       int i;
1328       for (i = priv->top_line; i >= 0; i--)
1329       {
1330         if ((!priv->hide_quoted || (priv->lines[i].color != MT_COLOR_QUOTED)) &&
1331             !priv->lines[i].cont_line && (priv->lines[i].search_arr_size > 0))
1332         {
1333           break;
1334         }
1335       }
1336 
1337       if (i >= 0)
1338         priv->top_line = i;
1339     }
1340 
1341     if (priv->lines[priv->top_line].search_arr_size == 0)
1342     {
1343       priv->search_flag = 0;
1344       mutt_error(_("Not found"));
1345     }
1346     else
1347     {
1348       const short c_search_context =
1349           cs_subset_number(NeoMutt->sub, "search_context");
1350       priv->search_flag = MUTT_SEARCH;
1351       /* give some context for search results */
1352       if (c_search_context < priv->pview->win_pager->state.rows)
1353         priv->searchctx = c_search_context;
1354       else
1355         priv->searchctx = 0;
1356       if (priv->top_line - priv->searchctx > 0)
1357         priv->top_line -= priv->searchctx;
1358     }
1359   }
1360   pager_queue_redraw(priv, MENU_REDRAW_BODY);
1361   notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
1362   return IR_SUCCESS;
1363 }
1364 
1365 /**
1366  * op_search_next - Search for next match - Implements ::pager_function_t - @ingroup pager_function_api
1367  */
op_search_next(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1368 static int op_search_next(struct IndexSharedData *shared,
1369                           struct PagerPrivateData *priv, int op)
1370 {
1371   if (priv->search_compiled)
1372   {
1373     const short c_search_context =
1374         cs_subset_number(NeoMutt->sub, "search_context");
1375     priv->wrapped = false;
1376 
1377     if (c_search_context < priv->pview->win_pager->state.rows)
1378       priv->searchctx = c_search_context;
1379     else
1380       priv->searchctx = 0;
1381 
1382   search_next:
1383     if ((!priv->search_back && (op == OP_SEARCH_NEXT)) ||
1384         (priv->search_back && (op == OP_SEARCH_OPPOSITE)))
1385     {
1386       /* searching forward */
1387       int i;
1388       for (i = priv->wrapped ? 0 : priv->top_line + priv->searchctx + 1;
1389            i < priv->lines_used; i++)
1390       {
1391         if ((!priv->hide_quoted || (priv->lines[i].color != MT_COLOR_QUOTED)) &&
1392             !priv->lines[i].cont_line && (priv->lines[i].search_arr_size > 0))
1393         {
1394           break;
1395         }
1396       }
1397 
1398       const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
1399       if (i < priv->lines_used)
1400         priv->top_line = i;
1401       else if (priv->wrapped || !c_wrap_search)
1402         mutt_error(_("Not found"));
1403       else
1404       {
1405         mutt_message(_("Search wrapped to top"));
1406         priv->wrapped = true;
1407         goto search_next;
1408       }
1409     }
1410     else
1411     {
1412       /* searching backward */
1413       int i;
1414       for (i = priv->wrapped ? priv->lines_used : priv->top_line + priv->searchctx - 1;
1415            i >= 0; i--)
1416       {
1417         if ((!priv->hide_quoted ||
1418              (priv->has_types && (priv->lines[i].color != MT_COLOR_QUOTED))) &&
1419             !priv->lines[i].cont_line && (priv->lines[i].search_arr_size > 0))
1420         {
1421           break;
1422         }
1423       }
1424 
1425       const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
1426       if (i >= 0)
1427         priv->top_line = i;
1428       else if (priv->wrapped || !c_wrap_search)
1429         mutt_error(_("Not found"));
1430       else
1431       {
1432         mutt_message(_("Search wrapped to bottom"));
1433         priv->wrapped = true;
1434         goto search_next;
1435       }
1436     }
1437 
1438     if (priv->lines[priv->top_line].search_arr_size > 0)
1439     {
1440       priv->search_flag = MUTT_SEARCH;
1441       /* give some context for search results */
1442       if (priv->top_line - priv->searchctx > 0)
1443         priv->top_line -= priv->searchctx;
1444     }
1445 
1446     notify_send(priv->notify, NT_PAGER, NT_PAGER_VIEW, priv);
1447     return IR_SUCCESS;
1448   }
1449 
1450   /* no previous search pattern */
1451   return op_search(shared, priv, op);
1452 }
1453 
1454 /**
1455  * op_search_toggle - Toggle search pattern coloring - Implements ::pager_function_t - @ingroup pager_function_api
1456  */
op_search_toggle(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1457 static int op_search_toggle(struct IndexSharedData *shared,
1458                             struct PagerPrivateData *priv, int op)
1459 {
1460   if (priv->search_compiled)
1461   {
1462     priv->search_flag ^= MUTT_SEARCH;
1463     pager_queue_redraw(priv, MENU_REDRAW_BODY);
1464   }
1465   return IR_SUCCESS;
1466 }
1467 
1468 /**
1469  * op_shell_escape - Invoke a command in a subshell - Implements ::pager_function_t - @ingroup pager_function_api
1470  */
op_shell_escape(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1471 static int op_shell_escape(struct IndexSharedData *shared,
1472                            struct PagerPrivateData *priv, int op)
1473 {
1474   if (mutt_shell_escape())
1475   {
1476     mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_FORCE);
1477   }
1478   return IR_SUCCESS;
1479 }
1480 
1481 /**
1482  * op_sort - Sort messages - Implements ::pager_function_t - @ingroup pager_function_api
1483  */
op_sort(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1484 static int op_sort(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1485 {
1486   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1487     return IR_NOT_IMPL;
1488   if (mutt_select_sort(op == OP_SORT_REVERSE))
1489   {
1490     OptNeedResort = true;
1491     priv->rc = OP_DISPLAY_MESSAGE;
1492     return IR_DONE;
1493   }
1494   return IR_SUCCESS;
1495 }
1496 
1497 /**
1498  * op_tag - Tag the current entry - Implements ::pager_function_t - @ingroup pager_function_api
1499  */
op_tag(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1500 static int op_tag(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1501 {
1502   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1503     return IR_NOT_IMPL;
1504   mutt_set_flag(shared->mailbox, shared->email, MUTT_TAG, !shared->email->tagged);
1505 
1506   pager_queue_redraw(priv, MENU_REDRAW_INDEX);
1507   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
1508   if (c_resolve)
1509   {
1510     priv->rc = OP_NEXT_ENTRY;
1511     return IR_DONE;
1512   }
1513   return IR_SUCCESS;
1514 }
1515 
1516 /**
1517  * op_toggle_new - Toggle a message's 'new' flag - Implements ::pager_function_t - @ingroup pager_function_api
1518  */
op_toggle_new(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1519 static int op_toggle_new(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1520 {
1521   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1522     return IR_NOT_IMPL;
1523   if (!assert_mailbox_writable(shared->mailbox))
1524     return IR_NO_ACTION;
1525   /* L10N: CHECK_ACL */
1526   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_SEEN, _("Can't toggle new")))
1527     return IR_ERROR;
1528 
1529   if (shared->email->read || shared->email->old)
1530     mutt_set_flag(shared->mailbox, shared->email, MUTT_NEW, true);
1531   else if (!priv->first || (priv->delay_read_timestamp != 0))
1532     mutt_set_flag(shared->mailbox, shared->email, MUTT_READ, true);
1533   priv->delay_read_timestamp = 0;
1534   priv->first = false;
1535   shared->ctx->msg_in_pager = -1;
1536   priv->win_pbar->actions |= WA_RECALC;
1537   pager_queue_redraw(priv, MENU_REDRAW_INDEX);
1538   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
1539   if (c_resolve)
1540   {
1541     priv->rc = OP_MAIN_NEXT_UNDELETED;
1542     return IR_DONE;
1543   }
1544   return IR_SUCCESS;
1545 }
1546 
1547 /**
1548  * op_undelete - Undelete the current entry - Implements ::pager_function_t - @ingroup pager_function_api
1549  */
op_undelete(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1550 static int op_undelete(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1551 {
1552   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1553     return IR_NOT_IMPL;
1554   if (!assert_mailbox_writable(shared->mailbox))
1555     return IR_NO_ACTION;
1556   /* L10N: CHECK_ACL */
1557   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_DELETE, _("Can't undelete message")))
1558   {
1559     return IR_ERROR;
1560   }
1561 
1562   mutt_set_flag(shared->mailbox, shared->email, MUTT_DELETE, false);
1563   mutt_set_flag(shared->mailbox, shared->email, MUTT_PURGE, false);
1564   pager_queue_redraw(priv, MENU_REDRAW_INDEX);
1565   const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
1566   if (c_resolve)
1567   {
1568     priv->rc = OP_NEXT_ENTRY;
1569     return IR_DONE;
1570   }
1571   return IR_SUCCESS;
1572 }
1573 
1574 /**
1575  * op_undelete_thread - Undelete all messages in thread - Implements ::pager_function_t - @ingroup pager_function_api
1576  */
op_undelete_thread(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1577 static int op_undelete_thread(struct IndexSharedData *shared,
1578                               struct PagerPrivateData *priv, int op)
1579 {
1580   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1581     return IR_NOT_IMPL;
1582   if (!assert_mailbox_writable(shared->mailbox))
1583     return IR_NO_ACTION;
1584   /* L10N: CHECK_ACL */
1585   /* L10N: Due to the implementation details we do not know whether we
1586      undelete zero, 1, 12, ... messages. So in English we use
1587      "messages". Your language might have other means to express this. */
1588   if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_DELETE, _("Can't undelete messages")))
1589   {
1590     return IR_ERROR;
1591   }
1592 
1593   int r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE,
1594                                false, (op != OP_UNDELETE_THREAD));
1595   if (r != -1)
1596   {
1597     r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE, false,
1598                              (op != OP_UNDELETE_THREAD));
1599   }
1600   if (r != -1)
1601   {
1602     const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
1603     if (c_resolve)
1604     {
1605       priv->rc = (op == OP_DELETE_THREAD) ? OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
1606     }
1607 
1608     if (!c_resolve && (cs_subset_number(NeoMutt->sub, "pager_index_lines") != 0))
1609       pager_queue_redraw(priv, MENU_REDRAW_FULL);
1610     else
1611       pager_queue_redraw(priv, MENU_REDRAW_INDEX);
1612 
1613     if (c_resolve)
1614       return IR_DONE;
1615   }
1616   return IR_SUCCESS;
1617 }
1618 
1619 /**
1620  * op_version - Show the NeoMutt version number and date - Implements ::pager_function_t - @ingroup pager_function_api
1621  */
op_version(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1622 static int op_version(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1623 {
1624   mutt_message(mutt_make_version());
1625   return IR_SUCCESS;
1626 }
1627 
1628 /**
1629  * op_view_attachments - Show MIME attachments - Implements ::pager_function_t - @ingroup pager_function_api
1630  */
op_view_attachments(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1631 static int op_view_attachments(struct IndexSharedData *shared,
1632                                struct PagerPrivateData *priv, int op)
1633 {
1634   struct PagerView *pview = priv->pview;
1635 
1636   if (pview->flags & MUTT_PAGER_ATTACHMENT)
1637   {
1638     priv->rc = OP_ATTACH_COLLAPSE;
1639     return IR_DONE;
1640   }
1641 
1642   if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
1643     return IR_NOT_IMPL;
1644   dlg_select_attachment(NeoMutt->sub, shared->mailbox, shared->email,
1645                         pview->pdata->fp);
1646   if (shared->email->attach_del)
1647     shared->mailbox->changed = true;
1648   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1649   return IR_SUCCESS;
1650 }
1651 
1652 /**
1653  * op_what_key - Display the keycode for a key press - Implements ::pager_function_t - @ingroup pager_function_api
1654  */
op_what_key(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1655 static int op_what_key(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1656 {
1657   mutt_what_key();
1658   return IR_SUCCESS;
1659 }
1660 
1661 // -----------------------------------------------------------------------------
1662 
1663 #ifdef USE_NNTP
1664 /**
1665  * op_followup - Followup to newsgroup - Implements ::pager_function_t - @ingroup pager_function_api
1666  */
op_followup(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1667 static int op_followup(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1668 {
1669   struct PagerView *pview = priv->pview;
1670 
1671   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
1672   {
1673     return IR_NOT_IMPL;
1674   }
1675   if (assert_attach_msg_mode(OptAttachMsg))
1676     return IR_ERROR;
1677 
1678   char *followup_to = NULL;
1679   if (pview->mode == PAGER_MODE_ATTACH_E)
1680     followup_to = pview->pdata->body->email->env->followup_to;
1681   else
1682     followup_to = shared->email->env->followup_to;
1683 
1684   const enum QuadOption c_followup_to_poster =
1685       cs_subset_quad(NeoMutt->sub, "followup_to_poster");
1686   if (!followup_to || !mutt_istr_equal(followup_to, "poster") ||
1687       (query_quadoption(c_followup_to_poster,
1688                         _("Reply by mail as poster prefers?")) != MUTT_YES))
1689   {
1690     const enum QuadOption c_post_moderated =
1691         cs_subset_quad(NeoMutt->sub, "post_moderated");
1692     if ((shared->mailbox->type == MUTT_NNTP) &&
1693         !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
1694     {
1695       return IR_ERROR;
1696     }
1697     if (pview->mode == PAGER_MODE_ATTACH_E)
1698     {
1699       mutt_attach_reply(pview->pdata->fp, shared->mailbox, shared->email,
1700                         pview->pdata->actx, pview->pdata->body, SEND_NEWS | SEND_REPLY);
1701     }
1702     else
1703     {
1704       struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1705       emaillist_add_email(&el, shared->email);
1706       mutt_send_message(SEND_NEWS | SEND_REPLY, NULL, NULL, shared->mailbox,
1707                         &el, NeoMutt->sub);
1708       emaillist_clear(&el);
1709     }
1710     pager_queue_redraw(priv, MENU_REDRAW_FULL);
1711     return IR_SUCCESS;
1712   }
1713 
1714   return op_reply(shared, priv, op);
1715 }
1716 
1717 /**
1718  * op_forward_to_group - Forward to newsgroup - Implements ::pager_function_t - @ingroup pager_function_api
1719  */
op_forward_to_group(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1720 static int op_forward_to_group(struct IndexSharedData *shared,
1721                                struct PagerPrivateData *priv, int op)
1722 {
1723   struct PagerView *pview = priv->pview;
1724 
1725   if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) || (pview->mode == PAGER_MODE_ATTACH_E)))
1726   {
1727     return IR_NOT_IMPL;
1728   }
1729   if (assert_attach_msg_mode(OptAttachMsg))
1730     return IR_ERROR;
1731   const enum QuadOption c_post_moderated =
1732       cs_subset_quad(NeoMutt->sub, "post_moderated");
1733   if ((shared->mailbox->type == MUTT_NNTP) &&
1734       !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
1735   {
1736     return IR_ERROR;
1737   }
1738   if (pview->mode == PAGER_MODE_ATTACH_E)
1739   {
1740     mutt_attach_forward(pview->pdata->fp, shared->email, pview->pdata->actx,
1741                         pview->pdata->body, SEND_NEWS);
1742   }
1743   else
1744   {
1745     struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
1746     emaillist_add_email(&el, shared->email);
1747 
1748     mutt_send_message(SEND_NEWS | SEND_FORWARD, NULL, NULL, shared->mailbox,
1749                       &el, NeoMutt->sub);
1750     emaillist_clear(&el);
1751   }
1752   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1753   return IR_SUCCESS;
1754 }
1755 
1756 /**
1757  * op_post - Post message to newsgroup - Implements ::pager_function_t - @ingroup pager_function_api
1758  */
op_post(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1759 static int op_post(struct IndexSharedData *shared, struct PagerPrivateData *priv, int op)
1760 {
1761   if (!assert_pager_mode(priv->pview->mode == PAGER_MODE_EMAIL))
1762     return IR_NOT_IMPL;
1763   if (assert_attach_msg_mode(OptAttachMsg))
1764     return IR_ERROR;
1765   const enum QuadOption c_post_moderated =
1766       cs_subset_quad(NeoMutt->sub, "post_moderated");
1767   if ((shared->mailbox->type == MUTT_NNTP) &&
1768       !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
1769   {
1770     return IR_ERROR;
1771   }
1772 
1773   mutt_send_message(SEND_NEWS, NULL, NULL, shared->mailbox, NULL, NeoMutt->sub);
1774   pager_queue_redraw(priv, MENU_REDRAW_FULL);
1775   return IR_SUCCESS;
1776 }
1777 #endif
1778 
1779 #ifdef USE_SIDEBAR
1780 /**
1781  * op_sidebar_move - Move the sidebar highlight - Implements ::pager_function_t - @ingroup pager_function_api
1782  */
op_sidebar_move(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1783 static int op_sidebar_move(struct IndexSharedData *shared,
1784                            struct PagerPrivateData *priv, int op)
1785 {
1786   struct MuttWindow *dlg = dialog_find(priv->pview->win_pager);
1787   struct MuttWindow *win_sidebar = window_find_child(dlg, WT_SIDEBAR);
1788   if (!win_sidebar)
1789     return IR_NO_ACTION;
1790   sb_change_mailbox(win_sidebar, op);
1791   return IR_SUCCESS;
1792 }
1793 
1794 /**
1795  * op_sidebar_toggle_visible - Make the sidebar (in)visible - Implements ::pager_function_t - @ingroup pager_function_api
1796  */
op_sidebar_toggle_visible(struct IndexSharedData * shared,struct PagerPrivateData * priv,int op)1797 static int op_sidebar_toggle_visible(struct IndexSharedData *shared,
1798                                      struct PagerPrivateData *priv, int op)
1799 {
1800   bool_str_toggle(NeoMutt->sub, "sidebar_visible", NULL);
1801   struct MuttWindow *dlg = dialog_find(priv->pview->win_pager);
1802   mutt_window_reflow(dlg);
1803   return IR_SUCCESS;
1804 }
1805 #endif
1806 
1807 // -----------------------------------------------------------------------------
1808 
1809 /**
1810  * PagerFunctions - All the NeoMutt functions that the Pager supports
1811  */
1812 struct PagerFunction PagerFunctions[] = {
1813   // clang-format off
1814   { OP_BOUNCE_MESSAGE,         op_bounce_message },
1815   { OP_CHECK_STATS,            op_check_stats },
1816   { OP_CHECK_TRADITIONAL,      op_check_traditional },
1817   { OP_COMPOSE_TO_SENDER,      op_compose_to_sender },
1818   { OP_COPY_MESSAGE,           op_copy_message },
1819   { OP_DECODE_COPY,            op_copy_message },
1820   { OP_DECODE_SAVE,            op_copy_message },
1821   { OP_DECRYPT_COPY,           op_copy_message },
1822   { OP_CREATE_ALIAS,           op_create_alias },
1823   { OP_DECRYPT_SAVE,           op_save },
1824   { OP_DELETE,                 op_delete },
1825   { OP_PURGE_MESSAGE,          op_delete },
1826   { OP_DELETE_SUBTHREAD,       op_delete_thread },
1827   { OP_DELETE_THREAD,          op_delete_thread },
1828   { OP_PURGE_THREAD,           op_delete_thread },
1829   { OP_DISPLAY_ADDRESS,        op_display_address },
1830   { OP_EDIT_LABEL,             op_edit_label },
1831   { OP_ENTER_COMMAND,          op_enter_command },
1832   { OP_EXIT,                   op_exit },
1833   { OP_EXTRACT_KEYS,           op_extract_keys },
1834   { OP_FLAG_MESSAGE,           op_flag_message },
1835 #ifdef USE_NNTP
1836   { OP_FOLLOWUP,               op_followup },
1837 #endif
1838   { OP_FORGET_PASSPHRASE,      op_forget_passphrase },
1839   { OP_FORWARD_MESSAGE,        op_forward_message },
1840 #ifdef USE_NNTP
1841   { OP_FORWARD_TO_GROUP,       op_forward_to_group },
1842 #endif
1843   { OP_HALF_DOWN,              op_half_down },
1844   { OP_HALF_UP,                op_half_up },
1845   { OP_HELP,                   op_help },
1846   { OP_MAIL,                   op_mail },
1847   { OP_MAILBOX_LIST,           op_mailbox_list },
1848   { OP_MAIL_KEY,               op_mail_key },
1849   { OP_MAIN_CLEAR_FLAG,        op_main_set_flag },
1850   { OP_MAIN_SET_FLAG,          op_main_set_flag },
1851   { OP_NEXT_LINE,              op_next_line },
1852   { OP_NEXT_PAGE,              op_next_page },
1853   { OP_PAGER_BOTTOM,           op_pager_bottom },
1854   { OP_PAGER_HIDE_QUOTED,      op_pager_hide_quoted },
1855   { OP_PAGER_SKIP_HEADERS,     op_pager_skip_headers },
1856   { OP_PAGER_SKIP_QUOTED,      op_pager_skip_quoted },
1857   { OP_PAGER_TOP,              op_pager_top },
1858   { OP_PIPE,                   op_pipe },
1859 #ifdef USE_NNTP
1860   { OP_POST,                   op_post },
1861 #endif
1862   { OP_PREV_LINE,              op_prev_line },
1863   { OP_PREV_PAGE,              op_prev_page },
1864   { OP_PRINT,                  op_print },
1865   { OP_QUIT,                   op_quit },
1866   { OP_RECALL_MESSAGE,         op_recall_message },
1867   { OP_REDRAW,                 op_redraw },
1868   { OP_GROUP_CHAT_REPLY,       op_reply },
1869   { OP_GROUP_REPLY,            op_reply },
1870   { OP_LIST_REPLY,             op_reply },
1871   { OP_LIST_SUBSCRIBE,         op_list_subscribe },
1872   { OP_LIST_UNSUBSCRIBE,       op_list_unsubscribe },
1873   { OP_REPLY,                  op_reply },
1874   { OP_RESEND,                 op_resend },
1875   { OP_SAVE,                   op_save },
1876   { OP_SEARCH,                 op_search },
1877   { OP_SEARCH_REVERSE,         op_search },
1878   { OP_SEARCH_NEXT,            op_search_next },
1879   { OP_SEARCH_OPPOSITE,        op_search_next },
1880   { OP_SEARCH_TOGGLE,          op_search_toggle },
1881   { OP_SHELL_ESCAPE,           op_shell_escape },
1882   { OP_SIDEBAR_FIRST,          op_sidebar_move },
1883   { OP_SIDEBAR_LAST,           op_sidebar_move },
1884   { OP_SIDEBAR_NEXT,           op_sidebar_move },
1885   { OP_SIDEBAR_NEXT_NEW,       op_sidebar_move },
1886   { OP_SIDEBAR_PAGE_DOWN,      op_sidebar_move },
1887   { OP_SIDEBAR_PAGE_UP,        op_sidebar_move },
1888   { OP_SIDEBAR_PREV,           op_sidebar_move },
1889   { OP_SIDEBAR_PREV_NEW,       op_sidebar_move },
1890   { OP_SIDEBAR_TOGGLE_VISIBLE, op_sidebar_toggle_visible },
1891   { OP_SORT,                   op_sort },
1892   { OP_SORT_REVERSE,           op_sort },
1893   { OP_TAG,                    op_tag },
1894   { OP_TOGGLE_NEW,             op_toggle_new },
1895   { OP_UNDELETE,               op_undelete },
1896   { OP_UNDELETE_SUBTHREAD,     op_undelete_thread },
1897   { OP_UNDELETE_THREAD,        op_undelete_thread },
1898   { OP_VERSION,                op_version },
1899   { OP_VIEW_ATTACHMENTS,       op_view_attachments },
1900   { OP_WHAT_KEY,               op_what_key },
1901   { 0, NULL },
1902   // clang-format on
1903 };
1904 
1905 /**
1906  * pager_function_dispatcher - Perform a Pager function
1907  * @param win_pager Window for the Index
1908  * @param op        Operation to perform, e.g. OP_MAIN_LIMIT
1909  * @retval num #IndexRetval, e.g. #IR_SUCCESS
1910  */
pager_function_dispatcher(struct MuttWindow * win_pager,int op)1911 int pager_function_dispatcher(struct MuttWindow *win_pager, int op)
1912 {
1913   if (!win_pager)
1914   {
1915     mutt_error(_(Not_available_in_this_menu));
1916     return IR_ERROR;
1917   }
1918 
1919   struct PagerPrivateData *priv = win_pager->parent->wdata;
1920   if (!priv)
1921     return IR_ERROR;
1922 
1923   struct MuttWindow *dlg = dialog_find(win_pager);
1924   if (!dlg || !dlg->wdata)
1925     return IR_ERROR;
1926 
1927   int rc = IR_UNKNOWN;
1928   for (size_t i = 0; PagerFunctions[i].op != OP_NULL; i++)
1929   {
1930     const struct PagerFunction *fn = &PagerFunctions[i];
1931     if (fn->op == op)
1932     {
1933       struct IndexSharedData *shared = dlg->wdata;
1934       rc = fn->function(shared, priv, op);
1935       break;
1936     }
1937   }
1938 
1939   return rc;
1940 }
1941