34a3d709db963592bd8476de3ca19ff936677bb8
[apps/madmutt.git] / color.c
1 /*
2  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "mutt_curses.h"
25 #include "mapping.h"
26
27 #include <string.h>
28 #include <stdlib.h>
29 #include <ctype.h>
30
31 /* globals */
32 int *ColorQuote;
33 int ColorQuoteUsed;
34 int ColorDefs[MT_COLOR_MAX];
35 COLOR_LINE *ColorHdrList = NULL;
36 COLOR_LINE *ColorBodyList = NULL;
37 COLOR_LINE *ColorIndexList = NULL;
38
39 /* local to this file */
40 static int ColorQuoteSize;
41
42 #ifdef HAVE_COLOR
43
44 #define COLOR_DEFAULT (-2)
45
46 typedef struct color_list {
47   short fg;
48   short bg;
49   short index;
50   short count;
51   struct color_list *next;
52 } COLOR_LIST;
53
54 static COLOR_LIST *ColorList = NULL;
55 static int UserColors = 0;
56
57 static struct mapping_t Colors[] = {
58   {"black", COLOR_BLACK},
59   {"blue", COLOR_BLUE},
60   {"cyan", COLOR_CYAN},
61   {"green", COLOR_GREEN},
62   {"magenta", COLOR_MAGENTA},
63   {"red", COLOR_RED},
64   {"white", COLOR_WHITE},
65   {"yellow", COLOR_YELLOW},
66 #if defined (USE_SLANG_CURSES) || defined (HAVE_USE_DEFAULT_COLORS)
67   {"default", COLOR_DEFAULT},
68 #endif
69   {0, 0}
70 };
71
72 #endif /* HAVE_COLOR */
73
74 static struct mapping_t Fields[] = {
75   {"hdrdefault", MT_COLOR_HDEFAULT},
76   {"quoted", MT_COLOR_QUOTED},
77   {"signature", MT_COLOR_SIGNATURE},
78   {"indicator", MT_COLOR_INDICATOR},
79   {"status", MT_COLOR_STATUS},
80   {"tree", MT_COLOR_TREE},
81   {"error", MT_COLOR_ERROR},
82   {"normal", MT_COLOR_NORMAL},
83   {"tilde", MT_COLOR_TILDE},
84   {"markers", MT_COLOR_MARKERS},
85   {"header", MT_COLOR_HEADER},
86   {"body", MT_COLOR_BODY},
87   {"message", MT_COLOR_MESSAGE},
88   {"attachment", MT_COLOR_ATTACHMENT},
89   {"search", MT_COLOR_SEARCH},
90   {"bold", MT_COLOR_BOLD},
91   {"underline", MT_COLOR_UNDERLINE},
92   {"index", MT_COLOR_INDEX},
93   {"sidebar_new", MT_COLOR_NEW},
94   {"sidebar", MT_COLOR_SIDEBAR},
95   {"sidebar_flagged", MT_COLOR_FLAGGED},
96   {NULL, 0}
97 };
98
99 #define COLOR_QUOTE_INIT        8
100
101 static COLOR_LINE *mutt_new_color_line (void)
102 {
103   COLOR_LINE *p = safe_calloc (1, sizeof (COLOR_LINE));
104
105   p->fg = p->bg = -1;
106
107   return (p);
108 }
109
110 static void mutt_free_color_line (COLOR_LINE ** l, int free_colors)
111 {
112   COLOR_LINE *tmp;
113
114   if (!l || !*l)
115     return;
116
117   tmp = *l;
118
119 #ifdef HAVE_COLOR
120   if (free_colors && tmp->fg != -1 && tmp->bg != -1)
121     mutt_free_color (tmp->fg, tmp->bg);
122 #endif
123
124   /* we should really introduce a container
125    * type for regular expressions.
126    */
127
128   regfree (&tmp->rx);
129   mutt_pattern_free (&tmp->color_pattern);
130   FREE (&tmp->pattern);
131   FREE (l);
132 }
133
134 void ci_start_color (void)
135 {
136   memset (ColorDefs, A_NORMAL, sizeof (int) * MT_COLOR_MAX);
137   ColorQuote = (int *) safe_malloc (COLOR_QUOTE_INIT * sizeof (int));
138   memset (ColorQuote, A_NORMAL, sizeof (int) * COLOR_QUOTE_INIT);
139   ColorQuoteSize = COLOR_QUOTE_INIT;
140   ColorQuoteUsed = 0;
141
142   /* set some defaults */
143   ColorDefs[MT_COLOR_STATUS] = A_REVERSE;
144   ColorDefs[MT_COLOR_INDICATOR] = A_REVERSE;
145   ColorDefs[MT_COLOR_SEARCH] = A_REVERSE;
146   ColorDefs[MT_COLOR_MARKERS] = A_REVERSE;
147   /* special meaning: toggle the relevant attribute */
148   ColorDefs[MT_COLOR_BOLD] = 0;
149   ColorDefs[MT_COLOR_UNDERLINE] = 0;
150
151 #ifdef HAVE_COLOR
152   start_color ();
153 #endif
154 }
155
156 #ifdef HAVE_COLOR
157
158 #ifdef USE_SLANG_CURSES
159 static char *get_color_name (char *dest, size_t destlen, int val)
160 {
161   static char *missing[3] = { "brown", "lightgray", "default" };
162   int i;
163
164   switch (val) {
165   case COLOR_YELLOW:
166     strfcpy (dest, missing[0], destlen);
167     return dest;
168
169   case COLOR_WHITE:
170     strfcpy (dest, missing[1], destlen);
171     return dest;
172
173   case COLOR_DEFAULT:
174     strfcpy (dest, missing[2], destlen);
175     return dest;
176   }
177
178   for (i = 0; Colors[i].name; i++) {
179     if (Colors[i].value == val) {
180       strfcpy (dest, Colors[i].name, destlen);
181       return dest;
182     }
183   }
184
185   /* Sigh. If we got this far, the color is of the form 'colorN'
186    * Slang can handle this itself, so just return 'colorN'
187    */
188
189   snprintf (dest, destlen, "color%d", val);
190   return dest;
191 }
192 #endif
193
194 int mutt_alloc_color (int fg, int bg)
195 {
196   COLOR_LIST *p = ColorList;
197   int i;
198
199 #if defined (USE_SLANG_CURSES)
200   char fgc[SHORT_STRING], bgc[SHORT_STRING];
201 #endif
202
203   /* check to see if this color is already allocated to save space */
204   while (p) {
205     if (p->fg == fg && p->bg == bg) {
206       (p->count)++;
207       return (COLOR_PAIR (p->index));
208     }
209     p = p->next;
210   }
211
212   /* check to see if there are colors left */
213   if (++UserColors > COLOR_PAIRS)
214     return (A_NORMAL);
215
216   /* find the smallest available index (object) */
217   i = 1;
218   FOREVER {
219     p = ColorList;
220     while (p) {
221       if (p->index == i)
222         break;
223       p = p->next;
224     }
225     if (p == NULL)
226       break;
227     i++;
228   }
229
230   p = (COLOR_LIST *) safe_malloc (sizeof (COLOR_LIST));
231   p->next = ColorList;
232   ColorList = p;
233
234   p->index = i;
235   p->count = 1;
236   p->bg = bg;
237   p->fg = fg;
238
239 #if defined (USE_SLANG_CURSES)
240   if (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
241     SLtt_set_color (i, NULL, get_color_name (fgc, sizeof (fgc), fg),
242                     get_color_name (bgc, sizeof (bgc), bg));
243   else
244 #elif defined (HAVE_USE_DEFAULT_COLORS)
245   if (fg == COLOR_DEFAULT)
246     fg = -1;
247   if (bg == COLOR_DEFAULT)
248     bg = -1;
249 #endif
250
251   init_pair (i, fg, bg);
252
253   dprint (1, (debugfile, "mutt_alloc_color(): Color pairs used so far: %d\n",
254               UserColors));
255
256   return (COLOR_PAIR (p->index));
257 }
258
259 void mutt_free_color (int fg, int bg)
260 {
261   COLOR_LIST *p, *q;
262
263   p = ColorList;
264   while (p) {
265     if (p->fg == fg && p->bg == bg) {
266       (p->count)--;
267       if (p->count > 0)
268         return;
269
270       UserColors--;
271       dprint (1,
272               (debugfile, "mutt_free_color(): Color pairs used so far: %d\n",
273                UserColors));
274
275       if (p == ColorList) {
276         ColorList = ColorList->next;
277         FREE (&p);
278         return;
279       }
280       q = ColorList;
281       while (q) {
282         if (q->next == p) {
283           q->next = p->next;
284           FREE (&p);
285           return;
286         }
287         q = q->next;
288       }
289       /* can't get here */
290     }
291     p = p->next;
292   }
293 }
294
295 #endif /* HAVE_COLOR */
296
297
298 #ifdef HAVE_COLOR
299
300 static int
301 parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER * err)
302 {
303   char *eptr;
304
305   if (mutt_strncasecmp (s, "bright", 6) == 0) {
306     *attr |= brite;
307     s += 6;
308   }
309
310   /* allow aliases for xterm color resources */
311   if (mutt_strncasecmp (s, "color", 5) == 0) {
312     s += 5;
313     *col = strtol (s, &eptr, 10);
314     if (!*s || *eptr || *col < 0 ||
315         (*col >= COLORS && !option (OPTNOCURSES) && has_colors ())) {
316       snprintf (err->data, err->dsize, _("%s: color not supported by term"),
317                 s);
318       return (-1);
319     }
320   }
321   else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) {
322     snprintf (err->data, err->dsize, _("%s: no such color"), s);
323     return (-1);
324   }
325
326   return 0;
327 }
328
329 #endif
330
331
332 /* usage: uncolor index pattern [pattern...]
333  *        unmono  index pattern [pattern...]
334  */
335
336 static int
337 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
338                      BUFFER * err, short parse_uncolor);
339
340
341 #ifdef HAVE_COLOR
342
343 int mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
344                         BUFFER * err)
345 {
346   return _mutt_parse_uncolor (buf, s, data, err, 1);
347 }
348
349 #endif
350
351 int mutt_parse_unmono (BUFFER * buf, BUFFER * s, unsigned long data,
352                        BUFFER * err)
353 {
354   return _mutt_parse_uncolor (buf, s, data, err, 0);
355 }
356
357 static int
358 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
359                      BUFFER * err, short parse_uncolor)
360 {
361   int object = 0, do_cache = 0;
362   COLOR_LINE *tmp, *last = NULL;
363
364   mutt_extract_token (buf, s, 0);
365
366   if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) {
367     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
368     return (-1);
369   }
370
371   if (mutt_strncmp (buf->data, "index", 5) != 0) {
372     snprintf (err->data, err->dsize,
373               _("%s: command valid only for index object"),
374               parse_uncolor ? "uncolor" : "unmono");
375     return (-1);
376   }
377
378   if (!MoreArgs (s)) {
379     snprintf (err->data, err->dsize,
380               _("%s: too few arguments"),
381               parse_uncolor ? "uncolor" : "unmono");
382     return (-1);
383   }
384
385   if (
386 #ifdef HAVE_COLOR
387        /* we're running without curses */
388        option (OPTNOCURSES)
389        ||                       /* we're parsing an uncolor command, and have no colors */
390        (parse_uncolor && !has_colors ())
391        /* we're parsing an unmono command, and have colors */
392        || (!parse_uncolor && has_colors ())
393 #else
394        /* We don't even have colors compiled in */
395        parse_uncolor
396 #endif
397     ) {
398     /* just eat the command, but don't do anything real about it */
399     do
400       mutt_extract_token (buf, s, 0);
401     while (MoreArgs (s));
402
403     return 0;
404   }
405
406
407   do {
408     mutt_extract_token (buf, s, 0);
409     if (!mutt_strcmp ("*", buf->data)) {
410       for (tmp = ColorIndexList; tmp;) {
411         if (!do_cache)
412           do_cache = 1;
413         last = tmp;
414         tmp = tmp->next;
415         mutt_free_color_line (&last, parse_uncolor);
416       }
417       ColorIndexList = NULL;
418     }
419     else {
420       for (last = NULL, tmp = ColorIndexList; tmp;
421            last = tmp, tmp = tmp->next) {
422         if (!mutt_strcmp (buf->data, tmp->pattern)) {
423           if (!do_cache)
424             do_cache = 1;
425           dprint (1,
426                   (debugfile, "Freeing pattern \"%s\" from ColorIndexList\n",
427                    tmp->pattern));
428           if (last)
429             last->next = tmp->next;
430           else
431             ColorIndexList = tmp->next;
432           mutt_free_color_line (&tmp, parse_uncolor);
433           break;
434         }
435       }
436     }
437   }
438   while (MoreArgs (s));
439
440
441   if (do_cache && !option (OPTNOCURSES)) {
442     int i;
443
444     set_option (OPTFORCEREDRAWINDEX);
445     /* force re-caching of index colors */
446     for (i = 0; Context && i < Context->msgcount; i++)
447       Context->hdrs[i]->pair = 0;
448   }
449   return (0);
450 }
451
452
453 static int
454 add_pattern (COLOR_LINE ** top, const char *s, int sensitive,
455              int fg, int bg, int attr, BUFFER * err, int is_index)
456 {
457
458   /* is_index used to store compiled pattern
459    * only for `index' color object 
460    * when called from mutt_parse_color() */
461
462   COLOR_LINE *tmp = *top;
463
464   while (tmp) {
465     if (sensitive) {
466       if (mutt_strcmp (s, tmp->pattern) == 0)
467         break;
468     }
469     else {
470       if (mutt_strcasecmp (s, tmp->pattern) == 0)
471         break;
472     }
473     tmp = tmp->next;
474   }
475
476   if (tmp) {
477 #ifdef HAVE_COLOR
478     if (fg != -1 && bg != -1) {
479       if (tmp->fg != fg || tmp->bg != bg) {
480         mutt_free_color (tmp->fg, tmp->bg);
481         tmp->fg = fg;
482         tmp->bg = bg;
483         attr |= mutt_alloc_color (fg, bg);
484       }
485       else
486         attr |= (tmp->pair & ~A_BOLD);
487     }
488 #endif /* HAVE_COLOR */
489     tmp->pair = attr;
490   }
491   else {
492     int r;
493     char buf[STRING];
494
495     tmp = mutt_new_color_line ();
496     if (is_index) {
497       int i;
498
499       strfcpy (buf, NONULL (s), sizeof (buf));
500       mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
501       if ((tmp->color_pattern =
502            mutt_pattern_comp (buf, M_FULL_MSG, err)) == NULL) {
503         mutt_free_color_line (&tmp, 1);
504         return -1;
505       }
506       /* force re-caching of index colors */
507       for (i = 0; Context && i < Context->msgcount; i++)
508         Context->hdrs[i]->pair = 0;
509     }
510     else
511       if ((r =
512            REGCOMP (&tmp->rx, s,
513                     (sensitive ? mutt_which_case (s) : REG_ICASE))) != 0) {
514       regerror (r, &tmp->rx, err->data, err->dsize);
515       mutt_free_color_line (&tmp, 1);
516       return (-1);
517     }
518     tmp->next = *top;
519     tmp->pattern = safe_strdup (s);
520 #ifdef HAVE_COLOR
521     if (fg != -1 && bg != -1) {
522       tmp->fg = fg;
523       tmp->bg = bg;
524       attr |= mutt_alloc_color (fg, bg);
525     }
526 #endif
527     tmp->pair = attr;
528     *top = tmp;
529   }
530
531   return 0;
532 }
533
534 static int
535 parse_object (BUFFER * buf, BUFFER * s, int *o, int *ql, BUFFER * err)
536 {
537   int q_level = 0;
538   char *eptr;
539
540   if (!MoreArgs (s)) {
541     strfcpy (err->data, _("Missing arguments."), err->dsize);
542     return -1;
543   }
544
545   mutt_extract_token (buf, s, 0);
546   if (!mutt_strncmp (buf->data, "quoted", 6)) {
547     if (buf->data[6]) {
548       *ql = strtol (buf->data + 6, &eptr, 10);
549       if (*eptr || q_level < 0) {
550         snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
551         return -1;
552       }
553     }
554     else
555       *ql = 0;
556
557     *o = MT_COLOR_QUOTED;
558   }
559   else if ((*o = mutt_getvaluebyname (buf->data, Fields)) == -1) {
560     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
561     return (-1);
562   }
563
564   return 0;
565 }
566
567 typedef int (*parser_callback_t) (BUFFER *, BUFFER *, int *, int *, int *,
568                                   BUFFER *);
569
570 #ifdef HAVE_COLOR
571
572 static int
573 parse_color_pair (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
574                   BUFFER * err)
575 {
576   if (!MoreArgs (s)) {
577     strfcpy (err->data, _("color: too few arguments"), err->dsize);
578     return (-1);
579   }
580
581   mutt_extract_token (buf, s, 0);
582
583   if (parse_color_name (buf->data, fg, attr, A_BOLD, err) != 0)
584     return (-1);
585
586   if (!MoreArgs (s)) {
587     strfcpy (err->data, _("color: too few arguments"), err->dsize);
588     return (-1);
589   }
590
591   mutt_extract_token (buf, s, 0);
592
593   if (parse_color_name (buf->data, bg, attr, A_BLINK, err) != 0)
594     return (-1);
595
596   return 0;
597 }
598
599 #endif
600
601 static int
602 parse_attr_spec (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
603                  BUFFER * err)
604 {
605
606   if (fg)
607     *fg = -1;
608   if (bg)
609     *bg = -1;
610
611   if (!MoreArgs (s)) {
612     strfcpy (err->data, _("mono: too few arguments"), err->dsize);
613     return (-1);
614   }
615
616   mutt_extract_token (buf, s, 0);
617
618   if (ascii_strcasecmp ("bold", buf->data) == 0)
619     *attr |= A_BOLD;
620   else if (ascii_strcasecmp ("underline", buf->data) == 0)
621     *attr |= A_UNDERLINE;
622   else if (ascii_strcasecmp ("none", buf->data) == 0)
623     *attr = A_NORMAL;
624   else if (ascii_strcasecmp ("reverse", buf->data) == 0)
625     *attr |= A_REVERSE;
626   else if (ascii_strcasecmp ("standout", buf->data) == 0)
627     *attr |= A_STANDOUT;
628   else if (ascii_strcasecmp ("normal", buf->data) == 0)
629     *attr = A_NORMAL;           /* needs use = instead of |= to clear other bits */
630   else {
631     snprintf (err->data, err->dsize, _("%s: no such attribute"), buf->data);
632     return (-1);
633   }
634
635   return 0;
636 }
637
638 static int fgbgattr_to_color (int fg, int bg, int attr)
639 {
640 #ifdef HAVE_COLOR
641   if (fg != -1 && bg != -1)
642     return attr | mutt_alloc_color (fg, bg);
643   else
644 #endif
645     return attr;
646 }
647
648 /* usage: color <object> <fg> <bg> [ <regexp> ] 
649  *        mono  <object> <attr> [ <regexp> ]
650  */
651
652 static int
653 _mutt_parse_color (BUFFER * buf, BUFFER * s, BUFFER * err,
654                    parser_callback_t callback, short dry_run)
655 {
656   int object = 0, attr = 0, fg = 0, bg = 0, q_level = 0;
657   int r = 0;
658
659   if (parse_object (buf, s, &object, &q_level, err) == -1)
660     return -1;
661
662   if (callback (buf, s, &fg, &bg, &attr, err) == -1)
663     return -1;
664
665   /* extract a regular expression if needed */
666
667   if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY
668       || object == MT_COLOR_INDEX) {
669     if (!MoreArgs (s)) {
670       strfcpy (err->data, _("too few arguments"), err->dsize);
671       return (-1);
672     }
673
674     mutt_extract_token (buf, s, 0);
675   }
676
677   if (MoreArgs (s)) {
678     strfcpy (err->data, _("too many arguments"), err->dsize);
679     return (-1);
680   }
681
682   /* dry run? */
683
684   if (dry_run)
685     return 0;
686
687
688 #ifdef HAVE_COLOR
689 # ifdef HAVE_USE_DEFAULT_COLORS
690   if (!option (OPTNOCURSES) && has_colors ()
691       /* delay use_default_colors() until needed, since it initializes things */
692       && (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
693       && use_default_colors () != OK) {
694     strfcpy (err->data, _("default colors not supported"), err->dsize);
695     return (-1);
696   }
697 # endif /* HAVE_USE_DEFAULT_COLORS */
698 #endif
699
700   if (object == MT_COLOR_HEADER)
701     r = add_pattern (&ColorHdrList, buf->data, 0, fg, bg, attr, err, 0);
702   else if (object == MT_COLOR_BODY)
703     r = add_pattern (&ColorBodyList, buf->data, 1, fg, bg, attr, err, 0);
704   else if (object == MT_COLOR_INDEX) {
705     r = add_pattern (&ColorIndexList, buf->data, 1, fg, bg, attr, err, 1);
706     set_option (OPTFORCEREDRAWINDEX);
707   }
708   else if (object == MT_COLOR_QUOTED) {
709     if (q_level >= ColorQuoteSize) {
710       safe_realloc (&ColorQuote, (ColorQuoteSize += 2) * sizeof (int));
711       ColorQuote[ColorQuoteSize - 2] = ColorDefs[MT_COLOR_QUOTED];
712       ColorQuote[ColorQuoteSize - 1] = ColorDefs[MT_COLOR_QUOTED];
713     }
714     if (q_level >= ColorQuoteUsed)
715       ColorQuoteUsed = q_level + 1;
716     if (q_level == 0) {
717       ColorDefs[MT_COLOR_QUOTED] = fgbgattr_to_color (fg, bg, attr);
718
719       ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED];
720       for (q_level = 1; q_level < ColorQuoteUsed; q_level++) {
721         if (ColorQuote[q_level] == A_NORMAL)
722           ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED];
723       }
724     }
725     else
726       ColorQuote[q_level] = fgbgattr_to_color (fg, bg, attr);
727   }
728   else
729     ColorDefs[object] = fgbgattr_to_color (fg, bg, attr);
730
731 #ifdef HAVE_COLOR
732 # ifdef HAVE_BKGDSET
733   if (object == MT_COLOR_NORMAL && !option (OPTNOCURSES) && has_colors ())
734     BKGDSET (MT_COLOR_NORMAL);
735 # endif
736 #endif
737
738   return (r);
739 }
740
741 #ifdef HAVE_COLOR
742
743 int mutt_parse_color (BUFFER * buff, BUFFER * s, unsigned long data,
744                       BUFFER * err)
745 {
746   int dry_run = 0;
747
748   if (option (OPTNOCURSES) || !has_colors ())
749     dry_run = 1;
750
751   return _mutt_parse_color (buff, s, err, parse_color_pair, dry_run);
752 }
753
754 #endif
755
756 int mutt_parse_mono (BUFFER * buff, BUFFER * s, unsigned long data,
757                      BUFFER * err)
758 {
759   int dry_run = 0;
760
761 #ifdef HAVE_COLOR
762   if (option (OPTNOCURSES) || has_colors ())
763     dry_run = 1;
764 #else
765   if (option (OPTNOCURSES))
766     dry_run = 1;
767 #endif
768
769   return _mutt_parse_color (buff, s, err, parse_attr_spec, dry_run);
770 }