Move get_pager() cmd1.c -> auxlily.c
[s-mailx.git] / cmd1.c
blobe2cb3876d56124bc32804cd226ddbf90d7f7b3a8
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 - 2013 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 fprintf(obuf, tr(17, "[-- Message %2d -- %lu lines, %lu bytes --]:\n"),
82 msg_no, (ul_it)mp->m_lines, (ul_it)mp->m_size);
85 static void
86 _parse_from_(struct message *mp, char date[FROM_DATEBUF])
88 FILE *ibuf;
89 int hlen;
90 char *hline = NULL;
91 size_t hsize = 0;
93 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
94 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
95 (void)extract_date_from_from_(hline, hlen, date);
96 if (hline != NULL)
97 free(hline);
100 static void
101 _print_head(size_t yetprinted, int msgno, FILE *f, bool_t threaded)
103 char attrlist[30], *cp;
104 char const *fmt;
106 if ((cp = ok_vlook(attrlist)) != NULL) {
107 size_t i = strlen(cp);
108 if (UICMP(32, i, >, sizeof attrlist - 1))
109 i = (int)sizeof attrlist - 1;
110 memcpy(attrlist, cp, i);
111 } else if (ok_blook(bsdcompat) || ok_blook(bsdflags) ||
112 getenv("SYSV3") != NULL) {
113 char const bsdattr[] = "NU *HMFAT+-$";
114 memcpy(attrlist, bsdattr, sizeof bsdattr - 1);
115 } else {
116 char const pattr[] = "NUROSPMFAT+-$";
117 memcpy(attrlist, pattr, sizeof pattr - 1);
120 if ((fmt = ok_vlook(headline)) == NULL) {
121 fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
122 ? "%>%a%m %-20f %16d %3l/%-5o %i%-S"
123 : "%>%a%m %-18f %16d %4l/%-5o %i%-s");
126 __hprf(yetprinted, fmt, msgno, f, threaded, attrlist);
129 static void
130 __hprf(size_t yetprinted, char const *fmt, int msgno, FILE *f, bool_t threaded,
131 char const *attrlist)
133 char datebuf[FROM_DATEBUF], *cp, *subjline;
134 char const *datefmt, *date, *name, *fp;
135 int B, c, i, n, s, wleft, subjlen, isto = 0, isaddr = 0;
136 struct message *mp = &message[msgno - 1];
137 time_t datet = mp->m_time;
139 date = NULL;
140 if ((datefmt = ok_vlook(datefield)) != NULL) {
141 fp = hfield1("date", mp);/* TODO use m_date field! */
142 if (fp == NULL) {
143 datefmt = NULL;
144 goto jdate_set;
146 datet = rfctime(fp);
147 date = fakedate(datet);
148 fp = ok_vlook(datefield_markout_older);
149 i = (*datefmt != '\0');
150 if (fp != NULL)
151 i |= (*fp != '\0') ? 2 | 4 : 2;
152 /* May we strftime(3)? */
153 if (i & (1 | 4))
154 memcpy(&time_current.tc_local, localtime(&datet),
155 sizeof time_current.tc_local);
156 if ((i & 2) &&
157 /* TODO *datefield-markout-older* we accept
158 * TODO one day in the future, should be UTC
159 * TODO offset only? and Stephen Isard had
160 * TODO one week once he proposed the patch! */
161 (datet > time_current.tc_time + DATE_SECSDAY ||
162 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
163 (datet + _6M < time_current.tc_time))) {
164 #undef _6M
165 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
166 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
167 memcpy(datebuf + 4, date + 4, 7);
168 datebuf[4 + 7] = ' ';
169 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
170 datebuf[4 + 7 + 1 + 4] = '\0';
171 date = datebuf;
173 } else if ((i & 1) == 0)
174 datefmt = NULL;
175 } else if (datet == (time_t)0 && (mp->m_flag & MNOFROM) == 0) {
176 /* TODO eliminate this path, query the FROM_ date in setptr(),
177 * TODO all other codepaths do so by themselves ALREADY ?????
178 * TODO assert(mp->m_time != 0);, then
179 * TODO ALSO changes behaviour of markout-non-current */
180 _parse_from_(mp, datebuf);
181 date = datebuf;
182 } else {
183 jdate_set:
184 date = fakedate(datet);
187 isaddr = 1;
188 name = name1(mp, 0);
189 if (name != NULL && ok_blook(showto) && is_myname(skin(name))) {
190 if ((cp = hfield1("to", mp)) != NULL) {
191 name = cp;
192 isto = 1;
195 if (name == NULL) {
196 name = "";
197 isaddr = 0;
199 if (isaddr) {
200 if (ok_blook(showname))
201 name = realname(name);
202 else {
203 name = prstr(skin(name));
207 subjline = NULL;
209 /* Detect the width of the non-format characters in *headline*;
210 * like that we can simply use putc() in the next loop, since we have
211 * already calculated their column widths (TODO it's sick) */
212 wleft =
213 subjlen = scrnwidth;
215 for (fp = fmt; *fp; ++fp) {
216 if (*fp == '%') {
217 if (*++fp == '-') {
218 ++fp;
219 } else if (*fp == '+')
220 ++fp;
221 if (digitchar(*fp)) {
222 n = 0;
224 n = 10*n + *fp - '0';
225 while (++fp, digitchar(*fp));
226 subjlen -= n;
229 if (*fp == '\0')
230 break;
231 } else {
232 #ifdef HAVE_WCWIDTH
233 if (mb_cur_max > 1) {
234 wchar_t wc;
235 if ((s = mbtowc(&wc, fp, mb_cur_max)) < 0)
236 n = s = 1;
237 else if ((n = wcwidth(wc)) < 0)
238 n = 1;
239 } else
240 #endif
241 n = s = 1;
242 subjlen -= n;
243 wleft -= n;
244 while (--s > 0)
245 ++fp;
249 /* Walk *headline*, producing output */
250 for (fp = fmt; *fp; ++fp) {
251 if ((c = *fp & 0xFF) == '%') {
252 B = 0;
253 n = 0;
254 s = 1;
255 if (*++fp == '-') {
256 s = -1;
257 ++fp;
258 } else if (*fp == '+')
259 ++fp;
260 if (digitchar(*fp)) {
262 n = 10*n + *fp - '0';
263 while (++fp, digitchar(*fp));
265 if (*fp == '\0')
266 break;
268 n *= s;
269 switch ((c = *fp & 0xFF)) {
270 case '%':
271 goto jputc;
272 case '>':
273 case '<':
274 if (dot != mp)
275 c = ' ';
276 goto jputc;
277 case 'a':
278 c = _dispc(mp, attrlist);
279 jputc:
280 if (UICMP(32, ABS(n), >, wleft))
281 n = (n < 0) ? -wleft : wleft;
282 n = fprintf(f, "%*c", n, c);
283 wleft = (n >= 0) ? wleft - n : 0;
284 break;
285 case 'm':
286 if (n == 0) {
287 n = 3;
288 if (threaded)
289 for (i=msgCount; i>999; i/=10)
290 n++;
292 if (UICMP(32, ABS(n), >, wleft))
293 n = (n < 0) ? -wleft : wleft;
294 n = fprintf(f, "%*d", n, msgno);
295 wleft = (n >= 0) ? wleft - n : 0;
296 break;
297 case 'f':
298 if (n == 0) {
299 n = 18;
300 if (s < 0)
301 n = -n;
303 i = ABS(n);
304 if (i > wleft) {
305 i = wleft;
306 n = (n < 0) ? -wleft : wleft;
308 if (isto) /* XXX tr()! */
309 i -= 3;
310 n = fprintf(f, "%s%s", (isto ? "To " : ""),
311 colalign(name, i, n, &wleft));
312 if (n < 0)
313 wleft = 0;
314 else if (isto)
315 wleft -= 3;
316 break;
317 case 'd':
318 if (datefmt != NULL) {
319 i = strftime(datebuf, sizeof datebuf,
320 datefmt,
321 &time_current.tc_local);
322 if (i != 0)
323 date = datebuf;
324 else
325 fprintf(stderr, tr(174,
326 "Ignored date format, "
327 "it excesses the "
328 "target buffer "
329 "(%lu bytes)\n"),
330 (ul_it)sizeof datebuf);
331 datefmt = NULL;
333 if (n == 0)
334 n = 16;
335 if (UICMP(32, ABS(n), >, wleft))
336 n = (n < 0) ? -wleft : wleft;
337 n = fprintf(f, "%*.*s", n, n, date);
338 wleft = (n >= 0) ? wleft - n : 0;
339 break;
340 case 'l':
341 if (n == 0)
342 n = 4;
343 if (UICMP(32, ABS(n), >, wleft))
344 n = (n < 0) ? -wleft : wleft;
345 if (mp->m_xlines) {
346 n = fprintf(f, "%*ld", n, mp->m_xlines);
347 wleft = (n >= 0) ? wleft - n : 0;
348 } else {
349 n = ABS(n);
350 wleft -= n;
351 while (n-- != 0)
352 putc(' ', f);
354 break;
355 case 'o':
356 if (n == 0)
357 n = -5;
358 if (UICMP(32, ABS(n), >, wleft))
359 n = (n < 0) ? -wleft : wleft;
360 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
361 wleft = (n >= 0) ? wleft - n : 0;
362 break;
363 case 'i':
364 if (threaded) {
365 n = __putindent(f, mp, MIN(wleft,
366 scrnwidth - 60));
367 wleft = (n >= 0) ? wleft - n : 0;
369 break;
370 case 'S':
371 B = 1;
372 /*FALLTHRU*/
373 case 's':
374 if (n == 0)
375 n = subjlen - 2;
376 if (n > 0 && s < 0)
377 n = -n;
378 if (subjlen > wleft)
379 subjlen = wleft;
380 if (UICMP(32, ABS(n), >, subjlen))
381 n = (n < 0) ? -subjlen : subjlen;
382 if (B)
383 n -= (n < 0) ? -2 : 2;
384 if (n == 0)
385 break;
386 if (subjline == NULL)
387 subjline = __subject(mp, threaded,
388 yetprinted);
389 if (subjline == (char*)-1) {
390 n = fprintf(f, "%*s", n, "");
391 wleft = (n >= 0) ? wleft-n : 0;
392 } else {
393 n = fprintf(f, (B ? "\"%s\"" : "%s"),
394 colalign(subjline, ABS(n), n,
395 &wleft));
396 if (n < 0)
397 wleft = 0;
399 break;
400 case 'U':
401 #ifdef HAVE_IMAP
402 if (n == 0)
403 n = 9;
404 if (UICMP(32, ABS(n), >, wleft))
405 n = (n < 0) ? -wleft : wleft;
406 n = fprintf(f, "%*lu", n, mp->m_uid);
407 wleft = (n >= 0) ? wleft - n : 0;
408 break;
409 #else
410 c = '?';
411 goto jputc;
412 #endif
413 case 'e':
414 if (n == 0)
415 n = 2;
416 if (UICMP(32, ABS(n), >, wleft))
417 n = (n < 0) ? -wleft : wleft;
418 n = fprintf(f, "%*u", n,
419 threaded == 1 ? mp->m_level : 0);
420 wleft = (n >= 0) ? wleft - n : 0;
421 break;
422 case 't':
423 if (n == 0) {
424 n = 3;
425 if (threaded)
426 for (i=msgCount; i>999; i/=10)
427 n++;
429 if (UICMP(32, ABS(n), >, wleft))
430 n = (n < 0) ? -wleft : wleft;
431 n = fprintf(f, "%*ld", n,
432 threaded ? mp->m_threadpos : msgno);
433 wleft = (n >= 0) ? wleft - n : 0;
434 break;
435 case '$':
436 #ifdef HAVE_SPAM
437 if (n == 0)
438 n = 4;
439 if (UICMP(32, ABS(n), >, wleft))
440 n = (n < 0) ? -wleft : wleft;
441 { char buf[16];
442 snprintf(buf, sizeof buf, "%u.%u",
443 (mp->m_spamscore >> 8),
444 (mp->m_spamscore & 0xFF));
445 n = fprintf(f, "%*s", n, buf);
446 wleft = (n >= 0) ? wleft - n : 0;
448 #else
449 c = '?';
450 goto jputc;
451 #endif
454 if (wleft <= 0)
455 break;
456 } else
457 putc(c, f);
459 putc('\n', f);
461 if (subjline != NULL && subjline != (char*)-1)
462 free(subjline);
465 static char *
466 __subject_trim(char *s)
468 struct {
469 ui8_t len;
470 char dat[7];
471 } const *pp, ignored[] = { /* TODO make ignore list configurable */
472 { 3, "re:" }, { 4, "fwd:" },
473 { 3, "aw:" }, { 5, "antw:" },
474 { 0, "" }
476 jouter:
477 while (*s != '\0') {
478 while (spacechar(*s))
479 ++s;
480 /* TODO While it is maybe ok not to MIME decode these, we
481 * TODO should skip =?..?= at the beginning? */
482 for (pp = ignored; pp->len > 0; ++pp)
483 if (is_asccaseprefix(pp->dat, s)) {
484 s += pp->len;
485 goto jouter;
487 break;
489 return s;
492 static char *
493 __subject(struct message *mp, bool_t threaded, size_t yetprinted)
495 /* XXX NOTE: because of efficiency reasons we simply ignore any encoded
496 * XXX parts and use ASCII case-insensitive comparison */
497 struct str in, out;
498 struct message *xmp;
499 char *rv = (char*)-1, *ms, *mso, *os;
501 if ((ms = hfield1("subject", mp)) == NULL)
502 goto jleave;
504 if (!threaded || mp->m_level == 0)
505 goto jconv;
507 /* In a display thread - check wether this message uses the same
508 * Subject: as it's parent or elder neighbour, suppress printing it if
509 * this is the case. To extend this a bit, ignore any leading Re: or
510 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
511 mso = __subject_trim(ms);
512 for (xmp = mp; (xmp = prev_in_thread(xmp)) != NULL && yetprinted-- > 0;)
513 if (visible(xmp) && (os = hfield1("subject", xmp)) != NULL &&
514 asccasecmp(mso, __subject_trim(os)) == 0)
515 goto jleave;
516 jconv:
517 in.s = ms;
518 in.l = strlen(ms);
519 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
520 rv = out.s;
521 jleave:
522 return rv;
525 static int
526 __putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
528 struct message *mq;
529 int *us, indlvl, indw, i, important = MNEW|MFLAGGED;
530 char *cs;
532 if (mp->m_level == 0 || maxwidth == 0)
533 return 0;
534 cs = ac_alloc(mp->m_level);
535 us = ac_alloc(mp->m_level * sizeof *us);
537 i = mp->m_level - 1;
538 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
539 if (mp->m_parent && mp->m_parent->m_flag & important)
540 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
541 else
542 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
543 cs[i] = '+';
544 } else {
545 if (mp->m_parent && mp->m_parent->m_flag & important)
546 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
547 else
548 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
549 cs[i] = '\\';
552 mq = mp->m_parent;
553 for (i = mp->m_level - 2; i >= 0; i--) {
554 if (mq) {
555 if (UICMP(32, i, >, mq->m_level - 1)) {
556 us[i] = cs[i] = ' ';
557 continue;
559 if (mq->m_younger) {
560 if (mq->m_parent &&
561 mq->m_parent->m_flag&important)
562 us[i] = 0x2503;
563 else
564 us[i] = 0x2502;
565 cs[i] = '|';
566 } else
567 us[i] = cs[i] = ' ';
568 mq = mq->m_parent;
569 } else
570 us[i] = cs[i] = ' ';
573 --maxwidth;
574 for (indlvl = indw = 0; (uc_it)indlvl < mp->m_level &&
575 indw < maxwidth; ++indlvl) {
576 if (indw < maxwidth - 1)
577 indw += (int)putuc(us[indlvl], cs[indlvl] & 0377, fp);
578 else
579 indw += (int)putuc(0x21B8, '^', fp);
581 indw += /*putuc(0x261E, fp)*/putc('>', fp) != EOF;
583 ac_free(us);
584 ac_free(cs);
585 return indw;
588 FL int
589 ccmdnotsupp(void *v) /* TODO -> lex.c */
591 (void)v;
592 fprintf(stderr, tr(10, "The requested feature is not compiled in\n"));
593 return (1);
596 FL int
597 headers(void *v)
599 ui32_t flag;
600 int *msgvec = v, g, k, n, mesg, size, lastg = 1;
601 struct message *mp, *mq, *lastmq = NULL;
602 enum mflag fl = MNEW|MFLAGGED;
604 time_current_update(&time_current, FAL0);
606 flag = 0;
607 size = screensize();
608 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
609 if (screen < 0)
610 screen = 0;
611 k = screen * size;
612 if (k >= msgCount)
613 k = msgCount - size;
614 if (k < 0)
615 k = 0;
617 if (mb.mb_threaded == 0) {
618 g = 0;
619 mq = &message[0];
620 for (mp = &message[0]; mp < &message[msgCount]; mp++)
621 if (visible(mp)) {
622 if (g % size == 0)
623 mq = mp;
624 if (mp->m_flag&fl) {
625 lastg = g;
626 lastmq = mq;
628 if ((n > 0 && mp == &message[n-1]) ||
629 (n == 0 && g == k) ||
630 (n == -2 && g == k + size &&
631 lastmq) ||
632 (n < 0 && g >= k &&
633 (mp->m_flag & fl) != 0))
634 break;
635 g++;
637 if (lastmq && (n==-2 || (n==-1 && mp == &message[msgCount]))) {
638 g = lastg;
639 mq = lastmq;
641 screen = g / size;
642 mp = mq;
643 mesg = mp - &message[0];
644 if (dot != &message[n-1]) {
645 for (mq = mp; mq < &message[msgCount]; mq++)
646 if (visible(mq)) {
647 setdot(mq);
648 break;
651 #ifdef HAVE_IMAP
652 if (mb.mb_type == MB_IMAP)
653 imap_getheaders(mesg+1, mesg + size);
654 #endif
655 srelax_hold();
656 for (; mp < &message[msgCount]; mp++) {
657 mesg++;
658 if (!visible(mp))
659 continue;
660 if (UICMP(32, flag++, >=, size))
661 break;
662 _print_head(0, mesg, stdout, 0);
663 srelax();
665 srelax_rele();
666 } else { /* threaded */
667 g = 0;
668 mq = threadroot;
669 for (mp = threadroot; mp; mp = next_in_thread(mp))
670 if (visible(mp) && (mp->m_collapsed <= 0 ||
671 mp == &message[n-1])) {
672 if (g % size == 0)
673 mq = mp;
674 if (mp->m_flag&fl) {
675 lastg = g;
676 lastmq = mq;
678 if ((n > 0 && mp == &message[n-1]) ||
679 (n == 0 && g == k) ||
680 (n == -2 && g == k + size &&
681 lastmq) ||
682 (n < 0 && g >= k &&
683 (mp->m_flag & fl) != 0))
684 break;
685 g++;
687 if (lastmq && (n==-2 || (n==-1 && mp==&message[msgCount]))) {
688 g = lastg;
689 mq = lastmq;
691 screen = g / size;
692 mp = mq;
693 if (dot != &message[n-1]) {
694 for (mq = mp; mq; mq = next_in_thread(mq))
695 if (visible(mq) && mq->m_collapsed <= 0) {
696 setdot(mq);
697 break;
700 srelax_hold();
701 while (mp) {
702 if (visible(mp) && (mp->m_collapsed <= 0 ||
703 mp == &message[n-1])) {
704 if (UICMP(32, flag++, >=, size))
705 break;
706 _print_head(flag - 1, mp - &message[0] + 1,
707 stdout, mb.mb_threaded);
708 srelax();
710 mp = next_in_thread(mp);
712 srelax_rele();
714 if (!flag)
715 printf(tr(6, "No more mail.\n"));
716 return !flag;
720 * Scroll to the next/previous screen
722 FL int
723 scroll(void *v)
725 return scroll1(v, 0);
728 FL int
729 Scroll(void *v)
731 return scroll1(v, 1);
734 static int
735 scroll1(char *arg, int onlynew)
737 int size;
738 int cur[1];
740 cur[0] = onlynew ? -1 : 0;
741 size = screensize();
742 switch (*arg) {
743 case '1': case '2': case '3': case '4': case '5':
744 case '6': case '7': case '8': case '9': case '0':
745 screen = atoi(arg);
746 goto scroll_forward;
747 case '\0':
748 screen++;
749 goto scroll_forward;
750 case '$':
751 screen = msgCount / size;
752 goto scroll_forward;
753 case '+':
754 if (arg[1] == '\0')
755 screen++;
756 else
757 screen += atoi(arg + 1);
758 scroll_forward:
759 if (screen * size > msgCount) {
760 screen = msgCount / size;
761 printf(tr(7, "On last screenful of messages\n"));
763 break;
765 case '-':
766 if (arg[1] == '\0')
767 screen--;
768 else
769 screen -= atoi(arg + 1);
770 if (screen < 0) {
771 screen = 0;
772 printf(tr(8, "On first screenful of messages\n"));
774 if (cur[0] == -1)
775 cur[0] = -2;
776 break;
778 default:
779 printf(tr(9, "Unrecognized scrolling command \"%s\"\n"), arg);
780 return(1);
782 return(headers(cur));
786 * Compute screen size.
788 FL int
789 screensize(void)
791 int s;
792 char *cp;
794 if ((cp = ok_vlook(screen)) != NULL && (s = atoi(cp)) > 0)
795 return s;
796 return scrnheight - 4;
799 static sigjmp_buf _cmd1_pipejmp;
801 /*ARGSUSED*/
802 static void
803 _cmd1_onpipe(int signo)
805 UNUSED(signo);
806 siglongjmp(_cmd1_pipejmp, 1);
810 * Print out the headlines for each message
811 * in the passed message list.
813 FL int
814 from(void *v)
816 int *msgvec = v, *ip, n;
817 char *cp;
818 FILE *volatile obuf = stdout;
820 time_current_update(&time_current, FAL0);
822 /* TODO unfixable memory leaks still */
823 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
824 for (n = 0, ip = msgvec; *ip; ip++)
825 n++;
826 if (n > (*cp == '\0' ? screensize() : atoi((char*)cp)) + 3) {
827 char const *p;
828 if (sigsetjmp(_cmd1_pipejmp, 1))
829 goto endpipe;
830 p = get_pager();
831 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
832 perror(p);
833 obuf = stdout;
834 cp=NULL;
835 } else
836 safe_signal(SIGPIPE, _cmd1_onpipe);
839 for (n = 0, ip = msgvec; *ip != 0; ip++)
840 _print_head((size_t)n++, *ip, obuf, mb.mb_threaded);
841 if (--ip >= msgvec)
842 setdot(&message[*ip - 1]);
843 endpipe:
844 if (obuf != stdout) {
845 safe_signal(SIGPIPE, SIG_IGN);
846 Pclose(obuf, TRU1);
847 safe_signal(SIGPIPE, dflpipe);
849 return(0);
852 static int
853 _dispc(struct message *mp, const char *a)
855 int i = ' ';
858 * Bletch!
860 if ((mp->m_flag & (MREAD|MNEW)) == MREAD)
861 i = a[3];
862 if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
863 i = a[2];
864 if (mp->m_flag & MANSWERED)
865 i = a[8];
866 if (mp->m_flag & MDRAFTED)
867 i = a[9];
868 if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
869 i = a[0];
870 if ((mp->m_flag & (MREAD|MNEW)) == 0)
871 i = a[1];
872 if (mp->m_flag & MSPAM)
873 i = a[12];
874 if (mp->m_flag & MSAVED)
875 i = a[4];
876 if (mp->m_flag & MPRESERVE)
877 i = a[5];
878 if (mp->m_flag & (MBOX|MBOXED))
879 i = a[6];
880 if (mp->m_flag & MFLAGGED)
881 i = a[7];
882 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
883 i = a[11];
884 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
885 i = a[10];
886 return i;
889 FL void
890 print_headers(size_t bottom, size_t topx)
892 size_t printed;
894 #ifdef HAVE_IMAP
895 if (mb.mb_type == MB_IMAP)
896 imap_getheaders(bottom, topx);
897 #endif
898 time_current_update(&time_current, FAL0);
900 for (printed = 0; bottom <= topx; ++bottom)
901 if (visible(&message[bottom - 1]))
902 _print_head(printed++, bottom, stdout, 0);
906 * Print out the value of dot.
908 /*ARGSUSED*/
909 FL int
910 pdot(void *v)
912 (void)v;
913 printf("%d\n", (int)(dot - &message[0] + 1));
914 return(0);
918 * Type out the messages requested.
920 static sigjmp_buf pipestop;
922 /*ARGSUSED*/
923 static void
924 brokpipe(int signo)
926 (void)signo;
927 siglongjmp(pipestop, 1);
930 static int
931 _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
932 bool_t dodecode, char *cmd, off_t *tstats)
934 enum sendaction const action = ((dopipe && ok_blook(piperaw))
935 ? SEND_MBOX : dodecode
936 ? SEND_SHOW : doign
937 ? SEND_TODISP : SEND_TODISP_ALL);
938 bool_t const formfeed = (dopipe && ok_blook(page));
939 off_t mstats[2];
940 int *ip;
941 struct message *mp;
942 char const *cp;
943 FILE * volatile obuf;
945 obuf = stdout;
946 if (sigsetjmp(pipestop, 1))
947 goto close_pipe;
948 if (dopipe) {
949 if ((cp = ok_vlook(SHELL)) == NULL)
950 cp = XSHELL;
951 if ((obuf = Popen(cmd, "w", cp, 1)) == NULL) {
952 perror(cmd);
953 obuf = stdout;
954 } else
955 safe_signal(SIGPIPE, brokpipe);
956 } else if ((options & OPT_TTYOUT) &&
957 (dopage || (cp = ok_vlook(crt)) != NULL)) {
958 long nlines = 0;
959 if (!dopage) {
960 for (ip = msgvec; *ip &&
961 PTRCMP(ip - msgvec, <, msgCount);
962 ++ip) {
963 if (!(message[*ip - 1].m_have & HAVE_BODY)) {
964 if ((get_body(&message[*ip - 1])) !=
965 OKAY)
966 return 1;
968 nlines += message[*ip - 1].m_lines;
971 if (dopage || nlines > (*cp ? atoi(cp) : realscreenheight)) {
972 char const *p = get_pager();
973 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
974 perror(p);
975 obuf = stdout;
976 } else
977 safe_signal(SIGPIPE, brokpipe);
981 /* This may jump, in which case srelax_rele() wouldn't be called, but
982 * it shouldn't matter, because we -- then -- directly reenter the
983 * lex.c:commands() loop, which sreset()s */
984 srelax_hold();
985 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
986 mp = &message[*ip - 1];
987 touch(mp);
988 setdot(mp);
989 uncollapse1(mp, 1);
990 if (!dopipe) {
991 if (ip != msgvec)
992 fprintf(obuf, "\n");
993 if (action != SEND_MBOX)
994 _show_msg_overview(obuf, mp, *ip);
996 sendmp(mp, obuf, (doign ? ignore : NULL), NULL, action, mstats);
997 srelax();
998 if (formfeed)
999 putc('\f', obuf);
1000 if (tstats) {
1001 tstats[0] += mstats[0];
1002 tstats[1] += mstats[1];
1005 srelax_rele();
1006 close_pipe:
1007 if (obuf != stdout) {
1008 /* Ignore SIGPIPE so it can't cause a duplicate close */
1009 safe_signal(SIGPIPE, SIG_IGN);
1010 Pclose(obuf, TRU1);
1011 safe_signal(SIGPIPE, dflpipe);
1013 return 0;
1017 * Pipe the messages requested.
1019 static int
1020 pipe1(char *str, int doign)
1022 char *cmd;
1023 int *msgvec, ret;
1024 off_t stats[2];
1025 bool_t f;
1027 /*LINTED*/
1028 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1029 if ((cmd = laststring(str, &f, 1)) == NULL) {
1030 cmd = ok_vlook(cmd);
1031 if (cmd == NULL || *cmd == '\0') {
1032 fputs(tr(16, "variable cmd not set\n"), stderr);
1033 return 1;
1036 if (!f) {
1037 *msgvec = first(0, MMNORM);
1038 if (*msgvec == 0) {
1039 if (inhook)
1040 return 0;
1041 puts(tr(18, "No messages to pipe."));
1042 return 1;
1044 msgvec[1] = 0;
1045 } else if (getmsglist(str, msgvec, 0) < 0)
1046 return 1;
1047 if (*msgvec == 0) {
1048 if (inhook)
1049 return 0;
1050 printf("No applicable messages.\n");
1051 return 1;
1053 printf(tr(268, "Pipe to: \"%s\"\n"), cmd);
1054 stats[0] = stats[1] = 0;
1055 if ((ret = _type1(msgvec, doign, FAL0, TRU1, FAL0, cmd, stats)) == 0) {
1056 printf("\"%s\" ", cmd);
1057 if (stats[0] >= 0)
1058 printf("%lu", (long)stats[0]);
1059 else
1060 printf(tr(27, "binary"));
1061 printf("/%lu\n", (long)stats[1]);
1063 return ret;
1067 * Paginate messages, honor ignored fields.
1069 FL int
1070 more(void *v)
1072 int *msgvec = v;
1074 return _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL);
1078 * Paginate messages, even printing ignored fields.
1080 FL int
1081 More(void *v)
1083 int *msgvec = v;
1085 return _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL);
1089 * Type out messages, honor ignored fields.
1091 FL int
1092 type(void *v)
1094 int *msgvec = v;
1096 return _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL);
1100 * Type out messages, even printing ignored fields.
1102 FL int
1103 Type(void *v)
1105 int *msgvec = v;
1107 return _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL);
1111 * Show MIME-encoded message text, including all fields.
1113 FL int
1114 show(void *v)
1116 int *msgvec = v;
1118 return _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL);
1122 * Pipe messages, honor ignored fields.
1124 FL int
1125 pipecmd(void *v)
1127 char *str = v;
1128 return(pipe1(str, 1));
1131 * Pipe messages, not respecting ignored fields.
1133 FL int
1134 Pipecmd(void *v)
1136 char *str = v;
1137 return(pipe1(str, 0));
1141 * Print the top so many lines of each desired message.
1142 * The number of lines is taken from the variable "toplines"
1143 * and defaults to 5.
1145 FL int
1146 top(void *v)
1148 int *msgvec = v, *ip, c, topl, lines, empty_last;
1149 struct message *mp;
1150 char *cp, *linebuf = NULL;
1151 size_t linesize = 0;
1152 FILE *ibuf;
1154 topl = 5;
1155 cp = ok_vlook(toplines);
1156 if (cp != NULL) {
1157 topl = atoi(cp);
1158 if (topl < 0 || topl > 10000)
1159 topl = 5;
1161 empty_last = 1;
1162 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1163 mp = &message[*ip - 1];
1164 touch(mp);
1165 setdot(mp);
1166 did_print_dot = TRU1;
1167 if (!empty_last)
1168 printf("\n");
1169 _show_msg_overview(stdout, mp, *ip);
1170 if (mp->m_flag & MNOFROM)
1171 printf("From %s %s\n", fakefrom(mp),
1172 fakedate(mp->m_time));
1173 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1174 v = NULL;
1175 break;
1177 c = mp->m_lines;
1178 for (lines = 0; lines < c && UICMP(32, lines, <=, topl);
1179 ++lines) {
1180 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1181 break;
1182 puts(linebuf);
1184 for (cp = linebuf; *cp && blankchar(*cp); ++cp)
1186 empty_last = (*cp == '\0');
1190 if (linebuf != NULL)
1191 free(linebuf);
1192 return (v != NULL);
1196 * Touch all the given messages so that they will
1197 * get mboxed.
1199 FL int
1200 stouch(void *v)
1202 int *msgvec = v;
1203 int *ip;
1205 for (ip = msgvec; *ip != 0; ip++) {
1206 setdot(&message[*ip-1]);
1207 dot->m_flag |= MTOUCH;
1208 dot->m_flag &= ~MPRESERVE;
1210 * POSIX interpretation necessary.
1212 did_print_dot = TRU1;
1214 return(0);
1218 * Make sure all passed messages get mboxed.
1220 FL int
1221 mboxit(void *v)
1223 int *msgvec = v;
1224 int *ip;
1226 for (ip = msgvec; *ip != 0; ip++) {
1227 setdot(&message[*ip-1]);
1228 dot->m_flag |= MTOUCH|MBOX;
1229 dot->m_flag &= ~MPRESERVE;
1231 * POSIX interpretation necessary.
1233 did_print_dot = TRU1;
1235 return(0);
1239 * List the folders the user currently has.
1241 FL int
1242 folders(void *v)
1244 char dirname[MAXPATHLEN], *name, **argv = v;
1245 char const *cmd;
1247 if (*argv) {
1248 name = expand(*argv);
1249 if (name == NULL)
1250 return 1;
1251 } else if (! getfold(dirname, sizeof dirname)) {
1252 fprintf(stderr, tr(20, "No value set for \"folder\"\n"));
1253 return 1;
1254 } else
1255 name = dirname;
1257 if (which_protocol(name) == PROTO_IMAP) {
1258 #ifdef HAVE_IMAP
1259 imap_folders(name, *argv == NULL);
1260 #else
1261 return ccmdnotsupp(NULL);
1262 #endif
1263 } else {
1264 if ((cmd = ok_vlook(LISTER)) == NULL)
1265 cmd = XLISTER;
1266 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1268 return 0;