collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / cmd-headers.c
blob5b298b485e8272a1bbca26031827bd8a537743e9
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Header display, search, etc., related user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd_headers
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 static int _screen;
44 /* ... And place the extracted date in `date' */
45 static void _parse_from_(struct message *mp, char date[n_FROM_DATEBUF]);
47 /* Print out the header of a specific message
48 * a_cmd__hprf: handle *headline*
49 * a_cmd__subject: -1 if Subject: yet seen, otherwise smalloc()d Subject:
50 * a_cmd__putindent: print out the indenting in threaded display
51 * a_cmd__putuc: print out a Unicode character or a substitute for it, return
52 * 0 on error or wcwidth() (or 1) on success */
53 static void a_cmd_print_head(size_t yetprinted, size_t msgno, FILE *f,
54 bool_t threaded);
56 static void a_cmd__hprf(size_t yetprinted, char const *fmt, size_t msgno,
57 FILE *f, bool_t threaded, char const *attrlist);
58 static char *a_cmd__subject(struct message *mp, bool_t threaded,
59 size_t yetprinted);
60 static int a_cmd__putindent(FILE *fp, struct message *mp, int maxwidth);
61 static size_t a_cmd__putuc(int u, int c, FILE *fp);
62 static int a_cmd__dispc(struct message *mp, char const *a);
64 /* Shared `z' implementation */
65 static int a_cmd_scroll(char const *arg, bool_t onlynew);
67 /* Shared `headers' implementation */
68 static int _headers(int msgspec);
70 static void
71 _parse_from_(struct message *mp, char date[n_FROM_DATEBUF]) /* TODO line pool */
73 FILE *ibuf;
74 int hlen;
75 char *hline = NULL;
76 size_t hsize = 0;
77 NYD_ENTER;
79 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
80 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
81 extract_date_from_from_(hline, hlen, date);
82 if (hline != NULL)
83 free(hline);
84 NYD_LEAVE;
87 static void
88 a_cmd_print_head(size_t yetprinted, size_t msgno, FILE *f, bool_t threaded){
89 enum {attrlen = 14};
90 char attrlist[attrlen +1], *cp;
91 char const *fmt;
92 NYD2_ENTER;
94 if((cp = ok_vlook(attrlist)) != NULL){
95 if(strlen(cp) == attrlen){
96 memcpy(attrlist, cp, attrlen +1);
97 goto jattrok;
99 n_err(_("*attrlist* is not of the correct length, using built-in\n"));
102 if(ok_blook(bsdcompat) || ok_blook(bsdflags)){
103 char const bsdattr[attrlen +1] = "NU *HMFAT+-$~";
105 memcpy(attrlist, bsdattr, sizeof bsdattr);
106 }else if(ok_blook(SYSV3)){
107 char const bsdattr[attrlen +1] = "NU *HMFAT+-$~";
109 memcpy(attrlist, bsdattr, sizeof bsdattr);
110 n_OBSOLETE(_("*SYSV3*: please use *bsdcompat* or *bsdflags*, "
111 "or set *attrlist*"));
112 }else{
113 char const pattr[attrlen +1] = "NUROSPMFAT+-$~";
115 memcpy(attrlist, pattr, sizeof pattr);
118 jattrok:
119 if((fmt = ok_vlook(headline)) == NULL){
120 fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
121 ? "%>%a%m %-20f %16d %4l/%-5o %i%-S"
122 : "%>%a%m %-18f %-16d %4l/%-5o %i%-s");
125 a_cmd__hprf(yetprinted, fmt, msgno, f, threaded, attrlist);
126 NYD2_LEAVE;
129 static void
130 a_cmd__hprf(size_t yetprinted, char const *fmt, size_t msgno, FILE *f,
131 bool_t threaded, char const *attrlist)
133 char buf[16], datebuf[n_FROM_DATEBUF], cbuf[8], *cp, *subjline;
134 char const *datefmt, *date, *name, *fp n_COLOUR( COMMA *colo_tag );
135 int i, n, s, wleft, subjlen;
136 struct message *mp;
137 time_t datet;
138 n_COLOUR( struct n_colour_pen *cpen_new COMMA *cpen_cur COMMA *cpen_bas; )
139 enum {
140 _NONE = 0,
141 _ISDOT = 1<<0,
142 _ISADDR = 1<<1,
143 _ISTO = 1<<2,
144 _IFMT = 1<<3,
145 _LOOP_MASK = (1<<4) - 1,
146 _SFMT = 1<<4, /* It is 'S' */
147 /* For the simple byte-based counts in wleft and n we sometimes need
148 * adjustments to compensate for additional bytes of UTF-8 sequences */
149 _PUTCB_UTF8_SHIFT = 5,
150 _PUTCB_UTF8_MASK = 3<<5
151 } flags = _NONE;
152 NYD2_ENTER;
153 n_UNUSED(buf);
155 if ((mp = message + msgno - 1) == dot)
156 flags = _ISDOT;
157 datet = mp->m_time;
158 date = NULL;
159 n_COLOUR( colo_tag = NULL; )
161 datefmt = ok_vlook(datefield);
162 jredo:
163 if (datefmt != NULL) {
164 fp = hfield1("date", mp);/* TODO use m_date field! */
165 if (fp == NULL) {
166 datefmt = NULL;
167 goto jredo;
169 datet = rfctime(fp);
170 date = n_time_ctime(datet, NULL);
171 fp = ok_vlook(datefield_markout_older);
172 i = (*datefmt != '\0');
173 if (fp != NULL)
174 i |= (*fp != '\0') ? 2 | 4 : 2; /* XXX no magics */
176 /* May we strftime(3)? */
177 if (i & (1 | 4)){
178 /* This localtime(3) should not fail since rfctime(3).. but .. */
179 struct tm *tmp;
180 time_t datet2;
182 /* TODO the datetime stuff is horror: mails should be parsed into
183 * TODO an object tree, and date: etc. have a datetime object, which
184 * TODO verifies upon parse time; then ALL occurrences of datetime are
185 * TODO valid all through the program; and: to_wire, to_user! */
186 datet2 = datet;
187 jredo_localtime:
188 if((tmp = localtime(&datet2)) == NULL){
189 datet2 = 0;
190 goto jredo_localtime;
192 memcpy(&time_current.tc_local, tmp, sizeof(*tmp));
195 if ((i & 2) &&
196 (UICMP(64,datet, >, time_current.tc_time + n_DATE_SECSDAY) ||
197 #define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY)
198 UICMP(64,datet + _6M, <, time_current.tc_time))) {
199 #undef _6M
200 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
201 memset(datebuf, ' ', n_FROM_DATEBUF); /* xxx ur */
202 memcpy(datebuf + 4, date + 4, 7);
203 datebuf[4 + 7] = ' ';
204 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
205 datebuf[4 + 7 + 1 + 4] = '\0';
206 date = datebuf;
208 n_COLOUR( colo_tag = n_COLOUR_TAG_SUM_OLDER; )
209 } else if ((i & 1) == 0)
210 datefmt = NULL;
211 } else if (datet == (time_t)0 && !(mp->m_flag & MNOFROM)) {
212 /* TODO eliminate this path, query the FROM_ date in setptr(),
213 * TODO all other codepaths do so by themselves ALREADY ?????
214 * TODO assert(mp->m_time != 0);, then
215 * TODO ALSO changes behaviour of datefield_markout_older */
216 _parse_from_(mp, datebuf);
217 date = datebuf;
218 } else
219 date = n_time_ctime(datet, NULL);
221 flags |= _ISADDR;
222 name = name1(mp, 0);
223 if (name != NULL && ok_blook(showto) && n_is_myname(skin(name))) {
224 if ((cp = hfield1("to", mp)) != NULL) {
225 name = cp;
226 flags |= _ISTO;
229 if (name == NULL) {
230 name = n_empty;
231 flags &= ~_ISADDR;
233 if (flags & _ISADDR)
234 name = ok_blook(showname) ? realname(name) : prstr(skin(name));
236 subjline = NULL;
238 /* Detect the width of the non-format characters in *headline*;
239 * like that we can simply use putc() in the next loop, since we have
240 * already calculated their column widths (TODO it's sick) */
241 wleft = subjlen = n_scrnwidth;
243 for (fp = fmt; *fp != '\0'; ++fp) {
244 if (*fp == '%') {
245 if (*++fp == '-')
246 ++fp;
247 else if (*fp == '+')
248 ++fp;
249 if (digitchar(*fp)) {
250 n = 0;
252 n = 10*n + *fp - '0';
253 while (++fp, digitchar(*fp));
254 subjlen -= n;
256 if (*fp == 'i')
257 flags |= _IFMT;
259 if (*fp == '\0')
260 break;
261 } else {
262 #ifdef HAVE_WCWIDTH
263 if (n_mb_cur_max > 1) {
264 wchar_t wc;
265 if ((s = mbtowc(&wc, fp, n_mb_cur_max)) == -1)
266 n = s = 1;
267 else if ((n = wcwidth(wc)) == -1)
268 n = 1;
269 } else
270 #endif
271 n = s = 1;
272 subjlen -= n;
273 wleft -= n;
274 while (--s > 0)
275 ++fp;
279 /* Walk *headline*, producing output TODO not (really) MB safe */
280 #ifdef HAVE_COLOUR
281 if(n_COLOUR_IS_ACTIVE()){
282 if(flags & _ISDOT)
283 colo_tag = n_COLOUR_TAG_SUM_DOT;
284 cpen_bas = n_colour_pen_create(n_COLOUR_ID_SUM_HEADER, colo_tag);
285 n_colour_pen_put(cpen_new = cpen_cur = cpen_bas);
286 }else
287 cpen_new = cpen_bas = cpen_cur = NULL;
288 #endif
290 for (fp = fmt; *fp != '\0'; ++fp) {
291 char c;
293 if ((c = *fp & 0xFF) != '%') {
294 n_COLOUR(
295 if(n_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
296 n_colour_pen_put(cpen_cur = cpen_new);
298 putc(c, f);
299 continue;
302 flags &= _LOOP_MASK;
303 n = 0;
304 s = 1;
305 if ((c = *++fp) == '-') {
306 s = -1;
307 ++fp;
308 } else if (c == '+')
309 ++fp;
310 if (digitchar(*fp)) {
312 n = 10*n + *fp - '0';
313 while (++fp, digitchar(*fp));
316 if ((c = *fp & 0xFF) == '\0')
317 break;
318 n *= s;
320 cbuf[1] = '\0';
321 switch (c) {
322 case '%':
323 goto jputcb;
324 case '>':
325 case '<':
326 if (flags & _ISDOT) {
327 n_COLOUR(
328 if(n_COLOUR_IS_ACTIVE())
329 cpen_new = n_colour_pen_create(n_COLOUR_ID_SUM_DOTMARK,
330 colo_tag);
332 if((n_psonce & n_PSO_UNICODE) && !ok_blook(headline_plain)){
333 if (c == '>')
334 /* 25B8;BLACK RIGHT-POINTING SMALL TRIANGLE */
335 cbuf[1] = (char)0x96, cbuf[2] = (char)0xB8;
336 else
337 /* 25C2;BLACK LEFT-POINTING SMALL TRIANGLE */
338 cbuf[1] = (char)0x97, cbuf[2] = (char)0x82;
339 c = (char)0xE2;
340 cbuf[3] = '\0';
341 flags |= 2 << _PUTCB_UTF8_SHIFT;
343 } else
344 c = ' ';
345 goto jputcb;
346 case '$':
347 #ifdef HAVE_SPAM
348 if (n == 0)
349 n = 5;
350 if (UICMP(32, n_ABS(n), >, wleft))
351 n = (n < 0) ? -wleft : wleft;
352 snprintf(buf, sizeof buf, "%u.%02u",
353 (mp->m_spamscore >> 8), (mp->m_spamscore & 0xFF));
354 n = fprintf(f, "%*s", n, buf);
355 wleft = (n >= 0) ? wleft - n : 0;
356 break;
357 #else
358 c = '?';
359 goto jputcb;
360 #endif
361 case 'a':
362 c = a_cmd__dispc(mp, attrlist);
363 jputcb:
364 #ifdef HAVE_COLOUR
365 if(n_COLOUR_IS_ACTIVE()){
366 if(cpen_new == cpen_cur)
367 cpen_new = cpen_bas;
368 if(cpen_new != cpen_cur)
369 n_colour_pen_put(cpen_cur = cpen_new);
371 #endif
372 if (UICMP(32, n_ABS(n), >, wleft))
373 n = (n < 0) ? -wleft : wleft;
374 cbuf[0] = c;
375 n = fprintf(f, "%*s", n, cbuf);
376 if (n >= 0) {
377 wleft -= n;
378 if ((n = (flags & _PUTCB_UTF8_MASK)) != 0) {
379 n >>= _PUTCB_UTF8_SHIFT;
380 wleft += n;
382 } else {
383 wleft = 0; /* TODO I/O error.. ? break? */
385 #ifdef HAVE_COLOUR
386 if(n_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
387 n_colour_pen_put(cpen_cur = cpen_new);
388 #endif
389 break;
390 case 'd':
391 if (datefmt != NULL) {
392 i = strftime(datebuf, sizeof datebuf, datefmt,
393 &time_current.tc_local);
394 if (i != 0)
395 date = datebuf;
396 else
397 n_err(_("Ignoring date format, it is either empty or "
398 "excesses buffer size (%lu bytes)\n"),
399 (ul_i)sizeof(datebuf));
400 datefmt = NULL;
402 if (n == 0)
403 n = 16;
404 if (UICMP(32, n_ABS(n), >, wleft))
405 n = (n < 0) ? -wleft : wleft;
406 n = fprintf(f, "%*.*s", n, n_ABS(n), date);
407 wleft = (n >= 0) ? wleft - n : 0;
408 break;
409 case 'e':
410 if (n == 0)
411 n = 2;
412 if (UICMP(32, n_ABS(n), >, wleft))
413 n = (n < 0) ? -wleft : wleft;
414 n = fprintf(f, "%*u", n, (threaded == 1 ? mp->m_level : 0));
415 wleft = (n >= 0) ? wleft - n : 0;
416 break;
417 case 'f':
418 if (n == 0) {
419 n = 18;
420 if (s < 0)
421 n = -n;
423 i = n_ABS(n);
424 if (i > wleft) {
425 i = wleft;
426 n = (n < 0) ? -wleft : wleft;
428 if (flags & _ISTO) /* XXX tr()! */
429 i -= 3;
430 n = fprintf(f, "%s%s", ((flags & _ISTO) ? "To " : n_empty),
431 colalign(name, i, n, &wleft));
432 if (n < 0)
433 wleft = 0;
434 else if (flags & _ISTO)
435 wleft -= 3;
436 break;
437 case 'i':
438 if (threaded) {
439 #ifdef HAVE_COLOUR
440 if(n_COLOUR_IS_ACTIVE()){
441 cpen_new = n_colour_pen_create(n_COLOUR_ID_SUM_THREAD, colo_tag);
442 if(cpen_new != cpen_cur)
443 n_colour_pen_put(cpen_cur = cpen_new);
445 #endif
446 n = a_cmd__putindent(f, mp, n_MIN(wleft, (int)n_scrnwidth - 60));
447 wleft = (n >= 0) ? wleft - n : 0;
448 #ifdef HAVE_COLOUR
449 if(n_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
450 n_colour_pen_put(cpen_cur = cpen_new);
451 #endif
453 break;
454 case 'l':
455 if (n == 0)
456 n = 4;
457 if (UICMP(32, n_ABS(n), >, wleft))
458 n = (n < 0) ? -wleft : wleft;
459 if (mp->m_xlines) {
460 n = fprintf(f, "%*ld", n, mp->m_xlines);
461 wleft = (n >= 0) ? wleft - n : 0;
462 } else {
463 n = n_ABS(n);
464 wleft -= n;
465 while (n-- != 0)
466 putc(' ', f);
468 break;
469 case 'm':
470 if (n == 0) {
471 n = 3;
472 if (threaded)
473 for (i = msgCount; i > 999; i /= 10)
474 ++n;
476 if (UICMP(32, n_ABS(n), >, wleft))
477 n = (n < 0) ? -wleft : wleft;
478 n = fprintf(f, "%*lu", n, (ul_i)msgno);
479 wleft = (n >= 0) ? wleft - n : 0;
480 break;
481 case 'o':
482 if (n == 0)
483 n = -5;
484 if (UICMP(32, n_ABS(n), >, wleft))
485 n = (n < 0) ? -wleft : wleft;
486 n = fprintf(f, "%*lu", n, (ul_i)mp->m_xsize);
487 wleft = (n >= 0) ? wleft - n : 0;
488 break;
489 case 'S':
490 flags |= _SFMT;
491 /*FALLTHRU*/
492 case 's':
493 if (n == 0)
494 n = subjlen - 2;
495 if (n > 0 && s < 0)
496 n = -n;
497 if (subjlen > wleft)
498 subjlen = wleft;
499 if (UICMP(32, n_ABS(n), >, subjlen))
500 n = (n < 0) ? -subjlen : subjlen;
501 if (flags & _SFMT)
502 n -= (n < 0) ? -2 : 2;
503 if (n == 0)
504 break;
505 if (subjline == NULL)
506 subjline = a_cmd__subject(mp, (threaded && (flags & _IFMT)),
507 yetprinted);
508 if (subjline == (char*)-1) {
509 n = fprintf(f, "%*s", n, n_empty);
510 wleft = (n >= 0) ? wleft - n : 0;
511 } else {
512 n = fprintf(f, ((flags & _SFMT) ? "\"%s\"" : "%s"),
513 colalign(subjline, n_ABS(n), n, &wleft));
514 if (n < 0)
515 wleft = 0;
517 break;
518 case 'T': /* Message recipient flags */
519 switch(is_mlist_mp(mp, MLIST_OTHER)){
520 case MLIST_OTHER: c = ' '; break;
521 case MLIST_KNOWN: c = 'l'; break;
522 case MLIST_SUBSCRIBED: c = 'L'; break;
524 goto jputcb;
525 case 't':
526 if (n == 0) {
527 n = 3;
528 if (threaded)
529 for (i = msgCount; i > 999; i /= 10)
530 ++n;
532 if (UICMP(32, n_ABS(n), >, wleft))
533 n = (n < 0) ? -wleft : wleft;
534 n = fprintf(f, "%*lu",
535 n, (threaded ? (ul_i)mp->m_threadpos : (ul_i)msgno));
536 wleft = (n >= 0) ? wleft - n : 0;
537 break;
538 case 'U':
539 #ifdef HAVE_IMAP
540 if (n == 0)
541 n = 9;
542 if (UICMP(32, n_ABS(n), >, wleft))
543 n = (n < 0) ? -wleft : wleft;
544 n = fprintf(f, "%*" PRIu64 , n, mp->m_uid);
545 wleft = (n >= 0) ? wleft - n : 0;
546 break;
547 #else
548 c = '0';
549 goto jputcb;
550 #endif
551 default:
552 if (n_poption & n_PO_D_V)
553 n_err(_("Unknown *headline* format: %%%c\n"), c);
554 c = '?';
555 goto jputcb;
558 if (wleft <= 0)
559 break;
562 n_COLOUR( n_colour_reset(); )
563 putc('\n', f);
565 if (subjline != NULL && subjline != (char*)-1)
566 free(subjline);
567 NYD2_LEAVE;
570 static char *
571 a_cmd__subject(struct message *mp, bool_t threaded, size_t yetprinted)
573 struct str in, out;
574 char *rv, *ms;
575 NYD2_ENTER;
577 rv = (char*)-1;
579 if ((ms = hfield1("subject", mp)) == NULL)
580 goto jleave;
582 in.l = strlen(in.s = ms);
583 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
584 rv = ms = out.s;
586 if (!threaded || mp->m_level == 0)
587 goto jleave;
589 /* In a display thread - check whether this message uses the same
590 * Subject: as it's parent or elder neighbour, suppress printing it if
591 * this is the case. To extend this a bit, ignore any leading Re: or
592 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
593 ms = n_UNCONST(subject_re_trim(n_UNCONST(ms)));
595 for (; (mp = prev_in_thread(mp)) != NULL && yetprinted-- > 0;) {
596 char *os;
598 if (visible(mp) && (os = hfield1("subject", mp)) != NULL) {
599 struct str oout;
600 int x;
602 in.l = strlen(in.s = os);
603 mime_fromhdr(&in, &oout, TD_ICONV | TD_ISPR);
604 x = asccasecmp(ms, subject_re_trim(oout.s));
605 free(oout.s);
607 if (!x) {
608 free(out.s);
609 rv = (char*)-1;
611 break;
614 jleave:
615 NYD2_LEAVE;
616 return rv;
619 static int
620 a_cmd__putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX magics */
622 struct message *mq;
623 int *us, indlvl, indw, i, important = MNEW | MFLAGGED;
624 char *cs;
625 NYD2_ENTER;
627 if (mp->m_level == 0 || maxwidth == 0) {
628 indw = 0;
629 goto jleave;
632 cs = ac_alloc(mp->m_level);
633 us = ac_alloc(mp->m_level * sizeof *us);
635 i = mp->m_level - 1;
636 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
637 if (mp->m_parent && mp->m_parent->m_flag & important)
638 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
639 else
640 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
641 cs[i] = '+';
642 } else {
643 if (mp->m_parent && mp->m_parent->m_flag & important)
644 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
645 else
646 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
647 cs[i] = '\\';
650 mq = mp->m_parent;
651 for (i = mp->m_level - 2; i >= 0; --i) {
652 if (mq) {
653 if (UICMP(32, i, >, mq->m_level - 1)) {
654 us[i] = cs[i] = ' ';
655 continue;
657 if (mq->m_younger) {
658 if (mq->m_parent && (mq->m_parent->m_flag & important))
659 us[i] = 0x2503;
660 else
661 us[i] = 0x2502;
662 cs[i] = '|';
663 } else
664 us[i] = cs[i] = ' ';
665 mq = mq->m_parent;
666 } else
667 us[i] = cs[i] = ' ';
670 --maxwidth;
671 for (indlvl = indw = 0; (ui8_t)indlvl < mp->m_level && indw < maxwidth;
672 ++indlvl) {
673 if (indw < maxwidth - 1)
674 indw += (int)a_cmd__putuc(us[indlvl], cs[indlvl] & 0xFF, fp);
675 else
676 indw += (int)a_cmd__putuc(0x21B8, '^', fp);
678 indw += a_cmd__putuc(0x25B8, '>', fp);
680 ac_free(us);
681 ac_free(cs);
682 jleave:
683 NYD2_LEAVE;
684 return indw;
687 static size_t
688 a_cmd__putuc(int u, int c, FILE *fp){
689 size_t rv;
690 NYD2_ENTER;
691 n_UNUSED(u);
693 #ifdef HAVE_NATCH_CHAR
694 if((n_psonce & n_PSO_UNICODE) && (u & ~(wchar_t)0177) &&
695 !ok_blook(headline_plain)){
696 char mbb[MB_LEN_MAX];
697 int i, n;
699 if((n = wctomb(mbb, u)) > 0){
700 rv = wcwidth(u);
701 for(i = 0; i < n; ++i)
702 if(putc(mbb[i] & 0377, fp) == EOF){
703 rv = 0;
704 break;
706 }else if(n == 0)
707 rv = (putc('\0', fp) != EOF);
708 else
709 rv = 0;
710 }else
711 #endif
712 rv = (putc(c, fp) != EOF);
713 NYD2_LEAVE;
714 return rv;
717 static int
718 a_cmd__dispc(struct message *mp, char const *a)
720 int i = ' ';
721 NYD2_ENTER;
723 if ((mp->m_flag & (MREAD | MNEW)) == MREAD)
724 i = a[3];
725 if ((mp->m_flag & (MREAD | MNEW)) == (MREAD | MNEW))
726 i = a[2];
727 if (mp->m_flag & MANSWERED)
728 i = a[8];
729 if (mp->m_flag & MDRAFTED)
730 i = a[9];
731 if ((mp->m_flag & (MREAD | MNEW)) == MNEW)
732 i = a[0];
733 if (!(mp->m_flag & (MREAD | MNEW)))
734 i = a[1];
735 if (mp->m_flag & MSPAM)
736 i = a[12];
737 if (mp->m_flag & MSPAMUNSURE)
738 i = a[13];
739 if (mp->m_flag & MSAVED)
740 i = a[4];
741 if (mp->m_flag & MPRESERVE)
742 i = a[5];
743 if (mp->m_flag & (MBOX | MBOXED))
744 i = a[6];
745 if (mp->m_flag & MFLAGGED)
746 i = a[7];
747 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
748 i = a[11];
749 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
750 i = a[10];
751 NYD2_LEAVE;
752 return i;
755 static int
756 a_cmd_scroll(char const *arg, bool_t onlynew){
757 siz_t l;
758 bool_t isabs;
759 int msgspec, size, maxs;
760 NYD2_ENTER;
762 /* TODO scroll problem: we do not know whether + and $ have already reached
763 * TODO the last screen in threaded mode */
764 msgspec = onlynew ? -1 : 0;
765 size = (int)/*TODO*/n_screensize();
766 if((maxs = msgCount / size) > 0 && msgCount % size == 0)
767 --maxs;
769 if(arg == NULL)
770 arg = n_empty;
771 switch(*arg){
772 case '\0':
773 ++_screen;
774 goto jfwd;
775 case '^':
776 if(arg[1] != '\0')
777 goto jerr;
778 if(_screen == 0)
779 goto jerrbwd;
780 _screen = 0;
781 break;
782 case '$':
783 if(arg[1] != '\0')
784 goto jerr;
785 if(_screen == maxs)
786 goto jerrfwd;
787 _screen = maxs;
788 break;
789 case '+':
790 if(arg[1] == '\0')
791 ++_screen;
792 else{
793 isabs = FAL0;
795 ++arg;
796 if(0){
797 case '1': case '2': case '3': case '4': case '5':
798 case '6': case '7': case '8': case '9': case '0':
799 isabs = TRU1;
801 if((n_idec_siz_cp(&l, arg, 0, NULL
802 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
803 ) != n_IDEC_STATE_CONSUMED)
804 goto jerr;
805 if(l > maxs - (isabs ? 0 : _screen))
806 goto jerrfwd;
807 _screen = isabs ? (int)l : _screen + l;
809 jfwd:
810 if(_screen > maxs){
811 jerrfwd:
812 _screen = maxs;
813 fprintf(n_stdout, _("On last screenful of messages\n"));
815 break;
817 case '-':
818 if(arg[1] == '\0')
819 --_screen;
820 else{
821 if((n_idec_siz_cp(&l, ++arg, 0, NULL
822 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
823 ) != n_IDEC_STATE_CONSUMED)
824 goto jerr;
825 if(l > _screen)
826 goto jerrbwd;
827 _screen -= l;
829 if(_screen < 0){
830 jerrbwd:
831 _screen = 0;
832 fprintf(n_stdout, _("On first screenful of messages\n"));
834 if(msgspec == -1)
835 msgspec = -2;
836 break;
837 default:
838 jerr:
839 n_err(_("Unrecognized scrolling command: %s\n"), arg);
840 size = 1;
841 goto jleave;
844 size = _headers(msgspec);
845 jleave:
846 NYD2_LEAVE;
847 return size;
850 static int
851 _headers(int msgspec) /* TODO rework v15 */
853 bool_t needdot, showlast;
854 int g, k, mesg, size;
855 struct message *lastmq, *mp, *mq;
856 int volatile lastg;
857 ui32_t volatile flag;
858 enum mflag fl;
859 NYD_ENTER;
861 time_current_update(&time_current, FAL0);
863 fl = MNEW | MFLAGGED;
864 flag = 0;
865 lastg = 1;
866 lastmq = NULL;
868 size = (int)/*TODO*/n_screensize();
869 if (_screen < 0)
870 _screen = 0;
871 #if 0 /* FIXME original code path */
872 k = _screen * size;
873 #else
874 if (msgspec <= 0)
875 k = _screen * size;
876 else
877 k = msgspec;
878 #endif
879 if (k >= msgCount)
880 k = msgCount - size;
881 if (k < 0)
882 k = 0;
884 needdot = (msgspec <= 0) ? TRU1 : (dot != &message[msgspec - 1]);
885 showlast = ok_blook(showlast);
887 if (mb.mb_threaded == 0) {
888 g = 0;
889 mq = message;
890 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
891 if (visible(mp)) {
892 if (g % size == 0)
893 mq = mp;
894 if (mp->m_flag & fl) {
895 lastg = g;
896 lastmq = mq;
898 if ((msgspec > 0 && PTRCMP(mp, ==, message + msgspec - 1)) ||
899 (msgspec == 0 && g == k) ||
900 (msgspec == -2 && g == k + size && lastmq) ||
901 (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
902 break;
903 g++;
905 if (lastmq && (msgspec == -2 ||
906 (msgspec == -1 && PTRCMP(mp, ==, message + msgCount)))) {
907 g = lastg;
908 mq = lastmq;
910 _screen = g / size;
911 mp = mq;
913 mesg = (int)PTR2SIZE(mp - message);
914 #ifdef HAVE_IMAP
915 if (mb.mb_type == MB_IMAP)
916 imap_getheaders(mesg + 1, mesg + size);
917 #endif
918 n_COLOUR( n_colour_env_create(n_COLOUR_CTX_SUM, n_stdout, FAL0); )
919 srelax_hold();
920 for(lastmq = NULL, mq = &message[msgCount]; mp < mq; lastmq = mp, ++mp){
921 ++mesg;
922 if (!visible(mp))
923 continue;
924 if (UICMP(32, flag, >=, size))
925 break;
926 if(needdot){
927 if(showlast){
928 if(UICMP(32, flag, ==, size - 1) || &mp[1] == mq)
929 goto jdot_unsort;
930 }else if(flag == 0){
931 jdot_unsort:
932 needdot = FAL0;
933 setdot(mp);
936 ++flag;
937 a_cmd_print_head(0, mesg, n_stdout, 0);
938 srelax();
940 if(needdot && ok_blook(showlast)) /* xxx will not show */
941 setdot(lastmq);
942 srelax_rele();
943 n_COLOUR( n_colour_env_gut(); )
945 } else { /* threaded */
946 g = 0;
947 mq = threadroot;
948 for (mp = threadroot; mp; mp = next_in_thread(mp))
949 if (visible(mp) &&
950 (mp->m_collapsed <= 0 ||
951 PTRCMP(mp, ==, message + msgspec - 1))) {
952 if (g % size == 0)
953 mq = mp;
954 if (mp->m_flag & fl) {
955 lastg = g;
956 lastmq = mq;
958 if ((msgspec > 0 && PTRCMP(mp, ==, message + msgspec - 1)) ||
959 (msgspec == 0 && g == k) ||
960 (msgspec == -2 && g == k + size && lastmq) ||
961 (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
962 break;
963 g++;
965 if (lastmq && (msgspec == -2 ||
966 (msgspec == -1 && PTRCMP(mp, ==, message + msgCount)))) {
967 g = lastg;
968 mq = lastmq;
970 _screen = g / size;
971 mp = mq;
973 n_COLOUR( n_colour_env_create(n_COLOUR_CTX_SUM, n_stdout, FAL0); )
974 srelax_hold();
975 for(lastmq = NULL; mp != NULL; lastmq = mp, mp = mq){
976 mq = next_in_thread(mp);
977 if (visible(mp) &&
978 (mp->m_collapsed <= 0 ||
979 PTRCMP(mp, ==, message + msgspec - 1))) {
980 if (UICMP(32, flag, >=, size))
981 break;
982 if(needdot){
983 if(showlast){
984 if(UICMP(32, flag, ==, size - 1) || mq == NULL)
985 goto jdot_sort;
986 }else if(flag == 0){
987 jdot_sort:
988 needdot = FAL0;
989 setdot(mp);
992 a_cmd_print_head(flag, PTR2SIZE(mp - message + 1), n_stdout,
993 mb.mb_threaded);
994 ++flag;
995 srelax();
998 if(needdot && ok_blook(showlast)) /* xxx will not show */
999 setdot(lastmq);
1000 srelax_rele();
1001 n_COLOUR( n_colour_env_gut(); )
1004 if (flag == 0) {
1005 fprintf(n_stdout, _("No more mail.\n"));
1006 if (n_pstate & (n_PS_ROBOT | n_PS_HOOK_MASK))
1007 flag = !flag;
1009 NYD_LEAVE;
1010 return !flag;
1013 FL int
1014 c_headers(void *v)
1016 int rv;
1017 NYD_ENTER;
1019 rv = print_header_group((int*)v);
1020 NYD_LEAVE;
1021 return rv;
1024 FL int
1025 print_header_group(int *vector)
1027 int rv;
1028 NYD_ENTER;
1030 assert(vector != NULL && vector != (void*)-1);
1031 rv = _headers(vector[0]);
1032 NYD_LEAVE;
1033 return rv;
1036 FL int
1037 c_scroll(void *v)
1039 int rv;
1040 NYD_ENTER;
1042 rv = a_cmd_scroll(*(char const**)v, FAL0);
1043 NYD_LEAVE;
1044 return rv;
1047 FL int
1048 c_Scroll(void *v)
1050 int rv;
1051 NYD_ENTER;
1053 rv = a_cmd_scroll(*(char const**)v, TRU1);
1054 NYD_LEAVE;
1055 return rv;
1058 FL int
1059 c_dotmove(void *v)
1061 char const *args;
1062 int msgvec[2], rv;
1063 NYD_ENTER;
1065 if (*(args = v) == '\0' || args[1] != '\0') {
1066 jerr:
1067 n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n"));
1068 rv = 1;
1069 } else switch (args[0]) {
1070 case '-':
1071 case '+':
1072 if (msgCount == 0) {
1073 fprintf(n_stdout, _("At EOF\n"));
1074 rv = 0;
1075 } else if (getmsglist(n_UNCONST(/*TODO*/ args), msgvec, 0) > 0) {
1076 setdot(message + msgvec[0] - 1);
1077 msgvec[1] = 0;
1078 rv = c_headers(msgvec);
1079 } else
1080 rv = 1;
1081 break;
1082 default:
1083 goto jerr;
1085 NYD_LEAVE;
1086 return rv;
1089 FL int
1090 c_from(void *vp)
1092 int *msgvec, *ip, n;
1093 char *cp;
1094 FILE * volatile obuf;
1095 NYD_ENTER;
1097 if(*(msgvec = vp) == 0)
1098 goto jleave;
1100 time_current_update(&time_current, FAL0);
1102 obuf = n_stdout;
1104 if (n_psonce & n_PSO_INTERACTIVE) {
1105 if ((cp = ok_vlook(crt)) != NULL) {
1106 uiz_t ib;
1108 for (n = 0, ip = msgvec; *ip != 0; ++ip)
1109 ++n;
1111 if(*cp == '\0')
1112 ib = n_screensize();
1113 else
1114 n_idec_uiz_cp(&ib, cp, 0, NULL);
1115 if (UICMP(z, n, >, ib) && (obuf = n_pager_open()) == NULL)
1116 obuf = n_stdout;
1120 /* Update dot before display so that the dotmark etc. are correct */
1121 for (ip = msgvec; ip[1] != 0; ++ip)
1123 setdot(&message[(ok_blook(showlast) ? *ip : *msgvec) - 1]);
1125 n_COLOUR( n_colour_env_create(n_COLOUR_CTX_SUM, obuf, obuf != n_stdout); )
1126 srelax_hold();
1127 for (n = 0, ip = msgvec; *ip != 0; ++ip) { /* TODO join into _print_head() */
1128 a_cmd_print_head((size_t)n++, (size_t)*ip, obuf, mb.mb_threaded);
1129 srelax();
1131 srelax_rele();
1132 n_COLOUR( n_colour_env_gut(); )
1134 if (obuf != n_stdout)
1135 n_pager_close(obuf);
1136 jleave:
1137 NYD_LEAVE;
1138 return 0;
1141 FL void
1142 print_headers(size_t bottom, size_t topx, bool_t only_marked)
1144 size_t printed;
1145 NYD_ENTER;
1147 #ifdef HAVE_IMAP
1148 if (mb.mb_type == MB_IMAP)
1149 imap_getheaders(bottom, topx);
1150 #endif
1151 time_current_update(&time_current, FAL0);
1153 n_COLOUR( n_colour_env_create(n_COLOUR_CTX_SUM, n_stdout, FAL0); )
1154 srelax_hold();
1155 for (printed = 0; bottom <= topx; ++bottom) {
1156 struct message *mp = message + bottom - 1;
1157 if (only_marked) {
1158 if (!(mp->m_flag & MMARK))
1159 continue;
1160 } else if (!visible(mp))
1161 continue;
1162 a_cmd_print_head(printed++, bottom, n_stdout, FAL0);
1163 srelax();
1165 srelax_rele();
1166 n_COLOUR( n_colour_env_gut(); )
1167 NYD_LEAVE;
1170 /* s-it-mode */