1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Implementation of colour.h.
3  *@ TODO As stated in header:
4  *@ TODO All the colour (pen etc. ) interfaces below have to vanish.
5  *@ TODO What we need here is a series of query functions which take context,
6  *@ TODO like a message* for the _VIEW_ series, and which return a 64-bit
7  *@ TODO flag carrier which returns font-attributes as well as foreground and
8  *@ TODO background colours (at least 24-bit each).
9  *@ TODO And the actual drawing stuff is up to the backend, maybe in termios,
10  *@ TODO or better termcap, or in ui-str.  I do not know.
11  *
12  * Copyright (c) 2014 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
13  * SPDX-License-Identifier: ISC
14  *
15  * Permission to use, copy, modify, and/or distribute this software for any
16  * purpose with or without fee is hereby granted, provided that the above
17  * copyright notice and this permission notice appear in all copies.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
20  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
21  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
22  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
23  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
24  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
25  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26  */
27 #undef su_FILE
28 #define su_FILE colour
29 #define mx_SOURCE
30 
31 #ifndef mx_HAVE_AMALGAMATION
32 # include "mx/nail.h"
33 #endif
34 
35 su_EMPTY_FILE()
36 #ifdef mx_HAVE_COLOUR
37 #include <su/cs.h>
38 #include <su/icodec.h>
39 #include <su/mem.h>
40 
41 #include "mx/sigs.h"
42 #include "mx/termcap.h"
43 
44 /* TODO fake */
45 #include "mx/colour.h"
46 #include "su/code-in.h"
47 
48 /* Not needed publicly, but extends a public set */
49 #define mx_COLOUR_TAG_ERR ((char*)-1)
50 #define a_COLOUR_TAG_IS_SPECIAL(P) (P2UZ(P) >= P2UZ(-3))
51 
52 enum a_colour_type{
53    a_COLOUR_T_256,
54    a_COLOUR_T_8,
55    a_COLOUR_T_1,
56    a_COLOUR_T_NONE, /* EQ largest real colour + 1! */
57    a_COLOUR_T_UNKNOWN /* Initial value: real one queried before 1st use */
58 };
59 
60 enum a_colour_tag_type{
61    a_COLOUR_TT_NONE,
62    a_COLOUR_TT_DOT = 1u<<0, /* "dot" */
63    a_COLOUR_TT_OLDER = 1u<<1, /* "older" */
64    a_COLOUR_TT_HEADERS = 1u<<2, /* Comma-separated list of headers allowed */
65 
66    a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER,
67    a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS
68 };
69 
70 struct a_colour_type_map{
71    u8 ctm_type; /* a_colour_type */
72    char ctm_name[7];
73 };
74 
75 struct a_colour_map_id{
76    u8 cmi_ctx; /* enum mx_colour_ctx */
77    u8 cmi_id; /* enum mx_colour_id */
78    u8 cmi_tt; /* enum a_colour_tag_type */
79    char const cmi_name[13];
80 };
81 CTA(mx__COLOUR_IDS <= U8_MAX, "Enumeration exceeds storage datatype");
82 
83 struct mx_colour_pen{
84    struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */
85 };
86 
87 struct a_colour_map /* : public mx_colour_pen */{
88    struct mx_colour_pen cm_pen; /* Points into .cm_buf */
89    struct a_colour_map *cm_next;
90    char const *cm_tag; /* Colour tag or NULL for default (last) */
91    struct a_colour_map_id const *cm_cmi;
92 #ifdef mx_HAVE_REGEX
93    regex_t *cm_regex;
94 #endif
95    u32 cm_refcnt; /* Beware of reference drops in recursions */
96    u32 cm_user_off; /* User input offset in .cm_buf */
97    char cm_buf[VFIELD_SIZE(0)];
98 };
99 
100 struct a_colour_g{
101    boole cg_is_init;
102    u8 cg_type; /* a_colour_type */
103    u8 __cg_pad[6];
104    struct mx_colour_pen cg_reset; /* The reset sequence */
105    struct a_colour_map
106       *cg_maps[a_COLOUR_T_NONE][mx__COLOUR_CTX_MAX1][mx__COLOUR_IDS];
107    char cg__reset_buf[Z_ALIGN(sizeof("\033[0m"))];
108 };
109 
110 /* C99: use [INDEX]={} */
111 /* */
112 CTA(a_COLOUR_T_256 == 0, "Unexpected value of constant");
113 CTA(a_COLOUR_T_8 == 1, "Unexpected value of constant");
114 CTA(a_COLOUR_T_1 == 2, "Unexpected value of constant");
115 static char const a_colour_types[][8] = {"256", "iso", "mono"};
116 
117 static struct a_colour_type_map const a_colour_type_maps[] = {
118    {a_COLOUR_T_256, "256"},
119    {a_COLOUR_T_8, "8"}, {a_COLOUR_T_8, "iso"}, {a_COLOUR_T_8, "ansi"},
120    {a_COLOUR_T_1, "1"}, {a_COLOUR_T_1, "mono"}
121 };
122 
123 CTAV(mx_COLOUR_CTX_SUM == 0);
124 CTAV(mx_COLOUR_CTX_VIEW == 1);
125 CTAV(mx_COLOUR_CTX_MLE == 2);
126 static char const a_colour_ctx_prefixes[mx__COLOUR_CTX_MAX1][8] = {
127    "sum-", "view-", "mle-"
128 };
129 
130 static struct a_colour_map_id const
131       a_colour_map_ids[mx__COLOUR_CTX_MAX1][mx__COLOUR_IDS] = {{
132    {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"},
133    {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"},
134    {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"},
135    }, {
136    {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"},
137    {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"},
138    {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE,
139       "msginfo"},
140    {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE,
141       "partinfo"},
142    }, {
143    {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_POSITION, a_COLOUR_TT_NONE,
144       "position"},
145    {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_PROMPT, a_COLOUR_TT_NONE, "prompt"},
146    {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_ERROR, a_COLOUR_TT_NONE, "error"},
147 }};
148 #define a_COLOUR_MAP_SHOW_FIELDWIDTH \
149    (int)(sizeof("view-")-1 + sizeof("partinfo")-1)
150 
151 static struct a_colour_g a_colour_g;
152 
153 /* */
154 static void a_colour_init(void);
155 static boole a_colour_termcap_init(void);
156 
157 /* May we work with colour at the very moment? */
158 SINLINE boole a_colour_ok_to_go(u32 get_flags);
159 
160 /* Find the type or -1 */
161 static enum a_colour_type a_colour_type_find(char const *name);
162 
163 /* `(un)?colour' implementations */
164 static boole a_colour_mux(char **argv);
165 static boole a_colour_unmux(char **argv);
166 
167 static boole a_colour__show(enum a_colour_type ct);
168 /* (regexpp may be NULL) */
169 static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip,
170       char const *ctag, void **regexpp);
171 
172 /* Try to find a mapping identity for user given slotname */
173 static struct a_colour_map_id const *a_colour_map_id_find(
174       char const *slotname);
175 
176 /* Find an existing mapping for the given combination */
177 static struct a_colour_map *a_colour_map_find(enum mx_colour_ctx cctx,
178       enum mx_colour_id cid, char const *ctag);
179 
180 /* In-/Decrement reference counter, destroy if counts gets zero */
181 #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0)
182 static void a_colour_map_unref(struct a_colour_map *self);
183 
184 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user
185  * input spec, store it or on error message in *store */
186 static boole a_colour_iso6429(enum a_colour_type ct, char **store,
187                char const *spec);
188 
189 static void
a_colour_init(void)190 a_colour_init(void){
191    NYD2_IN;
192    a_colour_g.cg_is_init = TRU1;
193    su_mem_copy(a_colour_g.cg_reset.cp_dat.s = a_colour_g.cg__reset_buf,
194       "\033[0m",
195       a_colour_g.cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */
196    a_colour_g.cg_type = a_COLOUR_T_UNKNOWN;
197    NYD2_OU;
198 }
199 
200 static boole
a_colour_termcap_init(void)201 a_colour_termcap_init(void){
202    boole rv;
203    NYD2_IN;
204 
205    rv = FAL0;
206 
207    if(n_psonce & n_PSO_STARTED){
208       struct mx_termcap_value tv;
209 
210       if(!mx_termcap_query(mx_TERMCAP_QUERY_colors, &tv))
211          a_colour_g.cg_type = a_COLOUR_T_NONE;
212       else{
213          rv = TRU1;
214          switch(tv.tv_data.tvd_numeric){
215          case 256: a_colour_g.cg_type = a_COLOUR_T_256; break;
216          case 8: a_colour_g.cg_type = a_COLOUR_T_8; break;
217          case 1: a_colour_g.cg_type = a_COLOUR_T_1; break;
218          default:
219             if(n_poption & n_PO_D_V)
220                n_err(_("Ignoring unsupported termcap entry for Co(lors)\n"));
221             /* FALLTHRU */
222          case 0:
223             a_colour_g.cg_type = a_COLOUR_T_NONE;
224             rv = FAL0;
225             break;
226          }
227       }
228    }
229 
230    NYD2_OU;
231    return rv;
232 }
233 
234 SINLINE boole
a_colour_ok_to_go(u32 get_flags)235 a_colour_ok_to_go(u32 get_flags){
236    u32 po;
237    boole rv;
238    NYD2_IN;
239 
240    rv = FAL0;
241    po = (n_poption & n_PO_V_MASK);/* TODO *colour-disable* */
242    n_poption &= ~n_PO_V_MASK; /* TODO log too loud - need "no log" bit!! */
243 
244    /* xxx Entire preamble could later be a shared function */
245    if(!(n_psonce & n_PSO_TTYANY) || !(n_psonce & n_PSO_STARTED) ||
246          ok_blook(colour_disable) ||
247          (!(get_flags & mx_COLOUR_GET_FORCED) && !mx_COLOUR_IS_ACTIVE()) ||
248          ((get_flags & mx_COLOUR_PAGER_USED) && !ok_blook(colour_pager)))
249       goto jleave;
250    if(UNLIKELY(!a_colour_g.cg_is_init))
251       a_colour_init();
252    if(UNLIKELY(a_colour_g.cg_type == a_COLOUR_T_NONE) ||
253          !a_colour_termcap_init())
254       goto jleave;
255 
256    rv = TRU1;
257 jleave:
258    n_poption |= po;
259    NYD2_OU;
260    return rv;
261 }
262 
263 static enum a_colour_type
a_colour_type_find(char const * name)264 a_colour_type_find(char const *name){
265    struct a_colour_type_map const *ctmp;
266    enum a_colour_type rv;
267    NYD2_IN;
268 
269    ctmp = a_colour_type_maps;
270    do if(!su_cs_cmp_case(ctmp->ctm_name, name)){
271       rv = ctmp->ctm_type;
272       goto jleave;
273    }while(PCMP(++ctmp, !=,
274       a_colour_type_maps + NELEM(a_colour_type_maps)));
275 
276    rv = (enum a_colour_type)-1;
277 jleave:
278    NYD2_OU;
279    return rv;
280 }
281 
282 static boole
a_colour_mux(char ** argv)283 a_colour_mux(char **argv){
284    void *regexp;
285    char const *mapname, *ctag;
286    struct a_colour_map **cmap, *blcmp, *lcmp, *cmp;
287    struct a_colour_map_id const *cmip;
288    boole rv;
289    enum a_colour_type ct;
290    NYD2_IN;
291 
292    if(*argv == NIL)
293       ct = R(enum a_colour_type,-1);
294    else if((ct = a_colour_type_find(*argv++)) == R(enum a_colour_type,-1) &&
295          (*argv != NIL || !n_is_all_or_aster(argv[-1]))){
296       n_err(_("colour: invalid colour type %s\n"),
297          n_shexp_quote_cp(argv[-1], FAL0));
298       rv = FAL0;
299       goto jleave;
300    }
301 
302    if(!a_colour_g.cg_is_init)
303       a_colour_init();
304 
305    if(*argv == NIL){
306       rv = a_colour__show(ct);
307       goto jleave;
308    }
309 
310    rv = FAL0;
311    regexp = NULL;
312 
313    if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){
314       n_err(_("colour: non-existing mapping: %s\n"),
315          n_shexp_quote_cp(mapname, FAL0));
316       goto jleave;
317    }
318 
319    if(argv[1] == NULL){
320       n_err(_("colour: %s: missing attribute argument\n"),
321          n_shexp_quote_cp(mapname, FAL0));
322       goto jleave;
323    }
324 
325    /* Check whether preconditions are at all allowed, verify them as far as
326     * possible as necessary.  For shell_quote() simplicity let's just ignore an
327     * empty precondition */
328    if((ctag = argv[2]) != NULL && *ctag != '\0'){
329       char const *xtag;
330 
331       if(cmip->cmi_tt == a_COLOUR_TT_NONE){
332          n_err(_("colour: %s does not support preconditions\n"),
333             n_shexp_quote_cp(mapname, FAL0));
334          goto jleave;
335       }else if((xtag = a_colour__tag_identify(cmip, ctag, &regexp)) ==
336             mx_COLOUR_TAG_ERR){
337          /* I18N: ..of colour mapping */
338          n_err(_("colour: %s: invalid precondition: %s\n"),
339             n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
340          goto jleave;
341       }
342       ctag = xtag;
343    }
344 
345    /* At this time we have all the information to be able to query whether such
346     * a mapping is yet established. If so, destroy it */
347    for(blcmp = lcmp = NULL,
348             cmp = *(cmap =
349                   &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
350          cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){
351       char const *xctag = cmp->cm_tag;
352 
353       if(xctag == ctag ||
354             (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
355              xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
356              !su_cs_cmp(xctag, ctag))){
357          if(lcmp == NULL)
358             *cmap = cmp->cm_next;
359          else
360             lcmp->cm_next = cmp->cm_next;
361          a_colour_map_unref(cmp);
362          break;
363       }
364    }
365 
366    /* Create mapping */
367    /* C99 */{
368       uz tl, usrl, cl;
369       char *bp, *cp;
370 
371       if(!a_colour_iso6429(ct, &cp, argv[1])){
372          /* I18N: colour command: mapping: error message: user argument */
373          n_err(_("colour: %s: %s: %s\n"), n_shexp_quote_cp(mapname, FAL0),
374             cp, n_shexp_quote_cp(argv[1], FAL0));
375          goto jleave;
376       }
377 
378       tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag))
379             ? su_cs_len(ctag) : 0;
380       cmp = n_alloc(VSTRUCT_SIZEOF(struct a_colour_map, cm_buf) +
381             tl +1 + (usrl = su_cs_len(argv[1])) +1 + (cl = su_cs_len(cp)) +1);
382 
383       /* .cm_buf stuff */
384       cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf;
385       cmp->cm_pen.cp_dat.l = cl;
386       su_mem_copy(bp, cp, ++cl);
387       bp += cl;
388 
389       cmp->cm_user_off = (u32)P2UZ(bp - cmp->cm_buf);
390       su_mem_copy(bp, argv[1], ++usrl);
391       bp += usrl;
392 
393       if(tl > 0){
394          cmp->cm_tag = bp;
395          su_mem_copy(bp, ctag, ++tl);
396          /*bp += tl;*/
397       }else
398          cmp->cm_tag = ctag;
399 
400       /* Non-buf stuff; default mapping */
401       if(lcmp != NULL){
402          /* Default mappings must be last */
403          if(ctag == NULL){
404             while(lcmp->cm_next != NULL)
405                lcmp = lcmp->cm_next;
406          }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){
407             if((lcmp = blcmp) == NULL)
408                goto jlinkhead;
409          }
410          cmp->cm_next = lcmp->cm_next;
411          lcmp->cm_next = cmp;
412       }else{
413 jlinkhead:
414          cmp->cm_next = *cmap;
415          *cmap = cmp;
416       }
417       cmp->cm_cmi = cmip;
418 #ifdef mx_HAVE_REGEX
419       cmp->cm_regex = regexp;
420 #endif
421       cmp->cm_refcnt = 0;
422       a_colour_map_ref(cmp);
423    }
424    rv = TRU1;
425 jleave:
426    NYD2_OU;
427    return rv;
428 }
429 
430 static boole
a_colour_unmux(char ** argv)431 a_colour_unmux(char **argv){
432    char const *mapname, *ctag, *xtag;
433    struct a_colour_map **cmap, *lcmp, *cmp;
434    struct a_colour_map_id const *cmip;
435    enum a_colour_type ct;
436    boole aster, rv;
437    NYD2_IN;
438 
439    rv = TRU1;
440    aster = FAL0;
441 
442    if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
443       if(!n_is_all_or_aster(argv[-1])){
444          n_err(_("uncolour: invalid colour type %s\n"),
445             n_shexp_quote_cp(argv[-1], FAL0));
446          rv = FAL0;
447          goto j_leave;
448       }
449       aster = TRU1;
450       ct = 0;
451    }
452 
453    mapname = argv[0];
454    ctag = argv[1];
455 
456    if(!a_colour_g.cg_is_init)
457       goto jemap;
458 
459    /* Delete anything? */
460 jredo:
461    if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){
462       uz i1, i2;
463       struct a_colour_map *tmp;
464 
465       for(i1 = 0; i1 < mx__COLOUR_CTX_MAX1; ++i1)
466          for(i2 = 0; i2 < mx__COLOUR_IDS; ++i2)
467             for(cmp = *(cmap = &a_colour_g.cg_maps[ct][i1][i2]), *cmap = NULL;
468                   cmp != NULL;){
469                tmp = cmp;
470                cmp = cmp->cm_next;
471                a_colour_map_unref(tmp);
472             }
473    }else{
474       if((cmip = a_colour_map_id_find(mapname)) == NULL){
475          rv = FAL0;
476 jemap:
477          /* I18N: colour cmd, mapping and precondition (option in quotes) */
478          n_err(_("uncolour: non-existing mapping: %s%s%s\n"),
479             n_shexp_quote_cp(mapname, FAL0), (ctag == NULL ? n_empty : " "),
480             (ctag == NULL ? n_empty : n_shexp_quote_cp(ctag, FAL0)));
481          goto jleave;
482       }
483 
484       if((xtag = ctag) != NULL){
485          if(cmip->cmi_tt == a_COLOUR_TT_NONE){
486             n_err(_("uncolour: %s does not support preconditions\n"),
487                n_shexp_quote_cp(mapname, FAL0));
488             rv = FAL0;
489             goto jleave;
490          }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) ==
491                mx_COLOUR_TAG_ERR){
492             n_err(_("uncolour: %s: invalid precondition: %s\n"),
493                n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
494             rv = FAL0;
495             goto jleave;
496          }
497          /* (Improve user experience) */
498          if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag))
499             ctag = xtag;
500       }
501 
502       lcmp = NULL;
503       cmp = *(cmap = &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
504       for(;;){
505          char const *xctag;
506 
507          if(cmp == NULL){
508             rv = FAL0;
509             goto jemap;
510          }
511          if((xctag = cmp->cm_tag) == ctag)
512             break;
513          if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
514                xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
515                !su_cs_cmp(xctag, ctag))
516             break;
517          lcmp = cmp;
518          cmp = cmp->cm_next;
519       }
520 
521       if(lcmp == NULL)
522          *cmap = cmp->cm_next;
523       else
524          lcmp->cm_next = cmp->cm_next;
525       a_colour_map_unref(cmp);
526    }
527 
528 jleave:
529    if(aster && ++ct != a_COLOUR_T_NONE)
530       goto jredo;
531 j_leave:
532    NYD2_OU;
533    return rv;
534 }
535 
536 static boole
a_colour__show(enum a_colour_type ct)537 a_colour__show(enum a_colour_type ct){
538    struct a_colour_map *cmp;
539    uz i1, i2;
540    boole rv;
541    NYD2_IN;
542 
543    /* Show all possible types? */
544    if((rv = (ct == (enum a_colour_type)-1 ? TRU1 : FAL0)))
545       ct = 0;
546 jredo:
547    for(i1 = 0; i1 < mx__COLOUR_CTX_MAX1; ++i1)
548       for(i2 = 0; i2 < mx__COLOUR_IDS; ++i2){
549          if((cmp = a_colour_g.cg_maps[ct][i1][i2]) == NULL)
550             continue;
551 
552          for(; cmp != NULL; cmp = cmp->cm_next){
553             char const *tag;
554 
555             if((tag = cmp->cm_tag) == NULL)
556                tag = n_empty;
557             else if(tag == mx_COLOUR_TAG_SUM_DOT)
558                tag = "dot";
559             else if(tag == mx_COLOUR_TAG_SUM_OLDER)
560                tag = "older";
561 
562             fprintf(n_stdout, "colour %s %-*s %s %s\n",
563                a_colour_types[ct], a_COLOUR_MAP_SHOW_FIELDWIDTH,
564                savecat(a_colour_ctx_prefixes[i1],
565                   a_colour_map_ids[i1][i2].cmi_name),
566                (char const*)cmp->cm_buf + cmp->cm_user_off,
567                n_shexp_quote_cp(tag, TRU1));
568          }
569       }
570 
571    if(rv && ++ct != a_COLOUR_T_NONE)
572       goto jredo;
573    rv = TRU1;
574    NYD2_OU;
575    return rv;
576 }
577 
578 static char const *
a_colour__tag_identify(struct a_colour_map_id const * cmip,char const * ctag,void ** regexpp)579 a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag,
580       void **regexpp){
581    NYD2_IN;
582    UNUSED(regexpp);
583 
584    if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !su_cs_cmp_case(ctag, "dot"))
585       ctag = mx_COLOUR_TAG_SUM_DOT;
586    else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) &&
587          !su_cs_cmp_case(ctag, "older"))
588       ctag = mx_COLOUR_TAG_SUM_OLDER;
589    else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){
590       char *cp, c;
591       uz i;
592 
593       /* Can this be a valid list of headers? However, with regular expressions
594        * simply use the input as such if it appears to be a regex */
595 #ifdef mx_HAVE_REGEX
596       if(n_is_maybe_regex(ctag)){
597          int s;
598 
599          if(regexpp != NULL &&
600                (s = regcomp(*regexpp = n_alloc(sizeof(regex_t)), ctag,
601                   REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
602             n_err(_("colour: invalid regular expression: %s: %s\n"),
603                n_shexp_quote_cp(ctag, FAL0), n_regex_err_to_doc(NULL, s));
604             n_free(*regexpp);
605             goto jetag;
606          }
607       }else
608 #endif
609       {
610          /* Normalize to lowercase and strip any whitespace before use */
611          i = su_cs_len(ctag);
612          cp = n_autorec_alloc(i +1);
613 
614          for(i = 0; (c = *ctag++) != '\0';){
615             boole isblspc = su_cs_is_space(c);
616 
617             if(!isblspc && !su_cs_is_alnum(c) && c != '-' && c != ',')
618                goto jetag;
619             /* Since we compare header names as they come from the message this
620              * lowercasing is however redundant: we need to casecmp() them */
621             if(!isblspc)
622                cp[i++] = su_cs_to_lower(c);
623          }
624          cp[i] = '\0';
625          ctag = cp;
626       }
627    }else
628 jetag:
629       ctag = mx_COLOUR_TAG_ERR;
630    NYD2_OU;
631    return ctag;
632 }
633 
634 static struct a_colour_map_id const *
a_colour_map_id_find(char const * cp)635 a_colour_map_id_find(char const *cp){
636    uz i;
637    struct a_colour_map_id const (*cmip)[mx__COLOUR_IDS], *rv;
638    NYD2_IN;
639 
640    rv = NULL;
641 
642    for(i = 0;; ++i){
643       if(i == mx__COLOUR_IDS)
644          goto jleave;
645       else{
646          uz j = su_cs_len(a_colour_ctx_prefixes[i]);
647          if(!su_cs_cmp_case_n(cp, a_colour_ctx_prefixes[i], j)){
648             cp += j;
649             break;
650          }
651       }
652    }
653    cmip = &a_colour_map_ids[i];
654 
655    for(i = 0;; ++i){
656       if(i == mx__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){
657          rv = NULL;
658          break;
659       }
660       if(!su_cs_cmp_case(cp, rv->cmi_name))
661          break;
662    }
663 jleave:
664    NYD2_OU;
665    return rv;
666 }
667 
668 static struct a_colour_map *
a_colour_map_find(enum mx_colour_ctx cctx,enum mx_colour_id cid,char const * ctag)669 a_colour_map_find(enum mx_colour_ctx cctx, enum mx_colour_id cid,
670       char const *ctag){
671    struct a_colour_map *cmp;
672    NYD2_IN;
673 
674    cmp = a_colour_g.cg_maps[a_colour_g.cg_type][cctx][cid];
675    for(; cmp != NULL; cmp = cmp->cm_next){
676       char const *xtag = cmp->cm_tag;
677 
678       if(xtag == ctag)
679          break;
680       if(xtag == NULL)
681          break;
682       if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag))
683          continue;
684 #ifdef mx_HAVE_REGEX
685       if(cmp->cm_regex != NULL){
686          if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH)
687             break;
688       }else
689 #endif
690       if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){
691          char *hlist = savestr(xtag), *cp;
692 
693          while((cp = su_cs_sep_c(&hlist, ',', TRU1)) != NULL){
694             if(!su_cs_cmp_case(cp, ctag))
695                break;
696          }
697          if(cp != NULL)
698             break;
699       }
700    }
701    NYD2_OU;
702    return cmp;
703 }
704 
705 static void
a_colour_map_unref(struct a_colour_map * self)706 a_colour_map_unref(struct a_colour_map *self){
707    NYD2_IN;
708    if(--self->cm_refcnt == 0){
709 #ifdef mx_HAVE_REGEX
710       if(self->cm_regex != NULL){
711          regfree(self->cm_regex);
712          n_free(self->cm_regex);
713       }
714 #endif
715       n_free(self);
716    }
717    NYD2_OU;
718 }
719 
720 static boole
a_colour_iso6429(enum a_colour_type ct,char ** store,char const * spec)721 a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){
722    struct isodesc{
723       char id_name[15];
724       char id_modc;
725    } const fta[] = {
726       {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
727    }, ca[] = {
728       {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
729       {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
730    }, *idp;
731    char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")];
732    u8 ftno_base, ftno;
733    boole rv;
734    NYD_IN;
735 
736    rv = FAL0;
737    /* 0/1 indicate usage, thereafter possibly 256 color sequences */
738    cfg[0] = cfg[1] = 0;
739 
740    /* Since we use autorec_alloc(), reuse the su_cs_sep_c() buffer also for the
741     * return value, ensure we have enough room for that */
742    /* C99 */{
743       uz i = su_cs_len(spec) +1;
744       xspec = n_autorec_alloc(MAX(i,
745             sizeof("\033[1;4;7;38;5;255;48;5;255m")));
746       su_mem_copy(xspec, spec, i);
747       spec = xspec;
748    }
749 
750    /* Iterate over the colour spec */
751    ftno = 0;
752    while((cp = su_cs_sep_c(&xspec, ',', TRU1)) != NULL){
753       char *y, *x = su_cs_find_c(cp, '=');
754       if(x == NULL){
755 jbail:
756          *store = n_UNCONST(_("invalid attribute list"));
757          goto jleave;
758       }
759       *x++ = '\0';
760 
761       if(!su_cs_cmp_case(cp, "ft")){
762          if(!su_cs_cmp_case(x, "inverse")){
763             n_OBSOLETE(_("please use reverse for ft= fonts, not inverse"));
764             x = n_UNCONST("reverse");
765          }
766          for(idp = fta;; ++idp)
767             if(idp == fta + NELEM(fta)){
768                *store = n_UNCONST(_("invalid font attribute"));
769                goto jleave;
770             }else if(!su_cs_cmp_case(x, idp->id_name)){
771                if(ftno < NELEM(fg))
772                   fg[ftno++] = idp->id_modc;
773                else{
774                   *store = n_UNCONST(_("too many font attributes"));
775                   goto jleave;
776                }
777                break;
778             }
779       }else if(!su_cs_cmp_case(cp, "fg")){
780          y = cfg + 0;
781          goto jiter_colour;
782       }else if(!su_cs_cmp_case(cp, "bg")){
783          y = cfg + 1;
784 jiter_colour:
785          if(ct == a_COLOUR_T_1){
786             *store = n_UNCONST(_("colours are not allowed"));
787             goto jleave;
788          }
789          /* Maybe 256 color spec */
790          if(su_cs_is_digit(x[0])){
791             u8 xv;
792 
793             if(ct == a_COLOUR_T_8){
794                *store = n_UNCONST(_("invalid colour for 8-colour mode"));
795                goto jleave;
796             }
797 
798             if((su_idec_u8_cp(&xv, x, 10, NULL
799                      ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
800                   ) != su_IDEC_STATE_CONSUMED){
801                *store = n_UNCONST(_("invalid 256-colour specification"));
802                goto jleave;
803             }
804             y[0] = 5;
805             su_mem_copy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x,
806                (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4)));
807          }else for(idp = ca;; ++idp)
808             if(idp == ca + NELEM(ca)){
809                *store = n_UNCONST(_("invalid colour attribute"));
810                goto jleave;
811             }else if(!su_cs_cmp_case(x, idp->id_name)){
812                y[0] = 1;
813                y[2] = idp->id_modc;
814                break;
815             }
816       }else
817          goto jbail;
818    }
819 
820    /* Restore our autorec_alloc() buffer, create return value */
821    xspec = n_UNCONST(spec);
822    if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */
823       xspec[0] = '\033';
824       xspec[1] = '[';
825       xspec += 2;
826 
827       for(ftno_base = ftno; ftno > 0;){
828          if(ftno-- != ftno_base)
829             *xspec++ = ';';
830          *xspec++ = fg[ftno];
831       }
832 
833       if(cfg[0]){
834          if(ftno_base > 0)
835             *xspec++ = ';';
836          xspec[0] = '3';
837          if(cfg[0] == 1){
838             xspec[1] = cfg[2];
839             xspec += 2;
840          }else{
841             su_mem_copy(xspec + 1, "8;5;", 4);
842             xspec += 5;
843             for(ftno = 2; cfg[ftno] != '\0'; ++ftno)
844                *xspec++ = cfg[ftno];
845          }
846       }
847 
848       if(cfg[1]){
849          if(ftno_base > 0 || cfg[0])
850             *xspec++ = ';';
851          xspec[0] = '4';
852          if(cfg[1] == 1){
853             xspec[1] = cfg[3];
854             xspec += 2;
855          }else{
856             su_mem_copy(xspec + 1, "8;5;", 4);
857             xspec += 5;
858             for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno)
859                *xspec++ = cfg[ftno];
860          }
861       }
862 
863       *xspec++ = 'm';
864    }
865    *xspec = '\0';
866    *store = n_UNCONST(spec);
867    rv = TRU1;
868 jleave:
869    NYD_OU;
870    return rv;
871 }
872 
873 int
c_colour(void * v)874 c_colour(void *v){
875    int rv;
876    NYD_IN;
877 
878    rv = !a_colour_mux(v);
879    NYD_OU;
880    return rv;
881 }
882 
883 int
c_uncolour(void * v)884 c_uncolour(void *v){
885    int rv;
886    NYD_IN;
887 
888    rv = !a_colour_unmux(v);
889    NYD_OU;
890    return rv;
891 }
892 
893 void
mx_colour_stack_del(struct n_go_data_ctx * gdcp)894 mx_colour_stack_del(struct n_go_data_ctx *gdcp){
895    struct mx_colour_env *vp, *cep;
896    NYD_IN;
897 
898    vp = gdcp->gdc_colour;
899    gdcp->gdc_colour = NULL;
900    gdcp->gdc_colour_active = FAL0;
901 
902    while((cep = vp) != NULL){
903       vp = cep->ce_last;
904 
905       if(cep->ce_current != NULL && cep->ce_outfp == n_stdout){
906          n_sighdl_t hdl;
907 
908          hdl = n_signal(SIGPIPE, SIG_IGN);
909          fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
910             cep->ce_outfp);
911          fflush(cep->ce_outfp);
912          n_signal(SIGPIPE, hdl);
913       }
914    }
915    NYD_OU;
916 }
917 
918 void
mx_colour_env_create(enum mx_colour_ctx cctx,FILE * fp,boole pager_used)919 mx_colour_env_create(enum mx_colour_ctx cctx, FILE *fp, boole pager_used){
920    struct mx_colour_env *cep;
921    NYD_IN;
922 
923    if(!(n_psonce & n_PSO_TTYANY))
924       goto jleave;
925    if(!a_colour_g.cg_is_init)
926       a_colour_init();
927 
928    /* TODO reset the outer level?  Iff ce_outfp==fp? */
929    cep = n_autorec_alloc(sizeof *cep);
930    cep->ce_last = n_go_data->gdc_colour;
931    cep->ce_enabled = FAL0;
932    cep->ce_ctx = cctx;
933    cep->ce_ispipe = pager_used;
934    cep->ce_outfp = fp;
935    cep->ce_current = NULL;
936    n_go_data->gdc_colour_active = FAL0;
937    n_go_data->gdc_colour = cep;
938 
939    if(ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager)))
940       goto jleave;
941    if(a_colour_g.cg_type == a_COLOUR_T_NONE || !a_colour_termcap_init())
942       goto jleave;
943 
944    n_go_data->gdc_colour_active = cep->ce_enabled = TRU1;
945 jleave:
946    NYD_OU;
947 }
948 
949 void
mx_colour_env_gut(void)950 mx_colour_env_gut(void){
951    struct mx_colour_env *cep;
952    NYD_IN;
953 
954    if(!(n_psonce & n_PSO_INTERACTIVE))
955       goto jleave;
956 
957    /* TODO v15: Could happen because of jump, causing _stack_del().. */
958    if((cep = n_go_data->gdc_colour) == NULL)
959       goto jleave;
960    n_go_data->gdc_colour_active = ((n_go_data->gdc_colour = cep->ce_last
961          ) != NULL && cep->ce_last->ce_enabled);
962 
963    if(cep->ce_current != NULL){
964       n_sighdl_t hdl;
965 
966       hdl = n_signal(SIGPIPE, SIG_IGN);
967       fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
968          cep->ce_outfp);
969       n_signal(SIGPIPE, hdl);
970    }
971 jleave:
972    NYD_OU;
973 }
974 
975 void
mx_colour_put(enum mx_colour_id cid,char const * ctag)976 mx_colour_put(enum mx_colour_id cid, char const *ctag){
977    NYD_IN;
978    if(mx_COLOUR_IS_ACTIVE()){
979       struct mx_colour_env *cep;
980 
981       cep = n_go_data->gdc_colour;
982 
983       if(cep->ce_current != NULL)
984          fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
985             cep->ce_outfp);
986 
987       if((cep->ce_current = a_colour_map_find(cep->ce_ctx, cid, ctag)) != NULL)
988          fwrite(cep->ce_current->cm_pen.cp_dat.s,
989             cep->ce_current->cm_pen.cp_dat.l, 1, cep->ce_outfp);
990    }
991    NYD_OU;
992 }
993 
994 void
mx_colour_reset(void)995 mx_colour_reset(void){
996    NYD_IN;
997    if(mx_COLOUR_IS_ACTIVE()){
998       struct mx_colour_env *cep;
999 
1000       cep = n_go_data->gdc_colour;
1001 
1002       if(cep->ce_current != NULL){
1003          cep->ce_current = NULL;
1004          fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
1005             cep->ce_outfp);
1006       }
1007    }
1008    NYD_OU;
1009 }
1010 
1011 struct str const *
mx_colour_reset_to_str(void)1012 mx_colour_reset_to_str(void){
1013    struct str *rv;
1014    NYD_IN;
1015 
1016    if(mx_COLOUR_IS_ACTIVE())
1017       rv = &a_colour_g.cg_reset.cp_dat;
1018    else
1019       rv = NULL;
1020    NYD_OU;
1021    return rv;
1022 }
1023 
1024 struct mx_colour_pen *
mx_colour_pen_create(enum mx_colour_id cid,char const * ctag)1025 mx_colour_pen_create(enum mx_colour_id cid, char const *ctag){
1026    struct a_colour_map *cmp;
1027    struct mx_colour_pen *rv;
1028    NYD_IN;
1029 
1030    if(mx_COLOUR_IS_ACTIVE() &&
1031          (cmp = a_colour_map_find(n_go_data->gdc_colour->ce_ctx, cid, ctag)
1032           ) != NULL){
1033       union {void *vp; char *cp; struct mx_colour_pen *cpp;} u;
1034 
1035       u.vp = cmp;
1036       rv = u.cpp;
1037    }else
1038       rv = NULL;
1039    NYD_OU;
1040    return rv;
1041 }
1042 
1043 void
mx_colour_pen_put(struct mx_colour_pen * self)1044 mx_colour_pen_put(struct mx_colour_pen *self){
1045    NYD_IN;
1046    if(mx_COLOUR_IS_ACTIVE()){
1047       union {void *vp; char *cp; struct a_colour_map *cmp;} u;
1048       struct mx_colour_env *cep;
1049 
1050       cep = n_go_data->gdc_colour;
1051       u.vp = self;
1052 
1053       if(u.cmp != cep->ce_current){
1054          if(cep->ce_current != NULL)
1055             fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l,
1056                1, cep->ce_outfp);
1057 
1058          if(u.cmp != NULL)
1059             fwrite(self->cp_dat.s, self->cp_dat.l, 1, cep->ce_outfp);
1060          cep->ce_current = u.cmp;
1061       }
1062    }
1063    NYD_OU;
1064 }
1065 
1066 struct str const *
mx_colour_pen_to_str(struct mx_colour_pen * self)1067 mx_colour_pen_to_str(struct mx_colour_pen *self){
1068    struct str *rv;
1069    NYD_IN;
1070 
1071    if(mx_COLOUR_IS_ACTIVE() && self != NULL)
1072       rv = &self->cp_dat;
1073    else
1074       rv = NULL;
1075    NYD_OU;
1076    return rv;
1077 }
1078 
1079 struct str const *
mx_colour_get_reset_cseq(u32 get_flags)1080 mx_colour_get_reset_cseq(u32 get_flags){
1081    struct str const *rv;
1082    NYD_IN;
1083 
1084    rv = a_colour_ok_to_go(get_flags) ? &a_colour_g.cg_reset.cp_dat : NIL;
1085    NYD_OU;
1086    return rv;
1087 }
1088 
1089 struct mx_colour_pen *
mx_colour_get_pen(u32 get_flags,enum mx_colour_ctx cctx,enum mx_colour_id cid,char const * ctag)1090 mx_colour_get_pen(u32 get_flags, enum mx_colour_ctx cctx,
1091       enum mx_colour_id cid, char const *ctag){
1092    struct a_colour_map *cmp;
1093    struct mx_colour_pen *rv;
1094    NYD_IN;
1095 
1096    rv = NIL;
1097 
1098    if(a_colour_ok_to_go(get_flags) &&
1099          (cmp = a_colour_map_find(cctx, cid, ctag)) != NIL){
1100       union {void *v; struct mx_colour_pen *cp;} p;
1101 
1102       p.v = cmp;
1103       rv = p.cp;
1104    }
1105    NYD_OU;
1106    return rv;
1107 }
1108 
1109 struct str const *
mx_colour_pen_get_cseq(struct mx_colour_pen const * self)1110 mx_colour_pen_get_cseq(struct mx_colour_pen const *self){
1111    struct str const *rv;
1112    NYD2_IN;
1113 
1114    rv = (self != NIL) ? &self->cp_dat : 0;
1115    NYD2_OU;
1116    return rv;
1117 }
1118 
1119 #include "su/code-ou.h"
1120 #endif /* mx_HAVE_COLOUR */
1121 /* s-it-mode */
1122