make-release.txt: new signing OpenPGP subkey
[s-mailx.git] / colour.c
blob313870827110a81b8c01ab6b662760e1e9054ff9
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?colour' commands, and anything working with it.
3 *@ TODO n_colour_env should be objects, n_COLOUR_IS_ACTIVE() should take
4 *@ TODO such an object! We still should work together with n_go_data,
5 *@ TODO but only for cleanup purposes. No stack at all, that is to say!
6 *@ TODO (Note we yet use autorec memory, so with JUMPS this needs care!)
8 * Copyright (c) 2014 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * Permission to use, copy, modify, and/or distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #undef n_FILE
23 #define n_FILE colour
25 #ifndef HAVE_AMALGAMATION
26 # include "nail.h"
27 #endif
29 EMPTY_FILE()
30 #ifdef HAVE_COLOUR
32 /* Not needed publically, but extends a set from nail.h */
33 #define n_COLOUR_TAG_ERR ((char*)-1)
34 #define a_COLOUR_TAG_IS_SPECIAL(P) (PTR2SIZE(P) >= PTR2SIZE(-3))
36 enum a_colour_type{
37 a_COLOUR_T_256,
38 a_COLOUR_T_8,
39 a_COLOUR_T_1,
40 a_COLOUR_T_NONE, /* EQ largest real colour + 1! */
41 a_COLOUR_T_UNKNOWN /* Initial value: real one queried before 1st use */
44 enum a_colour_tag_type{
45 a_COLOUR_TT_NONE,
46 a_COLOUR_TT_DOT = 1<<0, /* "dot" */
47 a_COLOUR_TT_OLDER = 1<<1, /* "older" */
48 a_COLOUR_TT_HEADERS = 1<<2, /* Comma-separated list of headers allowed */
50 a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER,
51 a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS
54 struct a_colour_type_map{
55 ui8_t ctm_type; /* a_colour_type */
56 char ctm_name[7];
59 struct a_colour_map_id{
60 ui8_t cmi_ctx; /* enum n_colour_ctx */
61 ui8_t cmi_id; /* enum n_colour_id */
62 ui8_t cmi_tt; /* enum a_colour_tag_type */
63 char const cmi_name[13];
65 n_CTA(n__COLOUR_IDS <= UI8_MAX, "Enumeration exceeds storage datatype");
67 struct n_colour_pen{
68 struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */
71 struct a_colour_map /* : public n_colour_pen */{
72 struct n_colour_pen cm_pen; /* Points into .cm_buf */
73 struct a_colour_map *cm_next;
74 char const *cm_tag; /* Colour tag or NULL for default (last) */
75 struct a_colour_map_id const *cm_cmi;
76 #ifdef HAVE_REGEX
77 regex_t *cm_regex;
78 #endif
79 ui32_t cm_refcnt; /* Beware of reference drops in recursions */
80 ui32_t cm_user_off; /* User input offset in .cm_buf */
81 char cm_buf[n_VFIELD_SIZE(0)];
84 struct a_colour_g{
85 bool_t cg_is_init;
86 ui8_t cg_type; /* a_colour_type */
87 ui8_t __cg_pad[6];
88 struct n_colour_pen cg_reset; /* The reset sequence */
89 struct a_colour_map
90 *cg_maps[a_COLOUR_T_NONE][n__COLOUR_CTX_MAX1][n__COLOUR_IDS];
91 char cg__reset_buf[n_ALIGN_SMALL(sizeof("\033[0m"))];
94 /* C99: use [INDEX]={} */
95 /* */
96 n_CTA(a_COLOUR_T_256 == 0, "Unexpected value of constant");
97 n_CTA(a_COLOUR_T_8 == 1, "Unexpected value of constant");
98 n_CTA(a_COLOUR_T_1 == 2, "Unexpected value of constant");
99 static char const a_colour_types[][8] = {"256", "iso", "mono"};
101 static struct a_colour_type_map const a_colour_type_maps[] = {
102 {a_COLOUR_T_256, "256"},
103 {a_COLOUR_T_8, "8"}, {a_COLOUR_T_8, "iso"}, {a_COLOUR_T_8, "ansi"},
104 {a_COLOUR_T_1, "1"}, {a_COLOUR_T_1, "mono"}
107 n_CTA(n_COLOUR_CTX_SUM == 0, "Unexpected value of constant");
108 n_CTA(n_COLOUR_CTX_VIEW == 1, "Unexpected value of constant");
109 n_CTA(n_COLOUR_CTX_MLE == 2, "Unexpected value of constant");
110 static char const a_colour_ctx_prefixes[n__COLOUR_CTX_MAX1][8] = {
111 "sum-", "view-", "mle-"
114 static struct a_colour_map_id const
115 a_colour_map_ids[n__COLOUR_CTX_MAX1][n__COLOUR_IDS] = {{
116 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"},
117 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"},
118 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"},
119 }, {
120 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"},
121 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"},
122 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE, "msginfo"},
123 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE, "partinfo"},
124 }, {
125 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_POSITION, a_COLOUR_TT_NONE, "position"},
126 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_PROMPT, a_COLOUR_TT_NONE, "prompt"},
128 #define a_COLOUR_MAP_SHOW_FIELDWIDTH \
129 (int)(sizeof("view-")-1 + sizeof("partinfo")-1)
131 static struct a_colour_g a_colour_g;
133 /* */
134 static void a_colour_init(void);
136 /* Find the type or -1 */
137 static enum a_colour_type a_colour_type_find(char const *name);
139 /* `(un)?colour' implementations */
140 static bool_t a_colour_mux(char **argv);
141 static bool_t a_colour_unmux(char **argv);
143 static bool_t a_colour__show(enum a_colour_type ct);
144 /* (regexpp may be NULL) */
145 static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip,
146 char const *ctag, void **regexpp);
148 /* Try to find a mapping identity for user given slotname */
149 static struct a_colour_map_id const *a_colour_map_id_find(char const *slotname);
151 /* Find an existing mapping for the given combination */
152 static struct a_colour_map *a_colour_map_find(enum n_colour_id cid,
153 enum n_colour_ctx cctx, char const *ctag);
155 /* In-/Decrement reference counter, destroy if counts gets zero */
156 #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0)
157 static void a_colour_map_unref(struct a_colour_map *self);
159 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user
160 * input spec, store it or on error message in *store */
161 static bool_t a_colour_iso6429(enum a_colour_type ct, char **store,
162 char const *spec);
164 static void
165 a_colour_init(void){
166 NYD2_ENTER;
167 a_colour_g.cg_is_init = TRU1;
168 memcpy(a_colour_g.cg_reset.cp_dat.s = a_colour_g.cg__reset_buf, "\033[0m",
169 a_colour_g.cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */
170 a_colour_g.cg_type = a_COLOUR_T_UNKNOWN;
171 NYD2_LEAVE;
174 static enum a_colour_type
175 a_colour_type_find(char const *name){
176 struct a_colour_type_map const *ctmp;
177 enum a_colour_type rv;
178 NYD2_ENTER;
180 ctmp = a_colour_type_maps;
181 do if(!asccasecmp(ctmp->ctm_name, name)){
182 rv = ctmp->ctm_type;
183 goto jleave;
184 }while(PTRCMP(++ctmp, !=, a_colour_type_maps + n_NELEM(a_colour_type_maps)));
186 rv = (enum a_colour_type)-1;
187 jleave:
188 NYD2_LEAVE;
189 return rv;
192 static bool_t
193 a_colour_mux(char **argv){
194 void *regexp;
195 char const *mapname, *ctag;
196 struct a_colour_map **cmap, *blcmp, *lcmp, *cmp;
197 struct a_colour_map_id const *cmip;
198 bool_t rv;
199 enum a_colour_type ct;
200 NYD2_ENTER;
202 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1 &&
203 (*argv != NULL || !n_is_all_or_aster(argv[-1]))){
204 n_err(_("`colour': invalid colour type %s\n"),
205 n_shexp_quote_cp(argv[-1], FAL0));
206 rv = FAL0;
207 goto jleave;
210 if(!a_colour_g.cg_is_init)
211 a_colour_init();
213 if(*argv == NULL){
214 rv = a_colour__show(ct);
215 goto jleave;
218 rv = FAL0;
219 regexp = NULL;
221 if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){
222 n_err(_("`colour': non-existing mapping: %s\n"),
223 n_shexp_quote_cp(mapname, FAL0));
224 goto jleave;
227 if(argv[1] == NULL){
228 n_err(_("`colour': %s: missing attribute argument\n"),
229 n_shexp_quote_cp(mapname, FAL0));
230 goto jleave;
233 /* Check whether preconditions are at all allowed, verify them as far as
234 * possible as necessary. For shell_quote() simplicity let's just ignore an
235 * empty precondition */
236 if((ctag = argv[2]) != NULL && *ctag != '\0'){
237 char const *xtag;
239 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
240 n_err(_("`colour': %s does not support preconditions\n"),
241 n_shexp_quote_cp(mapname, FAL0));
242 goto jleave;
243 }else if((xtag = a_colour__tag_identify(cmip, ctag, &regexp)) ==
244 n_COLOUR_TAG_ERR){
245 /* I18N: ..of colour mapping */
246 n_err(_("`colour': %s: invalid precondition: %s\n"),
247 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
248 goto jleave;
250 ctag = xtag;
253 /* At this time we have all the information to be able to query whether such
254 * a mapping is yet established. If so, destroy it */
255 for(blcmp = lcmp = NULL,
256 cmp = *(cmap =
257 &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
258 cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){
259 char const *xctag = cmp->cm_tag;
261 if(xctag == ctag ||
262 (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
263 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
264 !strcmp(xctag, ctag))){
265 if(lcmp == NULL)
266 *cmap = cmp->cm_next;
267 else
268 lcmp->cm_next = cmp->cm_next;
269 a_colour_map_unref(cmp);
270 break;
274 /* Create mapping */
275 /* C99 */{
276 size_t tl, ul, cl;
277 char *bp, *cp;
279 if(!a_colour_iso6429(ct, &cp, argv[1])){
280 /* I18N: colour command: mapping: error message: user argument */
281 n_err(_("`colour': %s: %s: %s\n"), n_shexp_quote_cp(mapname, FAL0),
282 cp, n_shexp_quote_cp(argv[1], FAL0));
283 goto jleave;
286 tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag)) ? strlen(ctag) : 0;
287 cmp = n_alloc(n_VSTRUCT_SIZEOF(struct a_colour_map, cm_buf) +
288 tl +1 + (ul = strlen(argv[1])) +1 + (cl = strlen(cp)) +1);
290 /* .cm_buf stuff */
291 cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf;
292 cmp->cm_pen.cp_dat.l = cl;
293 memcpy(bp, cp, ++cl);
294 bp += cl;
296 cmp->cm_user_off = (ui32_t)PTR2SIZE(bp - cmp->cm_buf);
297 memcpy(bp, argv[1], ++ul);
298 bp += ul;
300 if(tl > 0){
301 cmp->cm_tag = bp;
302 memcpy(bp, ctag, ++tl);
303 /*bp += tl;*/
304 }else
305 cmp->cm_tag = ctag;
307 /* Non-buf stuff; default mapping */
308 if(lcmp != NULL){
309 /* Default mappings must be last */
310 if(ctag == NULL){
311 while(lcmp->cm_next != NULL)
312 lcmp = lcmp->cm_next;
313 }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){
314 if((lcmp = blcmp) == NULL)
315 goto jlinkhead;
317 cmp->cm_next = lcmp->cm_next;
318 lcmp->cm_next = cmp;
319 }else{
320 jlinkhead:
321 cmp->cm_next = *cmap;
322 *cmap = cmp;
324 cmp->cm_cmi = cmip;
325 #ifdef HAVE_REGEX
326 cmp->cm_regex = regexp;
327 #endif
328 cmp->cm_refcnt = 0;
329 a_colour_map_ref(cmp);
331 rv = TRU1;
332 jleave:
333 NYD2_LEAVE;
334 return rv;
337 static bool_t
338 a_colour_unmux(char **argv){
339 char const *mapname, *ctag, *xtag;
340 struct a_colour_map **cmap, *lcmp, *cmp;
341 struct a_colour_map_id const *cmip;
342 enum a_colour_type ct;
343 bool_t aster, rv;
344 NYD2_ENTER;
346 rv = TRU1;
347 aster = FAL0;
349 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
350 if(!n_is_all_or_aster(argv[-1])){
351 n_err(_("`uncolour': invalid colour type %s\n"),
352 n_shexp_quote_cp(argv[-1], FAL0));
353 rv = FAL0;
354 goto j_leave;
356 aster = TRU1;
357 ct = 0;
360 mapname = argv[0];
361 ctag = argv[1];
363 if(!a_colour_g.cg_is_init)
364 goto jemap;
366 /* Delete anything? */
367 jredo:
368 if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){
369 size_t i1, i2;
370 struct a_colour_map *tmp;
372 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
373 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2)
374 for(cmp = *(cmap = &a_colour_g.cg_maps[ct][i1][i2]), *cmap = NULL;
375 cmp != NULL;){
376 tmp = cmp;
377 cmp = cmp->cm_next;
378 a_colour_map_unref(tmp);
380 }else{
381 if((cmip = a_colour_map_id_find(mapname)) == NULL){
382 rv = FAL0;
383 jemap:
384 /* I18N: colour command, mapping and precondition (option in quotes) */
385 n_err(_("`uncolour': non-existing mapping: %s%s%s\n"),
386 n_shexp_quote_cp(mapname, FAL0), (ctag == NULL ? n_empty : " "),
387 (ctag == NULL ? n_empty : n_shexp_quote_cp(ctag, FAL0)));
388 goto jleave;
391 if((xtag = ctag) != NULL){
392 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
393 n_err(_("`uncolour': %s does not support preconditions\n"),
394 n_shexp_quote_cp(mapname, FAL0));
395 rv = FAL0;
396 goto jleave;
397 }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) ==
398 n_COLOUR_TAG_ERR){
399 n_err(_("`uncolour': %s: invalid precondition: %s\n"),
400 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
401 rv = FAL0;
402 goto jleave;
404 /* (Improve user experience) */
405 if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag))
406 ctag = xtag;
409 lcmp = NULL;
410 cmp = *(cmap = &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
411 for(;;){
412 char const *xctag;
414 if(cmp == NULL){
415 rv = FAL0;
416 goto jemap;
418 if((xctag = cmp->cm_tag) == ctag)
419 break;
420 if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
421 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
422 !strcmp(xctag, ctag))
423 break;
424 lcmp = cmp;
425 cmp = cmp->cm_next;
428 if(lcmp == NULL)
429 *cmap = cmp->cm_next;
430 else
431 lcmp->cm_next = cmp->cm_next;
432 a_colour_map_unref(cmp);
435 jleave:
436 if(aster && ++ct != a_COLOUR_T_NONE)
437 goto jredo;
438 j_leave:
439 NYD2_LEAVE;
440 return rv;
443 static bool_t
444 a_colour__show(enum a_colour_type ct){
445 struct a_colour_map *cmp;
446 size_t i1, i2;
447 bool_t rv;
448 NYD2_ENTER;
450 /* Show all possible types? */
451 if((rv = (ct == (enum a_colour_type)-1 ? TRU1 : FAL0)))
452 ct = 0;
453 jredo:
454 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
455 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2){
456 if((cmp = a_colour_g.cg_maps[ct][i1][i2]) == NULL)
457 continue;
459 while(cmp != NULL){
460 char const *tagann, *tag;
462 tagann = n_empty;
463 if((tag = cmp->cm_tag) == NULL)
464 tag = n_empty;
465 else if(tag == n_COLOUR_TAG_SUM_DOT)
466 tag = "dot";
467 else if(tag == n_COLOUR_TAG_SUM_OLDER)
468 tag = "older";
469 #ifdef HAVE_REGEX
470 else if(cmp->cm_regex != NULL)
471 tagann = "[rx] ";
472 #endif
473 fprintf(n_stdout, "colour %s %-*s %s %s%s\n",
474 a_colour_types[ct], a_COLOUR_MAP_SHOW_FIELDWIDTH,
475 savecat(a_colour_ctx_prefixes[i1],
476 a_colour_map_ids[i1][i2].cmi_name),
477 (char const*)cmp->cm_buf + cmp->cm_user_off,
478 tagann, n_shexp_quote_cp(tag, TRU1));
479 cmp = cmp->cm_next;
483 if(rv && ++ct != a_COLOUR_T_NONE)
484 goto jredo;
485 rv = TRU1;
486 NYD2_LEAVE;
487 return rv;
490 static char const *
491 a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag,
492 void **regexpp){
493 NYD2_ENTER;
494 n_UNUSED(regexpp);
496 if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !asccasecmp(ctag, "dot"))
497 ctag = n_COLOUR_TAG_SUM_DOT;
498 else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) && !asccasecmp(ctag, "older"))
499 ctag = n_COLOUR_TAG_SUM_OLDER;
500 else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){
501 char *cp, c;
502 size_t i;
504 /* Can this be a valid list of headers? However, with regular expressions
505 * simply use the input as such if it appears to be a regex */
506 #ifdef HAVE_REGEX
507 if(n_is_maybe_regex(ctag)){
508 int s;
510 if(regexpp != NULL &&
511 (s = regcomp(*regexpp = n_alloc(sizeof(regex_t)), ctag,
512 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
513 n_err(_("`colour': invalid regular expression: %s: %s\n"),
514 n_shexp_quote_cp(ctag, FAL0), n_regex_err_to_doc(NULL, s));
515 n_free(*regexpp);
516 goto jetag;
518 }else
519 #endif
521 /* Normalize to lowercase and strip any whitespace before use */
522 i = strlen(ctag);
523 cp = n_autorec_alloc(i +1);
525 for(i = 0; (c = *ctag++) != '\0';){
526 bool_t isblspc = blankspacechar(c);
528 if(!isblspc && !alnumchar(c) && c != '-' && c != ',')
529 goto jetag;
530 /* Since we compare header names as they come from the message this
531 * lowercasing is however redundant: we need to asccasecmp() them */
532 if(!isblspc)
533 cp[i++] = lowerconv(c);
535 cp[i] = '\0';
536 ctag = cp;
538 }else
539 jetag:
540 ctag = n_COLOUR_TAG_ERR;
541 NYD2_LEAVE;
542 return ctag;
545 static struct a_colour_map_id const *
546 a_colour_map_id_find(char const *cp){
547 size_t i;
548 struct a_colour_map_id const (*cmip)[n__COLOUR_IDS], *rv;
549 NYD2_ENTER;
551 rv = NULL;
553 for(i = 0;; ++i){
554 if(i == n__COLOUR_IDS)
555 goto jleave;
556 else{
557 size_t j = strlen(a_colour_ctx_prefixes[i]);
558 if(!ascncasecmp(cp, a_colour_ctx_prefixes[i], j)){
559 cp += j;
560 break;
564 cmip = &a_colour_map_ids[i];
566 for(i = 0;; ++i){
567 if(i == n__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){
568 rv = NULL;
569 break;
571 if(!asccasecmp(cp, rv->cmi_name))
572 break;
574 jleave:
575 NYD2_LEAVE;
576 return rv;
579 static struct a_colour_map *
580 a_colour_map_find(enum n_colour_id cid, enum n_colour_ctx cctx,
581 char const *ctag){
582 struct a_colour_map *cmp;
583 NYD2_ENTER;
585 cmp = a_colour_g.cg_maps[a_colour_g.cg_type][cctx][cid];
586 for(; cmp != NULL; cmp = cmp->cm_next){
587 char const *xtag = cmp->cm_tag;
589 if(xtag == ctag)
590 break;
591 if(xtag == NULL)
592 break;
593 if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag))
594 continue;
595 #ifdef HAVE_REGEX
596 if(cmp->cm_regex != NULL){
597 if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH)
598 break;
599 }else
600 #endif
601 if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){
602 char *hlist = savestr(xtag), *cp;
604 while((cp = n_strsep(&hlist, ',', TRU1)) != NULL){
605 if(!asccasecmp(cp, ctag))
606 break;
608 if(cp != NULL)
609 break;
612 NYD2_LEAVE;
613 return cmp;
616 static void
617 a_colour_map_unref(struct a_colour_map *self){
618 NYD2_ENTER;
619 if(--self->cm_refcnt == 0){
620 #ifdef HAVE_REGEX
621 if(self->cm_regex != NULL){
622 regfree(self->cm_regex);
623 n_free(self->cm_regex);
625 #endif
626 n_free(self);
628 NYD2_LEAVE;
631 static bool_t
632 a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){
633 struct isodesc{
634 char id_name[15];
635 char id_modc;
636 } const fta[] = {
637 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
638 }, ca[] = {
639 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
640 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
641 }, *idp;
642 char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")];
643 ui8_t ftno_base, ftno;
644 bool_t rv;
645 NYD_ENTER;
647 rv = FAL0;
648 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
649 cfg[0] = cfg[1] = 0;
651 /* Since we use autorec_alloc(), reuse the n_strsep() buffer also for the
652 * return value, ensure we have enough room for that */
653 /* C99 */{
654 size_t i = strlen(spec) +1;
655 xspec = n_autorec_alloc(n_MAX(i,
656 sizeof("\033[1;4;7;38;5;255;48;5;255m")));
657 memcpy(xspec, spec, i);
658 spec = xspec;
661 /* Iterate over the colour spec */
662 ftno = 0;
663 while((cp = n_strsep(&xspec, ',', TRU1)) != NULL){
664 char *y, *x = strchr(cp, '=');
665 if(x == NULL){
666 jbail:
667 *store = n_UNCONST(_("invalid attribute list"));
668 goto jleave;
670 *x++ = '\0';
672 if(!asccasecmp(cp, "ft")){
673 if(!asccasecmp(x, "inverse")){
674 n_OBSOLETE(_("please use reverse for ft= fonts, not inverse"));
675 x = n_UNCONST("reverse");
677 for(idp = fta;; ++idp)
678 if(idp == fta + n_NELEM(fta)){
679 *store = n_UNCONST(_("invalid font attribute"));
680 goto jleave;
681 }else if(!asccasecmp(x, idp->id_name)){
682 if(ftno < n_NELEM(fg))
683 fg[ftno++] = idp->id_modc;
684 else{
685 *store = n_UNCONST(_("too many font attributes"));
686 goto jleave;
688 break;
690 }else if(!asccasecmp(cp, "fg")){
691 y = cfg + 0;
692 goto jiter_colour;
693 }else if(!asccasecmp(cp, "bg")){
694 y = cfg + 1;
695 jiter_colour:
696 if(ct == a_COLOUR_T_1){
697 *store = n_UNCONST(_("colours are not allowed"));
698 goto jleave;
700 /* Maybe 256 color spec */
701 if(digitchar(x[0])){
702 ui8_t xv;
704 if(ct == a_COLOUR_T_8){
705 *store = n_UNCONST(_("invalid colour for 8-colour mode"));
706 goto jleave;
709 if((n_idec_ui8_cp(&xv, x, 10, NULL
710 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
711 ) != n_IDEC_STATE_CONSUMED){
712 *store = n_UNCONST(_("invalid 256-colour specification"));
713 goto jleave;
715 y[0] = 5;
716 memcpy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x,
717 (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4)));
718 }else for(idp = ca;; ++idp)
719 if(idp == ca + n_NELEM(ca)){
720 *store = n_UNCONST(_("invalid colour attribute"));
721 goto jleave;
722 }else if(!asccasecmp(x, idp->id_name)){
723 y[0] = 1;
724 y[2] = idp->id_modc;
725 break;
727 }else
728 goto jbail;
731 /* Restore our autorec_alloc() buffer, create return value */
732 xspec = n_UNCONST(spec);
733 if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */
734 xspec[0] = '\033';
735 xspec[1] = '[';
736 xspec += 2;
738 for(ftno_base = ftno; ftno > 0;){
739 if(ftno-- != ftno_base)
740 *xspec++ = ';';
741 *xspec++ = fg[ftno];
744 if(cfg[0]){
745 if(ftno_base > 0)
746 *xspec++ = ';';
747 xspec[0] = '3';
748 if(cfg[0] == 1){
749 xspec[1] = cfg[2];
750 xspec += 2;
751 }else{
752 memcpy(xspec + 1, "8;5;", 4);
753 xspec += 5;
754 for(ftno = 2; cfg[ftno] != '\0'; ++ftno)
755 *xspec++ = cfg[ftno];
759 if(cfg[1]){
760 if(ftno_base > 0 || cfg[0])
761 *xspec++ = ';';
762 xspec[0] = '4';
763 if(cfg[1] == 1){
764 xspec[1] = cfg[3];
765 xspec += 2;
766 }else{
767 memcpy(xspec + 1, "8;5;", 4);
768 xspec += 5;
769 for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno)
770 *xspec++ = cfg[ftno];
774 *xspec++ = 'm';
776 *xspec = '\0';
777 *store = n_UNCONST(spec);
778 rv = TRU1;
779 jleave:
780 NYD_LEAVE;
781 return rv;
784 FL int
785 c_colour(void *v){
786 int rv;
787 NYD_ENTER;
789 rv = !a_colour_mux(v);
790 NYD_LEAVE;
791 return rv;
794 FL int
795 c_uncolour(void *v){
796 int rv;
797 NYD_ENTER;
799 rv = !a_colour_unmux(v);
800 NYD_LEAVE;
801 return rv;
804 FL void
805 n_colour_stack_del(struct n_go_data_ctx *gdcp){
806 struct n_colour_env *vp, *cep;
807 NYD_ENTER;
809 vp = gdcp->gdc_colour;
810 gdcp->gdc_colour = NULL;
811 gdcp->gdc_colour_active = FAL0;
813 while((cep = vp) != NULL){
814 vp = cep->ce_last;
816 if(cep->ce_current != NULL && cep->ce_outfp == n_stdout){
817 n_sighdl_t hdl;
819 hdl = n_signal(SIGPIPE, SIG_IGN);
820 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
821 cep->ce_outfp);
822 fflush(cep->ce_outfp);
823 n_signal(SIGPIPE, hdl);
826 NYD_LEAVE;
829 FL void
830 n_colour_env_create(enum n_colour_ctx cctx, FILE *fp, bool_t pager_used){
831 struct n_colour_env *cep;
832 NYD_ENTER;
834 if(!(n_psonce & n_PSO_INTERACTIVE))
835 goto jleave;
837 if(!a_colour_g.cg_is_init)
838 a_colour_init();
840 /* TODO reset the outer level? Iff ce_outfp==fp? */
841 cep = n_autorec_alloc(sizeof *cep);
842 cep->ce_last = n_go_data->gdc_colour;
843 cep->ce_enabled = FAL0;
844 cep->ce_ctx = cctx;
845 cep->ce_ispipe = pager_used;
846 cep->ce_outfp = fp;
847 cep->ce_current = NULL;
848 n_go_data->gdc_colour_active = FAL0;
849 n_go_data->gdc_colour = cep;
851 if(ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager)))
852 goto jleave;
854 if(n_UNLIKELY(a_colour_g.cg_type == a_COLOUR_T_UNKNOWN)){
855 struct n_termcap_value tv;
857 if(!n_termcap_query(n_TERMCAP_QUERY_colors, &tv)){
858 a_colour_g.cg_type = a_COLOUR_T_NONE;
859 goto jleave;
860 }else
861 switch(tv.tv_data.tvd_numeric){
862 case 256: a_colour_g.cg_type = a_COLOUR_T_256; break;
863 case 8: a_colour_g.cg_type = a_COLOUR_T_8; break;
864 case 1: a_colour_g.cg_type = a_COLOUR_T_1; break;
865 default:
866 if(n_poption & n_PO_D_V)
867 n_err(_("Ignoring unsupported termcap entry for Co(lors)\n"));
868 /* FALLTHRU */
869 case 0:
870 a_colour_g.cg_type = a_COLOUR_T_NONE;
871 goto jleave;
875 if(a_colour_g.cg_type == a_COLOUR_T_NONE)
876 goto jleave;
878 n_go_data->gdc_colour_active = cep->ce_enabled = TRU1;
879 jleave:
880 NYD_LEAVE;
883 FL void
884 n_colour_env_gut(void){
885 struct n_colour_env *cep;
886 NYD_ENTER;
888 if(!(n_psonce & n_PSO_INTERACTIVE))
889 goto jleave;
891 /* TODO v15: Could happen because of jump, causing _stack_del().. */
892 if((cep = n_go_data->gdc_colour) == NULL)
893 goto jleave;
894 n_go_data->gdc_colour_active = ((n_go_data->gdc_colour = cep->ce_last
895 ) != NULL && cep->ce_last->ce_enabled);
897 if(cep->ce_current != NULL){
898 n_sighdl_t hdl;
900 hdl = n_signal(SIGPIPE, SIG_IGN);
901 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
902 cep->ce_outfp);
903 n_signal(SIGPIPE, hdl);
905 jleave:
906 NYD_LEAVE;
909 FL void
910 n_colour_put(enum n_colour_id cid, char const *ctag){
911 NYD_ENTER;
912 if(n_COLOUR_IS_ACTIVE()){
913 struct n_colour_env *cep;
915 cep = n_go_data->gdc_colour;
917 if(cep->ce_current != NULL)
918 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
919 cep->ce_outfp);
921 if((cep->ce_current = a_colour_map_find(cid, cep->ce_ctx, ctag)) != NULL)
922 fwrite(cep->ce_current->cm_pen.cp_dat.s,
923 cep->ce_current->cm_pen.cp_dat.l, 1, cep->ce_outfp);
925 NYD_LEAVE;
928 FL void
929 n_colour_reset(void){
930 NYD_ENTER;
931 if(n_COLOUR_IS_ACTIVE()){
932 struct n_colour_env *cep;
934 cep = n_go_data->gdc_colour;
936 if(cep->ce_current != NULL){
937 cep->ce_current = NULL;
938 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
939 cep->ce_outfp);
942 NYD_LEAVE;
945 FL struct str const *
946 n_colour_reset_to_str(void){
947 struct str *rv;
948 NYD_ENTER;
950 if(n_COLOUR_IS_ACTIVE())
951 rv = &a_colour_g.cg_reset.cp_dat;
952 else
953 rv = NULL;
954 NYD_LEAVE;
955 return rv;
958 FL struct n_colour_pen *
959 n_colour_pen_create(enum n_colour_id cid, char const *ctag){
960 struct a_colour_map *cmp;
961 struct n_colour_pen *rv;
962 NYD_ENTER;
964 if(n_COLOUR_IS_ACTIVE() &&
965 (cmp = a_colour_map_find(cid, n_go_data->gdc_colour->ce_ctx, ctag)
966 ) != NULL){
967 union {void *vp; char *cp; struct n_colour_pen *cpp;} u;
969 u.vp = cmp;
970 rv = u.cpp;
971 }else
972 rv = NULL;
973 NYD_LEAVE;
974 return rv;
977 FL void
978 n_colour_pen_put(struct n_colour_pen *self){
979 NYD_ENTER;
980 if(n_COLOUR_IS_ACTIVE()){
981 union {void *vp; char *cp; struct a_colour_map *cmp;} u;
982 struct n_colour_env *cep;
984 cep = n_go_data->gdc_colour;
985 u.vp = self;
987 if(u.cmp != cep->ce_current){
988 if(cep->ce_current != NULL)
989 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l,
990 1, cep->ce_outfp);
992 if(u.cmp != NULL)
993 fwrite(self->cp_dat.s, self->cp_dat.l, 1, cep->ce_outfp);
994 cep->ce_current = u.cmp;
997 NYD_LEAVE;
1000 FL struct str const *
1001 n_colour_pen_to_str(struct n_colour_pen *self){
1002 struct str *rv;
1003 NYD_ENTER;
1005 if(n_COLOUR_IS_ACTIVE() && self != NULL)
1006 rv = &self->cp_dat;
1007 else
1008 rv = NULL;
1009 NYD_LEAVE;
1010 return rv;
1012 #endif /* HAVE_COLOUR */
1014 /* s-it-mode */