c573520e050ef910fffa159b91971378b1289b0b
[apps/madmutt.git] / lib-ui / color.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <lib-lib/mem.h>
15 #include <lib-lib/ascii.h>
16 #include <lib-lib/str.h>
17 #include <lib-lib/macros.h>
18 #include <lib-lib/buffer.h>
19 #include <lib-lib/mapping.h>
20
21 #include <lib-ui/curses.h>
22
23 #include "mutt.h"
24
25 #include "lib/debug.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 = p_new(COLOR_LINE, 1);
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   p_delete(&tmp->pattern);
131   p_delete(l);
132 }
133
134 void ci_start_color (void)
135 {
136     memset(ColorDefs, A_NORMAL, sizeof(int) * MT_COLOR_MAX);
137     ColorQuote = p_new(int, COLOR_QUOTE_INIT);
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     m_strcpy(dest, destlen, missing[0]);
167     return dest;
168
169   case COLOR_WHITE:
170     m_strcpy(dest, destlen, missing[1]);
171     return dest;
172
173   case COLOR_DEFAULT:
174     m_strcpy(dest, destlen, missing[2]);
175     return dest;
176   }
177
178   for (i = 0; Colors[i].name; i++) {
179     if (Colors[i].value == val) {
180       m_strcpy(dest, destlen, Colors[i].name);
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   for (;;) {
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 = p_new(COLOR_LIST, 1);
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   debug_print (1, ("Color pairs used so far: %d\n", UserColors));
254
255   return (COLOR_PAIR (p->index));
256 }
257
258 void mutt_free_color (int fg, int bg)
259 {
260   COLOR_LIST *p, *q;
261
262   p = ColorList;
263   while (p) {
264     if (p->fg == fg && p->bg == bg) {
265       (p->count)--;
266       if (p->count > 0)
267         return;
268
269       UserColors--;
270       debug_print (1, ("Color pairs used so far: %d\n", UserColors));
271
272       if (p == ColorList) {
273         ColorList = ColorList->next;
274         p_delete(&p);
275         return;
276       }
277       q = ColorList;
278       while (q) {
279         if (q->next == p) {
280           q->next = p->next;
281           p_delete(&p);
282           return;
283         }
284         q = q->next;
285       }
286       /* can't get here */
287     }
288     p = p->next;
289   }
290 }
291
292 #endif /* HAVE_COLOR */
293
294
295 #ifdef HAVE_COLOR
296
297 static int
298 parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER * err)
299 {
300   char *eptr;
301
302   if (m_strncasecmp(s, "bright", 6) == 0) {
303     *attr |= brite;
304     s += 6;
305   }
306
307   /* allow aliases for xterm color resources */
308   if (m_strncasecmp(s, "color", 5) == 0) {
309     s += 5;
310     *col = strtol (s, &eptr, 10);
311     if (!*s || *eptr || *col < 0 ||
312         (*col >= COLORS && !option (OPTNOCURSES) && has_colors ())) {
313       snprintf (err->data, err->dsize, _("%s: color not supported by term"),
314                 s);
315       return (-1);
316     }
317   }
318   else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) {
319     snprintf (err->data, err->dsize, _("%s: no such color"), s);
320     return (-1);
321   }
322
323   return 0;
324 }
325
326 #endif
327
328
329 /* usage: uncolor index pattern [pattern...]
330  *        unmono  index pattern [pattern...]
331  */
332
333 static int
334 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
335                      BUFFER * err, short parse_uncolor);
336
337
338 #ifdef HAVE_COLOR
339
340 int mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
341                         BUFFER * err)
342 {
343   return _mutt_parse_uncolor (buf, s, data, err, 1);
344 }
345
346 #endif
347
348 int mutt_parse_unmono (BUFFER * buf, BUFFER * s, unsigned long data,
349                        BUFFER * err)
350 {
351   return _mutt_parse_uncolor (buf, s, data, err, 0);
352 }
353
354 static int
355 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
356                      BUFFER * err, short parse_uncolor)
357 {
358   int object = 0, do_cache = 0;
359   COLOR_LINE *tmp, *last = NULL;
360
361   mutt_extract_token (buf, s, 0);
362
363   if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) {
364     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
365     return (-1);
366   }
367
368   if (m_strncmp(buf->data, "index", 5) != 0) {
369     snprintf (err->data, err->dsize,
370               _("%s: command valid only for index object"),
371               parse_uncolor ? "uncolor" : "unmono");
372     return (-1);
373   }
374
375   if (!MoreArgs (s)) {
376     snprintf (err->data, err->dsize,
377               _("%s: too few arguments"),
378               parse_uncolor ? "uncolor" : "unmono");
379     return (-1);
380   }
381
382   if (
383 #ifdef HAVE_COLOR
384        /* we're running without curses */
385        option (OPTNOCURSES)
386        ||                       /* we're parsing an uncolor command, and have no colors */
387        (parse_uncolor && !has_colors ())
388        /* we're parsing an unmono command, and have colors */
389        || (!parse_uncolor && has_colors ())
390 #else
391        /* We don't even have colors compiled in */
392        parse_uncolor
393 #endif
394     ) {
395     /* just eat the command, but don't do anything real about it */
396     do
397       mutt_extract_token (buf, s, 0);
398     while (MoreArgs (s));
399
400     return 0;
401   }
402
403
404   do {
405     mutt_extract_token (buf, s, 0);
406     if (!m_strcmp("*", buf->data)) {
407       for (tmp = ColorIndexList; tmp;) {
408         if (!do_cache)
409           do_cache = 1;
410         last = tmp;
411         tmp = tmp->next;
412         mutt_free_color_line (&last, parse_uncolor);
413       }
414       ColorIndexList = NULL;
415     }
416     else {
417       for (last = NULL, tmp = ColorIndexList; tmp;
418            last = tmp, tmp = tmp->next) {
419         if (!m_strcmp(buf->data, tmp->pattern)) {
420           if (!do_cache)
421             do_cache = 1;
422           debug_print (1, ("Freeing pattern \"%s\" from ColorIndexList\n", tmp->pattern));
423           if (last)
424             last->next = tmp->next;
425           else
426             ColorIndexList = tmp->next;
427           mutt_free_color_line (&tmp, parse_uncolor);
428           break;
429         }
430       }
431     }
432   }
433   while (MoreArgs (s));
434
435
436   if (do_cache && !option (OPTNOCURSES)) {
437     int i;
438
439     set_option (OPTFORCEREDRAWINDEX);
440     /* force re-caching of index colors */
441     for (i = 0; Context && i < Context->msgcount; i++)
442       Context->hdrs[i]->pair = 0;
443   }
444   return (0);
445 }
446
447
448 static int
449 add_pattern (COLOR_LINE ** top, const char *s, int sensitive,
450              int fg, int bg, int attr, BUFFER * err, int is_index)
451 {
452
453   /* is_index used to store compiled pattern
454    * only for `index' color object 
455    * when called from mutt_parse_color() */
456
457   COLOR_LINE *tmp = *top;
458
459   while (tmp) {
460     if (sensitive) {
461       if (m_strcmp(s, tmp->pattern) == 0)
462         break;
463     }
464     else {
465       if (m_strcasecmp(s, tmp->pattern) == 0)
466         break;
467     }
468     tmp = tmp->next;
469   }
470
471   if (tmp) {
472 #ifdef HAVE_COLOR
473     if (fg != -1 && bg != -1) {
474       if (tmp->fg != fg || tmp->bg != bg) {
475         mutt_free_color (tmp->fg, tmp->bg);
476         tmp->fg = fg;
477         tmp->bg = bg;
478         attr |= mutt_alloc_color (fg, bg);
479       }
480       else
481         attr |= (tmp->pair & ~A_BOLD);
482     }
483 #endif /* HAVE_COLOR */
484     tmp->pair = attr;
485   }
486   else {
487     int r;
488     char buf[STRING];
489
490     tmp = mutt_new_color_line ();
491     if (is_index) {
492       int i;
493
494       m_strcpy(buf, sizeof(buf), NONULL(s));
495       mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
496       if ((tmp->color_pattern =
497            mutt_pattern_comp (buf, M_FULL_MSG, err)) == NULL) {
498         mutt_free_color_line (&tmp, 1);
499         return -1;
500       }
501       /* force re-caching of index colors */
502       for (i = 0; Context && i < Context->msgcount; i++)
503         Context->hdrs[i]->pair = 0;
504     }
505     else
506       if ((r =
507            REGCOMP (&tmp->rx, s,
508                     (sensitive ? mutt_which_case (s) : REG_ICASE))) != 0) {
509       regerror (r, &tmp->rx, err->data, err->dsize);
510       mutt_free_color_line (&tmp, 1);
511       return (-1);
512     }
513     tmp->next = *top;
514     tmp->pattern = m_strdup(s);
515 #ifdef HAVE_COLOR
516     if (fg != -1 && bg != -1) {
517       tmp->fg = fg;
518       tmp->bg = bg;
519       attr |= mutt_alloc_color (fg, bg);
520     }
521 #endif
522     tmp->pair = attr;
523     *top = tmp;
524   }
525
526   return 0;
527 }
528
529 static int
530 parse_object (BUFFER * buf, BUFFER * s, int *o, int *ql, BUFFER * err)
531 {
532   int q_level = 0;
533   char *eptr;
534
535   if (!MoreArgs (s)) {
536     m_strcpy(err->data, err->dsize, _("Missing arguments."));
537     return -1;
538   }
539
540   mutt_extract_token (buf, s, 0);
541   if (!m_strncmp(buf->data, "quoted", 6)) {
542     if (buf->data[6]) {
543       *ql = strtol (buf->data + 6, &eptr, 10);
544       if (*eptr || q_level < 0) {
545         snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
546         return -1;
547       }
548     }
549     else
550       *ql = 0;
551
552     *o = MT_COLOR_QUOTED;
553   }
554   else if ((*o = mutt_getvaluebyname (buf->data, Fields)) == -1) {
555     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
556     return (-1);
557   }
558
559   return 0;
560 }
561
562 typedef int (*parser_callback_t) (BUFFER *, BUFFER *, int *, int *, int *,
563                                   BUFFER *);
564
565 #ifdef HAVE_COLOR
566
567 static int
568 parse_color_pair (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
569                   BUFFER * err)
570 {
571   if (!MoreArgs (s)) {
572     m_strcpy(err->data, err->dsize, _("color: too few arguments"));
573     return (-1);
574   }
575
576   mutt_extract_token (buf, s, 0);
577
578   if (parse_color_name (buf->data, fg, attr, A_BOLD, err) != 0)
579     return (-1);
580
581   if (!MoreArgs (s)) {
582     m_strcpy(err->data, err->dsize, _("color: too few arguments"));
583     return (-1);
584   }
585
586   mutt_extract_token (buf, s, 0);
587
588   if (parse_color_name (buf->data, bg, attr, A_BLINK, err) != 0)
589     return (-1);
590
591   return 0;
592 }
593
594 #endif
595
596 static int
597 parse_attr_spec (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
598                  BUFFER * err)
599 {
600
601   if (fg)
602     *fg = -1;
603   if (bg)
604     *bg = -1;
605
606   if (!MoreArgs (s)) {
607     m_strcpy(err->data, err->dsize, _("mono: too few arguments"));
608     return (-1);
609   }
610
611   mutt_extract_token (buf, s, 0);
612
613   if (ascii_strcasecmp ("bold", buf->data) == 0)
614     *attr |= A_BOLD;
615   else if (ascii_strcasecmp ("underline", buf->data) == 0)
616     *attr |= A_UNDERLINE;
617   else if (ascii_strcasecmp ("none", buf->data) == 0)
618     *attr = A_NORMAL;
619   else if (ascii_strcasecmp ("reverse", buf->data) == 0)
620     *attr |= A_REVERSE;
621   else if (ascii_strcasecmp ("standout", buf->data) == 0)
622     *attr |= A_STANDOUT;
623   else if (ascii_strcasecmp ("normal", buf->data) == 0)
624     *attr = A_NORMAL;           /* needs use = instead of |= to clear other bits */
625   else {
626     snprintf (err->data, err->dsize, _("%s: no such attribute"), buf->data);
627     return (-1);
628   }
629
630   return 0;
631 }
632
633 static int fgbgattr_to_color (int fg, int bg, int attr)
634 {
635 #ifdef HAVE_COLOR
636   if (fg != -1 && bg != -1)
637     return attr | mutt_alloc_color (fg, bg);
638   else
639 #endif
640     return attr;
641 }
642
643 /* usage: color <object> <fg> <bg> [ <regexp> ] 
644  *        mono  <object> <attr> [ <regexp> ]
645  */
646
647 static int
648 _mutt_parse_color (BUFFER * buf, BUFFER * s, BUFFER * err,
649                    parser_callback_t callback, short dry_run)
650 {
651   int object = 0, attr = 0, fg = 0, bg = 0, q_level = 0;
652   int r = 0;
653
654   if (parse_object (buf, s, &object, &q_level, err) == -1)
655     return -1;
656
657   if (callback (buf, s, &fg, &bg, &attr, err) == -1)
658     return -1;
659
660   /* extract a regular expression if needed */
661
662   if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY
663       || object == MT_COLOR_INDEX) {
664     if (!MoreArgs (s)) {
665       m_strcpy(err->data, err->dsize, _("too few arguments"));
666       return (-1);
667     }
668
669     mutt_extract_token (buf, s, 0);
670   }
671
672   if (MoreArgs (s)) {
673     m_strcpy(err->data, err->dsize, _("too many arguments"));
674     return (-1);
675   }
676
677   /* dry run? */
678
679   if (dry_run)
680     return 0;
681
682
683 #ifdef HAVE_COLOR
684 # ifdef HAVE_USE_DEFAULT_COLORS
685   if (!option (OPTNOCURSES) && has_colors ()
686       /* delay use_default_colors() until needed, since it initializes things */
687       && (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
688       && use_default_colors () != OK) {
689     m_strcpy(err->data, err->dsize, _("default colors not supported"));
690     return (-1);
691   }
692 # endif /* HAVE_USE_DEFAULT_COLORS */
693 #endif
694
695   if (object == MT_COLOR_HEADER)
696     r = add_pattern (&ColorHdrList, buf->data, 0, fg, bg, attr, err, 0);
697   else if (object == MT_COLOR_BODY)
698     r = add_pattern (&ColorBodyList, buf->data, 1, fg, bg, attr, err, 0);
699   else if (object == MT_COLOR_INDEX) {
700     r = add_pattern (&ColorIndexList, buf->data, 1, fg, bg, attr, err, 1);
701     set_option (OPTFORCEREDRAWINDEX);
702   }
703   else if (object == MT_COLOR_QUOTED) {
704     if (q_level >= ColorQuoteSize) {
705       p_realloc(&ColorQuote, ColorQuoteSize += 2);
706       ColorQuote[ColorQuoteSize - 2] = ColorDefs[MT_COLOR_QUOTED];
707       ColorQuote[ColorQuoteSize - 1] = ColorDefs[MT_COLOR_QUOTED];
708     }
709     if (q_level >= ColorQuoteUsed)
710       ColorQuoteUsed = q_level + 1;
711     if (q_level == 0) {
712       ColorDefs[MT_COLOR_QUOTED] = fgbgattr_to_color (fg, bg, attr);
713
714       ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED];
715       for (q_level = 1; q_level < ColorQuoteUsed; q_level++) {
716         if (ColorQuote[q_level] == A_NORMAL)
717           ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED];
718       }
719     }
720     else
721       ColorQuote[q_level] = fgbgattr_to_color (fg, bg, attr);
722   }
723   else
724     ColorDefs[object] = fgbgattr_to_color (fg, bg, attr);
725
726 #ifdef HAVE_COLOR
727 # ifdef HAVE_BKGDSET
728   if (object == MT_COLOR_NORMAL && !option (OPTNOCURSES) && has_colors ())
729     BKGDSET (MT_COLOR_NORMAL);
730 # endif
731 #endif
732
733   return (r);
734 }
735
736 #ifdef HAVE_COLOR
737
738 int mutt_parse_color (BUFFER * buff, BUFFER * s, unsigned long data __attribute__ ((unused)),
739                       BUFFER * err)
740 {
741   int dry_run = 0;
742
743   if (option (OPTNOCURSES) || !has_colors ())
744     dry_run = 1;
745
746   return _mutt_parse_color (buff, s, err, parse_color_pair, dry_run);
747 }
748
749 #endif
750
751 int mutt_parse_mono (BUFFER * buff, BUFFER * s, unsigned long data __attribute__ ((unused)),
752                      BUFFER * err)
753 {
754   int dry_run = 0;
755
756 #ifdef HAVE_COLOR
757   if (option (OPTNOCURSES) || has_colors ())
758     dry_run = 1;
759 #else
760   if (option (OPTNOCURSES))
761     dry_run = 1;
762 #endif
763
764   return _mutt_parse_color (buff, s, err, parse_attr_spec, dry_run);
765 }