1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Header display, search, etc., related user commands.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 /*
9  * Copyright (c) 1980, 1993
10  *      The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #undef su_FILE
37 #define su_FILE cmd_head
38 #define mx_SOURCE
39 
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43 
44 #include <su/cs.h>
45 #include <su/icodec.h>
46 #include <su/mem.h>
47 
48 #include "mx/cmd.h"
49 #include "mx/cmd-mlist.h"
50 #include "mx/colour.h"
51 #include "mx/termios.h"
52 #include "mx/ui-str.h"
53 
54 /* TODO fake */
55 #include "su/code-in.h"
56 
57 static int        _screen;
58 
59 /* Print out the header of a specific message.
60  * time_current must be up-to-date when this is called.
61  * a_chead__hprf: handle *headline*
62  * a_chead__subject: -1 if Subject: yet seen, otherwise n_alloc()d Subject:
63  * a_chead__putindent: print out the indenting in threaded display
64  * a_chead__putuc: print out a Unicode character or a substitute for it, return
65  *    0 on error or wcwidth() (or 1) on success */
66 static void a_chead_print_head(uz yetprinted, uz msgno, FILE *f,
67                boole threaded, boole subject_thread_compress);
68 
69 static void a_chead__hprf(uz yetprinted, char const *fmt, uz msgno,
70                FILE *f, boole threaded, boole subject_thread_compress,
71                char const *attrlist);
72 static char *a_chead__subject(struct message *mp, boole threaded,
73                boole subject_thread_compress, uz yetprinted);
74 static int a_chead__putindent(FILE *fp, struct message *mp, int maxwidth);
75 static uz a_chead__putuc(int u, int c, FILE *fp);
76 static int a_chead__dispc(struct message *mp, char const *a);
77 
78 /* Shared `z' implementation */
79 static int a_chead_scroll(char const *arg, boole onlynew);
80 
81 /* Shared `headers' implementation */
82 static int     _headers(int msgspec);
83 
84 static void
a_chead_print_head(uz yetprinted,uz msgno,FILE * f,boole threaded,boole subject_thread_compress)85 a_chead_print_head(uz yetprinted, uz msgno, FILE *f, boole threaded,
86       boole subject_thread_compress){
87    enum {attrlen = 14};
88    char attrlist[attrlen +1], *cp;
89    char const *fmt;
90    NYD2_IN;
91 
92    if((cp = ok_vlook(attrlist)) != NULL){
93       if(su_cs_len(cp) == attrlen){
94          su_mem_copy(attrlist, cp, attrlen +1);
95          goto jattrok;
96       }
97       n_err(_("*attrlist* is not of the correct length, using built-in\n"));
98    }
99 
100    if(ok_blook(bsdcompat) || ok_blook(bsdflags)){
101       char const bsdattr[attrlen +1] = "NU  *HMFAT+-$~";
102 
103       su_mem_copy(attrlist, bsdattr, sizeof bsdattr);
104    }else if(ok_blook(SYSV3)){
105       char const bsdattr[attrlen +1] = "NU  *HMFAT+-$~";
106 
107       su_mem_copy(attrlist, bsdattr, sizeof bsdattr);
108       n_OBSOLETE(_("*SYSV3*: please use *bsdcompat* or *bsdflags*, "
109          "or set *attrlist*"));
110    }else{
111       char const pattr[attrlen +1]   = "NUROSPMFAT+-$~";
112 
113       su_mem_copy(attrlist, pattr, sizeof pattr);
114    }
115 
116 jattrok:
117    if((fmt = ok_vlook(headline)) == NULL){
118       fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline))
119             ? "%>%a%m %-20f  %16d %4l/%-5o %i%-S"
120             : "%>%a%m %-18f %-16d %4l/%-5o %i%-s");
121    }
122 
123    a_chead__hprf(yetprinted, fmt, msgno, f, threaded, subject_thread_compress,
124       attrlist);
125    NYD2_OU;
126 }
127 
128 static void
a_chead__hprf(uz yetprinted,char const * fmt,uz msgno,FILE * f,boole threaded,boole subject_thread_compress,char const * attrlist)129 a_chead__hprf(uz yetprinted, char const *fmt, uz msgno, FILE *f,
130    boole threaded, boole subject_thread_compress, char const *attrlist)
131 {
132    char buf[16], cbuf[8], *cp, *subjline;
133    char const *date, *name, *fp, *color_tag;
134    int i, n, s, wleft, subjlen;
135    struct message *mp;
136    mx_COLOUR( struct mx_colour_pen *cpen_new su_COMMA
137       *cpen_cur su_COMMA *cpen_bas; )
138    enum {
139       _NONE       = 0,
140       _ISDOT      = 1<<0,
141       _ISTO       = 1<<1,
142       _IFMT       = 1<<2,
143       _LOOP_MASK  = (1<<4) - 1,
144       _SFMT       = 1<<4,        /* It is 'S' */
145       /* For the simple byte-based counts in wleft and n we sometimes need
146        * adjustments to compensate for additional bytes of UTF-8 sequences */
147       _PUTCB_UTF8_SHIFT = 5,
148       _PUTCB_UTF8_MASK = 3<<5
149    } flags = _NONE;
150    NYD2_IN;
151    UNUSED(buf);
152 
153    if ((mp = message + msgno - 1) == dot)
154       flags = _ISDOT;
155 
156    color_tag = NULL;
157    date = n_header_textual_date_info(mp, &color_tag);
158    /* C99 */{
159       boole isto;
160 
161       n_header_textual_sender_info(mp, &cp, NULL, NULL, NULL, &isto);
162       name = cp;
163       if(isto)
164          flags |= _ISTO;
165    }
166 
167    subjline = NULL;
168 
169    /* Detect the width of the non-format characters in *headline*;
170     * like that we can simply use putc() in the next loop, since we have
171     * already calculated their column widths (TODO it's sick) */
172    wleft = subjlen = mx_termios_dimen.tiosd_width;
173 
174    for (fp = fmt; *fp != '\0'; ++fp) {
175       if (*fp == '%') {
176          if (*++fp == '-')
177             ++fp;
178          else if (*fp == '+')
179             ++fp;
180          if (su_cs_is_digit(*fp)) {
181             n = 0;
182             do
183                n = 10*n + *fp - '0';
184             while (++fp, su_cs_is_digit(*fp));
185             subjlen -= n;
186          }
187          if (*fp == 'i')
188             flags |= _IFMT;
189 
190          if (*fp == '\0')
191             break;
192       } else {
193 #ifdef mx_HAVE_WCWIDTH
194          if (n_mb_cur_max > 1) {
195             wchar_t  wc;
196             if ((s = mbtowc(&wc, fp, n_mb_cur_max)) == -1)
197                n = s = 1;
198             else if ((n = wcwidth(wc)) == -1)
199                n = 1;
200          } else
201 #endif
202             n = s = 1;
203          subjlen -= n;
204          wleft -= n;
205          while (--s > 0)
206             ++fp;
207       }
208    }
209 
210    /* Walk *headline*, producing output TODO not (really) MB safe */
211 #ifdef mx_HAVE_COLOUR
212    if(mx_COLOUR_IS_ACTIVE()){
213       if(flags & _ISDOT)
214          color_tag = mx_COLOUR_TAG_SUM_DOT;
215       cpen_bas = mx_colour_pen_create(mx_COLOUR_ID_SUM_HEADER, color_tag);
216       mx_colour_pen_put(cpen_new = cpen_cur = cpen_bas);
217    }else
218       cpen_new = cpen_bas = cpen_cur = NULL;
219 #endif
220 
221    for (fp = fmt; *fp != '\0'; ++fp) {
222       char c;
223 
224       if ((c = *fp & 0xFF) != '%') {
225          mx_COLOUR(
226             if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
227                mx_colour_pen_put(cpen_cur = cpen_new);
228          );
229          putc(c, f);
230          continue;
231       }
232 
233       flags &= _LOOP_MASK;
234       n = 0;
235       s = 1;
236       if ((c = *++fp) == '-') {
237          s = -1;
238          ++fp;
239       } else if (c == '+')
240          ++fp;
241       if (su_cs_is_digit(*fp)) {
242          do
243             n = 10*n + *fp - '0';
244          while (++fp, su_cs_is_digit(*fp));
245       }
246 
247       if ((c = *fp & 0xFF) == '\0')
248          break;
249       n *= s;
250 
251       cbuf[1] = '\0';
252       switch (c) {
253       case '%':
254          goto jputcb;
255       case '>':
256       case '<':
257          if (flags & _ISDOT) {
258             mx_COLOUR(
259                if(mx_COLOUR_IS_ACTIVE())
260                   cpen_new = mx_colour_pen_create(mx_COLOUR_ID_SUM_DOTMARK,
261                         color_tag);
262             );
263             if((n_psonce & n_PSO_UNICODE) && !ok_blook(headline_plain)){
264                if (c == '>')
265                   /* 25B8;BLACK RIGHT-POINTING SMALL TRIANGLE */
266                   cbuf[1] = (char)0x96, cbuf[2] = (char)0xB8;
267                else
268                   /* 25C2;BLACK LEFT-POINTING SMALL TRIANGLE */
269                   cbuf[1] = (char)0x97, cbuf[2] = (char)0x82;
270                c = (char)0xE2;
271                cbuf[3] = '\0';
272                flags |= 2 << _PUTCB_UTF8_SHIFT;
273             }
274          } else
275             c = ' ';
276          goto jputcb;
277       case '$':
278 #ifdef mx_HAVE_SPAM
279          if (n == 0)
280             n = 5;
281          if (UCMP(32, ABS(n), >, wleft))
282             wleft = 0;
283          else{
284             snprintf(buf, sizeof buf, "%u.%02u",
285                (mp->m_spamscore >> 8), (mp->m_spamscore & 0xFF));
286             n = fprintf(f, "%*s", n, buf);
287             wleft = (n >= 0) ? wleft - n : 0;
288          }
289          break;
290 #else
291          c = '?';
292          goto jputcb;
293 #endif
294       case 'a':
295          c = a_chead__dispc(mp, attrlist);
296 jputcb:
297 #ifdef mx_HAVE_COLOUR
298          if(mx_COLOUR_IS_ACTIVE()){
299             if(cpen_new == cpen_cur)
300                cpen_new = cpen_bas;
301             if(cpen_new != cpen_cur)
302                mx_colour_pen_put(cpen_cur = cpen_new);
303          }
304 #endif
305          if (UCMP(32, ABS(n), >, wleft))
306             n = (n < 0) ? -wleft : wleft;
307          cbuf[0] = c;
308          n = fprintf(f, "%*s", n, cbuf);
309          if (n >= 0) {
310             wleft -= n;
311             if ((n = (flags & _PUTCB_UTF8_MASK)) != 0) {
312                n >>= _PUTCB_UTF8_SHIFT;
313                wleft += n;
314             }
315          } else {
316             wleft = 0; /* TODO I/O error.. ? break? */
317          }
318 #ifdef mx_HAVE_COLOUR
319          if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
320             mx_colour_pen_put(cpen_cur = cpen_new);
321 #endif
322          break;
323       case 'd':
324          if (n == 0)
325             n = 16;
326          if (UCMP(32, ABS(n), >, wleft))
327             n = (n < 0) ? -wleft : wleft;
328          n = fprintf(f, "%*.*s", n, ABS(n), date);
329          wleft = (n >= 0) ? wleft - n : 0;
330          break;
331       case 'e':
332          if (n == 0)
333             n = 2;
334          if (UCMP(32, ABS(n), >, wleft))
335             wleft = 0;
336          else{
337             n = fprintf(f, "%*u", n, (threaded == 1 ? mp->m_level : 0));
338             wleft = (n >= 0) ? wleft - n : 0;
339          }
340          break;
341       case 'f':
342          if (n == 0) {
343             n = 18;
344             if (s < 0)
345                n = -n;
346          }
347          i = ABS(n);
348          if (i > wleft) {
349             i = wleft;
350             n = (n < 0) ? -wleft : wleft;
351          }
352          if (flags & _ISTO) {/* XXX tr()! */
353             if(wleft <= 3){
354                wleft = 0;
355                break;
356             }
357             i -= 3;
358          }
359          n = fprintf(f, "%s%s", ((flags & _ISTO) ? "To " : n_empty),
360                colalign(name, i, n, &wleft));
361          if (n < 0)
362             wleft = 0;
363          else if (flags & _ISTO)
364             wleft -= 3;
365          break;
366       case 'i':
367          if (threaded) {
368 #ifdef mx_HAVE_COLOUR
369             if(mx_COLOUR_IS_ACTIVE()){
370                cpen_new = mx_colour_pen_create(mx_COLOUR_ID_SUM_THREAD,
371                      color_tag);
372                if(cpen_new != cpen_cur)
373                   mx_colour_pen_put(cpen_cur = cpen_new);
374             }
375 #endif
376             n = a_chead__putindent(f, mp,
377                   MIN(wleft, S(int,mx_termios_dimen.tiosd_width) - 60));
378             wleft = (n >= 0) ? wleft - n : 0;
379 #ifdef mx_HAVE_COLOUR
380             if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur)
381                mx_colour_pen_put(cpen_cur = cpen_new);
382 #endif
383          }
384          break;
385       case 'L': /* ML status */
386 jmlist: /* v15compat */
387          switch(mx_mlist_query_mp(mp, mx_MLIST_OTHER)){
388          case mx_MLIST_OTHER: c = ' '; break;
389          case mx_MLIST_KNOWN: c = 'l'; break;
390          case mx_MLIST_SUBSCRIBED: c = 'L'; break;
391          case mx_MLIST_POSSIBLY: c = 'P'; break;
392          }
393          goto jputcb;
394       case 'l':
395          if (n == 0)
396             n = 4;
397          if (UCMP(32, ABS(n), >, wleft))
398             wleft = 0;
399          else if (mp->m_xlines) {
400             n = fprintf(f, "%*ld", n, mp->m_xlines);
401             wleft = (n >= 0) ? wleft - n : 0;
402          } else {
403             n = ABS(n);
404             wleft -= n;
405             while (n-- != 0)
406                putc(' ', f);
407          }
408          break;
409       case 'm':
410          if (n == 0) {
411             n = 3;
412             if (threaded)
413                for (i = msgCount; i > 999; i /= 10)
414                   ++n;
415          }
416          if (UCMP(32, ABS(n), >, wleft))
417             wleft = 0;
418          else{
419             n = fprintf(f, "%*lu", n, (ul)msgno);
420             wleft = (n >= 0) ? wleft - n : 0;
421          }
422          break;
423       case 'o':
424          if (n == 0)
425             n = -5;
426          if (UCMP(32, ABS(n), >, wleft))
427             wleft = 0;
428          else{
429             n = fprintf(f, "%*lu", n, (ul)mp->m_xsize);
430             wleft = (n >= 0) ? wleft - n : 0;
431          }
432          break;
433       case 'S':
434          flags |= _SFMT;
435          /*FALLTHRU*/
436       case 's':
437          if (n == 0)
438             n = subjlen - 2;
439          if (n > 0 && s < 0)
440             n = -n;
441          if (subjlen > wleft)
442             subjlen = wleft;
443          if (UCMP(32, ABS(n), >, subjlen))
444             n = (n < 0) ? -subjlen : subjlen;
445          if (flags & _SFMT)
446             n -= (n < 0) ? -2 : 2;
447          if (n == 0)
448             break;
449          if (subjline == NULL)
450             subjline = a_chead__subject(mp, (threaded && (flags & _IFMT)),
451                   subject_thread_compress, yetprinted);
452          if (subjline == (char*)-1) {
453             n = fprintf(f, "%*s", n, n_empty);
454             wleft = (n >= 0) ? wleft - n : 0;
455          } else {
456             n = fprintf(f, ((flags & _SFMT) ? "\"%s\"" : "%s"),
457                   colalign(subjline, ABS(n), n, &wleft));
458             if (n < 0)
459                wleft = 0;
460          }
461          break;
462       case 'T':
463          n_OBSOLETE("*headline*: please use %L not %T for mailing-list "
464             "status");
465          goto jmlist;
466       case 't':
467          if (n == 0) {
468             n = 3;
469             if (threaded)
470                for (i = msgCount; i > 999; i /= 10)
471                   ++n;
472          }
473          if (UCMP(32, ABS(n), >, wleft))
474             wleft = 0;
475          else{
476             n = fprintf(f, "%*lu",
477                   n, (threaded ? (ul)mp->m_threadpos : (ul)msgno));
478             wleft = (n >= 0) ? wleft - n : 0;
479          }
480          break;
481       case 'U':
482 #ifdef mx_HAVE_IMAP
483             if (n == 0)
484                n = 9;
485             if (UCMP(32, ABS(n), >, wleft))
486                wleft = 0;
487             else{
488                n = fprintf(f, "%*" PRIu64 , n, mp->m_uid);
489                wleft = (n >= 0) ? wleft - n : 0;
490             }
491             break;
492 #else
493             c = '0';
494             goto jputcb;
495 #endif
496       default:
497          if (n_poption & n_PO_D_V)
498             n_err(_("Unknown *headline* format: %%%c\n"), c);
499          c = '?';
500          goto jputcb;
501       }
502 
503       if (wleft <= 0)
504          break;
505    }
506 
507    mx_COLOUR( mx_colour_reset(); )
508    putc('\n', f);
509 
510    if (subjline != NULL && subjline != (char*)-1)
511       n_free(subjline);
512    NYD2_OU;
513 }
514 
515 static char *
a_chead__subject(struct message * mp,boole threaded,boole subject_thread_compress,uz yetprinted)516 a_chead__subject(struct message *mp, boole threaded,
517    boole subject_thread_compress, uz yetprinted)
518 {
519    struct str in, out;
520    char *rv, *ms;
521    NYD2_IN;
522 
523    rv = (char*)-1;
524 
525    if ((ms = hfield1("subject", mp)) == NULL)
526       goto jleave;
527 
528    in.l = su_cs_len(in.s = ms);
529    mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
530    rv = ms = out.s;
531 
532    if (!threaded || !subject_thread_compress || mp->m_level == 0)
533       goto jleave;
534 
535    /* In a display thread - check whether this message uses the same
536     * Subject: as it's parent or elder neighbour, suppress printing it if
537     * this is the case.  To extend this a bit, ignore any leading Re: or
538     * Fwd: plus follow-up WS.  Ignore invisible messages along the way */
539    ms = n_UNCONST(subject_re_trim(n_UNCONST(ms)));
540 
541    for (; (mp = prev_in_thread(mp)) != NULL && yetprinted-- > 0;) {
542       char *os;
543 
544       if (visible(mp) && (os = hfield1("subject", mp)) != NULL) {
545          struct str oout;
546          int x;
547 
548          in.l = su_cs_len(in.s = os);
549          mime_fromhdr(&in, &oout, TD_ICONV | TD_ISPR);
550          x = su_cs_cmp_case(ms, subject_re_trim(oout.s));
551          n_free(oout.s);
552 
553          if (!x) {
554             n_free(out.s);
555             rv = (char*)-1;
556          }
557          break;
558       }
559    }
560 jleave:
561    NYD2_OU;
562    return rv;
563 }
564 
565 static int
a_chead__putindent(FILE * fp,struct message * mp,int maxwidth)566 a_chead__putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX magics */
567 {
568    struct message *mq;
569    int *unis, indlvl, indw, i, important = MNEW | MFLAGGED;
570    char *cs;
571    NYD2_IN;
572 
573    if (mp->m_level == 0 || maxwidth == 0) {
574       indw = 0;
575       goto jleave;
576    }
577 
578    cs = n_lofi_alloc(mp->m_level);
579    unis = n_lofi_alloc(mp->m_level * sizeof *unis);
580 
581    i = mp->m_level - 1;
582    if (mp->m_younger && UCMP(32, i + 1, ==, mp->m_younger->m_level)) {
583       if (mp->m_parent && mp->m_parent->m_flag & important)
584          unis[i] = mp->m_flag & important ? 0x2523 : 0x2520;
585       else
586          unis[i] = mp->m_flag & important ? 0x251D : 0x251C;
587       cs[i] = '+';
588    } else {
589       if (mp->m_parent && mp->m_parent->m_flag & important)
590          unis[i] = mp->m_flag & important ? 0x2517 : 0x2516;
591       else
592          unis[i] = mp->m_flag & important ? 0x2515 : 0x2514;
593       cs[i] = '\\';
594    }
595 
596    mq = mp->m_parent;
597    for (i = mp->m_level - 2; i >= 0; --i) {
598       if (mq) {
599          if (UCMP(32, i, >, mq->m_level - 1)) {
600             unis[i] = cs[i] = ' ';
601             continue;
602          }
603          if (mq->m_younger) {
604             if (mq->m_parent && (mq->m_parent->m_flag & important))
605                unis[i] = 0x2503;
606             else
607                unis[i] = 0x2502;
608             cs[i] = '|';
609          } else
610             unis[i] = cs[i] = ' ';
611          mq = mq->m_parent;
612       } else
613          unis[i] = cs[i] = ' ';
614    }
615 
616    --maxwidth;
617    for (indlvl = indw = 0; (u8)indlvl < mp->m_level && indw < maxwidth;
618          ++indlvl) {
619       if (indw < maxwidth - 1)
620          indw += (int)a_chead__putuc(unis[indlvl], cs[indlvl] & 0xFF, fp);
621       else
622          indw += (int)a_chead__putuc(0x21B8, '^', fp);
623    }
624    indw += a_chead__putuc(0x25B8, '>', fp);
625 
626    n_lofi_free(unis);
627    n_lofi_free(cs);
628 jleave:
629    NYD2_OU;
630    return indw;
631 }
632 
633 static uz
a_chead__putuc(int u,int c,FILE * fp)634 a_chead__putuc(int u, int c, FILE *fp){
635    uz rv;
636    NYD2_IN;
637    UNUSED(u);
638 
639 #ifdef mx_HAVE_NATCH_CHAR
640    if((n_psonce & n_PSO_UNICODE) && (u & ~(wchar_t)0177) &&
641          !ok_blook(headline_plain)){
642       char mbb[MB_LEN_MAX];
643       int i, n;
644 
645       if((n = wctomb(mbb, u)) > 0){
646          rv = wcwidth(u);
647          for(i = 0; i < n; ++i)
648             if(putc(mbb[i] & 0377, fp) == EOF){
649                rv = 0;
650                break;
651             }
652       }else if(n == 0)
653          rv = (putc('\0', fp) != EOF);
654       else
655          rv = 0;
656    }else
657 #endif
658       rv = (putc(c, fp) != EOF);
659    NYD2_OU;
660    return rv;
661 }
662 
663 static int
a_chead__dispc(struct message * mp,char const * a)664 a_chead__dispc(struct message *mp, char const *a)
665 {
666    int i = ' ';
667    NYD2_IN;
668 
669    if ((mp->m_flag & (MREAD | MNEW)) == MREAD)
670       i = a[3];
671    if ((mp->m_flag & (MREAD | MNEW)) == (MREAD | MNEW))
672       i = a[2];
673    if (mp->m_flag & MANSWERED)
674       i = a[8];
675    if (mp->m_flag & MDRAFTED)
676       i = a[9];
677    if ((mp->m_flag & (MREAD | MNEW)) == MNEW)
678       i = a[0];
679    if (!(mp->m_flag & (MREAD | MNEW)))
680       i = a[1];
681    if (mp->m_flag & MSPAM)
682       i = a[12];
683    if (mp->m_flag & MSPAMUNSURE)
684       i = a[13];
685    if (mp->m_flag & MSAVED)
686       i = a[4];
687    if (mp->m_flag & MPRESERVE)
688       i = a[5];
689    if (mp->m_flag & (MBOX | MBOXED))
690       i = a[6];
691    if (mp->m_flag & MFLAGGED)
692       i = a[7];
693    if (mb.mb_threaded == 1) { /* TODO bad, and m_collapsed is weird */
694       /* TODO So this does not work because of weird thread handling and
695        * TODO intermixing view and controller except when run via -L from
696        * TODO command line; in general these flags should go and we need
697        * TODO specific *headline* formats which always work and indicate
698        * TODO whether a message is in a thread, the head of a subthread etc. */
699       if (mp->m_collapsed > 0)
700          i = a[11];
701       else if (mp->m_collapsed < 0)
702          i = a[10];
703    }
704    NYD2_OU;
705    return i;
706 }
707 
708 static int
a_chead_scroll(char const * arg,boole onlynew)709 a_chead_scroll(char const *arg, boole onlynew){
710    sz l;
711    boole isabs;
712    int msgspec, size, maxs;
713    NYD2_IN;
714 
715    /* TODO scroll problem: we do not know whether + and $ have already reached
716     * TODO the last screen in threaded mode */
717    msgspec = onlynew ? -1 : 0;
718    size = (int)/*TODO*/n_screensize();
719    if((maxs = msgCount / size) > 0 && msgCount % size == 0)
720       --maxs;
721 
722    if(arg == NULL)
723       arg = n_empty;
724    switch(*arg){
725    case '\0':
726       ++_screen;
727       goto jfwd;
728    case '^':
729       if(arg[1] != '\0')
730          goto jerr;
731       if(_screen == 0)
732          goto jerrbwd;
733       _screen = 0;
734       break;
735    case '$':
736       if(arg[1] != '\0')
737          goto jerr;
738       if(_screen == maxs)
739          goto jerrfwd;
740       _screen = maxs;
741       break;
742    case '+':
743       if(arg[1] == '\0')
744          ++_screen;
745       else{
746          isabs = FAL0;
747 
748          ++arg;
749          if(0){
750    case '1': case '2': case '3': case '4': case '5':
751    case '6': case '7': case '8': case '9': case '0':
752             isabs = TRU1;
753          }
754          if((su_idec_sz_cp(&l, arg, 0, NULL
755                   ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
756                ) != su_IDEC_STATE_CONSUMED)
757             goto jerr;
758          if(l > maxs - (isabs ? 0 : _screen))
759             goto jerrfwd;
760          _screen = isabs ? (int)l : _screen + l;
761       }
762 jfwd:
763       if(_screen > maxs){
764 jerrfwd:
765          _screen = maxs;
766          fprintf(n_stdout, _("On last screenful of messages\n"));
767       }
768       break;
769 
770    case '-':
771       if(arg[1] == '\0')
772          --_screen;
773       else{
774          if((su_idec_sz_cp(&l, ++arg, 0, NULL
775                   ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
776                ) != su_IDEC_STATE_CONSUMED)
777             goto jerr;
778          if(l > _screen)
779             goto jerrbwd;
780          _screen -= l;
781       }
782       if(_screen < 0){
783 jerrbwd:
784          _screen = 0;
785          fprintf(n_stdout, _("On first screenful of messages\n"));
786       }
787       if(msgspec == -1)
788          msgspec = -2;
789       break;
790    default:
791 jerr:
792       n_err(_("Unrecognized scrolling command: %s\n"), arg);
793       size = 1;
794       goto jleave;
795    }
796 
797    size = _headers(msgspec);
798 jleave:
799    NYD2_OU;
800    return size;
801 }
802 
803 static int
_headers(int msgspec)804 _headers(int msgspec) /* TODO rework v15 */
805 {
806    boole needdot, showlast;
807    int g, k, mesg, size;
808    struct message *lastmq, *mp, *mq;
809    int volatile lastg;
810    u32 volatile flag;
811    enum mflag fl;
812    NYD_IN;
813 
814    time_current_update(&time_current, FAL0);
815 
816    fl = MNEW | MFLAGGED;
817    flag = 0;
818    lastg = 1;
819    lastmq = NULL;
820 
821    size = (int)/*TODO*/n_screensize();
822    if (_screen < 0)
823       _screen = 0;
824 #if 0 /* FIXME original code path */
825       k = _screen * size;
826 #else
827    if (msgspec <= 0)
828       k = _screen * size;
829    else
830       k = msgspec;
831 #endif
832    if (k >= msgCount)
833       k = msgCount - size;
834    if (k < 0)
835       k = 0;
836 
837    needdot = (msgspec <= 0) ? TRU1 : (dot != &message[msgspec - 1]);
838    showlast = ok_blook(showlast);
839 
840    if (mb.mb_threaded == 0) {
841       g = 0;
842       mq = message;
843       for (mp = message; PCMP(mp, <, message + msgCount); ++mp)
844          if (visible(mp)) {
845             if (g % size == 0)
846                mq = mp;
847             if (mp->m_flag & fl) {
848                lastg = g;
849                lastmq = mq;
850             }
851             if ((msgspec > 0 && PCMP(mp, ==, message + msgspec - 1)) ||
852                   (msgspec == 0 && g == k) ||
853                   (msgspec == -2 && g == k + size && lastmq) ||
854                   (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
855                break;
856             g++;
857          }
858       if (lastmq && (msgspec == -2 ||
859             (msgspec == -1 && PCMP(mp, ==, message + msgCount)))) {
860          g = lastg;
861          mq = lastmq;
862       }
863       _screen = g / size;
864       mp = mq;
865 
866       mesg = (int)P2UZ(mp - message);
867 #ifdef mx_HAVE_IMAP
868       if (mb.mb_type == MB_IMAP)
869          imap_getheaders(mesg + 1, mesg + size);
870 #endif
871       mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); )
872       n_autorec_relax_create();
873       for(lastmq = NULL, mq = &message[msgCount]; mp < mq; lastmq = mp, ++mp){
874          ++mesg;
875          if (!visible(mp))
876             continue;
877          if (UCMP(32, flag, >=, size))
878             break;
879          if(needdot){
880             if(showlast){
881                if(UCMP(32, flag, ==, size - 1) || &mp[1] == mq)
882                   goto jdot_unsort;
883             }else if(flag == 0){
884 jdot_unsort:
885                needdot = FAL0;
886                setdot(mp);
887             }
888          }
889          ++flag;
890          a_chead_print_head(0, mesg, n_stdout, FAL0, FAL0);
891          n_autorec_relax_unroll();
892       }
893       if(needdot && ok_blook(showlast)) /* xxx will not show */
894          setdot(lastmq);
895       n_autorec_relax_gut();
896       mx_COLOUR( mx_colour_env_gut(); )
897    } else { /* threaded */
898       g = 0;
899       mq = threadroot;
900       for (mp = threadroot; mp; mp = next_in_thread(mp)){
901          /* TODO thread handling needs rewrite, m_collapsed must go */
902          if (visible(mp) &&
903                (mp->m_collapsed <= 0 ||
904                 PCMP(mp, ==, message + msgspec - 1))) {
905             if (g % size == 0)
906                mq = mp;
907             if (mp->m_flag & fl) {
908                lastg = g;
909                lastmq = mq;
910             }
911             if ((msgspec > 0 && PCMP(mp, ==, message + msgspec - 1)) ||
912                   (msgspec == 0 && g == k) ||
913                   (msgspec == -2 && g == k + size && lastmq) ||
914                   (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0))
915                break;
916             g++;
917          }
918       }
919       if (lastmq && (msgspec == -2 ||
920             (msgspec == -1 && PCMP(mp, ==, message + msgCount)))) {
921          g = lastg;
922          mq = lastmq;
923       }
924       _screen = g / size;
925       mp = mq;
926 
927       mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); )
928       n_autorec_relax_create();
929       for(lastmq = NULL; mp != NULL; lastmq = mp, mp = mq){
930          mq = next_in_thread(mp);
931          if (visible(mp) &&
932                (mp->m_collapsed <= 0 ||
933                 PCMP(mp, ==, message + msgspec - 1))) {
934             if (UCMP(32, flag, >=, size))
935                break;
936             if(needdot){
937                if(showlast){
938                   if(UCMP(32, flag, ==, size - 1) || mq == NULL)
939                      goto jdot_sort;
940                }else if(flag == 0){
941 jdot_sort:
942                   needdot = FAL0;
943                   setdot(mp);
944                }
945             }
946             a_chead_print_head(flag, P2UZ(mp - message + 1), n_stdout,
947                mb.mb_threaded, TRU1);
948             ++flag;
949             n_autorec_relax_unroll();
950          }
951       }
952       if(needdot && ok_blook(showlast)) /* xxx will not show */
953          setdot(lastmq);
954       n_autorec_relax_gut();
955       mx_COLOUR( mx_colour_env_gut(); )
956    }
957 
958    if (flag == 0) {
959       fprintf(n_stdout, _("No more mail.\n"));
960       if (n_pstate & (n_PS_ROBOT | n_PS_HOOK_MASK))
961          flag = !flag;
962    }
963    NYD_OU;
964    return !flag;
965 }
966 
967 FL int
c_headers(void * v)968 c_headers(void *v)
969 {
970    int rv;
971    NYD_IN;
972 
973    rv = print_header_group((int*)v);
974    NYD_OU;
975    return rv;
976 }
977 
978 FL int
print_header_group(int * vector)979 print_header_group(int *vector)
980 {
981    int rv;
982    NYD_IN;
983 
984    ASSERT(vector != NULL && vector != (void*)-1);
985    rv = _headers(vector[0]);
986    NYD_OU;
987    return rv;
988 }
989 
990 FL int
c_scroll(void * v)991 c_scroll(void *v)
992 {
993    int rv;
994    NYD_IN;
995 
996    rv = a_chead_scroll(*(char const**)v, FAL0);
997    NYD_OU;
998    return rv;
999 }
1000 
1001 FL int
c_Scroll(void * v)1002 c_Scroll(void *v)
1003 {
1004    int rv;
1005    NYD_IN;
1006 
1007    rv = a_chead_scroll(*(char const**)v, TRU1);
1008    NYD_OU;
1009    return rv;
1010 }
1011 
1012 FL int
c_dotmove(void * v)1013 c_dotmove(void *v)
1014 {
1015    char const *args;
1016    int msgvec[2], rv;
1017    NYD_IN;
1018 
1019    if (*(args = v) == '\0' || args[1] != '\0') {
1020 jerr:
1021       mx_cmd_print_synopsis(mx_cmd_firstfit("dotmove"), NIL);
1022       rv = 1;
1023    } else switch (args[0]) {
1024    case '-':
1025    case '+':
1026       if (msgCount == 0) {
1027          fprintf(n_stdout, _("At EOF\n"));
1028          rv = 0;
1029       } else if (n_getmsglist(n_UNCONST(/*TODO*/args), msgvec, 0, NULL) > 0) {
1030          setdot(message + msgvec[0] - 1);
1031          msgvec[1] = 0;
1032          rv = c_headers(msgvec);
1033       } else
1034          rv = 1;
1035       break;
1036    default:
1037       goto jerr;
1038    }
1039    NYD_OU;
1040    return rv;
1041 }
1042 
1043 FL int
c_from(void * vp)1044 c_from(void *vp)
1045 {
1046    int *msgvec, *ip, n;
1047    char *cp;
1048    FILE * volatile obuf;
1049    NYD_IN;
1050 
1051    if(*(msgvec = vp) == 0)
1052       goto jleave;
1053 
1054    time_current_update(&time_current, FAL0);
1055 
1056    obuf = n_stdout;
1057 
1058    if (n_psonce & n_PSO_INTERACTIVE) {
1059       if ((cp = ok_vlook(crt)) != NULL) {
1060          uz ib;
1061 
1062          for (n = 0, ip = msgvec; *ip != 0; ++ip)
1063             ++n;
1064 
1065          if(*cp == '\0')
1066             ib = n_screensize();
1067          else
1068             su_idec_uz_cp(&ib, cp, 0, NULL);
1069          if (UCMP(z, n, >, ib) && (obuf = mx_pager_open()) == NULL)
1070             obuf = n_stdout;
1071       }
1072    }
1073 
1074    /* Update dot before display so that the dotmark etc. are correct */
1075    for (ip = msgvec; ip[1] != 0; ++ip)
1076       ;
1077    setdot(&message[(ok_blook(showlast) ? *ip : *msgvec) - 1]);
1078 
1079    mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, obuf,
1080       (obuf != n_stdout)); )
1081    n_autorec_relax_create();
1082    for(n = 0, ip = msgvec; *ip != 0; ++ip){ /* TODO join into _print_head() */
1083       a_chead_print_head((uz)n++, S(uz,*ip), obuf, mb.mb_threaded, FAL0);
1084       n_autorec_relax_unroll();
1085    }
1086    n_autorec_relax_gut();
1087    mx_COLOUR( mx_colour_env_gut(); )
1088 
1089    if(obuf != n_stdout)
1090       mx_pager_close(obuf);
1091    else
1092       clearerr(obuf);
1093 
1094 jleave:
1095    NYD_OU;
1096    return 0;
1097 }
1098 
1099 FL void
print_headers(int const * msgvec,boole only_marked,boole subject_thread_compress)1100 print_headers(int const *msgvec, boole only_marked,
1101    boole subject_thread_compress)
1102 {
1103    uz printed;
1104    NYD_IN;
1105 
1106    time_current_update(&time_current, FAL0);
1107 
1108    mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); )
1109    n_autorec_relax_create();
1110    for(printed = 0; *msgvec != 0; ++msgvec) {
1111       struct message *mp = message + *msgvec - 1;
1112       if (only_marked) {
1113          if (!(mp->m_flag & MMARK))
1114             continue;
1115       } else if (!visible(mp))
1116          continue;
1117       a_chead_print_head(printed++, *msgvec, n_stdout, mb.mb_threaded,
1118          subject_thread_compress);
1119       n_autorec_relax_unroll();
1120    }
1121    n_autorec_relax_gut();
1122    mx_COLOUR( mx_colour_env_gut(); )
1123    NYD_OU;
1124 }
1125 
1126 #include "su/code-ou.h"
1127 /* s-it-mode */
1128