make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / colour.c
blobc8d871cf4e1b4c8d171758a6e1dd45aab47ec775
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>.
9 * SPDX-License-Identifier: ISC
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 #undef n_FILE
24 #define n_FILE colour
26 #ifndef HAVE_AMALGAMATION
27 # include "nail.h"
28 #endif
30 EMPTY_FILE()
31 #ifdef HAVE_COLOUR
33 /* Not needed publically, but extends a set from nail.h */
34 #define n_COLOUR_TAG_ERR ((char*)-1)
35 #define a_COLOUR_TAG_IS_SPECIAL(P) (PTR2SIZE(P) >= PTR2SIZE(-3))
37 enum a_colour_type{
38 a_COLOUR_T_256,
39 a_COLOUR_T_8,
40 a_COLOUR_T_1,
41 a_COLOUR_T_NONE, /* EQ largest real colour + 1! */
42 a_COLOUR_T_UNKNOWN /* Initial value: real one queried before 1st use */
45 enum a_colour_tag_type{
46 a_COLOUR_TT_NONE,
47 a_COLOUR_TT_DOT = 1<<0, /* "dot" */
48 a_COLOUR_TT_OLDER = 1<<1, /* "older" */
49 a_COLOUR_TT_HEADERS = 1<<2, /* Comma-separated list of headers allowed */
51 a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER,
52 a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS
55 struct a_colour_type_map{
56 ui8_t ctm_type; /* a_colour_type */
57 char ctm_name[7];
60 struct a_colour_map_id{
61 ui8_t cmi_ctx; /* enum n_colour_ctx */
62 ui8_t cmi_id; /* enum n_colour_id */
63 ui8_t cmi_tt; /* enum a_colour_tag_type */
64 char const cmi_name[13];
66 n_CTA(n__COLOUR_IDS <= UI8_MAX, "Enumeration exceeds storage datatype");
68 struct n_colour_pen{
69 struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */
72 struct a_colour_map /* : public n_colour_pen */{
73 struct n_colour_pen cm_pen; /* Points into .cm_buf */
74 struct a_colour_map *cm_next;
75 char const *cm_tag; /* Colour tag or NULL for default (last) */
76 struct a_colour_map_id const *cm_cmi;
77 #ifdef HAVE_REGEX
78 regex_t *cm_regex;
79 #endif
80 ui32_t cm_refcnt; /* Beware of reference drops in recursions */
81 ui32_t cm_user_off; /* User input offset in .cm_buf */
82 char cm_buf[n_VFIELD_SIZE(0)];
85 struct a_colour_g{
86 bool_t cg_is_init;
87 ui8_t cg_type; /* a_colour_type */
88 ui8_t __cg_pad[6];
89 struct n_colour_pen cg_reset; /* The reset sequence */
90 struct a_colour_map
91 *cg_maps[a_COLOUR_T_NONE][n__COLOUR_CTX_MAX1][n__COLOUR_IDS];
92 char cg__reset_buf[n_ALIGN_SMALL(sizeof("\033[0m"))];
95 /* C99: use [INDEX]={} */
96 /* */
97 n_CTA(a_COLOUR_T_256 == 0, "Unexpected value of constant");
98 n_CTA(a_COLOUR_T_8 == 1, "Unexpected value of constant");
99 n_CTA(a_COLOUR_T_1 == 2, "Unexpected value of constant");
100 static char const a_colour_types[][8] = {"256", "iso", "mono"};
102 static struct a_colour_type_map const a_colour_type_maps[] = {
103 {a_COLOUR_T_256, "256"},
104 {a_COLOUR_T_8, "8"}, {a_COLOUR_T_8, "iso"}, {a_COLOUR_T_8, "ansi"},
105 {a_COLOUR_T_1, "1"}, {a_COLOUR_T_1, "mono"}
108 n_CTA(n_COLOUR_CTX_SUM == 0, "Unexpected value of constant");
109 n_CTA(n_COLOUR_CTX_VIEW == 1, "Unexpected value of constant");
110 n_CTA(n_COLOUR_CTX_MLE == 2, "Unexpected value of constant");
111 static char const a_colour_ctx_prefixes[n__COLOUR_CTX_MAX1][8] = {
112 "sum-", "view-", "mle-"
115 static struct a_colour_map_id const
116 a_colour_map_ids[n__COLOUR_CTX_MAX1][n__COLOUR_IDS] = {{
117 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"},
118 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"},
119 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"},
120 }, {
121 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"},
122 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"},
123 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE, "msginfo"},
124 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE, "partinfo"},
125 }, {
126 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_POSITION, a_COLOUR_TT_NONE, "position"},
127 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_PROMPT, a_COLOUR_TT_NONE, "prompt"},
129 #define a_COLOUR_MAP_SHOW_FIELDWIDTH \
130 (int)(sizeof("view-")-1 + sizeof("partinfo")-1)
132 static struct a_colour_g a_colour_g;
134 /* */
135 static void a_colour_init(void);
137 /* Find the type or -1 */
138 static enum a_colour_type a_colour_type_find(char const *name);
140 /* `(un)?colour' implementations */
141 static bool_t a_colour_mux(char **argv);
142 static bool_t a_colour_unmux(char **argv);
144 static bool_t a_colour__show(enum a_colour_type ct);
145 /* (regexpp may be NULL) */
146 static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip,
147 char const *ctag, void **regexpp);
149 /* Try to find a mapping identity for user given slotname */
150 static struct a_colour_map_id const *a_colour_map_id_find(char const *slotname);
152 /* Find an existing mapping for the given combination */
153 static struct a_colour_map *a_colour_map_find(enum n_colour_id cid,
154 enum n_colour_ctx cctx, char const *ctag);
156 /* In-/Decrement reference counter, destroy if counts gets zero */
157 #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0)
158 static void a_colour_map_unref(struct a_colour_map *self);
160 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user
161 * input spec, store it or on error message in *store */
162 static bool_t a_colour_iso6429(enum a_colour_type ct, char **store,
163 char const *spec);
165 static void
166 a_colour_init(void){
167 NYD2_ENTER;
168 a_colour_g.cg_is_init = TRU1;
169 memcpy(a_colour_g.cg_reset.cp_dat.s = a_colour_g.cg__reset_buf, "\033[0m",
170 a_colour_g.cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */
171 a_colour_g.cg_type = a_COLOUR_T_UNKNOWN;
172 NYD2_LEAVE;
175 static enum a_colour_type
176 a_colour_type_find(char const *name){
177 struct a_colour_type_map const *ctmp;
178 enum a_colour_type rv;
179 NYD2_ENTER;
181 ctmp = a_colour_type_maps;
182 do if(!asccasecmp(ctmp->ctm_name, name)){
183 rv = ctmp->ctm_type;
184 goto jleave;
185 }while(PTRCMP(++ctmp, !=, a_colour_type_maps + n_NELEM(a_colour_type_maps)));
187 rv = (enum a_colour_type)-1;
188 jleave:
189 NYD2_LEAVE;
190 return rv;
193 static bool_t
194 a_colour_mux(char **argv){
195 void *regexp;
196 char const *mapname, *ctag;
197 struct a_colour_map **cmap, *blcmp, *lcmp, *cmp;
198 struct a_colour_map_id const *cmip;
199 bool_t rv;
200 enum a_colour_type ct;
201 NYD2_ENTER;
203 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1 &&
204 (*argv != NULL || !n_is_all_or_aster(argv[-1]))){
205 n_err(_("`colour': invalid colour type %s\n"),
206 n_shexp_quote_cp(argv[-1], FAL0));
207 rv = FAL0;
208 goto jleave;
211 if(!a_colour_g.cg_is_init)
212 a_colour_init();
214 if(*argv == NULL){
215 rv = a_colour__show(ct);
216 goto jleave;
219 rv = FAL0;
220 regexp = NULL;
222 if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){
223 n_err(_("`colour': non-existing mapping: %s\n"),
224 n_shexp_quote_cp(mapname, FAL0));
225 goto jleave;
228 if(argv[1] == NULL){
229 n_err(_("`colour': %s: missing attribute argument\n"),
230 n_shexp_quote_cp(mapname, FAL0));
231 goto jleave;
234 /* Check whether preconditions are at all allowed, verify them as far as
235 * possible as necessary. For shell_quote() simplicity let's just ignore an
236 * empty precondition */
237 if((ctag = argv[2]) != NULL && *ctag != '\0'){
238 char const *xtag;
240 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
241 n_err(_("`colour': %s does not support preconditions\n"),
242 n_shexp_quote_cp(mapname, FAL0));
243 goto jleave;
244 }else if((xtag = a_colour__tag_identify(cmip, ctag, &regexp)) ==
245 n_COLOUR_TAG_ERR){
246 /* I18N: ..of colour mapping */
247 n_err(_("`colour': %s: invalid precondition: %s\n"),
248 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
249 goto jleave;
251 ctag = xtag;
254 /* At this time we have all the information to be able to query whether such
255 * a mapping is yet established. If so, destroy it */
256 for(blcmp = lcmp = NULL,
257 cmp = *(cmap =
258 &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
259 cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){
260 char const *xctag = cmp->cm_tag;
262 if(xctag == ctag ||
263 (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
264 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
265 !strcmp(xctag, ctag))){
266 if(lcmp == NULL)
267 *cmap = cmp->cm_next;
268 else
269 lcmp->cm_next = cmp->cm_next;
270 a_colour_map_unref(cmp);
271 break;
275 /* Create mapping */
276 /* C99 */{
277 size_t tl, ul, cl;
278 char *bp, *cp;
280 if(!a_colour_iso6429(ct, &cp, argv[1])){
281 /* I18N: colour command: mapping: error message: user argument */
282 n_err(_("`colour': %s: %s: %s\n"), n_shexp_quote_cp(mapname, FAL0),
283 cp, n_shexp_quote_cp(argv[1], FAL0));
284 goto jleave;
287 tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag)) ? strlen(ctag) : 0;
288 cmp = n_alloc(n_VSTRUCT_SIZEOF(struct a_colour_map, cm_buf) +
289 tl +1 + (ul = strlen(argv[1])) +1 + (cl = strlen(cp)) +1);
291 /* .cm_buf stuff */
292 cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf;
293 cmp->cm_pen.cp_dat.l = cl;
294 memcpy(bp, cp, ++cl);
295 bp += cl;
297 cmp->cm_user_off = (ui32_t)PTR2SIZE(bp - cmp->cm_buf);
298 memcpy(bp, argv[1], ++ul);
299 bp += ul;
301 if(tl > 0){
302 cmp->cm_tag = bp;
303 memcpy(bp, ctag, ++tl);
304 /*bp += tl;*/
305 }else
306 cmp->cm_tag = ctag;
308 /* Non-buf stuff; default mapping */
309 if(lcmp != NULL){
310 /* Default mappings must be last */
311 if(ctag == NULL){
312 while(lcmp->cm_next != NULL)
313 lcmp = lcmp->cm_next;
314 }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){
315 if((lcmp = blcmp) == NULL)
316 goto jlinkhead;
318 cmp->cm_next = lcmp->cm_next;
319 lcmp->cm_next = cmp;
320 }else{
321 jlinkhead:
322 cmp->cm_next = *cmap;
323 *cmap = cmp;
325 cmp->cm_cmi = cmip;
326 #ifdef HAVE_REGEX
327 cmp->cm_regex = regexp;
328 #endif
329 cmp->cm_refcnt = 0;
330 a_colour_map_ref(cmp);
332 rv = TRU1;
333 jleave:
334 NYD2_LEAVE;
335 return rv;
338 static bool_t
339 a_colour_unmux(char **argv){
340 char const *mapname, *ctag, *xtag;
341 struct a_colour_map **cmap, *lcmp, *cmp;
342 struct a_colour_map_id const *cmip;
343 enum a_colour_type ct;
344 bool_t aster, rv;
345 NYD2_ENTER;
347 rv = TRU1;
348 aster = FAL0;
350 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
351 if(!n_is_all_or_aster(argv[-1])){
352 n_err(_("`uncolour': invalid colour type %s\n"),
353 n_shexp_quote_cp(argv[-1], FAL0));
354 rv = FAL0;
355 goto j_leave;
357 aster = TRU1;
358 ct = 0;
361 mapname = argv[0];
362 ctag = argv[1];
364 if(!a_colour_g.cg_is_init)
365 goto jemap;
367 /* Delete anything? */
368 jredo:
369 if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){
370 size_t i1, i2;
371 struct a_colour_map *tmp;
373 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
374 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2)
375 for(cmp = *(cmap = &a_colour_g.cg_maps[ct][i1][i2]), *cmap = NULL;
376 cmp != NULL;){
377 tmp = cmp;
378 cmp = cmp->cm_next;
379 a_colour_map_unref(tmp);
381 }else{
382 if((cmip = a_colour_map_id_find(mapname)) == NULL){
383 rv = FAL0;
384 jemap:
385 /* I18N: colour command, mapping and precondition (option in quotes) */
386 n_err(_("`uncolour': non-existing mapping: %s%s%s\n"),
387 n_shexp_quote_cp(mapname, FAL0), (ctag == NULL ? n_empty : " "),
388 (ctag == NULL ? n_empty : n_shexp_quote_cp(ctag, FAL0)));
389 goto jleave;
392 if((xtag = ctag) != NULL){
393 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
394 n_err(_("`uncolour': %s does not support preconditions\n"),
395 n_shexp_quote_cp(mapname, FAL0));
396 rv = FAL0;
397 goto jleave;
398 }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) ==
399 n_COLOUR_TAG_ERR){
400 n_err(_("`uncolour': %s: invalid precondition: %s\n"),
401 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
402 rv = FAL0;
403 goto jleave;
405 /* (Improve user experience) */
406 if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag))
407 ctag = xtag;
410 lcmp = NULL;
411 cmp = *(cmap = &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
412 for(;;){
413 char const *xctag;
415 if(cmp == NULL){
416 rv = FAL0;
417 goto jemap;
419 if((xctag = cmp->cm_tag) == ctag)
420 break;
421 if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
422 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
423 !strcmp(xctag, ctag))
424 break;
425 lcmp = cmp;
426 cmp = cmp->cm_next;
429 if(lcmp == NULL)
430 *cmap = cmp->cm_next;
431 else
432 lcmp->cm_next = cmp->cm_next;
433 a_colour_map_unref(cmp);
436 jleave:
437 if(aster && ++ct != a_COLOUR_T_NONE)
438 goto jredo;
439 j_leave:
440 NYD2_LEAVE;
441 return rv;
444 static bool_t
445 a_colour__show(enum a_colour_type ct){
446 struct a_colour_map *cmp;
447 size_t i1, i2;
448 bool_t rv;
449 NYD2_ENTER;
451 /* Show all possible types? */
452 if((rv = (ct == (enum a_colour_type)-1 ? TRU1 : FAL0)))
453 ct = 0;
454 jredo:
455 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
456 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2){
457 if((cmp = a_colour_g.cg_maps[ct][i1][i2]) == NULL)
458 continue;
460 while(cmp != NULL){
461 char const *tagann, *tag;
463 tagann = n_empty;
464 if((tag = cmp->cm_tag) == NULL)
465 tag = n_empty;
466 else if(tag == n_COLOUR_TAG_SUM_DOT)
467 tag = "dot";
468 else if(tag == n_COLOUR_TAG_SUM_OLDER)
469 tag = "older";
470 #ifdef HAVE_REGEX
471 else if(cmp->cm_regex != NULL)
472 tagann = "[rx] ";
473 #endif
474 fprintf(n_stdout, "colour %s %-*s %s %s%s\n",
475 a_colour_types[ct], a_COLOUR_MAP_SHOW_FIELDWIDTH,
476 savecat(a_colour_ctx_prefixes[i1],
477 a_colour_map_ids[i1][i2].cmi_name),
478 (char const*)cmp->cm_buf + cmp->cm_user_off,
479 tagann, n_shexp_quote_cp(tag, TRU1));
480 cmp = cmp->cm_next;
484 if(rv && ++ct != a_COLOUR_T_NONE)
485 goto jredo;
486 rv = TRU1;
487 NYD2_LEAVE;
488 return rv;
491 static char const *
492 a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag,
493 void **regexpp){
494 NYD2_ENTER;
495 n_UNUSED(regexpp);
497 if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !asccasecmp(ctag, "dot"))
498 ctag = n_COLOUR_TAG_SUM_DOT;
499 else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) && !asccasecmp(ctag, "older"))
500 ctag = n_COLOUR_TAG_SUM_OLDER;
501 else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){
502 char *cp, c;
503 size_t i;
505 /* Can this be a valid list of headers? However, with regular expressions
506 * simply use the input as such if it appears to be a regex */
507 #ifdef HAVE_REGEX
508 if(n_is_maybe_regex(ctag)){
509 int s;
511 if(regexpp != NULL &&
512 (s = regcomp(*regexpp = n_alloc(sizeof(regex_t)), ctag,
513 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
514 n_err(_("`colour': invalid regular expression: %s: %s\n"),
515 n_shexp_quote_cp(ctag, FAL0), n_regex_err_to_doc(NULL, s));
516 n_free(*regexpp);
517 goto jetag;
519 }else
520 #endif
522 /* Normalize to lowercase and strip any whitespace before use */
523 i = strlen(ctag);
524 cp = n_autorec_alloc(i +1);
526 for(i = 0; (c = *ctag++) != '\0';){
527 bool_t isblspc = blankspacechar(c);
529 if(!isblspc && !alnumchar(c) && c != '-' && c != ',')
530 goto jetag;
531 /* Since we compare header names as they come from the message this
532 * lowercasing is however redundant: we need to asccasecmp() them */
533 if(!isblspc)
534 cp[i++] = lowerconv(c);
536 cp[i] = '\0';
537 ctag = cp;
539 }else
540 jetag:
541 ctag = n_COLOUR_TAG_ERR;
542 NYD2_LEAVE;
543 return ctag;
546 static struct a_colour_map_id const *
547 a_colour_map_id_find(char const *cp){
548 size_t i;
549 struct a_colour_map_id const (*cmip)[n__COLOUR_IDS], *rv;
550 NYD2_ENTER;
552 rv = NULL;
554 for(i = 0;; ++i){
555 if(i == n__COLOUR_IDS)
556 goto jleave;
557 else{
558 size_t j = strlen(a_colour_ctx_prefixes[i]);
559 if(!ascncasecmp(cp, a_colour_ctx_prefixes[i], j)){
560 cp += j;
561 break;
565 cmip = &a_colour_map_ids[i];
567 for(i = 0;; ++i){
568 if(i == n__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){
569 rv = NULL;
570 break;
572 if(!asccasecmp(cp, rv->cmi_name))
573 break;
575 jleave:
576 NYD2_LEAVE;
577 return rv;
580 static struct a_colour_map *
581 a_colour_map_find(enum n_colour_id cid, enum n_colour_ctx cctx,
582 char const *ctag){
583 struct a_colour_map *cmp;
584 NYD2_ENTER;
586 cmp = a_colour_g.cg_maps[a_colour_g.cg_type][cctx][cid];
587 for(; cmp != NULL; cmp = cmp->cm_next){
588 char const *xtag = cmp->cm_tag;
590 if(xtag == ctag)
591 break;
592 if(xtag == NULL)
593 break;
594 if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag))
595 continue;
596 #ifdef HAVE_REGEX
597 if(cmp->cm_regex != NULL){
598 if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH)
599 break;
600 }else
601 #endif
602 if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){
603 char *hlist = savestr(xtag), *cp;
605 while((cp = n_strsep(&hlist, ',', TRU1)) != NULL){
606 if(!asccasecmp(cp, ctag))
607 break;
609 if(cp != NULL)
610 break;
613 NYD2_LEAVE;
614 return cmp;
617 static void
618 a_colour_map_unref(struct a_colour_map *self){
619 NYD2_ENTER;
620 if(--self->cm_refcnt == 0){
621 #ifdef HAVE_REGEX
622 if(self->cm_regex != NULL){
623 regfree(self->cm_regex);
624 n_free(self->cm_regex);
626 #endif
627 n_free(self);
629 NYD2_LEAVE;
632 static bool_t
633 a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){
634 struct isodesc{
635 char id_name[15];
636 char id_modc;
637 } const fta[] = {
638 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
639 }, ca[] = {
640 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
641 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
642 }, *idp;
643 char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")];
644 ui8_t ftno_base, ftno;
645 bool_t rv;
646 NYD_ENTER;
648 rv = FAL0;
649 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
650 cfg[0] = cfg[1] = 0;
652 /* Since we use autorec_alloc(), reuse the n_strsep() buffer also for the
653 * return value, ensure we have enough room for that */
654 /* C99 */{
655 size_t i = strlen(spec) +1;
656 xspec = n_autorec_alloc(n_MAX(i,
657 sizeof("\033[1;4;7;38;5;255;48;5;255m")));
658 memcpy(xspec, spec, i);
659 spec = xspec;
662 /* Iterate over the colour spec */
663 ftno = 0;
664 while((cp = n_strsep(&xspec, ',', TRU1)) != NULL){
665 char *y, *x = strchr(cp, '=');
666 if(x == NULL){
667 jbail:
668 *store = n_UNCONST(_("invalid attribute list"));
669 goto jleave;
671 *x++ = '\0';
673 if(!asccasecmp(cp, "ft")){
674 if(!asccasecmp(x, "inverse")){
675 n_OBSOLETE(_("please use reverse for ft= fonts, not inverse"));
676 x = n_UNCONST("reverse");
678 for(idp = fta;; ++idp)
679 if(idp == fta + n_NELEM(fta)){
680 *store = n_UNCONST(_("invalid font attribute"));
681 goto jleave;
682 }else if(!asccasecmp(x, idp->id_name)){
683 if(ftno < n_NELEM(fg))
684 fg[ftno++] = idp->id_modc;
685 else{
686 *store = n_UNCONST(_("too many font attributes"));
687 goto jleave;
689 break;
691 }else if(!asccasecmp(cp, "fg")){
692 y = cfg + 0;
693 goto jiter_colour;
694 }else if(!asccasecmp(cp, "bg")){
695 y = cfg + 1;
696 jiter_colour:
697 if(ct == a_COLOUR_T_1){
698 *store = n_UNCONST(_("colours are not allowed"));
699 goto jleave;
701 /* Maybe 256 color spec */
702 if(digitchar(x[0])){
703 ui8_t xv;
705 if(ct == a_COLOUR_T_8){
706 *store = n_UNCONST(_("invalid colour for 8-colour mode"));
707 goto jleave;
710 if((n_idec_ui8_cp(&xv, x, 10, NULL
711 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
712 ) != n_IDEC_STATE_CONSUMED){
713 *store = n_UNCONST(_("invalid 256-colour specification"));
714 goto jleave;
716 y[0] = 5;
717 memcpy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x,
718 (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4)));
719 }else for(idp = ca;; ++idp)
720 if(idp == ca + n_NELEM(ca)){
721 *store = n_UNCONST(_("invalid colour attribute"));
722 goto jleave;
723 }else if(!asccasecmp(x, idp->id_name)){
724 y[0] = 1;
725 y[2] = idp->id_modc;
726 break;
728 }else
729 goto jbail;
732 /* Restore our autorec_alloc() buffer, create return value */
733 xspec = n_UNCONST(spec);
734 if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */
735 xspec[0] = '\033';
736 xspec[1] = '[';
737 xspec += 2;
739 for(ftno_base = ftno; ftno > 0;){
740 if(ftno-- != ftno_base)
741 *xspec++ = ';';
742 *xspec++ = fg[ftno];
745 if(cfg[0]){
746 if(ftno_base > 0)
747 *xspec++ = ';';
748 xspec[0] = '3';
749 if(cfg[0] == 1){
750 xspec[1] = cfg[2];
751 xspec += 2;
752 }else{
753 memcpy(xspec + 1, "8;5;", 4);
754 xspec += 5;
755 for(ftno = 2; cfg[ftno] != '\0'; ++ftno)
756 *xspec++ = cfg[ftno];
760 if(cfg[1]){
761 if(ftno_base > 0 || cfg[0])
762 *xspec++ = ';';
763 xspec[0] = '4';
764 if(cfg[1] == 1){
765 xspec[1] = cfg[3];
766 xspec += 2;
767 }else{
768 memcpy(xspec + 1, "8;5;", 4);
769 xspec += 5;
770 for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno)
771 *xspec++ = cfg[ftno];
775 *xspec++ = 'm';
777 *xspec = '\0';
778 *store = n_UNCONST(spec);
779 rv = TRU1;
780 jleave:
781 NYD_LEAVE;
782 return rv;
785 FL int
786 c_colour(void *v){
787 int rv;
788 NYD_ENTER;
790 rv = !a_colour_mux(v);
791 NYD_LEAVE;
792 return rv;
795 FL int
796 c_uncolour(void *v){
797 int rv;
798 NYD_ENTER;
800 rv = !a_colour_unmux(v);
801 NYD_LEAVE;
802 return rv;
805 FL void
806 n_colour_stack_del(struct n_go_data_ctx *gdcp){
807 struct n_colour_env *vp, *cep;
808 NYD_ENTER;
810 vp = gdcp->gdc_colour;
811 gdcp->gdc_colour = NULL;
812 gdcp->gdc_colour_active = FAL0;
814 while((cep = vp) != NULL){
815 vp = cep->ce_last;
817 if(cep->ce_current != NULL && cep->ce_outfp == n_stdout){
818 n_sighdl_t hdl;
820 hdl = n_signal(SIGPIPE, SIG_IGN);
821 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
822 cep->ce_outfp);
823 fflush(cep->ce_outfp);
824 n_signal(SIGPIPE, hdl);
827 NYD_LEAVE;
830 FL void
831 n_colour_env_create(enum n_colour_ctx cctx, FILE *fp, bool_t pager_used){
832 struct n_colour_env *cep;
833 NYD_ENTER;
835 if(!(n_psonce & n_PSO_INTERACTIVE))
836 goto jleave;
838 if(!a_colour_g.cg_is_init)
839 a_colour_init();
841 /* TODO reset the outer level? Iff ce_outfp==fp? */
842 cep = n_autorec_alloc(sizeof *cep);
843 cep->ce_last = n_go_data->gdc_colour;
844 cep->ce_enabled = FAL0;
845 cep->ce_ctx = cctx;
846 cep->ce_ispipe = pager_used;
847 cep->ce_outfp = fp;
848 cep->ce_current = NULL;
849 n_go_data->gdc_colour_active = FAL0;
850 n_go_data->gdc_colour = cep;
852 if(ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager)))
853 goto jleave;
855 if(n_UNLIKELY(a_colour_g.cg_type == a_COLOUR_T_UNKNOWN)){
856 struct n_termcap_value tv;
858 if(!n_termcap_query(n_TERMCAP_QUERY_colors, &tv)){
859 a_colour_g.cg_type = a_COLOUR_T_NONE;
860 goto jleave;
861 }else
862 switch(tv.tv_data.tvd_numeric){
863 case 256: a_colour_g.cg_type = a_COLOUR_T_256; break;
864 case 8: a_colour_g.cg_type = a_COLOUR_T_8; break;
865 case 1: a_colour_g.cg_type = a_COLOUR_T_1; break;
866 default:
867 if(n_poption & n_PO_D_V)
868 n_err(_("Ignoring unsupported termcap entry for Co(lors)\n"));
869 /* FALLTHRU */
870 case 0:
871 a_colour_g.cg_type = a_COLOUR_T_NONE;
872 goto jleave;
876 if(a_colour_g.cg_type == a_COLOUR_T_NONE)
877 goto jleave;
879 n_go_data->gdc_colour_active = cep->ce_enabled = TRU1;
880 jleave:
881 NYD_LEAVE;
884 FL void
885 n_colour_env_gut(void){
886 struct n_colour_env *cep;
887 NYD_ENTER;
889 if(!(n_psonce & n_PSO_INTERACTIVE))
890 goto jleave;
892 /* TODO v15: Could happen because of jump, causing _stack_del().. */
893 if((cep = n_go_data->gdc_colour) == NULL)
894 goto jleave;
895 n_go_data->gdc_colour_active = ((n_go_data->gdc_colour = cep->ce_last
896 ) != NULL && cep->ce_last->ce_enabled);
898 if(cep->ce_current != NULL){
899 n_sighdl_t hdl;
901 hdl = n_signal(SIGPIPE, SIG_IGN);
902 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
903 cep->ce_outfp);
904 n_signal(SIGPIPE, hdl);
906 jleave:
907 NYD_LEAVE;
910 FL void
911 n_colour_put(enum n_colour_id cid, char const *ctag){
912 NYD_ENTER;
913 if(n_COLOUR_IS_ACTIVE()){
914 struct n_colour_env *cep;
916 cep = n_go_data->gdc_colour;
918 if(cep->ce_current != NULL)
919 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
920 cep->ce_outfp);
922 if((cep->ce_current = a_colour_map_find(cid, cep->ce_ctx, ctag)) != NULL)
923 fwrite(cep->ce_current->cm_pen.cp_dat.s,
924 cep->ce_current->cm_pen.cp_dat.l, 1, cep->ce_outfp);
926 NYD_LEAVE;
929 FL void
930 n_colour_reset(void){
931 NYD_ENTER;
932 if(n_COLOUR_IS_ACTIVE()){
933 struct n_colour_env *cep;
935 cep = n_go_data->gdc_colour;
937 if(cep->ce_current != NULL){
938 cep->ce_current = NULL;
939 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1,
940 cep->ce_outfp);
943 NYD_LEAVE;
946 FL struct str const *
947 n_colour_reset_to_str(void){
948 struct str *rv;
949 NYD_ENTER;
951 if(n_COLOUR_IS_ACTIVE())
952 rv = &a_colour_g.cg_reset.cp_dat;
953 else
954 rv = NULL;
955 NYD_LEAVE;
956 return rv;
959 FL struct n_colour_pen *
960 n_colour_pen_create(enum n_colour_id cid, char const *ctag){
961 struct a_colour_map *cmp;
962 struct n_colour_pen *rv;
963 NYD_ENTER;
965 if(n_COLOUR_IS_ACTIVE() &&
966 (cmp = a_colour_map_find(cid, n_go_data->gdc_colour->ce_ctx, ctag)
967 ) != NULL){
968 union {void *vp; char *cp; struct n_colour_pen *cpp;} u;
970 u.vp = cmp;
971 rv = u.cpp;
972 }else
973 rv = NULL;
974 NYD_LEAVE;
975 return rv;
978 FL void
979 n_colour_pen_put(struct n_colour_pen *self){
980 NYD_ENTER;
981 if(n_COLOUR_IS_ACTIVE()){
982 union {void *vp; char *cp; struct a_colour_map *cmp;} u;
983 struct n_colour_env *cep;
985 cep = n_go_data->gdc_colour;
986 u.vp = self;
988 if(u.cmp != cep->ce_current){
989 if(cep->ce_current != NULL)
990 fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l,
991 1, cep->ce_outfp);
993 if(u.cmp != NULL)
994 fwrite(self->cp_dat.s, self->cp_dat.l, 1, cep->ce_outfp);
995 cep->ce_current = u.cmp;
998 NYD_LEAVE;
1001 FL struct str const *
1002 n_colour_pen_to_str(struct n_colour_pen *self){
1003 struct str *rv;
1004 NYD_ENTER;
1006 if(n_COLOUR_IS_ACTIVE() && self != NULL)
1007 rv = &self->cp_dat;
1008 else
1009 rv = NULL;
1010 NYD_LEAVE;
1011 return rv;
1013 #endif /* HAVE_COLOUR */
1015 /* s-it-mode */