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.
26 #ifndef HAVE_AMALGAMATION
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))
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
{
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 */
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");
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
;
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)];
87 ui8_t cg_type
; /* a_colour_type */
89 struct n_colour_pen cg_reset
; /* The reset sequence */
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]={} */
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"},
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"},
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
;
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
,
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
;
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
;
181 ctmp
= a_colour_type_maps
;
182 do if(!asccasecmp(ctmp
->ctm_name
, name
)){
185 }while(PTRCMP(++ctmp
, !=, a_colour_type_maps
+ n_NELEM(a_colour_type_maps
)));
187 rv
= (enum a_colour_type
)-1;
194 a_colour_mux(char **argv
){
196 char const *mapname
, *ctag
;
197 struct a_colour_map
**cmap
, *blcmp
, *lcmp
, *cmp
;
198 struct a_colour_map_id
const *cmip
;
200 enum a_colour_type ct
;
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
));
211 if(!a_colour_g
.cg_is_init
)
215 rv
= a_colour__show(ct
);
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
));
229 n_err(_("`colour': %s: missing attribute argument\n"),
230 n_shexp_quote_cp(mapname
, FAL0
));
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'){
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
));
244 }else if((xtag
= a_colour__tag_identify(cmip
, ctag
, ®exp
)) ==
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
));
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
,
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
;
263 (ctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(ctag
) &&
264 xctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xctag
) &&
265 !strcmp(xctag
, ctag
))){
267 *cmap
= cmp
->cm_next
;
269 lcmp
->cm_next
= cmp
->cm_next
;
270 a_colour_map_unref(cmp
);
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
));
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);
292 cmp
->cm_pen
.cp_dat
.s
= bp
= cmp
->cm_buf
;
293 cmp
->cm_pen
.cp_dat
.l
= cl
;
294 memcpy(bp
, cp
, ++cl
);
297 cmp
->cm_user_off
= (ui32_t
)PTR2SIZE(bp
- cmp
->cm_buf
);
298 memcpy(bp
, argv
[1], ++ul
);
303 memcpy(bp
, ctag
, ++tl
);
308 /* Non-buf stuff; default mapping */
310 /* Default mappings must be last */
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
)
318 cmp
->cm_next
= lcmp
->cm_next
;
322 cmp
->cm_next
= *cmap
;
327 cmp
->cm_regex
= regexp
;
330 a_colour_map_ref(cmp
);
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
;
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
));
364 if(!a_colour_g
.cg_is_init
)
367 /* Delete anything? */
369 if(ctag
== NULL
&& mapname
[0] == '*' && mapname
[1] == '\0'){
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
;
379 a_colour_map_unref(tmp
);
382 if((cmip
= a_colour_map_id_find(mapname
)) == NULL
){
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
)));
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
));
398 }else if((xtag
= a_colour__tag_identify(cmip
, ctag
, NULL
)) ==
400 n_err(_("`uncolour': %s: invalid precondition: %s\n"),
401 n_shexp_quote_cp(mapname
, FAL0
), n_shexp_quote_cp(ctag
, FAL0
));
405 /* (Improve user experience) */
406 if(xtag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xtag
))
411 cmp
= *(cmap
= &a_colour_g
.cg_maps
[ct
][cmip
->cmi_ctx
][cmip
->cmi_id
]);
419 if((xctag
= cmp
->cm_tag
) == ctag
)
421 if(ctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(ctag
) &&
422 xctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xctag
) &&
423 !strcmp(xctag
, ctag
))
430 *cmap
= cmp
->cm_next
;
432 lcmp
->cm_next
= cmp
->cm_next
;
433 a_colour_map_unref(cmp
);
437 if(aster
&& ++ct
!= a_COLOUR_T_NONE
)
445 a_colour__show(enum a_colour_type ct
){
446 struct a_colour_map
*cmp
;
451 /* Show all possible types? */
452 if((rv
= (ct
== (enum a_colour_type
)-1 ? TRU1
: FAL0
)))
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
)
461 char const *tagann
, *tag
;
464 if((tag
= cmp
->cm_tag
) == NULL
)
466 else if(tag
== n_COLOUR_TAG_SUM_DOT
)
468 else if(tag
== n_COLOUR_TAG_SUM_OLDER
)
471 else if(cmp
->cm_regex
!= NULL
)
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
));
484 if(rv
&& ++ct
!= a_COLOUR_T_NONE
)
492 a_colour__tag_identify(struct a_colour_map_id
const *cmip
, char const *ctag
,
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
){
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 */
508 if(n_is_maybe_regex(ctag
)){
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
));
522 /* Normalize to lowercase and strip any whitespace before use */
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
!= ',')
531 /* Since we compare header names as they come from the message this
532 * lowercasing is however redundant: we need to asccasecmp() them */
534 cp
[i
++] = lowerconv(c
);
541 ctag
= n_COLOUR_TAG_ERR
;
546 static struct a_colour_map_id
const *
547 a_colour_map_id_find(char const *cp
){
549 struct a_colour_map_id
const (*cmip
)[n__COLOUR_IDS
], *rv
;
555 if(i
== n__COLOUR_IDS
)
558 size_t j
= strlen(a_colour_ctx_prefixes
[i
]);
559 if(!ascncasecmp(cp
, a_colour_ctx_prefixes
[i
], j
)){
565 cmip
= &a_colour_map_ids
[i
];
568 if(i
== n__COLOUR_IDS
|| (rv
= &(*cmip
)[i
])->cmi_name
[0] == '\0'){
572 if(!asccasecmp(cp
, rv
->cmi_name
))
580 static struct a_colour_map
*
581 a_colour_map_find(enum n_colour_id cid
, enum n_colour_ctx cctx
,
583 struct a_colour_map
*cmp
;
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
;
594 if(ctag
== NULL
|| a_COLOUR_TAG_IS_SPECIAL(ctag
))
597 if(cmp
->cm_regex
!= NULL
){
598 if(regexec(cmp
->cm_regex
, ctag
, 0,NULL
, 0) != REG_NOMATCH
)
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
))
618 a_colour_map_unref(struct a_colour_map
*self
){
620 if(--self
->cm_refcnt
== 0){
622 if(self
->cm_regex
!= NULL
){
623 regfree(self
->cm_regex
);
624 n_free(self
->cm_regex
);
633 a_colour_iso6429(enum a_colour_type ct
, char **store
, char const *spec
){
638 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
640 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
641 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
643 char *xspec
, *cp
, fg
[3], cfg
[2 + 2*sizeof("255")];
644 ui8_t ftno_base
, ftno
;
649 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
652 /* Since we use autorec_alloc(), reuse the n_strsep() buffer also for the
653 * return value, ensure we have enough room for that */
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
);
662 /* Iterate over the colour spec */
664 while((cp
= n_strsep(&xspec
, ',', TRU1
)) != NULL
){
665 char *y
, *x
= strchr(cp
, '=');
668 *store
= n_UNCONST(_("invalid attribute list"));
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"));
682 }else if(!asccasecmp(x
, idp
->id_name
)){
683 if(ftno
< n_NELEM(fg
))
684 fg
[ftno
++] = idp
->id_modc
;
686 *store
= n_UNCONST(_("too many font attributes"));
691 }else if(!asccasecmp(cp
, "fg")){
694 }else if(!asccasecmp(cp
, "bg")){
697 if(ct
== a_COLOUR_T_1
){
698 *store
= n_UNCONST(_("colours are not allowed"));
701 /* Maybe 256 color spec */
705 if(ct
== a_COLOUR_T_8
){
706 *store
= n_UNCONST(_("invalid colour for 8-colour mode"));
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"));
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"));
723 }else if(!asccasecmp(x
, idp
->id_name
)){
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 */
739 for(ftno_base
= ftno
; ftno
> 0;){
740 if(ftno
-- != ftno_base
)
753 memcpy(xspec
+ 1, "8;5;", 4);
755 for(ftno
= 2; cfg
[ftno
] != '\0'; ++ftno
)
756 *xspec
++ = cfg
[ftno
];
761 if(ftno_base
> 0 || cfg
[0])
768 memcpy(xspec
+ 1, "8;5;", 4);
770 for(ftno
= 2 + sizeof("255"); cfg
[ftno
] != '\0'; ++ftno
)
771 *xspec
++ = cfg
[ftno
];
778 *store
= n_UNCONST(spec
);
790 rv
= !a_colour_mux(v
);
800 rv
= !a_colour_unmux(v
);
806 n_colour_stack_del(struct n_go_data_ctx
*gdcp
){
807 struct n_colour_env
*vp
, *cep
;
810 vp
= gdcp
->gdc_colour
;
811 gdcp
->gdc_colour
= NULL
;
812 gdcp
->gdc_colour_active
= FAL0
;
814 while((cep
= vp
) != NULL
){
817 if(cep
->ce_current
!= NULL
&& cep
->ce_outfp
== n_stdout
){
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,
823 fflush(cep
->ce_outfp
);
824 n_signal(SIGPIPE
, hdl
);
831 n_colour_env_create(enum n_colour_ctx cctx
, FILE *fp
, bool_t pager_used
){
832 struct n_colour_env
*cep
;
835 if(!(n_psonce
& n_PSO_INTERACTIVE
))
838 if(!a_colour_g
.cg_is_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
;
846 cep
->ce_ispipe
= pager_used
;
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
)))
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
;
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;
867 if(n_poption
& n_PO_D_V
)
868 n_err(_("Ignoring unsupported termcap entry for Co(lors)\n"));
871 a_colour_g
.cg_type
= a_COLOUR_T_NONE
;
876 if(a_colour_g
.cg_type
== a_COLOUR_T_NONE
)
879 n_go_data
->gdc_colour_active
= cep
->ce_enabled
= TRU1
;
885 n_colour_env_gut(void){
886 struct n_colour_env
*cep
;
889 if(!(n_psonce
& n_PSO_INTERACTIVE
))
892 /* TODO v15: Could happen because of jump, causing _stack_del().. */
893 if((cep
= n_go_data
->gdc_colour
) == NULL
)
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
){
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,
904 n_signal(SIGPIPE
, hdl
);
911 n_colour_put(enum n_colour_id cid
, char const *ctag
){
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,
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
);
930 n_colour_reset(void){
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,
946 FL
struct str
const *
947 n_colour_reset_to_str(void){
951 if(n_COLOUR_IS_ACTIVE())
952 rv
= &a_colour_g
.cg_reset
.cp_dat
;
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
;
965 if(n_COLOUR_IS_ACTIVE() &&
966 (cmp
= a_colour_map_find(cid
, n_go_data
->gdc_colour
->ce_ctx
, ctag
)
968 union {void *vp
; char *cp
; struct n_colour_pen
*cpp
;} u
;
979 n_colour_pen_put(struct n_colour_pen
*self
){
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
;
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
,
994 fwrite(self
->cp_dat
.s
, self
->cp_dat
.l
, 1, cep
->ce_outfp
);
995 cep
->ce_current
= u
.cmp
;
1001 FL
struct str
const *
1002 n_colour_pen_to_str(struct n_colour_pen
*self
){
1006 if(n_COLOUR_IS_ACTIVE() && self
!= NULL
)
1013 #endif /* HAVE_COLOUR */