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

neomutt / neomutt / 20704611763

04 Jan 2026 11:45AM UTC coverage: 45.271% (+1.1%) from 44.194%
20704611763

push

github

flatcap
notmuch: restore virtual-mailboxes

`virtual-mailboxes` has been dropped from the docs.

The preferred commands are:

- `mailboxes -label LABEL MAILBOX-PATH`
- `named-mailboxes LABEL MAILBOX-PATH`

11367 of 25109 relevant lines covered (45.27%)

291.36 hits per line

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

4.35
/pattern/message.c
1
/**
2
 * @file
3
 * Pattern handling for messages
4
 *
5
 * @authors
6
 * Copyright (C) 2023-2024 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2025 Thomas Klausner <wiz@gatalith.at>
8
 *
9
 * @copyright
10
 * This program is free software: you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License as published by the Free Software
12
 * Foundation, either version 2 of the License, or (at your option) any later
13
 * version.
14
 *
15
 * This program is distributed in the hope that it will be useful, but WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU General Public License along with
21
 * this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23

24
/**
25
 * @page pattern_message Pattern handling for messages
26
 *
27
 * Pattern handling for messages
28
 */
29

30
#include "config.h"
31
#include <stdbool.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include "private.h"
35
#include "mutt/lib.h"
36
#include "core/lib.h"
37
#include "lib.h"
38
#include "menu/lib.h"
39
#include "mview.h"
40

41
#define KILO 1024
42
#define MEGA 1048576
43

44
/**
45
 * enum EatRangeError - Error codes for eat_range_by_regex()
46
 */
47
enum EatRangeError
48
{
49
  RANGE_E_OK,     ///< Range is valid
50
  RANGE_E_SYNTAX, ///< Range contains syntax error
51
  RANGE_E_MVIEW,  ///< Range requires MailboxView, but none available
52
};
53

54
/**
55
 * report_regerror - Create a regex error message
56
 * @param regerr Regex error code
57
 * @param preg   Regex pattern buffer
58
 * @param err    Buffer for error messages
59
 * @retval #RANGE_E_SYNTAX Always
60
 */
61
static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
×
62
{
63
  size_t ds = err->dsize;
×
64

65
  if (regerror(regerr, preg, err->data, ds) > ds)
×
66
    mutt_debug(LL_DEBUG2, "warning: buffer too small for regerror\n");
×
67
  /* The return value is fixed, exists only to shorten code at callsite */
68
  return RANGE_E_SYNTAX;
×
69
}
70

71
/**
72
 * is_menu_available - Do we need a MailboxView for this Pattern?
73
 * @param s      String to check
74
 * @param pmatch Regex matches
75
 * @param kind   Range type, e.g. #RANGE_K_REL
76
 * @param err    Buffer for error messages
77
 * @param menu   Current Menu
78
 * @retval false MailboxView is required, but not available
79
 * @retval true  Otherwise
80
 */
81
static bool is_menu_available(struct Buffer *s, regmatch_t pmatch[], int kind,
×
82
                              struct Buffer *err, const struct Menu *menu)
83
{
84
  const char *context_req_chars[] = {
×
85
    [RANGE_K_REL] = ".0123456789",
86
    [RANGE_K_ABS] = ".",
87
    [RANGE_K_LT] = "",
88
    [RANGE_K_GT] = "",
89
    [RANGE_K_BARE] = ".",
90
  };
91

92
  /* First decide if we're going to need the menu at all.
93
   * Relative patterns need it if they contain a dot or a number.
94
   * Absolute patterns only need it if they contain a dot. */
95
  char *context_loc = strpbrk(s->dptr + pmatch[0].rm_so, context_req_chars[kind]);
×
96
  if (!context_loc || (context_loc >= &s->dptr[pmatch[0].rm_eo]))
×
97
    return true;
98

99
  /* We need a current message.  Do we actually have one? */
100
  if (menu)
×
101
    return true;
102

103
  /* Nope. */
104
  buf_strcpy(err, _("No current message"));
×
105
  return false;
×
106
}
107

108
/**
109
 * scan_range_num - Parse a number range
110
 * @param s      String to parse
111
 * @param pmatch Array of regex matches
112
 * @param group  Index of regex match to use
113
 * @param kind   Range type, e.g. #RANGE_K_REL
114
 * @param mv     Mailbox view
115
 * @retval num Parse number
116
 */
117
static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group,
×
118
                          int kind, struct MailboxView *mv)
119
{
120
  int num = (int) strtol(&s->dptr[pmatch[group].rm_so], NULL, 0);
×
121
  unsigned char c = (unsigned char) (s->dptr[pmatch[group].rm_eo - 1]);
×
122
  if (mutt_toupper(c) == 'K')
×
123
    num *= KILO;
×
124
  else if (mutt_toupper(c) == 'M')
×
125
    num *= MEGA;
×
126
  switch (kind)
×
127
  {
128
    case RANGE_K_REL:
×
129
    {
130
      struct Mailbox *m = mv->mailbox;
×
131
      struct Menu *menu = mv->menu;
×
132
      struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
×
133
      if (!e)
×
134
        return num;
135
      return num + email_msgno(e);
×
136
    }
137
    case RANGE_K_LT:
×
138
      return num - 1;
×
139
    case RANGE_K_GT:
×
140
      return num + 1;
×
141
    default:
142
      return num;
143
  }
144
}
145

146
/**
147
 * scan_range_slot - Parse a range of message numbers
148
 * @param s      String to parse
149
 * @param pmatch Regex matches
150
 * @param grp    Which regex match to use
151
 * @param side   Which side of the range is this?  #RANGE_S_LEFT or #RANGE_S_RIGHT
152
 * @param kind   Range type, e.g. #RANGE_K_REL
153
 * @param mv     Mailbox view
154
 * @retval num Index number for the message specified
155
 */
156
static int scan_range_slot(struct Buffer *s, regmatch_t pmatch[], int grp,
×
157
                           int side, int kind, struct MailboxView *mv)
158
{
159
  struct Mailbox *m = mv->mailbox;
×
160
  struct Menu *menu = mv->menu;
×
161

162
  /* This means the left or right subpattern was empty, e.g. ",." */
163
  if ((pmatch[grp].rm_so == -1) || (pmatch[grp].rm_so == pmatch[grp].rm_eo))
×
164
  {
165
    if (side == RANGE_S_LEFT)
×
166
      return 1;
167
    if (side == RANGE_S_RIGHT)
×
168
      return m->msg_count;
×
169
  }
170
  /* We have something, so determine what */
171
  unsigned char c = (unsigned char) (s->dptr[pmatch[grp].rm_so]);
×
172
  switch (c)
×
173
  {
174
    case RANGE_CIRCUM:
175
      return 1;
176
    case RANGE_DOLLAR:
×
177
      return m->msg_count;
×
178
    case RANGE_DOT:
×
179
    {
180
      struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
×
181
      if (!e)
×
182
        return 1;
183
      return email_msgno(e);
×
184
    }
185
    case RANGE_LT:
×
186
    case RANGE_GT:
187
      return scan_range_num(s, pmatch, grp + 1, kind, mv);
×
188
    default:
×
189
      /* Only other possibility: a number */
190
      return scan_range_num(s, pmatch, grp, kind, mv);
×
191
  }
192
}
193

194
/**
195
 * order_range - Put a range in order
196
 * @param pat Pattern to check
197
 */
198
static void order_range(struct Pattern *pat)
199
{
200
  if (pat->min <= pat->max)
×
201
    return;
202
  long num = pat->min;
203
  pat->min = pat->max;
×
204
  pat->max = num;
×
205
}
206

207
/**
208
 * eat_range_by_regex - Parse a range given as a regex
209
 * @param pat  Pattern to store the range in
210
 * @param s    String to parse
211
 * @param kind Range type, e.g. #RANGE_K_REL
212
 * @param err  Buffer for error messages
213
 * @param mv   Mailbox view
214
 * @retval num EatRangeError code, e.g. #RANGE_E_OK
215
 */
216
static int eat_range_by_regex(struct Pattern *pat, struct Buffer *s, int kind,
×
217
                              struct Buffer *err, struct MailboxView *mv)
218
{
219
  int regerr;
220
  regmatch_t pmatch[RANGE_RX_GROUPS] = { 0 };
×
221
  struct RangeRegex *pspec = &RangeRegexes[kind];
222

223
  /* First time through, compile the big regex */
224
  if (!pspec->ready)
×
225
  {
226
    regerr = regcomp(&pspec->cooked, pspec->raw, REG_EXTENDED);
×
227
    if (regerr != 0)
×
228
      return report_regerror(regerr, &pspec->cooked, err);
×
229
    pspec->ready = true;
×
230
  }
231

232
  /* Match the pattern buffer against the compiled regex.
233
   * No match means syntax error. */
234
  regerr = regexec(&pspec->cooked, s->dptr, RANGE_RX_GROUPS, pmatch, 0);
×
235
  if (regerr != 0)
×
236
    return report_regerror(regerr, &pspec->cooked, err);
×
237

238
  struct Mailbox *m = mv->mailbox;
×
239
  struct Menu *menu = mv->menu;
×
240
  if (!is_menu_available(s, pmatch, kind, err, menu))
×
241
    return RANGE_E_MVIEW;
242

243
  /* Snarf the contents of the two sides of the range. */
244
  pat->min = scan_range_slot(s, pmatch, pspec->lgrp, RANGE_S_LEFT, kind, mv);
×
245
  pat->max = scan_range_slot(s, pmatch, pspec->rgrp, RANGE_S_RIGHT, kind, mv);
×
246
  mutt_debug(LL_DEBUG1, "pat->min=%ld pat->max=%ld\n", pat->min, pat->max);
×
247

248
  /* Special case for a bare 0. */
249
  if ((kind == RANGE_K_BARE) && (pat->min == 0) && (pat->max == 0))
×
250
  {
251
    if (!m || !menu)
×
252
    {
253
      buf_strcpy(err, _("No current message"));
×
254
      return RANGE_E_MVIEW;
×
255
    }
256
    struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
×
257
    if (!e)
×
258
      return RANGE_E_MVIEW;
259

260
    pat->max = email_msgno(e);
×
261
    pat->min = pat->max;
×
262
  }
263

264
  /* Since we don't enforce order, we must swap bounds if they're backward */
265
  order_range(pat);
266

267
  /* Slide pointer past the entire match. */
268
  s->dptr += pmatch[0].rm_eo;
×
269
  return RANGE_E_OK;
×
270
}
271

272
/**
273
 * eat_message_range - Parse a range of message numbers - Implements ::eat_arg_t - @ingroup eat_arg_api
274
 * @param pat   Pattern to store the results in
275
 * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
276
 * @param s     String to parse
277
 * @param err   Buffer for error messages
278
 * @param mv    Mailbox view
279
 * @retval true The pattern was read successfully
280
 */
281
bool eat_message_range(struct Pattern *pat, PatternCompFlags flags,
2✔
282
                       struct Buffer *s, struct Buffer *err, struct MailboxView *mv)
283
{
284
  if (!mv || !mv->mailbox || !mv->menu)
2✔
285
  {
286
    // We need these for pretty much anything
287
    buf_strcpy(err, _("No mailbox is open"));
2✔
288
    return false;
2✔
289
  }
290

291
  bool skip_quote = false;
292

293
  /* If simple_search is set to "~m %s", the range will have double quotes
294
   * around it...  */
295
  if (*s->dptr == '"')
×
296
  {
297
    s->dptr++;
×
298
    skip_quote = true;
299
  }
300

301
  for (int i_kind = 0; i_kind != RANGE_K_INVALID; i_kind++)
×
302
  {
303
    switch (eat_range_by_regex(pat, s, i_kind, err, mv))
×
304
    {
305
      case RANGE_E_MVIEW:
306
        /* This means it matched syntactically but lacked context.
307
         * No point in continuing. */
308
        break;
309
      case RANGE_E_SYNTAX:
×
310
        /* Try another syntax, then */
311
        continue;
×
312
      case RANGE_E_OK:
×
313
        if (skip_quote && (*s->dptr == '"'))
×
314
          s->dptr++;
×
315
        SKIPWS(s->dptr);
×
316
        return true;
317
    }
318
  }
319
  return false;
320
}
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