1 /**
2  * @file
3  * Index Dialog
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2010,2012-2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2020 R Primus <rprimus@gmail.com>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page index_dialog Index Dialog
26  *
27  * The Index Dialog is the main screen within NeoMutt.  It contains @ref
28  * index_index (a list of emails), @ref pager_dialog (a view of an email) and
29  * @ref sidebar_window (a list of mailboxes).
30  *
31  * ## Windows
32  *
33  * | Name         | Type         | See Also          |
34  * | :----------- | :----------- | :---------------- |
35  * | Index Dialog | WT_DLG_INDEX | mutt_index_menu() |
36  *
37  * **Parent**
38  * - @ref gui_dialog
39  *
40  * **Children**
41  * - See: @ref index_ipanel
42  * - See: @ref pager_ppanel
43  * - See: @ref sidebar_window
44  *
45  * ## Data
46  * - #IndexSharedData
47  *
48  * ## Events
49  *
50  * None.
51  *
52  * Some other events are handled by the dialog's children.
53  */
54 
55 #include "config.h"
56 #include <assert.h>
57 #include <stdbool.h>
58 #include <stdio.h>
59 #include "private.h"
60 #include "mutt/lib.h"
61 #include "config/lib.h"
62 #include "email/lib.h"
63 #include "core/lib.h"
64 #include "conn/lib.h"
65 #include "gui/lib.h"
66 #include "lib.h"
67 #include "color/lib.h"
68 #include "menu/lib.h"
69 #include "pager/lib.h"
70 #include "pattern/lib.h"
71 #include "context.h"
72 #include "format_flags.h"
73 #include "functions.h"
74 #include "hdrline.h"
75 #include "hook.h"
76 #include "keymap.h"
77 #include "mutt_globals.h"
78 #include "mutt_logging.h"
79 #include "mutt_mailbox.h"
80 #include "mutt_thread.h"
81 #include "mx.h"
82 #include "opcodes.h"
83 #include "options.h"
84 #include "private_data.h"
85 #include "protos.h"
86 #include "shared_data.h"
87 #include "sort.h"
88 #include "status.h"
89 #ifdef USE_NOTMUCH
90 #include "notmuch/lib.h"
91 #endif
92 #ifdef USE_NNTP
93 #include "nntp/lib.h"
94 #include "nntp/adata.h" // IWYU pragma: keep
95 #include "nntp/mdata.h" // IWYU pragma: keep
96 #endif
97 #ifdef USE_INOTIFY
98 #include "monitor.h"
99 #endif
100 
101 /// Help Bar for the Index dialog
102 static const struct Mapping IndexHelp[] = {
103   // clang-format off
104   { N_("Quit"),  OP_QUIT },
105   { N_("Del"),   OP_DELETE },
106   { N_("Undel"), OP_UNDELETE },
107   { N_("Save"),  OP_SAVE },
108   { N_("Mail"),  OP_MAIL },
109   { N_("Reply"), OP_REPLY },
110   { N_("Group"), OP_GROUP_REPLY },
111   { N_("Help"),  OP_HELP },
112   { NULL, 0 },
113   // clang-format on
114 };
115 
116 #ifdef USE_NNTP
117 /// Help Bar for the News Index dialog
118 const struct Mapping IndexNewsHelp[] = {
119   // clang-format off
120   { N_("Quit"),     OP_QUIT },
121   { N_("Del"),      OP_DELETE },
122   { N_("Undel"),    OP_UNDELETE },
123   { N_("Save"),     OP_SAVE },
124   { N_("Post"),     OP_POST },
125   { N_("Followup"), OP_FOLLOWUP },
126   { N_("Catchup"),  OP_CATCHUP },
127   { N_("Help"),     OP_HELP },
128   { NULL, 0 },
129   // clang-format on
130 };
131 #endif
132 
133 /**
134  * check_acl - Check the ACLs for a function
135  * @param m   Mailbox
136  * @param acl ACL, see #AclFlags
137  * @param msg Error message for failure
138  * @retval true The function is permitted
139  */
check_acl(struct Mailbox * m,AclFlags acl,const char * msg)140 bool check_acl(struct Mailbox *m, AclFlags acl, const char *msg)
141 {
142   if (!m)
143     return false;
144 
145   if (!(m->rights & acl))
146   {
147     /* L10N: %s is one of the CHECK_ACL entries below. */
148     mutt_error(_("%s: Operation not permitted by ACL"), msg);
149     return false;
150   }
151 
152   return true;
153 }
154 
155 /**
156  * collapse_all - Collapse/uncollapse all threads
157  * @param ctx    Context
158  * @param menu   current menu
159  * @param toggle toggle collapsed state
160  *
161  * This function is called by the OP_MAIN_COLLAPSE_ALL command and on folder
162  * enter if the `$collapse_all` option is set. In the first case, the @a toggle
163  * parameter is 1 to actually toggle collapsed/uncollapsed state on all
164  * threads. In the second case, the @a toggle parameter is 0, actually turning
165  * this function into a one-way collapse.
166  */
collapse_all(struct Context * ctx,struct Menu * menu,int toggle)167 void collapse_all(struct Context *ctx, struct Menu *menu, int toggle)
168 {
169   if (!ctx || !ctx->mailbox || (ctx->mailbox->msg_count == 0) || !menu)
170     return;
171 
172   struct Email *e_cur = mutt_get_virt_email(ctx->mailbox, menu_get_index(menu));
173   if (!e_cur)
174     return;
175 
176   int final;
177 
178   /* Figure out what the current message would be after folding / unfolding,
179    * so that we can restore the cursor in a sane way afterwards. */
180   if (e_cur->collapsed && toggle)
181     final = mutt_uncollapse_thread(e_cur);
182   else if (mutt_thread_can_collapse(e_cur))
183     final = mutt_collapse_thread(e_cur);
184   else
185     final = e_cur->vnum;
186 
187   if (final == -1)
188     return;
189 
190   struct Email *base = mutt_get_virt_email(ctx->mailbox, final);
191   if (!base)
192     return;
193 
194   /* Iterate all threads, perform collapse/uncollapse as needed */
195   ctx->collapsed = toggle ? !ctx->collapsed : true;
196   mutt_thread_collapse(ctx->threads, ctx->collapsed);
197 
198   /* Restore the cursor */
199   mutt_set_vnum(ctx->mailbox);
200   menu->max = ctx->mailbox->vcount;
201   for (int i = 0; i < ctx->mailbox->vcount; i++)
202   {
203     struct Email *e = mutt_get_virt_email(ctx->mailbox, i);
204     if (!e)
205       break;
206     if (e->index == base->index)
207     {
208       menu_set_index(menu, i);
209       break;
210     }
211   }
212 
213   menu_queue_redraw(menu, MENU_REDRAW_INDEX);
214 }
215 
216 /**
217  * ci_next_undeleted - Find the next undeleted email
218  * @param m     Mailbox
219  * @param msgno Message number to start at
220  * @retval >=0 Message number of next undeleted email
221  * @retval  -1 No more undeleted messages
222  */
ci_next_undeleted(struct Mailbox * m,int msgno)223 int ci_next_undeleted(struct Mailbox *m, int msgno)
224 {
225   if (!m)
226     return -1;
227 
228   for (int i = msgno + 1; i < m->vcount; i++)
229   {
230     struct Email *e = mutt_get_virt_email(m, i);
231     if (!e)
232       continue;
233     if (!e->deleted)
234       return i;
235   }
236   return -1;
237 }
238 
239 /**
240  * ci_previous_undeleted - Find the previous undeleted email
241  * @param m     Mailbox
242  * @param msgno Message number to start at
243  * @retval >=0 Message number of next undeleted email
244  * @retval  -1 No more undeleted messages
245  */
ci_previous_undeleted(struct Mailbox * m,int msgno)246 int ci_previous_undeleted(struct Mailbox *m, int msgno)
247 {
248   if (!m)
249     return -1;
250 
251   for (int i = msgno - 1; i >= 0; i--)
252   {
253     struct Email *e = mutt_get_virt_email(m, i);
254     if (!e)
255       continue;
256     if (!e->deleted)
257       return i;
258   }
259   return -1;
260 }
261 
262 /**
263  * ci_first_message - Get index of first new message
264  * @param m Mailbox
265  * @retval num Index of first new message
266  *
267  * Return the index of the first new message, or failing that, the first
268  * unread message.
269  */
ci_first_message(struct Mailbox * m)270 int ci_first_message(struct Mailbox *m)
271 {
272   if (!m || (m->msg_count == 0))
273     return 0;
274 
275   int old = -1;
276   for (int i = 0; i < m->vcount; i++)
277   {
278     struct Email *e = mutt_get_virt_email(m, i);
279     if (!e)
280       continue;
281     if (!e->read && !e->deleted)
282     {
283       if (!e->old)
284         return i;
285       if (old == -1)
286         old = i;
287     }
288   }
289   if (old != -1)
290     return old;
291 
292   /* If `$use_threads` is not threaded and `$sort` is reverse, the latest
293    * message is first.  Otherwise, the latest message is first if exactly
294    * one of `$use_threads` and `$sort` are reverse.
295    */
296   short c_sort = cs_subset_sort(m->sub, "sort");
297   if ((c_sort & SORT_MASK) == SORT_THREADS)
298     c_sort = cs_subset_sort(m->sub, "sort_aux");
299   bool reverse = false;
300   switch (mutt_thread_style())
301   {
302     case UT_FLAT:
303       reverse = c_sort & SORT_REVERSE;
304       break;
305     case UT_THREADS:
306       reverse = c_sort & SORT_REVERSE;
307       break;
308     case UT_REVERSE:
309       reverse = !(c_sort & SORT_REVERSE);
310       break;
311     default:
312       assert(false);
313   }
314 
315   if (reverse || (m->vcount == 0))
316     return 0;
317 
318   return m->vcount - 1;
319 }
320 
321 /**
322  * resort_index - Resort the index
323  * @param ctx  Context
324  * @param menu Current Menu
325  */
resort_index(struct Context * ctx,struct Menu * menu)326 void resort_index(struct Context *ctx, struct Menu *menu)
327 {
328   if (!ctx || !ctx->mailbox || !menu)
329     return;
330 
331   struct Mailbox *m = ctx->mailbox;
332   const int old_index = menu_get_index(menu);
333   struct Email *e_cur = mutt_get_virt_email(m, old_index);
334 
335   int new_index = -1;
336   mutt_sort_headers(m, ctx->threads, false, &ctx->vsize);
337   /* Restore the current message */
338 
339   for (int i = 0; i < m->vcount; i++)
340   {
341     struct Email *e = mutt_get_virt_email(m, i);
342     if (!e)
343       continue;
344     if (e == e_cur)
345     {
346       new_index = i;
347       break;
348     }
349   }
350 
351   if (mutt_using_threads() && (old_index < 0))
352     new_index = mutt_parent_message(e_cur, false);
353 
354   if (old_index < 0)
355     new_index = ci_first_message(m);
356 
357   menu_set_index(menu, new_index);
358   menu_queue_redraw(menu, MENU_REDRAW_INDEX);
359 }
360 
361 /**
362  * update_index_threaded - Update the index (if threaded)
363  * @param ctx      Mailbox
364  * @param check    Flags, e.g. #MX_STATUS_REOPENED
365  * @param oldcount How many items are currently in the index
366  */
update_index_threaded(struct Context * ctx,enum MxStatus check,int oldcount)367 static void update_index_threaded(struct Context *ctx, enum MxStatus check, int oldcount)
368 {
369   struct Email **save_new = NULL;
370   const bool lmt = ctx_has_limit(ctx);
371 
372   struct Mailbox *m = ctx->mailbox;
373   int num_new = MAX(0, m->msg_count - oldcount);
374 
375   const bool c_uncollapse_new = cs_subset_bool(m->sub, "uncollapse_new");
376   /* save the list of new messages */
377   if ((check != MX_STATUS_REOPENED) && (oldcount > 0) &&
378       (lmt || c_uncollapse_new) && (num_new > 0))
379   {
380     save_new = mutt_mem_malloc(num_new * sizeof(struct Email *));
381     for (int i = oldcount; i < m->msg_count; i++)
382       save_new[i - oldcount] = m->emails[i];
383   }
384 
385   /* Sort first to thread the new messages, because some patterns
386    * require the threading information.
387    *
388    * If the mailbox was reopened, need to rethread from scratch. */
389   mutt_sort_headers(m, ctx->threads, (check == MX_STATUS_REOPENED), &ctx->vsize);
390 
391   if (lmt)
392   {
393     /* Because threading changes the order in m->emails, we don't
394      * know which emails are new. Hence, we need to re-apply the limit to the
395      * whole set.
396      */
397     for (int i = 0; i < m->msg_count; i++)
398     {
399       struct Email *e = m->emails[i];
400       if ((e->vnum != -1) || mutt_pattern_exec(SLIST_FIRST(ctx->limit_pattern),
401                                                MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
402       {
403         /* vnum will get properly set by mutt_set_vnum(), which
404          * is called by mutt_sort_headers() just below. */
405         e->vnum = 1;
406         e->visible = true;
407       }
408       else
409       {
410         e->vnum = -1;
411         e->visible = false;
412       }
413     }
414     /* Need a second sort to set virtual numbers and redraw the tree */
415     mutt_sort_headers(m, ctx->threads, false, &ctx->vsize);
416   }
417 
418   /* uncollapse threads with new mail */
419   if (c_uncollapse_new)
420   {
421     if (check == MX_STATUS_REOPENED)
422     {
423       ctx->collapsed = false;
424       mutt_thread_collapse(ctx->threads, ctx->collapsed);
425       mutt_set_vnum(m);
426     }
427     else if (oldcount > 0)
428     {
429       for (int j = 0; j < num_new; j++)
430       {
431         if (save_new[j]->visible)
432         {
433           mutt_uncollapse_thread(save_new[j]);
434         }
435       }
436       mutt_set_vnum(m);
437     }
438   }
439 
440   FREE(&save_new);
441 }
442 
443 /**
444  * update_index_unthreaded - Update the index (if unthreaded)
445  * @param ctx      Mailbox
446  * @param check    Flags, e.g. #MX_STATUS_REOPENED
447  */
update_index_unthreaded(struct Context * ctx,enum MxStatus check)448 static void update_index_unthreaded(struct Context *ctx, enum MxStatus check)
449 {
450   /* We are in a limited view. Check if the new message(s) satisfy
451    * the limit criteria. If they do, set their virtual msgno so that
452    * they will be visible in the limited view */
453   if (ctx_has_limit(ctx))
454   {
455     int padding = mx_msg_padding_size(ctx->mailbox);
456     ctx->mailbox->vcount = ctx->vsize = 0;
457     for (int i = 0; i < ctx->mailbox->msg_count; i++)
458     {
459       struct Email *e = ctx->mailbox->emails[i];
460       if (!e)
461         break;
462       if (mutt_pattern_exec(SLIST_FIRST(ctx->limit_pattern),
463                             MUTT_MATCH_FULL_ADDRESS, ctx->mailbox, e, NULL))
464       {
465         assert(ctx->mailbox->vcount < ctx->mailbox->msg_count);
466         e->vnum = ctx->mailbox->vcount;
467         ctx->mailbox->v2r[ctx->mailbox->vcount] = i;
468         e->visible = true;
469         ctx->mailbox->vcount++;
470         struct Body *b = e->body;
471         ctx->vsize += b->length + b->offset - b->hdr_offset + padding;
472       }
473       else
474       {
475         e->visible = false;
476       }
477     }
478   }
479 
480   /* if the mailbox was reopened, need to rethread from scratch */
481   mutt_sort_headers(ctx->mailbox, ctx->threads, (check == MX_STATUS_REOPENED), &ctx->vsize);
482 }
483 
484 /**
485  * update_index - Update the index
486  * @param menu     Current Menu
487  * @param ctx      Mailbox
488  * @param check    Flags, e.g. #MX_STATUS_REOPENED
489  * @param oldcount How many items are currently in the index
490  * @param shared   Shared Index data
491  */
update_index(struct Menu * menu,struct Context * ctx,enum MxStatus check,int oldcount,const struct IndexSharedData * shared)492 void update_index(struct Menu *menu, struct Context *ctx, enum MxStatus check,
493                   int oldcount, const struct IndexSharedData *shared)
494 {
495   if (!menu || !ctx)
496     return;
497 
498   struct Mailbox *m = ctx->mailbox;
499   if (mutt_using_threads())
500     update_index_threaded(ctx, check, oldcount);
501   else
502     update_index_unthreaded(ctx, check);
503 
504   const int old_index = menu_get_index(menu);
505   int index = -1;
506   if (oldcount)
507   {
508     /* restore the current message to the message it was pointing to */
509     for (int i = 0; i < m->vcount; i++)
510     {
511       struct Email *e = mutt_get_virt_email(m, i);
512       if (!e)
513         continue;
514       if (index_shared_data_is_cur_email(shared, e))
515       {
516         index = i;
517         break;
518       }
519     }
520   }
521 
522   if (index < 0)
523   {
524     index = (old_index < m->vcount) ? old_index : ci_first_message(m);
525   }
526   menu_set_index(menu, index);
527 }
528 
529 /**
530  * mutt_update_index - Update the index
531  * @param menu      Current Menu
532  * @param ctx       Mailbox
533  * @param check     Flags, e.g. #MX_STATUS_REOPENED
534  * @param oldcount  How many items are currently in the index
535  * @param shared    Shared Index data
536  */
mutt_update_index(struct Menu * menu,struct Context * ctx,enum MxStatus check,int oldcount,struct IndexSharedData * shared)537 void mutt_update_index(struct Menu *menu, struct Context *ctx, enum MxStatus check,
538                        int oldcount, struct IndexSharedData *shared)
539 {
540   update_index(menu, ctx, check, oldcount, shared);
541 }
542 
543 /**
544  * index_mailbox_observer - Notification that a Mailbox has changed - Implements ::observer_t - @ingroup observer_api
545  *
546  * If a Mailbox is closed, then set a pointer to NULL.
547  */
index_mailbox_observer(struct NotifyCallback * nc)548 static int index_mailbox_observer(struct NotifyCallback *nc)
549 {
550   if ((nc->event_type != NT_MAILBOX) || !nc->global_data)
551     return -1;
552 
553   if (nc->event_subtype != NT_MAILBOX_DELETE)
554     return 0;
555 
556   struct Mailbox **ptr = nc->global_data;
557   if (!ptr || !*ptr)
558     return 0;
559 
560   *ptr = NULL;
561   mutt_debug(LL_DEBUG5, "mailbox done\n");
562   return 0;
563 }
564 
565 /**
566  * change_folder_mailbox - Change to a different Mailbox by pointer
567  * @param menu      Current Menu
568  * @param m         Mailbox
569  * @param oldcount  How many items are currently in the index
570  * @param shared    Shared Index data
571  * @param read_only Open Mailbox in read-only mode
572  */
change_folder_mailbox(struct Menu * menu,struct Mailbox * m,int * oldcount,struct IndexSharedData * shared,bool read_only)573 void change_folder_mailbox(struct Menu *menu, struct Mailbox *m, int *oldcount,
574                            struct IndexSharedData *shared, bool read_only)
575 {
576   if (!m)
577     return;
578 
579   /* keepalive failure in mutt_enter_fname may kill connection. */
580   if (shared->mailbox && (mutt_buffer_is_empty(&shared->mailbox->pathbuf)))
581   {
582     ctx_free(&shared->ctx);
583     if (shared->mailbox->flags == MB_HIDDEN)
584       mailbox_free(&shared->mailbox);
585   }
586 
587   if (shared->mailbox)
588   {
589     char *new_last_folder = NULL;
590 #ifdef USE_INOTIFY
591     int monitor_remove_rc = mutt_monitor_remove(NULL);
592 #endif
593 #ifdef USE_COMP_MBOX
594     if (shared->mailbox->compress_info && (shared->mailbox->realpath[0] != '\0'))
595       new_last_folder = mutt_str_dup(shared->mailbox->realpath);
596     else
597 #endif
598       new_last_folder = mutt_str_dup(mailbox_path(shared->mailbox));
599     *oldcount = shared->mailbox->msg_count;
600 
601     const enum MxStatus check = mx_mbox_close(shared->mailbox);
602     if (check == MX_STATUS_OK)
603     {
604       ctx_free(&shared->ctx);
605       if ((shared->mailbox != m) && (shared->mailbox->flags == MB_HIDDEN))
606         mailbox_free(&shared->mailbox);
607     }
608     else
609     {
610 #ifdef USE_INOTIFY
611       if (monitor_remove_rc == 0)
612         mutt_monitor_add(NULL);
613 #endif
614       if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
615         update_index(menu, shared->ctx, check, *oldcount, shared);
616 
617       FREE(&new_last_folder);
618       OptSearchInvalid = true;
619       menu_queue_redraw(menu, MENU_REDRAW_INDEX);
620       return;
621     }
622     FREE(&LastFolder);
623     LastFolder = new_last_folder;
624   }
625   mutt_str_replace(&CurrentFolder, mailbox_path(m));
626 
627   /* If the `folder-hook` were to call `unmailboxes`, then the Mailbox (`m`)
628    * could be deleted, leaving `m` dangling. */
629   // TODO: Refactor this function to avoid the need for an observer
630   notify_observer_add(m->notify, NT_MAILBOX, index_mailbox_observer, &m);
631   char *dup_path = mutt_str_dup(mailbox_path(m));
632   char *dup_name = mutt_str_dup(m->name);
633 
634   mutt_folder_hook(dup_path, dup_name);
635   if (m)
636   {
637     /* `m` is still valid, but we won't need the observer again before the end
638      * of the function. */
639     notify_observer_remove(m->notify, index_mailbox_observer, &m);
640   }
641   else
642   {
643     // Recreate the Mailbox as the folder-hook might have invoked `mailboxes`
644     // and/or `unmailboxes`.
645     m = mx_path_resolve(dup_path);
646   }
647 
648   FREE(&dup_path);
649   FREE(&dup_name);
650 
651   if (!m)
652     return;
653 
654   const OpenMailboxFlags flags = read_only ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS;
655   if (mx_mbox_open(m, flags))
656   {
657     struct Context *ctx = ctx_new(m);
658     index_shared_data_set_context(shared, ctx);
659 
660     menu->max = m->msg_count;
661     menu_set_index(menu, ci_first_message(shared->mailbox));
662 #ifdef USE_INOTIFY
663     mutt_monitor_add(NULL);
664 #endif
665   }
666   else
667   {
668     index_shared_data_set_context(shared, NULL);
669     menu_set_index(menu, 0);
670   }
671 
672   const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all");
673   if (mutt_using_threads() && c_collapse_all)
674     collapse_all(shared->ctx, menu, 0);
675 
676   struct MuttWindow *dlg = dialog_find(menu->win);
677   struct EventMailbox ev_m = { shared->mailbox };
678   mutt_debug(LL_NOTIFY, "NT_MAILBOX_SWITCH: %p\n", shared->mailbox);
679   notify_send(dlg->notify, NT_MAILBOX, NT_MAILBOX_SWITCH, &ev_m);
680 
681   mutt_clear_error();
682   /* force the mailbox check after we have changed the folder */
683   mutt_mailbox_check(ev_m.mailbox, MUTT_MAILBOX_CHECK_FORCE);
684   menu_queue_redraw(menu, MENU_REDRAW_FULL);
685   OptSearchInvalid = true;
686 }
687 
688 #ifdef USE_NOTMUCH
689 /**
690  * change_folder_notmuch - Change to a different Notmuch Mailbox by string
691  * @param menu      Current Menu
692  * @param buf       Folder to change to
693  * @param buflen    Length of buffer
694  * @param oldcount  How many items are currently in the index
695  * @param shared    Shared Index data
696  * @param read_only Open Mailbox in read-only mode
697  */
change_folder_notmuch(struct Menu * menu,char * buf,int buflen,int * oldcount,struct IndexSharedData * shared,bool read_only)698 struct Mailbox *change_folder_notmuch(struct Menu *menu, char *buf, int buflen, int *oldcount,
699                                       struct IndexSharedData *shared, bool read_only)
700 {
701   if (!nm_url_from_query(NULL, buf, buflen))
702   {
703     mutt_message(_("Failed to create query, aborting"));
704     return NULL;
705   }
706 
707   struct Mailbox *m_query = mx_path_resolve(buf);
708   change_folder_mailbox(menu, m_query, oldcount, shared, read_only);
709   return m_query;
710 }
711 #endif
712 
713 /**
714  * change_folder_string - Change to a different Mailbox by string
715  * @param menu         Current Menu
716  * @param buf          Folder to change to
717  * @param buflen       Length of buffer
718  * @param oldcount     How many items are currently in the index
719  * @param shared       Shared Index data
720  * @param pager_return Return to the pager afterwards
721  * @param read_only    Open Mailbox in read-only mode
722  */
change_folder_string(struct Menu * menu,char * buf,size_t buflen,int * oldcount,struct IndexSharedData * shared,bool * pager_return,bool read_only)723 void change_folder_string(struct Menu *menu, char *buf, size_t buflen, int *oldcount,
724                           struct IndexSharedData *shared, bool *pager_return, bool read_only)
725 {
726 #ifdef USE_NNTP
727   if (OptNews)
728   {
729     OptNews = false;
730     nntp_expand_path(buf, buflen, &CurrentNewsSrv->conn->account);
731   }
732   else
733 #endif
734   {
735     const char *const c_folder = cs_subset_string(shared->sub, "folder");
736     mx_path_canon(buf, buflen, c_folder, NULL);
737   }
738 
739   enum MailboxType type = mx_path_probe(buf);
740   if ((type == MUTT_MAILBOX_ERROR) || (type == MUTT_UNKNOWN))
741   {
742     // Look for a Mailbox by its description, before failing
743     struct Mailbox *m = mailbox_find_name(buf);
744     if (m)
745     {
746       change_folder_mailbox(menu, m, oldcount, shared, read_only);
747       *pager_return = false;
748     }
749     else
750       mutt_error(_("%s is not a mailbox"), buf);
751     return;
752   }
753 
754   /* past this point, we don't return to the pager on error */
755   *pager_return = false;
756 
757   struct Mailbox *m = mx_path_resolve(buf);
758   change_folder_mailbox(menu, m, oldcount, shared, read_only);
759 }
760 
761 /**
762  * index_make_entry - Format a menu item for the index list - Implements Menu::make_entry() - @ingroup menu_make_entry
763  */
index_make_entry(struct Menu * menu,char * buf,size_t buflen,int line)764 void index_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
765 {
766   buf[0] = '\0';
767 
768   if (!menu || !menu->mdata)
769     return;
770 
771   struct IndexPrivateData *priv = menu->mdata;
772   struct IndexSharedData *shared = priv->shared;
773   struct Mailbox *m = shared->mailbox;
774 
775   if (!m || (line < 0) || (line >= m->email_max))
776     return;
777 
778   struct Email *e = mutt_get_virt_email(m, line);
779   if (!e)
780     return;
781 
782   MuttFormatFlags flags = MUTT_FORMAT_ARROWCURSOR | MUTT_FORMAT_INDEX;
783   struct MuttThread *tmp = NULL;
784 
785   const enum UseThreads c_threads = mutt_thread_style();
786   if ((c_threads > UT_FLAT) && e->tree)
787   {
788     flags |= MUTT_FORMAT_TREE; /* display the thread tree */
789     if (e->display_subject)
790       flags |= MUTT_FORMAT_FORCESUBJ;
791     else
792     {
793       const bool reverse = c_threads == UT_REVERSE;
794       int edgemsgno;
795       if (reverse)
796       {
797         if (menu->top + menu->pagelen > menu->max)
798           edgemsgno = m->v2r[menu->max - 1];
799         else
800           edgemsgno = m->v2r[menu->top + menu->pagelen - 1];
801       }
802       else
803         edgemsgno = m->v2r[menu->top];
804 
805       for (tmp = e->thread->parent; tmp; tmp = tmp->parent)
806       {
807         if (!tmp->message)
808           continue;
809 
810         /* if no ancestor is visible on current screen, provisionally force
811          * subject... */
812         if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno))
813         {
814           flags |= MUTT_FORMAT_FORCESUBJ;
815           break;
816         }
817         else if (tmp->message->vnum >= 0)
818           break;
819       }
820       if (flags & MUTT_FORMAT_FORCESUBJ)
821       {
822         for (tmp = e->thread->prev; tmp; tmp = tmp->prev)
823         {
824           if (!tmp->message)
825             continue;
826 
827           /* ...but if a previous sibling is available, don't force it */
828           if (reverse ? (tmp->message->msgno > edgemsgno) : (tmp->message->msgno < edgemsgno))
829             break;
830           else if (tmp->message->vnum >= 0)
831           {
832             flags &= ~MUTT_FORMAT_FORCESUBJ;
833             break;
834           }
835         }
836       }
837     }
838   }
839 
840   const char *const c_index_format =
841       cs_subset_string(shared->sub, "index_format");
842   mutt_make_string(buf, buflen, menu->win->state.cols, NONULL(c_index_format),
843                    m, shared->ctx->msg_in_pager, e, flags, NULL);
844 }
845 
846 /**
847  * index_color - Calculate the colour for a line of the index - Implements Menu::color() - @ingroup menu_color
848  */
index_color(struct Menu * menu,int line)849 int index_color(struct Menu *menu, int line)
850 {
851   struct IndexPrivateData *priv = menu->mdata;
852   struct IndexSharedData *shared = priv->shared;
853   struct Mailbox *m = shared->mailbox;
854   if (!m || (line < 0))
855     return 0;
856 
857   struct Email *e = mutt_get_virt_email(m, line);
858   if (!e)
859     return 0;
860 
861   if (e->pair)
862     return e->pair;
863 
864   mutt_set_header_color(m, e);
865   return e->pair;
866 }
867 
868 /**
869  * mutt_draw_statusline - Draw a highlighted status bar
870  * @param win    Window
871  * @param cols   Maximum number of screen columns
872  * @param buf    Message to be displayed
873  * @param buflen Length of the buffer
874  *
875  * Users configure the highlighting of the status bar, e.g.
876  *     color status red default "[0-9][0-9]:[0-9][0-9]"
877  *
878  * Where regexes overlap, the one nearest the start will be used.
879  * If two regexes start at the same place, the longer match will be used.
880  */
mutt_draw_statusline(struct MuttWindow * win,int cols,const char * buf,size_t buflen)881 void mutt_draw_statusline(struct MuttWindow *win, int cols, const char *buf, size_t buflen)
882 {
883   if (!buf || !stdscr)
884     return;
885 
886   size_t i = 0;
887   size_t offset = 0;
888   bool found = false;
889   size_t chunks = 0;
890   size_t len = 0;
891 
892   /**
893    * struct StatusSyntax - Colours of the status bar
894    */
895   struct StatusSyntax
896   {
897     int color; ///< Colour pair
898     int first; ///< First character of that colour
899     int last;  ///< Last character of that colour
900   } *syntax = NULL;
901 
902   do
903   {
904     struct RegexColor *cl = NULL;
905     found = false;
906 
907     if (!buf[offset])
908       break;
909 
910     /* loop through each "color status regex" */
911     STAILQ_FOREACH(cl, regex_colors_get_list(MT_COLOR_STATUS), entries)
912     {
913       regmatch_t pmatch[cl->match + 1];
914 
915       if (regexec(&cl->regex, buf + offset, cl->match + 1, pmatch, 0) != 0)
916         continue; /* regex doesn't match the status bar */
917 
918       int first = pmatch[cl->match].rm_so + offset;
919       int last = pmatch[cl->match].rm_eo + offset;
920 
921       if (first == last)
922         continue; /* ignore an empty regex */
923 
924       if (!found)
925       {
926         chunks++;
927         mutt_mem_realloc(&syntax, chunks * sizeof(struct StatusSyntax));
928       }
929 
930       i = chunks - 1;
931       if (!found || (first < syntax[i].first) ||
932           ((first == syntax[i].first) && (last > syntax[i].last)))
933       {
934         syntax[i].color = cl->pair;
935         syntax[i].first = first;
936         syntax[i].last = last;
937       }
938       found = true;
939     }
940 
941     if (syntax)
942     {
943       offset = syntax[i].last;
944     }
945   } while (found);
946 
947   /* Only 'len' bytes will fit into 'cols' screen columns */
948   len = mutt_wstr_trunc(buf, buflen, cols, NULL);
949 
950   offset = 0;
951 
952   if ((chunks > 0) && (syntax[0].first > 0))
953   {
954     /* Text before the first highlight */
955     mutt_window_addnstr(win, buf, MIN(len, syntax[0].first));
956     mutt_curses_set_color_by_id(MT_COLOR_STATUS);
957     if (len <= syntax[0].first)
958       goto dsl_finish; /* no more room */
959 
960     offset = syntax[0].first;
961   }
962 
963   for (i = 0; i < chunks; i++)
964   {
965     /* Highlighted text */
966     mutt_curses_set_attr(syntax[i].color);
967     mutt_window_addnstr(win, buf + offset, MIN(len, syntax[i].last) - offset);
968     if (len <= syntax[i].last)
969       goto dsl_finish; /* no more room */
970 
971     size_t next;
972     if ((i + 1) == chunks)
973     {
974       next = len;
975     }
976     else
977     {
978       next = MIN(len, syntax[i + 1].first);
979     }
980 
981     mutt_curses_set_color_by_id(MT_COLOR_STATUS);
982     offset = syntax[i].last;
983     mutt_window_addnstr(win, buf + offset, next - offset);
984 
985     offset = next;
986     if (offset >= len)
987       goto dsl_finish; /* no more room */
988   }
989 
990   mutt_curses_set_color_by_id(MT_COLOR_STATUS);
991   if (offset < len)
992   {
993     /* Text after the last highlight */
994     mutt_window_addnstr(win, buf + offset, len - offset);
995   }
996 
997   int width = mutt_strwidth(buf);
998   if (width < cols)
999   {
1000     /* Pad the rest of the line with whitespace */
1001     mutt_paddstr(win, cols - width, "");
1002   }
1003 dsl_finish:
1004   FREE(&syntax);
1005 }
1006 
1007 /**
1008  * index_custom_redraw - Redraw the index - Implements Menu::custom_redraw() - @ingroup menu_custom_redraw
1009  */
index_custom_redraw(struct Menu * menu)1010 static void index_custom_redraw(struct Menu *menu)
1011 {
1012   if (menu->redraw & MENU_REDRAW_FULL)
1013     menu_redraw_full(menu);
1014 
1015   struct IndexPrivateData *priv = menu->mdata;
1016   struct IndexSharedData *shared = priv->shared;
1017   struct Mailbox *m = shared->mailbox;
1018   const int index = menu_get_index(menu);
1019   if (m && m->emails && (index < m->vcount))
1020   {
1021     if (menu->redraw & MENU_REDRAW_INDEX)
1022     {
1023       menu_redraw_index(menu);
1024     }
1025     else if (menu->redraw & MENU_REDRAW_MOTION)
1026       menu_redraw_motion(menu);
1027     else if (menu->redraw & MENU_REDRAW_CURRENT)
1028       menu_redraw_current(menu);
1029   }
1030 
1031   menu->redraw = MENU_REDRAW_NO_FLAGS;
1032   mutt_debug(LL_DEBUG5, "repaint done\n");
1033 }
1034 
1035 /**
1036  * mutt_index_menu - Display a list of emails
1037  * @param dlg Dialog containing Windows to draw on
1038  * @param m_init Initial mailbox
1039  * @retval Mailbox open in the index
1040  *
1041  * This function handles the message index window as well as commands returned
1042  * from the pager (MENU_PAGER).
1043  */
mutt_index_menu(struct MuttWindow * dlg,struct Mailbox * m_init)1044 struct Mailbox *mutt_index_menu(struct MuttWindow *dlg, struct Mailbox *m_init)
1045 {
1046   struct Context *ctx_old = Context;
1047   struct IndexSharedData *shared = dlg->wdata;
1048   index_shared_data_set_context(shared, ctx_new(m_init));
1049 
1050   struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX);
1051   struct MuttWindow *panel_pager = window_find_child(dlg, WT_PAGER);
1052 
1053   struct IndexPrivateData *priv = panel_index->wdata;
1054   priv->attach_msg = OptAttachMsg;
1055   priv->win_index = window_find_child(panel_index, WT_MENU);
1056   priv->win_ibar = window_find_child(panel_index, WT_STATUS_BAR);
1057   priv->win_pager = window_find_child(panel_pager, WT_CUSTOM);
1058   priv->win_pbar = window_find_child(panel_pager, WT_STATUS_BAR);
1059 
1060   int op = OP_NULL;
1061 
1062 #ifdef USE_NNTP
1063   if (shared->mailbox && (shared->mailbox->type == MUTT_NNTP))
1064     dlg->help_data = IndexNewsHelp;
1065   else
1066 #endif
1067     dlg->help_data = IndexHelp;
1068   dlg->help_menu = MENU_MAIN;
1069 
1070   priv->menu = priv->win_index->wdata;
1071   priv->menu->make_entry = index_make_entry;
1072   priv->menu->color = index_color;
1073   priv->menu->custom_redraw = index_custom_redraw;
1074   priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0;
1075   menu_set_index(priv->menu, ci_first_message(shared->mailbox));
1076   mutt_window_reflow(NULL);
1077 
1078   if (!priv->attach_msg)
1079   {
1080     /* force the mailbox check after we enter the folder */
1081     mutt_mailbox_check(shared->mailbox, MUTT_MAILBOX_CHECK_FORCE);
1082   }
1083 #ifdef USE_INOTIFY
1084   mutt_monitor_add(NULL);
1085 #endif
1086 
1087   {
1088     const bool c_collapse_all = cs_subset_bool(shared->sub, "collapse_all");
1089     if (mutt_using_threads() && c_collapse_all)
1090     {
1091       collapse_all(shared->ctx, priv->menu, 0);
1092       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1093     }
1094   }
1095 
1096   while (true)
1097   {
1098     /* Clear the tag prefix unless we just started it.  Don't clear
1099      * the prefix on a timeout (op==-2), but do clear on an abort (op==-1) */
1100     if (priv->tag && (op != OP_TAG_PREFIX) && (op != OP_TAG_PREFIX_COND) && (op != -2))
1101       priv->tag = false;
1102 
1103     /* check if we need to resort the index because just about
1104      * any 'op' below could do mutt_enter_command(), either here or
1105      * from any new priv->menu launched, and change $sort/$sort_aux */
1106     if (OptNeedResort && shared->mailbox && (shared->mailbox->msg_count != 0) &&
1107         (menu_get_index(priv->menu) >= 0))
1108     {
1109       resort_index(shared->ctx, priv->menu);
1110     }
1111 
1112     priv->menu->max = shared->mailbox ? shared->mailbox->vcount : 0;
1113     priv->oldcount = shared->mailbox ? shared->mailbox->msg_count : 0;
1114 
1115     {
1116       if (OptRedrawTree && shared->mailbox &&
1117           (shared->mailbox->msg_count != 0) && mutt_using_threads())
1118       {
1119         mutt_draw_tree(shared->ctx->threads);
1120         OptRedrawTree = false;
1121       }
1122     }
1123 
1124     if (shared->mailbox)
1125     {
1126       mailbox_gc_run();
1127 
1128       shared->ctx->menu = priv->menu;
1129       /* check for new mail in the mailbox.  If nonzero, then something has
1130        * changed about the file (either we got new mail or the file was
1131        * modified underneath us.) */
1132       enum MxStatus check = mx_mbox_check(shared->mailbox);
1133 
1134       if (check == MX_STATUS_ERROR)
1135       {
1136         if (mutt_buffer_is_empty(&shared->mailbox->pathbuf))
1137         {
1138           /* fatal error occurred */
1139           ctx_free(&shared->ctx);
1140           menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1141         }
1142 
1143         OptSearchInvalid = true;
1144       }
1145       else if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED) ||
1146                (check == MX_STATUS_FLAGS))
1147       {
1148         /* notify the user of new mail */
1149         if (check == MX_STATUS_REOPENED)
1150         {
1151           mutt_error(
1152               _("Mailbox was externally modified.  Flags may be wrong."));
1153         }
1154         else if (check == MX_STATUS_NEW_MAIL)
1155         {
1156           for (size_t i = 0; i < shared->mailbox->msg_count; i++)
1157           {
1158             const struct Email *e = shared->mailbox->emails[i];
1159             if (e && !e->read && !e->old)
1160             {
1161               mutt_message(_("New mail in this mailbox"));
1162               const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new");
1163               if (c_beep_new)
1164                 mutt_beep(true);
1165               const char *const c_new_mail_command =
1166                   cs_subset_string(shared->sub, "new_mail_command");
1167               if (c_new_mail_command)
1168               {
1169                 char cmd[1024];
1170                 menu_status_line(cmd, sizeof(cmd), shared, priv->menu,
1171                                  sizeof(cmd), NONULL(c_new_mail_command));
1172                 if (mutt_system(cmd) != 0)
1173                   mutt_error(_("Error running \"%s\""), cmd);
1174               }
1175               break;
1176             }
1177           }
1178         }
1179         else if (check == MX_STATUS_FLAGS)
1180         {
1181           mutt_message(_("Mailbox was externally modified"));
1182         }
1183 
1184         /* avoid the message being overwritten by mailbox */
1185         priv->do_mailbox_notify = false;
1186 
1187         bool verbose = shared->mailbox->verbose;
1188         shared->mailbox->verbose = false;
1189         update_index(priv->menu, shared->ctx, check, priv->oldcount, shared);
1190         shared->mailbox->verbose = verbose;
1191         priv->menu->max = shared->mailbox->vcount;
1192         menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1193         OptSearchInvalid = true;
1194       }
1195 
1196       index_shared_data_set_email(
1197           shared, mutt_get_virt_email(shared->mailbox, menu_get_index(priv->menu)));
1198     }
1199 
1200     if (!priv->attach_msg)
1201     {
1202       /* check for new mail in the incoming folders */
1203       priv->oldcount = priv->newcount;
1204       priv->newcount = mutt_mailbox_check(shared->mailbox, 0);
1205       if (priv->do_mailbox_notify)
1206       {
1207         if (mutt_mailbox_notify(shared->mailbox))
1208         {
1209           const bool c_beep_new = cs_subset_bool(shared->sub, "beep_new");
1210           if (c_beep_new)
1211             mutt_beep(true);
1212           const char *const c_new_mail_command =
1213               cs_subset_string(shared->sub, "new_mail_command");
1214           if (c_new_mail_command)
1215           {
1216             char cmd[1024];
1217             menu_status_line(cmd, sizeof(cmd), shared, priv->menu, sizeof(cmd),
1218                              NONULL(c_new_mail_command));
1219             if (mutt_system(cmd) != 0)
1220               mutt_error(_("Error running \"%s\""), cmd);
1221           }
1222         }
1223       }
1224       else
1225         priv->do_mailbox_notify = true;
1226     }
1227 
1228     if (op >= 0)
1229       mutt_curses_set_cursor(MUTT_CURSOR_INVISIBLE);
1230 
1231     if (priv->in_pager)
1232     {
1233       mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE); /* fallback from the pager */
1234     }
1235     else
1236     {
1237       index_custom_redraw(priv->menu);
1238       window_redraw(NULL);
1239 
1240       /* give visual indication that the next command is a tag- command */
1241       if (priv->tag)
1242         msgwin_set_text(MT_COLOR_NORMAL, "tag-");
1243 
1244       const bool c_arrow_cursor = cs_subset_bool(shared->sub, "arrow_cursor");
1245       const bool c_braille_friendly =
1246           cs_subset_bool(shared->sub, "braille_friendly");
1247       const int index = menu_get_index(priv->menu);
1248       if (c_arrow_cursor)
1249       {
1250         mutt_window_move(priv->menu->win, 2, index - priv->menu->top);
1251       }
1252       else if (c_braille_friendly)
1253       {
1254         mutt_window_move(priv->menu->win, 0, index - priv->menu->top);
1255       }
1256       else
1257       {
1258         mutt_window_move(priv->menu->win, priv->menu->win->state.cols - 1,
1259                          index - priv->menu->top);
1260       }
1261       mutt_refresh();
1262 
1263       if (SigWinch)
1264       {
1265         SigWinch = false;
1266         mutt_resize_screen();
1267         priv->menu->top = 0; /* so we scroll the right amount */
1268         /* force a real complete redraw.  clrtobot() doesn't seem to be able
1269          * to handle every case without this.  */
1270         clearok(stdscr, true);
1271         msgwin_clear_text();
1272         continue;
1273       }
1274 
1275       window_redraw(NULL);
1276       op = km_dokey(MENU_MAIN);
1277 
1278       /* either user abort or timeout */
1279       if (op < 0)
1280       {
1281         mutt_timeout_hook();
1282         if (priv->tag)
1283           msgwin_clear_text();
1284         continue;
1285       }
1286 
1287       mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", OpStrings[op][0], op);
1288 
1289       mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE);
1290 
1291       /* special handling for the priv->tag-prefix function */
1292       const bool c_auto_tag = cs_subset_bool(shared->sub, "auto_tag");
1293       if ((op == OP_TAG_PREFIX) || (op == OP_TAG_PREFIX_COND))
1294       {
1295         /* A second priv->tag-prefix command aborts */
1296         if (priv->tag)
1297         {
1298           priv->tag = false;
1299           msgwin_clear_text();
1300           continue;
1301         }
1302 
1303         if (!shared->mailbox)
1304         {
1305           mutt_error(_("No mailbox is open"));
1306           continue;
1307         }
1308 
1309         if (shared->mailbox->msg_tagged == 0)
1310         {
1311           if (op == OP_TAG_PREFIX)
1312             mutt_error(_("No tagged messages"));
1313           else if (op == OP_TAG_PREFIX_COND)
1314           {
1315             mutt_flush_macro_to_endcond();
1316             mutt_message(_("Nothing to do"));
1317           }
1318           continue;
1319         }
1320 
1321         /* get the real command */
1322         priv->tag = true;
1323         continue;
1324       }
1325       else if (c_auto_tag && shared->mailbox && (shared->mailbox->msg_tagged != 0))
1326       {
1327         priv->tag = true;
1328       }
1329 
1330       mutt_clear_error();
1331     }
1332 
1333 #ifdef USE_NNTP
1334     OptNews = false; /* for any case */
1335 #endif
1336 
1337 #ifdef USE_NOTMUCH
1338     nm_db_debug_check(shared->mailbox);
1339 #endif
1340 
1341     int rc = index_function_dispatcher(priv->win_index, op);
1342 
1343     if (rc == IR_CONTINUE)
1344     {
1345       op = OP_DISPLAY_MESSAGE;
1346       continue;
1347     }
1348 
1349     if (rc > 0)
1350     {
1351       op = rc;
1352       continue;
1353     }
1354 
1355     if ((rc == IR_UNKNOWN) && !priv->in_pager)
1356       km_error_key(MENU_MAIN);
1357 
1358 #ifdef USE_NOTMUCH
1359     nm_db_debug_check(shared->mailbox);
1360 #endif
1361 
1362     if (priv->in_pager)
1363     {
1364       mutt_clear_pager_position();
1365       priv->in_pager = false;
1366       menu_queue_redraw(priv->menu, MENU_REDRAW_FULL);
1367     }
1368 
1369     if (rc == IR_DONE)
1370       break;
1371   }
1372 
1373   ctx_free(&shared->ctx);
1374   Context = ctx_old;
1375 
1376   return shared->mailbox;
1377 }
1378 
1379 /**
1380  * mutt_set_header_color - Select a colour for a message
1381  * @param m Mailbox
1382  * @param e Current Email
1383  */
mutt_set_header_color(struct Mailbox * m,struct Email * e)1384 void mutt_set_header_color(struct Mailbox *m, struct Email *e)
1385 {
1386   if (!e)
1387     return;
1388 
1389   struct RegexColor *color = NULL;
1390   struct PatternCache cache = { 0 };
1391 
1392   STAILQ_FOREACH(color, regex_colors_get_list(MT_COLOR_INDEX), entries)
1393   {
1394     if (mutt_pattern_exec(SLIST_FIRST(color->color_pattern),
1395                           MUTT_MATCH_FULL_ADDRESS, m, e, &cache))
1396     {
1397       e->pair = color->pair;
1398       return;
1399     }
1400   }
1401   e->pair = simple_colors_get(MT_COLOR_NORMAL);
1402 }
1403 
1404 /**
1405  * index_pager_init - Allocate the Windows for the Index/Pager
1406  * @retval ptr Dialog containing nested Windows
1407  */
index_pager_init(void)1408 struct MuttWindow *index_pager_init(void)
1409 {
1410   struct MuttWindow *dlg =
1411       mutt_window_new(WT_DLG_INDEX, MUTT_WIN_ORIENT_HORIZONTAL, MUTT_WIN_SIZE_MAXIMISE,
1412                       MUTT_WIN_SIZE_UNLIMITED, MUTT_WIN_SIZE_UNLIMITED);
1413 
1414   struct IndexSharedData *shared = index_shared_data_new();
1415   notify_set_parent(shared->notify, dlg->notify);
1416 
1417   dlg->wdata = shared;
1418   dlg->wdata_free = index_shared_data_free;
1419 
1420   const bool c_status_on_top = cs_subset_bool(NeoMutt->sub, "status_on_top");
1421 
1422   struct MuttWindow *panel_index = ipanel_new(c_status_on_top, shared);
1423   struct MuttWindow *panel_pager = ppanel_new(c_status_on_top, shared);
1424 
1425   mutt_window_add_child(dlg, panel_index);
1426   mutt_window_add_child(dlg, panel_pager);
1427 
1428   dlg->focus = panel_index;
1429 
1430   return dlg;
1431 }
1432