• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 21124756832

18 Jan 2026 09:18PM UTC coverage: 42.051% (-3.9%) from 45.967%
21124756832

push

github

flatcap
refactor command parsing: mailboxes

Refactor `parse_mailboxes()` into three parts:

- `parse_mailboxes()`
  Command handler, which calls:
- `parse_mailboxes_args()`
  Split the string into tokens
- `parse_mailboxes_exec()`
  Perform the actions of the `mailboxes` command

29 of 30 new or added lines in 1 file covered. (96.67%)

2607 existing lines in 19 files now uncovered.

11739 of 27916 relevant lines covered (42.05%)

445.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.63
/pattern/pattern.c
1
/**
2
 * @file
3
 * Match patterns to emails
4
 *
5
 * @authors
6
 * Copyright (C) 2020 Pietro Cerutti <gahr@gahr.ch>
7
 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
8
 * Copyright (C) 2020 Romeu Vieira <romeu.bizz@gmail.com>
9
 * Copyright (C) 2020-2023 Richard Russon <rich@flatcap.org>
10
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
11
 *
12
 * @copyright
13
 * This program is free software: you can redistribute it and/or modify it under
14
 * the terms of the GNU General Public License as published by the Free Software
15
 * Foundation, either version 2 of the License, or (at your option) any later
16
 * version.
17
 *
18
 * This program is distributed in the hope that it will be useful, but WITHOUT
19
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21
 * details.
22
 *
23
 * You should have received a copy of the GNU General Public License along with
24
 * this program.  If not, see <http://www.gnu.org/licenses/>.
25
 */
26

27
/**
28
 * @page pattern_pattern Match patterns to emails
29
 *
30
 * Match patterns to emails
31
 */
32

33
#include "config.h"
34
#include <stdbool.h>
35
#include <stddef.h>
36
#include "private.h"
37
#include "mutt/lib.h"
38
#include "config/lib.h"
39
#include "email/lib.h"
40
#include "core/lib.h"
41
#include "alias/gui.h" // IWYU pragma: keep
42
#include "alias/lib.h"
43
#include "gui/lib.h"
44
#include "mutt.h"
45
#include "lib.h"
46
#include "editor/lib.h"
47
#include "history/lib.h"
48
#include "imap/lib.h"
49
#include "menu/lib.h"
50
#include "progress/lib.h"
51
#include "mutt_logging.h"
52
#include "mx.h"
53
#include "search_state.h"
54

55
/**
56
 * RangeRegexes - Set of Regexes for various range types
57
 *
58
 * This array, will also contain the compiled regexes.
59
 */
60
struct RangeRegex RangeRegexes[] = {
61
  // clang-format off
62
  [RANGE_K_REL]  = { RANGE_REL_RX,  1, 3, 0, { 0 } },
63
  [RANGE_K_ABS]  = { RANGE_ABS_RX,  1, 3, 0, { 0 } },
64
  [RANGE_K_LT]   = { RANGE_LT_RX,   1, 2, 0, { 0 } },
65
  [RANGE_K_GT]   = { RANGE_GT_RX,   2, 1, 0, { 0 } },
66
  [RANGE_K_BARE] = { RANGE_BARE_RX, 1, 1, 0, { 0 } },
67
  // clang-format on
68
};
69

70
/**
71
 * @defgroup eat_arg_api Parse a pattern API
72
 *
73
 * Prototype for a function to parse a pattern
74
 *
75
 * @param pat   Pattern to store the results in
76
 * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
77
 * @param s     String to parse
78
 * @param err   Buffer for error messages
79
 * @retval true The pattern was read successfully
80
 */
81
typedef bool (*eat_arg_t)(struct Pattern *pat, PatternCompFlags flags,
82
                          struct Buffer *s, struct Buffer *err);
83

84
/**
85
 * quote_simple - Apply simple quoting to a string
86
 * @param str    String to quote
87
 * @param buf    Buffer for the result
88
 */
89
static void quote_simple(const char *str, struct Buffer *buf)
1✔
90
{
91
  buf_reset(buf);
1✔
92
  buf_addch(buf, '"');
1✔
93
  while (*str)
13✔
94
  {
95
    if ((*str == '\\') || (*str == '"'))
12✔
UNCOV
96
      buf_addch(buf, '\\');
×
97
    buf_addch(buf, *str++);
12✔
98
  }
99
  buf_addch(buf, '"');
1✔
100
}
1✔
101

102
/**
103
 * mutt_check_simple - Convert a simple search into a real request
104
 * @param buf    Buffer for the result
105
 * @param simple Search string to convert
106
 */
107
void mutt_check_simple(struct Buffer *buf, const char *simple)
15✔
108
{
109
  bool do_simple = true;
110

111
  for (const char *p = buf_string(buf); p && (p[0] != '\0'); p++)
29✔
112
  {
113
    if ((p[0] == '\\') && (p[1] != '\0'))
26✔
114
    {
UNCOV
115
      p++;
×
116
    }
117
    else if ((p[0] == '~') || (p[0] == '=') || (p[0] == '%'))
26✔
118
    {
119
      do_simple = false;
120
      break;
121
    }
122
  }
123

124
  /* XXX - is mutt_istr_cmp() right here, or should we use locale's
125
   * equivalences?  */
126

127
  if (do_simple) /* yup, so spoof a real request */
15✔
128
  {
129
    /* convert old tokens into the new format */
130
    if (mutt_istr_equal("all", buf_string(buf)) || mutt_str_equal("^", buf_string(buf)) ||
9✔
131
        mutt_str_equal(".", buf_string(buf))) /* ~A is more efficient */
3✔
132
    {
133
      buf_strcpy(buf, "~A");
2✔
134
    }
135
    else if (mutt_istr_equal("del", buf_string(buf)))
1✔
136
    {
UNCOV
137
      buf_strcpy(buf, "~D");
×
138
    }
139
    else if (mutt_istr_equal("flag", buf_string(buf)))
1✔
140
    {
UNCOV
141
      buf_strcpy(buf, "~F");
×
142
    }
143
    else if (mutt_istr_equal("new", buf_string(buf)))
1✔
144
    {
UNCOV
145
      buf_strcpy(buf, "~N");
×
146
    }
147
    else if (mutt_istr_equal("old", buf_string(buf)))
1✔
148
    {
UNCOV
149
      buf_strcpy(buf, "~O");
×
150
    }
151
    else if (mutt_istr_equal("repl", buf_string(buf)))
1✔
152
    {
UNCOV
153
      buf_strcpy(buf, "~Q");
×
154
    }
155
    else if (mutt_istr_equal("read", buf_string(buf)))
1✔
156
    {
UNCOV
157
      buf_strcpy(buf, "~R");
×
158
    }
159
    else if (mutt_istr_equal("tag", buf_string(buf)))
1✔
160
    {
UNCOV
161
      buf_strcpy(buf, "~T");
×
162
    }
163
    else if (mutt_istr_equal("unread", buf_string(buf)))
1✔
164
    {
UNCOV
165
      buf_strcpy(buf, "~U");
×
166
    }
167
    else
168
    {
169
      struct Buffer *tmp = buf_pool_get();
1✔
170
      quote_simple(buf_string(buf), tmp);
1✔
171
      mutt_file_expand_fmt(buf, simple, buf_string(tmp));
2✔
172
      buf_pool_release(&tmp);
1✔
173
    }
174
  }
175
}
15✔
176

177
/**
178
 * mutt_pattern_alias_func - Perform some Pattern matching for Alias
179
 * @param prompt    Prompt to show the user
180
 * @param mdata     Menu data holding Aliases
181
 * @param action    What to do with the results, e.g. #PAA_TAG
182
 * @param menu      Current menu
183
 * @retval  0 Success
184
 * @retval -1 Failure
185
 */
UNCOV
186
int mutt_pattern_alias_func(char *prompt, struct AliasMenuData *mdata,
×
187
                            enum PatternAlias action, struct Menu *menu)
188
{
189
  int rc = -1;
UNCOV
190
  struct Progress *progress = NULL;
×
UNCOV
191
  struct Buffer *buf = buf_pool_get();
×
192

193
  buf_strcpy(buf, mdata->limit);
×
UNCOV
194
  if (prompt)
×
195
  {
196
    if ((mw_get_field(prompt, buf, MUTT_COMP_CLEAR, HC_PATTERN, &CompletePatternOps, NULL) != 0) ||
×
UNCOV
197
        buf_is_empty(buf))
×
198
    {
199
      buf_pool_release(&buf);
×
UNCOV
200
      return -1;
×
201
    }
202
  }
203

UNCOV
204
  mutt_message(_("Compiling search pattern..."));
×
205

206
  bool match_all = false;
UNCOV
207
  struct PatternList *pat = NULL;
×
UNCOV
208
  char *simple = buf_strdup(buf);
×
209
  if (simple)
×
210
  {
211
    mutt_check_simple(buf, MUTT_ALIAS_SIMPLESEARCH);
×
UNCOV
212
    const char *pbuf = buf->data;
×
213
    while (*pbuf == ' ')
×
214
      pbuf++;
×
215
    match_all = mutt_str_equal(pbuf, "~A");
×
216

217
    struct Buffer *err = buf_pool_get();
×
UNCOV
218
    pat = mutt_pattern_comp(NULL, buf->data, MUTT_PC_FULL_MSG, err);
×
219
    if (!pat)
×
220
    {
221
      mutt_error("%s", buf_string(err));
×
UNCOV
222
      buf_pool_release(&err);
×
223
      goto bail;
×
224
    }
225
  }
226
  else
227
  {
228
    match_all = true;
229
  }
230

UNCOV
231
  progress = progress_new(MUTT_PROGRESS_READ, ARRAY_SIZE(&mdata->ava));
×
UNCOV
232
  progress_set_message(progress, _("Executing command on matching messages..."));
×
233

234
  int vcounter = 0;
235
  struct AliasView *avp = NULL;
UNCOV
236
  ARRAY_FOREACH(avp, &mdata->ava)
×
237
  {
238
    progress_update(progress, ARRAY_FOREACH_IDX_avp, -1);
×
239

240
    if (match_all ||
×
UNCOV
241
        mutt_pattern_alias_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, avp, NULL))
×
242
    {
243
      switch (action)
×
244
      {
245
        case PAA_TAG:
×
UNCOV
246
          avp->is_tagged = true;
×
247
          break;
×
248
        case PAA_UNTAG:
×
249
          avp->is_tagged = false;
×
250
          break;
×
251
        case PAA_VISIBLE:
×
252
          avp->is_visible = true;
×
253
          vcounter++;
×
254
          break;
×
255
      }
256
    }
257
    else
258
    {
UNCOV
259
      switch (action)
×
260
      {
261
        case PAA_TAG:
262
        case PAA_UNTAG:
263
          // Do nothing
264
          break;
UNCOV
265
        case PAA_VISIBLE:
×
UNCOV
266
          avp->is_visible = false;
×
267
          break;
×
268
      }
269
    }
270
  }
UNCOV
271
  progress_free(&progress);
×
272

273
  FREE(&mdata->limit);
×
UNCOV
274
  if (!match_all)
×
275
  {
276
    mdata->limit = simple;
×
UNCOV
277
    simple = NULL;
×
278
  }
279

UNCOV
280
  if (menu && (action == PAA_VISIBLE))
×
281
  {
282
    menu->max = vcounter;
×
UNCOV
283
    menu_set_index(menu, 0);
×
284
  }
285

UNCOV
286
  mutt_clear_error();
×
287

288
  rc = 0;
289

UNCOV
290
bail:
×
UNCOV
291
  buf_pool_release(&buf);
×
292
  FREE(&simple);
×
293
  mutt_pattern_free(&pat);
×
294

295
  return rc;
×
296
}
297

298
/**
299
 * mutt_pattern_func - Perform some Pattern matching
300
 * @param mv     Mailbox View
301
 * @param op     Operation to perform, e.g. #MUTT_LIMIT
302
 * @param prompt Prompt to show the user
303
 * @retval  0 Success
304
 * @retval -1 Failure
305
 */
UNCOV
306
int mutt_pattern_func(struct MailboxView *mv, int op, char *prompt)
×
307
{
308
  if (!mv || !mv->mailbox)
×
309
    return -1;
310

311
  struct Mailbox *m = mv->mailbox;
312

UNCOV
313
  struct Buffer *err = NULL;
×
314
  int rc = -1;
315
  struct Progress *progress = NULL;
×
UNCOV
316
  struct Buffer *buf = buf_pool_get();
×
317
  bool interrupted = false;
318

UNCOV
319
  buf_strcpy(buf, mv->pattern);
×
UNCOV
320
  if (prompt || (op != MUTT_LIMIT))
×
321
  {
322
    if ((mw_get_field(prompt, buf, MUTT_COMP_CLEAR, HC_PATTERN, &CompletePatternOps, NULL) != 0) ||
×
UNCOV
323
        buf_is_empty(buf))
×
324
    {
325
      buf_pool_release(&buf);
×
UNCOV
326
      return -1;
×
327
    }
328
  }
329

UNCOV
330
  mutt_message(_("Compiling search pattern..."));
×
331

332
  char *simple = buf_strdup(buf);
×
UNCOV
333
  const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
334
  mutt_check_simple(buf, NONULL(c_simple_search));
×
335
  const char *pbuf = buf->data;
×
336
  while (*pbuf == ' ')
×
337
    pbuf++;
×
338
  const bool match_all = mutt_str_equal(pbuf, "~A");
×
339

340
  err = buf_pool_get();
×
UNCOV
341
  struct PatternList *pat = mutt_pattern_comp(mv, buf->data, MUTT_PC_FULL_MSG, err);
×
342
  if (!pat)
×
343
  {
344
    mutt_error("%s", buf_string(err));
×
UNCOV
345
    goto bail;
×
346
  }
347

UNCOV
348
  if ((m->type == MUTT_IMAP) && (!imap_search(m, pat)))
×
UNCOV
349
    goto bail;
×
350

351
  progress = progress_new(MUTT_PROGRESS_READ, (op == MUTT_LIMIT) ? m->msg_count : m->vcount);
×
UNCOV
352
  progress_set_message(progress, _("Executing command on matching messages..."));
×
353

354
  if (op == MUTT_LIMIT)
×
355
  {
356
    m->vcount = 0;
×
UNCOV
357
    mv->vsize = 0;
×
358
    mv->collapsed = false;
×
359
    int padding = mx_msg_padding_size(m);
×
360

361
    for (int i = 0; i < m->msg_count; i++)
×
362
    {
363
      struct Email *e = m->emails[i];
×
UNCOV
364
      if (!e)
×
365
        break;
366

UNCOV
367
      if (SigInt)
×
368
      {
369
        interrupted = true;
UNCOV
370
        SigInt = false;
×
UNCOV
371
        break;
×
372
      }
373
      progress_update(progress, i, -1);
×
374
      /* new limit pattern implicitly uncollapses all threads */
375
      e->vnum = -1;
×
UNCOV
376
      e->visible = false;
×
377
      e->limit_visited = true;
×
378
      e->collapsed = false;
×
379
      e->num_hidden = 0;
×
380

381
      if (match_all ||
×
UNCOV
382
          mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
×
383
      {
384
        e->vnum = m->vcount;
×
UNCOV
385
        e->visible = true;
×
386
        m->v2r[m->vcount] = i;
×
387
        m->vcount++;
×
388
        struct Body *b = e->body;
×
389
        mv->vsize += b->length + b->offset - b->hdr_offset + padding;
×
390
      }
391
    }
392
  }
393
  else
394
  {
UNCOV
395
    for (int i = 0; i < m->vcount; i++)
×
396
    {
397
      struct Email *e = mutt_get_virt_email(m, i);
×
UNCOV
398
      if (!e)
×
399
        continue;
×
400

401
      if (SigInt)
×
402
      {
403
        interrupted = true;
UNCOV
404
        SigInt = false;
×
UNCOV
405
        break;
×
406
      }
407
      progress_update(progress, i, -1);
×
UNCOV
408
      if (mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
×
409
      {
410
        switch (op)
×
411
        {
412
          case MUTT_UNDELETE:
×
UNCOV
413
            mutt_set_flag(m, e, MUTT_PURGE, false, true);
×
414
            FALLTHROUGH;
415

UNCOV
416
          case MUTT_DELETE:
×
UNCOV
417
            mutt_set_flag(m, e, MUTT_DELETE, (op == MUTT_DELETE), true);
×
418
            break;
×
419
          case MUTT_TAG:
×
420
          case MUTT_UNTAG:
421
            mutt_set_flag(m, e, MUTT_TAG, (op == MUTT_TAG), true);
×
UNCOV
422
            break;
×
423
        }
424
      }
425
    }
426
  }
UNCOV
427
  progress_free(&progress);
×
428

429
  mutt_clear_error();
×
430

431
  if (op == MUTT_LIMIT)
×
432
  {
433
    /* drop previous limit pattern */
UNCOV
434
    FREE(&mv->pattern);
×
UNCOV
435
    mutt_pattern_free(&mv->limit_pattern);
×
436

437
    if (m->msg_count && !m->vcount)
×
UNCOV
438
      mutt_error(_("No messages matched criteria"));
×
439

440
    /* record new limit pattern, unless match all */
UNCOV
441
    if (!match_all)
×
442
    {
443
      mv->pattern = simple;
×
UNCOV
444
      simple = NULL; /* don't clobber it */
×
445
      mv->limit_pattern = mutt_pattern_comp(mv, buf->data, MUTT_PC_FULL_MSG, err);
×
446
    }
447
  }
448

UNCOV
449
  if (interrupted)
×
UNCOV
450
    mutt_error(_("Search interrupted"));
×
451

452
  rc = 0;
453

UNCOV
454
bail:
×
UNCOV
455
  buf_pool_release(&buf);
×
456
  buf_pool_release(&err);
×
457
  FREE(&simple);
×
458
  mutt_pattern_free(&pat);
×
459

460
  return rc;
×
461
}
462

463
/**
464
 * mutt_search_command - Perform a search
465
 * @param mv    Mailbox view to search through
466
 * @param menu  Current Menu
467
 * @param cur   Index number of current email
468
 * @param state Current search state
469
 * @param flags Search flags, e.g. SEARCH_PROMPT
470
 * @retval >=0  Index of matching email
471
 * @retval -1   No match, or error
472
 */
UNCOV
473
int mutt_search_command(struct MailboxView *mv, struct Menu *menu, int cur,
×
474
                        struct SearchState *state, SearchFlags flags)
475
{
UNCOV
476
  struct Progress *progress = NULL;
×
477
  int rc = -1;
478
  struct Mailbox *m = mv ? mv->mailbox : NULL;
×
UNCOV
479
  if (!m)
×
480
    return -1;
481
  bool pattern_changed = false;
482

UNCOV
483
  if (buf_is_empty(state->string) || (flags & SEARCH_PROMPT))
×
484
  {
485
    if ((mw_get_field((state->reverse) ? _("Reverse search for: ") : _("Search for: "),
×
486
                      state->string, MUTT_COMP_CLEAR, HC_PATTERN,
487
                      &CompletePatternOps, NULL) != 0) ||
×
UNCOV
488
        buf_is_empty(state->string))
×
489
    {
490
      goto done;
×
491
    }
492

493
    /* compare the *expanded* version of the search pattern in case
494
     * $simple_search has changed while we were searching */
UNCOV
495
    struct Buffer *tmp = buf_pool_get();
×
UNCOV
496
    buf_copy(tmp, state->string);
×
497
    const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
498
    mutt_check_simple(tmp, NONULL(c_simple_search));
×
499
    if (!buf_str_equal(tmp, state->string_expn))
×
500
    {
501
      mutt_pattern_free(&state->pattern);
×
UNCOV
502
      buf_copy(state->string_expn, tmp);
×
503
      buf_pool_release(&tmp);
×
504
    }
505
  }
506

UNCOV
507
  if (!state->pattern)
×
508
  {
509
    mutt_message(_("Compiling search pattern..."));
×
UNCOV
510
    mutt_pattern_free(&state->pattern);
×
511
    struct Buffer *err = buf_pool_get();
×
512
    state->pattern = mutt_pattern_comp(mv, state->string_expn->data, MUTT_PC_FULL_MSG, err);
×
513
    pattern_changed = true;
514
    if (!state->pattern)
×
515
    {
516
      mutt_error("%s", buf_string(err));
×
UNCOV
517
      buf_free(&err);
×
518
      buf_reset(state->string);
×
519
      buf_reset(state->string_expn);
×
520
      return -1;
×
521
    }
522
    buf_free(&err);
×
UNCOV
523
    mutt_clear_error();
×
524
  }
525

526
  if (pattern_changed)
527
  {
UNCOV
528
    for (int i = 0; i < m->msg_count; i++)
×
UNCOV
529
      m->emails[i]->searched = false;
×
530
    if ((m->type == MUTT_IMAP) && (!imap_search(m, state->pattern)))
×
531
      return -1;
532
  }
533

UNCOV
534
  int incr = state->reverse ? -1 : 1;
×
UNCOV
535
  if (flags & SEARCH_OPPOSITE)
×
536
    incr = -incr;
×
537

538
  progress = progress_new(MUTT_PROGRESS_READ, m->vcount);
×
UNCOV
539
  progress_set_message(progress, _("Searching..."));
×
540

541
  const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
×
UNCOV
542
  for (int i = cur + incr, j = 0; j != m->vcount; j++)
×
543
  {
544
    const char *msg = NULL;
UNCOV
545
    progress_update(progress, j, -1);
×
UNCOV
546
    if (i > m->vcount - 1)
×
547
    {
548
      i = 0;
UNCOV
549
      if (c_wrap_search)
×
550
      {
551
        msg = _("Search wrapped to top");
×
552
      }
553
      else
554
      {
UNCOV
555
        mutt_message(_("Search hit bottom without finding match"));
×
UNCOV
556
        goto done;
×
557
      }
558
    }
UNCOV
559
    else if (i < 0)
×
560
    {
561
      i = m->vcount - 1;
×
UNCOV
562
      if (c_wrap_search)
×
563
      {
564
        msg = _("Search wrapped to bottom");
×
565
      }
566
      else
567
      {
UNCOV
568
        mutt_message(_("Search hit top without finding match"));
×
UNCOV
569
        goto done;
×
570
      }
571
    }
572

UNCOV
573
    struct Email *e = mutt_get_virt_email(m, i);
×
UNCOV
574
    if (!e)
×
575
      goto done;
×
576

577
    if (e->searched)
×
578
    {
579
      /* if we've already evaluated this message, use the cached value */
UNCOV
580
      if (e->matched)
×
581
      {
582
        mutt_clear_error();
×
UNCOV
583
        if (msg && *msg)
×
584
          mutt_message("%s", msg);
×
585
        rc = i;
586
        goto done;
×
587
      }
588
    }
589
    else
590
    {
591
      /* remember that we've already searched this message */
UNCOV
592
      e->searched = true;
×
UNCOV
593
      e->matched = mutt_pattern_exec(SLIST_FIRST(state->pattern),
×
594
                                     MUTT_MATCH_FULL_ADDRESS, m, e, NULL);
595
      if (e->matched)
×
596
      {
597
        mutt_clear_error();
×
UNCOV
598
        if (msg && *msg)
×
599
          mutt_message("%s", msg);
×
600
        rc = i;
601
        goto done;
×
602
      }
603
    }
604

UNCOV
605
    if (SigInt)
×
606
    {
607
      mutt_error(_("Search interrupted"));
×
UNCOV
608
      SigInt = false;
×
609
      goto done;
×
610
    }
611

UNCOV
612
    i += incr;
×
613
  }
614

UNCOV
615
  mutt_error(_("Not found"));
×
UNCOV
616
done:
×
617
  progress_free(&progress);
×
618
  return rc;
×
619
}
620

621
/**
622
 * mutt_search_alias_command - Perform a search
623
 * @param menu  Menu to search through
624
 * @param cur   Index number of current email
625
 * @param state Current search state
626
 * @param flags Search flags, e.g. SEARCH_PROMPT
627
 * @retval >=0  Index of matching alias
628
 * @retval -1   No match, or error
629
 */
UNCOV
630
int mutt_search_alias_command(struct Menu *menu, int cur,
×
631
                              struct SearchState *state, SearchFlags flags)
632
{
UNCOV
633
  struct Progress *progress = NULL;
×
UNCOV
634
  const struct AliasMenuData *mdata = menu->mdata;
×
635
  const struct AliasViewArray *ava = &mdata->ava;
636
  int rc = -1;
637
  bool pattern_changed = false;
638

UNCOV
639
  if (buf_is_empty(state->string) || flags & SEARCH_PROMPT)
×
640
  {
641
    if ((mw_get_field((flags & OP_SEARCH_REVERSE) ? _("Reverse search for: ") : _("Search for: "),
×
642
                      state->string, MUTT_COMP_CLEAR, HC_PATTERN,
643
                      &CompletePatternOps, NULL) != 0) ||
×
UNCOV
644
        buf_is_empty(state->string))
×
645
    {
646
      goto done;
×
647
    }
648

649
    /* compare the *expanded* version of the search pattern in case
650
     * $simple_search has changed while we were searching */
UNCOV
651
    struct Buffer *tmp = buf_pool_get();
×
UNCOV
652
    buf_copy(tmp, state->string);
×
653
    mutt_check_simple(tmp, MUTT_ALIAS_SIMPLESEARCH);
×
654
    if (!buf_str_equal(tmp, state->string_expn))
×
655
    {
656
      mutt_pattern_free(&state->pattern);
×
UNCOV
657
      buf_copy(state->string_expn, tmp);
×
658
      buf_pool_release(&tmp);
×
659
    }
660
  }
661

UNCOV
662
  if (!state->pattern)
×
663
  {
664
    mutt_message(_("Compiling search pattern..."));
×
UNCOV
665
    struct Buffer *err = buf_pool_get();
×
666
    state->pattern = mutt_pattern_comp(NULL, state->string_expn->data, MUTT_PC_FULL_MSG, err);
×
667
    pattern_changed = true;
668
    if (!state->pattern)
×
669
    {
670
      mutt_error("%s", buf_string(err));
×
UNCOV
671
      buf_free(&err);
×
672
      buf_reset(state->string);
×
673
      buf_reset(state->string_expn);
×
674
      return -1;
×
675
    }
676
    buf_free(&err);
×
UNCOV
677
    mutt_clear_error();
×
678
  }
679

680
  if (pattern_changed)
681
  {
682
    struct AliasView *av = NULL;
UNCOV
683
    ARRAY_FOREACH(av, ava)
×
684
    {
685
      av->is_searched = false;
×
686
    }
687
  }
688

UNCOV
689
  int incr = state->reverse ? -1 : 1;
×
UNCOV
690
  if (flags & SEARCH_OPPOSITE)
×
691
    incr = -incr;
×
692

693
  progress = progress_new(MUTT_PROGRESS_READ, ARRAY_SIZE(ava));
×
UNCOV
694
  progress_set_message(progress, _("Searching..."));
×
695

696
  const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
×
UNCOV
697
  for (int i = cur + incr, j = 0; j != ARRAY_SIZE(ava); j++)
×
698
  {
699
    const char *msg = NULL;
UNCOV
700
    progress_update(progress, j, -1);
×
UNCOV
701
    if (i > ARRAY_SIZE(ava) - 1)
×
702
    {
703
      i = 0;
UNCOV
704
      if (c_wrap_search)
×
705
      {
706
        msg = _("Search wrapped to top");
×
707
      }
708
      else
709
      {
UNCOV
710
        mutt_message(_("Search hit bottom without finding match"));
×
UNCOV
711
        goto done;
×
712
      }
713
    }
UNCOV
714
    else if (i < 0)
×
715
    {
716
      i = ARRAY_SIZE(ava) - 1;
×
UNCOV
717
      if (c_wrap_search)
×
718
      {
719
        msg = _("Search wrapped to bottom");
×
720
      }
721
      else
722
      {
UNCOV
723
        mutt_message(_("Search hit top without finding match"));
×
UNCOV
724
        goto done;
×
725
      }
726
    }
727

UNCOV
728
    struct AliasView *av = ARRAY_GET(ava, i);
×
UNCOV
729
    if (av->is_searched)
×
730
    {
731
      /* if we've already evaluated this message, use the cached value */
UNCOV
732
      if (av->is_matched)
×
733
      {
734
        mutt_clear_error();
×
UNCOV
735
        if (msg && *msg)
×
736
          mutt_message("%s", msg);
×
737
        rc = i;
738
        goto done;
×
739
      }
740
    }
741
    else
742
    {
743
      /* remember that we've already searched this message */
UNCOV
744
      av->is_searched = true;
×
UNCOV
745
      av->is_matched = mutt_pattern_alias_exec(SLIST_FIRST(state->pattern),
×
746
                                               MUTT_MATCH_FULL_ADDRESS, av, NULL);
747
      if (av->is_matched)
×
748
      {
749
        mutt_clear_error();
×
UNCOV
750
        if (msg && *msg)
×
751
          mutt_message("%s", msg);
×
752
        rc = i;
753
        goto done;
×
754
      }
755
    }
756

UNCOV
757
    if (SigInt)
×
758
    {
759
      mutt_error(_("Search interrupted"));
×
UNCOV
760
      SigInt = false;
×
761
      goto done;
×
762
    }
763

UNCOV
764
    i += incr;
×
765
  }
766

UNCOV
767
  mutt_error(_("Not found"));
×
UNCOV
768
done:
×
769
  progress_free(&progress);
×
770
  return rc;
×
771
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc