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