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