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