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