1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?colour' commands, and anything working with it.
4 * Copyright (c) 2014 - 2016 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.
21 #ifndef HAVE_AMALGAMATION
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))
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
{
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 */
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");
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
;
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)];
81 ui8_t cg_type
; /* a_colour_type */
82 /* TODO cg_has_env not used, we have to go for PS_COLOUR_ACTIVE */
84 ui8_t cg_ctx
; /* If .cg_has_env, enum n_colour_ctx */
86 struct a_colour_map
*cg_active
; /* The currently active colour */
87 struct n_colour_pen cg_reset
; /* The reset sequence */
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 */
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? */
111 /* C99: use [INDEX]={} */
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"},
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"},
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
,
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
,
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 */
197 a_colour_atexit(void){
199 if(a_colour_env
!= NULL
)
200 n_colour_env_pop(TRU1
);
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
;
213 ctmp
= a_colour_type_maps
;
214 do if(!asccasecmp(ctmp
->ctm_name
, name
)){
217 }while(PTRCMP(++ctmp
, !=, a_colour_type_maps
+ n_NELEM(a_colour_type_maps
)));
219 rv
= (enum a_colour_type
)-1;
226 a_colour_mux(char **argv
){
228 char const *mapname
, *ctag
;
229 struct a_colour_map
**cmap
, *blcmp
, *lcmp
, *cmp
;
230 struct a_colour_map_id
const *cmip
;
232 enum a_colour_type ct
;
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
));
243 if(a_colour_g
== NULL
)
247 rv
= a_colour__show(ct
);
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
));
261 n_err(_("`colour': %s: missing attribute argument\n"),
262 n_shexp_quote_cp(mapname
, FAL0
));
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'){
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
));
276 }else if((xtag
= a_colour__tag_identify(cmip
, ctag
, ®exp
)) ==
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
));
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
,
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
;
295 (ctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(ctag
) &&
296 xctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xctag
) &&
297 !strcmp(xctag
, ctag
))){
299 *cmap
= cmp
->cm_next
;
301 lcmp
->cm_next
= cmp
->cm_next
;
302 a_colour_map_unref(cmp
);
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
));
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);
324 cmp
->cm_pen
.cp_dat
.s
= bp
= cmp
->cm_buf
;
325 cmp
->cm_pen
.cp_dat
.l
= cl
;
326 memcpy(bp
, cp
, ++cl
);
329 cmp
->cm_user_off
= (ui32_t
)PTR2SIZE(bp
- cmp
->cm_buf
);
330 memcpy(bp
, argv
[1], ++ul
);
335 memcpy(bp
, ctag
, ++tl
);
340 /* Non-buf stuff; default mapping */
342 /* Default mappings must be last */
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
)
350 cmp
->cm_next
= lcmp
->cm_next
;
354 cmp
->cm_next
= *cmap
;
359 cmp
->cm_regex
= regexp
;
362 a_colour_map_ref(cmp
);
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
;
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
));
394 ctag
= (mapname
!= NULL
) ? argv
[1] : mapname
;
396 if(a_colour_g
== NULL
)
399 /* Delete anything? */
401 if(ctag
== NULL
&& mapname
[0] == '*' && mapname
[1] == '\0'){
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
;
411 a_colour_map_unref(tmp
);
414 if(a_colour_g
== NULL
){
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
)));
424 if((cmip
= a_colour_map_id_find(mapname
)) == NULL
){
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
));
435 }else if((xtag
= a_colour__tag_identify(cmip
, ctag
, NULL
)) ==
437 n_err(_("`uncolour': %s: invalid precondition: %s\n"),
438 n_shexp_quote_cp(mapname
, FAL0
), n_shexp_quote_cp(ctag
, FAL0
));
442 /* (Improve user experience) */
443 if(xtag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xtag
))
448 cmp
= *(cmap
= &a_colour_g
->cg_maps
[ct
][cmip
->cmi_ctx
][cmip
->cmi_id
]);
456 if((xctag
= cmp
->cm_tag
) == ctag
)
458 if(ctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(ctag
) &&
459 xctag
!= NULL
&& !a_COLOUR_TAG_IS_SPECIAL(xctag
) &&
460 !strcmp(xctag
, ctag
))
467 *cmap
= cmp
->cm_next
;
469 lcmp
->cm_next
= cmp
->cm_next
;
470 a_colour_map_unref(cmp
);
474 if(aster
&& ++ct
!= a_COLOUR_T_NONE
)
482 a_colour__show(enum a_colour_type ct
){
483 struct a_colour_map
*cmp
;
488 /* Show all possible types? */
489 if((rv
= (ct
== (enum a_colour_type
)-1 ? TRU1
: FAL0
)))
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
)
498 char const *tagann
, *tag
;
501 if((tag
= cmp
->cm_tag
) == NULL
)
503 else if(tag
== n_COLOUR_TAG_SUM_DOT
)
505 else if(tag
== n_COLOUR_TAG_SUM_OLDER
)
508 else if(cmp
->cm_regex
!= NULL
)
511 printf("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
));
521 if(rv
&& ++ct
!= a_COLOUR_T_NONE
)
529 a_colour__tag_identify(struct a_colour_map_id
const *cmip
, char const *ctag
,
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
){
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 */
545 if(is_maybe_regex(ctag
)){
546 if(regexpp
!= NULL
&& regcomp(*regexpp
= smalloc(sizeof(regex_t
)),
547 ctag
, REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)){
554 /* Normalize to lowercase and strip any whitespace before use */
558 for(i
= 0; (c
= *ctag
++) != '\0';){
559 bool_t isblspc
= blankspacechar(c
);
561 if(!isblspc
&& !alnumchar(c
) && c
!= '-' && c
!= ',')
563 /* Since we compare header names as they come from the message this
564 * lowercasing is however redundant: we need to asccasecmp() them */
566 cp
[i
++] = lowerconv(c
);
573 ctag
= n_COLOUR_TAG_ERR
;
578 static struct a_colour_map_id
const *
579 a_colour_map_id_find(char const *cp
){
581 struct a_colour_map_id
const (*cmip
)[n__COLOUR_IDS
], *rv
;
587 if(i
== n__COLOUR_IDS
)
590 size_t j
= strlen(a_colour_ctx_prefixes
[i
]);
591 if(!ascncasecmp(cp
, a_colour_ctx_prefixes
[i
], j
)){
597 cmip
= &a_colour_map_ids
[i
];
600 if(i
== n__COLOUR_IDS
|| (rv
= &(*cmip
)[i
])->cmi_name
[0] == '\0'){
604 if(!asccasecmp(cp
, rv
->cmi_name
))
612 static struct a_colour_map
*
613 a_colour_map_find(enum n_colour_id cid
, char const *ctag
){
614 struct a_colour_map
*cmp
;
617 cmp
= a_colour_g
->cg_maps
[a_colour_g
->cg_type
][a_colour_g
->cg_ctx
][cid
];
618 for(; cmp
!= NULL
; cmp
= cmp
->cm_next
){
619 char const *xtag
= cmp
->cm_tag
;
625 if(ctag
== NULL
|| a_COLOUR_TAG_IS_SPECIAL(ctag
))
628 if(cmp
->cm_regex
!= NULL
){
629 if(regexec(cmp
->cm_regex
, ctag
, 0,NULL
, 0) != REG_NOMATCH
)
633 if(cmp
->cm_cmi
->cmi_tt
& a_COLOUR_TT_HEADERS
){
634 char *hlist
= savestr(xtag
), *cp
;
636 while((cp
= n_strsep(&hlist
, ',', TRU1
)) != NULL
){
637 if(!asccasecmp(cp
, ctag
))
649 a_colour_map_unref(struct a_colour_map
*self
){
651 if(--self
->cm_refcnt
== 0){
653 if(self
->cm_regex
!= NULL
){
654 regfree(self
->cm_regex
);
655 free(self
->cm_regex
);
664 a_colour_iso6429(enum a_colour_type ct
, char **store
, char const *spec
){
669 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
671 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
672 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
674 char *xspec
, *cp
, fg
[3], cfg
[2 + 2*sizeof("255")];
675 ui8_t ftno_base
, ftno
;
680 /* 0/1 indicate usage, thereafter possibly 256 color sequences */
683 /* Since we use salloc(), reuse the n_strsep() buffer also for the return
684 * value, ensure we have enough room for that */
686 size_t i
= strlen(spec
) +1;
687 xspec
= salloc(n_MAX(i
, sizeof("\033[1;4;7;38;5;255;48;5;255m")));
688 memcpy(xspec
, spec
, i
);
692 /* Iterate over the colour spec */
694 while((cp
= n_strsep(&xspec
, ',', TRU1
)) != NULL
){
695 char *y
, *x
= strchr(cp
, '=');
698 *store
= n_UNCONST(_("invalid attribute list"));
703 if(!asccasecmp(cp
, "ft")){
704 if(!asccasecmp(x
, "inverse")){
705 OBSOLETE(_("please use reverse for ft= fonts, not inverse"));
706 x
= n_UNCONST("reverse");
708 for(idp
= fta
;; ++idp
)
709 if(idp
== fta
+ n_NELEM(fta
)){
710 *store
= n_UNCONST(_("invalid font attribute"));
712 }else if(!asccasecmp(x
, idp
->id_name
)){
713 if(ftno
< n_NELEM(fg
))
714 fg
[ftno
++] = idp
->id_modc
;
716 *store
= n_UNCONST(_("too many font attributes"));
721 }else if(!asccasecmp(cp
, "fg")){
724 }else if(!asccasecmp(cp
, "bg")){
727 if(ct
== a_COLOUR_T_1
){
728 *store
= n_UNCONST(_("colours are not allowed"));
731 /* Maybe 256 color spec */
735 if(ct
== a_COLOUR_T_8
){
736 *store
= n_UNCONST(_("invalid colour for 8-colour mode"));
740 xv
= strtol(x
, &cp
, 10);
741 if(xv
< 0 || xv
> 255 || *cp
!= '\0' || PTRCMP(&x
[3], <, cp
)){
742 *store
= n_UNCONST(_("invalid 256-colour specification"));
746 memcpy((y
== &cfg
[0] ? y
+ 2 : y
+ 1 + sizeof("255")), x
,
747 (x
[1] == '\0' ? 2 : (x
[2] == '\0' ? 3 : 4)));
748 }else for(idp
= ca
;; ++idp
)
749 if(idp
== ca
+ n_NELEM(ca
)){
750 *store
= n_UNCONST(_("invalid colour attribute"));
752 }else if(!asccasecmp(x
, idp
->id_name
)){
761 /* Restore our salloc() buffer, create return value */
762 xspec
= n_UNCONST(spec
);
763 if(ftno
> 0 || cfg
[0] || cfg
[1]){ /* TODO unite/share colour setters */
768 for(ftno_base
= ftno
; ftno
> 0;){
769 if(ftno
-- != ftno_base
)
782 memcpy(xspec
+ 1, "8;5;", 4);
784 for(ftno
= 2; cfg
[ftno
] != '\0'; ++ftno
)
785 *xspec
++ = cfg
[ftno
];
790 if(ftno_base
> 0 || cfg
[0])
797 memcpy(xspec
+ 1, "8;5;", 4);
799 for(ftno
= 2 + sizeof("255"); cfg
[ftno
] != '\0'; ++ftno
)
800 *xspec
++ = cfg
[ftno
];
807 *store
= n_UNCONST(spec
);
819 rv
= !a_colour_mux(v
);
829 rv
= !a_colour_unmux(v
);
835 n_colour_env_create(enum n_colour_ctx cctx
, bool_t pager_used
){
837 if(!(options
& OPT_INTERACTIVE
))
840 if (ok_blook(colour_disable
) || (pager_used
&& !ok_blook(colour_pager
))){
841 n_colour_env_push(); /* FIXME lex.c only pops (always env */
845 if(n_UNLIKELY(a_colour_g
== NULL
))
848 if(n_UNLIKELY(a_colour_g
->cg_type
== a_COLOUR_T_UNKNOWN
)){
849 struct n_termcap_value tv
;
851 if(!n_termcap_query(n_TERMCAP_QUERY_colors
, &tv
)){
852 a_colour_g
->cg_type
= a_COLOUR_T_NONE
;
855 switch(tv
.tv_data
.tvd_numeric
){
856 case 256: a_colour_g
->cg_type
= a_COLOUR_T_256
; break;
857 case 8: a_colour_g
->cg_type
= a_COLOUR_T_8
; break;
858 case 1: a_colour_g
->cg_type
= a_COLOUR_T_1
; break;
860 if(options
& OPT_D_V
)
861 n_err(_("Ignoring unsupported termcap entry for Co(lors)\n"));
864 a_colour_g
->cg_type
= a_COLOUR_T_NONE
;
869 if(a_colour_g
->cg_type
== a_COLOUR_T_NONE
)
872 a_colour_g
->cg_ctx
= cctx
;
873 a_colour_g
->cg_active
= NULL
;
874 pstate
|= PS_COLOUR_ACTIVE
;
880 n_colour_env_push(void){
881 struct a_colour_env
*cep
;
884 if(!(options
& OPT_INTERACTIVE
))
887 cep
= smalloc(sizeof *cep
);
888 cep
->ce_last
= a_colour_env
;
889 if(a_colour_g
!= NULL
){
890 cep
->ce_ctx
= a_colour_g
->cg_ctx
;
891 a_colour_g
->cg_active
= NULL
;
893 cep
->ce_is_active
= ((pstate
& PS_COLOUR_ACTIVE
) != 0);
896 pstate
&= ~PS_COLOUR_ACTIVE
;
902 n_colour_env_pop(bool_t any_env_till_root
){
904 if(!(options
& OPT_INTERACTIVE
))
907 while(a_colour_env
!= NULL
){
908 struct a_colour_env
*cep
;
910 if((cep
= a_colour_env
)->ce_is_active
)
911 pstate
|= PS_COLOUR_ACTIVE
;
913 pstate
&= ~PS_COLOUR_ACTIVE
;
915 if(a_colour_g
!= NULL
){
916 a_colour_g
->cg_active
= NULL
;
917 a_colour_g
->cg_ctx
= cep
->ce_ctx
;
919 a_colour_env
= cep
->ce_last
;
922 if(!any_env_till_root
)
926 if(any_env_till_root
&& a_colour_g
!= NULL
&& (pstate
& PS_COLOUR_ACTIVE
)){
927 pstate
&= ~PS_COLOUR_ACTIVE
;
928 a_colour_g
->cg_active
= NULL
;
935 n_colour_env_gut(FILE *fp
){
937 if((options
& OPT_INTERACTIVE
) && (pstate
& PS_COLOUR_ACTIVE
)){
938 pstate
&= ~PS_COLOUR_ACTIVE
;
940 if(a_colour_g
->cg_active
!= NULL
){
941 a_colour_g
->cg_active
= NULL
;
943 fwrite(a_colour_g
->cg_reset
.cp_dat
.s
, a_colour_g
->cg_reset
.cp_dat
.l
,
951 n_colour_put(FILE *fp
, enum n_colour_id cid
, char const *ctag
){
953 if(pstate
& PS_COLOUR_ACTIVE
){
954 if(a_colour_g
->cg_active
!= NULL
)
955 fwrite(a_colour_g
->cg_reset
.cp_dat
.s
, a_colour_g
->cg_reset
.cp_dat
.l
, 1,
957 if((a_colour_g
->cg_active
= a_colour_map_find(cid
, ctag
)) != NULL
)
958 fwrite(a_colour_g
->cg_active
->cm_pen
.cp_dat
.s
,
959 a_colour_g
->cg_active
->cm_pen
.cp_dat
.l
, 1, fp
);
965 n_colour_reset(FILE *fp
){
967 if((pstate
& PS_COLOUR_ACTIVE
) && a_colour_g
->cg_active
!= NULL
){
968 a_colour_g
->cg_active
= NULL
;
969 fwrite(a_colour_g
->cg_reset
.cp_dat
.s
, a_colour_g
->cg_reset
.cp_dat
.l
, 1,
975 FL
struct str
const *
976 n_colour_reset_to_str(void){
980 if(pstate
& PS_COLOUR_ACTIVE
)
981 rv
= &a_colour_g
->cg_reset
.cp_dat
;
988 FL
struct n_colour_pen
*
989 n_colour_pen_create(enum n_colour_id cid
, char const *ctag
){
990 struct a_colour_map
*cmp
;
991 struct n_colour_pen
*rv
;
994 if((pstate
& PS_COLOUR_ACTIVE
) &&
995 (cmp
= a_colour_map_find(cid
, ctag
)) != NULL
){
996 union {void *vp
; char *cp
; struct n_colour_pen
*cpp
;} u
;
1006 n_colour_pen_put(struct n_colour_pen
*self
, FILE *fp
){
1008 if(pstate
& PS_COLOUR_ACTIVE
){
1009 union {void *vp
; char *cp
; struct a_colour_map
*cmp
;} u
;
1012 if(u
.cmp
!= a_colour_g
->cg_active
){
1013 if(a_colour_g
->cg_active
!= NULL
)
1014 fwrite(a_colour_g
->cg_reset
.cp_dat
.s
, a_colour_g
->cg_reset
.cp_dat
.l
,
1017 fwrite(self
->cp_dat
.s
, self
->cp_dat
.l
, 1, fp
);
1018 a_colour_g
->cg_active
= u
.cmp
;
1024 FL
struct str
const *
1025 n_colour_pen_to_str(struct n_colour_pen
*self
){
1029 if((pstate
& PS_COLOUR_ACTIVE
) && self
!= NULL
)
1036 #endif /* HAVE_COLOUR */