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