Coverage Report

Created: 2023-06-07 06:15

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