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