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