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

neomutt / neomutt / 22837812302

07 Mar 2026 01:58PM UTC coverage: 42.377% (+0.009%) from 42.368%
22837812302

push

github

flatcap
rename MT_COLOR_SIDEBAR_SPOOL_FILE

Rename MT_COLOR_SIDEBAR_SPOOLFILE to MT_COLOR_SIDEBAR_SPOOL_FILE
to match the colour name.

0 of 2 new or added lines in 1 file covered. (0.0%)

2351 existing lines in 28 files now uncovered.

12119 of 28598 relevant lines covered (42.38%)

2821.95 hits per line

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

8.61
/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✔
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
    {
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
    {
137
      buf_strcpy(buf, "~D");
×
138
    }
139
    else if (mutt_istr_equal("flag", buf_string(buf)))
1✔
140
    {
141
      buf_strcpy(buf, "~F");
×
142
    }
143
    else if (mutt_istr_equal("new", buf_string(buf)))
1✔
144
    {
145
      buf_strcpy(buf, "~N");
×
146
    }
147
    else if (mutt_istr_equal("old", buf_string(buf)))
1✔
148
    {
149
      buf_strcpy(buf, "~O");
×
150
    }
151
    else if (mutt_istr_equal("repl", buf_string(buf)))
1✔
152
    {
153
      buf_strcpy(buf, "~Q");
×
154
    }
155
    else if (mutt_istr_equal("read", buf_string(buf)))
1✔
156
    {
157
      buf_strcpy(buf, "~R");
×
158
    }
159
    else if (mutt_istr_equal("tag", buf_string(buf)))
1✔
160
    {
161
      buf_strcpy(buf, "~T");
×
162
    }
163
    else if (mutt_istr_equal("unread", buf_string(buf)))
1✔
164
    {
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
 */
186
int mutt_pattern_alias_func(char *prompt, struct AliasMenuData *mdata,
×
187
                            enum PatternAlias action, struct Menu *menu)
188
{
189
  int rc = -1;
190
  struct Progress *progress = NULL;
×
191
  struct Buffer *buf = buf_pool_get();
×
192

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

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

206
  /* Compile the pattern string; "~A" means match-all.  If the simple string
207
   * was expanded into a pattern, compile it for evaluation. */
208
  bool match_all = false;
209
  struct PatternList *pat = NULL;
×
UNCOV
210
  char *simple = buf_strdup(buf);
×
211
  if (simple)
×
212
  {
213
    mutt_check_simple(buf, MUTT_ALIAS_SIMPLESEARCH);
×
214
    const char *pbuf = buf->data;
×
215
    while (*pbuf == ' ')
×
UNCOV
216
      pbuf++;
×
217
    match_all = mutt_str_equal(pbuf, "~A");
×
218

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

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

237
  /* Apply the action (tag, untag, or filter visibility) to each alias
238
   * that matches the compiled pattern */
239
  int vcounter = 0;
240
  struct AliasView *avp = NULL;
241
  ARRAY_FOREACH(avp, &mdata->ava)
×
242
  {
UNCOV
243
    progress_update(progress, ARRAY_FOREACH_IDX_avp, -1);
×
244

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

278
  FREE(&mdata->limit);
×
UNCOV
279
  if (!match_all)
×
280
  {
281
    mdata->limit = simple;
×
UNCOV
282
    simple = NULL;
×
283
  }
284

UNCOV
285
  if (menu && (action == PAA_VISIBLE))
×
286
  {
287
    menu->max = vcounter;
×
UNCOV
288
    menu_set_index(menu, 0);
×
289
  }
290

291
  mutt_clear_error();
×
292

293
  rc = 0;
294

UNCOV
295
bail:
×
296
  buf_pool_release(&buf);
×
UNCOV
297
  FREE(&simple);
×
UNCOV
298
  mutt_pattern_free(&pat);
×
299

UNCOV
300
  return rc;
×
301
}
302

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

316
  struct Mailbox *m = mv->mailbox;
317

UNCOV
318
  struct Buffer *err = NULL;
×
319
  int rc = -1;
320
  struct Progress *progress = NULL;
×
321
  struct Buffer *buf = buf_pool_get();
×
322
  bool interrupted = false;
323

324
  buf_strcpy(buf, mv->pattern);
×
UNCOV
325
  if (prompt || (op != MUTT_LIMIT))
×
326
  {
327
    if ((mw_get_field(prompt, buf, MUTT_COMP_CLEAR, HC_PATTERN, &CompletePatternOps, NULL) != 0) ||
×
UNCOV
328
        buf_is_empty(buf))
×
329
    {
UNCOV
330
      buf_pool_release(&buf);
×
331
      return -1;
×
332
    }
333
  }
334

335
  mutt_message(_("Compiling search pattern..."));
×
336

337
  char *simple = buf_strdup(buf);
×
338
  const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
339
  mutt_check_simple(buf, NONULL(c_simple_search));
×
UNCOV
340
  const char *pbuf = buf->data;
×
341
  while (*pbuf == ' ')
×
342
    pbuf++;
×
343
  const bool match_all = mutt_str_equal(pbuf, "~A");
×
344

345
  err = buf_pool_get();
×
346
  struct PatternList *pat = mutt_pattern_comp(mv, buf->data, MUTT_PC_FULL_MSG, err);
×
UNCOV
347
  if (!pat)
×
348
  {
349
    mutt_error("%s", buf_string(err));
×
350
    goto bail;
×
351
  }
352

353
  if ((m->type == MUTT_IMAP) && (!imap_search(m, pat)))
×
UNCOV
354
    goto bail;
×
355

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

359
  if (op == MUTT_LIMIT)
×
360
  {
UNCOV
361
    m->vcount = 0;
×
362
    mv->vsize = 0;
×
UNCOV
363
    mv->collapsed = false;
×
364
    int padding = mx_msg_padding_size(m);
×
365

UNCOV
366
    for (int i = 0; i < m->msg_count; i++)
×
367
    {
368
      struct Email *e = m->emails[i];
×
UNCOV
369
      if (!e)
×
370
        break;
371

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

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

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

UNCOV
421
          case MUTT_DELETE:
×
422
            mutt_set_flag(m, e, MUTT_DELETE, (op == MUTT_DELETE), true);
×
423
            break;
×
UNCOV
424
          case MUTT_TAG:
×
425
          case MUTT_UNTAG:
UNCOV
426
            mutt_set_flag(m, e, MUTT_TAG, (op == MUTT_TAG), true);
×
UNCOV
427
            break;
×
428
        }
429
      }
430
    }
431
  }
432
  progress_free(&progress);
×
433

UNCOV
434
  mutt_clear_error();
×
435

436
  if (op == MUTT_LIMIT)
×
437
  {
438
    /* drop previous limit pattern */
439
    FREE(&mv->pattern);
×
UNCOV
440
    mutt_pattern_free(&mv->limit_pattern);
×
441

442
    if (m->msg_count && !m->vcount)
×
UNCOV
443
      mutt_error(_("No messages matched criteria"));
×
444

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

UNCOV
454
  if (interrupted)
×
455
    mutt_error(_("Search interrupted"));
×
456

457
  rc = 0;
458

459
bail:
×
UNCOV
460
  buf_pool_release(&buf);
×
461
  buf_pool_release(&err);
×
UNCOV
462
  FREE(&simple);
×
UNCOV
463
  mutt_pattern_free(&pat);
×
464

UNCOV
465
  return rc;
×
466
}
467

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

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

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

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

531
  if (pattern_changed)
532
  {
UNCOV
533
    for (int i = 0; i < m->msg_count; i++)
×
UNCOV
534
      m->emails[i]->searched = false;
×
535
    if ((m->type == MUTT_IMAP) && (!imap_search(m, state->pattern)))
×
536
      return -1;
537
  }
538

539
  int incr = state->reverse ? -1 : 1;
×
540
  if (flags & SEARCH_OPPOSITE)
×
UNCOV
541
    incr = -incr;
×
542

543
  progress = progress_new(MUTT_PROGRESS_READ, m->vcount);
×
UNCOV
544
  progress_set_message(progress, _("Searching..."));
×
545

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

578
    struct Email *e = mutt_get_virt_email(m, i);
×
UNCOV
579
    if (!e)
×
UNCOV
580
      goto done;
×
581

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

610
    if (SigInt)
×
611
    {
UNCOV
612
      mutt_error(_("Search interrupted"));
×
613
      SigInt = false;
×
UNCOV
614
      goto done;
×
615
    }
616

617
    i += incr;
×
618
  }
619

UNCOV
620
  mutt_error(_("Not found"));
×
UNCOV
621
done:
×
UNCOV
622
  progress_free(&progress);
×
UNCOV
623
  return rc;
×
624
}
625

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

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

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

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

685
  if (pattern_changed)
686
  {
687
    struct AliasView *av = NULL;
UNCOV
688
    ARRAY_FOREACH(av, ava)
×
689
    {
690
      av->is_searched = false;
×
691
    }
692
  }
693

694
  int incr = state->reverse ? -1 : 1;
×
695
  if (flags & SEARCH_OPPOSITE)
×
UNCOV
696
    incr = -incr;
×
697

698
  progress = progress_new(MUTT_PROGRESS_READ, ARRAY_SIZE(ava));
×
UNCOV
699
  progress_set_message(progress, _("Searching..."));
×
700

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

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

762
    if (SigInt)
×
763
    {
UNCOV
764
      mutt_error(_("Search interrupted"));
×
765
      SigInt = false;
×
UNCOV
766
      goto done;
×
767
    }
768

769
    i += incr;
×
770
  }
771

UNCOV
772
  mutt_error(_("Not found"));
×
UNCOV
773
done:
×
UNCOV
774
  progress_free(&progress);
×
UNCOV
775
  return rc;
×
776
}
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