Add `customhdr' (Sergey Matveev)..
[s-mailx.git] / colour.c
blobed75d5c77ee6f3c194c12236322a87e6578cad14
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?colour' commands, and anything working with it.
4 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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_COLOUR256,
34 a_COLOUR_T_COLOUR,
35 a_COLOUR_T_MONO
38 enum a_colour_tag_type{
39 a_COLOUR_TT_NONE,
40 a_COLOUR_TT_DOT = 1<<0, /* "dot" */
41 a_COLOUR_TT_OLDER = 1<<1, /* "older" */
42 a_COLOUR_TT_HEADERS = 1<<2, /* Comma-separated list of headers allowed */
44 a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER,
45 a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS
48 struct a_colour_map_id{
49 ui8_t cmi_group; /* enum n_colour_group */
50 ui8_t cmi_id; /* enum n_colour_id */
51 ui8_t cmi_tt; /* enum a_colour_tag_type */
52 char const cmi_name[13];
54 CTA(n__COLOUR_IDS <= UI8_MAX);
56 struct n_colour_pen{
57 struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */
60 struct a_colour_map /* : public n_colour_pen */{
61 struct n_colour_pen cm_pen; /* Points into .cm_buf */
62 struct a_colour_map *cm_next;
63 char const *cm_tag; /* Colour tag or NULL for default (last) */
64 struct a_colour_map_id const *cm_cmi;
65 #ifdef HAVE_REGEX
66 regex_t *cm_regex;
67 #endif
68 ui32_t cm_refcnt; /* Beware of reference drops in recursions */
69 ui32_t cm_user_off; /* User input offset in .cm_buf */
70 char cm_buf[VFIELD_SIZE(0)];
73 struct a_colour_g{
74 /* TODO cg_has_env not used, we have to go for PS_COLOUR_ACTIVE */
75 bool_t cg_has_env;
76 ui8_t cg_group; /* If .cg_has_env, enum n_colour_group */
77 ui8_t __cg_pad[6];
78 struct a_colour_map *cg_active; /* The currently active colour */
79 /* Active mapping: .cg_colour*_maps on colour terminal, _mono_ otherwise */
80 struct a_colour_map *(*cg_maps)[n__COLOUR_GROUPS][n__COLOUR_IDS];
81 struct n_colour_pen cg_reset; /* The reset sequence */
82 struct a_colour_map *cg_colour256_maps[n__COLOUR_GROUPS][n__COLOUR_IDS];
83 struct a_colour_map *cg_colour_maps[n__COLOUR_GROUPS][n__COLOUR_IDS];
84 struct a_colour_map *cg_mono_maps[n__COLOUR_GROUPS][n__COLOUR_IDS];
85 char cg_reset_buf[sizeof("\033[0m")];
88 /* TODO The colour environment simply should be a pointer into an
89 * TODO carrier structure in equal spirit to the fio.c stack, which gets
90 * TODO created for each execute() cycle (long in TODO), and carries along
91 * TODO all the information, memory allocations and also output (filter)
92 * TODO chains, so that we could actually decide wether we could simply
93 * TODO suspend output for a chain, need to place reset sequences, etc.
94 * TODO For now, since we have no such carrier to know where colour
95 * TODO sequences have to be written, creating a colour environment requires
96 * TODO that the current colour state is "reset", because we wouldn't know
97 * TODO where to place reset sequences and ditto, reestablish colour.
98 * TODO This should be no problem in practice, however */
99 struct a_colour_env{
100 struct a_colour_env *ce_last;
101 ui8_t ce_group; /* enum n_colour_group active upon switch */
102 bool_t ce_is_active; /* Was colour active in outer level? */
103 ui8_t __ce_pad[6];
106 /* C99: use [INDEX]={} */
107 CTA(n_COLOUR_GROUP_SUM == 0);
108 CTA(n_COLOUR_GROUP_VIEW == 1);
109 static char const a_colour_group_prefixes[n__COLOUR_GROUPS][8] = {
110 "sum-", "view-"
113 static struct a_colour_map_id const
114 a_colour_map_ids[n__COLOUR_GROUPS][n__COLOUR_IDS] = {{
115 {n_COLOUR_GROUP_SUM, n_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"},
116 {n_COLOUR_GROUP_SUM, n_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"},
117 {n_COLOUR_GROUP_SUM, n_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"},
118 }, {
119 {n_COLOUR_GROUP_VIEW, n_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"},
120 {n_COLOUR_GROUP_VIEW, n_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"},
121 {n_COLOUR_GROUP_VIEW, n_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE, "msginfo"},
122 {n_COLOUR_GROUP_VIEW, n_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE,
123 "partinfo"},
125 #define a_COLOUR_MAP_SHOW_FIELDWIDTH \
126 (int)(sizeof("view-")-1 + sizeof("partinfo")-1)
128 static struct a_colour_g *a_colour_g;
129 static struct a_colour_env *a_colour_env;
131 static void a_colour_init(void);
132 DBG( static void a_colour_atexit(void); )
134 /* Find the type or -1 */
135 static enum a_colour_type a_colour_type_find(char const *name);
137 /* `(un)?colour' implementations */
138 static bool_t a_colour_mux(char **argv);
139 static bool_t a_colour_unmux(char **argv);
141 static bool_t a_colour__show(enum a_colour_type ct);
142 /* (regexpp may be NULL) */
143 static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip,
144 char const *ctag, void **regexpp);
146 /* Try to find a mapping identity for user given slotname */
147 static struct a_colour_map_id const *a_colour_map_id_find(char const *slotname);
149 /* Find an existing mapping for the given combination */
150 static struct a_colour_map *a_colour_map_find(enum n_colour_id cid,
151 char const *ctag);
153 /* In-/Decrement reference counter, destroy if counts gets zero */
154 #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0)
155 static void a_colour_map_unref(struct a_colour_map *self);
157 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user
158 * input spec, store it or on error message in *store */
159 static bool_t a_colour_iso6429(enum a_colour_type ct, char **store,
160 char const *spec);
162 static void
163 a_colour_init(void){
164 char const *term, *cp;
165 char *okterms;
166 NYD2_ENTER;
168 a_colour_g = scalloc(1, sizeof *a_colour_g);
170 if((term = env_vlook("TERM", FAL0)) == NULL || !asccasecmp(term, "dumb"))
171 goto jleave;
173 memcpy(a_colour_g->cg_reset.cp_dat.s = a_colour_g->cg_reset_buf, "\033[0m",
174 a_colour_g->cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */
176 /* terminfo rocks: if we find "color", assume it's right; don't case care */
177 a_colour_g->cg_maps = &a_colour_g->cg_colour256_maps;
178 if(asccasestr(term, "256color") != NULL) /* FIXME temporary hack */
179 goto jmaps;
181 a_colour_g->cg_maps = &a_colour_g->cg_colour_maps;
183 if(asccasestr(term, "color") != NULL)
184 goto jmaps;
186 if((cp = ok_vlook(colour_terms)) == NULL)
187 cp = n_COLOUR_TERMS;
188 okterms = savestr(cp);
189 while((cp = n_strsep(&okterms, ',', TRU1)) != NULL)
190 if(!asccasecmp(cp, term))
191 goto jmaps;
193 a_colour_g->cg_maps = &a_colour_g->cg_mono_maps;
194 jmaps:
195 DBG( atexit(&a_colour_atexit); ) /* TODO prog-global atexit event */
196 jleave:
197 NYD2_LEAVE;
200 #ifdef HAVE_DEBUG
201 static void
202 a_colour_atexit(void){
203 NYD_ENTER;
204 if(a_colour_env != NULL)
205 n_colour_env_pop(TRU1);
206 free(a_colour_g);
207 a_colour_g = NULL;
208 NYD_LEAVE;
210 #endif
212 static enum a_colour_type
213 a_colour_type_find(char const *name){
214 struct{
215 char name[7];
216 ui8_t type;
217 } const ta[] = {
218 {"256", a_COLOUR_T_COLOUR256},
219 {"8", a_COLOUR_T_COLOUR},
220 {"iso", a_COLOUR_T_COLOUR},
221 {"ansi", a_COLOUR_T_COLOUR},
222 {"1", a_COLOUR_T_MONO},
223 {"mono", a_COLOUR_T_MONO}
224 }, *tp;
225 enum a_colour_type rv;
226 NYD2_ENTER;
228 tp = ta;
229 do if(!asccasecmp(tp->name, name)){
230 rv = tp->type;
231 goto jleave;
232 }while(PTRCMP(++tp, !=, ta + NELEM(ta)));
234 rv = (enum a_colour_type)-1;
235 jleave:
236 NYD2_LEAVE;
237 return rv;
240 static bool_t
241 a_colour_mux(char **argv){
242 void *regexp;
243 char const *mapname, *ctag;
244 struct a_colour_map *(*mapp)[n__COLOUR_GROUPS][n__COLOUR_IDS], **cmap,
245 *blcmp, *lcmp, *cmp;
246 struct a_colour_map_id const *cmip;
247 bool_t rv;
248 enum a_colour_type ct;
249 NYD2_ENTER;
251 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
252 n_err(_("`colour': invalid colour type \"%s\"\n"), argv[-1]);
253 rv = FAL0;
254 goto jleave;
257 if(a_colour_g == NULL)
258 a_colour_init();
260 if(*argv == NULL){
261 rv = a_colour__show(ct);
262 goto jleave;
265 rv = FAL0;
266 regexp = NULL;
267 switch(ct){
268 case a_COLOUR_T_COLOUR256:
269 mapp = &a_colour_g->cg_colour256_maps;
270 break;
271 case a_COLOUR_T_COLOUR:
272 mapp = &a_colour_g->cg_colour_maps;
273 break;
274 default:
275 mapp = &a_colour_g->cg_mono_maps;
276 break;
279 if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){
280 n_err(_("`colour': non-existing mapping: \"%s\"\n"), mapname);
281 goto jleave;
284 if(argv[1] == NULL){
285 n_err(_("`colour': \"%s\": missing attribute argument\n"), mapname);
286 goto jleave;
289 /* Check wether preconditions are at all allowed, verify them as far as
290 * possible as necessary */
291 if((ctag = argv[2]) != NULL){
292 char const *xtag;
294 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
295 n_err(_("`colour': \"%s\" doesn't support preconditions\n"), mapname);
296 goto jleave;
297 }else if((xtag = a_colour__tag_identify(cmip, ctag, &regexp)) ==
298 n_COLOUR_TAG_ERR){
299 /* I18N: ..of colour mapping */
300 n_err(_("`colour': \"%s\": invalid precondition: \"%s\"\n"),
301 mapname, ctag);
302 goto jleave;
304 ctag = xtag;
307 /* At this time we have all the information to be able to query wether such
308 * a mapping is yet established. If so, destroy it */
309 for(blcmp = lcmp = NULL,
310 cmp = *(cmap = &(*mapp)[cmip->cmi_group][cmip->cmi_id]);
311 cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){
312 char const *xctag = cmp->cm_tag;
314 if(xctag == ctag ||
315 (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
316 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
317 !strcmp(xctag, ctag))){
318 if(lcmp == NULL)
319 *cmap = cmp->cm_next;
320 else
321 lcmp->cm_next = cmp->cm_next;
322 a_colour_map_unref(cmp);
323 break;
327 /* Create mapping */
328 /* C99 */{
329 size_t tl, ul, cl;
330 char *bp, *cp;
332 if(!a_colour_iso6429(ct, &cp, argv[1])){
333 /* I18N: colour command: mapping: error message: user argument */
334 n_err(_("`colour': \"%s\": %s: \"%s\"\n"), mapname, cp, argv[1]);
335 goto jleave;
338 tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag)) ? strlen(ctag) : 0;
339 cmp = smalloc(sizeof(*cmp) - VFIELD_SIZEOF(struct a_colour_map, cm_buf) +
340 tl +1 + (ul = strlen(argv[1])) +1 + (cl = strlen(cp)) +1);
342 /* .cm_buf stuff */
343 cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf;
344 cmp->cm_pen.cp_dat.l = cl;
345 memcpy(bp, cp, ++cl);
346 bp += cl;
348 cmp->cm_user_off = (ui32_t)PTR2SIZE(bp - cmp->cm_buf);
349 memcpy(bp, argv[1], ++ul);
350 bp += ul;
352 if(tl > 0){
353 cmp->cm_tag = bp;
354 memcpy(bp, ctag, ++tl);
355 bp += tl;
356 }else
357 cmp->cm_tag = ctag;
359 /* Non-buf stuff; default mapping */
360 if(lcmp != NULL){
361 /* Default mappings must be last */
362 if(ctag == NULL){
363 while(lcmp->cm_next != NULL)
364 lcmp = lcmp->cm_next;
365 }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){
366 if((lcmp = blcmp) == NULL)
367 goto jlinkhead;
369 cmp->cm_next = lcmp->cm_next;
370 lcmp->cm_next = cmp;
371 }else{
372 jlinkhead:
373 cmp->cm_next = *cmap;
374 *cmap = cmp;
376 cmp->cm_cmi = cmip;
377 #ifdef HAVE_REGEX
378 cmp->cm_regex = regexp;
379 #endif
380 cmp->cm_refcnt = 0;
381 a_colour_map_ref(cmp);
383 rv = TRU1;
384 jleave:
385 NYD2_LEAVE;
386 return rv;
389 static bool_t
390 a_colour_unmux(char **argv){
391 char const *mapname, *ctag, *xtag;
392 struct a_colour_map *(*mapp)[n__COLOUR_GROUPS][n__COLOUR_IDS], **cmap,
393 *lcmp, *cmp;
394 struct a_colour_map_id const *cmip;
395 bool_t rv;
396 enum a_colour_type ct;
397 NYD2_ENTER;
399 if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){
400 n_err(_("`uncolour': invalid colour type \"%s\"\n"), argv[-1]);
401 rv = FAL0;
402 goto jleave;
405 mapname = argv[0];
406 ctag = (mapname != NULL) ? argv[1] : mapname;
408 rv = TRU1;
409 if(a_colour_g == NULL)
410 goto jemap;
411 rv = FAL0;
413 switch(ct){
414 case a_COLOUR_T_COLOUR256:
415 mapp = &a_colour_g->cg_colour256_maps;
416 break;
417 case a_COLOUR_T_COLOUR:
418 mapp = &a_colour_g->cg_colour_maps;
419 break;
420 default:
421 mapp = &a_colour_g->cg_mono_maps;
422 break;
425 /* Delete anything? */
426 if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){
427 size_t i1, i2;
428 struct a_colour_map *tmp;
430 for(i1 = 0; i1 < n__COLOUR_GROUPS; ++i1)
431 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2)
432 for(cmp = *(cmap = &(*mapp)[i1][i2]), *cmap = NULL; cmp != NULL;){
433 tmp = cmp;
434 cmp = cmp->cm_next;
435 a_colour_map_unref(tmp);
437 }else{
438 if(a_colour_g == NULL){
439 jemap:
440 /* I18N: colour command, mapping and precondition (option in quotes) */
441 n_err(_("`uncolour': non-existing mapping: \"%s\"%s%s%s\n"),
442 mapname, (ctag == NULL ? "" : " \""),
443 (ctag == NULL ? "" : ctag), (ctag == NULL ? "" : "\""));
444 goto jleave;
447 if((cmip = a_colour_map_id_find(mapname)) == NULL)
448 goto jemap;
450 if((xtag = ctag) != NULL){
451 if(cmip->cmi_tt == a_COLOUR_TT_NONE){
452 n_err(_("`uncolour': \"%s\" doesn't support preconditions\n"),
453 mapname);
454 goto jleave;
455 }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) ==
456 n_COLOUR_TAG_ERR){
457 n_err(_("`uncolour': \"%s\": invalid precondition: \"%s\"\n"),
458 mapname, ctag);
459 goto jleave;
461 /* (Improve user experience) */
462 if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag))
463 ctag = xtag;
466 lcmp = NULL;
467 cmp = *(cmap = &(*mapp)[cmip->cmi_group][cmip->cmi_id]);
468 for(;;){
469 char const *xctag;
471 if(cmp == NULL)
472 goto jemap;
473 if((xctag = cmp->cm_tag) == ctag)
474 break;
475 if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) &&
476 xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) &&
477 !strcmp(xctag, ctag))
478 break;
479 lcmp = cmp;
480 cmp = cmp->cm_next;
483 if(lcmp == NULL)
484 *cmap = cmp->cm_next;
485 else
486 lcmp->cm_next = cmp->cm_next;
487 a_colour_map_unref(cmp);
489 rv = TRU1;
490 jleave:
491 NYD2_LEAVE;
492 return rv;
495 static bool_t
496 a_colour__show(enum a_colour_type ct){
497 char const *type;
498 struct a_colour_map *(*mapp)[n__COLOUR_GROUPS][n__COLOUR_IDS], *cmp;
499 size_t i1, i2;
500 bool_t rv;
501 NYD2_ENTER;
503 switch(ct){
504 case a_COLOUR_T_COLOUR256:
505 type = "256";
506 mapp = &a_colour_g->cg_colour256_maps;
507 break;
508 case a_COLOUR_T_COLOUR:
509 type = "iso";
510 mapp = &a_colour_g->cg_colour_maps;
511 break;
512 default:
513 type = "mono";
514 mapp = &a_colour_g->cg_mono_maps;
515 break;
518 for(i1 = 0; i1 < n__COLOUR_GROUPS; ++i1)
519 for(i2 = 0; i2 < n__COLOUR_IDS; ++i2){
520 if((cmp = (*mapp)[i1][i2]) == NULL)
521 continue;
523 while(cmp != NULL){
524 char const *tagann, *tag;
526 tagann = "";
527 if((tag = cmp->cm_tag) == NULL)
528 tag = "";
529 else if(tag == n_COLOUR_TAG_SUM_DOT)
530 tag = "dot";
531 else if(tag == n_COLOUR_TAG_SUM_OLDER)
532 tag = "older";
533 #ifdef HAVE_REGEX
534 else if(cmp->cm_regex != NULL)
535 tagann = "[rx] ";
536 #endif
537 printf("colour %s %-*s %s %s%s\n",
538 type, a_COLOUR_MAP_SHOW_FIELDWIDTH,
539 savecat(a_colour_group_prefixes[i1],
540 a_colour_map_ids[i1][i2].cmi_name),
541 (char const*)cmp->cm_buf + cmp->cm_user_off,
542 tagann, tag);
543 cmp = cmp->cm_next;
546 rv = TRU1;
547 NYD2_LEAVE;
548 return rv;
551 static char const *
552 a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag,
553 void **regexpp){
554 NYD2_ENTER;
555 UNUSED(regexpp);
557 if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !asccasecmp(ctag, "dot"))
558 ctag = n_COLOUR_TAG_SUM_DOT;
559 else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) && !asccasecmp(ctag, "older"))
560 ctag = n_COLOUR_TAG_SUM_OLDER;
561 else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){
562 char *cp, c;
563 size_t i;
565 /* Can this be a valid list of headers? However, with regular expressions
566 * simply use the input as such if it appears to be a regex */
567 #ifdef HAVE_REGEX
568 if(is_maybe_regex(ctag)){
569 if(regexpp != NULL && regcomp(*regexpp = smalloc(sizeof(regex_t)),
570 ctag, REG_EXTENDED | REG_ICASE | REG_NOSUB)){
571 free(*regexpp);
572 goto jetag;
574 }else
575 #endif
577 /* Normalize to lowercase and strip any whitespace before use */
578 i = strlen(ctag);
579 cp = salloc(i +1);
581 for(i = 0; (c = *ctag++) != '\0';){
582 bool_t isblspc = blankspacechar(c);
584 if(!isblspc && !alnumchar(c) && c != '-' && c != ',')
585 goto jetag;
586 /* Since we compare header names as they come from the message this
587 * lowercasing is however redundant: we need to asccasecmp() them */
588 if(!isblspc)
589 cp[i++] = lowerconv(c);
591 cp[i] = '\0';
592 ctag = cp;
594 }else
595 jetag:
596 ctag = n_COLOUR_TAG_ERR;
597 NYD2_LEAVE;
598 return ctag;
601 static struct a_colour_map_id const *
602 a_colour_map_id_find(char const *cp){
603 size_t i;
604 struct a_colour_map_id const (*cmip)[n__COLOUR_IDS], *rv;
605 NYD2_ENTER;
607 rv = NULL;
609 for(i = 0;; ++i){
610 if(i == n__COLOUR_IDS)
611 goto jleave;
612 else{
613 size_t j = strlen(a_colour_group_prefixes[i]);
614 if(!ascncasecmp(cp, a_colour_group_prefixes[i], j)){
615 cp += j;
616 break;
620 cmip = &a_colour_map_ids[i];
622 for(i = 0;; ++i){
623 if(i == n__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){
624 rv = NULL;
625 break;
627 if(!asccasecmp(cp, rv->cmi_name))
628 break;
630 jleave:
631 NYD2_LEAVE;
632 return rv;
635 static struct a_colour_map *
636 a_colour_map_find(enum n_colour_id cid, char const *ctag){
637 struct a_colour_map *cmp;
638 NYD2_ENTER;
640 for(cmp = (*a_colour_g->cg_maps)[a_colour_g->cg_group][cid]; cmp != NULL;
641 cmp = cmp->cm_next){
642 char const *xtag = cmp->cm_tag;
644 if(xtag == ctag)
645 break;
646 if(xtag == NULL)
647 break;
648 if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag))
649 continue;
650 #ifdef HAVE_REGEX
651 if(cmp->cm_regex != NULL){
652 if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH)
653 break;
654 }else
655 #endif
656 if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){
657 char *hlist = savestr(xtag), *cp;
659 while((cp = n_strsep(&hlist, ',', TRU1)) != NULL){
660 if(!asccasecmp(cp, ctag))
661 break;
663 if(cp != NULL)
664 break;
667 NYD2_LEAVE;
668 return cmp;
671 static void
672 a_colour_map_unref(struct a_colour_map *self){
673 NYD2_ENTER;
674 if(--self->cm_refcnt == 0){
675 #ifdef HAVE_REGEX
676 if(self->cm_regex != NULL){
677 regfree(self->cm_regex);
678 free(self->cm_regex);
680 #endif
681 free(self);
683 NYD2_LEAVE;
686 static bool_t
687 a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){
688 struct isodesc{
689 char id_name[15];
690 char id_modc;
691 } const fta[] = {
692 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
693 }, ca[] = {
694 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
695 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
696 }, *idp;
697 char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")];
698 ui8_t ftno_base, ftno;
699 bool_t rv;
700 NYD_ENTER;
702 rv = FAL0;
703 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
704 cfg[0] = cfg[1] = 0;
706 /* Since we use salloc(), reuse the n_strsep() buffer also for the return
707 * value, ensure we have enough room for that */
708 /* C99 */{
709 size_t i = strlen(spec) +1;
710 xspec = salloc(MAX(i, sizeof("\033[1;4;7;38;5;255;48;5;255m")));
711 memcpy(xspec, spec, i);
712 spec = xspec;
715 /* Iterate over the colour spec */
716 ftno = 0;
717 while((cp = n_strsep(&xspec, ',', TRU1)) != NULL){
718 char *y, *x = strchr(cp, '=');
719 if(x == NULL){
720 jbail:
721 *store = UNCONST(_("invalid attribute list"));
722 goto jleave;
724 *x++ = '\0';
726 if(!asccasecmp(cp, "ft")){
727 if(!asccasecmp(x, "inverse")){
728 OBSOLETE(_("please use \"reverse\" not \"inverse\" for ft= fonts"));
729 x = UNCONST("reverse");
731 for(idp = fta;; ++idp)
732 if(idp == fta + NELEM(fta)){
733 *store = UNCONST(_("invalid font attribute"));
734 goto jleave;
735 }else if(!asccasecmp(x, idp->id_name)){
736 if(ftno < NELEM(fg))
737 fg[ftno++] = idp->id_modc;
738 else{
739 *store = UNCONST(_("too many font attributes"));
740 goto jleave;
742 break;
744 }else if(!asccasecmp(cp, "fg")){
745 y = cfg + 0;
746 goto jiter_colour;
747 }else if(!asccasecmp(cp, "bg")){
748 y = cfg + 1;
749 jiter_colour:
750 if(ct == a_COLOUR_T_MONO){
751 *store = UNCONST(_("colours are not allowed"));
752 goto jleave;
754 /* Maybe 256 color spec */
755 if(digitchar(x[0])){
756 sl_i xv;
758 if(ct == a_COLOUR_T_COLOUR){
759 *store = UNCONST(_("invalid colour for 8-colour mode"));
760 goto jleave;
763 xv = strtol(x, &cp, 10);
764 if(xv < 0 || xv > 255 || *cp != '\0' || PTRCMP(&x[3], <, cp)){
765 *store = UNCONST(_("invalid 256-colour specification"));
766 goto jleave;
768 y[0] = 5;
769 memcpy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x,
770 (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4)));
771 }else for(idp = ca;; ++idp)
772 if(idp == ca + NELEM(ca)){
773 *store = UNCONST(_("invalid colour attribute"));
774 goto jleave;
775 }else if(!asccasecmp(x, idp->id_name)){
776 y[0] = 1;
777 y[2] = idp->id_modc;
778 break;
780 }else
781 goto jbail;
784 /* Restore our salloc() buffer, create return value */
785 xspec = UNCONST(spec);
786 if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */
787 xspec[0] = '\033';
788 xspec[1] = '[';
789 xspec += 2;
791 for(ftno_base = ftno; ftno > 0;){
792 if(ftno-- != ftno_base)
793 *xspec++ = ';';
794 *xspec++ = fg[ftno];
797 if(cfg[0]){
798 if(ftno_base > 0)
799 *xspec++ = ';';
800 xspec[0] = '3';
801 if(cfg[0] == 1){
802 xspec[1] = cfg[2];
803 xspec += 2;
804 }else{
805 memcpy(xspec + 1, "8;5;", 4);
806 xspec += 5;
807 for(ftno = 2; cfg[ftno] != '\0'; ++ftno)
808 *xspec++ = cfg[ftno];
812 if(cfg[1]){
813 if(ftno_base > 0 || cfg[0])
814 *xspec++ = ';';
815 xspec[0] = '4';
816 if(cfg[1] == 1){
817 xspec[1] = cfg[3];
818 xspec += 2;
819 }else{
820 memcpy(xspec + 1, "8;5;", 4);
821 xspec += 5;
822 for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno)
823 *xspec++ = cfg[ftno];
827 *xspec++ = 'm';
829 *xspec = '\0';
830 *store = UNCONST(spec);
831 rv = TRU1;
832 jleave:
833 NYD_LEAVE;
834 return rv;
837 FL int
838 c_colour(void *v){
839 int rv;
840 NYD_ENTER;
842 rv = !a_colour_mux(v);
843 NYD_LEAVE;
844 return rv;
847 FL int
848 c_uncolour(void *v){
849 int rv;
850 NYD_ENTER;
852 rv = !a_colour_unmux(v);
853 NYD_LEAVE;
854 return rv;
857 FL void
858 n_colour_env_create(enum n_colour_group cgrp, bool_t pager_used){
859 NYD_ENTER;
860 if(!(options & OPT_INTERACTIVE))
861 goto jleave;
863 if (ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager))){
864 n_colour_env_push(); /* FIXME lex.c only pops (always env */
865 goto jleave;
868 if(a_colour_g == NULL)
869 a_colour_init();
870 if(a_colour_g->cg_maps == NULL)
871 goto jleave;
873 a_colour_g->cg_group = cgrp;
874 a_colour_g->cg_active = NULL;
875 pstate |= PS_COLOUR_ACTIVE;
876 jleave:
877 NYD_LEAVE;
880 FL void
881 n_colour_env_push(void){
882 struct a_colour_env *cep;
883 NYD_ENTER;
885 if(!(options & OPT_INTERACTIVE))
886 goto jleave;
888 cep = smalloc(sizeof *cep);
889 cep->ce_last = a_colour_env;
890 if(a_colour_g != NULL){
891 cep->ce_group = a_colour_g->cg_group;
892 a_colour_g->cg_active = NULL;
894 cep->ce_is_active = ((pstate & PS_COLOUR_ACTIVE) != 0);
895 a_colour_env = cep;
897 pstate &= ~PS_COLOUR_ACTIVE;
898 jleave:
899 NYD_LEAVE;
902 FL void
903 n_colour_env_pop(bool_t any_env_till_root){
904 NYD_ENTER;
905 if(!(options & OPT_INTERACTIVE))
906 goto jleave;
908 while(a_colour_env != NULL){
909 struct a_colour_env *cep;
911 if((cep = a_colour_env)->ce_is_active)
912 pstate |= PS_COLOUR_ACTIVE;
913 else
914 pstate &= ~PS_COLOUR_ACTIVE;
916 if(a_colour_g != NULL){
917 a_colour_g->cg_active = NULL;
918 a_colour_g->cg_group = cep->ce_group;
920 a_colour_env = cep->ce_last;
922 free(cep);
923 if(!any_env_till_root)
924 break;
927 if(any_env_till_root && a_colour_g != NULL && (pstate & PS_COLOUR_ACTIVE)){
928 pstate &= ~PS_COLOUR_ACTIVE;
929 a_colour_g->cg_active = NULL;
931 jleave:
932 NYD_LEAVE;
935 FL void
936 n_colour_env_gut(FILE *fp){
937 NYD_ENTER;
938 if((options & OPT_INTERACTIVE) && (pstate & PS_COLOUR_ACTIVE)){
939 pstate &= ~PS_COLOUR_ACTIVE;
941 if(a_colour_g->cg_active != NULL){
942 a_colour_g->cg_active = NULL;
943 if(fp != NULL)
944 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l,
945 1, fp);
948 NYD_LEAVE;
951 FL void
952 n_colour_put(FILE *fp, enum n_colour_id cid, char const *ctag){
953 NYD_ENTER;
954 if(pstate & PS_COLOUR_ACTIVE){
955 if(a_colour_g->cg_active != NULL)
956 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l, 1,
957 fp);
958 if((a_colour_g->cg_active = a_colour_map_find(cid, ctag)) != NULL)
959 fwrite(a_colour_g->cg_active->cm_pen.cp_dat.s,
960 a_colour_g->cg_active->cm_pen.cp_dat.l, 1, fp);
962 NYD_LEAVE;
965 FL void
966 n_colour_reset(FILE *fp){
967 NYD_ENTER;
968 if((pstate & PS_COLOUR_ACTIVE) && a_colour_g->cg_active != NULL){
969 a_colour_g->cg_active = NULL;
970 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l, 1,
971 fp);
973 NYD_LEAVE;
976 FL struct str const *
977 n_colour_reset_to_str(void){
978 struct str *rv;
979 NYD_ENTER;
981 if(pstate & PS_COLOUR_ACTIVE)
982 rv = &a_colour_g->cg_reset.cp_dat;
983 else
984 rv = NULL;
985 NYD_LEAVE;
986 return rv;
989 FL struct n_colour_pen *
990 n_colour_pen_create(enum n_colour_id cid, char const *ctag){
991 struct a_colour_map *cmp;
992 struct n_colour_pen *rv;
993 NYD_ENTER;
995 if((pstate & PS_COLOUR_ACTIVE) &&
996 (cmp = a_colour_map_find(cid, ctag)) != NULL){
997 union {void *vp; char *cp; struct n_colour_pen *cpp;} u;
998 u.vp = cmp;
999 rv = u.cpp;
1000 }else
1001 rv = NULL;
1002 NYD_LEAVE;
1003 return rv;
1006 FL void
1007 n_colour_pen_put(struct n_colour_pen *self, FILE *fp){
1008 NYD_ENTER;
1009 if(pstate & PS_COLOUR_ACTIVE){
1010 union {void *vp; char *cp; struct a_colour_map *cmp;} u;
1012 u.vp = self;
1013 if(u.cmp != a_colour_g->cg_active){
1014 if(a_colour_g->cg_active != NULL)
1015 fwrite(a_colour_g->cg_reset.cp_dat.s, a_colour_g->cg_reset.cp_dat.l,
1016 1, fp);
1017 if(u.cmp != NULL)
1018 fwrite(self->cp_dat.s, self->cp_dat.l, 1, fp);
1019 a_colour_g->cg_active = u.cmp;
1022 NYD_LEAVE;
1025 FL struct str const *
1026 n_colour_pen_to_str(struct n_colour_pen *self){
1027 struct str *rv;
1028 NYD_ENTER;
1030 if((pstate & PS_COLOUR_ACTIVE) && self != NULL)
1031 rv = &self->cp_dat;
1032 else
1033 rv = NULL;
1034 NYD_LEAVE;
1035 return rv;
1037 #endif /* HAVE_COLOUR */
1039 /* s-it-mode */