]> granicus.if.org Git - neomutt/blob - color.c
Fix mutt_write_mime_body() application/pgp-encrypted handling
[neomutt] / color.c
1 /**
2  * @file
3  * Color and attribute parsing
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2012 Michael R. Elkins <me@mutt.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 color Color and attribute parsing
25  *
26  * Color and attribute parsing
27  */
28
29 #include "config.h"
30 #include <regex.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "mutt/mutt.h"
36 #include "email/lib.h"
37 #include "core/lib.h"
38 #include "mutt.h"
39 #include "color.h"
40 #include "context.h"
41 #include "globals.h"
42 #include "keymap.h"
43 #include "mutt_commands.h"
44 #include "mutt_curses.h"
45 #include "mutt_menu.h"
46 #include "options.h"
47 #include "pattern.h"
48 #ifdef USE_SLANG_CURSES
49 #include <assert.h>
50 #endif
51
52 /* globals */
53 int *ColorQuote = NULL;
54 int ColorQuoteUsed;
55 int ColorDefs[MT_COLOR_MAX];
56 struct ColorLineList ColorAttachList = STAILQ_HEAD_INITIALIZER(ColorAttachList);
57 struct ColorLineList ColorBodyList = STAILQ_HEAD_INITIALIZER(ColorBodyList);
58 struct ColorLineList ColorHdrList = STAILQ_HEAD_INITIALIZER(ColorHdrList);
59 struct ColorLineList ColorIndexAuthorList = STAILQ_HEAD_INITIALIZER(ColorIndexAuthorList);
60 struct ColorLineList ColorIndexFlagsList = STAILQ_HEAD_INITIALIZER(ColorIndexFlagsList);
61 struct ColorLineList ColorIndexList = STAILQ_HEAD_INITIALIZER(ColorIndexList);
62 struct ColorLineList ColorIndexSubjectList = STAILQ_HEAD_INITIALIZER(ColorIndexSubjectList);
63 struct ColorLineList ColorIndexTagList = STAILQ_HEAD_INITIALIZER(ColorIndexTagList);
64 struct ColorLineList ColorStatusList = STAILQ_HEAD_INITIALIZER(ColorStatusList);
65
66 /* local to this file */
67 static int ColorQuoteSize;
68
69 #ifdef HAVE_COLOR
70
71 #define COLOR_DEFAULT (-2)
72 #define COLOR_UNSET UINT32_MAX
73
74 /*
75  * Flags for the high 8bits of the color value.
76  *
77  * Note that no flag means it's a palette color.
78  */
79 #define RGB24 (1u << 24)
80
81 /**
82  * struct ColorList - A set of colors
83  */
84 struct ColorList
85 {
86   /* TrueColor uses 24bit. Use fixed-width integer type to make sure it fits.
87    * Use the upper 8 bits to store flags.  */
88   uint32_t fg;
89   uint32_t bg;
90   short index;
91   short count;
92   struct ColorList *next;
93 };
94
95 static struct ColorList *ColorList = NULL;
96 static int UserColors = 0;
97
98 static const struct Mapping Colors[] = {
99   { "black", COLOR_BLACK },
100   { "blue", COLOR_BLUE },
101   { "cyan", COLOR_CYAN },
102   { "green", COLOR_GREEN },
103   { "magenta", COLOR_MAGENTA },
104   { "red", COLOR_RED },
105   { "white", COLOR_WHITE },
106   { "yellow", COLOR_YELLOW },
107 #if defined(USE_SLANG_CURSES) || defined(HAVE_USE_DEFAULT_COLORS)
108   { "default", COLOR_DEFAULT },
109 #endif
110   { 0, 0 },
111 };
112
113 #endif /* HAVE_COLOR */
114
115 static const struct Mapping Fields[] = {
116   { "attachment", MT_COLOR_ATTACHMENT },
117   { "attach_headers", MT_COLOR_ATTACH_HEADERS },
118   { "body", MT_COLOR_BODY },
119   { "bold", MT_COLOR_BOLD },
120   { "error", MT_COLOR_ERROR },
121   { "hdrdefault", MT_COLOR_HDEFAULT },
122   { "header", MT_COLOR_HEADER },
123   { "index", MT_COLOR_INDEX },
124   { "index_author", MT_COLOR_INDEX_AUTHOR },
125   { "index_collapsed", MT_COLOR_INDEX_COLLAPSED },
126   { "index_date", MT_COLOR_INDEX_DATE },
127   { "index_flags", MT_COLOR_INDEX_FLAGS },
128   { "index_label", MT_COLOR_INDEX_LABEL },
129   { "index_number", MT_COLOR_INDEX_NUMBER },
130   { "index_size", MT_COLOR_INDEX_SIZE },
131   { "index_subject", MT_COLOR_INDEX_SUBJECT },
132   { "index_tag", MT_COLOR_INDEX_TAG },
133   { "index_tags", MT_COLOR_INDEX_TAGS },
134   { "indicator", MT_COLOR_INDICATOR },
135   { "markers", MT_COLOR_MARKERS },
136   { "message", MT_COLOR_MESSAGE },
137   { "normal", MT_COLOR_NORMAL },
138   { "options", MT_COLOR_OPTIONS },
139   { "progress", MT_COLOR_PROGRESS },
140   { "prompt", MT_COLOR_PROMPT },
141   { "quoted", MT_COLOR_QUOTED },
142   { "search", MT_COLOR_SEARCH },
143 #ifdef USE_SIDEBAR
144   { "sidebar_divider", MT_COLOR_DIVIDER },
145   { "sidebar_flagged", MT_COLOR_FLAGGED },
146   { "sidebar_highlight", MT_COLOR_HIGHLIGHT },
147   { "sidebar_indicator", MT_COLOR_SB_INDICATOR },
148   { "sidebar_new", MT_COLOR_NEW },
149   { "sidebar_ordinary", MT_COLOR_ORDINARY },
150   { "sidebar_spoolfile", MT_COLOR_SB_SPOOLFILE },
151 #endif
152   { "signature", MT_COLOR_SIGNATURE },
153   { "status", MT_COLOR_STATUS },
154   { "tilde", MT_COLOR_TILDE },
155   { "tree", MT_COLOR_TREE },
156   { "underline", MT_COLOR_UNDERLINE },
157   { "warning", MT_COLOR_WARNING },
158   { NULL, 0 },
159 };
160
161 static const struct Mapping ComposeFields[] = {
162   { "header", MT_COLOR_COMPOSE_HEADER },
163   { "security_encrypt", MT_COLOR_COMPOSE_SECURITY_ENCRYPT },
164   { "security_sign", MT_COLOR_COMPOSE_SECURITY_SIGN },
165   { "security_both", MT_COLOR_COMPOSE_SECURITY_BOTH },
166   { "security_none", MT_COLOR_COMPOSE_SECURITY_NONE },
167   { NULL, 0 }
168 };
169
170 #define COLOR_QUOTE_INIT 8
171
172 /**
173  * color_line_new - Create a new ColorLine
174  * @retval ptr Newly allocated ColorLine
175  */
176 static struct ColorLine *color_line_new(void)
177 {
178   struct ColorLine *p = mutt_mem_calloc(1, sizeof(struct ColorLine));
179
180   p->fg = COLOR_UNSET;
181   p->bg = COLOR_UNSET;
182
183   return p;
184 }
185
186 /**
187  * color_line_free - Free a ColorLine
188  * @param ptr         ColorLine to free
189  * @param free_colors If true, free its colours too
190  */
191 static void color_line_free(struct ColorLine **ptr, bool free_colors)
192 {
193   if (!ptr || !*ptr)
194     return;
195
196   struct ColorLine *cl = *ptr;
197
198 #ifdef HAVE_COLOR
199   if (free_colors && (cl->fg != COLOR_UNSET) && (cl->bg != COLOR_UNSET))
200     mutt_color_free(cl->fg, cl->bg);
201 #endif
202
203   regfree(&cl->regex);
204   mutt_pattern_free(&cl->color_pattern);
205   FREE(&cl->pattern);
206   FREE(ptr);
207 }
208
209 /**
210  * mutt_color_init - Set up the default colours
211  */
212 void mutt_color_init(void)
213 {
214   memset(ColorDefs, A_NORMAL, sizeof(int) * MT_COLOR_MAX);
215   ColorQuote = mutt_mem_malloc(COLOR_QUOTE_INIT * sizeof(int));
216   memset(ColorQuote, A_NORMAL, sizeof(int) * COLOR_QUOTE_INIT);
217   ColorQuoteSize = COLOR_QUOTE_INIT;
218   ColorQuoteUsed = 0;
219
220   /* set some defaults */
221   ColorDefs[MT_COLOR_STATUS] = A_REVERSE;
222   ColorDefs[MT_COLOR_INDICATOR] = A_REVERSE;
223   ColorDefs[MT_COLOR_SEARCH] = A_REVERSE;
224   ColorDefs[MT_COLOR_MARKERS] = A_REVERSE;
225 #ifdef USE_SIDEBAR
226   ColorDefs[MT_COLOR_HIGHLIGHT] = A_UNDERLINE;
227 #endif
228   /* special meaning: toggle the relevant attribute */
229   ColorDefs[MT_COLOR_BOLD] = 0;
230   ColorDefs[MT_COLOR_UNDERLINE] = 0;
231
232 #ifdef HAVE_COLOR
233   start_color();
234 #endif
235 }
236
237 #ifdef HAVE_COLOR
238
239 #ifdef USE_SLANG_CURSES
240 /**
241  * get_color_name - Get a colour's name from its ID
242  * @param dest    Buffer for the result
243  * @param destlen Length of buffer
244  * @param val     Colour ID to look up
245  * @retval ptr Pointer to the results buffer
246  */
247 static char *get_color_name(char *dest, size_t destlen, uint32_t val)
248 {
249   static const char *const missing[3] = { "brown", "lightgray", "default" };
250
251   if (val & RGB24)
252   {
253     assert(snprintf(dest, destlen, "#%06X", val & 0xffffff) == 7);
254     return dest;
255   }
256
257   switch (val)
258   {
259     case COLOR_YELLOW:
260       mutt_str_strfcpy(dest, missing[0], destlen);
261       return dest;
262
263     case COLOR_WHITE:
264       mutt_str_strfcpy(dest, missing[1], destlen);
265       return dest;
266
267     case COLOR_DEFAULT:
268       mutt_str_strfcpy(dest, missing[2], destlen);
269       return dest;
270   }
271
272   for (int i = 0; Colors[i].name; i++)
273   {
274     if (Colors[i].value == val)
275     {
276       mutt_str_strfcpy(dest, Colors[i].name, destlen);
277       return dest;
278     }
279   }
280
281   /* Sigh. If we got this far, the color is of the form 'colorN'
282    * Slang can handle this itself, so just return 'colorN' */
283   snprintf(dest, destlen, "color%d", val);
284
285   return dest;
286 }
287 #endif
288
289 /**
290  * mutt_color_alloc - Allocate a colour pair
291  * @param fg Foreground colour ID
292  * @param bg Background colour ID
293  * @retval num Combined colour pair
294  */
295 int mutt_color_alloc(uint32_t fg, uint32_t bg)
296 {
297 #ifdef USE_SLANG_CURSES
298   char fgc[128], bgc[128];
299 #endif
300   struct ColorList *p = ColorList;
301   int i;
302
303   /* check to see if this color is already allocated to save space */
304   while (p)
305   {
306     if ((p->fg == fg) && (p->bg == bg))
307     {
308       (p->count)++;
309       return COLOR_PAIR(p->index);
310     }
311     p = p->next;
312   }
313
314   /* check to see if there are colors left */
315   if (++UserColors > COLOR_PAIRS)
316     return A_NORMAL;
317
318   /* find the smallest available index (object) */
319   i = 1;
320   while (true)
321   {
322     p = ColorList;
323     while (p)
324     {
325       if (p->index == i)
326         break;
327       p = p->next;
328     }
329     if (!p)
330       break;
331     i++;
332   }
333
334   p = mutt_mem_malloc(sizeof(struct ColorList));
335   p->next = ColorList;
336   ColorList = p;
337
338   p->index = i;
339   p->count = 1;
340   p->bg = bg;
341   p->fg = fg;
342
343 #ifdef USE_SLANG_CURSES
344   /*
345    * If using s-lang always use SLtt_set_color which allows using truecolor
346    * values. Note that I couldn't figure out if s-lang somehow reports
347    * truecolor support.
348    */
349   SLtt_set_color(i, NULL, get_color_name(fgc, sizeof(fgc), fg),
350                  get_color_name(bgc, sizeof(bgc), bg));
351 #else
352 #ifdef HAVE_USE_DEFAULT_COLORS
353   if (fg == COLOR_DEFAULT)
354     fg = COLOR_UNSET;
355   if (bg == COLOR_DEFAULT)
356     bg = COLOR_UNSET;
357 #endif
358   init_pair(i, fg, bg);
359 #endif
360
361   mutt_debug(LL_DEBUG3, "Color pairs used so far: %d\n", UserColors);
362
363   return COLOR_PAIR(p->index);
364 }
365
366 /**
367  * mutt_lookup_color - Get the colours from a colour pair
368  * @param[in]  pair Colour pair
369  * @param[out] fg   Foreground colour (OPTIONAL)
370  * @param[out] bg   Background colour (OPTIONAL)
371  * @retval  0 Success
372  * @retval -1 Error
373  */
374 static int mutt_lookup_color(short pair, uint32_t *fg, uint32_t *bg)
375 {
376   struct ColorList *p = ColorList;
377
378   while (p)
379   {
380     if (COLOR_PAIR(p->index) == pair)
381     {
382       if (fg)
383         *fg = p->fg;
384       if (bg)
385         *bg = p->bg;
386       return 0;
387     }
388     p = p->next;
389   }
390   return -1;
391 }
392
393 /**
394  * mutt_color_combine - Combine two colours
395  * @param fg_attr Colour pair of foreground to use
396  * @param bg_attr Colour pair of background to use
397  * @retval num Colour pair of combined colour
398  */
399 int mutt_color_combine(uint32_t fg_attr, uint32_t bg_attr)
400 {
401   uint32_t fg = COLOR_DEFAULT;
402   uint32_t bg = COLOR_DEFAULT;
403
404   mutt_lookup_color(fg_attr, &fg, NULL);
405   mutt_lookup_color(bg_attr, NULL, &bg);
406
407   if ((fg == COLOR_DEFAULT) && (bg == COLOR_DEFAULT))
408     return A_NORMAL;
409   return mutt_color_alloc(fg, bg);
410 }
411
412 /**
413  * mutt_color_free - Free a colour
414  * @param fg Foreground colour ID
415  * @param bg Background colour ID
416  *
417  * If there are no more users, the resource will be freed.
418  */
419 void mutt_color_free(uint32_t fg, uint32_t bg)
420 {
421   struct ColorList *q = NULL;
422
423   struct ColorList *p = ColorList;
424   while (p)
425   {
426     if ((p->fg == fg) && (p->bg == bg))
427     {
428       (p->count)--;
429       if (p->count > 0)
430         return;
431
432       UserColors--;
433       mutt_debug(LL_DEBUG1, "Color pairs used so far: %d\n", UserColors);
434
435       if (p == ColorList)
436       {
437         ColorList = ColorList->next;
438         FREE(&p);
439         return;
440       }
441       q = ColorList;
442       while (q)
443       {
444         if (q->next == p)
445         {
446           q->next = p->next;
447           FREE(&p);
448           return;
449         }
450         q = q->next;
451       }
452       /* can't get here */
453     }
454     p = p->next;
455   }
456 }
457
458 #endif /* HAVE_COLOR */
459
460 #ifdef HAVE_COLOR
461
462 /**
463  * parse_color_name - Parse a colour name
464  * @param[in]  s     String to parse
465  * @param[out] col   Number for 'colorNNN' colours
466  * @param[out] attr  Attribute flags, e.g. A_BOLD
467  * @param[in]  is_fg true if this is a foreground colour
468  * @param[out] err   Buffer for error messages
469  * @retval  0 Success
470  * @retval -1 Error
471  *
472  * Parse a colour name, such as "red", "brightgreen", "color123".
473  */
474 static int parse_color_name(const char *s, uint32_t *col, int *attr, bool is_fg,
475                             struct Buffer *err)
476 {
477   char *eptr = NULL;
478   bool is_alert = false, is_bright = false, is_light = false;
479   int clen;
480
481   if ((clen = mutt_str_startswith(s, "bright", CASE_IGNORE)))
482   {
483     is_bright = true;
484     s += clen;
485   }
486   else if ((clen = mutt_str_startswith(s, "alert", CASE_IGNORE)))
487   {
488     is_alert = true;
489     is_bright = true;
490     s += clen;
491   }
492   else if ((clen = mutt_str_startswith(s, "light", CASE_IGNORE)))
493   {
494     is_light = true;
495     s += clen;
496   }
497
498   /* allow aliases for xterm color resources */
499   if ((clen = mutt_str_startswith(s, "color", CASE_IGNORE)))
500   {
501     s += clen;
502     *col = strtoul(s, &eptr, 10);
503     if (!*s || *eptr || ((*col >= COLORS) && !OptNoCurses && has_colors()))
504     {
505       mutt_buffer_printf(err, _("%s: color not supported by term"), s);
506       return -1;
507     }
508   }
509 #ifdef HAVE_DIRECTCOLOR
510   else if (*s == '#')
511   {
512     s += 1;
513     *col = strtoul(s, &eptr, 16);
514     if (!*s || *eptr || ((*col == COLOR_UNSET) && !OptNoCurses && has_colors()))
515     {
516       snprintf(err->data, err->dsize, _("%s: color not supported by term"), s);
517       return -1;
518     }
519     *col |= RGB24;
520   }
521 #endif
522   else if ((*col = mutt_map_get_value(s, Colors)) == -1)
523   {
524     mutt_buffer_printf(err, _("%s: no such color"), s);
525     return -1;
526   }
527
528   if (is_bright || is_light)
529   {
530     if (is_alert)
531     {
532       *attr |= A_BOLD;
533       *attr |= A_BLINK;
534     }
535     else if (is_fg)
536     {
537       if ((COLORS >= 16) && is_light)
538       {
539         if (*col <= 7)
540         {
541           /* Advance the color 0-7 by 8 to get the light version */
542           *col += 8;
543         }
544       }
545       else
546       {
547         *attr |= A_BOLD;
548       }
549     }
550     else if (!(*col & RGB24))
551     {
552       if (COLORS >= 16)
553       {
554         if (*col <= 7)
555         {
556           /* Advance the color 0-7 by 8 to get the light version */
557           *col += 8;
558         }
559       }
560     }
561   }
562
563   return 0;
564 }
565
566 #endif
567
568 /**
569  * do_uncolor - Parse the "uncolor" or "unmono" command
570  * @param[in]     buf           Buffer for temporary storage
571  * @param[in]     s             Buffer containing the uncolor command
572  * @param[in]     cl            List of existing colours
573  * @param[in,out] do_cache      Set to true if colours were freed
574  * @param[in]     parse_uncolor If true, 'uncolor', else 'unmono'
575  */
576 static void do_uncolor(struct Buffer *buf, struct Buffer *s,
577                        struct ColorLineList *cl, bool *do_cache, bool parse_uncolor)
578 {
579   struct ColorLine *np = NULL, *tmp = NULL;
580   do
581   {
582     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
583     if (mutt_str_strcmp("*", buf->data) == 0)
584     {
585       np = STAILQ_FIRST(cl);
586       while (np)
587       {
588         tmp = STAILQ_NEXT(np, entries);
589         if (!*do_cache)
590         {
591           *do_cache = true;
592         }
593         color_line_free(&np, parse_uncolor);
594         np = tmp;
595       }
596       STAILQ_INIT(cl);
597       return;
598     }
599     else
600     {
601       tmp = NULL;
602       STAILQ_FOREACH(np, cl, entries)
603       {
604         if (mutt_str_strcmp(buf->data, np->pattern) == 0)
605         {
606           if (!*do_cache)
607           {
608             *do_cache = true;
609           }
610           mutt_debug(LL_DEBUG1, "Freeing pattern \"%s\" from ColorList\n", buf->data);
611           if (tmp)
612             STAILQ_REMOVE_AFTER(cl, tmp, entries);
613           else
614             STAILQ_REMOVE_HEAD(cl, entries);
615           color_line_free(&np, parse_uncolor);
616           break;
617         }
618         tmp = np;
619       }
620     }
621   } while (MoreArgs(s));
622 }
623
624 /**
625  * parse_uncolor - Parse an 'uncolor' command
626  * @param buf           Temporary Buffer space
627  * @param s             Buffer containing string to be parsed
628  * @param data          Flags associated with the command
629  * @param err           Buffer for error messages
630  * @param parse_uncolor If true, 'uncolor', else 'unmono'
631  * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
632  *
633  * usage:
634  * * uncolor index pattern [pattern...]
635  * * unmono  index pattern [pattern...]
636  */
637 static enum CommandResult parse_uncolor(struct Buffer *buf, struct Buffer *s,
638                                         unsigned long data, struct Buffer *err, bool parse_uncolor)
639 {
640   int object = 0;
641   bool do_cache = false;
642
643   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
644
645   object = mutt_map_get_value(buf->data, Fields);
646   if (object == -1)
647   {
648     mutt_buffer_printf(err, _("%s: no such object"), buf->data);
649     return MUTT_CMD_ERROR;
650   }
651
652   if (object > MT_COLOR_INDEX_SUBJECT)
653   { /* uncolor index column */
654     ColorDefs[object] = 0;
655     mutt_menu_set_redraw_full(MENU_MAIN);
656     return MUTT_CMD_SUCCESS;
657   }
658
659   if (!mutt_str_startswith(buf->data, "body", CASE_MATCH) &&
660       !mutt_str_startswith(buf->data, "header", CASE_MATCH) &&
661       !mutt_str_startswith(buf->data, "index", CASE_MATCH))
662   {
663     mutt_buffer_printf(err, _("%s: command valid only for index, body, header objects"),
664                        parse_uncolor ? "uncolor" : "unmono");
665     return MUTT_CMD_WARNING;
666   }
667
668   if (!MoreArgs(s))
669   {
670     mutt_buffer_printf(err, _("%s: too few arguments"), parse_uncolor ? "uncolor" : "unmono");
671     return MUTT_CMD_WARNING;
672   }
673
674   if (
675 #ifdef HAVE_COLOR
676       /* we're running without curses */
677       OptNoCurses || /* we're parsing an uncolor command, and have no colors */
678       (parse_uncolor && !has_colors())
679       /* we're parsing an unmono command, and have colors */
680       || (!parse_uncolor && has_colors())
681 #else
682       /* We don't even have colors compiled in */
683       parse_uncolor
684 #endif
685   )
686   {
687     /* just eat the command, but don't do anything real about it */
688     do
689     {
690       mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
691     } while (MoreArgs(s));
692
693     return MUTT_CMD_SUCCESS;
694   }
695
696   if (object == MT_COLOR_BODY)
697     do_uncolor(buf, s, &ColorBodyList, &do_cache, parse_uncolor);
698   else if (object == MT_COLOR_HEADER)
699     do_uncolor(buf, s, &ColorHdrList, &do_cache, parse_uncolor);
700   else if (object == MT_COLOR_ATTACH_HEADERS)
701     do_uncolor(buf, s, &ColorAttachList, &do_cache, parse_uncolor);
702   else if (object == MT_COLOR_INDEX)
703     do_uncolor(buf, s, &ColorIndexList, &do_cache, parse_uncolor);
704   else if (object == MT_COLOR_INDEX_AUTHOR)
705     do_uncolor(buf, s, &ColorIndexAuthorList, &do_cache, parse_uncolor);
706   else if (object == MT_COLOR_INDEX_FLAGS)
707     do_uncolor(buf, s, &ColorIndexFlagsList, &do_cache, parse_uncolor);
708   else if (object == MT_COLOR_INDEX_SUBJECT)
709     do_uncolor(buf, s, &ColorIndexSubjectList, &do_cache, parse_uncolor);
710   else if (object == MT_COLOR_INDEX_TAG)
711     do_uncolor(buf, s, &ColorIndexTagList, &do_cache, parse_uncolor);
712
713   bool is_index = ((object == MT_COLOR_INDEX) || (object == MT_COLOR_INDEX_AUTHOR) ||
714                    (object == MT_COLOR_INDEX_FLAGS) || (object == MT_COLOR_INDEX_SUBJECT) ||
715                    (object == MT_COLOR_INDEX_TAG));
716
717   if (is_index && do_cache && !OptNoCurses)
718   {
719     mutt_menu_set_redraw_full(MENU_MAIN);
720     /* force re-caching of index colors */
721     for (int i = 0; Context && i < Context->mailbox->msg_count; i++)
722       Context->mailbox->emails[i]->pair = 0;
723   }
724   return MUTT_CMD_SUCCESS;
725 }
726
727 #ifdef HAVE_COLOR
728
729 /**
730  * mutt_parse_uncolor - Parse the 'uncolor' command - Implements ::command_t
731  */
732 enum CommandResult mutt_parse_uncolor(struct Buffer *buf, struct Buffer *s,
733                                       unsigned long data, struct Buffer *err)
734 {
735   return parse_uncolor(buf, s, data, err, true);
736 }
737
738 #endif
739
740 /**
741  * mutt_parse_unmono - Parse the 'unmono' command - Implements ::command_t
742  */
743 enum CommandResult mutt_parse_unmono(struct Buffer *buf, struct Buffer *s,
744                                      unsigned long data, struct Buffer *err)
745 {
746   return parse_uncolor(buf, s, data, err, false);
747 }
748
749 /**
750  * add_pattern - Associate a colour to a pattern
751  * @param top       List of existing colours
752  * @param s         String to match
753  * @param sensitive true if the pattern case-sensitive
754  * @param fg        Foreground colour ID
755  * @param bg        Background colour ID
756  * @param attr      Attribute flags, e.g. A_BOLD
757  * @param err       Buffer for error messages
758  * @param is_index  true of this is for the index
759  * @param match     Number of regex subexpression to match (0 for entire pattern)
760  * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
761  */
762 static enum CommandResult add_pattern(struct ColorLineList *top, const char *s,
763                                       bool sensitive, uint32_t fg, uint32_t bg, int attr,
764                                       struct Buffer *err, bool is_index, int match)
765 {
766   /* is_index used to store compiled pattern
767    * only for 'index' color object
768    * when called from mutt_parse_color() */
769
770   struct ColorLine *tmp = NULL;
771
772   STAILQ_FOREACH(tmp, top, entries)
773   {
774     if (sensitive)
775     {
776       if (mutt_str_strcmp(s, tmp->pattern) == 0)
777         break;
778     }
779     else
780     {
781       if (mutt_str_strcasecmp(s, tmp->pattern) == 0)
782         break;
783     }
784   }
785
786   if (tmp)
787   {
788 #ifdef HAVE_COLOR
789     if ((fg != COLOR_UNSET) && (bg != COLOR_UNSET))
790     {
791       if ((tmp->fg != fg) || (tmp->bg != bg))
792       {
793         mutt_color_free(tmp->fg, tmp->bg);
794         tmp->fg = fg;
795         tmp->bg = bg;
796         attr |= mutt_color_alloc(fg, bg);
797       }
798       else
799         attr |= (tmp->pair & ~A_BOLD);
800     }
801 #endif /* HAVE_COLOR */
802     tmp->pair = attr;
803   }
804   else
805   {
806     tmp = color_line_new();
807     if (is_index)
808     {
809       struct Buffer *buf = mutt_buffer_pool_get();
810       mutt_buffer_strcpy(buf, s);
811       mutt_check_simple(buf, NONULL(C_SimpleSearch));
812       tmp->color_pattern = mutt_pattern_comp(buf->data, MUTT_PC_FULL_MSG, err);
813       mutt_buffer_pool_release(&buf);
814       if (!tmp->color_pattern)
815       {
816         color_line_free(&tmp, true);
817         return MUTT_CMD_ERROR;
818       }
819     }
820     else
821     {
822       int flags = 0;
823       if (sensitive)
824         flags = mutt_mb_is_lower(s) ? REG_ICASE : 0;
825       else
826         flags = REG_ICASE;
827
828       const int r = REG_COMP(&tmp->regex, s, flags);
829       if (r != 0)
830       {
831         regerror(r, &tmp->regex, err->data, err->dsize);
832         color_line_free(&tmp, true);
833         return MUTT_CMD_ERROR;
834       }
835     }
836     tmp->pattern = mutt_str_strdup(s);
837     tmp->match = match;
838 #ifdef HAVE_COLOR
839     if ((fg != COLOR_UNSET) && (bg != COLOR_UNSET))
840     {
841       tmp->fg = fg;
842       tmp->bg = bg;
843       attr |= mutt_color_alloc(fg, bg);
844     }
845 #endif
846     tmp->pair = attr;
847     STAILQ_INSERT_HEAD(top, tmp, entries);
848   }
849
850   /* force re-caching of index colors */
851   if (is_index)
852   {
853     for (int i = 0; Context && i < Context->mailbox->msg_count; i++)
854       Context->mailbox->emails[i]->pair = 0;
855   }
856
857   return MUTT_CMD_SUCCESS;
858 }
859
860 /**
861  * parse_object - Parse a colour config line
862  * @param[in]  buf Temporary Buffer space
863  * @param[in]  s   Buffer containing string to be parsed
864  * @param[out] o   Index into the fields map
865  * @param[out] ql  Quote level
866  * @param[out] err Buffer for error messages
867  * @retval  0 Success
868  * @retval -1 Error
869  */
870 static int parse_object(struct Buffer *buf, struct Buffer *s, uint32_t *o,
871                         uint32_t *ql, struct Buffer *err)
872 {
873   if (!MoreArgs(s))
874   {
875     mutt_buffer_printf(err, _("%s: too few arguments"), "color");
876     return -1;
877   }
878
879   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
880   if (mutt_str_startswith(buf->data, "quoted", CASE_MATCH))
881   {
882     if (buf->data[6])
883     {
884       char *eptr = NULL;
885       *ql = strtoul(buf->data + 6, &eptr, 10);
886       if (*eptr || (*ql == COLOR_UNSET))
887       {
888         mutt_buffer_printf(err, _("%s: no such object"), buf->data);
889         return -1;
890       }
891     }
892     else
893       *ql = 0;
894
895     *o = MT_COLOR_QUOTED;
896   }
897   else if (mutt_str_strcasecmp(buf->data, "compose") == 0)
898   {
899     if (!MoreArgs(s))
900     {
901       mutt_buffer_printf(err, _("%s: too few arguments"), "color");
902       return -1;
903     }
904
905     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
906
907     *o = mutt_map_get_value(buf->data, ComposeFields);
908     if (*o == -1)
909     {
910       mutt_buffer_printf(err, _("%s: no such object"), buf->data);
911       return -1;
912     }
913   }
914   else if ((*o = mutt_map_get_value(buf->data, Fields)) == -1)
915   {
916     mutt_buffer_printf(err, _("%s: no such object"), buf->data);
917     return -1;
918   }
919
920   return 0;
921 }
922
923 /**
924  * typedef parser_callback_t - Prototype for a function to parse color config
925  * @param[in]  buf Temporary Buffer space
926  * @param[in]  s    Buffer containing string to be parsed
927  * @param[out] fg   Foreground colour (set to -1)
928  * @param[out] bg   Background colour (set to -1)
929  * @param[out] attr Attribute flags
930  * @param[out] err  Buffer for error messages
931  * @retval  0 Success
932  * @retval -1 Error
933  */
934 typedef int (*parser_callback_t)(struct Buffer *buf, struct Buffer *s, uint32_t *fg,
935                                  uint32_t *bg, int *attr, struct Buffer *err);
936
937 #ifdef HAVE_COLOR
938
939 /**
940  * parse_color_pair - Parse a pair of colours - Implements ::parser_callback_t
941  */
942 static int parse_color_pair(struct Buffer *buf, struct Buffer *s, uint32_t *fg,
943                             uint32_t *bg, int *attr, struct Buffer *err)
944 {
945   while (true)
946   {
947     if (!MoreArgs(s))
948     {
949       mutt_buffer_printf(err, _("%s: too few arguments"), "color");
950       return -1;
951     }
952
953     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
954
955     if (mutt_str_strcasecmp("bold", buf->data) == 0)
956       *attr |= A_BOLD;
957     else if (mutt_str_strcasecmp("underline", buf->data) == 0)
958       *attr |= A_UNDERLINE;
959     else if (mutt_str_strcasecmp("none", buf->data) == 0)
960       *attr = A_NORMAL;
961     else if (mutt_str_strcasecmp("reverse", buf->data) == 0)
962       *attr |= A_REVERSE;
963     else if (mutt_str_strcasecmp("standout", buf->data) == 0)
964       *attr |= A_STANDOUT;
965     else if (mutt_str_strcasecmp("normal", buf->data) == 0)
966       *attr = A_NORMAL; /* needs use = instead of |= to clear other bits */
967     else
968     {
969       if (parse_color_name(buf->data, fg, attr, true, err) != 0)
970         return -1;
971       break;
972     }
973   }
974
975   if (!MoreArgs(s))
976   {
977     mutt_buffer_printf(err, _("%s: too few arguments"), "color");
978     return -1;
979   }
980
981   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
982
983   if (parse_color_name(buf->data, bg, attr, false, err) != 0)
984     return -1;
985
986   return 0;
987 }
988
989 #endif
990
991 /**
992  * parse_attr_spec - Parse an attribute description - Implements ::parser_callback_t
993  */
994 static int parse_attr_spec(struct Buffer *buf, struct Buffer *s, uint32_t *fg,
995                            uint32_t *bg, int *attr, struct Buffer *err)
996 {
997   if (fg)
998     *fg = COLOR_UNSET;
999   if (bg)
1000     *bg = COLOR_UNSET;
1001
1002   if (!MoreArgs(s))
1003   {
1004     mutt_buffer_printf(err, _("%s: too few arguments"), "mono");
1005     return -1;
1006   }
1007
1008   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1009
1010   if (mutt_str_strcasecmp("bold", buf->data) == 0)
1011     *attr |= A_BOLD;
1012   else if (mutt_str_strcasecmp("underline", buf->data) == 0)
1013     *attr |= A_UNDERLINE;
1014   else if (mutt_str_strcasecmp("none", buf->data) == 0)
1015     *attr = A_NORMAL;
1016   else if (mutt_str_strcasecmp("reverse", buf->data) == 0)
1017     *attr |= A_REVERSE;
1018   else if (mutt_str_strcasecmp("standout", buf->data) == 0)
1019     *attr |= A_STANDOUT;
1020   else if (mutt_str_strcasecmp("normal", buf->data) == 0)
1021     *attr = A_NORMAL; /* needs use = instead of |= to clear other bits */
1022   else
1023   {
1024     mutt_buffer_printf(err, _("%s: no such attribute"), buf->data);
1025     return -1;
1026   }
1027
1028   return 0;
1029 }
1030
1031 /**
1032  * fgbgattr_to_color - Convert a foreground, background, attribute triplet into a colour
1033  * @param fg Foreground colour ID
1034  * @param bg Background colour ID
1035  * @param attr Attribute flags, e.g. A_BOLD
1036  * @retval num Combined colour pair
1037  */
1038 static int fgbgattr_to_color(int fg, int bg, int attr)
1039 {
1040 #ifdef HAVE_COLOR
1041   if ((fg != COLOR_UNSET) && (bg != COLOR_UNSET))
1042     return attr | mutt_color_alloc(fg, bg);
1043   else
1044 #endif
1045     return attr;
1046 }
1047
1048 /**
1049  * parse_color - Parse a "color" command
1050  * @param buf      Temporary Buffer space
1051  * @param s        Buffer containing string to be parsed
1052  * @param err      Buffer for error messages
1053  * @param callback Function to handle command - Implements ::parser_callback_t
1054  * @param dry_run  If true, test the command, but don't apply it
1055  * @param color    If true "color", else "mono"
1056  * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
1057  *
1058  * usage: color OBJECT FG BG [ REGEX ]
1059  *        mono  OBJECT ATTR [ REGEX ]
1060  */
1061 static enum CommandResult parse_color(struct Buffer *buf, struct Buffer *s,
1062                                       struct Buffer *err, parser_callback_t callback,
1063                                       bool dry_run, bool color)
1064 {
1065   int attr = 0;
1066   uint32_t fg = 0, bg = 0, object = 0, q_level = 0, match = 0;
1067   enum CommandResult rc = MUTT_CMD_SUCCESS;
1068
1069   if (parse_object(buf, s, &object, &q_level, err) == -1)
1070     return MUTT_CMD_ERROR;
1071
1072   if (callback(buf, s, &fg, &bg, &attr, err) == -1)
1073     return MUTT_CMD_ERROR;
1074
1075   /* extract a regular expression if needed */
1076
1077   if ((object == MT_COLOR_BODY) || (object == MT_COLOR_HEADER) ||
1078       (object == MT_COLOR_ATTACH_HEADERS) || (object == MT_COLOR_INDEX) ||
1079       (object == MT_COLOR_INDEX_AUTHOR) || (object == MT_COLOR_INDEX_FLAGS) ||
1080       (object == MT_COLOR_INDEX_TAG) || (object == MT_COLOR_INDEX_SUBJECT))
1081   {
1082     if (!MoreArgs(s))
1083     {
1084       mutt_buffer_printf(err, _("%s: too few arguments"), color ? "color" : "mono");
1085       return MUTT_CMD_WARNING;
1086     }
1087
1088     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1089   }
1090
1091   if (MoreArgs(s) && (object != MT_COLOR_STATUS))
1092   {
1093     mutt_buffer_printf(err, _("%s: too many arguments"), color ? "color" : "mono");
1094     return MUTT_CMD_WARNING;
1095   }
1096
1097   /* dry run? */
1098
1099   if (dry_run)
1100   {
1101     *s->dptr = '\0'; /* fake that we're done parsing */
1102     return MUTT_CMD_SUCCESS;
1103   }
1104
1105 #ifdef HAVE_COLOR
1106 #ifdef HAVE_USE_DEFAULT_COLORS
1107   if (!OptNoCurses &&
1108       has_colors()
1109       /* delay use_default_colors() until needed, since it initializes things */
1110       && ((fg == COLOR_DEFAULT) || (bg == COLOR_DEFAULT) || (object == MT_COLOR_TREE)) &&
1111       (use_default_colors() != OK))
1112   /* the case of the tree object is special, because a non-default fg color of
1113    * the tree element may be combined dynamically with the default bg color of
1114    * an index line, not necessarily defined in a rc file.  */
1115   {
1116     mutt_buffer_strcpy(err, _("default colors not supported"));
1117     return MUTT_CMD_ERROR;
1118   }
1119 #endif /* HAVE_USE_DEFAULT_COLORS */
1120 #endif
1121
1122   if (object == MT_COLOR_HEADER)
1123     rc = add_pattern(&ColorHdrList, buf->data, false, fg, bg, attr, err, false, match);
1124   else if (object == MT_COLOR_BODY)
1125     rc = add_pattern(&ColorBodyList, buf->data, true, fg, bg, attr, err, false, match);
1126   else if (object == MT_COLOR_ATTACH_HEADERS)
1127     rc = add_pattern(&ColorAttachList, buf->data, true, fg, bg, attr, err, false, match);
1128   else if ((object == MT_COLOR_STATUS) && MoreArgs(s))
1129   {
1130     /* 'color status fg bg' can have up to 2 arguments:
1131      * 0 arguments: sets the default status color (handled below by else part)
1132      * 1 argument : colorize pattern on match
1133      * 2 arguments: colorize nth submatch of pattern */
1134     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1135
1136     if (MoreArgs(s))
1137     {
1138       struct Buffer tmp = mutt_buffer_make(0);
1139       mutt_extract_token(&tmp, s, MUTT_TOKEN_NO_FLAGS);
1140       if (mutt_str_atoui(tmp.data, &match) < 0)
1141       {
1142         mutt_buffer_printf(err, _("%s: invalid number: %s"),
1143                            color ? "color" : "mono", tmp.data);
1144         mutt_buffer_dealloc(&tmp);
1145         return MUTT_CMD_WARNING;
1146       }
1147       mutt_buffer_dealloc(&tmp);
1148     }
1149
1150     if (MoreArgs(s))
1151     {
1152       mutt_buffer_printf(err, _("%s: too many arguments"), color ? "color" : "mono");
1153       return MUTT_CMD_WARNING;
1154     }
1155
1156     rc = add_pattern(&ColorStatusList, buf->data, true, fg, bg, attr, err, false, match);
1157   }
1158   else if (object == MT_COLOR_INDEX)
1159   {
1160     rc = add_pattern(&ColorIndexList, buf->data, true, fg, bg, attr, err, true, match);
1161     mutt_menu_set_redraw_full(MENU_MAIN);
1162   }
1163   else if (object == MT_COLOR_INDEX_AUTHOR)
1164   {
1165     rc = add_pattern(&ColorIndexAuthorList, buf->data, true, fg, bg, attr, err, true, match);
1166     mutt_menu_set_redraw_full(MENU_MAIN);
1167   }
1168   else if (object == MT_COLOR_INDEX_FLAGS)
1169   {
1170     rc = add_pattern(&ColorIndexFlagsList, buf->data, true, fg, bg, attr, err, true, match);
1171     mutt_menu_set_redraw_full(MENU_MAIN);
1172   }
1173   else if (object == MT_COLOR_INDEX_SUBJECT)
1174   {
1175     rc = add_pattern(&ColorIndexSubjectList, buf->data, true, fg, bg, attr, err, true, match);
1176     mutt_menu_set_redraw_full(MENU_MAIN);
1177   }
1178   else if (object == MT_COLOR_INDEX_TAG)
1179   {
1180     rc = add_pattern(&ColorIndexTagList, buf->data, true, fg, bg, attr, err, true, match);
1181     mutt_menu_set_redraw_full(MENU_MAIN);
1182   }
1183   else if (object == MT_COLOR_QUOTED)
1184   {
1185     if (q_level >= ColorQuoteSize)
1186     {
1187       mutt_mem_realloc(&ColorQuote, (ColorQuoteSize += 2) * sizeof(int));
1188       ColorQuote[ColorQuoteSize - 2] = ColorDefs[MT_COLOR_QUOTED];
1189       ColorQuote[ColorQuoteSize - 1] = ColorDefs[MT_COLOR_QUOTED];
1190     }
1191     if (q_level >= ColorQuoteUsed)
1192       ColorQuoteUsed = q_level + 1;
1193     if (q_level == 0)
1194     {
1195       ColorDefs[MT_COLOR_QUOTED] = fgbgattr_to_color(fg, bg, attr);
1196
1197       ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED];
1198       for (q_level = 1; q_level < ColorQuoteUsed; q_level++)
1199       {
1200         if (ColorQuote[q_level] == A_NORMAL)
1201           ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED];
1202       }
1203     }
1204     else
1205       ColorQuote[q_level] = fgbgattr_to_color(fg, bg, attr);
1206   }
1207   else
1208   {
1209     ColorDefs[object] = fgbgattr_to_color(fg, bg, attr);
1210     if (object > MT_COLOR_INDEX_AUTHOR)
1211       mutt_menu_set_redraw_full(MENU_MAIN);
1212   }
1213
1214   return rc;
1215 }
1216
1217 #ifdef HAVE_COLOR
1218
1219 /**
1220  * mutt_parse_color - Parse the 'color' command - Implements ::command_t
1221  */
1222 enum CommandResult mutt_parse_color(struct Buffer *buf, struct Buffer *s,
1223                                     unsigned long data, struct Buffer *err)
1224 {
1225   bool dry_run = false;
1226
1227   if (OptNoCurses || !has_colors())
1228     dry_run = true;
1229
1230   return parse_color(buf, s, err, parse_color_pair, dry_run, true);
1231 }
1232
1233 #endif
1234
1235 /**
1236  * mutt_parse_mono - Parse the 'mono' command - Implements ::command_t
1237  */
1238 enum CommandResult mutt_parse_mono(struct Buffer *buf, struct Buffer *s,
1239                                    unsigned long data, struct Buffer *err)
1240 {
1241   bool dry_run = false;
1242
1243 #ifdef HAVE_COLOR
1244   if (OptNoCurses || has_colors())
1245     dry_run = true;
1246 #else
1247   if (OptNoCurses)
1248     dry_run = true;
1249 #endif
1250
1251   return parse_color(buf, s, err, parse_attr_spec, dry_run, false);
1252 }
1253
1254 /**
1255  * mutt_color_list_free - Free a list of colours
1256  * @param list ColorLine List
1257  */
1258 static void mutt_color_list_free(struct ColorLineList *list)
1259 {
1260   struct ColorLine *np = NULL, *tmp = NULL;
1261   STAILQ_FOREACH_SAFE(np, list, entries, tmp)
1262   {
1263     STAILQ_REMOVE(list, np, ColorLine, entries);
1264     color_line_free(&np, true);
1265   }
1266 }
1267
1268 /**
1269  * mutt_colors_free - Free all the colours (on shutdown)
1270  */
1271 void mutt_colors_free(void)
1272 {
1273   mutt_color_list_free(&ColorAttachList);
1274   mutt_color_list_free(&ColorBodyList);
1275   mutt_color_list_free(&ColorHdrList);
1276   mutt_color_list_free(&ColorIndexAuthorList);
1277   mutt_color_list_free(&ColorIndexFlagsList);
1278   mutt_color_list_free(&ColorIndexList);
1279   mutt_color_list_free(&ColorIndexSubjectList);
1280   mutt_color_list_free(&ColorIndexTagList);
1281   mutt_color_list_free(&ColorStatusList);
1282
1283   struct ColorList *cl = ColorList;
1284   struct ColorList *next = NULL;
1285   while (cl)
1286   {
1287     next = cl->next;
1288     FREE(&cl);
1289     cl = next;
1290   }
1291   ColorList = NULL;
1292 }