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