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