Introduce coloured `headers' summary display, plus FIXES..
[s-mailx.git] / colour.c
blobad73273fe11a739e74c5d144edc1fcd3e52b538d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `colour' and `mono' 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 CTA(n_COLOUR_ID_RESET > _n_COLOUR_ID_VIEW_USER_HEADERS);
29 CTA(n_COLOUR_ID_RESET > n_COLOUR_ID_HSUM_THREAD);
31 /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence */
32 static char * _colour_iso6429(char const *wish);
34 static char *
35 _colour_iso6429(char const *wish)
37 struct isodesc {
38 char id_name[15];
39 char id_modc;
40 } const fta[] = {
41 {"bold", '1'}, {"underline", '4'}, {"reverse", '7'}
42 }, ca[] = {
43 {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'},
44 {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'}
45 }, *idp;
46 char const * const wish_orig = wish;
47 char *xwish, *cp, fg[3], cfg[3] = {0, 0, 0};
48 ui8_t ftno_base, ftno;
49 NYD_ENTER;
51 /* Since we use salloc(), reuse the n_strsep() buffer also for the return
52 * value, ensure we have enough room for that */
54 size_t i = strlen(wish) +1;
55 xwish = salloc(MAX(i, sizeof("\033[1;4;7;30;40m")));
56 memcpy(xwish, wish, i);
57 wish = xwish;
60 /* Iterate over the colour spec */
61 ftno = 0;
62 while ((cp = n_strsep(&xwish, ',', TRU1)) != NULL) {
63 char *y, *x = strchr(cp, '=');
64 if (x == NULL) {
65 jbail:
66 n_err(_("Invalid colour specification \"%s\": %s\n"), wish_orig, cp);
67 continue;
69 *x++ = '\0';
71 if (!asccasecmp(cp, "ft")) {
72 if (!asccasecmp(x, "inverse")) {
73 OBSOLETE(_("please use \"reverse\" not \"inverse\" for ft= fonts"));
74 x = UNCONST("reverse");
76 for (idp = fta;; ++idp)
77 if (idp == fta + NELEM(fta))
78 goto jbail;
79 else if (!asccasecmp(x, idp->id_name)) {
80 if (ftno < NELEM(fg))
81 fg[ftno++] = idp->id_modc;
82 else
83 goto jbail;
84 break;
86 } else if (!asccasecmp(cp, "fg")) {
87 y = cfg + 1;
88 goto jiter_colour;
89 } else if (!asccasecmp(cp, "bg")) {
90 y = cfg + 2;
91 jiter_colour:
92 for (idp = ca;; ++idp)
93 if (idp == ca + NELEM(ca))
94 goto jbail;
95 else if (!asccasecmp(x, idp->id_name)) {
96 *y = idp->id_modc;
97 break;
99 } else
100 goto jbail;
103 /* Restore our salloc() buffer, create return value */
104 xwish = UNCONST(wish);
105 if (ftno > 0 || cfg[1] || cfg[2]) {
106 xwish[0] = '\033';
107 xwish[1] = '[';
108 xwish += 2;
110 for (ftno_base = ftno; ftno > 0;) {
111 if (ftno-- != ftno_base)
112 *xwish++ = ';';
113 *xwish++ = fg[ftno];
116 if (cfg[1]) {
117 if (ftno_base > 0)
118 *xwish++ = ';';
119 xwish[0] = '3';
120 xwish[1] = cfg[1];
121 xwish += 2;
124 if (cfg[2]) {
125 if (ftno_base > 0 || cfg[1])
126 *xwish++ = ';';
127 xwish[0] = '4';
128 xwish[1] = cfg[2];
129 xwish += 2;
132 *xwish++ = 'm';
134 *xwish = '\0';
135 NYD_LEAVE;
136 return UNCONST(wish);
139 FL void
140 n_colour_table_create(bool_t pager_used, bool_t headerview)
142 union {char *cp; char const *ccp; void *vp; struct n_colour_table *ctp;} u;
143 size_t i;
144 struct n_colour_table *ct;
145 NYD_ENTER;
147 if (ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager)))
148 goto jleave;
149 else {
150 char *term, *okterms;
152 if ((term = env_vlook("TERM", FAL0)) == NULL)
153 goto jleave;
154 /* terminfo rocks: if we find "color", assume it's right */
155 if (strstr(term, "color") != NULL)
156 goto jok;
157 if ((okterms = ok_vlook(colour_terms)) == NULL)
158 okterms = UNCONST(n_COLOUR_TERMS);
159 okterms = savestr(okterms);
161 i = strlen(term);
162 while ((u.cp = n_strsep(&okterms, ',', TRU1)) != NULL)
163 if (!strncmp(u.cp, term, i))
164 goto jok;
165 goto jleave;
168 jok:
169 n_colour_table = ct = salloc(sizeof *ct); /* XXX lex.c yet resets (FILTER!) */
170 { static struct {
171 enum okeys okey;
172 enum n_colour_id cid;
173 char const *defval;
174 } const
175 /* Header summary set */
176 hsum_map[] = {
177 {ok_v_colour_hsum_current,
178 n_COLOUR_ID_HSUM_CURRENT, n_COLOUR_HSUM_CURRENT},
179 {ok_v_colour_hsum_dot,
180 n_COLOUR_ID_HSUM_DOT, n_COLOUR_HSUM_DOT},
181 {ok_v_colour_hsum_dot_mark,
182 n_COLOUR_ID_HSUM_DOT_MARK, n_COLOUR_HSUM_DOT_MARK},
183 {ok_v_colour_hsum_dot_thread,
184 n_COLOUR_ID_HSUM_DOT_THREAD, n_COLOUR_HSUM_DOT_THREAD},
185 {ok_v_colour_hsum_older,
186 n_COLOUR_ID_HSUM_OLDER, n_COLOUR_HSUM_OLDER},
187 {ok_v_colour_hsum_thread,
188 n_COLOUR_ID_HSUM_THREAD, n_COLOUR_HSUM_THREAD}
189 }, view_map[] = {
190 {ok_v_colour_view_msginfo,
191 n_COLOUR_ID_VIEW_MSGINFO, n_COLOUR_VIEW_MSGINFO},
192 {ok_v_colour_view_partinfo,
193 n_COLOUR_ID_VIEW_PARTINFO, n_COLOUR_VIEW_PARTINFO},
194 {ok_v_colour_view_from_,
195 n_COLOUR_ID_VIEW_FROM_, n_COLOUR_VIEW_FROM_},
196 {ok_v_colour_view_header,
197 n_COLOUR_ID_VIEW_HEADER, n_COLOUR_VIEW_HEADER},
198 {ok_v_colour_view_uheader,
199 n_COLOUR_ID_VIEW_UHEADER, n_COLOUR_VIEW_UHEADER},
200 {ok_v_colour_view_user_headers,
201 _n_COLOUR_ID_VIEW_USER_HEADERS, n_COLOUR_VIEW_USER_HEADERS}
202 }, oview_map[] = { /* TODO Message display set, !*v15-compat* */
203 {ok_v_colour_msginfo,
204 n_COLOUR_ID_VIEW_MSGINFO, n_COLOUR_VIEW_MSGINFO},
205 {ok_v_colour_partinfo,
206 n_COLOUR_ID_VIEW_PARTINFO, n_COLOUR_VIEW_PARTINFO},
207 {ok_v_colour_from_,
208 n_COLOUR_ID_VIEW_FROM_, n_COLOUR_VIEW_FROM_},
209 {ok_v_colour_header,
210 n_COLOUR_ID_VIEW_HEADER, n_COLOUR_VIEW_HEADER},
211 {ok_v_colour_uheader,
212 n_COLOUR_ID_VIEW_UHEADER, n_COLOUR_VIEW_UHEADER},
213 {ok_v_colour_user_headers,
214 _n_COLOUR_ID_VIEW_USER_HEADERS, n_COLOUR_VIEW_USER_HEADERS}
215 }, * map;
216 size_t nelem;
217 enum okeys v_nocolor; /* *-user-headers* is a string list */
218 bool_t v15noted; /* TODO v15-compat */
220 if (headerview) {
221 map = hsum_map;
222 nelem = NELEM(hsum_map);
223 v15noted = TRU1;
224 } else if (ok_blook(v15_compat)) {
225 map = view_map;
226 nelem = NELEM(view_map);
227 v_nocolor = ok_v_colour_view_user_headers;
228 v15noted = TRU1;
229 } else {
230 map = oview_map;
231 nelem = NELEM(oview_map);
232 v_nocolor = ok_v_colour_user_headers;
233 v15noted = FAL0;
236 for (i = 0; i < nelem; ++i) {
237 if ((u.cp = _var_oklook(map[i].okey)) == NULL)
238 u.ccp = map[i].defval;
239 else if (!v15noted) {
240 OBSOLETE(_("please use *colour-view-XY* instead of *colour-XY*"));
241 v15noted = TRU1;
243 if (u.ccp[0] != '\0') {
244 if (headerview || map[i].okey != v_nocolor)
245 u.cp = _colour_iso6429(u.ccp);
246 else
247 u.cp = savestr(u.cp);
248 if ((ct->ct_csinfo[map[i].cid].l = strlen(u.cp)) == 0)
249 u.cp = NULL;
250 ct->ct_csinfo[map[i].cid].s = u.cp;
251 } else {
252 ct->ct_csinfo[map[i].cid].l = 0;
253 ct->ct_csinfo[map[i].cid].s = NULL;
258 /* XXX using [0m is hard, we should selectively turn off what is on */
259 ct->ct_csinfo[n_COLOUR_ID_RESET].l = sizeof("\033[0m") -1;
260 ct->ct_csinfo[n_COLOUR_ID_RESET].s = UNCONST("\033[0m");
261 jleave:
262 NYD_LEAVE;
265 FL void
266 n_colour_put(FILE *fp, enum n_colour_id cid)
268 NYD_ENTER;
269 if (n_colour_table != NULL) {
270 struct str const *cp = n_colour_get(cid);
272 if (cp != NULL)
273 fwrite(cp->s, cp->l, 1, fp);
275 NYD_LEAVE;
278 FL void
279 n_colour_put_user_header(FILE *fp, char const *name)
281 enum n_colour_id cid = n_COLOUR_ID_VIEW_HEADER;
282 struct str const *uheads;
283 char *cp, *cp_base, *x;
284 size_t namelen;
285 NYD_ENTER;
287 if (n_colour_table == NULL)
288 goto j_leave;
290 /* Normal header colours if there are no user headers */
291 uheads = n_colour_table->ct_csinfo + _n_COLOUR_ID_VIEW_USER_HEADERS;
292 if (uheads->s == NULL)
293 goto jleave;
295 /* Iterate over all entries in the *colour-user-headers* list */
296 cp = ac_alloc(uheads->l +1);
297 memcpy(cp, uheads->s, uheads->l +1);
298 cp_base = cp;
299 namelen = strlen(name);
300 while ((x = n_strsep(&cp, ',', TRU1)) != NULL) {
301 size_t l = (cp != NULL) ? PTR2SIZE(cp - x) - 1 : strlen(x);
302 if (l == namelen && !ascncasecmp(x, name, namelen)) {
303 cid = n_COLOUR_ID_VIEW_UHEADER;
304 break;
307 ac_free(cp_base);
308 jleave:
309 n_colour_put(fp, cid);
310 j_leave:
311 NYD_LEAVE;
314 FL void
315 n_colour_reset(FILE *fp)
317 NYD_ENTER;
318 if (n_colour_table != NULL)
319 fwrite("\033[0m", 4, 1, fp);
320 NYD_LEAVE;
323 FL struct str const *
324 n_colour_get(enum n_colour_id cid)
326 struct str const *rv;
327 NYD_ENTER;
329 if (n_colour_table != NULL) {
330 if ((rv = n_colour_table->ct_csinfo + cid)->s == NULL)
331 rv = NULL;
332 assert(cid != n_COLOUR_ID_RESET || rv != NULL);
333 } else
334 rv = NULL;
335 NYD_LEAVE;
336 return rv;
338 #endif /* HAVE_COLOUR */
340 /* s-it-mode */