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

neomutt / neomutt / 18829067163

26 Oct 2025 12:48PM UTC coverage: 50.06%. Remained the same
18829067163

push

github

flatcap
trivial tidying

- drop unused OP_PARTIAL_KEY
- debug: missing name
- doxy: fix headers

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

63.53
/color/regex.c
1
/**
2
 * @file
3
 * Regex Colour
4
 *
5
 * @authors
6
 * Copyright (C) 2021-2023 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2022 Pietro Cerutti <gahr@gahr.ch>
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 color_regex Regex Colour
26
 *
27
 * A set of regexes and colours that should be applied to a graphical object,
28
 * e.g Body of an Email.
29
 */
30

31
#include "config.h"
32
#include <stdbool.h>
33
#include <stddef.h>
34
#include <stdint.h>
35
#include "mutt/lib.h"
36
#include "config/lib.h"
37
#include "core/lib.h"
38
#include "index/lib.h"
39
#include "pattern/lib.h"
40
#include "attr.h"
41
#include "color.h"
42
#include "commands.h"
43
#include "debug.h"
44
#include "notify2.h"
45
#include "regex4.h"
46

47
// clang-format off
48
struct RegexColorList AttachList;         ///< List of colours applied to the attachment headers
49
struct RegexColorList BodyList;           ///< List of colours applied to the email body
50
struct RegexColorList HeaderList;         ///< List of colours applied to the email headers
51
struct RegexColorList IndexAuthorList;    ///< List of colours applied to the author in the index
52
struct RegexColorList IndexCollapsedList; ///< List of colours applied to a collapsed thread in the index
53
struct RegexColorList IndexDateList;      ///< List of colours applied to the date in the index
54
struct RegexColorList IndexFlagsList;     ///< List of colours applied to the flags in the index
55
struct RegexColorList IndexLabelList;     ///< List of colours applied to the label in the index
56
struct RegexColorList IndexList;          ///< List of default colours applied to the index
57
struct RegexColorList IndexNumberList;    ///< List of colours applied to the message number in the index
58
struct RegexColorList IndexSizeList;      ///< List of colours applied to the size in the index
59
struct RegexColorList IndexSubjectList;   ///< List of colours applied to the subject in the index
60
struct RegexColorList IndexTagList;       ///< List of colours applied to tags in the index
61
struct RegexColorList IndexTagsList;      ///< List of colours applied to the tags in the index
62
struct RegexColorList StatusList;         ///< List of colours applied to the status bar
63
// clang-format on
64

65
/**
66
 * regex_colors_init - Initialise the Regex colours
67
 */
68
void regex_colors_init(void)
1✔
69
{
70
  color_debug(LL_DEBUG5, "init AttachList, BodyList, etc\n");
71
  STAILQ_INIT(&AttachList);
1✔
72
  STAILQ_INIT(&BodyList);
1✔
73
  STAILQ_INIT(&HeaderList);
1✔
74
  STAILQ_INIT(&IndexAuthorList);
1✔
75
  STAILQ_INIT(&IndexCollapsedList);
1✔
76
  STAILQ_INIT(&IndexDateList);
1✔
77
  STAILQ_INIT(&IndexLabelList);
1✔
78
  STAILQ_INIT(&IndexNumberList);
1✔
79
  STAILQ_INIT(&IndexSizeList);
1✔
80
  STAILQ_INIT(&IndexTagsList);
1✔
81
  STAILQ_INIT(&IndexFlagsList);
1✔
82
  STAILQ_INIT(&IndexList);
1✔
83
  STAILQ_INIT(&IndexSubjectList);
1✔
84
  STAILQ_INIT(&IndexTagList);
1✔
85
  STAILQ_INIT(&StatusList);
1✔
86
}
1✔
87

88
/**
89
 * regex_colors_reset - Reset the Regex colours
90
 */
91
void regex_colors_reset(void)
2✔
92
{
93
  color_debug(LL_DEBUG5, "reset regex\n");
94
  regex_color_list_clear(&AttachList);
2✔
95
  regex_color_list_clear(&BodyList);
2✔
96
  regex_color_list_clear(&HeaderList);
2✔
97
  regex_color_list_clear(&IndexList);
2✔
98
  regex_color_list_clear(&IndexAuthorList);
2✔
99
  regex_color_list_clear(&IndexCollapsedList);
2✔
100
  regex_color_list_clear(&IndexDateList);
2✔
101
  regex_color_list_clear(&IndexLabelList);
2✔
102
  regex_color_list_clear(&IndexNumberList);
2✔
103
  regex_color_list_clear(&IndexSizeList);
2✔
104
  regex_color_list_clear(&IndexTagsList);
2✔
105
  regex_color_list_clear(&IndexFlagsList);
2✔
106
  regex_color_list_clear(&IndexSubjectList);
2✔
107
  regex_color_list_clear(&IndexTagList);
2✔
108
  regex_color_list_clear(&StatusList);
2✔
109
}
2✔
110

111
/**
112
 * regex_colors_cleanup - Cleanup the Regex colours
113
 */
114
void regex_colors_cleanup(void)
2✔
115
{
116
  regex_colors_reset();
2✔
117
}
2✔
118

119
/**
120
 * regex_color_clear - Free the contents of a Regex colour
121
 * @param rcol RegexColor to empty
122
 *
123
 * @note The RegexColor object isn't freed
124
 */
125
void regex_color_clear(struct RegexColor *rcol)
×
126
{
127
  if (!rcol)
×
128
    return;
129

130
  rcol->match = 0;
×
131
  rcol->stop_matching = false;
×
132

133
  attr_color_clear(&rcol->attr_color);
×
134
  FREE(&rcol->pattern);
×
135
  regfree(&rcol->regex);
×
136
  mutt_pattern_free(&rcol->color_pattern);
×
137
}
138

139
/**
140
 * regex_color_free - Free a Regex colour
141
 * @param ptr RegexColor to free
142
 */
143
void regex_color_free(struct RegexColor **ptr)
×
144
{
145
  if (!ptr || !*ptr)
×
146
    return;
147

148
  struct RegexColor *rcol = *ptr;
149
  regex_color_clear(rcol);
×
150

151
  FREE(ptr);
×
152
}
153

154
/**
155
 * regex_color_new - Create a new RegexColor
156
 * @retval ptr New RegexColor
157
 */
158
struct RegexColor *regex_color_new(void)
×
159
{
160
  return MUTT_MEM_CALLOC(1, struct RegexColor);
4✔
161
}
162

163
/**
164
 * regex_color_list_new - Create a new RegexColorList
165
 * @retval ptr New RegexColorList
166
 */
167
struct RegexColorList *regex_color_list_new(void)
×
168
{
169
  struct RegexColorList *rcl = MUTT_MEM_CALLOC(1, struct RegexColorList);
×
170

171
  STAILQ_INIT(rcl);
×
172

173
  struct RegexColor *rcol = regex_color_new();
174
  STAILQ_INSERT_TAIL(rcl, rcol, entries);
×
175

176
  return rcl;
×
177
}
178

179
/**
180
 * regex_color_list_clear - Free the contents of a RegexColorList
181
 * @param rcl List to clear
182
 *
183
 * Free each of the RegexColorList in a list.
184
 *
185
 * @note The list object isn't freed, only emptied
186
 */
187
void regex_color_list_clear(struct RegexColorList *rcl)
30✔
188
{
189
  if (!rcl)
30✔
190
    return;
×
191

192
  struct RegexColor *np = NULL, *tmp = NULL;
193
  STAILQ_FOREACH_SAFE(np, rcl, entries, tmp)
30✔
194
  {
195
    STAILQ_REMOVE(rcl, np, RegexColor, entries);
×
196
    regex_color_free(&np);
×
197
  }
198
}
199

200
/**
201
 * regex_colors_get_list - Return the RegexColorList for a Colour ID
202
 * @param cid Colour ID, e.g. #MT_COLOR_BODY
203
 * @retval ptr RegexColorList
204
 */
205
struct RegexColorList *regex_colors_get_list(enum ColorId cid)
32✔
206
{
207
  switch (cid)
32✔
208
  {
209
    case MT_COLOR_ATTACH_HEADERS:
210
      return &AttachList;
211
    case MT_COLOR_BODY:
4✔
212
      return &BodyList;
4✔
213
    case MT_COLOR_HEADER:
2✔
214
      return &HeaderList;
2✔
215
    case MT_COLOR_INDEX:
2✔
216
      return &IndexList;
2✔
217
    case MT_COLOR_INDEX_AUTHOR:
2✔
218
      return &IndexAuthorList;
2✔
219
    case MT_COLOR_INDEX_COLLAPSED:
2✔
220
      return &IndexCollapsedList;
2✔
221
    case MT_COLOR_INDEX_DATE:
2✔
222
      return &IndexDateList;
2✔
223
    case MT_COLOR_INDEX_FLAGS:
2✔
224
      return &IndexFlagsList;
2✔
225
    case MT_COLOR_INDEX_LABEL:
2✔
226
      return &IndexLabelList;
2✔
227
    case MT_COLOR_INDEX_NUMBER:
2✔
228
      return &IndexNumberList;
2✔
229
    case MT_COLOR_INDEX_SIZE:
2✔
230
      return &IndexSizeList;
2✔
231
    case MT_COLOR_INDEX_SUBJECT:
2✔
232
      return &IndexSubjectList;
2✔
233
    case MT_COLOR_INDEX_TAG:
2✔
234
      return &IndexTagList;
2✔
235
    case MT_COLOR_INDEX_TAGS:
2✔
236
      return &IndexTagsList;
2✔
237
    case MT_COLOR_STATUS:
2✔
238
      return &StatusList;
2✔
239
    default:
×
240
      return NULL;
×
241
  }
242
}
243

244
/**
245
 * add_pattern - Associate a colour to a pattern
246
 * @param rcl       List of existing colours
247
 * @param s         String to match
248
 * @param ac_val    Colour value to use
249
 * @param err       Buffer for error messages
250
 * @param is_index  true of this is for the index
251
 * @param match     Number of regex subexpression to match (0 for entire pattern)
252
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
253
 *
254
 * is_index used to store compiled pattern only for 'index' color object when
255
 * called from parse_color()
256
 */
257
static enum CommandResult add_pattern(struct RegexColorList *rcl, const char *s,
4✔
258
                                      struct AttrColor *ac_val,
259
                                      struct Buffer *err, bool is_index, int match)
260
{
261
  struct RegexColor *rcol = NULL;
262

263
  STAILQ_FOREACH(rcol, rcl, entries)
6✔
264
  {
265
    if (mutt_str_equal(s, rcol->pattern))
2✔
266
      break;
267
  }
268

269
  if (rcol) // found a matching regex
4✔
270
  {
271
    struct AttrColor *ac = &rcol->attr_color;
×
272
    attr_color_overwrite(ac, ac_val);
×
273
  }
274
  else
275
  {
276
    rcol = regex_color_new();
4✔
277
    if (is_index)
4✔
278
    {
279
      struct Buffer *buf = buf_pool_get();
×
280
      buf_strcpy(buf, s);
×
281
      const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
282
      mutt_check_simple(buf, NONULL(c_simple_search));
×
283
      struct MailboxView *mv_cur = get_current_mailbox_view();
×
284
      rcol->color_pattern = mutt_pattern_comp(mv_cur, buf_string(buf), MUTT_PC_FULL_MSG, err);
×
285
      buf_pool_release(&buf);
×
286
      if (!rcol->color_pattern)
×
287
      {
288
        regex_color_free(&rcol);
×
289
        return MUTT_CMD_ERROR;
×
290
      }
291
    }
292
    else
293
    {
294
      // Smart case matching
295
      uint16_t flags = mutt_mb_is_lower(s) ? REG_ICASE : 0;
4✔
296

297
      const int r = REG_COMP(&rcol->regex, s, flags);
4✔
298
      if (r != 0)
4✔
299
      {
300
        regerror(r, &rcol->regex, err->data, err->dsize);
×
301
        regex_color_free(&rcol);
×
302
        return MUTT_CMD_ERROR;
×
303
      }
304
    }
305
    rcol->pattern = mutt_str_dup(s);
4✔
306
    rcol->match = match;
4✔
307

308
    struct AttrColor *ac = &rcol->attr_color;
4✔
309

310
    attr_color_overwrite(ac, ac_val);
4✔
311

312
    STAILQ_INSERT_TAIL(rcl, rcol, entries);
4✔
313
  }
314

315
  if (is_index)
4✔
316
  {
317
    /* force re-caching of index colors */
318
    struct EventColor ev_c = { MT_COLOR_INDEX, NULL };
×
319
    notify_send(ColorsNotify, NT_COLOR, NT_COLOR_SET, &ev_c);
×
320
  }
321

322
  return MUTT_CMD_SUCCESS;
323
}
324

325
/**
326
 * regex_colors_parse_color_list - Parse a Regex 'color' command
327
 * @param cid     Colour ID, should be #MT_COLOR_STATUS
328
 * @param pat     Regex pattern
329
 * @param ac      Colour value to use
330
 * @param rc      Return code, e.g. #MUTT_CMD_SUCCESS
331
 * @param err     Buffer for error messages
332
 * @retval true Colour was parsed
333
 *
334
 * Parse a Regex 'color' command, e.g. "color index green default pattern"
335
 */
336
bool regex_colors_parse_color_list(enum ColorId cid, const char *pat,
2✔
337
                                   struct AttrColor *ac, int *rc, struct Buffer *err)
338

339
{
340
  if (cid == MT_COLOR_STATUS)
2✔
341
    return false;
342

343
  struct RegexColorList *rcl = regex_colors_get_list(cid);
2✔
344
  if (!rcl)
2✔
345
    return false;
346

347
  bool is_index = false;
348
  switch (cid)
2✔
349
  {
350
    case MT_COLOR_ATTACH_HEADERS:
351
    case MT_COLOR_BODY:
352
      break;
353
    case MT_COLOR_HEADER:
354
      break;
355
    case MT_COLOR_INDEX:
×
356
    case MT_COLOR_INDEX_AUTHOR:
357
    case MT_COLOR_INDEX_COLLAPSED:
358
    case MT_COLOR_INDEX_DATE:
359
    case MT_COLOR_INDEX_FLAGS:
360
    case MT_COLOR_INDEX_LABEL:
361
    case MT_COLOR_INDEX_NUMBER:
362
    case MT_COLOR_INDEX_SIZE:
363
    case MT_COLOR_INDEX_SUBJECT:
364
    case MT_COLOR_INDEX_TAG:
365
    case MT_COLOR_INDEX_TAGS:
366
      is_index = true;
367
      break;
×
368
    default:
369
      return false;
370
  }
371

372
  *rc = add_pattern(rcl, pat, ac, err, is_index, 0);
2✔
373

374
  struct Buffer *buf = buf_pool_get();
2✔
375
  get_colorid_name(cid, buf);
2✔
376
  color_debug(LL_DEBUG5, "NT_COLOR_SET: %s\n", buf_string(buf));
377
  buf_pool_release(&buf);
2✔
378

379
  if (!is_index) // else it will be logged in add_pattern()
2✔
380
  {
381
    struct EventColor ev_c = { cid, NULL };
2✔
382
    notify_send(ColorsNotify, NT_COLOR, NT_COLOR_SET, &ev_c);
2✔
383
  }
384

385
  return true;
386
}
387

388
/**
389
 * regex_colors_parse_status_list - Parse a Regex 'color status' command
390
 * @param cid     Colour ID, should be #MT_COLOR_STATUS
391
 * @param pat     Regex pattern
392
 * @param ac      Colour value to use
393
 * @param match   Use the nth regex submatch
394
 * @param err     Buffer for error messages
395
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
396
 */
397
int regex_colors_parse_status_list(enum ColorId cid, const char *pat,
2✔
398
                                   struct AttrColor *ac, int match, struct Buffer *err)
399
{
400
  if (cid != MT_COLOR_STATUS)
2✔
401
    return MUTT_CMD_ERROR;
402

403
  int rc = add_pattern(&StatusList, pat, ac, err, false, match);
2✔
404
  if (rc != MUTT_CMD_SUCCESS)
2✔
405
    return rc;
406

407
  struct Buffer *buf = buf_pool_get();
2✔
408
  get_colorid_name(cid, buf);
2✔
409
  color_debug(LL_DEBUG5, "NT_COLOR_SET: %s\n", buf_string(buf));
410
  buf_pool_release(&buf);
2✔
411

412
  struct EventColor ev_c = { cid, NULL };
2✔
413
  notify_send(ColorsNotify, NT_COLOR, NT_COLOR_SET, &ev_c);
2✔
414

415
  return rc;
2✔
416
}
417

418
/**
419
 * regex_colors_parse_uncolor - Parse a Regex 'uncolor' command
420
 * @param cid     Colour ID, e.g. #MT_COLOR_STATUS
421
 * @param pat     Pattern to remove (NULL to remove all)
422
 * @retval true If colours were unset
423
 */
424
bool regex_colors_parse_uncolor(enum ColorId cid, const char *pat)
×
425
{
426
  struct RegexColorList *cl = regex_colors_get_list(cid);
×
427
  if (!cl)
×
428
    return false;
429

430
  if (!pat) // Reset all patterns
×
431
  {
432
    if (STAILQ_EMPTY(cl))
×
433
      return true;
434

435
    mutt_debug(LL_NOTIFY, "NT_COLOR_RESET: [ALL]\n");
×
436
    struct EventColor ev_c = { cid, NULL };
×
437
    notify_send(ColorsNotify, NT_COLOR, NT_COLOR_RESET, &ev_c);
×
438

439
    regex_color_list_clear(cl);
×
440
    return true;
×
441
  }
442

443
  bool rc = false;
444
  struct RegexColor *np = NULL, *prev = NULL;
445
  prev = NULL;
446
  STAILQ_FOREACH(np, cl, entries)
×
447
  {
448
    if (mutt_str_equal(pat, np->pattern))
×
449
    {
450
      rc = true;
451

452
      color_debug(LL_DEBUG1, "Freeing pattern \"%s\" from XXX\n", pat);
453
      if (prev)
×
454
        STAILQ_REMOVE_AFTER(cl, prev, entries);
×
455
      else
456
        STAILQ_REMOVE_HEAD(cl, entries);
×
457

458
      mutt_debug(LL_NOTIFY, "NT_COLOR_RESET: XXX\n");
×
459
      struct EventColor ev_c = { cid, &np->attr_color };
×
460
      notify_send(ColorsNotify, NT_COLOR, NT_COLOR_RESET, &ev_c);
×
461

462
      regex_color_free(&np);
×
463
      break;
464
    }
465
    prev = np;
×
466
  }
467

468
  return rc;
469
}
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