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