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