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