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