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

neomutt / neomutt / 19023188702

01 Nov 2025 06:23PM UTC coverage: 50.058% (-0.002%) from 50.06%
19023188702

push

github

flatcap
merge: key: light refactoring

 * key: format the KeyNames table for clarity
 * key: split out the key-related commands
 * key: move prototypes to correct library
 * key: move config observer
 * key: move dump functions
 * key: eliminate MenuNamesLen

9127 of 18233 relevant lines covered (50.06%)

274.09 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
 *
8
 * @copyright
9
 * This program is free software: you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License as published by the Free Software
11
 * Foundation, either version 2 of the License, or (at your option) any later
12
 * version.
13
 *
14
 * This program is distributed in the hope that it will be useful, but WITHOUT
15
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17
 * details.
18
 *
19
 * You should have received a copy of the GNU General Public License along with
20
 * this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22

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

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

40
#define KILO 1024
41
#define MEGA 1048576
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

290
  bool skip_quote = false;
291

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

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