send.c: keep entire colour stuff on a single line..
[s-mailx.git] / cmd1.c
blobf23d0408040b75628ef8746d9b6a24eb81ce96cd
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 = &message[msgno - 1];
149 time_t datet = mp->m_time;
151 date = NULL;
152 if ((datefmt = ok_vlook(datefield)) != NULL) {
153 fp = hfield1("date", mp);/* TODO use m_date field! */
154 if (fp == NULL) {
155 datefmt = NULL;
156 goto jdate_set;
158 datet = rfctime(fp);
159 date = fakedate(datet);
160 fp = ok_vlook(datefield_markout_older);
161 i = (*datefmt != '\0');
162 if (fp != NULL)
163 i |= (*fp != '\0') ? 2 | 4 : 2;
164 /* May we strftime(3)? */
165 if (i & (1 | 4))
166 memcpy(&time_current.tc_local, localtime(&datet),
167 sizeof time_current.tc_local);
168 if ((i & 2) &&
169 /* TODO *datefield-markout-older* we accept
170 * TODO one day in the future, should be UTC
171 * TODO offset only? and Stephen Isard had
172 * TODO one week once he proposed the patch! */
173 (datet > time_current.tc_time + DATE_SECSDAY ||
174 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
175 (datet + _6M < time_current.tc_time))) {
176 #undef _6M
177 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
178 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
179 memcpy(datebuf + 4, date + 4, 7);
180 datebuf[4 + 7] = ' ';
181 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
182 datebuf[4 + 7 + 1 + 4] = '\0';
183 date = datebuf;
185 } else if ((i & 1) == 0)
186 datefmt = NULL;
187 } else if (datet == (time_t)0 && (mp->m_flag & MNOFROM) == 0) {
188 /* TODO eliminate this path, query the FROM_ date in setptr(),
189 * TODO all other codepaths do so by themselves ALREADY ?????
190 * TODO assert(mp->m_time != 0);, then
191 * TODO ALSO changes behaviour of markout-non-current */
192 _parse_from_(mp, datebuf);
193 date = datebuf;
194 } else {
195 jdate_set:
196 date = fakedate(datet);
199 isaddr = 1;
200 name = name1(mp, 0);
201 if (name != NULL && ok_blook(showto) && is_myname(skin(name))) {
202 if ((cp = hfield1("to", mp)) != NULL) {
203 name = cp;
204 isto = 1;
207 if (name == NULL) {
208 name = "";
209 isaddr = 0;
211 if (isaddr) {
212 if (ok_blook(showname))
213 name = realname(name);
214 else {
215 name = prstr(skin(name));
219 subjline = NULL;
221 /* Detect the width of the non-format characters in *headline*;
222 * like that we can simply use putc() in the next loop, since we have
223 * already calculated their column widths (TODO it's sick) */
224 wleft =
225 subjlen = scrnwidth;
227 for (fp = fmt; *fp; ++fp) {
228 if (*fp == '%') {
229 if (*++fp == '-') {
230 ++fp;
231 } else if (*fp == '+')
232 ++fp;
233 if (digitchar(*fp)) {
234 n = 0;
236 n = 10*n + *fp - '0';
237 while (++fp, digitchar(*fp));
238 subjlen -= n;
241 if (*fp == '\0')
242 break;
243 } else {
244 #ifdef HAVE_WCWIDTH
245 if (mb_cur_max > 1) {
246 wchar_t wc;
247 if ((s = mbtowc(&wc, fp, mb_cur_max)) < 0)
248 n = s = 1;
249 else if ((n = wcwidth(wc)) < 0)
250 n = 1;
251 } else
252 #endif
253 n = s = 1;
254 subjlen -= n;
255 wleft -= n;
256 while (--s > 0)
257 ++fp;
261 /* Walk *headline*, producing output */
262 for (fp = fmt; *fp; ++fp) {
263 if ((c = *fp & 0xFF) == '%') {
264 B = 0;
265 n = 0;
266 s = 1;
267 if (*++fp == '-') {
268 s = -1;
269 ++fp;
270 } else if (*fp == '+')
271 ++fp;
272 if (digitchar(*fp)) {
274 n = 10*n + *fp - '0';
275 while (++fp, digitchar(*fp));
277 if (*fp == '\0')
278 break;
280 n *= s;
281 switch ((c = *fp & 0xFF)) {
282 case '%':
283 goto jputc;
284 case '>':
285 case '<':
286 if (dot != mp)
287 c = ' ';
288 goto jputc;
289 case 'a':
290 c = _dispc(mp, attrlist);
291 jputc:
292 if (UICMP(32, ABS(n), >, wleft))
293 n = (n < 0) ? -wleft : wleft;
294 n = fprintf(f, "%*c", n, c);
295 wleft = (n >= 0) ? wleft - n : 0;
296 break;
297 case 'm':
298 if (n == 0) {
299 n = 3;
300 if (threaded)
301 for (i=msgCount; i>999; i/=10)
302 n++;
304 if (UICMP(32, ABS(n), >, wleft))
305 n = (n < 0) ? -wleft : wleft;
306 n = fprintf(f, "%*d", n, msgno);
307 wleft = (n >= 0) ? wleft - n : 0;
308 break;
309 case 'f':
310 if (n == 0) {
311 n = 18;
312 if (s < 0)
313 n = -n;
315 i = ABS(n);
316 if (i > wleft) {
317 i = wleft;
318 n = (n < 0) ? -wleft : wleft;
320 if (isto) /* XXX tr()! */
321 i -= 3;
322 n = fprintf(f, "%s%s", (isto ? "To " : ""),
323 colalign(name, i, n, &wleft));
324 if (n < 0)
325 wleft = 0;
326 else if (isto)
327 wleft -= 3;
328 break;
329 case 'd':
330 if (datefmt != NULL) {
331 i = strftime(datebuf, sizeof datebuf,
332 datefmt,
333 &time_current.tc_local);
334 if (i != 0)
335 date = datebuf;
336 else
337 fprintf(stderr, tr(174,
338 "Ignored date format, "
339 "it excesses the "
340 "target buffer "
341 "(%lu bytes)\n"),
342 (ul_it)sizeof datebuf);
343 datefmt = NULL;
345 if (n == 0)
346 n = 16;
347 if (UICMP(32, ABS(n), >, wleft))
348 n = (n < 0) ? -wleft : wleft;
349 n = fprintf(f, "%*.*s", n, n, date);
350 wleft = (n >= 0) ? wleft - n : 0;
351 break;
352 case 'l':
353 if (n == 0)
354 n = 4;
355 if (UICMP(32, ABS(n), >, wleft))
356 n = (n < 0) ? -wleft : wleft;
357 if (mp->m_xlines) {
358 n = fprintf(f, "%*ld", n, mp->m_xlines);
359 wleft = (n >= 0) ? wleft - n : 0;
360 } else {
361 n = ABS(n);
362 wleft -= n;
363 while (n-- != 0)
364 putc(' ', f);
366 break;
367 case 'o':
368 if (n == 0)
369 n = -5;
370 if (UICMP(32, ABS(n), >, wleft))
371 n = (n < 0) ? -wleft : wleft;
372 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
373 wleft = (n >= 0) ? wleft - n : 0;
374 break;
375 case 'i':
376 if (threaded) {
377 n = __putindent(f, mp, MIN(wleft,
378 scrnwidth - 60));
379 wleft = (n >= 0) ? wleft - n : 0;
381 break;
382 case 'S':
383 B = 1;
384 /*FALLTHRU*/
385 case 's':
386 if (n == 0)
387 n = subjlen - 2;
388 if (n > 0 && s < 0)
389 n = -n;
390 if (subjlen > wleft)
391 subjlen = wleft;
392 if (UICMP(32, ABS(n), >, subjlen))
393 n = (n < 0) ? -subjlen : subjlen;
394 if (B)
395 n -= (n < 0) ? -2 : 2;
396 if (n == 0)
397 break;
398 if (subjline == NULL)
399 subjline = __subject(mp, threaded,
400 yetprinted);
401 if (subjline == (char*)-1) {
402 n = fprintf(f, "%*s", n, "");
403 wleft = (n >= 0) ? wleft-n : 0;
404 } else {
405 n = fprintf(f, (B ? "\"%s\"" : "%s"),
406 colalign(subjline, ABS(n), n,
407 &wleft));
408 if (n < 0)
409 wleft = 0;
411 break;
412 case 'U':
413 #ifdef HAVE_IMAP
414 if (n == 0)
415 n = 9;
416 if (UICMP(32, ABS(n), >, wleft))
417 n = (n < 0) ? -wleft : wleft;
418 n = fprintf(f, "%*lu", n, mp->m_uid);
419 wleft = (n >= 0) ? wleft - n : 0;
420 break;
421 #else
422 c = '?';
423 goto jputc;
424 #endif
425 case 'e':
426 if (n == 0)
427 n = 2;
428 if (UICMP(32, ABS(n), >, wleft))
429 n = (n < 0) ? -wleft : wleft;
430 n = fprintf(f, "%*u", n,
431 threaded == 1 ? mp->m_level : 0);
432 wleft = (n >= 0) ? wleft - n : 0;
433 break;
434 case 't':
435 if (n == 0) {
436 n = 3;
437 if (threaded)
438 for (i=msgCount; i>999; i/=10)
439 n++;
441 if (UICMP(32, ABS(n), >, wleft))
442 n = (n < 0) ? -wleft : wleft;
443 n = fprintf(f, "%*ld", n,
444 threaded ? mp->m_threadpos : msgno);
445 wleft = (n >= 0) ? wleft - n : 0;
446 break;
447 case '$':
448 #ifdef HAVE_SPAM
449 if (n == 0)
450 n = 4;
451 if (UICMP(32, ABS(n), >, wleft))
452 n = (n < 0) ? -wleft : wleft;
453 { char buf[16];
454 snprintf(buf, sizeof buf, "%u.%u",
455 (mp->m_spamscore >> 8),
456 (mp->m_spamscore & 0xFF));
457 n = fprintf(f, "%*s", n, buf);
458 wleft = (n >= 0) ? wleft - n : 0;
460 #else
461 c = '?';
462 goto jputc;
463 #endif
466 if (wleft <= 0)
467 break;
468 } else
469 putc(c, f);
471 putc('\n', f);
473 if (subjline != NULL && subjline != (char*)-1)
474 free(subjline);
477 static char *
478 __subject_trim(char *s)
480 struct {
481 ui8_t len;
482 char dat[7];
483 } const *pp, ignored[] = { /* TODO make ignore list configurable */
484 { 3, "re:" }, { 4, "fwd:" },
485 { 3, "aw:" }, { 5, "antw:" },
486 { 0, "" }
488 jouter:
489 while (*s != '\0') {
490 while (spacechar(*s))
491 ++s;
492 /* TODO While it is maybe ok not to MIME decode these, we
493 * TODO should skip =?..?= at the beginning? */
494 for (pp = ignored; pp->len > 0; ++pp)
495 if (is_asccaseprefix(pp->dat, s)) {
496 s += pp->len;
497 goto jouter;
499 break;
501 return s;
504 static char *
505 __subject(struct message *mp, bool_t threaded, size_t yetprinted)
507 /* XXX NOTE: because of efficiency reasons we simply ignore any encoded
508 * XXX parts and use ASCII case-insensitive comparison */
509 struct str in, out;
510 struct message *xmp;
511 char *rv = (char*)-1, *ms, *mso, *os;
513 if ((ms = hfield1("subject", mp)) == NULL)
514 goto jleave;
516 if (!threaded || mp->m_level == 0)
517 goto jconv;
519 /* In a display thread - check wether this message uses the same
520 * Subject: as it's parent or elder neighbour, suppress printing it if
521 * this is the case. To extend this a bit, ignore any leading Re: or
522 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
523 mso = __subject_trim(ms);
524 for (xmp = mp; (xmp = prev_in_thread(xmp)) != NULL && yetprinted-- > 0;)
525 if (visible(xmp) && (os = hfield1("subject", xmp)) != NULL &&
526 asccasecmp(mso, __subject_trim(os)) == 0)
527 goto jleave;
528 jconv:
529 in.s = ms;
530 in.l = strlen(ms);
531 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
532 rv = out.s;
533 jleave:
534 return rv;
537 static int
538 __putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
540 struct message *mq;
541 int *us, indlvl, indw, i, important = MNEW|MFLAGGED;
542 char *cs;
544 if (mp->m_level == 0 || maxwidth == 0)
545 return 0;
546 cs = ac_alloc(mp->m_level);
547 us = ac_alloc(mp->m_level * sizeof *us);
549 i = mp->m_level - 1;
550 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
551 if (mp->m_parent && mp->m_parent->m_flag & important)
552 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
553 else
554 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
555 cs[i] = '+';
556 } else {
557 if (mp->m_parent && mp->m_parent->m_flag & important)
558 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
559 else
560 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
561 cs[i] = '\\';
564 mq = mp->m_parent;
565 for (i = mp->m_level - 2; i >= 0; i--) {
566 if (mq) {
567 if (UICMP(32, i, >, mq->m_level - 1)) {
568 us[i] = cs[i] = ' ';
569 continue;
571 if (mq->m_younger) {
572 if (mq->m_parent &&
573 mq->m_parent->m_flag&important)
574 us[i] = 0x2503;
575 else
576 us[i] = 0x2502;
577 cs[i] = '|';
578 } else
579 us[i] = cs[i] = ' ';
580 mq = mq->m_parent;
581 } else
582 us[i] = cs[i] = ' ';
585 --maxwidth;
586 for (indlvl = indw = 0; (uc_it)indlvl < mp->m_level &&
587 indw < maxwidth; ++indlvl) {
588 if (indw < maxwidth - 1)
589 indw += (int)putuc(us[indlvl], cs[indlvl] & 0377, fp);
590 else
591 indw += (int)putuc(0x21B8, '^', fp);
593 indw += /*putuc(0x261E, fp)*/putc('>', fp) != EOF;
595 ac_free(us);
596 ac_free(cs);
597 return indw;
600 FL int
601 ccmdnotsupp(void *v) /* TODO -> lex.c */
603 (void)v;
604 fprintf(stderr, tr(10, "The requested feature is not compiled in\n"));
605 return (1);
608 FL int
609 headers(void *v)
611 ui32_t flag;
612 int *msgvec = v, g, k, n, mesg, size, lastg = 1;
613 struct message *mp, *mq, *lastmq = NULL;
614 enum mflag fl = MNEW|MFLAGGED;
616 time_current_update(&time_current, FAL0);
618 flag = 0;
619 size = screensize();
620 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
621 if (screen < 0)
622 screen = 0;
623 k = screen * size;
624 if (k >= msgCount)
625 k = msgCount - size;
626 if (k < 0)
627 k = 0;
629 if (mb.mb_threaded == 0) {
630 g = 0;
631 mq = &message[0];
632 for (mp = &message[0]; mp < &message[msgCount]; mp++)
633 if (visible(mp)) {
634 if (g % size == 0)
635 mq = mp;
636 if (mp->m_flag&fl) {
637 lastg = g;
638 lastmq = mq;
640 if ((n > 0 && mp == &message[n-1]) ||
641 (n == 0 && g == k) ||
642 (n == -2 && g == k + size &&
643 lastmq) ||
644 (n < 0 && g >= k &&
645 (mp->m_flag & fl) != 0))
646 break;
647 g++;
649 if (lastmq && (n==-2 || (n==-1 && mp == &message[msgCount]))) {
650 g = lastg;
651 mq = lastmq;
653 screen = g / size;
654 mp = mq;
655 mesg = mp - &message[0];
656 if (dot != &message[n-1]) {
657 for (mq = mp; mq < &message[msgCount]; mq++)
658 if (visible(mq)) {
659 setdot(mq);
660 break;
663 #ifdef HAVE_IMAP
664 if (mb.mb_type == MB_IMAP)
665 imap_getheaders(mesg+1, mesg + size);
666 #endif
667 srelax_hold();
668 for (; mp < &message[msgCount]; mp++) {
669 mesg++;
670 if (!visible(mp))
671 continue;
672 if (UICMP(32, flag++, >=, size))
673 break;
674 _print_head(0, mesg, stdout, 0);
675 srelax();
677 srelax_rele();
678 } else { /* threaded */
679 g = 0;
680 mq = threadroot;
681 for (mp = threadroot; mp; mp = next_in_thread(mp))
682 if (visible(mp) && (mp->m_collapsed <= 0 ||
683 mp == &message[n-1])) {
684 if (g % size == 0)
685 mq = mp;
686 if (mp->m_flag&fl) {
687 lastg = g;
688 lastmq = mq;
690 if ((n > 0 && mp == &message[n-1]) ||
691 (n == 0 && g == k) ||
692 (n == -2 && g == k + size &&
693 lastmq) ||
694 (n < 0 && g >= k &&
695 (mp->m_flag & fl) != 0))
696 break;
697 g++;
699 if (lastmq && (n==-2 || (n==-1 && mp==&message[msgCount]))) {
700 g = lastg;
701 mq = lastmq;
703 screen = g / size;
704 mp = mq;
705 if (dot != &message[n-1]) {
706 for (mq = mp; mq; mq = next_in_thread(mq))
707 if (visible(mq) && mq->m_collapsed <= 0) {
708 setdot(mq);
709 break;
712 srelax_hold();
713 while (mp) {
714 if (visible(mp) && (mp->m_collapsed <= 0 ||
715 mp == &message[n-1])) {
716 if (UICMP(32, flag++, >=, size))
717 break;
718 _print_head(flag - 1, mp - &message[0] + 1,
719 stdout, mb.mb_threaded);
720 srelax();
722 mp = next_in_thread(mp);
724 srelax_rele();
726 if (!flag)
727 printf(tr(6, "No more mail.\n"));
728 return !flag;
732 * Scroll to the next/previous screen
734 FL int
735 scroll(void *v)
737 return scroll1(v, 0);
740 FL int
741 Scroll(void *v)
743 return scroll1(v, 1);
746 static int
747 scroll1(char *arg, int onlynew)
749 int size;
750 int cur[1];
752 cur[0] = onlynew ? -1 : 0;
753 size = screensize();
754 switch (*arg) {
755 case '1': case '2': case '3': case '4': case '5':
756 case '6': case '7': case '8': case '9': case '0':
757 screen = atoi(arg);
758 goto scroll_forward;
759 case '\0':
760 screen++;
761 goto scroll_forward;
762 case '$':
763 screen = msgCount / size;
764 goto scroll_forward;
765 case '+':
766 if (arg[1] == '\0')
767 screen++;
768 else
769 screen += atoi(arg + 1);
770 scroll_forward:
771 if (screen * size > msgCount) {
772 screen = msgCount / size;
773 printf(tr(7, "On last screenful of messages\n"));
775 break;
777 case '-':
778 if (arg[1] == '\0')
779 screen--;
780 else
781 screen -= atoi(arg + 1);
782 if (screen < 0) {
783 screen = 0;
784 printf(tr(8, "On first screenful of messages\n"));
786 if (cur[0] == -1)
787 cur[0] = -2;
788 break;
790 default:
791 printf(tr(9, "Unrecognized scrolling command \"%s\"\n"), arg);
792 return(1);
794 return(headers(cur));
798 * Compute screen size.
800 FL int
801 screensize(void)
803 int s;
804 char *cp;
806 if ((cp = ok_vlook(screen)) != NULL && (s = atoi(cp)) > 0)
807 return s;
808 return scrnheight - 4;
811 static sigjmp_buf _cmd1_pipejmp;
813 /*ARGSUSED*/
814 static void
815 _cmd1_onpipe(int signo)
817 UNUSED(signo);
818 siglongjmp(_cmd1_pipejmp, 1);
822 * Print out the headlines for each message
823 * in the passed message list.
825 FL int
826 from(void *v)
828 int *msgvec = v, *ip, n;
829 char *cp;
830 FILE *volatile obuf = stdout;
832 time_current_update(&time_current, FAL0);
834 /* TODO unfixable memory leaks still */
835 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
836 for (n = 0, ip = msgvec; *ip; ip++)
837 n++;
838 if (n > (*cp == '\0' ? screensize() : atoi((char*)cp)) + 3) {
839 char const *p;
840 if (sigsetjmp(_cmd1_pipejmp, 1))
841 goto endpipe;
842 p = get_pager();
843 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
844 perror(p);
845 obuf = stdout;
846 cp=NULL;
847 } else
848 safe_signal(SIGPIPE, _cmd1_onpipe);
851 for (n = 0, ip = msgvec; *ip != 0; ip++)
852 _print_head((size_t)n++, *ip, obuf, mb.mb_threaded);
853 if (--ip >= msgvec)
854 setdot(&message[*ip - 1]);
855 endpipe:
856 if (obuf != stdout) {
857 safe_signal(SIGPIPE, SIG_IGN);
858 Pclose(obuf, TRU1);
859 safe_signal(SIGPIPE, dflpipe);
861 return(0);
864 static int
865 _dispc(struct message *mp, const char *a)
867 int i = ' ';
870 * Bletch!
872 if ((mp->m_flag & (MREAD|MNEW)) == MREAD)
873 i = a[3];
874 if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
875 i = a[2];
876 if (mp->m_flag & MANSWERED)
877 i = a[8];
878 if (mp->m_flag & MDRAFTED)
879 i = a[9];
880 if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
881 i = a[0];
882 if ((mp->m_flag & (MREAD|MNEW)) == 0)
883 i = a[1];
884 if (mp->m_flag & MSPAM)
885 i = a[12];
886 if (mp->m_flag & MSAVED)
887 i = a[4];
888 if (mp->m_flag & MPRESERVE)
889 i = a[5];
890 if (mp->m_flag & (MBOX|MBOXED))
891 i = a[6];
892 if (mp->m_flag & MFLAGGED)
893 i = a[7];
894 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
895 i = a[11];
896 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
897 i = a[10];
898 return i;
901 FL void
902 print_headers(size_t bottom, size_t topx)
904 size_t printed;
906 #ifdef HAVE_IMAP
907 if (mb.mb_type == MB_IMAP)
908 imap_getheaders(bottom, topx);
909 #endif
910 time_current_update(&time_current, FAL0);
912 for (printed = 0; bottom <= topx; ++bottom)
913 if (visible(&message[bottom - 1]))
914 _print_head(printed++, bottom, stdout, 0);
918 * Print out the value of dot.
920 /*ARGSUSED*/
921 FL int
922 pdot(void *v)
924 (void)v;
925 printf("%d\n", (int)(dot - &message[0] + 1));
926 return(0);
930 * Type out the messages requested.
932 static sigjmp_buf pipestop;
934 /*ARGSUSED*/
935 static void
936 brokpipe(int signo)
938 (void)signo;
939 siglongjmp(pipestop, 1);
942 static int
943 _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
944 bool_t dodecode, char *cmd, off_t *tstats)
946 enum sendaction const action = ((dopipe && ok_blook(piperaw))
947 ? SEND_MBOX : dodecode
948 ? SEND_SHOW : doign
949 ? SEND_TODISP : SEND_TODISP_ALL);
950 bool_t const formfeed = (dopipe && ok_blook(page));
951 off_t mstats[2];
952 int *ip;
953 struct message *mp;
954 char const *cp;
955 FILE * volatile obuf;
957 obuf = stdout;
958 if (sigsetjmp(pipestop, 1))
959 goto close_pipe;
960 if (dopipe) {
961 if ((cp = ok_vlook(SHELL)) == NULL)
962 cp = XSHELL;
963 if ((obuf = Popen(cmd, "w", cp, 1)) == NULL) {
964 perror(cmd);
965 obuf = stdout;
966 } else
967 safe_signal(SIGPIPE, brokpipe);
968 } else if ((options & OPT_TTYOUT) &&
969 (dopage || (cp = ok_vlook(crt)) != NULL)) {
970 char const *pager = NULL;
971 size_t nlines = 0;
972 if (!dopage) {
973 for (ip = msgvec; *ip &&
974 PTRCMP(ip - msgvec, <, msgCount);
975 ++ip) {
976 if (!(message[*ip - 1].m_have & HAVE_BODY)) {
977 if ((get_body(&message[*ip - 1])) !=
978 OKAY)
979 return 1;
981 nlines += message[*ip - 1].m_lines;
984 if (dopage || UICMP(z, nlines, >,
985 (*cp != '\0' ? atoi(cp) : realscreenheight))) {
986 pager = get_pager();
987 #ifdef HAVE_SETENV
988 if ((cp = getenv("LESS")) == NULL) /* XXX not here! */
989 setenv("LESS", "FRXi", 0); /* XXX add env. */
990 #endif
991 obuf = Popen(pager, "w", NULL, 1);
992 #ifdef HAVE_SETENV
993 if (cp == NULL)
994 unsetenv("LESS"); /* XXX to Popen() etc.?!! */
995 #endif
996 if (obuf == NULL) {
997 perror(pager);
998 obuf = stdout;
999 pager = NULL;
1000 } else
1001 safe_signal(SIGPIPE, brokpipe);
1003 #ifdef HAVE_COLOUR
1004 if (action != SEND_MBOX)
1005 colour_table_create(pager); /* (salloc()s!) */
1006 #endif
1008 #ifdef HAVE_COLOUR
1009 else if ((options & OPT_TTYOUT) && action != SEND_MBOX)
1010 colour_table_create(NULL); /* (salloc()s!) */
1011 #endif
1013 /* This may jump, in which case srelax_rele() wouldn't be called, but
1014 * it shouldn't matter, because we -- then -- directly reenter the
1015 * lex.c:commands() loop, which sreset()s */
1016 srelax_hold();
1017 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
1018 mp = &message[*ip - 1];
1019 touch(mp);
1020 setdot(mp);
1021 uncollapse1(mp, 1);
1022 if (!dopipe) {
1023 if (ip != msgvec)
1024 fprintf(obuf, "\n");
1025 if (action != SEND_MBOX)
1026 _show_msg_overview(obuf, mp, *ip);
1028 sendmp(mp, obuf, (doign ? ignore : NULL), NULL, action, mstats);
1029 srelax();
1030 if (formfeed)
1031 putc('\f', obuf);
1032 if (tstats) {
1033 tstats[0] += mstats[0];
1034 tstats[1] += mstats[1];
1037 srelax_rele();
1038 close_pipe:
1039 if (obuf != stdout) {
1040 /* Ignore SIGPIPE so it can't cause a duplicate close */
1041 safe_signal(SIGPIPE, SIG_IGN);
1042 colour_reset(obuf); /* XXX hacky; only here because we still jump */
1043 Pclose(obuf, TRU1);
1044 safe_signal(SIGPIPE, dflpipe);
1046 return 0;
1050 * Pipe the messages requested.
1052 static int
1053 pipe1(char *str, int doign)
1055 char *cmd;
1056 int *msgvec, ret;
1057 off_t stats[2];
1058 bool_t f;
1060 /*LINTED*/
1061 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1062 if ((cmd = laststring(str, &f, 1)) == NULL) {
1063 cmd = ok_vlook(cmd);
1064 if (cmd == NULL || *cmd == '\0') {
1065 fputs(tr(16, "variable cmd not set\n"), stderr);
1066 return 1;
1069 if (!f) {
1070 *msgvec = first(0, MMNORM);
1071 if (*msgvec == 0) {
1072 if (inhook)
1073 return 0;
1074 puts(tr(18, "No messages to pipe."));
1075 return 1;
1077 msgvec[1] = 0;
1078 } else if (getmsglist(str, msgvec, 0) < 0)
1079 return 1;
1080 if (*msgvec == 0) {
1081 if (inhook)
1082 return 0;
1083 printf("No applicable messages.\n");
1084 return 1;
1086 printf(tr(268, "Pipe to: \"%s\"\n"), cmd);
1087 stats[0] = stats[1] = 0;
1088 if ((ret = _type1(msgvec, doign, FAL0, TRU1, FAL0, cmd, stats)) == 0) {
1089 printf("\"%s\" ", cmd);
1090 if (stats[0] >= 0)
1091 printf("%lu", (long)stats[0]);
1092 else
1093 printf(tr(27, "binary"));
1094 printf("/%lu\n", (long)stats[1]);
1096 return ret;
1100 * Paginate messages, honor ignored fields.
1102 FL int
1103 more(void *v)
1105 int *msgvec = v;
1107 return _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL);
1111 * Paginate messages, even printing ignored fields.
1113 FL int
1114 More(void *v)
1116 int *msgvec = v;
1118 return _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL);
1122 * Type out messages, honor ignored fields.
1124 FL int
1125 type(void *v)
1127 int *msgvec = v;
1129 return _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL);
1133 * Type out messages, even printing ignored fields.
1135 FL int
1136 Type(void *v)
1138 int *msgvec = v;
1140 return _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL);
1144 * Show MIME-encoded message text, including all fields.
1146 FL int
1147 show(void *v)
1149 int *msgvec = v;
1151 return _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL);
1155 * Pipe messages, honor ignored fields.
1157 FL int
1158 pipecmd(void *v)
1160 char *str = v;
1161 return(pipe1(str, 1));
1164 * Pipe messages, not respecting ignored fields.
1166 FL int
1167 Pipecmd(void *v)
1169 char *str = v;
1170 return(pipe1(str, 0));
1174 * Print the top so many lines of each desired message.
1175 * The number of lines is taken from the variable "toplines"
1176 * and defaults to 5.
1178 FL int
1179 top(void *v)
1181 int *msgvec = v, *ip, c, topl, lines, empty_last;
1182 struct message *mp;
1183 char *cp, *linebuf = NULL;
1184 size_t linesize = 0;
1185 FILE *ibuf;
1187 topl = 5;
1188 cp = ok_vlook(toplines);
1189 if (cp != NULL) {
1190 topl = atoi(cp);
1191 if (topl < 0 || topl > 10000)
1192 topl = 5;
1195 #ifdef HAVE_COLOUR
1196 if (options & OPT_TTYOUT)
1197 colour_table_create(NULL); /* (salloc()s) */
1198 #endif
1199 empty_last = 1;
1200 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1201 mp = &message[*ip - 1];
1202 touch(mp);
1203 setdot(mp);
1204 did_print_dot = TRU1;
1205 if (!empty_last)
1206 printf("\n");
1207 _show_msg_overview(stdout, mp, *ip);
1208 if (mp->m_flag & MNOFROM)
1209 /* XXX top(): coloured output? */
1210 printf("From %s %s\n", fakefrom(mp),
1211 fakedate(mp->m_time));
1212 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1213 v = NULL;
1214 break;
1216 c = mp->m_lines;
1217 for (lines = 0; lines < c && UICMP(32, lines, <=, topl);
1218 ++lines) {
1219 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1220 break;
1221 puts(linebuf);
1223 for (cp = linebuf; *cp && blankchar(*cp); ++cp)
1225 empty_last = (*cp == '\0');
1229 if (linebuf != NULL)
1230 free(linebuf);
1231 return (v != NULL);
1235 * Touch all the given messages so that they will
1236 * get mboxed.
1238 FL int
1239 stouch(void *v)
1241 int *msgvec = v;
1242 int *ip;
1244 for (ip = msgvec; *ip != 0; ip++) {
1245 setdot(&message[*ip-1]);
1246 dot->m_flag |= MTOUCH;
1247 dot->m_flag &= ~MPRESERVE;
1249 * POSIX interpretation necessary.
1251 did_print_dot = TRU1;
1253 return(0);
1257 * Make sure all passed messages get mboxed.
1259 FL int
1260 mboxit(void *v)
1262 int *msgvec = v;
1263 int *ip;
1265 for (ip = msgvec; *ip != 0; ip++) {
1266 setdot(&message[*ip-1]);
1267 dot->m_flag |= MTOUCH|MBOX;
1268 dot->m_flag &= ~MPRESERVE;
1270 * POSIX interpretation necessary.
1272 did_print_dot = TRU1;
1274 return(0);
1278 * List the folders the user currently has.
1280 FL int
1281 folders(void *v)
1283 char dirname[MAXPATHLEN], *name, **argv = v;
1284 char const *cmd;
1286 if (*argv) {
1287 name = expand(*argv);
1288 if (name == NULL)
1289 return 1;
1290 } else if (! getfold(dirname, sizeof dirname)) {
1291 fprintf(stderr, tr(20, "No value set for \"folder\"\n"));
1292 return 1;
1293 } else
1294 name = dirname;
1296 if (which_protocol(name) == PROTO_IMAP) {
1297 #ifdef HAVE_IMAP
1298 imap_folders(name, *argv == NULL);
1299 #else
1300 return ccmdnotsupp(NULL);
1301 #endif
1302 } else {
1303 if ((cmd = ok_vlook(LISTER)) == NULL)
1304 cmd = XLISTER;
1305 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1307 return 0;