a_main_setscreensize(): no need to test n_PSO_INTERACTIVE
[s-mailx.git] / colour.c
blob455a16d4d22ce9f99d5c3893cc3b07263d38909c
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?colour' commands, and anything working with it.
4 * Copyright (c) 2014 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #undef n_FILE
19 #define n_FILE colour
21 #ifndef HAVE_AMALGAMATION
22 # include "nail.h"
23 #endif
25 EMPTY_FILE()
26 #ifdef HAVE_COLOUR
28 /* Not needed publically, but extends a set from nail.h */
29 #define n_COLOUR_TAG_ERR ((char*)-1)
30 #define a_COLOUR_TAG_IS_SPECIAL(P) (PTR2SIZE(P) >= PTR2SIZE(-3))
32 enum a_colour_type{
33 a_COLOUR_T_256,
34 a_COLOUR_T_8,
35 a_COLOUR_T_1,
36 a_COLOUR_T_NONE, /* EQ largest real colour + 1! */
37 a_COLOUR_T_UNKNOWN /* Initial value: real one queried before 1st use */
40 enum a_colour_tag_type{
41 a_COLOUR_TT_NONE,
42 a_COLOUR_TT_DOT = 1<<0, /* "dot" */
43 a_COLOUR_TT_OLDER = 1<<1, /* "older" */
44 a_COLOUR_TT_HEADERS = 1<<2, /* Comma-separated list of headers allowed */
46 a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER,
47 a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS
50 struct a_colour_type_map{
51 ui8_t ctm_type; /* a_colour_type */
52 char ctm_name[7];
55 struct a_colour_map_id{
56 ui8_t cmi_ctx; /* enum n_colour_ctx */
57 ui8_t cmi_id; /* enum n_colour_id */
58 ui8_t cmi_tt; /* enum a_colour_tag_type */
59 char const cmi_name[13];
61 n_CTA(n__COLOUR_IDS <= UI8_MAX, "Enumeration exceeds storage datatype");
63 struct n_colour_pen{
64 struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */
67 struct a_colour_map /* : public n_colour_pen */{
68 struct n_colour_pen cm_pen; /* Points into .cm_buf */
69 struct a_colour_map *cm_next;
70 char const *cm_tag; /* Colour tag or NULL for default (last) */
71 struct a_colour_map_id const *cm_cmi;
72 #ifdef HAVE_REGEX
73 regex_t *cm_regex;
74 #endif
75 ui32_t cm_refcnt; /* Beware of reference drops in recursions */
76 ui32_t cm_user_off; /* User input offset in .cm_buf */
77 char cm_buf[n_VFIELD_SIZE(0)];
80 struct a_colour_g{
81 ui8_t cg_type; /* a_colour_type */
82 /* TODO cg_has_env not used, we have to go for n_PS_COLOUR_ACTIVE */
83 bool_t cg_has_env;
84 ui8_t cg_ctx; /* If .cg_has_env, enum n_colour_ctx */
85 ui8_t __cg_pad[5];
86 struct a_colour_map *cg_active; /* The currently active colour */
87 struct n_colour_pen cg_reset; /* The reset sequence */
88 struct a_colour_map
89 *cg_maps[a_COLOUR_T_NONE][n__COLOUR_CTX_MAX1][n__COLOUR_IDS];
90 char cg_reset_buf[n_ALIGN_SMALL(sizeof("\033[0m"))];
93 /* TODO The colour environment simply should be a pointer into an
94 * TODO carrier structure in equal spirit to the fio.c stack, which gets
95 * TODO created for each execute() cycle (long in TODO), and carries along
96 * TODO all the information, memory allocations and also output (filter)
97 * TODO chains, so that we could actually decide whether we could simply
98 * TODO suspend output for a chain, need to place reset sequences, etc.
99 * TODO For now, since we have no such carrier to know where colour
100 * TODO sequences have to be written, creating a colour environment requires
101 * TODO that the current colour state is "reset", because we wouldn't know
102 * TODO where to place reset sequences and ditto, reestablish colour.
103 * TODO This should be no problem in practice, however */
104 struct a_colour_env{
105 struct a_colour_env *ce_last;
106 ui8_t ce_ctx; /* enum n_colour_ctx active upon switch */
107 bool_t ce_is_active; /* Was colour active in outer level? */
108 ui8_t __ce_pad[6];
111 /* C99: use [INDEX]={} */
112 /* */
113 n_CTA(a_COLOUR_T_256 == 0, "Unexpected value of constant");
114 n_CTA(a_COLOUR_T_8 == 1, "Unexpected value of constant");
115 n_CTA(a_COLOUR_T_1 == 2, "Unexpected value of constant");
116 static char const a_colour_types[][8] = {"256", "iso", "mono"};
118 static struct a_colour_type_map const a_colour_type_maps[] = {
119 {a_COLOUR_T_256, "256"},
120 {a_COLOUR_T_8, "8"}, {a_COLOUR_T_8, "iso"}, {a_COLOUR_T_8, "ansi"},
121 {a_COLOUR_T_1, "1"}, {a_COLOUR_T_1, "mono"}
124 n_CTA(n_COLOUR_CTX_SUM == 0, "Unexpected value of constant");
125 n_CTA(n_COLOUR_CTX_VIEW == 1, "Unexpected value of constant");
126 n_CTA(n_COLOUR_CTX_MLE == 2, "Unexpected value of constant");
127 static char const a_colour_ctx_prefixes[n__COLOUR_CTX_MAX1][8] = {
128 "sum-", "view-", "mle-"
131 static struct a_colour_map_id const
132 a_colour_map_ids[n__COLOUR_CTX_MAX1][n__COLOUR_IDS] = {{
133 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"},
134 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"},
135 {n_COLOUR_CTX_SUM, n_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"},
136 }, {
137 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"},
138 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"},
139 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE, "msginfo"},
140 {n_COLOUR_CTX_VIEW, n_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE, "partinfo"},
141 }, {
142 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_POSITION, a_COLOUR_TT_NONE, "position"},
143 {n_COLOUR_CTX_MLE, n_COLOUR_ID_MLE_PROMPT, a_COLOUR_TT_NONE, "prompt"},
145 #define a_COLOUR_MAP_SHOW_FIELDWIDTH \
146 (int)(sizeof("view-")-1 + sizeof("partinfo")-1)
148 static struct a_colour_g *a_colour_g;
149 static struct a_colour_env *a_colour_env;
151 static void a_colour_init(void);
152 DBG( static void a_colour_atexit(void); )
154 /* Find the type or -1 */
155 static enum a_colour_type a_colour_type_find(char const *name);
157 /* `(un)?colour' implementations */
158 static bool_t a_colour_mux(char **argv);
159 static bool_t a_colour_unmux(char **argv);
161 static bool_t a_colour__show(enum a_colour_type ct);
162 /* (regexpp may be NULL) */
163 static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip,
164 char const *ctag, void **regexpp);
166 /* Try to find a mapping identity for user given slotname */
167 static struct a_colour_map_id const *a_colour_map_id_find(char const *slotname);
169 /* Find an existing mapping for the given combination */
170 static struct a_colour_map *a_colour_map_find(enum n_colour_id cid,
171 char const *ctag);
173 /* In-/Decrement reference counter, destroy if counts gets zero */
174 #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0)
175 static void a_colour_map_unref(struct a_colour_map *self);
177 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user
178 * input spec, store it or on error message in *store */
179 static bool_t a_colour_iso6429(enum a_colour_type ct, char **store,
180 char const *spec);
182 static void
183 a_colour_init(void){
184 NYD2_ENTER;
186 a_colour_g = scalloc(1, sizeof *a_colour_g);
188 memcpy(a_colour_g->cg_reset.cp_dat.s = a_colour_g->cg_reset_buf, "\033[0m",
189 a_colour_g->cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */
190 a_colour_g->cg_type = a_COLOUR_T_UNKNOWN;
191 DBG( atexit(&a_colour_atexit); ) /* TODO prog-global atexit event */
192 NYD2_LEAVE;
195 #ifdef HAVE_DEBUG
196 static void
197 a_colour_atexit(void){
198 NYD_ENTER;
199 if(a_colour_env != NULL)
200 n_colour_env_pop(TRU1);
201 free(a_colour_g);
202 a_colour_g = NULL;
203 NYD_LEAVE;
205 #endif
207 static enum a_colour_type
208 a_colour_type_find(char const *name){
209 struct a_colour_type_map const *ctmp;
210 enum a_colour_type rv;
211 NYD2_ENTER;
213 ctmp = a_colour_type_maps;
214 do if(!asccasecmp(ctmp->ctm_name, name)){
215 rv = ctmp->ctm_type;
216 goto jleave;
217 }while(PTRCMP(++ctmp, !=, a_colour_type_maps + n_NELEM(a_colour_type_maps)));
219 rv = (enum a_colour_type)-1;
220 jleave:
221 NYD2_LEAVE;
222 return rv;
225 static bool_t
226 a_colour_mux(char **argv){
227 void *regexp;
228 char const *mapname, *ctag;
229 struct a_colour_map **cmap, *blcmp, *lcmp, *cmp;
230 struct a_colour_map_id const *cmip;
231 bool_t rv;
232 enum a_colour_type ct;
233 NYD2_ENTER;
235 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1 &&
236 (*argv != NULL || !n_is_all_or_aster(argv[-1]))){
237 n_err(_("`colour': invalid colour type %s\n"),
238 n_shexp_quote_cp(argv[-1], FAL0));
239 rv = FAL0;
240 goto jleave;
243 if(a_colour_g == NULL)
244 a_colour_init();
246 if(*argv == NULL){
247 rv = a_colour__show(ct);
248 goto jleave;
251 rv = FAL0;
252 regexp = NULL;
254 if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){
255 n_err(_("`colour': non-existing mapping: %s\n"),
256 n_shexp_quote_cp(mapname, FAL0));
257 goto jleave;
260 if(argv[1] == NULL){
261 n_err(_("`colour': %s: missing attribute argument\n"),
262 n_shexp_quote_cp(mapname, FAL0));
263 goto jleave;
266 /* Check whether preconditions are at all allowed, verify them as far as
267 * possible as necessary. For shell_quote() simplicity let's just ignore an
268 * empty precondition */
269 if((ctag = argv[2]) != NULL && *ctag != '\0'){
270 char const *xtag;
272 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
273 n_err(_("`colour': %s doesn't support preconditions\n"),
274 n_shexp_quote_cp(mapname, FAL0));
275 goto jleave;
276 }else if((xtag = a_colour__tag_identify(cmip, ctag, &regexp)) ==
277 n_COLOUR_TAG_ERR){
278 /* I18N: ..of colour mapping */
279 n_err(_("`colour': %s: invalid precondition: %s\n"),
280 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
281 goto jleave;
283 ctag = xtag;
286 /* At this time we have all the information to be able to query whether such
287 * a mapping is yet established. If so, destroy it */
288 for(blcmp = lcmp = NULL,
289 cmp = *(cmap =
290 &a_colour_g->cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
291 cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){
292 char const *xctag = cmp->cm_tag;
294 if(xctag == ctag ||
295 (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
296 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
297 !strcmp(xctag, ctag))){
298 if(lcmp == NULL)
299 *cmap = cmp->cm_next;
300 else
301 lcmp->cm_next = cmp->cm_next;
302 a_colour_map_unref(cmp);
303 break;
307 /* Create mapping */
308 /* C99 */{
309 size_t tl, ul, cl;
310 char *bp, *cp;
312 if(!a_colour_iso6429(ct, &cp, argv[1])){
313 /* I18N: colour command: mapping: error message: user argument */
314 n_err(_("`colour': %s: %s: %s\n"), n_shexp_quote_cp(mapname, FAL0),
315 cp, n_shexp_quote_cp(argv[1], FAL0));
316 goto jleave;
319 tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag)) ? strlen(ctag) : 0;
320 cmp = smalloc(n_VSTRUCT_SIZEOF(struct a_colour_map, cm_buf) +
321 tl +1 + (ul = strlen(argv[1])) +1 + (cl = strlen(cp)) +1);
323 /* .cm_buf stuff */
324 cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf;
325 cmp->cm_pen.cp_dat.l = cl;
326 memcpy(bp, cp, ++cl);
327 bp += cl;
329 cmp->cm_user_off = (ui32_t)PTR2SIZE(bp - cmp->cm_buf);
330 memcpy(bp, argv[1], ++ul);
331 bp += ul;
333 if(tl > 0){
334 cmp->cm_tag = bp;
335 memcpy(bp, ctag, ++tl);
336 /*bp += tl;*/
337 }else
338 cmp->cm_tag = ctag;
340 /* Non-buf stuff; default mapping */
341 if(lcmp != NULL){
342 /* Default mappings must be last */
343 if(ctag == NULL){
344 while(lcmp->cm_next != NULL)
345 lcmp = lcmp->cm_next;
346 }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){
347 if((lcmp = blcmp) == NULL)
348 goto jlinkhead;
350 cmp->cm_next = lcmp->cm_next;
351 lcmp->cm_next = cmp;
352 }else{
353 jlinkhead:
354 cmp->cm_next = *cmap;
355 *cmap = cmp;
357 cmp->cm_cmi = cmip;
358 #ifdef HAVE_REGEX
359 cmp->cm_regex = regexp;
360 #endif
361 cmp->cm_refcnt = 0;
362 a_colour_map_ref(cmp);
364 rv = TRU1;
365 jleave:
366 NYD2_LEAVE;
367 return rv;
370 static bool_t
371 a_colour_unmux(char **argv){
372 char const *mapname, *ctag, *xtag;
373 struct a_colour_map **cmap, *lcmp, *cmp;
374 struct a_colour_map_id const *cmip;
375 enum a_colour_type ct;
376 bool_t aster, rv;
377 NYD2_ENTER;
379 rv = TRU1;
380 aster = FAL0;
382 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
383 if(!n_is_all_or_aster(argv[-1])){
384 n_err(_("`uncolour': invalid colour type %s\n"),
385 n_shexp_quote_cp(argv[-1], FAL0));
386 rv = FAL0;
387 goto j_leave;
389 aster = TRU1;
390 ct = 0;
393 mapname = argv[0];
394 ctag = (mapname != NULL) ? argv[1] : mapname;
396 if(a_colour_g == NULL)
397 goto jemap;
399 /* Delete anything? */
400 jredo:
401 if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){
402 size_t i1, i2;
403 struct a_colour_map *tmp;
405 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
406 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2)
407 for(cmp = *(cmap = &a_colour_g->cg_maps[ct][i1][i2]), *cmap = NULL;
408 cmp != NULL;){
409 tmp = cmp;
410 cmp = cmp->cm_next;
411 a_colour_map_unref(tmp);
413 }else{
414 if(a_colour_g == NULL){
415 rv = FAL0;
416 jemap:
417 /* I18N: colour command, mapping and precondition (option in quotes) */
418 n_err(_("`uncolour': non-existing mapping: %s%s%s\n"),
419 n_shexp_quote_cp(mapname, FAL0), (ctag == NULL ? n_empty : " "),
420 (ctag == NULL ? n_empty : n_shexp_quote_cp(ctag, FAL0)));
421 goto jleave;
424 if((cmip = a_colour_map_id_find(mapname)) == NULL){
425 rv = FAL0;
426 goto jemap;
429 if((xtag = ctag) != NULL){
430 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
431 n_err(_("`uncolour': %s doesn't support preconditions\n"),
432 n_shexp_quote_cp(mapname, FAL0));
433 rv = FAL0;
434 goto jleave;
435 }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) ==
436 n_COLOUR_TAG_ERR){
437 n_err(_("`uncolour': %s: invalid precondition: %s\n"),
438 n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0));
439 rv = FAL0;
440 goto jleave;
442 /* (Improve user experience) */
443 if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag))
444 ctag = xtag;
447 lcmp = NULL;
448 cmp = *(cmap = &a_colour_g->cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]);
449 for(;;){
450 char const *xctag;
452 if(cmp == NULL){
453 rv = FAL0;
454 goto jemap;
456 if((xctag = cmp->cm_tag) == ctag)
457 break;
458 if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
459 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
460 !strcmp(xctag, ctag))
461 break;
462 lcmp = cmp;
463 cmp = cmp->cm_next;
466 if(lcmp == NULL)
467 *cmap = cmp->cm_next;
468 else
469 lcmp->cm_next = cmp->cm_next;
470 a_colour_map_unref(cmp);
473 jleave:
474 if(aster && ++ct != a_COLOUR_T_NONE)
475 goto jredo;
476 j_leave:
477 NYD2_LEAVE;
478 return rv;
481 static bool_t
482 a_colour__show(enum a_colour_type ct){
483 struct a_colour_map *cmp;
484 size_t i1, i2;
485 bool_t rv;
486 NYD2_ENTER;
488 /* Show all possible types? */
489 if((rv = (ct == (enum a_colour_type)-1 ? TRU1 : FAL0)))
490 ct = 0;
491 jredo:
492 for(i1 = 0; i1 < n__COLOUR_CTX_MAX1; ++i1)
493 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2){
494 if((cmp = a_colour_g->cg_maps[ct][i1][i2]) == NULL)
495 continue;
497 while(cmp != NULL){
498 char const *tagann, *tag;
500 tagann = n_empty;
501 if((tag = cmp->cm_tag) == NULL)
502 tag = n_empty;
503 else if(tag == n_COLOUR_TAG_SUM_DOT)
504 tag = "dot";
505 else if(tag == n_COLOUR_TAG_SUM_OLDER)
506 tag = "older";
507 #ifdef HAVE_REGEX
508 else if(cmp->cm_regex != NULL)
509 tagann = "[rx] ";
510 #endif
511 fprintf(n_stdout, "colour %s %-*s %s %s%s\n",
512 a_colour_types[ct], a_COLOUR_MAP_SHOW_FIELDWIDTH,
513 savecat(a_colour_ctx_prefixes[i1],
514 a_colour_map_ids[i1][i2].cmi_name),
515 (char const*)cmp->cm_buf + cmp->cm_user_off,
516 tagann, n_shexp_quote_cp(tag, TRU1));
517 cmp = cmp->cm_next;
521 if(rv && ++ct != a_COLOUR_T_NONE)
522 goto jredo;
523 rv = TRU1;
524 NYD2_LEAVE;
525 return rv;
528 static char const *
529 a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag,
530 void **regexpp){
531 NYD2_ENTER;
532 n_UNUSED(regexpp);
534 if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !asccasecmp(ctag, "dot"))
535 ctag = n_COLOUR_TAG_SUM_DOT;
536 else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) && !asccasecmp(ctag, "older"))
537 ctag = n_COLOUR_TAG_SUM_OLDER;
538 else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){
539 char *cp, c;
540 size_t i;
542 /* Can this be a valid list of headers? However, with regular expressions
543 * simply use the input as such if it appears to be a regex */
544 #ifdef HAVE_REGEX
545 if(n_is_maybe_regex(ctag)){
546 int s;
548 if(regexpp != NULL &&
549 (s = regcomp(*regexpp = smalloc(sizeof(regex_t)), ctag,
550 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
551 n_err(_("`colour': invalid regular expression: %s: %s\n"),
552 n_shexp_quote_cp(ctag, FAL0), n_regex_err_to_str(*regexpp, s));
553 free(*regexpp);
554 goto jetag;
556 }else
557 #endif
559 /* Normalize to lowercase and strip any whitespace before use */
560 i = strlen(ctag);
561 cp = salloc(i +1);
563 for(i = 0; (c = *ctag++) != '\0';){
564 bool_t isblspc = blankspacechar(c);
566 if(!isblspc && !alnumchar(c) && c != '-' && c != ',')
567 goto jetag;
568 /* Since we compare header names as they come from the message this
569 * lowercasing is however redundant: we need to asccasecmp() them */
570 if(!isblspc)
571 cp[i++] = lowerconv(c);
573 cp[i] = '\0';
574 ctag = cp;
576 }else
577 jetag:
578 ctag = n_COLOUR_TAG_ERR;
579 NYD2_LEAVE;
580 return ctag;
583 static struct a_colour_map_id const *
584 a_colour_map_id_find(char const *cp){
585 size_t i;
586 struct a_colour_map_id const (*cmip)[n__COLOUR_IDS], *rv;
587 NYD2_ENTER;
589 rv = NULL;
591 for(i = 0;; ++i){
592 if(i == n__COLOUR_IDS)
593 goto jleave;
594 else{
595 size_t j = strlen(a_colour_ctx_prefixes[i]);
596 if(!ascncasecmp(cp, a_colour_ctx_prefixes[i], j)){
597 cp += j;
598 break;
602 cmip = &a_colour_map_ids[i];
604 for(i = 0;; ++i){
605 if(i == n__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){
606 rv = NULL;
607 break;
609 if(!asccasecmp(cp, rv->cmi_name))
610 break;
612 jleave:
613 NYD2_LEAVE;
614 return rv;
617 static struct a_colour_map *
618 a_colour_map_find(enum n_colour_id cid, char const *ctag){
619 struct a_colour_map *cmp;
620 NYD2_ENTER;
622 cmp = a_colour_g->cg_maps[a_colour_g->cg_type][a_colour_g->cg_ctx][cid];
623 for(; cmp != NULL; cmp = cmp->cm_next){
624 char const *xtag = cmp->cm_tag;
626 if(xtag == ctag)
627 break;
628 if(xtag == NULL)
629 break;
630 if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag))
631 continue;
632 #ifdef HAVE_REGEX
633 if(cmp->cm_regex != NULL){
634 if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH)
635 break;
636 }else
637 #endif
638 if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){
639 char *hlist = savestr(xtag), *cp;
641 while((cp = n_strsep(&hlist, ',', TRU1)) != NULL){
642 if(!asccasecmp(cp, ctag))
643 break;
645 if(cp != NULL)
646 break;
649 NYD2_LEAVE;
650 return cmp;
653 static void
654 a_colour_map_unref(struct a_colour_map *self){
655 NYD2_ENTER;
656 if(--self->cm_refcnt == 0){
657 #ifdef HAVE_REGEX
658 if(self->cm_regex != NULL){
659 regfree(self->cm_regex);
660 free(self->cm_regex);
662 #endif
663 free(self);
665 NYD2_LEAVE;
668 static bool_t
669 a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){
670 struct isodesc{
671 char id_name[15];
672 char id_modc;
673 } const fta[] = {
674 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
675 }, ca[] = {
676 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
677 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
678 }, *idp;
679 char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")];
680 ui8_t ftno_base, ftno;
681 bool_t rv;
682 NYD_ENTER;
684 rv = FAL0;
685 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
686 cfg[0] = cfg[1] = 0;
688 /* Since we use salloc(), reuse the n_strsep() buffer also for the return
689 * value, ensure we have enough room for that */
690 /* C99 */{
691 size_t i = strlen(spec) +1;
692 xspec = salloc(n_MAX(i, sizeof("\033[1;4;7;38;5;255;48;5;255m")));
693 memcpy(xspec, spec, i);
694 spec = xspec;
697 /* Iterate over the colour spec */
698 ftno = 0;
699 while((cp = n_strsep(&xspec, ',', TRU1)) != NULL){
700 char *y, *x = strchr(cp, '=');
701 if(x == NULL){
702 jbail:
703 *store = n_UNCONST(_("invalid attribute list"));
704 goto jleave;
706 *x++ = '\0';
708 if(!asccasecmp(cp, "ft")){
709 if(!asccasecmp(x, "inverse")){
710 n_OBSOLETE(_("please use reverse for ft= fonts, not inverse"));
711 x = n_UNCONST("reverse");
713 for(idp = fta;; ++idp)
714 if(idp == fta + n_NELEM(fta)){
715 *store = n_UNCONST(_("invalid font attribute"));
716 goto jleave;
717 }else if(!asccasecmp(x, idp->id_name)){
718 if(ftno < n_NELEM(fg))
719 fg[ftno++] = idp->id_modc;
720 else{
721 *store = n_UNCONST(_("too many font attributes"));
722 goto jleave;
724 break;
726 }else if(!asccasecmp(cp, "fg")){
727 y = cfg + 0;
728 goto jiter_colour;
729 }else if(!asccasecmp(cp, "bg")){
730 y = cfg + 1;
731 jiter_colour:
732 if(ct == a_COLOUR_T_1){
733 *store = n_UNCONST(_("colours are not allowed"));
734 goto jleave;
736 /* Maybe 256 color spec */
737 if(digitchar(x[0])){
738 ui8_t xv;
740 if(ct == a_COLOUR_T_8){
741 *store = n_UNCONST(_("invalid colour for 8-colour mode"));
742 goto jleave;
745 if((n_idec_ui8_cp(&xv, x, 10, NULL
746 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
747 ) != n_IDEC_STATE_CONSUMED){
748 *store = n_UNCONST(_("invalid 256-colour specification"));
749 goto jleave;
751 y[0] = 5;
752 memcpy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x,
753 (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4)));
754 }else for(idp = ca;; ++idp)
755 if(idp == ca + n_NELEM(ca)){
756 *store = n_UNCONST(_("invalid colour attribute"));
757 goto jleave;
758 }else if(!asccasecmp(x, idp->id_name)){
759 y[0] = 1;
760 y[2] = idp->id_modc;
761 break;
763 }else
764 goto jbail;
767 /* Restore our salloc() buffer, create return value */
768 xspec = n_UNCONST(spec);
769 if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */
770 xspec[0] = '\033';
771 xspec[1] = '[';
772 xspec += 2;
774 for(ftno_base = ftno; ftno > 0;){
775 if(ftno-- != ftno_base)
776 *xspec++ = ';';
777 *xspec++ = fg[ftno];
780 if(cfg[0]){
781 if(ftno_base > 0)
782 *xspec++ = ';';
783 xspec[0] = '3';
784 if(cfg[0] == 1){
785 xspec[1] = cfg[2];
786 xspec += 2;
787 }else{
788 memcpy(xspec + 1, "8;5;", 4);
789 xspec += 5;
790 for(ftno = 2; cfg[ftno] != '\0'; ++ftno)
791 *xspec++ = cfg[ftno];
795 if(cfg[1]){
796 if(ftno_base > 0 || cfg[0])
797 *xspec++ = ';';
798 xspec[0] = '4';
799 if(cfg[1] == 1){
800 xspec[1] = cfg[3];
801 xspec += 2;
802 }else{
803 memcpy(xspec + 1, "8;5;", 4);
804 xspec += 5;
805 for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno)
806 *xspec++ = cfg[ftno];
810 *xspec++ = 'm';
812 *xspec = '\0';
813 *store = n_UNCONST(spec);
814 rv = TRU1;
815 jleave:
816 NYD_LEAVE;
817 return rv;
820 FL int
821 c_colour(void *v){
822 int rv;
823 NYD_ENTER;
825 rv = !a_colour_mux(v);
826 NYD_LEAVE;
827 return rv;
830 FL int
831 c_uncolour(void *v){
832 int rv;
833 NYD_ENTER;
835 rv = !a_colour_unmux(v);
836 NYD_LEAVE;
837 return rv;
840 FL void
841 n_colour_env_create(enum n_colour_ctx cctx, bool_t pager_used){
842 NYD_ENTER;
843 if(!(n_psonce & n_PSO_INTERACTIVE))
844 goto jleave;
846 if (ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager))){
847 n_colour_env_push(); /* FIXME lex.c only pops (always env */
848 goto jleave;
851 if(n_UNLIKELY(a_colour_g == NULL))
852 a_colour_init();
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 a_colour_g->cg_ctx = cctx;
879 a_colour_g->cg_active = NULL;
880 n_pstate |= n_PS_COLOUR_ACTIVE;
881 jleave:
882 NYD_LEAVE;
885 FL void
886 n_colour_env_push(void){
887 struct a_colour_env *cep;
888 NYD_ENTER;
890 if(!(n_psonce & n_PSO_INTERACTIVE))
891 goto jleave;
893 cep = smalloc(sizeof *cep);
894 cep->ce_last = a_colour_env;
895 if(a_colour_g != NULL){
896 cep->ce_ctx = a_colour_g->cg_ctx;
897 a_colour_g->cg_active = NULL;
899 cep->ce_is_active = ((n_pstate & n_PS_COLOUR_ACTIVE) != 0);
900 a_colour_env = cep;
902 n_pstate &= ~n_PS_COLOUR_ACTIVE;
903 jleave:
904 NYD_LEAVE;
907 FL void
908 n_colour_env_pop(bool_t any_env_till_root){
909 NYD_ENTER;
910 if(!(n_psonce & n_PSO_INTERACTIVE))
911 goto jleave;
913 while(a_colour_env != NULL){
914 struct a_colour_env *cep;
916 if((cep = a_colour_env)->ce_is_active)
917 n_pstate |= n_PS_COLOUR_ACTIVE;
918 else
919 n_pstate &= ~n_PS_COLOUR_ACTIVE;
921 if(a_colour_g != NULL){
922 a_colour_g->cg_active = NULL;
923 a_colour_g->cg_ctx = cep->ce_ctx;
925 a_colour_env = cep->ce_last;
927 free(cep);
928 if(!any_env_till_root)
929 break;
932 if(any_env_till_root && a_colour_g != NULL &&
933 (n_pstate & n_PS_COLOUR_ACTIVE)){
934 n_pstate &= ~n_PS_COLOUR_ACTIVE;
935 a_colour_g->cg_active = NULL;
937 jleave:
938 NYD_LEAVE;
941 FL void
942 n_colour_env_gut(FILE *fp){
943 NYD_ENTER;
944 if((n_psonce & n_PSO_INTERACTIVE) && (n_pstate & n_PS_COLOUR_ACTIVE)){
945 n_pstate &= ~n_PS_COLOUR_ACTIVE;
947 if(a_colour_g->cg_active != NULL){
948 a_colour_g->cg_active = NULL;
949 if(fp != NULL)
950 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l,
951 1, fp);
954 NYD_LEAVE;
957 FL void
958 n_colour_put(FILE *fp, enum n_colour_id cid, char const *ctag){
959 NYD_ENTER;
960 if(n_pstate & n_PS_COLOUR_ACTIVE){
961 if(a_colour_g->cg_active != NULL)
962 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l, 1,
963 fp);
964 if((a_colour_g->cg_active = a_colour_map_find(cid, ctag)) != NULL)
965 fwrite(a_colour_g->cg_active->cm_pen.cp_dat.s,
966 a_colour_g->cg_active->cm_pen.cp_dat.l, 1, fp);
968 NYD_LEAVE;
971 FL void
972 n_colour_reset(FILE *fp){
973 NYD_ENTER;
974 if((n_pstate & n_PS_COLOUR_ACTIVE) && a_colour_g->cg_active != NULL){
975 a_colour_g->cg_active = NULL;
976 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l, 1,
977 fp);
979 NYD_LEAVE;
982 FL struct str const *
983 n_colour_reset_to_str(void){
984 struct str *rv;
985 NYD_ENTER;
987 if(n_pstate & n_PS_COLOUR_ACTIVE)
988 rv = &a_colour_g->cg_reset.cp_dat;
989 else
990 rv = NULL;
991 NYD_LEAVE;
992 return rv;
995 FL struct n_colour_pen *
996 n_colour_pen_create(enum n_colour_id cid, char const *ctag){
997 struct a_colour_map *cmp;
998 struct n_colour_pen *rv;
999 NYD_ENTER;
1001 if((n_pstate & n_PS_COLOUR_ACTIVE) &&
1002 (cmp = a_colour_map_find(cid, ctag)) != NULL){
1003 union {void *vp; char *cp; struct n_colour_pen *cpp;} u;
1004 u.vp = cmp;
1005 rv = u.cpp;
1006 }else
1007 rv = NULL;
1008 NYD_LEAVE;
1009 return rv;
1012 FL void
1013 n_colour_pen_put(struct n_colour_pen *self, FILE *fp){
1014 NYD_ENTER;
1015 if(n_pstate & n_PS_COLOUR_ACTIVE){
1016 union {void *vp; char *cp; struct a_colour_map *cmp;} u;
1018 u.vp = self;
1019 if(u.cmp != a_colour_g->cg_active){
1020 if(a_colour_g->cg_active != NULL)
1021 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l,
1022 1, fp);
1023 if(u.cmp != NULL)
1024 fwrite(self->cp_dat.s, self->cp_dat.l, 1, fp);
1025 a_colour_g->cg_active = u.cmp;
1028 NYD_LEAVE;
1031 FL struct str const *
1032 n_colour_pen_to_str(struct n_colour_pen *self){
1033 struct str *rv;
1034 NYD_ENTER;
1036 if((n_pstate & n_PS_COLOUR_ACTIVE) && self != NULL)
1037 rv = &self->cp_dat;
1038 else
1039 rv = NULL;
1040 NYD_LEAVE;
1041 return rv;
1043 #endif /* HAVE_COLOUR */
1045 /* s-it-mode */