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

neomutt / neomutt / 12308706524

12 Dec 2024 04:06PM UTC coverage: 49.224% (-0.02%) from 49.243%
12308706524

push

github

flatcap
merge: colour refactoring

 * color: split out qstyle
 * color: move color_is_header()
 * color: factor out sidebar init
 * color: remove expando '%' hack
 * color: move notify parent
 * color: add macro COLOR_QUOTED()
 * color: make NumQuotedColors static
 * color: create a deprecated colours section
 * color: distinguish color_reset/cleanup()

31 of 210 new or added lines in 8 files covered. (14.76%)

2 existing lines in 1 file now uncovered.

8875 of 18030 relevant lines covered (49.22%)

232.72 hits per line

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

6.37
/color/command.c
1
/**
2
 * @file
3
 * Parse colour commands
4
 *
5
 * @authors
6
 * Copyright (C) 2021-2022 Pietro Cerutti <gahr@gahr.ch>
7
 * Copyright (C) 2021-2023 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24

25
/**
26
 * @page color_command Parse colour commands
27
 *
28
 * Parse NeoMutt 'color', 'uncolor', 'mono' and 'unmono' commands.
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 "gui/lib.h"
39
#include "mutt.h"
40
#include "parse/lib.h"
41
#include "attr.h"
42
#include "color.h"
43
#include "command2.h"
44
#include "debug.h"
45
#include "dump.h"
46
#include "globals.h"
47
#include "notify2.h"
48
#include "parse_color.h"
49
#include "quoted.h"
50
#include "regex4.h"
51
#include "simple2.h"
52

53
/**
54
 * ColorFields - Mapping of colour names to their IDs
55
 */
56
const struct Mapping ColorFields[] = {
57
  // clang-format off
58
  { "attachment",        MT_COLOR_ATTACHMENT },
59
  { "attach_headers",    MT_COLOR_ATTACH_HEADERS },
60
  { "body",              MT_COLOR_BODY },
61
  { "bold",              MT_COLOR_BOLD },
62
  { "error",             MT_COLOR_ERROR },
63
  { "hdrdefault",        MT_COLOR_HDRDEFAULT },
64
  { "header",            MT_COLOR_HEADER },
65
  { "index",             MT_COLOR_INDEX },
66
  { "index_author",      MT_COLOR_INDEX_AUTHOR },
67
  { "index_collapsed",   MT_COLOR_INDEX_COLLAPSED },
68
  { "index_date",        MT_COLOR_INDEX_DATE },
69
  { "index_flags",       MT_COLOR_INDEX_FLAGS },
70
  { "index_label",       MT_COLOR_INDEX_LABEL },
71
  { "index_number",      MT_COLOR_INDEX_NUMBER },
72
  { "index_size",        MT_COLOR_INDEX_SIZE },
73
  { "index_subject",     MT_COLOR_INDEX_SUBJECT },
74
  { "index_tag",         MT_COLOR_INDEX_TAG },
75
  { "index_tags",        MT_COLOR_INDEX_TAGS },
76
  { "indicator",         MT_COLOR_INDICATOR },
77
  { "italic",            MT_COLOR_ITALIC },
78
  { "markers",           MT_COLOR_MARKERS },
79
  { "message",           MT_COLOR_MESSAGE },
80
  { "normal",            MT_COLOR_NORMAL },
81
  { "options",           MT_COLOR_OPTIONS },
82
  { "progress",          MT_COLOR_PROGRESS },
83
  { "prompt",            MT_COLOR_PROMPT },
84
  { "quoted",            MT_COLOR_QUOTED },
85
  { "search",            MT_COLOR_SEARCH },
86
  { "sidebar_background", MT_COLOR_SIDEBAR_BACKGROUND },
87
  { "sidebar_divider",   MT_COLOR_SIDEBAR_DIVIDER },
88
  { "sidebar_flagged",   MT_COLOR_SIDEBAR_FLAGGED },
89
  { "sidebar_highlight", MT_COLOR_SIDEBAR_HIGHLIGHT },
90
  { "sidebar_indicator", MT_COLOR_SIDEBAR_INDICATOR },
91
  { "sidebar_new",       MT_COLOR_SIDEBAR_NEW },
92
  { "sidebar_ordinary",  MT_COLOR_SIDEBAR_ORDINARY },
93
  { "sidebar_spool_file", MT_COLOR_SIDEBAR_SPOOLFILE },
94
  { "sidebar_unread",    MT_COLOR_SIDEBAR_UNREAD },
95
  { "signature",         MT_COLOR_SIGNATURE },
96
  { "status",            MT_COLOR_STATUS },
97
  { "stripe_even",       MT_COLOR_STRIPE_EVEN},
98
  { "stripe_odd",        MT_COLOR_STRIPE_ODD},
99
  { "tilde",             MT_COLOR_TILDE },
100
  { "tree",              MT_COLOR_TREE },
101
  { "underline",         MT_COLOR_UNDERLINE },
102
  { "warning",           MT_COLOR_WARNING },
103
  // Deprecated
104
  { "sidebar_spoolfile", MT_COLOR_SIDEBAR_SPOOLFILE },
105
  { NULL, 0 },
106
  // clang-format on
107
};
108

109
/**
110
 * ComposeColorFields - Mapping of compose colour names to their IDs
111
 */
112
const struct Mapping ComposeColorFields[] = {
113
  // clang-format off
114
  { "header",            MT_COLOR_COMPOSE_HEADER },
115
  { "security_encrypt",  MT_COLOR_COMPOSE_SECURITY_ENCRYPT },
116
  { "security_sign",     MT_COLOR_COMPOSE_SECURITY_SIGN },
117
  { "security_both",     MT_COLOR_COMPOSE_SECURITY_BOTH },
118
  { "security_none",     MT_COLOR_COMPOSE_SECURITY_NONE },
119
  { NULL, 0 }
120
  // clang-format on
121
};
122

123
/**
124
 * get_colorid_name - Get the name of a color id
125
 * @param cid Colour, e.g. #MT_COLOR_HEADER
126
 * @param buf Buffer for result
127
 */
128
void get_colorid_name(unsigned int cid, struct Buffer *buf)
15✔
129
{
130
  const char *name = NULL;
131

132
  if ((cid >= MT_COLOR_COMPOSE_HEADER) && (cid <= MT_COLOR_COMPOSE_SECURITY_SIGN))
15✔
133
  {
134
    name = mutt_map_get_name(cid, ComposeColorFields);
2✔
135
    if (name)
2✔
136
    {
137
      buf_printf(buf, "compose %s", name);
2✔
138
      return;
2✔
139
    }
140
  }
141

142
  name = mutt_map_get_name(cid, ColorFields);
13✔
143
  if (name)
13✔
144
    buf_addstr(buf, name);
12✔
145
  else
146
    buf_printf(buf, "UNKNOWN %d", cid);
1✔
147
}
148

149
/**
150
 * parse_object - Identify a colour object
151
 * @param[in]  buf   Temporary Buffer space
152
 * @param[in]  s     Buffer containing string to be parsed
153
 * @param[out] cid   Object type, e.g. #MT_COLOR_TILDE
154
 * @param[out] ql    Quote level, if type #MT_COLOR_QUOTED
155
 * @param[out] err   Buffer for error messages
156
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
157
 *
158
 * Identify a colour object, e.g. "quoted", "compose header"
159
 */
160
static enum CommandResult parse_object(struct Buffer *buf, struct Buffer *s,
×
161
                                       enum ColorId *cid, int *ql, struct Buffer *err)
162
{
163
  int rc;
164

165
  if (mutt_str_startswith(buf->data, "quoted") != 0)
×
166
  {
167
    int val = 0;
×
168
    if (buf->data[6] != '\0')
×
169
    {
170
      if (!mutt_str_atoi_full(buf->data + 6, &val) || (val > COLOR_QUOTES_MAX))
×
171
      {
172
        buf_printf(err, _("%s: no such object"), buf->data);
×
173
        return MUTT_CMD_WARNING;
×
174
      }
175
    }
176

177
    *ql = val;
×
178
    *cid = MT_COLOR_QUOTED;
×
179
    return MUTT_CMD_SUCCESS;
×
180
  }
181

182
  if (mutt_istr_equal(buf->data, "compose"))
×
183
  {
184
    if (!MoreArgs(s))
×
185
    {
186
      buf_printf(err, _("%s: too few arguments"), "color");
×
187
      return MUTT_CMD_WARNING;
×
188
    }
189

190
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
191

192
    rc = mutt_map_get_value(buf->data, ComposeColorFields);
×
193
    if (rc == -1)
×
194
    {
195
      buf_printf(err, _("%s: no such object"), buf->data);
×
196
      return MUTT_CMD_WARNING;
×
197
    }
198

199
    *cid = rc;
×
200
    return MUTT_CMD_SUCCESS;
×
201
  }
202

203
  rc = mutt_map_get_value(buf->data, ColorFields);
×
204
  if (rc == -1)
×
205
  {
206
    buf_printf(err, _("%s: no such object"), buf->data);
×
207
    return MUTT_CMD_WARNING;
×
208
  }
209
  else
210
  {
211
    color_debug(LL_DEBUG5, "object: %s\n", mutt_map_get_name(rc, ColorFields));
×
212
  }
213

214
  *cid = rc;
×
215
  return MUTT_CMD_SUCCESS;
×
216
}
217

218
/**
219
 * parse_uncolor - Parse an 'uncolor' command
220
 * @param buf     Temporary Buffer space
221
 * @param s       Buffer containing string to be parsed
222
 * @param err     Buffer for error messages
223
 * @param uncolor If true, 'uncolor', else 'unmono'
224
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
225
 *
226
 * Usage:
227
 * * uncolor index pattern [pattern...]
228
 * * unmono  index pattern [pattern...]
229
 */
230
static enum CommandResult parse_uncolor(struct Buffer *buf, struct Buffer *s,
×
231
                                        struct Buffer *err, bool uncolor)
232
{
233
  if (OptNoCurses) // No GUI, so quietly discard the command
×
234
  {
235
    while (MoreArgs(s))
×
236
    {
237
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
238
    }
239
    return MUTT_CMD_SUCCESS;
240
  }
241

242
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
243

244
  if (mutt_str_equal(buf->data, "*"))
×
245
  {
NEW
246
    colors_reset();
×
247
    return MUTT_CMD_SUCCESS;
×
248
  }
249

250
  unsigned int cid = MT_COLOR_NONE;
×
251
  int ql = 0;
×
252
  color_debug(LL_DEBUG5, "uncolor: %s\n", buf_string(buf));
253
  enum CommandResult rc = parse_object(buf, s, &cid, &ql, err);
×
254
  if (rc != MUTT_CMD_SUCCESS)
×
255
    return rc;
256

257
  if (cid == -1)
×
258
  {
259
    buf_printf(err, _("%s: no such object"), buf->data);
×
260
    return MUTT_CMD_ERROR;
×
261
  }
262

NEW
263
  if (COLOR_QUOTED(cid))
×
264
  {
265
    color_debug(LL_DEBUG5, "quoted\n");
266
    return quoted_colors_parse_uncolor(cid, ql, err);
×
267
  }
268

269
  if ((cid == MT_COLOR_STATUS) && !MoreArgs(s))
×
270
  {
271
    color_debug(LL_DEBUG5, "simple\n");
272
    simple_color_reset(cid); // default colour for the status bar
×
273
    return MUTT_CMD_SUCCESS;
×
274
  }
275

276
  if (!mutt_color_has_pattern(cid))
×
277
  {
278
    color_debug(LL_DEBUG5, "simple\n");
279
    simple_color_reset(cid);
×
280
    return MUTT_CMD_SUCCESS;
×
281
  }
282

283
  if (!MoreArgs(s))
×
284
  {
285
    if (regex_colors_parse_uncolor(cid, NULL, uncolor))
×
286
      return MUTT_CMD_SUCCESS;
287
    else
288
      return MUTT_CMD_ERROR;
289
  }
290

291
  do
292
  {
293
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
294
    if (mutt_str_equal("*", buf->data))
×
295
    {
296
      if (regex_colors_parse_uncolor(cid, NULL, uncolor))
×
297
        return MUTT_CMD_SUCCESS;
298
      else
299
        return MUTT_CMD_ERROR;
300
    }
301

302
    regex_colors_parse_uncolor(cid, buf->data, uncolor);
×
303

304
  } while (MoreArgs(s));
×
305

306
  return MUTT_CMD_SUCCESS;
307
}
308

309
/**
310
 * parse_color - Parse a 'color' command
311
 * @param buf      Temporary Buffer space
312
 * @param s        Buffer containing string to be parsed
313
 * @param err      Buffer for error messages
314
 * @param callback Function to handle command - Implements ::parser_callback_t
315
 * @param color    If true "color", else "mono"
316
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
317
 *
318
 * Usage:
319
 * * color OBJECT [ ATTRS ] FG BG [ REGEX ]
320
 * * mono  OBJECT   ATTRS         [ REGEX ]
321
 */
322
static enum CommandResult parse_color(struct Buffer *buf, struct Buffer *s,
×
323
                                      struct Buffer *err,
324
                                      parser_callback_t callback, bool color)
325
{
326
  int q_level = 0;
×
327
  unsigned int match = 0;
×
328
  enum ColorId cid = MT_COLOR_NONE;
×
329
  enum CommandResult rc = MUTT_CMD_ERROR;
×
330
  struct AttrColor *ac = NULL;
×
331

332
  if (!MoreArgs(s))
×
333
  {
334
    if (StartupComplete)
×
335
    {
336
      color_dump();
×
337
      rc = MUTT_CMD_SUCCESS;
×
338
    }
339
    else
340
    {
341
      buf_printf(err, _("%s: too few arguments"), color ? "color" : "mono");
×
342
      rc = MUTT_CMD_WARNING;
×
343
    }
344

345
    goto done;
×
346
  }
347

348
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
349
  color_debug(LL_DEBUG5, "color: %s\n", buf_string(buf));
350

351
  rc = parse_object(buf, s, &cid, &q_level, err);
×
352
  if (rc != MUTT_CMD_SUCCESS)
×
353
    goto done;
×
354

355
  ac = attr_color_new();
×
356
  rc = callback(buf, s, ac, err);
×
357
  if (rc != MUTT_CMD_SUCCESS)
×
358
    goto done;
×
359

360
  //------------------------------------------------------------------
361
  // Business Logic
362

363
  if ((ac->fg.type == CT_RGB) || (ac->bg.type == CT_RGB))
×
364
  {
365
#ifndef NEOMUTT_DIRECT_COLORS
366
    buf_printf(err, _("Direct colors support not compiled in: %s"), buf_string(s));
367
    return MUTT_CMD_ERROR;
368
#endif
369

370
    const bool c_color_directcolor = cs_subset_bool(NeoMutt->sub, "color_directcolor");
×
371
    if (!c_color_directcolor)
×
372
    {
373
      buf_printf(err, _("Direct colors support disabled: %s"), buf_string(s));
×
374
      return MUTT_CMD_ERROR;
×
375
    }
376
  }
377

378
  if ((ac->fg.color >= COLORS) || (ac->bg.color >= COLORS))
×
379
  {
380
    buf_printf(err, _("%s: color not supported by term"), buf_string(s));
×
381
    return MUTT_CMD_ERROR;
×
382
  }
383

384
  //------------------------------------------------------------------
385

386
  /* extract a regular expression if needed */
387

388
  if (mutt_color_has_pattern(cid) && (cid != MT_COLOR_STATUS))
×
389
  {
390
    color_debug(LL_DEBUG5, "regex needed\n");
391
    if (MoreArgs(s))
×
392
    {
393
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
394
    }
395
    else
396
    {
397
      buf_strcpy(buf, ".*");
×
398
    }
399
  }
400

401
  if (MoreArgs(s) && (cid != MT_COLOR_STATUS))
×
402
  {
403
    buf_printf(err, _("%s: too many arguments"), color ? "color" : "mono");
×
404
    rc = MUTT_CMD_WARNING;
×
405
    goto done;
×
406
  }
407

408
  if (regex_colors_parse_color_list(cid, buf->data, ac, &rc, err))
×
409
  {
410
    color_debug(LL_DEBUG5, "regex_colors_parse_color_list done\n");
411
    goto done;
×
412
    // do nothing
413
  }
414
  else if (quoted_colors_parse_color(cid, ac, q_level, &rc, err))
×
415
  {
416
    color_debug(LL_DEBUG5, "quoted_colors_parse_color done\n");
417
    goto done;
×
418
    // do nothing
419
  }
420
  else if ((cid == MT_COLOR_STATUS) && MoreArgs(s))
×
421
  {
422
    color_debug(LL_DEBUG5, "status\n");
423
    /* 'color status fg bg' can have up to 2 arguments:
424
     * 0 arguments: sets the default status color (handled below by else part)
425
     * 1 argument : colorize pattern on match
426
     * 2 arguments: colorize nth submatch of pattern */
427
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
428

429
    if (MoreArgs(s))
×
430
    {
431
      struct Buffer *tmp = buf_pool_get();
×
432
      parse_extract_token(tmp, s, TOKEN_NO_FLAGS);
×
433
      if (!mutt_str_atoui_full(tmp->data, &match))
×
434
      {
435
        buf_printf(err, _("%s: invalid number: %s"), color ? "color" : "mono", tmp->data);
×
436
        buf_pool_release(&tmp);
×
437
        rc = MUTT_CMD_WARNING;
×
438
        goto done;
×
439
      }
440
      buf_pool_release(&tmp);
×
441
    }
442

443
    if (MoreArgs(s))
×
444
    {
445
      buf_printf(err, _("%s: too many arguments"), color ? "color" : "mono");
×
446
      rc = MUTT_CMD_WARNING;
×
447
      goto done;
×
448
    }
449

450
    rc = regex_colors_parse_status_list(cid, buf->data, ac, match, err);
×
451
    goto done;
×
452
  }
453
  else // Remaining simple colours
454
  {
455
    color_debug(LL_DEBUG5, "simple\n");
456
    if (simple_color_set(cid, ac))
×
457
      rc = MUTT_CMD_SUCCESS;
×
458
    else
459
      rc = MUTT_CMD_ERROR;
×
460
  }
461

462
  if (rc == MUTT_CMD_SUCCESS)
×
463
  {
464
    get_colorid_name(cid, buf);
×
465
    color_debug(LL_DEBUG5, "NT_COLOR_SET: %s\n", buf->data);
466
    struct EventColor ev_c = { cid, NULL };
×
467
    notify_send(ColorsNotify, NT_COLOR, NT_COLOR_SET, &ev_c);
×
468
  }
469

470
done:
×
471
  attr_color_free(&ac);
×
472
  return rc;
×
473
}
474

475
/**
476
 * mutt_parse_uncolor - Parse the 'uncolor' command - Implements Command::parse() - @ingroup command_parse
477
 */
478
enum CommandResult mutt_parse_uncolor(struct Buffer *buf, struct Buffer *s,
×
479
                                      intptr_t data, struct Buffer *err)
480
{
481
  if (OptNoCurses) // No GUI, so quietly discard the command
×
482
  {
483
    while (MoreArgs(s))
×
484
    {
485
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
486
    }
487
    return MUTT_CMD_SUCCESS;
488
  }
489

490
  color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf));
491
  enum CommandResult rc = parse_uncolor(buf, s, err, true);
×
492
  curses_colors_dump(buf);
493
  return rc;
×
494
}
495

496
/**
497
 * mutt_parse_unmono - Parse the 'unmono' command - Implements Command::parse() - @ingroup command_parse
498
 */
499
enum CommandResult mutt_parse_unmono(struct Buffer *buf, struct Buffer *s,
×
500
                                     intptr_t data, struct Buffer *err)
501
{
502
  *s->dptr = '\0'; /* fake that we're done parsing */
×
503
  return MUTT_CMD_SUCCESS;
×
504
}
505

506
/**
507
 * mutt_parse_color - Parse the 'color' command - Implements Command::parse() - @ingroup command_parse
508
 */
509
enum CommandResult mutt_parse_color(struct Buffer *buf, struct Buffer *s,
×
510
                                    intptr_t data, struct Buffer *err)
511
{
512
  // No GUI, or no colours, so quietly discard the command
513
  if (OptNoCurses || (COLORS == 0))
×
514
  {
515
    while (MoreArgs(s))
×
516
    {
517
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
518
    }
519
    return MUTT_CMD_SUCCESS;
520
  }
521

522
  color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf));
523
  enum CommandResult rc = parse_color(buf, s, err, parse_color_pair, true);
×
524
  curses_colors_dump(buf);
525
  return rc;
×
526
}
527

528
/**
529
 * mutt_parse_mono - Parse the 'mono' command - Implements Command::parse() - @ingroup command_parse
530
 */
531
enum CommandResult mutt_parse_mono(struct Buffer *buf, struct Buffer *s,
×
532
                                   intptr_t data, struct Buffer *err)
533
{
534
  // No GUI, or colours available, so quietly discard the command
535
  if (OptNoCurses || (COLORS != 0))
×
536
  {
537
    while (MoreArgs(s))
×
538
    {
539
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
×
540
    }
541
    return MUTT_CMD_SUCCESS;
542
  }
543

544
  color_debug(LL_DEBUG5, "parse: %s\n", buf_string(buf));
545
  enum CommandResult rc = parse_color(buf, s, err, parse_attr_spec, false);
×
546
  curses_colors_dump(buf);
547
  return rc;
×
548
}
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

© 2025 Coveralls, Inc