Nico Golde:
[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 <string.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21
22 /* globals */
23 int *ColorQuote;
24 int ColorQuoteUsed;
25 int ColorDefs[MT_COLOR_MAX];
26 COLOR_LINE *ColorHdrList = NULL;
27 COLOR_LINE *ColorBodyList = NULL;
28 COLOR_LINE *ColorIndexList = NULL;
29
30 /* local to this file */
31 static int ColorQuoteSize;
32
33 #ifdef HAVE_COLOR
34
35 #define COLOR_DEFAULT (-2)
36
37 typedef struct color_list {
38   short fg;
39   short bg;
40   short index;
41   short count;
42   struct color_list *next;
43 } COLOR_LIST;
44
45 static COLOR_LIST *ColorList = NULL;
46 static int UserColors = 0;
47
48 static struct mapping_t Colors[] = {
49   {"black", COLOR_BLACK},
50   {"blue", COLOR_BLUE},
51   {"cyan", COLOR_CYAN},
52   {"green", COLOR_GREEN},
53   {"magenta", COLOR_MAGENTA},
54   {"red", COLOR_RED},
55   {"white", COLOR_WHITE},
56   {"yellow", COLOR_YELLOW},
57 #if defined (USE_SLANG_CURSES) || defined (HAVE_USE_DEFAULT_COLORS)
58   {"default", COLOR_DEFAULT},
59 #endif
60   {0, 0}
61 };
62
63 #endif /* HAVE_COLOR */
64
65 static struct mapping_t Fields[] = {
66   {"hdrdefault", MT_COLOR_HDEFAULT},
67   {"quoted", MT_COLOR_QUOTED},
68   {"signature", MT_COLOR_SIGNATURE},
69   {"indicator", MT_COLOR_INDICATOR},
70   {"status", MT_COLOR_STATUS},
71   {"tree", MT_COLOR_TREE},
72   {"error", MT_COLOR_ERROR},
73   {"normal", MT_COLOR_NORMAL},
74   {"tilde", MT_COLOR_TILDE},
75   {"markers", MT_COLOR_MARKERS},
76   {"header", MT_COLOR_HEADER},
77   {"body", MT_COLOR_BODY},
78   {"message", MT_COLOR_MESSAGE},
79   {"attachment", MT_COLOR_ATTACHMENT},
80   {"search", MT_COLOR_SEARCH},
81   {"bold", MT_COLOR_BOLD},
82   {"underline", MT_COLOR_UNDERLINE},
83   {"index", MT_COLOR_INDEX},
84   {"sidebar_new", MT_COLOR_NEW},
85   {"sidebar", MT_COLOR_SIDEBAR},
86   {"sidebar_flagged", MT_COLOR_FLAGGED},
87   {NULL, 0}
88 };
89
90 #define COLOR_QUOTE_INIT        8
91
92 static COLOR_LINE *mutt_new_color_line (void)
93 {
94   COLOR_LINE *p = safe_calloc (1, sizeof (COLOR_LINE));
95
96   p->fg = p->bg = -1;
97
98   return (p);
99 }
100
101 static void mutt_free_color_line (COLOR_LINE ** l, int free_colors)
102 {
103   COLOR_LINE *tmp;
104
105   if (!l || !*l)
106     return;
107
108   tmp = *l;
109
110 #ifdef HAVE_COLOR
111   if (free_colors && tmp->fg != -1 && tmp->bg != -1)
112     mutt_free_color (tmp->fg, tmp->bg);
113 #endif
114
115   /* we should really introduce a container
116    * type for regular expressions.
117    */
118
119   regfree (&tmp->rx);
120   mutt_pattern_free (&tmp->color_pattern);
121   FREE (&tmp->pattern);
122   FREE (l);
123 }
124
125 void ci_start_color (void)
126 {
127   memset (ColorDefs, A_NORMAL, sizeof (int) * MT_COLOR_MAX);
128   ColorQuote = (int *) safe_malloc (COLOR_QUOTE_INIT * sizeof (int));
129   memset (ColorQuote, A_NORMAL, sizeof (int) * COLOR_QUOTE_INIT);
130   ColorQuoteSize = COLOR_QUOTE_INIT;
131   ColorQuoteUsed = 0;
132
133   /* set some defaults */
134   ColorDefs[MT_COLOR_STATUS] = A_REVERSE;
135   ColorDefs[MT_COLOR_INDICATOR] = A_REVERSE;
136   ColorDefs[MT_COLOR_SEARCH] = A_REVERSE;
137   ColorDefs[MT_COLOR_MARKERS] = A_REVERSE;
138   /* special meaning: toggle the relevant attribute */
139   ColorDefs[MT_COLOR_BOLD] = 0;
140   ColorDefs[MT_COLOR_UNDERLINE] = 0;
141
142 #ifdef HAVE_COLOR
143   start_color ();
144 #endif
145 }
146
147 #ifdef HAVE_COLOR
148
149 #ifdef USE_SLANG_CURSES
150 static char *get_color_name (char *dest, size_t destlen, int val)
151 {
152   static char *missing[3] = { "brown", "lightgray", "default" };
153   int i;
154
155   switch (val) {
156   case COLOR_YELLOW:
157     strfcpy (dest, missing[0], destlen);
158     return dest;
159
160   case COLOR_WHITE:
161     strfcpy (dest, missing[1], destlen);
162     return dest;
163
164   case COLOR_DEFAULT:
165     strfcpy (dest, missing[2], destlen);
166     return dest;
167   }
168
169   for (i = 0; Colors[i].name; i++) {
170     if (Colors[i].value == val) {
171       strfcpy (dest, Colors[i].name, destlen);
172       return dest;
173     }
174   }
175
176   /* Sigh. If we got this far, the color is of the form 'colorN'
177    * Slang can handle this itself, so just return 'colorN'
178    */
179
180   snprintf (dest, destlen, "color%d", val);
181   return dest;
182 }
183 #endif
184
185 int mutt_alloc_color (int fg, int bg)
186 {
187   COLOR_LIST *p = ColorList;
188   int i;
189
190 #if defined (USE_SLANG_CURSES)
191   char fgc[SHORT_STRING], bgc[SHORT_STRING];
192 #endif
193
194   /* check to see if this color is already allocated to save space */
195   while (p) {
196     if (p->fg == fg && p->bg == bg) {
197       (p->count)++;
198       return (COLOR_PAIR (p->index));
199     }
200     p = p->next;
201   }
202
203   /* check to see if there are colors left */
204   if (++UserColors > COLOR_PAIRS)
205     return (A_NORMAL);
206
207   /* find the smallest available index (object) */
208   i = 1;
209   FOREVER {
210     p = ColorList;
211     while (p) {
212       if (p->index == i)
213         break;
214       p = p->next;
215     }
216     if (p == NULL)
217       break;
218     i++;
219   }
220
221   p = (COLOR_LIST *) safe_malloc (sizeof (COLOR_LIST));
222   p->next = ColorList;
223   ColorList = p;
224
225   p->index = i;
226   p->count = 1;
227   p->bg = bg;
228   p->fg = fg;
229
230 #if defined (USE_SLANG_CURSES)
231   if (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
232     SLtt_set_color (i, NULL, get_color_name (fgc, sizeof (fgc), fg),
233                     get_color_name (bgc, sizeof (bgc), bg));
234   else
235 #elif defined (HAVE_USE_DEFAULT_COLORS)
236   if (fg == COLOR_DEFAULT)
237     fg = -1;
238   if (bg == COLOR_DEFAULT)
239     bg = -1;
240 #endif
241
242   init_pair (i, fg, bg);
243
244   dprint (1, (debugfile, "mutt_alloc_color(): Color pairs used so far: %d\n",
245               UserColors));
246
247   return (COLOR_PAIR (p->index));
248 }
249
250 void mutt_free_color (int fg, int bg)
251 {
252   COLOR_LIST *p, *q;
253
254   p = ColorList;
255   while (p) {
256     if (p->fg == fg && p->bg == bg) {
257       (p->count)--;
258       if (p->count > 0)
259         return;
260
261       UserColors--;
262       dprint (1,
263               (debugfile, "mutt_free_color(): Color pairs used so far: %d\n",
264                UserColors));
265
266       if (p == ColorList) {
267         ColorList = ColorList->next;
268         FREE (&p);
269         return;
270       }
271       q = ColorList;
272       while (q) {
273         if (q->next == p) {
274           q->next = p->next;
275           FREE (&p);
276           return;
277         }
278         q = q->next;
279       }
280       /* can't get here */
281     }
282     p = p->next;
283   }
284 }
285
286 #endif /* HAVE_COLOR */
287
288
289 #ifdef HAVE_COLOR
290
291 static int
292 parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER * err)
293 {
294   char *eptr;
295
296   if (mutt_strncasecmp (s, "bright", 6) == 0) {
297     *attr |= brite;
298     s += 6;
299   }
300
301   /* allow aliases for xterm color resources */
302   if (mutt_strncasecmp (s, "color", 5) == 0) {
303     s += 5;
304     *col = strtol (s, &eptr, 10);
305     if (!*s || *eptr || *col < 0 ||
306         (*col >= COLORS && !option (OPTNOCURSES) && has_colors ())) {
307       snprintf (err->data, err->dsize, _("%s: color not supported by term"),
308                 s);
309       return (-1);
310     }
311   }
312   else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) {
313     snprintf (err->data, err->dsize, _("%s: no such color"), s);
314     return (-1);
315   }
316
317   return 0;
318 }
319
320 #endif
321
322
323 /* usage: uncolor index pattern [pattern...]
324  *        unmono  index pattern [pattern...]
325  */
326
327 static int
328 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
329                      BUFFER * err, short parse_uncolor);
330
331
332 #ifdef HAVE_COLOR
333
334 int mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
335                         BUFFER * err)
336 {
337   return _mutt_parse_uncolor (buf, s, data, err, 1);
338 }
339
340 #endif
341
342 int mutt_parse_unmono (BUFFER * buf, BUFFER * s, unsigned long data,
343                        BUFFER * err)
344 {
345   return _mutt_parse_uncolor (buf, s, data, err, 0);
346 }
347
348 static int
349 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
350                      BUFFER * err, short parse_uncolor)
351 {
352   int object = 0, do_cache = 0;
353   COLOR_LINE *tmp, *last = NULL;
354
355   mutt_extract_token (buf, s, 0);
356
357   if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) {
358     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
359     return (-1);
360   }
361
362   if (mutt_strncmp (buf->data, "index", 5) != 0) {
363     snprintf (err->data, err->dsize,
364               _("%s: command valid only for index object"),
365               parse_uncolor ? "uncolor" : "unmono");
366     return (-1);
367   }
368
369   if (!MoreArgs (s)) {
370     snprintf (err->data, err->dsize,
371               _("%s: too few arguments"),
372               parse_uncolor ? "uncolor" : "unmono");
373     return (-1);
374   }
375
376   if (
377 #ifdef HAVE_COLOR
378        /* we're running without curses */
379        option (OPTNOCURSES)
380        ||                       /* we're parsing an uncolor command, and have no colors */
381        (parse_uncolor && !has_colors ())
382        /* we're parsing an unmono command, and have colors */
383        || (!parse_uncolor && has_colors ())
384 #else
385        /* We don't even have colors compiled in */
386        parse_uncolor
387 #endif
388     ) {
389     /* just eat the command, but don't do anything real about it */
390     do
391       mutt_extract_token (buf, s, 0);
392     while (MoreArgs (s));
393
394     return 0;
395   }
396
397
398   do {
399     mutt_extract_token (buf, s, 0);
400     if (!mutt_strcmp ("*", buf->data)) {
401       for (tmp = ColorIndexList; tmp;) {
402         if (!do_cache)
403           do_cache = 1;
404         last = tmp;
405         tmp = tmp->next;
406         mutt_free_color_line (&last, parse_uncolor);
407       }
408       ColorIndexList = NULL;
409     }
410     else {
411       for (last = NULL, tmp = ColorIndexList; tmp;
412            last = tmp, tmp = tmp->next) {
413         if (!mutt_strcmp (buf->data, tmp->pattern)) {
414           if (!do_cache)
415             do_cache = 1;
416           dprint (1,
417                   (debugfile, "Freeing pattern \"%s\" from ColorIndexList\n",
418                    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 (mutt_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 (!mutt_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 }