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