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

neomutt / neomutt / 18454331572

12 Oct 2025 05:03PM UTC coverage: 50.06% (-0.03%) from 50.093%
18454331572

push

github

flatcap
Use buf_string() to get the data from a Buffer

5 of 17 new or added lines in 3 files covered. (29.41%)

203 existing lines in 4 files now uncovered.

9127 of 18232 relevant lines covered (50.06%)

274.1 hits per line

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

0.0
/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 "mview.h"
53
#include "mx.h"
54
#include "protos.h"
55
#include "search_state.h"
56

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

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

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

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

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

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

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

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

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

206
  mutt_message(_("Compiling search pattern..."));
×
207

208
  bool match_all = false;
209
  struct PatternList *pat = NULL;
×
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 == ' ')
×
216
      pbuf++;
×
217
    match_all = mutt_str_equal(pbuf, "~A");
×
218

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

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

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

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

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

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

288
  mutt_clear_error();
×
289

290
  rc = 0;
291

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

297
  return rc;
×
298
}
299

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

431
  mutt_clear_error();
×
432

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

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

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

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

454
  rc = 0;
455

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

462
  return rc;
×
463
}
464

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

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

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

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

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

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

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

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

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

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

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

UNCOV
614
    i += incr;
×
615
  }
616

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

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

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

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

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

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

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

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

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

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

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

UNCOV
766
    i += incr;
×
767
  }
768

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