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