Add subject_re_trim()
[s-mailx.git] / cmd1.c
blob3be294235deba834d3fe1a99de4b561c390e6554
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
44 static int _screen;
45 static sigjmp_buf _cmd1_pipestop;
46 static sigjmp_buf _cmd1_pipejmp;
48 static void _cmd1_onpipe(int signo);
49 static void _cmd1_brokpipe(int signo);
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, size_t msgno, FILE *f,
62 bool_t threaded);
63 static void __hprf(size_t yetprinted, char const *fmt, size_t msgno,
64 FILE *f, bool_t threaded, char const *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 int _dispc(struct message *mp, char const *a);
71 static int _scroll1(char *arg, int onlynew);
73 /* Show the requested messages */
74 static int _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
75 bool_t dodecode, char *cmd, off_t *tstats);
77 /* Pipe the requested messages */
78 static int _pipe1(char *str, int doign);
80 static void
81 _cmd1_onpipe(int signo)
83 NYD_X; /* Signal handler */
84 UNUSED(signo);
85 siglongjmp(_cmd1_pipejmp, 1);
88 static void
89 _cmd1_brokpipe(int signo)
91 NYD_X; /* Signal handler */
92 UNUSED(signo);
93 siglongjmp(_cmd1_pipestop, 1);
96 static void
97 _show_msg_overview(FILE *obuf, struct message *mp, int msg_no)
99 char const *cpre = "", *csuf = "";
100 NYD_ENTER;
102 #ifdef HAVE_COLOUR
103 if (colour_table != NULL) {
104 struct str const *sp;
106 if ((sp = colour_get(COLOURSPEC_MSGINFO)) != NULL)
107 cpre = sp->s;
108 csuf = colour_get(COLOURSPEC_RESET)->s;
110 #endif
111 fprintf(obuf, _("%s[-- Message %2d -- %lu lines, %lu bytes --]:%s\n"),
112 cpre, msg_no, (ul_it)mp->m_lines, (ul_it)mp->m_size, csuf);
113 NYD_LEAVE;
116 static void
117 _parse_from_(struct message *mp, char date[FROM_DATEBUF]) /* TODO line pool */
119 FILE *ibuf;
120 int hlen;
121 char *hline = NULL;
122 size_t hsize = 0;
123 NYD_ENTER;
125 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
126 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
127 extract_date_from_from_(hline, hlen, date);
128 if (hline != NULL)
129 free(hline);
130 NYD_LEAVE;
133 static void
134 _print_head(size_t yetprinted, size_t msgno, FILE *f, bool_t threaded)
136 enum {attrlen = 13};
137 char attrlist[attrlen +1], *cp;
138 char const *fmt;
139 NYD_ENTER;
141 if ((cp = ok_vlook(attrlist)) != NULL) {
142 if (strlen(cp) == attrlen) {
143 memcpy(attrlist, cp, attrlen +1);
144 goto jattrok;
146 fprintf(stderr, _(
147 "The value of *attrlist* is not of the correct length\n"));
149 if (ok_blook(bsdcompat) || ok_blook(bsdflags) ||
150 getenv("SYSV3") != NULL) {
151 char const bsdattr[attrlen +1] = "NU *HMFAT+-$";
152 memcpy(attrlist, bsdattr, sizeof bsdattr);
153 } else {
154 char const pattr[attrlen +1] = "NUROSPMFAT+-$";
155 memcpy(attrlist, pattr, sizeof pattr);
157 jattrok:
158 if ((fmt = ok_vlook(headline)) == NULL) {
159 fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
160 ? "%>%a%m %-20f %16d %3l/%-5o %i%-S"
161 : "%>%a%m %-18f %16d %4l/%-5o %i%-s");
164 __hprf(yetprinted, fmt, msgno, f, threaded, attrlist);
165 NYD_LEAVE;
168 static void
169 __hprf(size_t yetprinted, char const *fmt, size_t msgno, FILE *f,
170 bool_t threaded, char const *attrlist)
172 char datebuf[FROM_DATEBUF], *cp, *subjline;
173 char const *datefmt, *date, *name, *fp;
174 int i, n, s, wleft, subjlen;
175 struct message *mp;
176 time_t datet;
177 enum {
178 _NONE = 0,
179 _ISADDR = 1<<0,
180 _ISTO = 1<<1,
181 _IFMT = 1<<2,
182 _LOOP_MASK = (1<<3) - 1,
183 _SFMT = 1<<3
184 } flags = _NONE;
185 NYD_ENTER;
187 mp = message + msgno - 1;
188 datet = mp->m_time;
189 date = NULL;
191 datefmt = ok_vlook(datefield);
192 jredo:
193 if (datefmt != NULL) {
194 fp = hfield1("date", mp);/* TODO use m_date field! */
195 if (fp == NULL) {
196 datefmt = NULL;
197 goto jredo;
199 datet = rfctime(fp);
200 date = fakedate(datet);
201 fp = ok_vlook(datefield_markout_older);
202 i = (*datefmt != '\0');
203 if (fp != NULL)
204 i |= (*fp != '\0') ? 2 | 4 : 2; /* XXX no magics */
206 /* May we strftime(3)? */
207 if (i & (1 | 4))
208 memcpy(&time_current.tc_local, localtime(&datet),
209 sizeof time_current.tc_local);
211 if ((i & 2) && (datet > time_current.tc_time + DATE_SECSDAY ||
212 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
213 (datet + _6M < time_current.tc_time))) {
214 #undef _6M
215 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
216 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
217 memcpy(datebuf + 4, date + 4, 7);
218 datebuf[4 + 7] = ' ';
219 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
220 datebuf[4 + 7 + 1 + 4] = '\0';
221 date = datebuf;
223 } else if ((i & 1) == 0)
224 datefmt = NULL;
225 } else if (datet == (time_t)0 && !(mp->m_flag & MNOFROM)) {
226 /* TODO eliminate this path, query the FROM_ date in setptr(),
227 * TODO all other codepaths do so by themselves ALREADY ?????
228 * TODO assert(mp->m_time != 0);, then
229 * TODO ALSO changes behaviour of datefield_markout_older */
230 _parse_from_(mp, datebuf);
231 date = datebuf;
232 } else
233 date = fakedate(datet);
235 flags |= _ISADDR;
236 name = name1(mp, 0);
237 if (name != NULL && ok_blook(showto) && is_myname(skin(name))) {
238 if ((cp = hfield1("to", mp)) != NULL) {
239 name = cp;
240 flags |= _ISTO;
243 if (name == NULL) {
244 name = "";
245 flags &= ~_ISADDR;
247 if (flags & _ISADDR)
248 name = ok_blook(showname) ? realname(name) : prstr(skin(name));
250 subjline = NULL;
252 /* Detect the width of the non-format characters in *headline*;
253 * like that we can simply use putc() in the next loop, since we have
254 * already calculated their column widths (TODO it's sick) */
255 wleft = subjlen = scrnwidth;
257 for (fp = fmt; *fp != '\0'; ++fp) {
258 if (*fp == '%') {
259 if (*++fp == '-')
260 ++fp;
261 else if (*fp == '+')
262 ++fp;
263 if (digitchar(*fp)) {
264 n = 0;
266 n = 10*n + *fp - '0';
267 while (++fp, digitchar(*fp));
268 subjlen -= n;
270 if (*fp == 'i')
271 flags |= _IFMT;
273 if (*fp == '\0')
274 break;
275 } else {
276 #ifdef HAVE_WCWIDTH
277 if (mb_cur_max > 1) {
278 wchar_t wc;
279 if ((s = mbtowc(&wc, fp, mb_cur_max)) == -1)
280 n = s = 1;
281 else if ((n = wcwidth(wc)) == -1)
282 n = 1;
283 } else
284 #endif
285 n = s = 1;
286 subjlen -= n;
287 wleft -= n;
288 while (--s > 0)
289 ++fp;
293 /* Walk *headline*, producing output TODO not (really) MB safe */
294 for (fp = fmt; *fp != '\0'; ++fp) {
295 char c;
296 if ((c = *fp & 0xFF) != '%')
297 putc(c, f);
298 else {
299 flags &= _LOOP_MASK;
300 n = 0;
301 s = 1;
302 if (*++fp == '-') {
303 s = -1;
304 ++fp;
305 } else if (*fp == '+')
306 ++fp;
307 if (digitchar(*fp)) {
309 n = 10*n + *fp - '0';
310 while (++fp, digitchar(*fp));
312 if (*fp == '\0')
313 break;
315 n *= s;
316 switch ((c = *fp & 0xFF)) {
317 case '%':
318 goto jputc;
319 case '>':
320 case '<':
321 if (dot != mp)
322 c = ' ';
323 goto jputc;
324 case '$':
325 #ifdef HAVE_SPAM
326 if (n == 0)
327 n = 4;
328 if (UICMP(32, ABS(n), >, wleft))
329 n = (n < 0) ? -wleft : wleft;
330 { char buf[16];
331 snprintf(buf, sizeof buf, "%u.%u",
332 (mp->m_spamscore >> 8), (mp->m_spamscore & 0xFF));
333 n = fprintf(f, "%*s", n, buf);
334 wleft = (n >= 0) ? wleft - n : 0;
336 #else
337 c = '?';
338 goto jputc;
339 #endif
340 case 'a':
341 c = _dispc(mp, attrlist);
342 jputc:
343 if (UICMP(32, ABS(n), >, wleft))
344 n = (n < 0) ? -wleft : wleft;
345 n = fprintf(f, "%*c", n, c);
346 wleft = (n >= 0) ? wleft - n : 0;
347 break;
348 case 'd':
349 if (datefmt != NULL) {
350 i = strftime(datebuf, sizeof datebuf, datefmt,
351 &time_current.tc_local);
352 if (i != 0)
353 date = datebuf;
354 else
355 fprintf(stderr, _(
356 "Ignored date format, it excesses the target buffer "
357 "(%lu bytes)\n"), (ul_it)sizeof datebuf);
358 datefmt = NULL;
360 if (n == 0)
361 n = 16;
362 if (UICMP(32, ABS(n), >, wleft))
363 n = (n < 0) ? -wleft : wleft;
364 n = fprintf(f, "%*.*s", n, n, date);
365 wleft = (n >= 0) ? wleft - n : 0;
366 break;
367 case 'e':
368 if (n == 0)
369 n = 2;
370 if (UICMP(32, ABS(n), >, wleft))
371 n = (n < 0) ? -wleft : wleft;
372 n = fprintf(f, "%*u", n, (threaded == 1 ? mp->m_level : 0));
373 wleft = (n >= 0) ? wleft - n : 0;
374 break;
375 case 'f':
376 if (n == 0) {
377 n = 18;
378 if (s < 0)
379 n = -n;
381 i = ABS(n);
382 if (i > wleft) {
383 i = wleft;
384 n = (n < 0) ? -wleft : wleft;
386 if (flags & _ISTO) /* XXX tr()! */
387 i -= 3;
388 n = fprintf(f, "%s%s", ((flags & _ISTO) ? "To " : ""),
389 colalign(name, i, n, &wleft));
390 if (n < 0)
391 wleft = 0;
392 else if (flags & _ISTO)
393 wleft -= 3;
394 break;
395 case 'i':
396 if (threaded) {
397 n = __putindent(f, mp, MIN(wleft, scrnwidth - 60));
398 wleft = (n >= 0) ? wleft - n : 0;
400 break;
401 case 'l':
402 if (n == 0)
403 n = 4;
404 if (UICMP(32, ABS(n), >, wleft))
405 n = (n < 0) ? -wleft : wleft;
406 if (mp->m_xlines) {
407 n = fprintf(f, "%*ld", n, mp->m_xlines);
408 wleft = (n >= 0) ? wleft - n : 0;
409 } else {
410 n = ABS(n);
411 wleft -= n;
412 while (n-- != 0)
413 putc(' ', f);
415 break;
416 case 'm':
417 if (n == 0) {
418 n = 3;
419 if (threaded)
420 for (i = msgCount; i > 999; i /= 10)
421 ++n;
423 if (UICMP(32, ABS(n), >, wleft))
424 n = (n < 0) ? -wleft : wleft;
425 n = fprintf(f, "%*lu", n, (ul_it)msgno);
426 wleft = (n >= 0) ? wleft - n : 0;
427 break;
428 case 'o':
429 if (n == 0)
430 n = -5;
431 if (UICMP(32, ABS(n), >, wleft))
432 n = (n < 0) ? -wleft : wleft;
433 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
434 wleft = (n >= 0) ? wleft - n : 0;
435 break;
436 case 'S':
437 flags |= _SFMT;
438 /*FALLTHRU*/
439 case 's':
440 if (n == 0)
441 n = subjlen - 2;
442 if (n > 0 && s < 0)
443 n = -n;
444 if (subjlen > wleft)
445 subjlen = wleft;
446 if (UICMP(32, ABS(n), >, subjlen))
447 n = (n < 0) ? -subjlen : subjlen;
448 if (flags & _SFMT)
449 n -= (n < 0) ? -2 : 2;
450 if (n == 0)
451 break;
452 if (subjline == NULL)
453 subjline = __subject(mp, (threaded && (flags & _IFMT)),
454 yetprinted);
455 if (subjline == (char*)-1) {
456 n = fprintf(f, "%*s", n, "");
457 wleft = (n >= 0) ? wleft - n : 0;
458 } else {
459 n = fprintf(f, ((flags & _SFMT) ? "\"%s\"" : "%s"),
460 colalign(subjline, ABS(n), n, &wleft));
461 if (n < 0)
462 wleft = 0;
464 break;
465 case 't':
466 if (n == 0) {
467 n = 3;
468 if (threaded)
469 for (i = msgCount; i > 999; i /= 10)
470 ++n;
472 if (UICMP(32, ABS(n), >, wleft))
473 n = (n < 0) ? -wleft : wleft;
474 n = fprintf(f, "%*lu", n,
475 (threaded ? (ul_it)mp->m_threadpos : (ul_it)msgno));
476 wleft = (n >= 0) ? wleft - n : 0;
477 break;
478 case 'U':
479 #ifdef HAVE_IMAP
480 if (n == 0)
481 n = 9;
482 if (UICMP(32, ABS(n), >, wleft))
483 n = (n < 0) ? -wleft : wleft;
484 n = fprintf(f, "%*lu", n, mp->m_uid);
485 wleft = (n >= 0) ? wleft - n : 0;
486 break;
487 #else
488 c = '?';
489 goto jputc;
490 #endif
493 if (wleft <= 0)
494 break;
497 putc('\n', f);
499 if (subjline != NULL && subjline != (char*)-1)
500 free(subjline);
501 NYD_LEAVE;
504 static char *
505 __subject_trim(char *s)
507 struct {
508 ui8_t len;
509 char dat[7];
510 } const *pp, ignored[] = { /* TODO make ignore list configurable */
511 { 3, "re:" }, { 4, "fwd:" },
512 { 3, "aw:" }, { 5, "antw:" },
513 { 0, "" }
515 NYD_ENTER;
517 jouter:
518 while (*s != '\0') {
519 while (spacechar(*s))
520 ++s;
521 /* TODO While it is maybe ok not to MIME decode these (for purpose), we
522 * TODO should skip =?..?= at the beginning? */
523 for (pp = ignored; pp->len > 0; ++pp)
524 if (is_asccaseprefix(pp->dat, s)) {
525 s += pp->len;
526 goto jouter;
528 break;
530 NYD_LEAVE;
531 return s;
534 static char *
535 __subject(struct message *mp, bool_t threaded, size_t yetprinted)
537 /* XXX NOTE: because of efficiency reasons we simply ignore any encoded
538 * XXX parts and use ASCII case-insensitive comparison */
539 struct str in, out;
540 struct message *xmp;
541 char *rv = (char*)-1, *ms, *mso, *os;
542 NYD_ENTER;
544 if ((ms = hfield1("subject", mp)) == NULL)
545 goto jleave;
547 if (!threaded || mp->m_level == 0)
548 goto jconv;
550 /* In a display thread - check wether this message uses the same
551 * Subject: as it's parent or elder neighbour, suppress printing it if
552 * this is the case. To extend this a bit, ignore any leading Re: or
553 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
554 mso = __subject_trim(ms);
555 for (xmp = mp; (xmp = prev_in_thread(xmp)) != NULL && yetprinted-- > 0;)
556 if (visible(xmp) && (os = hfield1("subject", xmp)) != NULL &&
557 !asccasecmp(mso, __subject_trim(os)))
558 goto jleave;
559 jconv:
560 in.s = ms;
561 in.l = strlen(ms);
562 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
563 rv = out.s;
564 jleave:
565 NYD_LEAVE;
566 return rv;
569 static int
570 __putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
572 struct message *mq;
573 int *us, indlvl, indw, i, important = MNEW | MFLAGGED;
574 char *cs;
575 NYD_ENTER;
577 if (mp->m_level == 0 || maxwidth == 0) {
578 indw = 0;
579 goto jleave;
582 cs = ac_alloc(mp->m_level);
583 us = ac_alloc(mp->m_level * sizeof *us);
585 i = mp->m_level - 1;
586 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
587 if (mp->m_parent && mp->m_parent->m_flag & important)
588 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
589 else
590 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
591 cs[i] = '+';
592 } else {
593 if (mp->m_parent && mp->m_parent->m_flag & important)
594 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
595 else
596 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
597 cs[i] = '\\';
600 mq = mp->m_parent;
601 for (i = mp->m_level - 2; i >= 0; --i) {
602 if (mq) {
603 if (UICMP(32, i, >, mq->m_level - 1)) {
604 us[i] = cs[i] = ' ';
605 continue;
607 if (mq->m_younger) {
608 if (mq->m_parent && (mq->m_parent->m_flag & important))
609 us[i] = 0x2503;
610 else
611 us[i] = 0x2502;
612 cs[i] = '|';
613 } else
614 us[i] = cs[i] = ' ';
615 mq = mq->m_parent;
616 } else
617 us[i] = cs[i] = ' ';
620 --maxwidth;
621 for (indlvl = indw = 0; (ui8_t)indlvl < mp->m_level && indw < maxwidth;
622 ++indlvl) {
623 if (indw < maxwidth - 1)
624 indw += (int)putuc(us[indlvl], cs[indlvl] & 0xFF, fp);
625 else
626 indw += (int)putuc(0x21B8, '^', fp);
628 indw += (/*putuc(0x261E, fp)*/putc('>', fp) != EOF);
630 ac_free(us);
631 ac_free(cs);
632 jleave:
633 NYD_LEAVE;
634 return indw;
637 static int
638 _dispc(struct message *mp, char const *a)
640 int i = ' ';
641 NYD_ENTER;
643 if ((mp->m_flag & (MREAD | MNEW)) == MREAD)
644 i = a[3];
645 if ((mp->m_flag & (MREAD | MNEW)) == (MREAD | MNEW))
646 i = a[2];
647 if (mp->m_flag & MANSWERED)
648 i = a[8];
649 if (mp->m_flag & MDRAFTED)
650 i = a[9];
651 if ((mp->m_flag & (MREAD | MNEW)) == MNEW)
652 i = a[0];
653 if (!(mp->m_flag & (MREAD | MNEW)))
654 i = a[1];
655 if (mp->m_flag & MSPAM)
656 i = a[12];
657 if (mp->m_flag & MSAVED)
658 i = a[4];
659 if (mp->m_flag & MPRESERVE)
660 i = a[5];
661 if (mp->m_flag & (MBOX | MBOXED))
662 i = a[6];
663 if (mp->m_flag & MFLAGGED)
664 i = a[7];
665 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
666 i = a[11];
667 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
668 i = a[10];
669 NYD_LEAVE;
670 return i;
673 static int
674 _scroll1(char *arg, int onlynew)
676 int cur[1], size;
677 NYD_ENTER;
679 cur[0] = onlynew ? -1 : 0;
680 size = screensize();
681 switch (*arg) {
682 case '1': case '2': case '3': case '4': case '5':
683 case '6': case '7': case '8': case '9': case '0':
684 _screen = atoi(arg);
685 goto jscroll_forward;
686 case '\0':
687 ++_screen;
688 goto jscroll_forward;
689 case '$':
690 _screen = msgCount / size;
691 goto jscroll_forward;
692 case '+':
693 if (arg[1] == '\0')
694 ++_screen;
695 else
696 _screen += atoi(arg + 1);
697 jscroll_forward:
698 if (_screen * size > msgCount) {
699 _screen = msgCount / size;
700 printf(_("On last screenful of messages\n"));
702 break;
704 case '-':
705 if (arg[1] == '\0')
706 --_screen;
707 else
708 _screen -= atoi(arg + 1);
709 if (_screen < 0) {
710 _screen = 0;
711 printf(_("On first screenful of messages\n"));
713 if (cur[0] == -1)
714 cur[0] = -2;
715 break;
717 default:
718 fprintf(stderr, _("Unrecognized scrolling command \"%s\"\n"), arg);
719 size = 1;
720 goto jleave;
722 size = c_headers(cur);
723 jleave:
724 NYD_LEAVE;
725 return size;
728 static int
729 _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
730 bool_t dodecode, char *cmd, off_t *tstats)
732 off_t mstats[2];
733 int rv, *ip;
734 struct message *mp;
735 char const *cp;
736 FILE * volatile obuf;
737 bool_t volatile hadsig = FAL0, isrelax = FAL0;
738 NYD_ENTER;
739 {/* C89.. */
740 enum sendaction const action = ((dopipe && ok_blook(piperaw))
741 ? SEND_MBOX : dodecode
742 ? SEND_SHOW : doign
743 ? SEND_TODISP : SEND_TODISP_ALL);
744 bool_t const volatile formfeed = (dopipe && ok_blook(page));
745 obuf = stdout;
747 if (sigsetjmp(_cmd1_pipestop, 1)) {
748 hadsig = TRU1;
749 goto jclose_pipe;
752 if (dopipe) {
753 if ((cp = ok_vlook(SHELL)) == NULL)
754 cp = XSHELL;
755 if ((obuf = Popen(cmd, "w", cp, NULL, 1)) == NULL) {
756 perror(cmd);
757 obuf = stdout;
758 } else
759 safe_signal(SIGPIPE, &_cmd1_brokpipe);
760 } else if ((options & OPT_TTYOUT) &&
761 (dopage || (cp = ok_vlook(crt)) != NULL)) {
762 char const *pager = NULL;
763 size_t nlines = 0;
765 if (!dopage) {
766 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
767 mp = message + *ip - 1;
768 if (!(mp->m_have & HAVE_BODY))
769 if (get_body(mp) != OKAY) {
770 rv = 1;
771 goto jleave;
773 nlines += mp->m_lines + 1; /* Message info XXX and PARTS... */
777 /* `>=' not `<': we return to the prompt */
778 if (dopage || UICMP(z, nlines, >=,
779 (*cp != '\0' ? atoi(cp) : realscreenheight))) {
780 char const *env_add[2];
781 pager = get_pager(env_add + 0);
782 env_add[1] = NULL;
783 obuf = Popen(pager, "w", NULL, env_add, 1);
784 if (obuf == NULL) {
785 perror(pager);
786 obuf = stdout;
787 pager = NULL;
788 } else
789 safe_signal(SIGPIPE, &_cmd1_brokpipe);
791 #ifdef HAVE_COLOUR
792 if (action != SEND_MBOX)
793 colour_table_create(pager != NULL); /* (salloc()s!) */
794 #endif
796 #ifdef HAVE_COLOUR
797 else if ((options & OPT_TTYOUT) && action != SEND_MBOX)
798 colour_table_create(FAL0); /* (salloc()s!) */
799 #endif
801 /*TODO unless we have our signal manager special care must be taken */
802 srelax_hold();
803 isrelax = TRU1;
804 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
805 mp = message + *ip - 1;
806 touch(mp);
807 setdot(mp);
808 uncollapse1(mp, 1);
809 if (!dopipe) {
810 if (ip != msgvec)
811 fprintf(obuf, "\n");
812 if (action != SEND_MBOX)
813 _show_msg_overview(obuf, mp, *ip);
815 sendmp(mp, obuf, (doign ? ignore : NULL), NULL, action, mstats);
816 srelax();
817 if (formfeed) /* TODO a nicer way to separate piped messages! */
818 putc('\f', obuf);
819 if (tstats) {
820 tstats[0] += mstats[0];
821 tstats[1] += mstats[1];
824 srelax_rele();
825 isrelax = FAL0;
827 jclose_pipe:
828 if (obuf != stdout) {
829 /* Ignore SIGPIPE so it can't cause a duplicate close */
830 safe_signal(SIGPIPE, SIG_IGN);
831 if (hadsig && isrelax)
832 srelax_rele();
833 colour_reset(obuf); /* XXX hacky; only here because we still jump */
834 Pclose(obuf, TRU1);
835 safe_signal(SIGPIPE, dflpipe);
837 rv = 0;
839 jleave:
840 NYD_LEAVE;
841 return rv;
844 static int
845 _pipe1(char *str, int doign)
847 off_t stats[2];
848 char *cmd;
849 int *msgvec, rv = 1;
850 bool_t needs_list;
851 NYD_ENTER;
853 if ((cmd = laststring(str, &needs_list, TRU1)) == NULL) {
854 cmd = ok_vlook(cmd);
855 if (cmd == NULL || *cmd == '\0') {
856 fputs(_("variable cmd not set\n"), stderr);
857 goto jleave;
861 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
863 if (!needs_list) {
864 *msgvec = first(0, MMNORM);
865 if (*msgvec == 0) {
866 if (inhook) {
867 rv = 0;
868 goto jleave;
870 puts(_("No messages to pipe."));
871 goto jleave;
873 msgvec[1] = 0;
874 } else if (getmsglist(str, msgvec, 0) < 0)
875 goto jleave;
876 if (*msgvec == 0) {
877 if (inhook) {
878 rv = 0;
879 goto jleave;
881 printf("No applicable messages.\n");
882 goto jleave;
885 printf(_("Pipe to: \"%s\"\n"), cmd);
886 stats[0] = stats[1] = 0;
887 if ((rv = _type1(msgvec, doign, FAL0, TRU1, FAL0, cmd, stats)) == 0) {
888 printf("\"%s\" ", cmd);
889 if (stats[0] >= 0)
890 printf("%lu", (long)stats[0]);
891 else
892 printf(_("binary"));
893 printf("/%lu\n", (long)stats[1]);
895 jleave:
896 NYD_LEAVE;
897 return rv;
900 FL int
901 c_cmdnotsupp(void *v) /* TODO -> lex.c */
903 NYD_ENTER;
904 UNUSED(v);
905 fprintf(stderr, _("The requested feature is not compiled in\n"));
906 NYD_LEAVE;
907 return 1;
910 FL int
911 c_headers(void *v)
913 ui32_t flag;
914 int *msgvec = v, g, k, n, mesg, size, lastg = 1;
915 struct message *mp, *mq, *lastmq = NULL;
916 enum mflag fl = MNEW | MFLAGGED;
917 NYD_ENTER;
919 time_current_update(&time_current, FAL0);
921 flag = 0;
922 size = screensize();
923 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
924 if (_screen < 0)
925 _screen = 0;
926 k = _screen * size;
927 if (k >= msgCount)
928 k = msgCount - size;
929 if (k < 0)
930 k = 0;
932 if (mb.mb_threaded == 0) {
933 g = 0;
934 mq = message;
935 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
936 if (visible(mp)) {
937 if (g % size == 0)
938 mq = mp;
939 if (mp->m_flag & fl) {
940 lastg = g;
941 lastmq = mq;
943 if ((n > 0 && PTRCMP(mp, ==, message + n - 1)) ||
944 (n == 0 && g == k) || (n == -2 && g == k + size && lastmq) ||
945 (n < 0 && g >= k && (mp->m_flag & fl) != 0))
946 break;
947 g++;
949 if (lastmq && (n == -2 ||
950 (n == -1 && PTRCMP(mp, ==, message + msgCount)))) {
951 g = lastg;
952 mq = lastmq;
954 _screen = g / size;
955 mp = mq;
956 mesg = (int)PTR2SIZE(mp - message);
957 if (PTRCMP(dot, !=, message + n - 1)) {
958 for (mq = mp; PTRCMP(mq, <, message + msgCount); ++mq)
959 if (visible(mq)) {
960 setdot(mq);
961 break;
964 #ifdef HAVE_IMAP
965 if (mb.mb_type == MB_IMAP)
966 imap_getheaders(mesg + 1, mesg + size);
967 #endif
968 srelax_hold();
969 for (; PTRCMP(mp, <, message + msgCount); ++mp) {
970 ++mesg;
971 if (!visible(mp))
972 continue;
973 if (UICMP(32, flag++, >=, size))
974 break;
975 _print_head(0, mesg, stdout, 0);
976 srelax();
978 srelax_rele();
979 } else { /* threaded */
980 g = 0;
981 mq = threadroot;
982 for (mp = threadroot; mp; mp = next_in_thread(mp))
983 if (visible(mp) &&
984 (mp->m_collapsed <= 0 || PTRCMP(mp, ==, message + n - 1))) {
985 if (g % size == 0)
986 mq = mp;
987 if (mp->m_flag & fl) {
988 lastg = g;
989 lastmq = mq;
991 if ((n > 0 && PTRCMP(mp, ==, message + n - 1)) ||
992 (n == 0 && g == k) || (n == -2 && g == k + size && lastmq) ||
993 (n < 0 && g >= k && (mp->m_flag & fl) != 0))
994 break;
995 g++;
997 if (lastmq && (n == -2 ||
998 (n == -1 && PTRCMP(mp, ==, message + msgCount)))) {
999 g = lastg;
1000 mq = lastmq;
1002 _screen = g / size;
1003 mp = mq;
1004 if (PTRCMP(dot, !=, message + n - 1)) {
1005 for (mq = mp; mq; mq = next_in_thread(mq))
1006 if (visible(mq) && mq->m_collapsed <= 0) {
1007 setdot(mq);
1008 break;
1011 srelax_hold();
1012 while (mp) {
1013 if (visible(mp) &&
1014 (mp->m_collapsed <= 0 || PTRCMP(mp, ==, message + n - 1))) {
1015 if (UICMP(32, flag++, >=, size))
1016 break;
1017 _print_head(flag - 1, PTR2SIZE(mp - message + 1), stdout,
1018 mb.mb_threaded);
1019 srelax();
1021 mp = next_in_thread(mp);
1023 srelax_rele();
1026 if (!flag)
1027 printf(_("No more mail.\n"));
1028 NYD_LEAVE;
1029 return !flag;
1032 FL int
1033 c_scroll(void *v)
1035 int rv;
1036 NYD_ENTER;
1038 rv = _scroll1(v, 0);
1039 NYD_LEAVE;
1040 return rv;
1043 FL int
1044 c_Scroll(void *v)
1046 int rv;
1047 NYD_ENTER;
1049 rv = _scroll1(v, 1);
1050 NYD_LEAVE;
1051 return rv;
1054 FL int
1055 c_from(void *v)
1057 int *msgvec = v, *ip, n;
1058 char *cp;
1059 FILE * volatile obuf;
1060 NYD_ENTER;
1062 time_current_update(&time_current, FAL0);
1063 obuf = stdout;
1065 /* TODO unfixable memory leaks still */
1066 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
1067 for (n = 0, ip = msgvec; *ip != 0; ++ip)
1068 n++;
1069 if (n > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
1070 char const *p;
1071 if (sigsetjmp(_cmd1_pipejmp, 1))
1072 goto jendpipe;
1073 p = get_pager(NULL);
1074 if ((obuf = Popen(p, "w", NULL, NULL, 1)) == NULL) {
1075 perror(p);
1076 obuf = stdout;
1077 cp = NULL;
1078 } else
1079 safe_signal(SIGPIPE, &_cmd1_onpipe);
1083 for (n = 0, ip = msgvec; *ip != 0; ++ip) /* TODO join into _print_head() */
1084 _print_head((size_t)n++, (size_t)*ip, obuf, mb.mb_threaded);
1085 if (--ip >= msgvec)
1086 setdot(message + *ip - 1);
1088 jendpipe:
1089 if (obuf != stdout) {
1090 safe_signal(SIGPIPE, SIG_IGN);
1091 Pclose(obuf, TRU1);
1092 safe_signal(SIGPIPE, dflpipe);
1094 NYD_LEAVE;
1095 return 0;
1098 FL void
1099 print_headers(size_t bottom, size_t topx, bool_t only_marked)
1101 size_t printed;
1102 NYD_ENTER;
1104 #ifdef HAVE_IMAP
1105 if (mb.mb_type == MB_IMAP)
1106 imap_getheaders(bottom, topx);
1107 #endif
1108 time_current_update(&time_current, FAL0);
1110 for (printed = 0; bottom <= topx; ++bottom) {
1111 struct message *mp = message + bottom - 1;
1112 if (only_marked) {
1113 if (!(mp->m_flag & MMARK))
1114 continue;
1115 } else if (!visible(mp))
1116 continue;
1117 _print_head(printed++, bottom, stdout, FAL0);
1119 NYD_LEAVE;
1122 FL int
1123 c_pdot(void *v)
1125 NYD_ENTER;
1126 UNUSED(v);
1127 printf("%d\n", (int)PTR2SIZE(dot - message + 1));
1128 NYD_LEAVE;
1129 return 0;
1132 FL int
1133 c_more(void *v)
1135 int *msgvec = v, rv;
1136 NYD_ENTER;
1138 rv = _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL);
1139 NYD_LEAVE;
1140 return rv;
1143 FL int
1144 c_More(void *v)
1146 int *msgvec = v, rv;
1147 NYD_ENTER;
1149 rv = _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL);
1150 NYD_LEAVE;
1151 return rv;
1154 FL int
1155 c_type(void *v)
1157 int *msgvec = v, rv;
1158 NYD_ENTER;
1160 rv = _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL);
1161 NYD_LEAVE;
1162 return rv;
1165 FL int
1166 c_Type(void *v)
1168 int *msgvec = v, rv;
1169 NYD_ENTER;
1171 rv = _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL);
1172 NYD_LEAVE;
1173 return rv;
1176 FL int
1177 c_show(void *v)
1179 int *msgvec = v, rv;
1180 NYD_ENTER;
1182 rv = _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL);
1183 NYD_LEAVE;
1184 return rv;
1187 FL int
1188 c_pipe(void *v)
1190 char *str = v;
1191 int rv;
1192 NYD_ENTER;
1194 rv = _pipe1(str, 1);
1195 NYD_LEAVE;
1196 return rv;
1199 FL int
1200 c_Pipe(void *v)
1202 char *str = v;
1203 int rv;
1204 NYD_ENTER;
1206 rv = _pipe1(str, 0);
1207 NYD_LEAVE;
1208 return rv;
1211 FL int
1212 c_top(void *v)
1214 int *msgvec = v, *ip, c, topl, lines, empty_last;
1215 struct message *mp;
1216 char *cp, *linebuf = NULL;
1217 size_t linesize = 0;
1218 FILE *ibuf;
1219 NYD_ENTER;
1221 topl = 5;
1222 cp = ok_vlook(toplines);
1223 if (cp != NULL) {
1224 topl = atoi(cp);
1225 if (topl < 0 || topl > 10000)
1226 topl = 5;
1229 #ifdef HAVE_COLOUR
1230 if (options & OPT_TTYOUT)
1231 colour_table_create(FAL0); /* (salloc()s) */
1232 #endif
1233 empty_last = 1;
1234 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
1235 ++ip) {
1236 mp = message + *ip - 1;
1237 touch(mp);
1238 setdot(mp);
1239 did_print_dot = TRU1;
1240 if (!empty_last)
1241 printf("\n");
1242 _show_msg_overview(stdout, mp, *ip);
1243 if (mp->m_flag & MNOFROM)
1244 /* XXX c_top(): coloured output? */
1245 printf("From %s %s\n", fakefrom(mp), fakedate(mp->m_time));
1246 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1247 v = NULL;
1248 break;
1250 c = mp->m_lines;
1251 for (lines = 0; lines < c && UICMP(32, lines, <=, topl); ++lines) {
1252 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1253 break;
1254 puts(linebuf);
1256 for (cp = linebuf; *cp != '\0' && blankchar(*cp); ++cp)
1258 empty_last = (*cp == '\0');
1262 if (linebuf != NULL)
1263 free(linebuf);
1264 NYD_LEAVE;
1265 return (v != NULL);
1268 FL int
1269 c_stouch(void *v)
1271 int *msgvec = v, *ip;
1272 NYD_ENTER;
1274 for (ip = msgvec; *ip != 0; ++ip) {
1275 setdot(message + *ip - 1);
1276 dot->m_flag |= MTOUCH;
1277 dot->m_flag &= ~MPRESERVE;
1278 did_print_dot = TRU1;
1280 NYD_LEAVE;
1281 return 0;
1284 FL int
1285 c_mboxit(void *v)
1287 int *msgvec = v, *ip;
1288 NYD_ENTER;
1290 for (ip = msgvec; *ip != 0; ++ip) {
1291 setdot(message + *ip - 1);
1292 dot->m_flag |= MTOUCH | MBOX;
1293 dot->m_flag &= ~MPRESERVE;
1294 did_print_dot = TRU1;
1296 NYD_LEAVE;
1297 return 0;
1300 FL int
1301 c_folders(void *v)
1303 char dirname[PATH_MAX], *name, **argv = v;
1304 char const *cmd;
1305 int rv = 1;
1306 NYD_ENTER;
1308 if (*argv) {
1309 name = expand(*argv);
1310 if (name == NULL)
1311 goto jleave;
1312 } else if (!getfold(dirname, sizeof dirname)) {
1313 fprintf(stderr, _("No value set for \"folder\"\n"));
1314 goto jleave;
1315 } else
1316 name = dirname;
1318 if (which_protocol(name) == PROTO_IMAP) {
1319 #ifdef HAVE_IMAP
1320 imap_folders(name, *argv == NULL);
1321 #else
1322 rv = c_cmdnotsupp(NULL);
1323 #endif
1324 } else {
1325 if ((cmd = ok_vlook(LISTER)) == NULL)
1326 cmd = XLISTER;
1327 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1329 jleave:
1330 NYD_LEAVE;
1331 return rv;
1334 /* s-it-mode */