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