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

neomutt / neomutt / 21345785562

24 Jan 2026 12:51AM UTC coverage: 42.019% (-0.03%) from 42.051%
21345785562

push

github

flatcap
status: just the version number

11738 of 27935 relevant lines covered (42.02%)

445.24 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
  bool match_all = false;
207
  struct PatternList *pat = NULL;
×
208
  char *simple = buf_strdup(buf);
×
209
  if (simple)
×
210
  {
211
    mutt_check_simple(buf, MUTT_ALIAS_SIMPLESEARCH);
×
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();
×
218
    pat = mutt_pattern_comp(NULL, buf->data, MUTT_PC_FULL_MSG, err);
×
219
    if (!pat)
×
220
    {
221
      mutt_error("%s", buf_string(err));
×
222
      buf_pool_release(&err);
×
223
      goto bail;
×
224
    }
225
    buf_pool_release(&err);
×
226
  }
227
  else
228
  {
229
    match_all = true;
230
  }
231

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

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

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

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

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

287
  mutt_clear_error();
×
288

289
  rc = 0;
290

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

296
  return rc;
×
297
}
298

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

430
  mutt_clear_error();
×
431

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

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

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

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

453
  rc = 0;
454

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

461
  return rc;
×
462
}
463

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

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

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

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

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

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

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

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

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

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

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

613
    i += incr;
×
614
  }
615

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

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

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

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

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

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

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

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

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

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

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

765
    i += incr;
×
766
  }
767

768
  mutt_error(_("Not found"));
×
769
done:
×
770
  progress_free(&progress);
×
771
  return rc;
×
772
}
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