yes, we always have colors, screw stupid antiquated ncurses.
[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 (USE_SLANG_CURSES) || 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 #ifdef USE_SLANG_CURSES
133 static char *get_color_name (char *dest, size_t destlen, int val)
134 {
135   static char *missing[3] = { "brown", "lightgray", "default" };
136   int i;
137
138   switch (val) {
139   case COLOR_YELLOW:
140     m_strcpy(dest, destlen, missing[0]);
141     return dest;
142
143   case COLOR_WHITE:
144     m_strcpy(dest, destlen, missing[1]);
145     return dest;
146
147   case COLOR_DEFAULT:
148     m_strcpy(dest, destlen, missing[2]);
149     return dest;
150   }
151
152   for (i = 0; Colors[i].name; i++) {
153     if (Colors[i].value == val) {
154       m_strcpy(dest, destlen, Colors[i].name);
155       return dest;
156     }
157   }
158
159   /* Sigh. If we got this far, the color is of the form 'colorN'
160    * Slang can handle this itself, so just return 'colorN'
161    */
162
163   snprintf (dest, destlen, "color%d", val);
164   return dest;
165 }
166 #endif
167
168 int mutt_alloc_color (int fg, int bg)
169 {
170   COLOR_LIST *p = ColorList;
171   int i;
172
173 #if defined (USE_SLANG_CURSES)
174   char fgc[STRING], bgc[STRING];
175 #endif
176
177   /* check to see if this color is already allocated to save space */
178   while (p) {
179     if (p->fg == fg && p->bg == bg) {
180       (p->count)++;
181       return (COLOR_PAIR (p->index));
182     }
183     p = p->next;
184   }
185
186   /* check to see if there are colors left */
187   if (++UserColors > COLOR_PAIRS)
188     return (A_NORMAL);
189
190   /* find the smallest available index (object) */
191   i = 1;
192   for (;;) {
193     p = ColorList;
194     while (p) {
195       if (p->index == i)
196         break;
197       p = p->next;
198     }
199     if (p == NULL)
200       break;
201     i++;
202   }
203
204   p = p_new(COLOR_LIST, 1);
205   p->next = ColorList;
206   ColorList = p;
207
208   p->index = i;
209   p->count = 1;
210   p->bg = bg;
211   p->fg = fg;
212
213 #if defined (USE_SLANG_CURSES)
214   if (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
215     SLtt_set_color (i, NULL, get_color_name (fgc, sizeof (fgc), fg),
216                     get_color_name (bgc, sizeof (bgc), bg));
217   else
218 #elif defined (HAVE_USE_DEFAULT_COLORS)
219   if (fg == COLOR_DEFAULT)
220     fg = -1;
221   if (bg == COLOR_DEFAULT)
222     bg = -1;
223 #endif
224
225   init_pair (i, fg, bg);
226
227   return (COLOR_PAIR (p->index));
228 }
229
230 void mutt_free_color (int fg, int bg)
231 {
232   COLOR_LIST *p, *q;
233
234   p = ColorList;
235   while (p) {
236     if (p->fg == fg && p->bg == bg) {
237       (p->count)--;
238       if (p->count > 0)
239         return;
240
241       UserColors--;
242
243       if (p == ColorList) {
244         ColorList = ColorList->next;
245         p_delete(&p);
246         return;
247       }
248       q = ColorList;
249       while (q) {
250         if (q->next == p) {
251           q->next = p->next;
252           p_delete(&p);
253           return;
254         }
255         q = q->next;
256       }
257       /* can't get here */
258     }
259     p = p->next;
260   }
261 }
262
263 static int
264 parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER * err)
265 {
266   char *eptr;
267
268   if (m_strncasecmp(s, "bright", 6) == 0) {
269     *attr |= brite;
270     s += 6;
271   }
272
273   /* allow aliases for xterm color resources */
274   if (m_strncasecmp(s, "color", 5) == 0) {
275     s += 5;
276     *col = strtol (s, &eptr, 10);
277     if (!*s || *eptr || *col < 0 ||
278         (*col >= COLORS && !option (OPTNOCURSES) && has_colors ())) {
279       snprintf (err->data, err->dsize, _("%s: color not supported by term"),
280                 s);
281       return (-1);
282     }
283   }
284   else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) {
285     snprintf (err->data, err->dsize, _("%s: no such color"), s);
286     return (-1);
287   }
288
289   return 0;
290 }
291
292 /* usage: uncolor index pattern [pattern...]
293  *        unmono  index pattern [pattern...]
294  */
295
296 static int
297 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
298                      BUFFER * err, short parse_uncolor);
299
300 int mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
301                         BUFFER * err)
302 {
303   return _mutt_parse_uncolor (buf, s, data, err, 1);
304 }
305
306 int mutt_parse_unmono (BUFFER * buf, BUFFER * s, unsigned long data,
307                        BUFFER * err)
308 {
309   return _mutt_parse_uncolor (buf, s, data, err, 0);
310 }
311
312 static int
313 _mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
314                      BUFFER * err, short parse_uncolor)
315 {
316   int object = 0, do_cache = 0;
317   COLOR_LINE *tmp, *last = NULL;
318
319   mutt_extract_token (buf, s, 0);
320
321   if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) {
322     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
323     return (-1);
324   }
325
326   if (m_strncmp(buf->data, "index", 5) != 0) {
327     snprintf (err->data, err->dsize,
328               _("%s: command valid only for index object"),
329               parse_uncolor ? "uncolor" : "unmono");
330     return (-1);
331   }
332
333   if (!MoreArgs (s)) {
334     snprintf (err->data, err->dsize,
335               _("%s: too few arguments"),
336               parse_uncolor ? "uncolor" : "unmono");
337     return (-1);
338   }
339
340   if (option (OPTNOCURSES) || (parse_uncolor != has_colors())) {
341     /* just eat the command, but don't do anything real about it */
342     do {
343       mutt_extract_token (buf, s, 0);
344     } while (MoreArgs(s));
345     return 0;
346   }
347
348   do {
349     mutt_extract_token (buf, s, 0);
350     if (!m_strcmp("*", buf->data)) {
351       for (tmp = ColorIndexList; tmp;) {
352         if (!do_cache)
353           do_cache = 1;
354         last = tmp;
355         tmp = tmp->next;
356         mutt_free_color_line (&last, parse_uncolor);
357       }
358       ColorIndexList = NULL;
359     }
360     else {
361       for (last = NULL, tmp = ColorIndexList; tmp;
362            last = tmp, tmp = tmp->next) {
363         if (!m_strcmp(buf->data, tmp->pattern)) {
364           if (!do_cache)
365             do_cache = 1;
366           if (last)
367             last->next = tmp->next;
368           else
369             ColorIndexList = tmp->next;
370           mutt_free_color_line (&tmp, parse_uncolor);
371           break;
372         }
373       }
374     }
375   }
376   while (MoreArgs (s));
377
378
379   if (do_cache && !option (OPTNOCURSES)) {
380     int i;
381
382     set_option (OPTFORCEREDRAWINDEX);
383     /* force re-caching of index colors */
384     for (i = 0; Context && i < Context->msgcount; i++)
385       Context->hdrs[i]->pair = 0;
386   }
387   return (0);
388 }
389
390
391 static int
392 add_pattern (COLOR_LINE ** top, const char *s, int sensitive,
393              int fg, int bg, int attr, BUFFER * err, int is_index)
394 {
395
396   /* is_index used to store compiled pattern
397    * only for `index' color object 
398    * when called from mutt_parse_color() */
399
400   COLOR_LINE *tmp = *top;
401
402   while (tmp) {
403     if (sensitive) {
404       if (m_strcmp(s, tmp->pattern) == 0)
405         break;
406     }
407     else {
408       if (m_strcasecmp(s, tmp->pattern) == 0)
409         break;
410     }
411     tmp = tmp->next;
412   }
413
414   if (tmp) {
415     if (fg != -1 && bg != -1) {
416       if (tmp->fg != fg || tmp->bg != bg) {
417         mutt_free_color (tmp->fg, tmp->bg);
418         tmp->fg = fg;
419         tmp->bg = bg;
420         attr |= mutt_alloc_color (fg, bg);
421       }
422       else
423         attr |= (tmp->pair & ~A_BOLD);
424     }
425     tmp->pair = attr;
426   } else {
427     int r;
428     char buf[STRING];
429
430     tmp = mutt_new_color_line ();
431     if (is_index) {
432       int i;
433
434       m_strcpy(buf, sizeof(buf), NONULL(s));
435       mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
436       if ((tmp->color_pattern =
437            mutt_pattern_comp (buf, M_FULL_MSG, err)) == NULL) {
438         mutt_free_color_line (&tmp, 1);
439         return -1;
440       }
441       /* force re-caching of index colors */
442       for (i = 0; Context && i < Context->msgcount; i++)
443         Context->hdrs[i]->pair = 0;
444     }
445     else
446       if ((r =
447            REGCOMP (&tmp->rx, s,
448                     (sensitive ? mutt_which_case (s) : REG_ICASE))) != 0) {
449       regerror (r, &tmp->rx, err->data, err->dsize);
450       mutt_free_color_line (&tmp, 1);
451       return (-1);
452     }
453     tmp->next = *top;
454     tmp->pattern = m_strdup(s);
455     if (fg != -1 && bg != -1) {
456       tmp->fg = fg;
457       tmp->bg = bg;
458       attr |= mutt_alloc_color (fg, bg);
459     }
460     tmp->pair = attr;
461     *top = tmp;
462   }
463
464   return 0;
465 }
466
467 static int
468 parse_object (BUFFER * buf, BUFFER * s, int *o, int *ql, BUFFER * err)
469 {
470   int q_level = 0;
471   char *eptr;
472
473   if (!MoreArgs (s)) {
474     m_strcpy(err->data, err->dsize, _("Missing arguments."));
475     return -1;
476   }
477
478   mutt_extract_token (buf, s, 0);
479   if (!m_strncmp(buf->data, "quoted", 6)) {
480     if (buf->data[6]) {
481       *ql = strtol (buf->data + 6, &eptr, 10);
482       if (*eptr || q_level < 0) {
483         snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
484         return -1;
485       }
486     }
487     else
488       *ql = 0;
489
490     *o = MT_COLOR_QUOTED;
491   }
492   else if ((*o = mutt_getvaluebyname (buf->data, Fields)) == -1) {
493     snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
494     return (-1);
495   }
496
497   return 0;
498 }
499
500 typedef int (*parser_callback_t) (BUFFER *, BUFFER *, int *, int *, int *,
501                                   BUFFER *);
502
503 static int
504 parse_color_pair (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
505                   BUFFER * err)
506 {
507   if (!MoreArgs (s)) {
508     m_strcpy(err->data, err->dsize, _("color: too few arguments"));
509     return (-1);
510   }
511
512   mutt_extract_token (buf, s, 0);
513
514   if (parse_color_name (buf->data, fg, attr, A_BOLD, err) != 0)
515     return (-1);
516
517   if (!MoreArgs (s)) {
518     m_strcpy(err->data, err->dsize, _("color: too few arguments"));
519     return (-1);
520   }
521
522   mutt_extract_token (buf, s, 0);
523
524   if (parse_color_name (buf->data, bg, attr, A_BLINK, err) != 0)
525     return (-1);
526
527   return 0;
528 }
529
530 static int
531 parse_attr_spec (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
532                  BUFFER * err)
533 {
534
535   if (fg)
536     *fg = -1;
537   if (bg)
538     *bg = -1;
539
540   if (!MoreArgs (s)) {
541     m_strcpy(err->data, err->dsize, _("mono: too few arguments"));
542     return (-1);
543   }
544
545   mutt_extract_token (buf, s, 0);
546
547   if (ascii_strcasecmp ("bold", buf->data) == 0)
548     *attr |= A_BOLD;
549   else if (ascii_strcasecmp ("underline", buf->data) == 0)
550     *attr |= A_UNDERLINE;
551   else if (ascii_strcasecmp ("none", buf->data) == 0)
552     *attr = A_NORMAL;
553   else if (ascii_strcasecmp ("reverse", buf->data) == 0)
554     *attr |= A_REVERSE;
555   else if (ascii_strcasecmp ("standout", buf->data) == 0)
556     *attr |= A_STANDOUT;
557   else if (ascii_strcasecmp ("normal", buf->data) == 0)
558     *attr = A_NORMAL;           /* needs use = instead of |= to clear other bits */
559   else {
560     snprintf (err->data, err->dsize, _("%s: no such attribute"), buf->data);
561     return (-1);
562   }
563
564   return 0;
565 }
566
567 static int fgbgattr_to_color (int fg, int bg, int attr)
568 {
569   if (fg != -1 && bg != -1)
570     return attr | mutt_alloc_color (fg, bg);
571   else
572     return attr;
573 }
574
575 /* usage: color <object> <fg> <bg> [ <regexp> ] 
576  *        mono  <object> <attr> [ <regexp> ]
577  */
578
579 static int
580 _mutt_parse_color (BUFFER * buf, BUFFER * s, BUFFER * err,
581                    parser_callback_t callback, short dry_run)
582 {
583   int object = 0, attr = 0, fg = 0, bg = 0, q_level = 0;
584   int r = 0;
585
586   if (parse_object (buf, s, &object, &q_level, err) == -1)
587     return -1;
588
589   if (callback (buf, s, &fg, &bg, &attr, err) == -1)
590     return -1;
591
592   /* extract a regular expression if needed */
593
594   if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY
595       || object == MT_COLOR_INDEX) {
596     if (!MoreArgs (s)) {
597       m_strcpy(err->data, err->dsize, _("too few arguments"));
598       return (-1);
599     }
600
601     mutt_extract_token (buf, s, 0);
602   }
603
604   if (MoreArgs (s)) {
605     m_strcpy(err->data, err->dsize, _("too many arguments"));
606     return (-1);
607   }
608
609   /* dry run? */
610
611   if (dry_run)
612     return 0;
613
614
615 #ifdef HAVE_USE_DEFAULT_COLORS
616   if (!option (OPTNOCURSES) && has_colors ()
617       /* delay use_default_colors() until needed, since it initializes things */
618       && (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
619       && use_default_colors () != OK) {
620     m_strcpy(err->data, err->dsize, _("default colors not supported"));
621     return (-1);
622   }
623 #endif /* HAVE_USE_DEFAULT_COLORS */
624
625   if (object == MT_COLOR_HEADER)
626     r = add_pattern (&ColorHdrList, buf->data, 0, fg, bg, attr, err, 0);
627   else if (object == MT_COLOR_BODY)
628     r = add_pattern (&ColorBodyList, buf->data, 1, fg, bg, attr, err, 0);
629   else if (object == MT_COLOR_INDEX) {
630     r = add_pattern (&ColorIndexList, buf->data, 1, fg, bg, attr, err, 1);
631     set_option (OPTFORCEREDRAWINDEX);
632   }
633   else if (object == MT_COLOR_QUOTED) {
634     if (q_level >= ColorQuoteSize) {
635       p_realloc(&ColorQuote, ColorQuoteSize += 2);
636       ColorQuote[ColorQuoteSize - 2] = ColorDefs[MT_COLOR_QUOTED];
637       ColorQuote[ColorQuoteSize - 1] = ColorDefs[MT_COLOR_QUOTED];
638     }
639     if (q_level >= ColorQuoteUsed)
640       ColorQuoteUsed = q_level + 1;
641     if (q_level == 0) {
642       ColorDefs[MT_COLOR_QUOTED] = fgbgattr_to_color (fg, bg, attr);
643
644       ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED];
645       for (q_level = 1; q_level < ColorQuoteUsed; q_level++) {
646         if (ColorQuote[q_level] == A_NORMAL)
647           ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED];
648       }
649     }
650     else
651       ColorQuote[q_level] = fgbgattr_to_color (fg, bg, attr);
652   }
653   else
654     ColorDefs[object] = fgbgattr_to_color (fg, bg, attr);
655
656 #ifdef HAVE_BKGDSET
657   if (object == MT_COLOR_NORMAL && !option (OPTNOCURSES) && has_colors ())
658     BKGDSET (MT_COLOR_NORMAL);
659 #endif
660
661   return (r);
662 }
663
664 int mutt_parse_color (BUFFER * buff, BUFFER * s, unsigned long data __attribute__ ((unused)),
665                       BUFFER * err)
666 {
667   int dry_run = 0;
668
669   if (option (OPTNOCURSES) || !has_colors ())
670     dry_run = 1;
671
672   return _mutt_parse_color (buff, s, err, parse_color_pair, dry_run);
673 }
674
675 int mutt_parse_mono (BUFFER * buff, BUFFER * s, unsigned long data __attribute__ ((unused)),
676                      BUFFER * err)
677 {
678   int dry_run = 0;
679
680   if (option (OPTNOCURSES) || has_colors ())
681     dry_run = 1;
682
683   return _mutt_parse_color (buff, s, err, parse_attr_spec, dry_run);
684 }