cmd1.c:__hprf(): fix header display..
[s-mailx.git] / cmd1.c
blob411c05194187939b1cb8ac7125c9219c29a73e7f
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ User commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
45 * Print the current active headings.
46 * Don't change dot if invoker didn't give an argument.
49 static int screen;
51 /* Prepare and print "[Message: xy]:" intro */
52 static void _show_msg_overview(FILE *obuf, struct message *mp, int msg_no);
54 /* ... And place the extracted date in `date' */
55 static void _parse_from_(struct message *mp, char date[FROM_DATEBUF]);
57 /* Print out the header of a specific message
58 * __hprf: handle *headline*
59 * __subject: Subject:, but return NULL if threaded and Subject: yet seen
60 * __putindent: print out the indenting in threaded display */
61 static void _print_head(size_t yetprinted, int msgno, FILE *f,
62 bool_t threaded);
63 static void __hprf(size_t yetprinted, const char *fmt, int mesg, FILE *f,
64 bool_t threaded, const char *attrlist);
65 static char * __subject(struct message *mp, bool_t threaded,
66 size_t yetprinted);
67 static char * __subject_trim(char *s);
68 static int __putindent(FILE *fp, struct message *mp, int maxwidth);
70 static void _cmd1_onpipe(int signo);
71 static int _dispc(struct message *mp, const char *a);
72 static int scroll1(char *arg, int onlynew);
74 static int _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
75 bool_t dodecode, char *cmd, off_t *tstats);
76 static int pipe1(char *str, int doign);
78 static void
79 _show_msg_overview(FILE *obuf, struct message *mp, int msg_no)
81 char const *cpre = "", *csuf = "";
83 #ifdef HAVE_COLOUR
84 if (colour_table != NULL) {
85 struct str const *sp;
87 if ((sp = colour_get(COLOURSPEC_MSGINFO)) != NULL)
88 cpre = sp->s;
89 csuf = colour_get(COLOURSPEC_RESET)->s;
91 #endif
92 fprintf(obuf, tr(17,
93 "%s[-- Message %2d -- %lu lines, %lu bytes --]:%s\n"),
94 cpre, msg_no, (ul_it)mp->m_lines, (ul_it)mp->m_size, csuf);
97 static void
98 _parse_from_(struct message *mp, char date[FROM_DATEBUF])
100 FILE *ibuf;
101 int hlen;
102 char *hline = NULL;
103 size_t hsize = 0;
105 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
106 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
107 (void)extract_date_from_from_(hline, hlen, date);
108 if (hline != NULL)
109 free(hline);
112 static void
113 _print_head(size_t yetprinted, int msgno, FILE *f, bool_t threaded)
115 char attrlist[30], *cp;
116 char const *fmt;
118 if ((cp = ok_vlook(attrlist)) != NULL) {
119 size_t i = strlen(cp);
120 if (UICMP(32, i, >, sizeof attrlist - 1))
121 i = (int)sizeof attrlist - 1;
122 memcpy(attrlist, cp, i);
123 } else if (ok_blook(bsdcompat) || ok_blook(bsdflags) ||
124 getenv("SYSV3") != NULL) {
125 char const bsdattr[] = "NU *HMFAT+-$";
126 memcpy(attrlist, bsdattr, sizeof bsdattr - 1);
127 } else {
128 char const pattr[] = "NUROSPMFAT+-$";
129 memcpy(attrlist, pattr, sizeof pattr - 1);
132 if ((fmt = ok_vlook(headline)) == NULL) {
133 fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
134 ? "%>%a%m %-20f %16d %3l/%-5o %i%-S"
135 : "%>%a%m %-18f %16d %4l/%-5o %i%-s");
138 __hprf(yetprinted, fmt, msgno, f, threaded, attrlist);
141 static void
142 __hprf(size_t yetprinted, char const *fmt, int msgno, FILE *f, bool_t threaded,
143 char const *attrlist)
145 char datebuf[FROM_DATEBUF], *cp, *subjline;
146 char const *datefmt, *date, *name, *fp;
147 int B, c, i, n, s, wleft, subjlen, isto = 0, isaddr = 0;
148 struct message *mp;
149 time_t datet;
151 mp = &message[msgno - 1];
152 datet = mp->m_time;
153 date = NULL;
155 datefmt = ok_vlook(datefield);
156 jredo:
157 if (datefmt != NULL) {
158 fp = hfield1("date", mp);/* TODO use m_date field! */
159 if (fp == NULL) {
160 datefmt = NULL;
161 goto jredo;
163 datet = rfctime(fp);
164 date = fakedate(datet);
165 fp = ok_vlook(datefield_markout_older);
166 i = (*datefmt != '\0');
167 if (fp != NULL)
168 i |= (*fp != '\0') ? 2 | 4 : 2;
169 /* May we strftime(3)? */
170 if (i & (1 | 4))
171 memcpy(&time_current.tc_local, localtime(&datet),
172 sizeof time_current.tc_local);
173 if ((i & 2) &&
174 /* TODO *datefield-markout-older* we accept
175 * TODO one day in the future, should be UTC
176 * TODO offset only? and Stephen Isard had
177 * TODO one week once he proposed the patch! */
178 (datet > time_current.tc_time + DATE_SECSDAY ||
179 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
180 (datet + _6M < time_current.tc_time))) {
181 #undef _6M
182 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
183 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
184 memcpy(datebuf + 4, date + 4, 7);
185 datebuf[4 + 7] = ' ';
186 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
187 datebuf[4 + 7 + 1 + 4] = '\0';
188 date = datebuf;
190 } else if ((i & 1) == 0)
191 datefmt = NULL;
192 } else if (datet == (time_t)0 && !(mp->m_flag & MNOFROM)) {
193 /* TODO eliminate this path, query the FROM_ date in setptr(),
194 * TODO all other codepaths do so by themselves ALREADY ?????
195 * TODO assert(mp->m_time != 0);, then
196 * TODO ALSO changes behaviour of markout-non-current */
197 _parse_from_(mp, datebuf);
198 date = datebuf;
199 } else
200 date = fakedate(datet);
202 isaddr = 1;
203 name = name1(mp, 0);
204 if (name != NULL && ok_blook(showto) && is_myname(skin(name))) {
205 if ((cp = hfield1("to", mp)) != NULL) {
206 name = cp;
207 isto = 1;
210 if (name == NULL) {
211 name = "";
212 isaddr = 0;
214 if (isaddr) {
215 if (ok_blook(showname))
216 name = realname(name);
217 else {
218 name = prstr(skin(name));
222 subjline = NULL;
224 /* Detect the width of the non-format characters in *headline*;
225 * like that we can simply use putc() in the next loop, since we have
226 * already calculated their column widths (TODO it's sick) */
227 wleft =
228 subjlen = scrnwidth;
230 for (fp = fmt; *fp; ++fp) {
231 if (*fp == '%') {
232 if (*++fp == '-') {
233 ++fp;
234 } else if (*fp == '+')
235 ++fp;
236 if (digitchar(*fp)) {
237 n = 0;
239 n = 10*n + *fp - '0';
240 while (++fp, digitchar(*fp));
241 subjlen -= n;
244 if (*fp == '\0')
245 break;
246 } else {
247 #ifdef HAVE_WCWIDTH
248 if (mb_cur_max > 1) {
249 wchar_t wc;
250 if ((s = mbtowc(&wc, fp, mb_cur_max)) < 0)
251 n = s = 1;
252 else if ((n = wcwidth(wc)) < 0)
253 n = 1;
254 } else
255 #endif
256 n = s = 1;
257 subjlen -= n;
258 wleft -= n;
259 while (--s > 0)
260 ++fp;
264 /* Walk *headline*, producing output */
265 for (fp = fmt; *fp; ++fp) {
266 if ((c = *fp & 0xFF) == '%') {
267 B = 0;
268 n = 0;
269 s = 1;
270 if (*++fp == '-') {
271 s = -1;
272 ++fp;
273 } else if (*fp == '+')
274 ++fp;
275 if (digitchar(*fp)) {
277 n = 10*n + *fp - '0';
278 while (++fp, digitchar(*fp));
280 if (*fp == '\0')
281 break;
283 n *= s;
284 switch ((c = *fp & 0xFF)) {
285 case '%':
286 goto jputc;
287 case '>':
288 case '<':
289 if (dot != mp)
290 c = ' ';
291 goto jputc;
292 case 'a':
293 c = _dispc(mp, attrlist);
294 jputc:
295 if (UICMP(32, ABS(n), >, wleft))
296 n = (n < 0) ? -wleft : wleft;
297 n = fprintf(f, "%*c", n, c);
298 wleft = (n >= 0) ? wleft - n : 0;
299 break;
300 case 'm':
301 if (n == 0) {
302 n = 3;
303 if (threaded)
304 for (i=msgCount; i>999; i/=10)
305 n++;
307 if (UICMP(32, ABS(n), >, wleft))
308 n = (n < 0) ? -wleft : wleft;
309 n = fprintf(f, "%*d", n, msgno);
310 wleft = (n >= 0) ? wleft - n : 0;
311 break;
312 case 'f':
313 if (n == 0) {
314 n = 18;
315 if (s < 0)
316 n = -n;
318 i = ABS(n);
319 if (i > wleft) {
320 i = wleft;
321 n = (n < 0) ? -wleft : wleft;
323 if (isto) /* XXX tr()! */
324 i -= 3;
325 n = fprintf(f, "%s%s", (isto ? "To " : ""),
326 colalign(name, i, n, &wleft));
327 if (n < 0)
328 wleft = 0;
329 else if (isto)
330 wleft -= 3;
331 break;
332 case 'd':
333 if (datefmt != NULL) {
334 i = strftime(datebuf, sizeof datebuf,
335 datefmt,
336 &time_current.tc_local);
337 if (i != 0)
338 date = datebuf;
339 else
340 fprintf(stderr, tr(174,
341 "Ignored date format, "
342 "it excesses the "
343 "target buffer "
344 "(%lu bytes)\n"),
345 (ul_it)sizeof datebuf);
346 datefmt = NULL;
348 if (n == 0)
349 n = 16;
350 if (UICMP(32, ABS(n), >, wleft))
351 n = (n < 0) ? -wleft : wleft;
352 n = fprintf(f, "%*.*s", n, n, date);
353 wleft = (n >= 0) ? wleft - n : 0;
354 break;
355 case 'l':
356 if (n == 0)
357 n = 4;
358 if (UICMP(32, ABS(n), >, wleft))
359 n = (n < 0) ? -wleft : wleft;
360 if (mp->m_xlines) {
361 n = fprintf(f, "%*ld", n, mp->m_xlines);
362 wleft = (n >= 0) ? wleft - n : 0;
363 } else {
364 n = ABS(n);
365 wleft -= n;
366 while (n-- != 0)
367 putc(' ', f);
369 break;
370 case 'o':
371 if (n == 0)
372 n = -5;
373 if (UICMP(32, ABS(n), >, wleft))
374 n = (n < 0) ? -wleft : wleft;
375 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
376 wleft = (n >= 0) ? wleft - n : 0;
377 break;
378 case 'i':
379 if (threaded) {
380 n = __putindent(f, mp, MIN(wleft,
381 scrnwidth - 60));
382 wleft = (n >= 0) ? wleft - n : 0;
384 break;
385 case 'S':
386 B = 1;
387 /*FALLTHRU*/
388 case 's':
389 if (n == 0)
390 n = subjlen - 2;
391 if (n > 0 && s < 0)
392 n = -n;
393 if (subjlen > wleft)
394 subjlen = wleft;
395 if (UICMP(32, ABS(n), >, subjlen))
396 n = (n < 0) ? -subjlen : subjlen;
397 if (B)
398 n -= (n < 0) ? -2 : 2;
399 if (n == 0)
400 break;
401 if (subjline == NULL)
402 subjline = __subject(mp, threaded,
403 yetprinted);
404 if (subjline == (char*)-1) {
405 n = fprintf(f, "%*s", n, "");
406 wleft = (n >= 0) ? wleft-n : 0;
407 } else {
408 n = fprintf(f, (B ? "\"%s\"" : "%s"),
409 colalign(subjline, ABS(n), n,
410 &wleft));
411 if (n < 0)
412 wleft = 0;
414 break;
415 case 'U':
416 #ifdef HAVE_IMAP
417 if (n == 0)
418 n = 9;
419 if (UICMP(32, ABS(n), >, wleft))
420 n = (n < 0) ? -wleft : wleft;
421 n = fprintf(f, "%*lu", n, mp->m_uid);
422 wleft = (n >= 0) ? wleft - n : 0;
423 break;
424 #else
425 c = '?';
426 goto jputc;
427 #endif
428 case 'e':
429 if (n == 0)
430 n = 2;
431 if (UICMP(32, ABS(n), >, wleft))
432 n = (n < 0) ? -wleft : wleft;
433 n = fprintf(f, "%*u", n,
434 threaded == 1 ? mp->m_level : 0);
435 wleft = (n >= 0) ? wleft - n : 0;
436 break;
437 case 't':
438 if (n == 0) {
439 n = 3;
440 if (threaded)
441 for (i=msgCount; i>999; i/=10)
442 n++;
444 if (UICMP(32, ABS(n), >, wleft))
445 n = (n < 0) ? -wleft : wleft;
446 n = fprintf(f, "%*ld", n,
447 threaded ? mp->m_threadpos : msgno);
448 wleft = (n >= 0) ? wleft - n : 0;
449 break;
450 case '$':
451 #ifdef HAVE_SPAM
452 if (n == 0)
453 n = 4;
454 if (UICMP(32, ABS(n), >, wleft))
455 n = (n < 0) ? -wleft : wleft;
456 { char buf[16];
457 snprintf(buf, sizeof buf, "%u.%u",
458 (mp->m_spamscore >> 8),
459 (mp->m_spamscore & 0xFF));
460 n = fprintf(f, "%*s", n, buf);
461 wleft = (n >= 0) ? wleft - n : 0;
463 #else
464 c = '?';
465 goto jputc;
466 #endif
469 if (wleft <= 0)
470 break;
471 } else
472 putc(c, f);
474 putc('\n', f);
476 if (subjline != NULL && subjline != (char*)-1)
477 free(subjline);
480 static char *
481 __subject_trim(char *s)
483 struct {
484 ui8_t len;
485 char dat[7];
486 } const *pp, ignored[] = { /* TODO make ignore list configurable */
487 { 3, "re:" }, { 4, "fwd:" },
488 { 3, "aw:" }, { 5, "antw:" },
489 { 0, "" }
491 jouter:
492 while (*s != '\0') {
493 while (spacechar(*s))
494 ++s;
495 /* TODO While it is maybe ok not to MIME decode these, we
496 * TODO should skip =?..?= at the beginning? */
497 for (pp = ignored; pp->len > 0; ++pp)
498 if (is_asccaseprefix(pp->dat, s)) {
499 s += pp->len;
500 goto jouter;
502 break;
504 return s;
507 static char *
508 __subject(struct message *mp, bool_t threaded, size_t yetprinted)
510 /* XXX NOTE: because of efficiency reasons we simply ignore any encoded
511 * XXX parts and use ASCII case-insensitive comparison */
512 struct str in, out;
513 struct message *xmp;
514 char *rv = (char*)-1, *ms, *mso, *os;
516 if ((ms = hfield1("subject", mp)) == NULL)
517 goto jleave;
519 if (!threaded || mp->m_level == 0)
520 goto jconv;
522 /* In a display thread - check wether this message uses the same
523 * Subject: as it's parent or elder neighbour, suppress printing it if
524 * this is the case. To extend this a bit, ignore any leading Re: or
525 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
526 mso = __subject_trim(ms);
527 for (xmp = mp; (xmp = prev_in_thread(xmp)) != NULL && yetprinted-- > 0;)
528 if (visible(xmp) && (os = hfield1("subject", xmp)) != NULL &&
529 asccasecmp(mso, __subject_trim(os)) == 0)
530 goto jleave;
531 jconv:
532 in.s = ms;
533 in.l = strlen(ms);
534 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
535 rv = out.s;
536 jleave:
537 return rv;
540 static int
541 __putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
543 struct message *mq;
544 int *us, indlvl, indw, i, important = MNEW|MFLAGGED;
545 char *cs;
547 if (mp->m_level == 0 || maxwidth == 0)
548 return 0;
549 cs = ac_alloc(mp->m_level);
550 us = ac_alloc(mp->m_level * sizeof *us);
552 i = mp->m_level - 1;
553 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
554 if (mp->m_parent && mp->m_parent->m_flag & important)
555 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
556 else
557 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
558 cs[i] = '+';
559 } else {
560 if (mp->m_parent && mp->m_parent->m_flag & important)
561 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
562 else
563 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
564 cs[i] = '\\';
567 mq = mp->m_parent;
568 for (i = mp->m_level - 2; i >= 0; i--) {
569 if (mq) {
570 if (UICMP(32, i, >, mq->m_level - 1)) {
571 us[i] = cs[i] = ' ';
572 continue;
574 if (mq->m_younger) {
575 if (mq->m_parent &&
576 mq->m_parent->m_flag&important)
577 us[i] = 0x2503;
578 else
579 us[i] = 0x2502;
580 cs[i] = '|';
581 } else
582 us[i] = cs[i] = ' ';
583 mq = mq->m_parent;
584 } else
585 us[i] = cs[i] = ' ';
588 --maxwidth;
589 for (indlvl = indw = 0; (uc_it)indlvl < mp->m_level &&
590 indw < maxwidth; ++indlvl) {
591 if (indw < maxwidth - 1)
592 indw += (int)putuc(us[indlvl], cs[indlvl] & 0377, fp);
593 else
594 indw += (int)putuc(0x21B8, '^', fp);
596 indw += /*putuc(0x261E, fp)*/putc('>', fp) != EOF;
598 ac_free(us);
599 ac_free(cs);
600 return indw;
603 FL int
604 ccmdnotsupp(void *v) /* TODO -> lex.c */
606 (void)v;
607 fprintf(stderr, tr(10, "The requested feature is not compiled in\n"));
608 return (1);
611 FL int
612 headers(void *v)
614 ui32_t flag;
615 int *msgvec = v, g, k, n, mesg, size, lastg = 1;
616 struct message *mp, *mq, *lastmq = NULL;
617 enum mflag fl = MNEW|MFLAGGED;
619 time_current_update(&time_current, FAL0);
621 flag = 0;
622 size = screensize();
623 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
624 if (screen < 0)
625 screen = 0;
626 k = screen * size;
627 if (k >= msgCount)
628 k = msgCount - size;
629 if (k < 0)
630 k = 0;
632 if (mb.mb_threaded == 0) {
633 g = 0;
634 mq = &message[0];
635 for (mp = &message[0]; mp < &message[msgCount]; mp++)
636 if (visible(mp)) {
637 if (g % size == 0)
638 mq = mp;
639 if (mp->m_flag&fl) {
640 lastg = g;
641 lastmq = mq;
643 if ((n > 0 && mp == &message[n-1]) ||
644 (n == 0 && g == k) ||
645 (n == -2 && g == k + size &&
646 lastmq) ||
647 (n < 0 && g >= k &&
648 (mp->m_flag & fl) != 0))
649 break;
650 g++;
652 if (lastmq && (n==-2 || (n==-1 && mp == &message[msgCount]))) {
653 g = lastg;
654 mq = lastmq;
656 screen = g / size;
657 mp = mq;
658 mesg = mp - &message[0];
659 if (dot != &message[n-1]) {
660 for (mq = mp; mq < &message[msgCount]; mq++)
661 if (visible(mq)) {
662 setdot(mq);
663 break;
666 #ifdef HAVE_IMAP
667 if (mb.mb_type == MB_IMAP)
668 imap_getheaders(mesg+1, mesg + size);
669 #endif
670 srelax_hold();
671 for (; mp < &message[msgCount]; mp++) {
672 mesg++;
673 if (!visible(mp))
674 continue;
675 if (UICMP(32, flag++, >=, size))
676 break;
677 _print_head(0, mesg, stdout, 0);
678 srelax();
680 srelax_rele();
681 } else { /* threaded */
682 g = 0;
683 mq = threadroot;
684 for (mp = threadroot; mp; mp = next_in_thread(mp))
685 if (visible(mp) && (mp->m_collapsed <= 0 ||
686 mp == &message[n-1])) {
687 if (g % size == 0)
688 mq = mp;
689 if (mp->m_flag&fl) {
690 lastg = g;
691 lastmq = mq;
693 if ((n > 0 && mp == &message[n-1]) ||
694 (n == 0 && g == k) ||
695 (n == -2 && g == k + size &&
696 lastmq) ||
697 (n < 0 && g >= k &&
698 (mp->m_flag & fl) != 0))
699 break;
700 g++;
702 if (lastmq && (n==-2 || (n==-1 && mp==&message[msgCount]))) {
703 g = lastg;
704 mq = lastmq;
706 screen = g / size;
707 mp = mq;
708 if (dot != &message[n-1]) {
709 for (mq = mp; mq; mq = next_in_thread(mq))
710 if (visible(mq) && mq->m_collapsed <= 0) {
711 setdot(mq);
712 break;
715 srelax_hold();
716 while (mp) {
717 if (visible(mp) && (mp->m_collapsed <= 0 ||
718 mp == &message[n-1])) {
719 if (UICMP(32, flag++, >=, size))
720 break;
721 _print_head(flag - 1, mp - &message[0] + 1,
722 stdout, mb.mb_threaded);
723 srelax();
725 mp = next_in_thread(mp);
727 srelax_rele();
729 if (!flag)
730 printf(tr(6, "No more mail.\n"));
731 return !flag;
735 * Scroll to the next/previous screen
737 FL int
738 scroll(void *v)
740 return scroll1(v, 0);
743 FL int
744 Scroll(void *v)
746 return scroll1(v, 1);
749 static int
750 scroll1(char *arg, int onlynew)
752 int size;
753 int cur[1];
755 cur[0] = onlynew ? -1 : 0;
756 size = screensize();
757 switch (*arg) {
758 case '1': case '2': case '3': case '4': case '5':
759 case '6': case '7': case '8': case '9': case '0':
760 screen = atoi(arg);
761 goto scroll_forward;
762 case '\0':
763 screen++;
764 goto scroll_forward;
765 case '$':
766 screen = msgCount / size;
767 goto scroll_forward;
768 case '+':
769 if (arg[1] == '\0')
770 screen++;
771 else
772 screen += atoi(arg + 1);
773 scroll_forward:
774 if (screen * size > msgCount) {
775 screen = msgCount / size;
776 printf(tr(7, "On last screenful of messages\n"));
778 break;
780 case '-':
781 if (arg[1] == '\0')
782 screen--;
783 else
784 screen -= atoi(arg + 1);
785 if (screen < 0) {
786 screen = 0;
787 printf(tr(8, "On first screenful of messages\n"));
789 if (cur[0] == -1)
790 cur[0] = -2;
791 break;
793 default:
794 printf(tr(9, "Unrecognized scrolling command \"%s\"\n"), arg);
795 return(1);
797 return(headers(cur));
801 * Compute screen size.
803 FL int
804 screensize(void)
806 int s;
807 char *cp;
809 if ((cp = ok_vlook(screen)) != NULL && (s = atoi(cp)) > 0)
810 return s;
811 return scrnheight - 4;
814 static sigjmp_buf _cmd1_pipejmp;
816 /*ARGSUSED*/
817 static void
818 _cmd1_onpipe(int signo)
820 UNUSED(signo);
821 siglongjmp(_cmd1_pipejmp, 1);
825 * Print out the headlines for each message
826 * in the passed message list.
828 FL int
829 from(void *v)
831 int *msgvec = v, *ip, n;
832 char *cp;
833 FILE *volatile obuf = stdout;
835 time_current_update(&time_current, FAL0);
837 /* TODO unfixable memory leaks still */
838 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
839 for (n = 0, ip = msgvec; *ip; ip++)
840 n++;
841 if (n > (*cp == '\0' ? screensize() : atoi((char*)cp)) + 3) {
842 char const *p;
843 if (sigsetjmp(_cmd1_pipejmp, 1))
844 goto endpipe;
845 p = get_pager();
846 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
847 perror(p);
848 obuf = stdout;
849 cp=NULL;
850 } else
851 safe_signal(SIGPIPE, _cmd1_onpipe);
854 for (n = 0, ip = msgvec; *ip != 0; ip++)
855 _print_head((size_t)n++, *ip, obuf, mb.mb_threaded);
856 if (--ip >= msgvec)
857 setdot(&message[*ip - 1]);
858 endpipe:
859 if (obuf != stdout) {
860 safe_signal(SIGPIPE, SIG_IGN);
861 Pclose(obuf, TRU1);
862 safe_signal(SIGPIPE, dflpipe);
864 return(0);
867 static int
868 _dispc(struct message *mp, const char *a)
870 int i = ' ';
873 * Bletch!
875 if ((mp->m_flag & (MREAD|MNEW)) == MREAD)
876 i = a[3];
877 if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
878 i = a[2];
879 if (mp->m_flag & MANSWERED)
880 i = a[8];
881 if (mp->m_flag & MDRAFTED)
882 i = a[9];
883 if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
884 i = a[0];
885 if ((mp->m_flag & (MREAD|MNEW)) == 0)
886 i = a[1];
887 if (mp->m_flag & MSPAM)
888 i = a[12];
889 if (mp->m_flag & MSAVED)
890 i = a[4];
891 if (mp->m_flag & MPRESERVE)
892 i = a[5];
893 if (mp->m_flag & (MBOX|MBOXED))
894 i = a[6];
895 if (mp->m_flag & MFLAGGED)
896 i = a[7];
897 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
898 i = a[11];
899 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
900 i = a[10];
901 return i;
904 FL void
905 print_headers(size_t bottom, size_t topx)
907 size_t printed;
909 #ifdef HAVE_IMAP
910 if (mb.mb_type == MB_IMAP)
911 imap_getheaders(bottom, topx);
912 #endif
913 time_current_update(&time_current, FAL0);
915 for (printed = 0; bottom <= topx; ++bottom)
916 if (visible(&message[bottom - 1]))
917 _print_head(printed++, bottom, stdout, 0);
921 * Print out the value of dot.
923 /*ARGSUSED*/
924 FL int
925 pdot(void *v)
927 (void)v;
928 printf("%d\n", (int)(dot - &message[0] + 1));
929 return(0);
933 * Type out the messages requested.
935 static sigjmp_buf pipestop;
937 /*ARGSUSED*/
938 static void
939 brokpipe(int signo)
941 (void)signo;
942 siglongjmp(pipestop, 1);
945 static int
946 _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
947 bool_t dodecode, char *cmd, off_t *tstats)
949 enum sendaction const action = ((dopipe && ok_blook(piperaw))
950 ? SEND_MBOX : dodecode
951 ? SEND_SHOW : doign
952 ? SEND_TODISP : SEND_TODISP_ALL);
953 bool_t const formfeed = (dopipe && ok_blook(page));
954 off_t mstats[2];
955 int *ip;
956 struct message *mp;
957 char const *cp;
958 FILE * volatile obuf;
960 obuf = stdout;
961 if (sigsetjmp(pipestop, 1))
962 goto close_pipe;
963 if (dopipe) {
964 if ((cp = ok_vlook(SHELL)) == NULL)
965 cp = XSHELL;
966 if ((obuf = Popen(cmd, "w", cp, 1)) == NULL) {
967 perror(cmd);
968 obuf = stdout;
969 } else
970 safe_signal(SIGPIPE, brokpipe);
971 } else if ((options & OPT_TTYOUT) &&
972 (dopage || (cp = ok_vlook(crt)) != NULL)) {
973 char const *pager = NULL;
974 size_t nlines = 0;
975 if (!dopage) {
976 for (ip = msgvec; *ip &&
977 PTRCMP(ip - msgvec, <, msgCount);
978 ++ip) {
979 if (!(message[*ip - 1].m_have & HAVE_BODY)) {
980 if ((get_body(&message[*ip - 1])) !=
981 OKAY)
982 return 1;
984 nlines += message[*ip - 1].m_lines;
987 if (dopage || UICMP(z, nlines, >,
988 (*cp != '\0' ? atoi(cp) : realscreenheight))) {
989 pager = get_pager();
990 #ifdef HAVE_SETENV
991 if ((cp = getenv("LESS")) == NULL) /* XXX not here! */
992 setenv("LESS", "FRXi", 0); /* XXX add env. */
993 #endif
994 obuf = Popen(pager, "w", NULL, 1);
995 #ifdef HAVE_SETENV
996 if (cp == NULL)
997 unsetenv("LESS"); /* XXX to Popen() etc.?!! */
998 #endif
999 if (obuf == NULL) {
1000 perror(pager);
1001 obuf = stdout;
1002 pager = NULL;
1003 } else
1004 safe_signal(SIGPIPE, brokpipe);
1006 #ifdef HAVE_COLOUR
1007 if (action != SEND_MBOX)
1008 colour_table_create(pager); /* (salloc()s!) */
1009 #endif
1011 #ifdef HAVE_COLOUR
1012 else if ((options & OPT_TTYOUT) && action != SEND_MBOX)
1013 colour_table_create(NULL); /* (salloc()s!) */
1014 #endif
1016 /* This may jump, in which case srelax_rele() wouldn't be called, but
1017 * it shouldn't matter, because we -- then -- directly reenter the
1018 * lex.c:commands() loop, which sreset()s */
1019 srelax_hold();
1020 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
1021 mp = &message[*ip - 1];
1022 touch(mp);
1023 setdot(mp);
1024 uncollapse1(mp, 1);
1025 if (!dopipe) {
1026 if (ip != msgvec)
1027 fprintf(obuf, "\n");
1028 if (action != SEND_MBOX)
1029 _show_msg_overview(obuf, mp, *ip);
1031 sendmp(mp, obuf, (doign ? ignore : NULL), NULL, action, mstats);
1032 srelax();
1033 if (formfeed)
1034 putc('\f', obuf);
1035 if (tstats) {
1036 tstats[0] += mstats[0];
1037 tstats[1] += mstats[1];
1040 srelax_rele();
1041 close_pipe:
1042 if (obuf != stdout) {
1043 /* Ignore SIGPIPE so it can't cause a duplicate close */
1044 safe_signal(SIGPIPE, SIG_IGN);
1045 colour_reset(obuf); /* XXX hacky; only here because we still jump */
1046 Pclose(obuf, TRU1);
1047 safe_signal(SIGPIPE, dflpipe);
1049 return 0;
1053 * Pipe the messages requested.
1055 static int
1056 pipe1(char *str, int doign)
1058 char *cmd;
1059 int *msgvec, ret;
1060 off_t stats[2];
1061 bool_t f;
1063 /*LINTED*/
1064 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1065 if ((cmd = laststring(str, &f, 1)) == NULL) {
1066 cmd = ok_vlook(cmd);
1067 if (cmd == NULL || *cmd == '\0') {
1068 fputs(tr(16, "variable cmd not set\n"), stderr);
1069 return 1;
1072 if (!f) {
1073 *msgvec = first(0, MMNORM);
1074 if (*msgvec == 0) {
1075 if (inhook)
1076 return 0;
1077 puts(tr(18, "No messages to pipe."));
1078 return 1;
1080 msgvec[1] = 0;
1081 } else if (getmsglist(str, msgvec, 0) < 0)
1082 return 1;
1083 if (*msgvec == 0) {
1084 if (inhook)
1085 return 0;
1086 printf("No applicable messages.\n");
1087 return 1;
1089 printf(tr(268, "Pipe to: \"%s\"\n"), cmd);
1090 stats[0] = stats[1] = 0;
1091 if ((ret = _type1(msgvec, doign, FAL0, TRU1, FAL0, cmd, stats)) == 0) {
1092 printf("\"%s\" ", cmd);
1093 if (stats[0] >= 0)
1094 printf("%lu", (long)stats[0]);
1095 else
1096 printf(tr(27, "binary"));
1097 printf("/%lu\n", (long)stats[1]);
1099 return ret;
1103 * Paginate messages, honor ignored fields.
1105 FL int
1106 more(void *v)
1108 int *msgvec = v;
1110 return _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL);
1114 * Paginate messages, even printing ignored fields.
1116 FL int
1117 More(void *v)
1119 int *msgvec = v;
1121 return _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL);
1125 * Type out messages, honor ignored fields.
1127 FL int
1128 type(void *v)
1130 int *msgvec = v;
1132 return _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL);
1136 * Type out messages, even printing ignored fields.
1138 FL int
1139 Type(void *v)
1141 int *msgvec = v;
1143 return _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL);
1147 * Show MIME-encoded message text, including all fields.
1149 FL int
1150 show(void *v)
1152 int *msgvec = v;
1154 return _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL);
1158 * Pipe messages, honor ignored fields.
1160 FL int
1161 pipecmd(void *v)
1163 char *str = v;
1164 return(pipe1(str, 1));
1167 * Pipe messages, not respecting ignored fields.
1169 FL int
1170 Pipecmd(void *v)
1172 char *str = v;
1173 return(pipe1(str, 0));
1177 * Print the top so many lines of each desired message.
1178 * The number of lines is taken from the variable "toplines"
1179 * and defaults to 5.
1181 FL int
1182 top(void *v)
1184 int *msgvec = v, *ip, c, topl, lines, empty_last;
1185 struct message *mp;
1186 char *cp, *linebuf = NULL;
1187 size_t linesize = 0;
1188 FILE *ibuf;
1190 topl = 5;
1191 cp = ok_vlook(toplines);
1192 if (cp != NULL) {
1193 topl = atoi(cp);
1194 if (topl < 0 || topl > 10000)
1195 topl = 5;
1198 #ifdef HAVE_COLOUR
1199 if (options & OPT_TTYOUT)
1200 colour_table_create(NULL); /* (salloc()s) */
1201 #endif
1202 empty_last = 1;
1203 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1204 mp = &message[*ip - 1];
1205 touch(mp);
1206 setdot(mp);
1207 did_print_dot = TRU1;
1208 if (!empty_last)
1209 printf("\n");
1210 _show_msg_overview(stdout, mp, *ip);
1211 if (mp->m_flag & MNOFROM)
1212 /* XXX top(): coloured output? */
1213 printf("From %s %s\n", fakefrom(mp),
1214 fakedate(mp->m_time));
1215 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1216 v = NULL;
1217 break;
1219 c = mp->m_lines;
1220 for (lines = 0; lines < c && UICMP(32, lines, <=, topl);
1221 ++lines) {
1222 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1223 break;
1224 puts(linebuf);
1226 for (cp = linebuf; *cp && blankchar(*cp); ++cp)
1228 empty_last = (*cp == '\0');
1232 if (linebuf != NULL)
1233 free(linebuf);
1234 return (v != NULL);
1238 * Touch all the given messages so that they will
1239 * get mboxed.
1241 FL int
1242 stouch(void *v)
1244 int *msgvec = v;
1245 int *ip;
1247 for (ip = msgvec; *ip != 0; ip++) {
1248 setdot(&message[*ip-1]);
1249 dot->m_flag |= MTOUCH;
1250 dot->m_flag &= ~MPRESERVE;
1252 * POSIX interpretation necessary.
1254 did_print_dot = TRU1;
1256 return(0);
1260 * Make sure all passed messages get mboxed.
1262 FL int
1263 mboxit(void *v)
1265 int *msgvec = v;
1266 int *ip;
1268 for (ip = msgvec; *ip != 0; ip++) {
1269 setdot(&message[*ip-1]);
1270 dot->m_flag |= MTOUCH|MBOX;
1271 dot->m_flag &= ~MPRESERVE;
1273 * POSIX interpretation necessary.
1275 did_print_dot = TRU1;
1277 return(0);
1281 * List the folders the user currently has.
1283 FL int
1284 folders(void *v)
1286 char dirname[MAXPATHLEN], *name, **argv = v;
1287 char const *cmd;
1289 if (*argv) {
1290 name = expand(*argv);
1291 if (name == NULL)
1292 return 1;
1293 } else if (! getfold(dirname, sizeof dirname)) {
1294 fprintf(stderr, tr(20, "No value set for \"folder\"\n"));
1295 return 1;
1296 } else
1297 name = dirname;
1299 if (which_protocol(name) == PROTO_IMAP) {
1300 #ifdef HAVE_IMAP
1301 imap_folders(name, *argv == NULL);
1302 #else
1303 return ccmdnotsupp(NULL);
1304 #endif
1305 } else {
1306 if ((cmd = ok_vlook(LISTER)) == NULL)
1307 cmd = XLISTER;
1308 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1310 return 0;