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