`unaccount', `undefine': return errors as appropriate..
[s-mailx.git] / cmd1.c
blob8548a61f8032af44f550f656ed91904339dcf868
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 - 2015 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd1
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 static int _screen;
43 static sigjmp_buf _cmd1_pipestop;
44 static sigjmp_buf _cmd1_pipejmp;
46 static void _cmd1_onpipe(int signo);
47 static void _cmd1_brokpipe(int signo);
49 /* Prepare and print "[Message: xy]:" intro */
50 static void _show_msg_overview(FILE *obuf, struct message *mp, int msg_no);
52 /* ... And place the extracted date in `date' */
53 static void _parse_from_(struct message *mp, char date[FROM_DATEBUF]);
55 /* Print out the header of a specific message
56 * __hprf: handle *headline*
57 * __subject: return -1 if Subject: yet seen, otherwise smalloc()d Subject:
58 * __putindent: print out the indenting in threaded display */
59 static void _print_head(size_t yetprinted, size_t msgno, FILE *f,
60 bool_t threaded);
61 static void __hprf(size_t yetprinted, char const *fmt, size_t msgno,
62 FILE *f, bool_t threaded, char const *attrlist);
63 static char * __subject(struct message *mp, bool_t threaded,
64 size_t yetprinted);
65 static int __putindent(FILE *fp, struct message *mp, int maxwidth);
67 static int _dispc(struct message *mp, char const *a);
69 /* Shared `z' implementation */
70 static int a_cmd_scroll(char const *arg, bool_t onlynew);
72 /* Shared `headers' implementation */
73 static int _headers(int msgspec);
75 /* Show the requested messages */
76 static int _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
77 bool_t dodecode, char *cmd, ui64_t *tstats);
79 /* Pipe the requested messages */
80 static int _pipe1(char *str, int doign);
82 static void
83 _cmd1_onpipe(int signo)
85 NYD_X; /* Signal handler */
86 UNUSED(signo);
87 siglongjmp(_cmd1_pipejmp, 1);
90 static void
91 _cmd1_brokpipe(int signo)
93 NYD_X; /* Signal handler */
94 UNUSED(signo);
95 siglongjmp(_cmd1_pipestop, 1);
98 static void
99 _show_msg_overview(FILE *obuf, struct message *mp, int msg_no)
101 char const *cpre = "", *csuf = "";
102 NYD_ENTER;
104 #ifdef HAVE_COLOUR
105 if (colour_table != NULL) {
106 struct str const *sp;
108 if ((sp = colour_get(COLOURSPEC_MSGINFO)) != NULL)
109 cpre = sp->s;
110 csuf = colour_get(COLOURSPEC_RESET)->s;
112 #endif
113 fprintf(obuf, _("%s[-- Message %2d -- %lu lines, %lu bytes --]:%s\n"),
114 cpre, msg_no, (ul_i)mp->m_lines, (ul_i)mp->m_size, csuf);
115 NYD_LEAVE;
118 static void
119 _parse_from_(struct message *mp, char date[FROM_DATEBUF]) /* TODO line pool */
121 FILE *ibuf;
122 int hlen;
123 char *hline = NULL;
124 size_t hsize = 0;
125 NYD_ENTER;
127 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
128 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
129 extract_date_from_from_(hline, hlen, date);
130 if (hline != NULL)
131 free(hline);
132 NYD_LEAVE;
135 static void
136 _print_head(size_t yetprinted, size_t msgno, FILE *f, bool_t threaded)
138 enum {attrlen = 14};
139 char attrlist[attrlen +1], *cp;
140 char const *fmt;
141 NYD_ENTER;
143 if ((cp = ok_vlook(attrlist)) != NULL) {
144 if (strlen(cp) == attrlen) {
145 memcpy(attrlist, cp, attrlen +1);
146 goto jattrok;
148 n_err(_("*attrlist* is not of the correct length, using builtin\n"));
151 if (ok_blook(bsdcompat) || ok_blook(bsdflags)) {
152 char const bsdattr[attrlen +1] = "NU *HMFAT+-$~";
153 memcpy(attrlist, bsdattr, sizeof bsdattr);
154 } else if (env_blook("SYSV3", FAL0)) {
155 char const bsdattr[attrlen +1] = "NU *HMFAT+-$~";
156 memcpy(attrlist, bsdattr, sizeof bsdattr);
157 OBSOLETE(_("*SYSV3*: please use *bsdcompat* or *bsdflags*, "
158 "or set *attrlist*"));
159 } else {
160 char const pattr[attrlen +1] = "NUROSPMFAT+-$~";
161 memcpy(attrlist, pattr, sizeof pattr);
164 jattrok:
165 if ((fmt = ok_vlook(headline)) == NULL) {
166 fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
167 ? "%>%a%m %-20f %16d %3l/%-5o %i%-S"
168 : "%>%a%m %-18f %16d %4l/%-5o %i%-s");
171 __hprf(yetprinted, fmt, msgno, f, threaded, attrlist);
172 NYD_LEAVE;
175 static void
176 __hprf(size_t yetprinted, char const *fmt, size_t msgno, FILE *f,
177 bool_t threaded, char const *attrlist)
179 char buf[16], datebuf[FROM_DATEBUF], *cp, *subjline;
180 char const *datefmt, *date, *name, *fp;
181 int i, n, s, wleft, subjlen;
182 struct message *mp;
183 time_t datet;
184 enum {
185 _NONE = 0,
186 _ISADDR = 1<<0,
187 _ISTO = 1<<1,
188 _IFMT = 1<<2,
189 _LOOP_MASK = (1<<3) - 1,
190 _SFMT = 1<<3
191 } flags = _NONE;
192 NYD_ENTER;
193 UNUSED(buf);
195 mp = message + msgno - 1;
196 datet = mp->m_time;
197 date = NULL;
199 datefmt = ok_vlook(datefield);
200 jredo:
201 if (datefmt != NULL) {
202 fp = hfield1("date", mp);/* TODO use m_date field! */
203 if (fp == NULL) {
204 datefmt = NULL;
205 goto jredo;
207 datet = rfctime(fp);
208 date = fakedate(datet);
209 fp = ok_vlook(datefield_markout_older);
210 i = (*datefmt != '\0');
211 if (fp != NULL)
212 i |= (*fp != '\0') ? 2 | 4 : 2; /* XXX no magics */
214 /* May we strftime(3)? */
215 if (i & (1 | 4))
216 memcpy(&time_current.tc_local, localtime(&datet),
217 sizeof time_current.tc_local);
219 if ((i & 2) && (datet > time_current.tc_time + DATE_SECSDAY ||
220 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
221 (datet + _6M < time_current.tc_time))) {
222 #undef _6M
223 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
224 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
225 memcpy(datebuf + 4, date + 4, 7);
226 datebuf[4 + 7] = ' ';
227 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
228 datebuf[4 + 7 + 1 + 4] = '\0';
229 date = datebuf;
231 } else if ((i & 1) == 0)
232 datefmt = NULL;
233 } else if (datet == (time_t)0 && !(mp->m_flag & MNOFROM)) {
234 /* TODO eliminate this path, query the FROM_ date in setptr(),
235 * TODO all other codepaths do so by themselves ALREADY ?????
236 * TODO assert(mp->m_time != 0);, then
237 * TODO ALSO changes behaviour of datefield_markout_older */
238 _parse_from_(mp, datebuf);
239 date = datebuf;
240 } else
241 date = fakedate(datet);
243 flags |= _ISADDR;
244 name = name1(mp, 0);
245 if (name != NULL && ok_blook(showto) && is_myname(skin(name))) {
246 if ((cp = hfield1("to", mp)) != NULL) {
247 name = cp;
248 flags |= _ISTO;
251 if (name == NULL) {
252 name = "";
253 flags &= ~_ISADDR;
255 if (flags & _ISADDR)
256 name = ok_blook(showname) ? realname(name) : prstr(skin(name));
258 subjline = NULL;
260 /* Detect the width of the non-format characters in *headline*;
261 * like that we can simply use putc() in the next loop, since we have
262 * already calculated their column widths (TODO it's sick) */
263 wleft = subjlen = scrnwidth;
265 for (fp = fmt; *fp != '\0'; ++fp) {
266 if (*fp == '%') {
267 if (*++fp == '-')
268 ++fp;
269 else if (*fp == '+')
270 ++fp;
271 if (digitchar(*fp)) {
272 n = 0;
274 n = 10*n + *fp - '0';
275 while (++fp, digitchar(*fp));
276 subjlen -= n;
278 if (*fp == 'i')
279 flags |= _IFMT;
281 if (*fp == '\0')
282 break;
283 } else {
284 #ifdef HAVE_WCWIDTH
285 if (mb_cur_max > 1) {
286 wchar_t wc;
287 if ((s = mbtowc(&wc, fp, mb_cur_max)) == -1)
288 n = s = 1;
289 else if ((n = wcwidth(wc)) == -1)
290 n = 1;
291 } else
292 #endif
293 n = s = 1;
294 subjlen -= n;
295 wleft -= n;
296 while (--s > 0)
297 ++fp;
301 /* Walk *headline*, producing output TODO not (really) MB safe */
302 for (fp = fmt; *fp != '\0'; ++fp) {
303 char c;
304 if ((c = *fp & 0xFF) != '%')
305 putc(c, f);
306 else {
307 flags &= _LOOP_MASK;
308 n = 0;
309 s = 1;
310 if (*++fp == '-') {
311 s = -1;
312 ++fp;
313 } else if (*fp == '+')
314 ++fp;
315 if (digitchar(*fp)) {
317 n = 10*n + *fp - '0';
318 while (++fp, digitchar(*fp));
320 if (*fp == '\0')
321 break;
323 n *= s;
324 switch ((c = *fp & 0xFF)) {
325 case '%':
326 goto jputc;
327 case '>':
328 case '<':
329 if (dot != mp)
330 c = ' ';
331 goto jputc;
332 case '$':
333 #ifdef HAVE_SPAM
334 if (n == 0)
335 n = 5;
336 if (UICMP(32, ABS(n), >, wleft))
337 n = (n < 0) ? -wleft : wleft;
338 snprintf(buf, sizeof buf, "%u.%02u",
339 (mp->m_spamscore >> 8), (mp->m_spamscore & 0xFF));
340 n = fprintf(f, "%*s", n, buf);
341 wleft = (n >= 0) ? wleft - n : 0;
342 break;
343 #else
344 c = '?';
345 goto jputc;
346 #endif
347 case 'a':
348 c = _dispc(mp, attrlist);
349 jputc:
350 if (UICMP(32, ABS(n), >, wleft))
351 n = (n < 0) ? -wleft : wleft;
352 n = fprintf(f, "%*c", n, c);
353 wleft = (n >= 0) ? wleft - n : 0;
354 break;
355 case 'd':
356 if (datefmt != NULL) {
357 i = strftime(datebuf, sizeof datebuf, datefmt,
358 &time_current.tc_local);
359 if (i != 0)
360 date = datebuf;
361 else
362 n_err(_("Ignored date format, it excesses the target "
363 "buffer (%lu bytes)\n"), (ul_i)sizeof(datebuf));
364 datefmt = NULL;
366 if (n == 0)
367 n = 16;
368 if (UICMP(32, ABS(n), >, wleft))
369 n = (n < 0) ? -wleft : wleft;
370 n = fprintf(f, "%*.*s", n, n, date);
371 wleft = (n >= 0) ? wleft - n : 0;
372 break;
373 case 'e':
374 if (n == 0)
375 n = 2;
376 if (UICMP(32, ABS(n), >, wleft))
377 n = (n < 0) ? -wleft : wleft;
378 n = fprintf(f, "%*u", n, (threaded == 1 ? mp->m_level : 0));
379 wleft = (n >= 0) ? wleft - n : 0;
380 break;
381 case 'f':
382 if (n == 0) {
383 n = 18;
384 if (s < 0)
385 n = -n;
387 i = ABS(n);
388 if (i > wleft) {
389 i = wleft;
390 n = (n < 0) ? -wleft : wleft;
392 if (flags & _ISTO) /* XXX tr()! */
393 i -= 3;
394 n = fprintf(f, "%s%s", ((flags & _ISTO) ? "To " : ""),
395 colalign(name, i, n, &wleft));
396 if (n < 0)
397 wleft = 0;
398 else if (flags & _ISTO)
399 wleft -= 3;
400 break;
401 case 'i':
402 if (threaded) {
403 n = __putindent(f, mp, MIN(wleft, scrnwidth - 60));
404 wleft = (n >= 0) ? wleft - n : 0;
406 break;
407 case 'l':
408 if (n == 0)
409 n = 4;
410 if (UICMP(32, ABS(n), >, wleft))
411 n = (n < 0) ? -wleft : wleft;
412 if (mp->m_xlines) {
413 n = fprintf(f, "%*ld", n, mp->m_xlines);
414 wleft = (n >= 0) ? wleft - n : 0;
415 } else {
416 n = ABS(n);
417 wleft -= n;
418 while (n-- != 0)
419 putc(' ', f);
421 break;
422 case 'm':
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, "%*lu", n, (ul_i)msgno);
432 wleft = (n >= 0) ? wleft - n : 0;
433 break;
434 case 'o':
435 if (n == 0)
436 n = -5;
437 if (UICMP(32, ABS(n), >, wleft))
438 n = (n < 0) ? -wleft : wleft;
439 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
440 wleft = (n >= 0) ? wleft - n : 0;
441 break;
442 case 'S':
443 flags |= _SFMT;
444 /*FALLTHRU*/
445 case 's':
446 if (n == 0)
447 n = subjlen - 2;
448 if (n > 0 && s < 0)
449 n = -n;
450 if (subjlen > wleft)
451 subjlen = wleft;
452 if (UICMP(32, ABS(n), >, subjlen))
453 n = (n < 0) ? -subjlen : subjlen;
454 if (flags & _SFMT)
455 n -= (n < 0) ? -2 : 2;
456 if (n == 0)
457 break;
458 if (subjline == NULL)
459 subjline = __subject(mp, (threaded && (flags & _IFMT)),
460 yetprinted);
461 if (subjline == (char*)-1) {
462 n = fprintf(f, "%*s", n, "");
463 wleft = (n >= 0) ? wleft - n : 0;
464 } else {
465 n = fprintf(f, ((flags & _SFMT) ? "\"%s\"" : "%s"),
466 colalign(subjline, ABS(n), n, &wleft));
467 if (n < 0)
468 wleft = 0;
470 break;
471 case 'T': { /* Message recipient flags */
472 /* We never can reuse "name" since it's the full name */
473 struct name const *np = lextract(hfield1("to", mp), GTO | GSKIN);
474 c = ' ';
475 i = 0;
476 j_A_redo:
477 for (; np != NULL; np = np->n_flink) {
478 switch (is_mlist(np->n_name, FAL0)) {
479 case MLIST_SUBSCRIBED: c = 'S'; goto jputc;
480 case MLIST_KNOWN: c = 'L'; goto jputc;
481 case MLIST_OTHER:
482 default: break;
485 if (i != 0)
486 goto jputc;
487 ++i;
488 np = lextract(hfield1("cc", mp), GCC | GSKIN);
489 goto j_A_redo;
491 case 't':
492 if (n == 0) {
493 n = 3;
494 if (threaded)
495 for (i = msgCount; i > 999; i /= 10)
496 ++n;
498 if (UICMP(32, ABS(n), >, wleft))
499 n = (n < 0) ? -wleft : wleft;
500 n = fprintf(f, "%*lu", n,
501 (threaded ? (ul_i)mp->m_threadpos : (ul_i)msgno));
502 wleft = (n >= 0) ? wleft - n : 0;
503 break;
504 case 'U':
505 #ifdef HAVE_IMAP
506 if (n == 0)
507 n = 9;
508 if (UICMP(32, ABS(n), >, wleft))
509 n = (n < 0) ? -wleft : wleft;
510 n = fprintf(f, "%*lu", n, mp->m_uid);
511 wleft = (n >= 0) ? wleft - n : 0;
512 break;
513 #else
514 c = '?';
515 goto jputc;
516 #endif
517 default:
518 if (options & OPT_D_V)
519 n_err(_("Unkown *headline* format: \"%%%c\"\n"), c);
520 c = '?';
521 goto jputc;
524 if (wleft <= 0)
525 break;
528 putc('\n', f);
530 if (subjline != NULL && subjline != (char*)-1)
531 free(subjline);
532 NYD_LEAVE;
535 static char *
536 __subject(struct message *mp, bool_t threaded, size_t yetprinted)
538 struct str in, out;
539 char *rv = (char*)-1, *ms;
540 NYD_ENTER;
542 if ((ms = hfield1("subject", mp)) == NULL)
543 goto jleave;
545 in.l = strlen(in.s = ms);
546 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
547 rv = ms = out.s;
549 if (!threaded || mp->m_level == 0)
550 goto jleave;
552 /* In a display thread - check wether this message uses the same
553 * Subject: as it's parent or elder neighbour, suppress printing it if
554 * this is the case. To extend this a bit, ignore any leading Re: or
555 * Fwd: plus follow-up WS. Ignore invisible messages along the way */
556 ms = subject_re_trim(ms);
558 for (; (mp = prev_in_thread(mp)) != NULL && yetprinted-- > 0;) {
559 char *os;
561 if (visible(mp) && (os = hfield1("subject", mp)) != NULL) {
562 struct str oout;
563 int x;
565 in.l = strlen(in.s = os);
566 mime_fromhdr(&in, &oout, TD_ICONV | TD_ISPR);
567 x = asccasecmp(ms, subject_re_trim(oout.s));
568 free(oout.s);
570 if (!x) {
571 free(out.s);
572 rv = (char*)-1;
574 break;
577 jleave:
578 NYD_LEAVE;
579 return rv;
582 static int
583 __putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
585 struct message *mq;
586 int *us, indlvl, indw, i, important = MNEW | MFLAGGED;
587 char *cs;
588 NYD_ENTER;
590 if (mp->m_level == 0 || maxwidth == 0) {
591 indw = 0;
592 goto jleave;
595 cs = ac_alloc(mp->m_level);
596 us = ac_alloc(mp->m_level * sizeof *us);
598 i = mp->m_level - 1;
599 if (mp->m_younger && UICMP(32, i + 1, ==, mp->m_younger->m_level)) {
600 if (mp->m_parent && mp->m_parent->m_flag & important)
601 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
602 else
603 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
604 cs[i] = '+';
605 } else {
606 if (mp->m_parent && mp->m_parent->m_flag & important)
607 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
608 else
609 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
610 cs[i] = '\\';
613 mq = mp->m_parent;
614 for (i = mp->m_level - 2; i >= 0; --i) {
615 if (mq) {
616 if (UICMP(32, i, >, mq->m_level - 1)) {
617 us[i] = cs[i] = ' ';
618 continue;
620 if (mq->m_younger) {
621 if (mq->m_parent && (mq->m_parent->m_flag & important))
622 us[i] = 0x2503;
623 else
624 us[i] = 0x2502;
625 cs[i] = '|';
626 } else
627 us[i] = cs[i] = ' ';
628 mq = mq->m_parent;
629 } else
630 us[i] = cs[i] = ' ';
633 --maxwidth;
634 for (indlvl = indw = 0; (ui8_t)indlvl < mp->m_level && indw < maxwidth;
635 ++indlvl) {
636 if (indw < maxwidth - 1)
637 indw += (int)putuc(us[indlvl], cs[indlvl] & 0xFF, fp);
638 else
639 indw += (int)putuc(0x21B8, '^', fp);
641 indw += putuc(0x25B8, '>', fp);
643 ac_free(us);
644 ac_free(cs);
645 jleave:
646 NYD_LEAVE;
647 return indw;
650 static int
651 _dispc(struct message *mp, char const *a)
653 int i = ' ';
654 NYD_ENTER;
656 if ((mp->m_flag & (MREAD | MNEW)) == MREAD)
657 i = a[3];
658 if ((mp->m_flag & (MREAD | MNEW)) == (MREAD | MNEW))
659 i = a[2];
660 if (mp->m_flag & MANSWERED)
661 i = a[8];
662 if (mp->m_flag & MDRAFTED)
663 i = a[9];
664 if ((mp->m_flag & (MREAD | MNEW)) == MNEW)
665 i = a[0];
666 if (!(mp->m_flag & (MREAD | MNEW)))
667 i = a[1];
668 if (mp->m_flag & MSPAM)
669 i = a[12];
670 if (mp->m_flag & MSPAMUNSURE)
671 i = a[13];
672 if (mp->m_flag & MSAVED)
673 i = a[4];
674 if (mp->m_flag & MPRESERVE)
675 i = a[5];
676 if (mp->m_flag & (MBOX | MBOXED))
677 i = a[6];
678 if (mp->m_flag & MFLAGGED)
679 i = a[7];
680 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
681 i = a[11];
682 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
683 i = a[10];
684 NYD_LEAVE;
685 return i;
688 static int
689 a_cmd_scroll(char const *arg, bool_t onlynew){
690 long l;
691 char *eptr;
692 bool_t isabs;
693 int msgspec, size, maxs;
694 NYD2_ENTER;
696 msgspec = onlynew ? -1 : 0;
697 size = screensize();
698 maxs = msgCount / size;
700 switch(*arg){
701 case '\0':
702 ++_screen;
703 goto jfwd;
704 case '$':
705 if(arg[1] != '\0')
706 goto jerr;
707 if(_screen == maxs)
708 goto jerrfwd;
709 _screen = maxs;
710 break;
711 case '+':
712 if(arg[1] == '\0')
713 ++_screen;
714 else{
715 isabs = FAL0;
717 ++arg;
718 if(0){
719 case '1': case '2': case '3': case '4': case '5':
720 case '6': case '7': case '8': case '9': case '0':
721 isabs = TRU1;
723 l = strtol(arg, &eptr, 10);
724 if(*eptr != '\0')
725 goto jerr;
726 if(l > maxs - (isabs ? 0 : _screen))
727 goto jerrfwd;
728 _screen = isabs ? (int)l : _screen + l;
730 jfwd:
731 if(_screen > maxs){
732 jerrfwd:
733 _screen = maxs;
734 printf(_("On last screenful of messages\n"));
736 break;
738 case '-':
739 if(arg[1] == '\0')
740 --_screen;
741 else{
742 ++arg;
743 l = strtol(arg, &eptr, 10);
744 if(*eptr != '\0')
745 goto jerr;
746 if(l > _screen)
747 goto jerrbwd;
748 _screen -= l;
750 if(_screen < 0){
751 jerrbwd:
752 _screen = 0;
753 printf(_("On first screenful of messages\n"));
755 if(msgspec == -1)
756 msgspec = -2;
757 break;
758 default:
759 jerr:
760 n_err(_("Unrecognized scrolling command \"%s\"\n"), arg);
761 size = 1;
762 goto jleave;
765 size = _headers(msgspec);
766 jleave:
767 NYD2_LEAVE;
768 return size;
771 static int
772 _headers(int msgspec) /* TODO rework v15 */
774 ui32_t flag;
775 int g, k, mesg, size, lastg = 1;
776 struct message *mp, *mq, *lastmq = NULL;
777 enum mflag fl = MNEW | MFLAGGED;
778 NYD_ENTER;
780 time_current_update(&time_current, FAL0);
782 flag = 0;
783 size = screensize();
784 if (_screen < 0)
785 _screen = 0;
786 #if 0 /* FIXME original code path */
787 k = _screen * size;
788 #else
789 if (msgspec <= 0)
790 k = _screen * size;
791 else
792 k = msgspec;
793 #endif
794 if (k >= msgCount)
795 k = msgCount - size;
796 if (k < 0)
797 k = 0;
799 if (mb.mb_threaded == 0) {
800 g = 0;
801 mq = message;
802 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
803 if (visible(mp)) {
804 if (g % size == 0)
805 mq = mp;
806 if (mp->m_flag & fl) {
807 lastg = g;
808 lastmq = mq;
810 if ((msgspec > 0 && PTRCMP(mp, ==, message + msgspec - 1)) ||
811 (msgspec == 0 && g == k) ||
812 (msgspec == -2 && g == k + size && lastmq) ||
813 (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
814 break;
815 g++;
817 if (lastmq && (msgspec == -2 ||
818 (msgspec == -1 && PTRCMP(mp, ==, message + msgCount)))) {
819 g = lastg;
820 mq = lastmq;
822 _screen = g / size;
823 mp = mq;
824 mesg = (int)PTR2SIZE(mp - message);
825 if (PTRCMP(dot, !=, message + msgspec - 1)) { /* TODO really?? */
826 for (mq = mp; PTRCMP(mq, <, message + msgCount); ++mq)
827 if (visible(mq)) {
828 setdot(mq);
829 break;
832 #ifdef HAVE_IMAP
833 if (mb.mb_type == MB_IMAP)
834 imap_getheaders(mesg + 1, mesg + size);
835 #endif
836 srelax_hold();
837 for (; PTRCMP(mp, <, message + msgCount); ++mp) {
838 ++mesg;
839 if (!visible(mp))
840 continue;
841 if (UICMP(32, flag++, >=, size))
842 break;
843 _print_head(0, mesg, stdout, 0);
844 srelax();
846 srelax_rele();
847 } else { /* threaded */
848 g = 0;
849 mq = threadroot;
850 for (mp = threadroot; mp; mp = next_in_thread(mp))
851 if (visible(mp) &&
852 (mp->m_collapsed <= 0 ||
853 PTRCMP(mp, ==, message + msgspec - 1))) {
854 if (g % size == 0)
855 mq = mp;
856 if (mp->m_flag & fl) {
857 lastg = g;
858 lastmq = mq;
860 if ((msgspec > 0 && PTRCMP(mp, ==, message + msgspec - 1)) ||
861 (msgspec == 0 && g == k) ||
862 (msgspec == -2 && g == k + size && lastmq) ||
863 (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
864 break;
865 g++;
867 if (lastmq && (msgspec == -2 ||
868 (msgspec == -1 && PTRCMP(mp, ==, message + msgCount)))) {
869 g = lastg;
870 mq = lastmq;
872 _screen = g / size;
873 mp = mq;
874 if (PTRCMP(dot, !=, message + msgspec - 1)) { /* TODO really?? */
875 for (mq = mp; mq; mq = next_in_thread(mq))
876 if (visible(mq) && mq->m_collapsed <= 0) {
877 setdot(mq);
878 break;
881 srelax_hold();
882 while (mp) {
883 if (visible(mp) &&
884 (mp->m_collapsed <= 0 ||
885 PTRCMP(mp, ==, message + msgspec - 1))) {
886 if (UICMP(32, flag++, >=, size))
887 break;
888 _print_head(flag - 1, PTR2SIZE(mp - message + 1), stdout,
889 mb.mb_threaded);
890 srelax();
892 mp = next_in_thread(mp);
894 srelax_rele();
897 if (!flag)
898 printf(_("No more mail.\n"));
899 NYD_LEAVE;
900 return !flag;
903 static int
904 _type1(int *msgvec, bool_t doign, bool_t dopage, bool_t dopipe,
905 bool_t dodecode, char *cmd, ui64_t *tstats)
907 ui64_t mstats[1];
908 int rv, *ip;
909 struct message *mp;
910 char const *cp;
911 FILE * volatile obuf;
912 bool_t volatile hadsig = FAL0, isrelax = FAL0;
913 NYD_ENTER;
914 {/* C89.. */
915 enum sendaction const action = ((dopipe && ok_blook(piperaw))
916 ? SEND_MBOX : dodecode
917 ? SEND_SHOW : doign
918 ? SEND_TODISP : SEND_TODISP_ALL);
919 bool_t const volatile formfeed = (dopipe && ok_blook(page));
920 obuf = stdout;
922 if (sigsetjmp(_cmd1_pipestop, 1)) {
923 hadsig = TRU1;
924 goto jclose_pipe;
927 if (dopipe) {
928 if ((cp = ok_vlook(SHELL)) == NULL)
929 cp = XSHELL;
930 if ((obuf = Popen(cmd, "w", cp, NULL, 1)) == NULL) {
931 n_perr(cmd, 0);
932 obuf = stdout;
933 } else
934 safe_signal(SIGPIPE, &_cmd1_brokpipe);
935 } else if ((options & OPT_TTYOUT) && (dopage ||
936 ((options & OPT_INTERACTIVE) && (cp = ok_vlook(crt)) != NULL))) {
937 char const *pager = NULL;
938 size_t nlines = 0;
940 if (!dopage) {
941 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
942 mp = message + *ip - 1;
943 if (!(mp->m_have & HAVE_BODY))
944 if (get_body(mp) != OKAY) {
945 rv = 1;
946 goto jleave;
948 nlines += mp->m_lines + 1; /* Message info XXX and PARTS... */
952 /* >= not <: we return to the prompt */
953 if (dopage || UICMP(z, nlines, >=,
954 (*cp != '\0' ? atoi(cp) : realscreenheight))) {
955 char const *env_add[2];
956 pager = get_pager(env_add + 0);
957 env_add[1] = NULL;
958 obuf = Popen(pager, "w", NULL, env_add, 1);
959 if (obuf == NULL) {
960 n_perr(pager, 0);
961 obuf = stdout;
962 pager = NULL;
963 } else
964 safe_signal(SIGPIPE, &_cmd1_brokpipe);
966 #ifdef HAVE_COLOUR
967 if (IS_TTY_SESSION() && action != SEND_MBOX)
968 colour_table_create(pager != NULL); /* (salloc()s!) */
969 #endif
971 #ifdef HAVE_COLOUR
972 else if (IS_TTY_SESSION() && action != SEND_MBOX)
973 colour_table_create(FAL0); /* (salloc()s!) */
974 #endif
976 /*TODO unless we have our signal manager special care must be taken */
977 srelax_hold();
978 isrelax = TRU1;
979 for (ip = msgvec; *ip && PTRCMP(ip - msgvec, <, msgCount); ++ip) {
980 mp = message + *ip - 1;
981 touch(mp);
982 setdot(mp);
983 uncollapse1(mp, 1);
984 if (!dopipe && ip != msgvec)
985 fprintf(obuf, "\n");
986 if (action != SEND_MBOX)
987 _show_msg_overview(obuf, mp, *ip);
988 sendmp(mp, obuf, (doign ? ignore : NULL), NULL, action, mstats);
989 srelax();
990 if (formfeed) /* TODO a nicer way to separate piped messages! */
991 putc('\f', obuf);
992 if (tstats != NULL)
993 tstats[0] += mstats[0];
995 srelax_rele();
996 isrelax = FAL0;
998 jclose_pipe:
999 if (obuf != stdout) {
1000 /* Ignore SIGPIPE so it can't cause a duplicate close */
1001 safe_signal(SIGPIPE, SIG_IGN);
1002 if (hadsig && isrelax)
1003 srelax_rele();
1004 colour_reset(obuf); /* XXX hacky; only here because we still jump */
1005 Pclose(obuf, TRU1);
1006 safe_signal(SIGPIPE, dflpipe);
1008 rv = 0;
1010 jleave:
1011 NYD_LEAVE;
1012 return rv;
1015 static int
1016 _pipe1(char *str, int doign)
1018 ui64_t stats[1];
1019 char *cmd;
1020 int *msgvec, rv = 1;
1021 bool_t needs_list;
1022 NYD_ENTER;
1024 if ((cmd = laststring(str, &needs_list, TRU1)) == NULL) {
1025 cmd = ok_vlook(cmd);
1026 if (cmd == NULL || *cmd == '\0') {
1027 n_err(_("Variable *cmd* not set\n"));
1028 goto jleave;
1032 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
1034 if (!needs_list) {
1035 *msgvec = first(0, MMNORM);
1036 if (*msgvec == 0) {
1037 if (pstate & PS_HOOK_MASK) {
1038 rv = 0;
1039 goto jleave;
1041 puts(_("No messages to pipe."));
1042 goto jleave;
1044 msgvec[1] = 0;
1045 } else if (getmsglist(str, msgvec, 0) < 0)
1046 goto jleave;
1047 if (*msgvec == 0) {
1048 if (pstate & PS_HOOK_MASK) {
1049 rv = 0;
1050 goto jleave;
1052 printf("No applicable messages.\n");
1053 goto jleave;
1056 printf(_("Pipe to: \"%s\"\n"), cmd);
1057 stats[0] = 0;
1058 if ((rv = _type1(msgvec, doign, FAL0, TRU1, FAL0, cmd, stats)) == 0)
1059 printf("\"%s\" %" PRIu64 " bytes\n", cmd, stats[0]);
1060 jleave:
1061 NYD_LEAVE;
1062 return rv;
1065 FL int
1066 c_cmdnotsupp(void *v) /* TODO -> lex.c */
1068 NYD_ENTER;
1069 UNUSED(v);
1070 n_err(_("The requested feature is not compiled in\n"));
1071 NYD_LEAVE;
1072 return 1;
1075 FL int
1076 c_headers(void *v)
1078 int rv;
1079 NYD_ENTER;
1081 rv = print_header_group((int*)v);
1082 NYD_LEAVE;
1083 return rv;
1086 FL int
1087 print_header_group(int *vector)
1089 int rv;
1090 NYD_ENTER;
1092 assert(vector != NULL && vector != (void*)-1);
1093 rv = _headers(vector[0]);
1094 NYD_LEAVE;
1095 return rv;
1098 FL int
1099 c_scroll(void *v)
1101 int rv;
1102 NYD_ENTER;
1104 rv = a_cmd_scroll(v, FAL0);
1105 NYD_LEAVE;
1106 return rv;
1109 FL int
1110 c_Scroll(void *v)
1112 int rv;
1113 NYD_ENTER;
1115 rv = a_cmd_scroll(v, TRU1);
1116 NYD_LEAVE;
1117 return rv;
1120 FL int
1121 c_from(void *v)
1123 int *msgvec = v, *ip, n;
1124 char *cp;
1125 FILE * volatile obuf;
1126 NYD_ENTER;
1128 time_current_update(&time_current, FAL0);
1129 obuf = stdout;
1131 /* TODO unfixable memory leaks still */
1132 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
1133 for (n = 0, ip = msgvec; *ip != 0; ++ip)
1134 n++;
1135 if (n > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
1136 char const *p;
1137 if (sigsetjmp(_cmd1_pipejmp, 1))
1138 goto jendpipe;
1139 p = get_pager(NULL);
1140 if ((obuf = Popen(p, "w", NULL, NULL, 1)) == NULL) {
1141 n_perr(p, 0);
1142 obuf = stdout;
1143 cp = NULL;
1144 } else
1145 safe_signal(SIGPIPE, &_cmd1_onpipe);
1149 srelax_hold();
1150 for (n = 0, ip = msgvec; *ip != 0; ++ip) { /* TODO join into _print_head() */
1151 _print_head((size_t)n++, (size_t)*ip, obuf, mb.mb_threaded);
1152 srelax();
1154 srelax_rele();
1156 if (--ip >= msgvec)
1157 setdot(message + *ip - 1);
1159 jendpipe:
1160 if (obuf != stdout) {
1161 safe_signal(SIGPIPE, SIG_IGN);
1162 Pclose(obuf, TRU1);
1163 safe_signal(SIGPIPE, dflpipe);
1165 NYD_LEAVE;
1166 return 0;
1169 FL void
1170 print_headers(size_t bottom, size_t topx, bool_t only_marked)
1172 size_t printed;
1173 NYD_ENTER;
1175 #ifdef HAVE_IMAP
1176 if (mb.mb_type == MB_IMAP)
1177 imap_getheaders(bottom, topx);
1178 #endif
1179 time_current_update(&time_current, FAL0);
1181 srelax_hold();
1182 for (printed = 0; bottom <= topx; ++bottom) {
1183 struct message *mp = message + bottom - 1;
1184 if (only_marked) {
1185 if (!(mp->m_flag & MMARK))
1186 continue;
1187 } else if (!visible(mp))
1188 continue;
1189 _print_head(printed++, bottom, stdout, FAL0);
1190 srelax();
1192 srelax_rele();
1193 NYD_LEAVE;
1196 FL int
1197 c_pdot(void *v)
1199 NYD_ENTER;
1200 UNUSED(v);
1201 printf("%d\n", (int)PTR2SIZE(dot - message + 1));
1202 NYD_LEAVE;
1203 return 0;
1206 FL int
1207 c_more(void *v)
1209 int *msgvec = v, rv;
1210 NYD_ENTER;
1212 rv = _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL);
1213 NYD_LEAVE;
1214 return rv;
1217 FL int
1218 c_More(void *v)
1220 int *msgvec = v, rv;
1221 NYD_ENTER;
1223 rv = _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL);
1224 NYD_LEAVE;
1225 return rv;
1228 FL int
1229 c_type(void *v)
1231 int *msgvec = v, rv;
1232 NYD_ENTER;
1234 rv = _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL);
1235 NYD_LEAVE;
1236 return rv;
1239 FL int
1240 c_Type(void *v)
1242 int *msgvec = v, rv;
1243 NYD_ENTER;
1245 rv = _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL);
1246 NYD_LEAVE;
1247 return rv;
1250 FL int
1251 c_show(void *v)
1253 int *msgvec = v, rv;
1254 NYD_ENTER;
1256 rv = _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL);
1257 NYD_LEAVE;
1258 return rv;
1261 FL int
1262 c_pipe(void *v)
1264 char *str = v;
1265 int rv;
1266 NYD_ENTER;
1268 rv = _pipe1(str, 1);
1269 NYD_LEAVE;
1270 return rv;
1273 FL int
1274 c_Pipe(void *v)
1276 char *str = v;
1277 int rv;
1278 NYD_ENTER;
1280 rv = _pipe1(str, 0);
1281 NYD_LEAVE;
1282 return rv;
1285 FL int
1286 c_top(void *v)
1288 int *msgvec = v, *ip, c, topl, lines, empty_last;
1289 struct message *mp;
1290 char *cp, *linebuf = NULL;
1291 size_t linesize = 0;
1292 FILE *ibuf;
1293 NYD_ENTER;
1295 topl = 5;
1296 cp = ok_vlook(toplines);
1297 if (cp != NULL) {
1298 topl = atoi(cp);
1299 if (topl < 0 || topl > 10000)
1300 topl = 5;
1303 #ifdef HAVE_COLOUR
1304 if (IS_TTY_SESSION())
1305 colour_table_create(FAL0); /* (salloc()s) */
1306 #endif
1307 empty_last = 1;
1308 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
1309 ++ip) {
1310 mp = message + *ip - 1;
1311 touch(mp);
1312 setdot(mp);
1313 pstate |= PS_DID_PRINT_DOT;
1314 if (!empty_last)
1315 printf("\n");
1316 _show_msg_overview(stdout, mp, *ip);
1317 if (mp->m_flag & MNOFROM)
1318 /* XXX c_top(): coloured output? */
1319 printf("From %s %s\n", fakefrom(mp), fakedate(mp->m_time));
1320 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1321 v = NULL;
1322 break;
1324 c = mp->m_lines;
1325 for (lines = 0; lines < c && UICMP(32, lines, <=, topl); ++lines) {
1326 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1327 break;
1328 puts(linebuf);
1330 for (cp = linebuf; *cp != '\0' && blankchar(*cp); ++cp)
1332 empty_last = (*cp == '\0');
1336 if (linebuf != NULL)
1337 free(linebuf);
1338 NYD_LEAVE;
1339 return (v != NULL);
1342 FL int
1343 c_stouch(void *v)
1345 int *msgvec = v, *ip;
1346 NYD_ENTER;
1348 for (ip = msgvec; *ip != 0; ++ip) {
1349 setdot(message + *ip - 1);
1350 dot->m_flag |= MTOUCH;
1351 dot->m_flag &= ~MPRESERVE;
1352 pstate |= PS_DID_PRINT_DOT;
1354 NYD_LEAVE;
1355 return 0;
1358 FL int
1359 c_mboxit(void *v)
1361 int *msgvec = v, *ip;
1362 NYD_ENTER;
1364 if (pstate & PS_EDIT) {
1365 n_err(_("`mbox' can only be used in a system mailbox\n")); /* TODO */
1366 goto jleave;
1369 for (ip = msgvec; *ip != 0; ++ip) {
1370 setdot(message + *ip - 1);
1371 dot->m_flag |= MTOUCH | MBOX;
1372 dot->m_flag &= ~MPRESERVE;
1373 pstate |= PS_DID_PRINT_DOT;
1375 jleave:
1376 NYD_LEAVE;
1377 return 0;
1380 FL int
1381 c_folders(void *v)
1383 char dirname[PATH_MAX], *name, **argv = v;
1384 char const *cmd;
1385 int rv = 1;
1386 NYD_ENTER;
1388 if (*argv) {
1389 name = expand(*argv);
1390 if (name == NULL)
1391 goto jleave;
1392 } else if (!getfold(dirname, sizeof dirname)) {
1393 n_err(_("No value set for \"folder\"\n"));
1394 goto jleave;
1395 } else
1396 name = dirname;
1398 if (which_protocol(name) == PROTO_IMAP) {
1399 #ifdef HAVE_IMAP
1400 imap_folders(name, *argv == NULL);
1401 #else
1402 rv = c_cmdnotsupp(NULL);
1403 #endif
1404 } else {
1405 if ((cmd = ok_vlook(LISTER)) == NULL)
1406 cmd = XLISTER;
1407 run_command(cmd, 0, COMMAND_FD_PASS, COMMAND_FD_PASS, name, NULL, NULL,
1408 NULL);
1410 jleave:
1411 NYD_LEAVE;
1412 return rv;
1415 /* s-it-mode */