yes, we always have colors, screw stupid antiquated ncurses.
[apps/madmutt.git] / pager.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * Parts were written/modified by:
6  * Nico Golde <nico@ngolde.de>
7  * Andreas Krennmair <ak@synflood.at>
8  *
9  * This file is part of mutt-ng, see http://www.muttng.org/.
10  * It's licensed under the GNU General Public License,
11  * please see the file GPL in the top level source directory.
12  */
13
14 #include <lib-lib/lib-lib.h>
15 #include <lib-crypt/crypt.h>
16
17 #include <lib-ui/curses.h>
18 #include <lib-ui/enter.h>
19 #include <lib-ui/menu.h>
20 #include <lib-ui/sidebar.h>
21 #include <lib-mx/mx.h>
22
23 #include "mutt.h"
24 #include "alias.h"
25 #include "keymap.h"
26 #include "sort.h"
27 #include "pager.h"
28 #include "attach.h"
29 #include "recvattach.h"
30 #include "charset.h"
31 #include "buffy.h"
32
33 #include <imap/imap_private.h>
34
35 #define ISHEADER(x) ((x) == MT_COLOR_HEADER || (x) == MT_COLOR_HDEFAULT)
36
37 #define IsAttach(x)      (x && (x)->bdy)
38 #define IsRecvAttach(x)  (x && (x)->bdy && (x)->fp)
39 #define IsSendAttach(x)  (x && (x)->bdy && !(x)->fp)
40 #define IsMsgAttach(x)   (x && (x)->fp && (x)->bdy && (x)->bdy->hdr)
41 #define IsHeader(x)      (x && (x)->hdr && !(x)->bdy)
42 #define SW               (option(OPTMBOXPANE)?SidebarWidth:0)
43
44 /* hack to return to position when returning from index to same message */
45 static int TopLine = 0;
46 static HEADER *OldHdr = NULL;
47
48 #define CHECK_MODE(x) \
49     if (!(x)) {                                         \
50         mutt_flushinp();                                \
51         mutt_error _("Not available in this menu.");    \
52         break;                                          \
53     }
54
55 #define CHECK_READONLY \
56     if (Context->readonly) {                            \
57         mutt_flushinp();                                \
58         mutt_error _("Mailbox is read-only.");          \
59         break;                                          \
60     }
61
62 #define CHECK_ATTACH \
63     if (option(OPTATTACHMSG)) {                         \
64         mutt_flushinp();                                \
65         mutt_error _("Function not permitted in attach-message mode."); \
66         break;                                          \
67     }
68
69 struct q_class_t {
70   int length;
71   int index;
72   int color;
73   char *prefix;
74   struct q_class_t *next, *prev;
75   struct q_class_t *down, *up;
76 };
77
78 struct syntax_t {
79   int color;
80   int first;
81   int last;
82 };
83
84 struct line_t {
85   off_t offset;
86   short type;
87   short continuation;
88   short chunks;
89   short search_cnt;
90   struct syntax_t *syntax;
91   struct syntax_t *search;
92   struct q_class_t *quote;
93 };
94
95 #define ANSI_OFF       (1<<0)
96 #define ANSI_BLINK     (1<<1)
97 #define ANSI_BOLD      (1<<2)
98 #define ANSI_UNDERLINE (1<<3)
99 #define ANSI_REVERSE   (1<<4)
100 #define ANSI_COLOR     (1<<5)
101
102 typedef struct _ansi_attr {
103   int attr;
104   int fg;
105   int bg;
106   int pair;
107 } ansi_attr;
108
109 static short InHelp = 0;
110
111 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
112 static struct resize {
113   int line;
114   int SearchCompiled;
115   int SearchBack;
116 } *Resize = NULL;
117 #endif
118
119 #define NumSigLines 4
120
121 static int check_sig(const char *s, struct line_t *info, int n)
122 {
123     int count = 0;
124
125     while (n > 0 && count <= NumSigLines) {
126         if (info[n].type != MT_COLOR_SIGNATURE)
127             break;
128         count++;
129         n--;
130     }
131
132     if (count == 0)
133         return -1;
134
135     if (count > NumSigLines) {
136         /* check for a blank line */
137         s = skipspaces(s);
138         return *s ? -1 : 0;
139     }
140
141     return 0;
142 }
143
144 static void
145 resolve_color (struct line_t *lineInfo, int n, int cnt, int flags,
146                int special, ansi_attr * a)
147 {
148   int def_color;                /* color without syntax hilight */
149   int color;                    /* final color */
150   static int last_color;        /* last color set */
151   int search = 0, i, m;
152
153   if (!cnt)
154     last_color = -1;            /* force attrset() */
155
156   if (lineInfo[n].continuation) {
157     if (!cnt && option (OPTMARKERS)) {
158       SETCOLOR (MT_COLOR_MARKERS);
159       addch ('+');
160       last_color = ColorDefs[MT_COLOR_MARKERS];
161     }
162     m = (lineInfo[n].syntax)[0].first;
163     cnt += (lineInfo[n].syntax)[0].last;
164   } else {
165     m = n;
166   }
167
168   if (!(flags & M_SHOWCOLOR))
169     def_color = ColorDefs[MT_COLOR_NORMAL];
170   else if (lineInfo[m].type == MT_COLOR_HEADER)
171     def_color = (lineInfo[m].syntax)[0].color;
172   else
173     def_color = ColorDefs[lineInfo[m].type];
174
175   if ((flags & M_SHOWCOLOR) && lineInfo[m].type == MT_COLOR_QUOTED) {
176     struct q_class_t *class = lineInfo[m].quote;
177
178     if (class) {
179       def_color = class->color;
180
181       while (class && class->length > cnt) {
182         def_color = class->color;
183         class = class->up;
184       }
185     }
186   }
187
188   color = def_color;
189   if (flags & M_SHOWCOLOR) {
190     for (i = 0; i < lineInfo[m].chunks; i++) {
191       /* we assume the chunks are sorted */
192       if (cnt > (lineInfo[m].syntax)[i].last)
193         continue;
194       if (cnt < (lineInfo[m].syntax)[i].first)
195         break;
196       if (cnt != (lineInfo[m].syntax)[i].last) {
197         color = (lineInfo[m].syntax)[i].color;
198         break;
199       }
200       /* don't break here, as cnt might be in the next chunk as well */
201     }
202   }
203
204   if (flags & M_SEARCH) {
205     for (i = 0; i < lineInfo[m].search_cnt; i++) {
206       if (cnt > (lineInfo[m].search)[i].last)
207         continue;
208       if (cnt < (lineInfo[m].search)[i].first)
209         break;
210       if (cnt != (lineInfo[m].search)[i].last) {
211         color = ColorDefs[MT_COLOR_SEARCH];
212         search = 1;
213         break;
214       }
215     }
216   }
217
218   /* handle "special" bold & underlined characters */
219   if (special || a->attr) {
220     if ((a->attr & ANSI_COLOR)) {
221       if (a->pair == -1)
222         a->pair = mutt_alloc_color (a->fg, a->bg);
223       color = a->pair;
224       if (a->attr & ANSI_BOLD)
225         color |= A_BOLD;
226     } else
227     if ((special & A_BOLD) || (a->attr & ANSI_BOLD)) {
228       if (ColorDefs[MT_COLOR_BOLD] && !search)
229         color = ColorDefs[MT_COLOR_BOLD];
230       else
231         color ^= A_BOLD;
232     }
233     if ((special & A_UNDERLINE) || (a->attr & ANSI_UNDERLINE)) {
234       if (ColorDefs[MT_COLOR_UNDERLINE] && !search)
235         color = ColorDefs[MT_COLOR_UNDERLINE];
236       else
237         color ^= A_UNDERLINE;
238     }
239     else if (a->attr & ANSI_REVERSE) {
240       color ^= A_REVERSE;
241     }
242     else if (a->attr & ANSI_BLINK) {
243       color ^= A_BLINK;
244     }
245     else if (a->attr & ANSI_OFF) {
246       a->attr = 0;
247     }
248   }
249
250   if (color != last_color) {
251     attrset (color);
252     last_color = color;
253   }
254 }
255
256 static void append_line (struct line_t *lineInfo, int n, int cnt)
257 {
258   int m;
259
260   lineInfo[n + 1].type = lineInfo[n].type;
261   lineInfo[n + 1].syntax[0].color = lineInfo[n].syntax[0].color;
262   lineInfo[n + 1].continuation = 1;
263
264   /* find the real start of the line */
265   for (m = n; m >= 0; m--)
266     if (lineInfo[m].continuation == 0)
267       break;
268
269   (lineInfo[n + 1].syntax)[0].first = m;
270   (lineInfo[n + 1].syntax)[0].last = (lineInfo[n].continuation) ?
271     cnt + (lineInfo[n].syntax)[0].last : cnt;
272 }
273
274 static void new_class_color (struct q_class_t *class, int *q_level)
275 {
276   class->index = (*q_level)++;
277   class->color = ColorQuote[class->index % ColorQuoteUsed];
278 }
279
280 static void
281 shift_class_colors (struct q_class_t *QuoteList, struct q_class_t *new_class,
282                     int lindex, int *q_level)
283 {
284   struct q_class_t *q_list;
285
286   q_list = QuoteList;
287   new_class->index = -1;
288
289   while (q_list) {
290     if (q_list->index >= lindex) {
291       q_list->index++;
292       q_list->color = ColorQuote[q_list->index % ColorQuoteUsed];
293     }
294     if (q_list->down)
295       q_list = q_list->down;
296     else if (q_list->next)
297       q_list = q_list->next;
298     else {
299       while (!q_list->next) {
300         q_list = q_list->up;
301         if (q_list == NULL)
302           break;
303       }
304       if (q_list)
305         q_list = q_list->next;
306     }
307   }
308
309   new_class->index = lindex;
310   new_class->color = ColorQuote[lindex % ColorQuoteUsed];
311   (*q_level)++;
312 }
313
314 static void cleanup_quote (struct q_class_t **QuoteList)
315 {
316   struct q_class_t *ptr;
317
318   while (*QuoteList) {
319     if ((*QuoteList)->down)
320       cleanup_quote (&((*QuoteList)->down));
321     ptr = (*QuoteList)->next;
322     if ((*QuoteList)->prefix)
323       p_delete(&(*QuoteList)->prefix);
324     p_delete(QuoteList);
325     *QuoteList = ptr;
326   }
327
328   return;
329 }
330
331 static struct q_class_t *classify_quote (struct q_class_t **QuoteList,
332                                          const char *qptr, int length,
333                                          int *force_redraw, int *q_level)
334 {
335   struct q_class_t *q_list = *QuoteList;
336   struct q_class_t *class = NULL, *tmp = NULL, *ptr, *save;
337   char *tail_qptr;
338   int offset, tail_lng;
339   int lindex = -1;
340
341   if (ColorQuoteUsed <= 1) {
342     /* not much point in classifying quotes... */
343
344     if (*QuoteList == NULL) {
345       class = p_new(struct q_class_t, 1);
346       class->color = ColorQuote[0];
347       *QuoteList = class;
348     }
349     return (*QuoteList);
350   }
351
352   /* Did I mention how much I like emulating Lisp in C? */
353
354   /* classify quoting prefix */
355   while (q_list) {
356     if (length <= q_list->length) {
357       /* case 1: check the top level nodes */
358
359       if (m_strncmp(qptr, q_list->prefix, length) == 0) {
360         if (length == q_list->length)
361           return q_list;        /* same prefix: return the current class */
362
363         /* found shorter prefix */
364         if (tmp == NULL) {
365           /* add a node above q_list */
366           tmp = p_new(struct q_class_t, 1);
367           tmp->prefix = p_dupstr(qptr, length);
368           tmp->length = length;
369
370           /* replace q_list by tmp in the top level list */
371           if (q_list->next) {
372             tmp->next = q_list->next;
373             q_list->next->prev = tmp;
374           }
375           if (q_list->prev) {
376             tmp->prev = q_list->prev;
377             q_list->prev->next = tmp;
378           }
379
380           /* make q_list a child of tmp */
381           tmp->down = q_list;
382           q_list->up = tmp;
383
384           /* q_list has no siblings for now */
385           q_list->next = NULL;
386           q_list->prev = NULL;
387
388           /* update the root if necessary */
389           if (q_list == *QuoteList)
390             *QuoteList = tmp;
391
392           lindex = q_list->index;
393
394           /* tmp should be the return class too */
395           class = tmp;
396
397           /* next class to test; if tmp is a shorter prefix for another
398            * node, that node can only be in the top level list, so don't
399            * go down after this point
400            */
401           q_list = tmp->next;
402         } else {
403           /* found another branch for which tmp is a shorter prefix */
404
405           /* save the next sibling for later */
406           save = q_list->next;
407
408           /* unlink q_list from the top level list */
409           if (q_list->next)
410             q_list->next->prev = q_list->prev;
411           if (q_list->prev)
412             q_list->prev->next = q_list->next;
413
414           /* at this point, we have a tmp->down; link q_list to it */
415           ptr = tmp->down;
416           /* sibling order is important here, q_list should be linked last */
417           while (ptr->next)
418             ptr = ptr->next;
419           ptr->next = q_list;
420           q_list->next = NULL;
421           q_list->prev = ptr;
422           q_list->up = tmp;
423
424           lindex = q_list->index;
425
426           /* next class to test; as above, we shouldn't go down */
427           q_list = save;
428         }
429
430         /* we found a shorter prefix, so certain quotes have changed classes */
431         *force_redraw = 1;
432         continue;
433       } else {
434         /* shorter, but not a substring of the current class: try next */
435         q_list = q_list->next;
436         continue;
437       }
438     } else {
439       /* case 2: try subclassing the current top level node */
440
441       /* tmp != NULL means we already found a shorter prefix at case 1 */
442       if (tmp == NULL
443           && m_strncmp(qptr, q_list->prefix, q_list->length) == 0) {
444         /* ok, it's a subclass somewhere on this branch */
445
446         ptr = q_list;
447         offset = q_list->length;
448
449         q_list = q_list->down;
450         tail_lng = length - offset;
451         tail_qptr = (char *) qptr + offset;
452
453         while (q_list) {
454           if (length <= q_list->length) {
455             if (m_strncmp(tail_qptr, (q_list->prefix) + offset, tail_lng)
456                 == 0) {
457               /* same prefix: return the current class */
458               if (length == q_list->length)
459                 return q_list;
460
461               /* found shorter common prefix */
462               if (tmp == NULL) {
463                 /* add a node above q_list */
464                 tmp = p_new(struct q_class_t, 1);
465                 tmp->prefix = p_dupstr(qptr, length);
466                 tmp->length = length;
467
468                 /* replace q_list by tmp */
469                 if (q_list->next) {
470                   tmp->next = q_list->next;
471                   q_list->next->prev = tmp;
472                 }
473                 if (q_list->prev) {
474                   tmp->prev = q_list->prev;
475                   q_list->prev->next = tmp;
476                 }
477
478                 /* make q_list a child of tmp */
479                 tmp->down = q_list;
480                 tmp->up = q_list->up;
481                 q_list->up = tmp;
482                 if (tmp->up->down == q_list)
483                   tmp->up->down = tmp;
484
485                 /* q_list has no siblings */
486                 q_list->next = NULL;
487                 q_list->prev = NULL;
488
489                 lindex = q_list->index;
490
491                 /* tmp should be the return class too */
492                 class = tmp;
493
494                 /* next class to test */
495                 q_list = tmp->next;
496               } else {
497                 /* found another branch for which tmp is a shorter prefix */
498
499                 /* save the next sibling for later */
500                 save = q_list->next;
501
502                 /* unlink q_list from the top level list */
503                 if (q_list->next)
504                   q_list->next->prev = q_list->prev;
505                 if (q_list->prev)
506                   q_list->prev->next = q_list->next;
507
508                 /* at this point, we have a tmp->down; link q_list to it */
509                 ptr = tmp->down;
510                 while (ptr->next)
511                   ptr = ptr->next;
512                 ptr->next = q_list;
513                 q_list->next = NULL;
514                 q_list->prev = ptr;
515                 q_list->up = tmp;
516
517                 lindex = q_list->index;
518
519                 /* next class to test */
520                 q_list = save;
521               }
522
523               /* we found a shorter prefix, so we need a redraw */
524               *force_redraw = 1;
525               continue;
526             } else {
527               q_list = q_list->next;
528               continue;
529             }
530           } else {
531             /* longer than the current prefix: try subclassing it */
532             if (tmp == NULL
533                 && m_strncmp(tail_qptr, (q_list->prefix) + offset,
534                                  q_list->length - offset) == 0) {
535               /* still a subclass: go down one level */
536               ptr = q_list;
537               offset = q_list->length;
538
539               q_list = q_list->down;
540               tail_lng = length - offset;
541               tail_qptr = (char *) qptr + offset;
542
543               continue;
544             } else {
545               /* nope, try the next prefix */
546               q_list = q_list->next;
547               continue;
548             }
549           }
550         }
551
552         /* still not found so far: add it as a sibling to the current node */
553         if (class == NULL) {
554           tmp = p_new(struct q_class_t, 1);
555           tmp->prefix = p_dupstr(qptr, length);
556           tmp->length = length;
557
558           if (ptr->down) {
559             tmp->next = ptr->down;
560             ptr->down->prev = tmp;
561           }
562           ptr->down = tmp;
563           tmp->up = ptr;
564
565           new_class_color (tmp, q_level);
566
567           return tmp;
568         } else {
569           if (lindex != -1)
570             shift_class_colors (*QuoteList, tmp, lindex, q_level);
571
572           return class;
573         }
574       } else {
575         /* nope, try the next prefix */
576         q_list = q_list->next;
577         continue;
578       }
579     }
580   }
581
582   if (class == NULL) {
583     /* not found so far: add it as a top level class */
584     class = p_new(struct q_class_t, 1);
585     class->prefix = p_dupstr(qptr, length);
586     class->length = length;
587     new_class_color (class, q_level);
588
589     if (*QuoteList) {
590       class->next = *QuoteList;
591       (*QuoteList)->prev = class;
592     }
593     *QuoteList = class;
594   }
595
596   if (lindex != -1)
597     shift_class_colors (*QuoteList, tmp, lindex, q_level);
598
599   return class;
600 }
601
602 static int brailleLine = -1;
603 static int brailleCol = -1;
604
605 static int check_attachment_marker (char *);
606
607 static void
608 resolve_types (char *buf, char *rawbuf, struct line_t *lineInfo, int n, int last,
609                struct q_class_t **QuoteList, int *q_level, int *force_redraw,
610                int q_classify)
611 {
612   COLOR_LINE *color_line;
613   regmatch_t pmatch[1], smatch[1];
614   int found, offset, null_rx, i;
615
616   if (n == 0 || ISHEADER (lineInfo[n - 1].type)) {
617     if (buf[0] == '\n') {
618       lineInfo[n].type = MT_COLOR_NORMAL;
619       getyx(stdscr, brailleLine, brailleCol);
620     }
621     else if (n > 0 && (buf[0] == ' ' || buf[0] == '\t')) {
622       lineInfo[n].type = lineInfo[n - 1].type;  /* wrapped line */
623       (lineInfo[n].syntax)[0].color = (lineInfo[n - 1].syntax)[0].color;
624     } else {
625       lineInfo[n].type = MT_COLOR_HDEFAULT;
626       color_line = ColorHdrList;
627       while (color_line) {
628         if (REGEXEC (&color_line->rx, buf) == 0) {
629           lineInfo[n].type = MT_COLOR_HEADER;
630           lineInfo[n].syntax[0].color = color_line->pair;
631           break;
632         }
633         color_line = color_line->next;
634       }
635     }
636   }
637   else if (m_strncmp("\033[0m", rawbuf, 4) == 0)       /* a little hack... */
638     lineInfo[n].type = MT_COLOR_NORMAL;
639   else if (check_attachment_marker ((char *) rawbuf) == 0)
640     lineInfo[n].type = MT_COLOR_ATTACHMENT;
641   else if (m_strcmp("-- \n", buf) == 0
642            || m_strcmp("-- \r\n", buf) == 0) {
643     i = n + 1;
644
645     lineInfo[n].type = MT_COLOR_SIGNATURE;
646     while (i < last && check_sig(buf, lineInfo, i - 1) == 0 &&
647            (lineInfo[i].type == MT_COLOR_NORMAL ||
648             lineInfo[i].type == MT_COLOR_QUOTED ||
649             lineInfo[i].type == MT_COLOR_HEADER)) {
650       /* oops... */
651       if (lineInfo[i].chunks) {
652         lineInfo[i].chunks = 0;
653         p_realloc(&(lineInfo[n].syntax), 1);
654       }
655       lineInfo[i++].type = MT_COLOR_SIGNATURE;
656     }
657   }
658   else if (check_sig(buf, lineInfo, n - 1) == 0)
659     lineInfo[n].type = MT_COLOR_SIGNATURE;
660   else if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) {
661     if (regexec ((regex_t *) Smileys.rx, buf, 1, smatch, 0) == 0) {
662       if (smatch[0].rm_so > 0) {
663         char c;
664
665         /* hack to avoid making an extra copy of buf */
666         c = buf[smatch[0].rm_so];
667         buf[smatch[0].rm_so] = 0;
668
669         if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) {
670           if (q_classify && lineInfo[n].quote == NULL)
671             lineInfo[n].quote = classify_quote (QuoteList,
672                                                 buf + pmatch[0].rm_so,
673                                                 pmatch[0].rm_eo -
674                                                 pmatch[0].rm_so, force_redraw,
675                                                 q_level);
676           lineInfo[n].type = MT_COLOR_QUOTED;
677         } else {
678           lineInfo[n].type = MT_COLOR_NORMAL;
679         }
680
681         buf[smatch[0].rm_so] = c;
682       } else {
683         lineInfo[n].type = MT_COLOR_NORMAL;
684       }
685     } else {
686       if (q_classify && lineInfo[n].quote == NULL)
687         lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so,
688                                             pmatch[0].rm_eo - pmatch[0].rm_so,
689                                             force_redraw, q_level);
690       lineInfo[n].type = MT_COLOR_QUOTED;
691     }
692   } else {
693     lineInfo[n].type = MT_COLOR_NORMAL;
694   }
695
696   /* body patterns */
697   if (lineInfo[n].type == MT_COLOR_NORMAL ||
698       lineInfo[n].type == MT_COLOR_QUOTED) {
699     i = 0;
700
701     offset = 0;
702     lineInfo[n].chunks = 0;
703     do {
704       if (!buf[offset])
705         break;
706
707       found = 0;
708       null_rx = 0;
709       color_line = ColorBodyList;
710       while (color_line) {
711         if (regexec (&color_line->rx, buf + offset, 1, pmatch,
712                      (offset ? REG_NOTBOL : 0)) == 0) {
713           if (pmatch[0].rm_eo != pmatch[0].rm_so) {
714             if (!found) {
715               if (++(lineInfo[n].chunks) > 1)
716                 p_realloc(&(lineInfo[n].syntax), lineInfo[n].chunks);
717             }
718             i = lineInfo[n].chunks - 1;
719             pmatch[0].rm_so += offset;
720             pmatch[0].rm_eo += offset;
721             if (!found ||
722                 pmatch[0].rm_so < (lineInfo[n].syntax)[i].first ||
723                 (pmatch[0].rm_so == (lineInfo[n].syntax)[i].first &&
724                  pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last)) {
725               (lineInfo[n].syntax)[i].color = color_line->pair;
726               (lineInfo[n].syntax)[i].first = pmatch[0].rm_so;
727               (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo;
728             }
729             found = 1;
730             null_rx = 0;
731           } else {
732             null_rx = 1;        /* empty regexp; don't add it, but keep looking */
733           }
734         }
735         color_line = color_line->next;
736       }
737
738       if (null_rx)
739         offset++;               /* avoid degenerate cases */
740       else
741         offset = lineInfo[n].syntax[i].last;
742     } while (found || null_rx);
743   }
744 }
745
746 static int is_ansi(unsigned char *buf)
747 {
748     while (isdigit(*buf) || *buf == ';')
749         buf++;
750     return (*buf == 'm');
751 }
752
753 static int check_attachment_marker(char *p)
754 {
755     char *q = AttachmentMarker;
756
757     for (; *p == *q && *q && *p && *q != '\a' && *p != '\a'; p++, q++);
758     return (int)(*p - *q);
759 }
760
761 static int grok_ansi (unsigned char *buf, int pos, ansi_attr * a)
762 {
763   int x = pos;
764
765   while (isdigit (buf[x]) || buf[x] == ';')
766     x++;
767
768   /* Character Attributes */
769   if (option (OPTALLOWANSI) && a != NULL && buf[x] == 'm') {
770     if (pos == x) {
771       if (a->pair != -1)
772         mutt_free_color (a->fg, a->bg);
773       a->attr = ANSI_OFF;
774       a->pair = -1;
775     }
776     a->bg = -2;
777     a->fg = -2;
778     while (pos < x) {
779       if (buf[pos] == '1' && (pos + 1 == x || buf[pos + 1] == ';')) {
780         a->attr |= ANSI_BOLD;
781         pos += 2;
782       }
783       else if (buf[pos] == '4' && (pos + 1 == x || buf[pos + 1] == ';')) {
784         a->attr |= ANSI_UNDERLINE;
785         pos += 2;
786       }
787       else if (buf[pos] == '5' && (pos + 1 == x || buf[pos + 1] == ';')) {
788         a->attr |= ANSI_BLINK;
789         pos += 2;
790       }
791       else if (buf[pos] == '7' && (pos + 1 == x || buf[pos + 1] == ';')) {
792         a->attr |= ANSI_REVERSE;
793         pos += 2;
794       }
795       else if (buf[pos] == '0' && (pos + 1 == x || buf[pos + 1] == ';')) {
796         a->bg = -2;
797         a->fg = -2;
798         pos += 2;
799       }
800       else if (buf[pos] == '3' && isdigit (buf[pos + 1])) {
801         if (a->pair != -1)
802           mutt_free_color (a->fg, a->bg);
803         a->pair = -1;
804         a->attr |= ANSI_COLOR;
805         if (buf[pos + 1] != '9')
806           a->fg = buf[pos + 1] - '0';
807         pos += 3;
808       }
809       else if (buf[pos] == '4' && isdigit (buf[pos + 1])) {
810         if (a->pair != -1)
811           mutt_free_color (a->fg, a->bg);
812         a->pair = -1;
813         a->attr |= ANSI_COLOR;
814         if (buf[pos + 1] != '9')
815           a->bg = buf[pos + 1] - '0';
816         pos += 3;
817       } else {
818         while (pos < x && buf[pos] != ';')
819           pos++;
820         pos++;
821       }
822     }
823   }
824   pos = x;
825   return pos;
826 }
827
828 /* trim tail of buf so that it contains complete multibyte characters */
829 static int trim_incomplete_mbyte(unsigned char *buf, size_t len) {
830   mbstate_t mbstate;
831   ssize_t k;
832
833   p_clear(&mbstate, 1);
834   for (; len > 0; buf += k, len -= k) {
835     k = mbrtowc (NULL, (char *) buf, len, &mbstate);
836     if (k == -2)
837       break;
838     if (k == -1 || k == 0)
839       k = 1;
840   }
841   *buf = '\0';
842
843   return len;
844 }
845
846 static int
847 fill_buffer (FILE * f, off_t *last_pos, off_t offset, unsigned char *buf,
848              unsigned char *fmt, ssize_t blen, int *buf_ready)
849 {
850   unsigned char *p;
851   static int b_read = 0;
852
853   if (*buf_ready == 0) {
854     buf[blen - 1] = 0;
855     if (offset != *last_pos)
856       fseeko (f, offset, 0);
857     if (fgets ((char *) buf, blen - 1, f) == NULL) {
858       fmt[0] = 0;
859       return (-1);
860     }
861     *last_pos = ftello (f);
862     b_read = (int) (*last_pos - offset);
863     *buf_ready = 1;
864
865     /* incomplete mbyte characters trigger a segfault in regex processing for
866      * certain versions of glibc. Trim them if necessary. */
867     if (b_read == blen - 2)
868       b_read -= trim_incomplete_mbyte(buf, b_read);
869
870     /* copy "buf" to "fmt", but without bold and underline controls */
871     p = buf;
872     while (*p) {
873       if (*p == '\010' && (p > buf)) {
874         if (*(p + 1) == '_')    /* underline */
875           p += 2;
876         else if (*(p + 1)) {    /* bold or overstrike */
877           *(fmt - 1) = *(p + 1);
878           p += 2;
879         } else {                  /* ^H */
880           *fmt++ = *p++;
881         }
882       }
883       else if (*p == '\033' && *(p + 1) == '[' && is_ansi (p + 2)) {
884         while (*p++ != 'm')     /* skip ANSI sequence */
885           ;
886       }
887       else if (*p == '\033' && *(p + 1) == ']'
888                && check_attachment_marker ((char *) p) == 0) {
889         while (*p++ != '\a')    /* skip pseudo-ANSI sequence */
890           ;
891       } else {
892         *fmt++ = *p++;
893       }
894     }
895     *fmt = 0;
896   }
897   return b_read;
898 }
899
900 #ifdef USE_NNTP
901 #include <nntp/nntp.h>
902 #endif
903
904 static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
905                         int flags, ansi_attr * pa, int cnt,
906                         int *pspace, int *pvch, int *pcol, int *pspecial)
907 {
908   int space = -1;               /* index of the last space or TAB */
909   int col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0;
910   int ch, vch, k, last_special = -1, special = 0, t;
911   wchar_t wc;
912   mbstate_t mbstate;
913
914   int wrap_cols = COLS;
915
916   if (!(flags & (M_SHOWFLAT)))
917     wrap_cols -= WrapMargin;
918   wrap_cols -= SW;
919
920   if (wrap_cols <= 0)
921     wrap_cols = COLS;
922
923   /* FIXME: this should come from lineInfo */
924   p_clear(&mbstate, 1);
925
926   for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k) {
927     /* Handle ANSI sequences */
928     while (cnt - ch >= 2 && buf[ch] == '\033' && buf[ch + 1] == '[' &&
929            is_ansi (buf + ch + 2))
930       ch = grok_ansi (buf, ch + 2, pa) + 1;
931
932     while (cnt - ch >= 2 && buf[ch] == '\033' && buf[ch + 1] == ']' &&
933            check_attachment_marker ((char *) buf + ch) == 0) {
934       while (buf[ch++] != '\a')
935         if (ch >= cnt)
936           break;
937     }
938
939     /* is anything left to do? */
940     if (ch >= cnt)
941       break;
942
943     k = mbrtowc (&wc, (char *) buf + ch, cnt - ch, &mbstate);
944     if (k == -2 || k == -1) {
945       if (col + 4 > wrap_cols)
946         break;
947       col += 4;
948       if (pa)
949         printw ("\\%03o", buf[ch]);
950       k = 1;
951       continue;
952     }
953     if (k == 0)
954       k = 1;
955
956     /* Handle backspace */
957     special = 0;
958     if (iswprint(wc)) {
959       wchar_t wc1;
960       mbstate_t mbstate1;
961       int k1, k2;
962
963       while ((wc1 = 0, mbstate1 = mbstate,
964               k1 =
965               k + mbrtowc (&wc1, (char *) buf + ch + k, cnt - ch - k,
966                            &mbstate1), k1 - k > 0 && wc1 == '\b')
967              && (wc1 = 0, k2 =
968                  mbrtowc (&wc1, (char *) buf + ch + k1, cnt - ch - k1,
969                           &mbstate1), k2 > 0 && iswprint(wc1))) {
970         if (wc == wc1) {
971           special |= (wc == '_' && special & A_UNDERLINE)
972             ? A_UNDERLINE : A_BOLD;
973         }
974         else if (wc == '_' || wc1 == '_') {
975           special |= A_UNDERLINE;
976           wc = (wc1 == '_') ? wc : wc1;
977         } else {
978           /* special = 0; / * overstrike: nothing to do! */
979           wc = wc1;
980         }
981         ch += k1;
982         k = k2;
983         mbstate = mbstate1;
984       }
985     }
986
987     if (pa &&
988         ((flags & (M_SHOWCOLOR | M_SEARCH | M_PAGER_MARKER)) ||
989          special || last_special || pa->attr)) {
990       resolve_color (*lineInfo, n, vch, flags, special, pa);
991       last_special = special;
992     }
993
994     if (iswprint(wc)) {
995       if (wc == ' ')
996         space = ch;
997       t = wcwidth (wc);
998       if (col + t > wrap_cols)
999         break;
1000       col += t;
1001       if (pa)
1002         mutt_addwch (wc);
1003     }
1004     else if (wc == '\n')
1005       break;
1006     else if (wc == '\t') {
1007       space = ch;
1008       t = (col & ~7) + 8;
1009       if (t > wrap_cols)
1010         break;
1011       if (pa)
1012         for (; col < t; col++)
1013           addch (' ');
1014       else
1015         col = t;
1016     }
1017     else if (wc < 0x20 || wc == 0x7f) {
1018       if (col + 2 > wrap_cols)
1019         break;
1020       col += 2;
1021       if (pa)
1022         printw ("^%c", ('@' + wc) & 0x7f);
1023     }
1024     else if (wc < 0x100) {
1025       if (col + 4 > wrap_cols)
1026         break;
1027       col += 4;
1028       if (pa)
1029         printw ("\\%03o", wc);
1030     } else {
1031       if (col + 1 > wrap_cols)
1032         break;
1033       ++col;
1034       if (pa)
1035         addch (CharsetReplacement);
1036     }
1037   }
1038   *pspace = space;
1039   *pcol = col;
1040   *pvch = vch;
1041   *pspecial = special;
1042   return ch;
1043 }
1044
1045 /*
1046  * Args:
1047  *      flags   M_SHOWFLAT, show characters (used for displaying help)
1048  *              M_SHOWCOLOR, show characters in color
1049  *                      otherwise don't show characters
1050  *              M_HIDE, don't show quoted text
1051  *              M_SEARCH, resolve search patterns
1052  *              M_TYPES, compute line's type
1053  *              M_PAGER_NSKIP, keeps leading whitespace
1054  *              M_PAGER_MARKER, eventually show markers
1055  *
1056  * Return values:
1057  *      -1      EOF was reached
1058  *      0       normal exit, line was not displayed
1059  *      >0      normal exit, line was displayed
1060  */
1061 static int
1062 display_line (FILE * f, off_t *last_pos, struct line_t **lineInfo, int n,
1063               int *last, int *max, int flags, struct q_class_t **QuoteList,
1064               int *q_level, int *force_redraw, regex_t * SearchRE)
1065 {
1066   unsigned char buf[LONG_STRING], fmt[LONG_STRING];
1067   unsigned char *buf_ptr = buf;
1068   int ch, vch, col, cnt, b_read;
1069   int buf_ready = 0, change_last = 0;
1070   int special;
1071   int offset;
1072   int def_color;
1073   int m;
1074   ansi_attr a = { 0, 0, 0, -1 };
1075   regmatch_t pmatch[1];
1076
1077   if (n == *last) {
1078     (*last)++;
1079     change_last = 1;
1080   }
1081
1082   if (*last == *max) {
1083     p_realloc(lineInfo, *max += LINES);
1084     for (ch = *last; ch < *max; ch++) {
1085       p_clear(&(*lineInfo)[ch], 1);
1086       (*lineInfo)[ch].type = -1;
1087       (*lineInfo)[ch].search_cnt = -1;
1088       (*lineInfo)[ch].syntax = p_new(struct syntax_t, 1);
1089       ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last =
1090         -1;
1091     }
1092   }
1093
1094   /* only do color hiliting if we are viewing a message */
1095   if (flags & (M_SHOWCOLOR | M_TYPES)) {
1096     if ((*lineInfo)[n].type == -1) {
1097       /* determine the line class */
1098       if (fill_buffer
1099           (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1100            &buf_ready) < 0) {
1101         if (change_last)
1102           (*last)--;
1103         return (-1);
1104       }
1105
1106       resolve_types ((char *) fmt, (char *) buf, *lineInfo, n, *last,
1107                      QuoteList, q_level, force_redraw, flags & M_SHOWCOLOR);
1108
1109       /* avoid race condition for continuation lines when scrolling up */
1110       for (m = n + 1;
1111            m < *last && (*lineInfo)[m].offset && (*lineInfo)[m].continuation;
1112            m++)
1113         (*lineInfo)[m].type = (*lineInfo)[n].type;
1114     }
1115
1116     /* this also prevents searching through the hidden lines */
1117     if ((flags & M_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED)
1118       flags = 0;                /* M_NOSHOW */
1119   }
1120
1121   /* At this point, (*lineInfo[n]).quote may still be undefined. We 
1122    * don't want to compute it every time M_TYPES is set, since this
1123    * would slow down the "bottom" function unacceptably. A compromise
1124    * solution is hence to call regexec() again, just to find out the
1125    * length of the quote prefix.
1126    */
1127   if ((flags & M_SHOWCOLOR) && !(*lineInfo)[n].continuation &&
1128       (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL)
1129   {
1130     if (fill_buffer
1131         (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1132          &buf_ready) < 0) {
1133       if (change_last)
1134         (*last)--;
1135       return (-1);
1136     }
1137     regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0);
1138     (*lineInfo)[n].quote = classify_quote (QuoteList,
1139                                            (char *) fmt + pmatch[0].rm_so,
1140                                            pmatch[0].rm_eo - pmatch[0].rm_so,
1141                                            force_redraw, q_level);
1142   }
1143
1144   if ((flags & M_SEARCH) && !(*lineInfo)[n].continuation
1145       && (*lineInfo)[n].search_cnt == -1) {
1146     if (fill_buffer
1147         (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1148          &buf_ready) < 0) {
1149       if (change_last)
1150         (*last)--;
1151       return (-1);
1152     }
1153
1154     offset = 0;
1155     (*lineInfo)[n].search_cnt = 0;
1156     while (regexec
1157            (SearchRE, (char *) fmt + offset, 1, pmatch,
1158             (offset ? REG_NOTBOL : 0)) == 0) {
1159       if (++((*lineInfo)[n].search_cnt) > 1)
1160         p_realloc(&(*lineInfo)[n].search, (*lineInfo)[n].search_cnt);
1161       else
1162         (*lineInfo)[n].search = p_new(struct syntax_t, 1);
1163       pmatch[0].rm_so += offset;
1164       pmatch[0].rm_eo += offset;
1165       ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first =
1166         pmatch[0].rm_so;
1167       ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last =
1168         pmatch[0].rm_eo;
1169
1170       if (pmatch[0].rm_eo == pmatch[0].rm_so)
1171         offset++;               /* avoid degenerate cases */
1172       else
1173         offset = pmatch[0].rm_eo;
1174       if (!fmt[offset])
1175         break;
1176     }
1177   }
1178
1179   if (!(flags & M_SHOW) && (*lineInfo)[n + 1].offset > 0) {
1180     /* we've already scanned this line, so just exit */
1181     return (0);
1182   }
1183   if ((flags & M_SHOWCOLOR) && *force_redraw && (*lineInfo)[n + 1].offset > 0) {
1184     /* no need to try to display this line... */
1185     return (1);                 /* fake display */
1186   }
1187
1188   if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt,
1189                              sizeof (buf), &buf_ready)) < 0) {
1190     if (change_last)
1191       (*last)--;
1192     return (-1);
1193   }
1194
1195   /* now chose a good place to break the line */
1196   cnt = format_line(lineInfo, n, buf, flags, 0, b_read, &ch, &vch, &col,
1197                     &special);
1198   buf_ptr = buf + cnt;
1199
1200   /* move the break point only if smart_wrap is set */
1201   if (option (OPTWRAP)) {
1202     if (cnt < b_read) {
1203       if (ch != -1 && buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n'
1204           && buf[cnt] != '\r') {
1205         buf_ptr = buf + ch;
1206         /* skip trailing blanks */
1207         while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r'))
1208           ch--;
1209         /* a very long word with leading spaces causes infinite wrapping */
1210         if ((!ch) && (flags & M_PAGER_NSKIP))
1211           buf_ptr = buf + cnt;
1212         else
1213           cnt = ch + 1;
1214       } else {
1215         buf_ptr = buf + cnt;    /* a very long word... */
1216       }
1217     }
1218     if (!(flags & M_PAGER_NSKIP))
1219       /* skip leading blanks on the next line too */
1220       while (*buf_ptr == ' ' || *buf_ptr == '\t')
1221         buf_ptr++;
1222   }
1223
1224   if (*buf_ptr == '\r')
1225     buf_ptr++;
1226   if (*buf_ptr == '\n')
1227     buf_ptr++;
1228
1229   if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n + 1].continuation)
1230     append_line (*lineInfo, n, (int) (buf_ptr - buf));
1231   (*lineInfo)[n + 1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf);
1232
1233   /* if we don't need to display the line we are done */
1234   if (!(flags & M_SHOW))
1235     return 0;
1236
1237   /* display the line */
1238   format_line (lineInfo, n, buf, flags, &a, cnt, &ch, &vch, &col, &special);
1239
1240   /* avoid a bug in ncurses... */
1241 #ifndef USE_SLANG_CURSES
1242   if (col == 0) {
1243     SETCOLOR (MT_COLOR_NORMAL);
1244     addch (' ');
1245   }
1246 #endif
1247
1248   /* end the last color pattern (needed by S-Lang) */
1249   if (special || (col != COLS && (flags & (M_SHOWCOLOR | M_SEARCH))))
1250     resolve_color (*lineInfo, n, vch, flags, 0, &a);
1251
1252   /*
1253    * Fill the blank space at the end of the line with the prevailing color.
1254    * ncurses does an implicit clrtoeol() when you do addch('\n') so we have
1255    * to make sure to reset the color *after* that
1256    */
1257   if (flags & M_SHOWCOLOR) {
1258     m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n;
1259     if ((*lineInfo)[m].type == MT_COLOR_HEADER)
1260       def_color = ((*lineInfo)[m].syntax)[0].color;
1261     else
1262       def_color = ColorDefs[(*lineInfo)[m].type];
1263
1264     attrset (def_color);
1265 #ifdef HAVE_BKGDSET
1266     bkgdset (def_color | ' ');
1267 #endif
1268   }
1269
1270   /* ncurses always wraps lines when you get to the right side of the
1271    * screen, but S-Lang seems to only wrap if the next character is *not*
1272    * a newline (grr!).
1273    */
1274 #ifndef USE_SLANG_CURSES
1275   if (col < COLS)
1276 #endif
1277     addch ('\n');
1278
1279   /*
1280    * reset the color back to normal.  This *must* come after the
1281    * addch('\n'), otherwise the color for this line will not be
1282    * filled to the right margin.
1283    */
1284   if (flags & M_SHOWCOLOR) {
1285     SETCOLOR (MT_COLOR_NORMAL);
1286     BKGDSET (MT_COLOR_NORMAL);
1287   }
1288
1289   /* build a return code */
1290   if (!(flags & M_SHOW))
1291     flags = 0;
1292
1293   return (flags);
1294 }
1295
1296 static int upNLines (int nlines, struct line_t *info, int cur, int hiding)
1297 {
1298   while (cur > 0 && nlines > 0) {
1299     cur--;
1300     if (!hiding || info[cur].type != MT_COLOR_QUOTED)
1301       nlines--;
1302   }
1303
1304   return cur;
1305 }
1306
1307 static struct mapping_t PagerHelp[] = {
1308   {N_("Exit"), OP_EXIT},
1309   {N_("PrevPg"), OP_PREV_PAGE},
1310   {N_("NextPg"), OP_NEXT_PAGE},
1311   {NULL, 0}
1312 };
1313 static struct mapping_t PagerHelpExtra[] = {
1314   {N_("View Attachm."), OP_VIEW_ATTACHMENTS},
1315   {N_("Del"), OP_DELETE},
1316   {N_("Reply"), OP_REPLY},
1317   {N_("Next"), OP_MAIN_NEXT_UNDELETED},
1318   {NULL, 0}
1319 };
1320
1321 #ifdef USE_NNTP
1322 static struct mapping_t PagerNewsHelpExtra[] = {
1323   {N_("Post"), OP_POST},
1324   {N_("Followup"), OP_FOLLOWUP},
1325   {N_("Del"), OP_DELETE},
1326   {N_("Next"), OP_MAIN_NEXT_UNDELETED},
1327   {NULL, 0}
1328 };
1329 #endif
1330
1331
1332 /* This pager is actually not so simple as it once was.  It now operates in
1333    two modes: one for viewing messages and the other for viewing help.  These
1334    can be distinguished by whether or not ``hdr'' is NULL.  The ``hdr'' arg
1335    is there so that we can do operations on the current message without the
1336    need to pop back out to the main-menu.  */
1337 int
1338 mutt_pager (const char *banner, const char *fname, int flags, pager_t * extra)
1339 {
1340   static char searchbuf[STRING];
1341   char buffer[LONG_STRING];
1342   char helpstr[STRING * 2];
1343   char tmphelp[STRING * 2];
1344   int maxLine, lastLine = 0;
1345   struct line_t *lineInfo;
1346   struct q_class_t *QuoteList = NULL;
1347   int i, j, ch = 0, rc = -1, hideQuoted = 0, q_level = 0, force_redraw = 0;
1348   int lines = 0, curline = 0, topline = 0, oldtopline = 0, err, first = 1;
1349   int r = -1;
1350   int redraw = REDRAW_FULL;
1351   FILE *fp = NULL;
1352   off_t last_pos = 0, last_offset = 0;
1353   int old_smart_wrap, old_markers;
1354   struct stat sb;
1355   regex_t SearchRE;
1356   int SearchCompiled = 0, SearchFlag = 0, SearchBack = 0;
1357   int has_types = (IsHeader (extra) || (flags & M_SHOWCOLOR)) ? M_TYPES : 0;    /* main message or rfc822 attachment */
1358
1359   int bodyoffset = 1;           /* offset of first line of real text */
1360   int statusoffset = 0;         /* offset for the status bar */
1361   int helpoffset = LINES - 2;   /* offset for the help bar. */
1362   int bodylen = LINES - 2 - bodyoffset; /* length of displayable area */
1363
1364   MUTTMENU *pager_index = NULL;       /* the Pager Index (PI) */
1365   int indexoffset = 0;          /* offset for the PI */
1366   int indexlen = PagerIndexLines;       /* indexlen not always == PIL */
1367   int indicator = indexlen / 3; /* the indicator line of the PI */
1368   int old_PagerIndexLines;      /* some people want to resize it
1369                                  * while inside the pager... */
1370
1371 #ifdef USE_NNTP
1372   char *followup_to;
1373 #endif
1374
1375   if (!(flags & M_SHOWCOLOR))
1376     flags |= M_SHOWFLAT;
1377
1378   if ((fp = fopen (fname, "r")) == NULL) {
1379     mutt_perror (fname);
1380     return (-1);
1381   }
1382
1383   if (stat (fname, &sb) != 0) {
1384     mutt_perror (fname);
1385     m_fclose(&fp);
1386     return (-1);
1387   }
1388   unlink (fname);
1389
1390   /* Initialize variables */
1391
1392   if (IsHeader (extra) && !extra->hdr->read) {
1393     Context->msgnotreadyet = extra->hdr->msgno;
1394     mutt_set_flag (Context, extra->hdr, M_READ, 1);
1395   }
1396
1397   lineInfo = p_new(struct line_t, maxLine = LINES);
1398   for (i = 0; i < maxLine; i++) {
1399     p_clear(&lineInfo[i], 1);
1400     lineInfo[i].type = -1;
1401     lineInfo[i].search_cnt = -1;
1402     lineInfo[i].syntax = p_new(struct syntax_t, 1);
1403     (lineInfo[i].syntax)[0].first = (lineInfo[i].syntax)[0].last = -1;
1404   }
1405
1406   mutt_compile_help (helpstr, sizeof(helpstr), MENU_PAGER, PagerHelp);
1407   if (IsHeader (extra)) {
1408     m_strcpy(tmphelp, sizeof(tmphelp), helpstr);
1409     mutt_compile_help (buffer, sizeof(buffer), MENU_PAGER,
1410 #ifdef USE_NNTP
1411                        (Context
1412                         && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra :
1413 #endif
1414                        PagerHelpExtra);
1415     snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
1416   }
1417   if (!InHelp) {
1418     m_strcpy(tmphelp, sizeof(tmphelp), helpstr);
1419     mutt_make_help (buffer, sizeof(buffer), _("Help"), MENU_PAGER, OP_HELP);
1420     snprintf (helpstr, sizeof(helpstr), "%s %s", tmphelp, buffer);
1421   }
1422
1423   while (ch != -1) {
1424     mutt_curs_set (0);
1425     imap_keepalive ();
1426
1427     if (redraw & REDRAW_FULL) {
1428       SETCOLOR (MT_COLOR_NORMAL);
1429       /* clear() doesn't optimize screen redraws */
1430       move (0, 0);
1431       clrtobot ();
1432
1433       if (IsHeader (extra) && Context->vcount + 1 < PagerIndexLines)
1434         indexlen = Context->vcount + 1;
1435       else
1436         indexlen = PagerIndexLines;
1437
1438       indicator = indexlen / 3;
1439
1440       if (option (OPTSTATUSONTOP)) {
1441         indexoffset = 0;
1442         statusoffset = IsHeader (extra) ? indexlen : 0;
1443         bodyoffset = statusoffset + 1;
1444         helpoffset = LINES - 2;
1445         bodylen = helpoffset - bodyoffset;
1446         if (!option (OPTHELP))
1447           bodylen++;
1448       } else {
1449         helpoffset = 0;
1450         indexoffset = 1;
1451         statusoffset = LINES - 2;
1452         if (!option (OPTHELP))
1453           indexoffset = 0;
1454         bodyoffset = indexoffset + (IsHeader (extra) ? indexlen : 0);
1455         bodylen = statusoffset - bodyoffset;
1456       }
1457
1458       if (option (OPTHELP)) {
1459         SETCOLOR (MT_COLOR_STATUS);
1460         move (helpoffset, SW);
1461         mutt_paddstr (COLS-SW, helpstr);
1462         SETCOLOR (MT_COLOR_NORMAL);
1463       }
1464
1465 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1466       if (Resize != NULL) {
1467         if ((SearchCompiled = Resize->SearchCompiled)) {
1468           REGCOMP
1469             (&SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf));
1470           SearchFlag = M_SEARCH;
1471           SearchBack = Resize->SearchBack;
1472         }
1473         lines = Resize->line;
1474         redraw |= REDRAW_SIGWINCH;
1475
1476         p_delete(&Resize);
1477       }
1478 #endif
1479
1480       if (IsHeader (extra) && PagerIndexLines) {
1481         if (pager_index == NULL) {
1482           /* only allocate the space if/when we need the index.
1483              Initialise the menu as per the main index */
1484           pager_index = mutt_new_menu ();
1485           pager_index->menu = MENU_MAIN;
1486           pager_index->make_entry = index_make_entry;
1487           pager_index->color = index_color;
1488           pager_index->max = Context->vcount;
1489           pager_index->current = extra->hdr->virtual;
1490         }
1491
1492         SETCOLOR (MT_COLOR_NORMAL);
1493         pager_index->offset = indexoffset + (option (OPTSTATUSONTOP) ? 1 : 0);
1494
1495         pager_index->pagelen = indexlen - 1;
1496
1497         /* some fudge to work out where abouts the indicator should go */
1498         if (pager_index->current - indicator < 0)
1499           pager_index->top = 0;
1500         else if (pager_index->max - pager_index->current < pager_index->pagelen - indicator)
1501           pager_index->top = pager_index->max - pager_index->pagelen;
1502         else
1503           pager_index->top = pager_index->current - indicator;
1504
1505         menu_redraw_index (pager_index);
1506       }
1507
1508       redraw |= REDRAW_BODY | REDRAW_INDEX | REDRAW_STATUS;
1509       mutt_show_error ();
1510     }
1511
1512     if (redraw & REDRAW_SIGWINCH) {
1513       i = -1;
1514       j = -1;
1515       while (display_line (fp, &last_pos, &lineInfo, ++i, &lastLine, &maxLine,
1516                            has_types | SearchFlag, &QuoteList, &q_level,
1517                            &force_redraw, &SearchRE) == 0) {
1518         if (!lineInfo[i].continuation && ++j == lines) {
1519           topline = i;
1520           if (!SearchFlag)
1521             break;
1522         }
1523         redraw |= REDRAW_SIDEBAR;
1524       }                         /* while */
1525     }
1526
1527     if ((redraw & REDRAW_BODY) || topline != oldtopline) {
1528       do {
1529         move (bodyoffset, SW);
1530         curline = oldtopline = topline;
1531         lines = 0;
1532         force_redraw = 0;
1533
1534         while (lines < bodylen && lineInfo[curline].offset <= sb.st_size - 1) {
1535           if (display_line (fp, &last_pos, &lineInfo, curline, &lastLine,
1536                             &maxLine,
1537                             (flags & M_DISPLAYFLAGS) | hideQuoted |
1538                             SearchFlag, &QuoteList, &q_level, &force_redraw,
1539                             &SearchRE) > 0)
1540             lines++;
1541           curline++;
1542           move (lines + bodyoffset, SW);
1543           redraw |= REDRAW_SIDEBAR;
1544         }
1545         last_offset = lineInfo[curline].offset;
1546       } while (force_redraw);
1547
1548       SETCOLOR (MT_COLOR_TILDE);
1549       BKGDSET (MT_COLOR_TILDE);
1550       while (lines < bodylen) {
1551         clrtoeol ();
1552         if (option (OPTTILDE))
1553           addch ('~');
1554         addch ('\n');
1555         lines++;
1556         move (lines + bodyoffset, SW);
1557       }
1558       /* We are going to update the pager status bar, so it isn't
1559        * necessary to reset to normal color now. */
1560
1561       redraw |= REDRAW_STATUS;  /* need to update the % seen */
1562     }
1563
1564     if (redraw & REDRAW_STATUS) {
1565       /* print out the pager status bar */
1566       SETCOLOR (MT_COLOR_STATUS);
1567       BKGDSET (MT_COLOR_STATUS);
1568       CLEARLINE_WIN (statusoffset);
1569       if (IsHeader (extra)) {
1570         size_t l1 = (COLS - 9) * MB_LEN_MAX;
1571         size_t l2 = sizeof (buffer);
1572
1573         _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt),
1574                            Context, extra->hdr, M_FORMAT_MAKEPRINT);
1575       }
1576       else if (IsMsgAttach (extra)) {
1577         size_t l1 = (COLS - 9) * MB_LEN_MAX;
1578         size_t l2 = sizeof (buffer);
1579
1580         _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt),
1581                            Context, extra->bdy->hdr, M_FORMAT_MAKEPRINT);
1582       }
1583       move(statusoffset,SW);
1584       mutt_paddstr (COLS - 10 - SW, IsHeader (extra) || 
1585                     IsMsgAttach (extra) ? buffer : banner);
1586
1587       addstr (" -- (");
1588       if (last_pos < sb.st_size - 1)
1589         printw ("%d%%)", (int) (100 * last_offset / sb.st_size));
1590       else
1591         addstr (topline == 0 ? "all)" : "end)");
1592       BKGDSET (MT_COLOR_NORMAL);
1593       SETCOLOR (MT_COLOR_NORMAL);
1594     }
1595
1596     if (redraw & REDRAW_SIDEBAR)
1597       sidebar_draw ();
1598
1599     if ((redraw & REDRAW_INDEX) && pager_index) {
1600       /* redraw the pager_index indicator, because the
1601        * flags for this message might have changed. */
1602       menu_redraw_current (pager_index);
1603       sidebar_draw ();
1604       /* print out the pager_index status bar */
1605       menu_status_line (buffer, sizeof (buffer), pager_index, NONULL (Status));
1606       move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), SW);
1607       SETCOLOR (MT_COLOR_STATUS);
1608       BKGDSET (MT_COLOR_STATUS);
1609       mutt_paddstr (COLS-SW, buffer);
1610       SETCOLOR (MT_COLOR_NORMAL);
1611       BKGDSET (MT_COLOR_NORMAL);
1612     }
1613     /* if we're not using the index, update every time */
1614     if (index == 0)
1615       sidebar_draw ();
1616
1617     redraw = 0;
1618
1619     if (option(OPTBRAILLEFRIENDLY)) {
1620       if (brailleLine!=-1) {
1621         move(brailleLine+1, 0);
1622         brailleLine = -1;
1623       }
1624     } else
1625       move (statusoffset, COLS-1);
1626     mutt_refresh ();
1627
1628     if (IsHeader (extra) && OldHdr == extra->hdr && TopLine != topline
1629         && lineInfo[curline].offset < sb.st_size-1) {
1630       if (TopLine - topline > lines)
1631         topline += lines;
1632       else
1633         topline = TopLine;
1634       continue;
1635     } else {
1636       OldHdr = NULL;
1637     }
1638
1639     ch = km_dokey (MENU_PAGER);
1640     if (ch != -1)
1641       mutt_clear_error ();
1642     mutt_curs_set (1);
1643
1644     if (SigInt) {
1645       mutt_query_exit ();
1646       continue;
1647     }
1648 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1649     else if (SigWinch) {
1650       mutt_resize_screen ();
1651
1652       /* Store current position. */
1653       lines = -1;
1654       for (i = 0; i <= topline; i++)
1655         if (!lineInfo[i].continuation)
1656           lines++;
1657
1658       if (flags & M_PAGER_RETWINCH) {
1659         Resize = p_new(struct resize, 1);
1660
1661         Resize->line = lines;
1662         Resize->SearchCompiled = SearchCompiled;
1663         Resize->SearchBack = SearchBack;
1664
1665         ch = -1;
1666         rc = OP_REFORMAT_WINCH;
1667       } else {
1668         for (i = 0; i < maxLine; i++) {
1669           lineInfo[i].offset = 0;
1670           lineInfo[i].type = -1;
1671           lineInfo[i].continuation = 0;
1672           lineInfo[i].chunks = 0;
1673           lineInfo[i].search_cnt = -1;
1674           lineInfo[i].quote = NULL;
1675
1676           p_realloc(&lineInfo[i].syntax, 1);
1677           if (SearchCompiled && lineInfo[i].search)
1678             p_delete(&(lineInfo[i].search));
1679         }
1680
1681         lastLine = 0;
1682         topline = 0;
1683
1684         redraw = REDRAW_FULL | REDRAW_SIGWINCH;
1685         ch = 0;
1686       }
1687
1688       SigWinch = 0;
1689       clearok (stdscr, TRUE);   /*force complete redraw */
1690       continue;
1691     }
1692 #endif
1693     else if (ch == -1) {
1694       ch = 0;
1695       continue;
1696     }
1697
1698     rc = ch;
1699
1700     switch (ch) {
1701     case OP_EXIT:
1702       rc = -1;
1703       ch = -1;
1704       break;
1705
1706     case OP_NEXT_PAGE:
1707       if (lineInfo[curline].offset < sb.st_size - 1) {
1708         topline = upNLines (PagerContext, lineInfo, curline, hideQuoted);
1709       }
1710       else if (option (OPTPAGERSTOP)) {
1711         /* emulate "less -q" and don't go on to the next message. */
1712         mutt_error _("Bottom of message is shown.");
1713       } else {
1714         /* end of the current message, so display the next message. */
1715         rc = OP_MAIN_NEXT_UNDELETED;
1716         ch = -1;
1717       }
1718       break;
1719
1720     case OP_PREV_PAGE:
1721       if (topline != 0) {
1722         topline =
1723           upNLines (bodylen - PagerContext, lineInfo, topline, hideQuoted);
1724       }
1725       else
1726         mutt_error _("Top of message is shown.");
1727       break;
1728
1729     case OP_NEXT_LINE:
1730       if (lineInfo[curline].offset < sb.st_size - 1) {
1731         topline++;
1732         if (hideQuoted) {
1733           while (lineInfo[topline].type == MT_COLOR_QUOTED &&
1734                  topline < lastLine)
1735             topline++;
1736         }
1737       }
1738       else
1739         mutt_error _("Bottom of message is shown.");
1740       break;
1741
1742     case OP_PREV_LINE:
1743       if (topline)
1744         topline = upNLines (1, lineInfo, topline, hideQuoted);
1745       else
1746         mutt_error _("Top of message is shown.");
1747       break;
1748
1749     case OP_PAGER_TOP:
1750       if (topline)
1751         topline = 0;
1752       else
1753         mutt_error _("Top of message is shown.");
1754       break;
1755
1756     case OP_HALF_UP:
1757       if (topline)
1758         topline = upNLines (bodylen / 2, lineInfo, topline, hideQuoted);
1759       else
1760         mutt_error _("Top of message is shown.");
1761       break;
1762
1763     case OP_HALF_DOWN:
1764       if (lineInfo[curline].offset < sb.st_size - 1) {
1765         topline = upNLines (bodylen / 2, lineInfo, curline, hideQuoted);
1766       }
1767       else if (option (OPTPAGERSTOP)) {
1768         /* emulate "less -q" and don't go on to the next message. */
1769         mutt_error _("Bottom of message is shown.");
1770       } else {
1771         /* end of the current message, so display the next message. */
1772         rc = OP_MAIN_NEXT_UNDELETED;
1773         ch = -1;
1774       }
1775       break;
1776
1777     case OP_SEARCH_NEXT:
1778     case OP_SEARCH_OPPOSITE:
1779       if (SearchCompiled) {
1780       search_next:
1781         if ((!SearchBack && ch == OP_SEARCH_NEXT) ||
1782             (SearchBack && ch == OP_SEARCH_OPPOSITE)) {
1783           /* searching forward */
1784           for (i = topline + 1; i < lastLine; i++) {
1785             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1786                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1787               break;
1788           }
1789
1790           if (i < lastLine)
1791             topline = i;
1792           else
1793             mutt_error _("Not found.");
1794         }
1795         else {
1796           /* searching backward */
1797           for (i = topline - 1; i >= 0; i--) {
1798             if ((!hideQuoted || (has_types &&
1799                                  lineInfo[i].type != MT_COLOR_QUOTED)) &&
1800                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1801               break;
1802           }
1803
1804           if (i >= 0)
1805             topline = i;
1806           else
1807             mutt_error _("Not found.");
1808         }
1809
1810         if (lineInfo[topline].search_cnt > 0)
1811           SearchFlag = M_SEARCH;
1812
1813         break;
1814       }
1815       /* no previous search pattern, so fall through to search */
1816
1817     case OP_SEARCH:
1818     case OP_SEARCH_REVERSE:
1819       m_strcpy(buffer, sizeof(buffer), searchbuf);
1820       if (mutt_get_field ((SearchBack ? _("Reverse search: ") :
1821                            _("Search: ")), buffer, sizeof (buffer),
1822                           M_CLEAR) != 0)
1823         break;
1824
1825       if (!m_strcmp (buffer, searchbuf)) {
1826         if (SearchCompiled) {
1827           /* do an implicit search-next */
1828           if (ch == OP_SEARCH)
1829             ch = OP_SEARCH_NEXT;
1830           else
1831             ch = OP_SEARCH_OPPOSITE;
1832
1833           goto search_next;
1834         }
1835       }
1836
1837       if (!buffer[0])
1838         break;
1839
1840       m_strcpy(searchbuf, sizeof(searchbuf), buffer);
1841
1842       /* leave SearchBack alone if ch == OP_SEARCH_NEXT */
1843       if (ch == OP_SEARCH)
1844         SearchBack = 0;
1845       else if (ch == OP_SEARCH_REVERSE)
1846         SearchBack = 1;
1847
1848       if (SearchCompiled) {
1849         regfree (&SearchRE);
1850         for (i = 0; i < lastLine; i++) {
1851           if (lineInfo[i].search)
1852             p_delete(&(lineInfo[i].search));
1853           lineInfo[i].search_cnt = -1;
1854         }
1855       }
1856
1857       if ((err =
1858            REGCOMP (&SearchRE, searchbuf,
1859                     REG_NEWLINE | mutt_which_case (searchbuf))) != 0) {
1860         regerror (err, &SearchRE, buffer, sizeof (buffer));
1861         mutt_error ("%s", buffer);
1862         regfree (&SearchRE);
1863         for (i = 0; i < maxLine; i++) {
1864           /* cleanup */
1865           if (lineInfo[i].search)
1866             p_delete(&(lineInfo[i].search));
1867           lineInfo[i].search_cnt = -1;
1868         }
1869         SearchFlag = 0;
1870         SearchCompiled = 0;
1871       }
1872       else {
1873         SearchCompiled = 1;
1874         /* update the search pointers */
1875         i = 0;
1876         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
1877                              &maxLine, M_SEARCH | (flags & M_PAGER_NSKIP),
1878                              &QuoteList, &q_level,
1879                              &force_redraw, &SearchRE) == 0) {
1880           i++;
1881           redraw |= REDRAW_SIDEBAR;
1882         }
1883
1884         if (!SearchBack) {
1885           /* searching forward */
1886           for (i = topline; i < lastLine; i++) {
1887             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1888                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1889               break;
1890           }
1891
1892           if (i < lastLine)
1893             topline = i;
1894         }
1895         else {
1896           /* searching backward */
1897           for (i = topline; i >= 0; i--) {
1898             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1899                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1900               break;
1901           }
1902
1903           if (i >= 0)
1904             topline = i;
1905         }
1906
1907         if (lineInfo[topline].search_cnt == 0) {
1908           SearchFlag = 0;
1909           mutt_error _("Not found.");
1910         }
1911         else
1912           SearchFlag = M_SEARCH;
1913       }
1914       redraw = REDRAW_BODY;
1915       break;
1916
1917     case OP_SEARCH_TOGGLE:
1918       if (SearchCompiled) {
1919         SearchFlag ^= M_SEARCH;
1920         redraw = REDRAW_BODY;
1921       }
1922       break;
1923
1924     case OP_HELP:
1925       /* don't let the user enter the help-menu from the help screen! */
1926       if (!InHelp) {
1927         InHelp = 1;
1928         mutt_help (MENU_PAGER);
1929         redraw = REDRAW_FULL;
1930         InHelp = 0;
1931       }
1932       else
1933         mutt_error _("Help is currently being shown.");
1934       break;
1935
1936     case OP_PAGER_HIDE_QUOTED:
1937       if (has_types) {
1938         hideQuoted ^= M_HIDE;
1939         if (hideQuoted && lineInfo[topline].type == MT_COLOR_QUOTED)
1940           topline = upNLines (1, lineInfo, topline, hideQuoted);
1941         else
1942           redraw = REDRAW_BODY;
1943       }
1944       break;
1945
1946     case OP_PAGER_SKIP_QUOTED:
1947       if (has_types) {
1948         int dretval = 0;
1949         int new_topline = topline;
1950
1951         while ((new_topline < lastLine ||
1952                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
1953                                                new_topline, &lastLine,
1954                                                &maxLine, M_TYPES, &QuoteList,
1955                                                &q_level, &force_redraw,
1956                                                &SearchRE))))
1957                && lineInfo[new_topline].type != MT_COLOR_QUOTED) {
1958           redraw |= REDRAW_SIDEBAR;
1959           new_topline++;
1960         }
1961
1962         if (dretval < 0) {
1963           mutt_error _("No more quoted text.");
1964
1965           break;
1966         }
1967
1968         while ((new_topline < lastLine ||
1969                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
1970                                                new_topline, &lastLine,
1971                                                &maxLine, M_TYPES, &QuoteList,
1972                                                &q_level, &force_redraw,
1973                                                &SearchRE))))
1974                && lineInfo[new_topline].type == MT_COLOR_QUOTED) {
1975           new_topline++;
1976           redraw |= REDRAW_SIDEBAR;
1977         }
1978
1979         if (dretval < 0) {
1980           mutt_error _("No more unquoted text after quoted text.");
1981
1982           break;
1983         }
1984         topline = new_topline;
1985       }
1986       break;
1987
1988     case OP_PAGER_BOTTOM:      /* move to the end of the file */
1989       if (lineInfo[curline].offset < sb.st_size - 1) {
1990         i = curline;
1991         /* make sure the types are defined to the end of file */
1992         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
1993                              &maxLine, has_types,
1994                              &QuoteList, &q_level, &force_redraw,
1995                              &SearchRE) == 0) {
1996           i++;
1997           redraw |= REDRAW_SIDEBAR;
1998         }
1999         topline = upNLines (bodylen, lineInfo, lastLine, hideQuoted);
2000       }
2001       else
2002         mutt_error _("Bottom of message is shown.");
2003       break;
2004
2005     case OP_REDRAW:
2006       clearok (stdscr, TRUE);
2007       redraw = REDRAW_FULL;
2008       break;
2009
2010     case OP_NULL:
2011       km_error_key (MENU_PAGER);
2012       break;
2013
2014       /* --------------------------------------------------------------------
2015        * The following are operations on the current message rather than
2016        * adjusting the view of the message.
2017        */
2018
2019     case OP_BOUNCE_MESSAGE:
2020       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2021         CHECK_ATTACH;
2022       if (IsMsgAttach (extra))
2023         mutt_attach_bounce (extra->fp, extra->hdr,
2024                             extra->idx, extra->idxlen, extra->bdy);
2025       else
2026         ci_bounce_message (extra->hdr, &redraw);
2027       break;
2028
2029     case OP_RESEND:
2030       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2031         CHECK_ATTACH;
2032       if (IsMsgAttach (extra))
2033         mutt_attach_resend (extra->fp, extra->hdr,
2034                             extra->idx, extra->idxlen, extra->bdy);
2035       else
2036         mutt_resend_message (NULL, extra->ctx, extra->hdr);
2037       redraw = REDRAW_FULL;
2038       break;
2039
2040     case OP_CHECK_TRADITIONAL:
2041       CHECK_MODE (IsHeader (extra));
2042       if (!(extra->hdr->security & PGP_TRADITIONAL_CHECKED)) {
2043         ch = -1;
2044         rc = OP_CHECK_TRADITIONAL;
2045       }
2046       break;
2047
2048     case OP_CREATE_ALIAS:
2049       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2050       if (IsMsgAttach (extra))
2051         mutt_create_alias (extra->bdy->hdr->env, NULL);
2052       else
2053         mutt_create_alias (extra->hdr->env, NULL);
2054       MAYBE_REDRAW (redraw);
2055       break;
2056
2057     case OP_PURGE_MESSAGE:
2058     case OP_DELETE:
2059       CHECK_MODE (IsHeader (extra));
2060       CHECK_READONLY;
2061
2062       CHECK_MX_ACL (Context, ACL_DELETE, _("Deletion"));
2063
2064       mutt_set_flag (Context, extra->hdr, M_DELETE, 1);
2065       mutt_set_flag (Context, extra->hdr, M_PURGED,
2066                      ch != OP_PURGE_MESSAGE ? 0 : 1);
2067       if (option (OPTDELETEUNTAG))
2068         mutt_set_flag (Context, extra->hdr, M_TAG, 0);
2069       redraw = REDRAW_STATUS | REDRAW_INDEX;
2070       if (option (OPTRESOLVE)) {
2071         ch = -1;
2072         rc = OP_MAIN_NEXT_UNDELETED;
2073       }
2074       break;
2075
2076     case OP_DELETE_THREAD:
2077     case OP_DELETE_SUBTHREAD:
2078       CHECK_MODE (IsHeader (extra));
2079       CHECK_READONLY;
2080
2081       CHECK_MX_ACL (Context, ACL_DELETE, _("Deletion"));
2082
2083       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 1,
2084                                 ch == OP_DELETE_THREAD ? 0 : 1);
2085
2086       if (r != -1) {
2087         if (option (OPTDELETEUNTAG))
2088           mutt_thread_set_flag (extra->hdr, M_TAG, 0,
2089                                 ch == OP_DELETE_THREAD ? 0 : 1);
2090         if (option (OPTRESOLVE)) {
2091           rc = OP_MAIN_NEXT_UNDELETED;
2092           ch = -1;
2093         }
2094
2095         if (!option (OPTRESOLVE) && PagerIndexLines)
2096           redraw = REDRAW_FULL;
2097         else
2098           redraw = REDRAW_STATUS | REDRAW_INDEX;
2099       }
2100       break;
2101
2102     case OP_DISPLAY_ADDRESS:
2103       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2104       if (IsMsgAttach (extra))
2105         mutt_display_address (extra->bdy->hdr->env);
2106       else
2107         mutt_display_address (extra->hdr->env);
2108       break;
2109
2110     case OP_ENTER_COMMAND:
2111       old_smart_wrap = option (OPTWRAP);
2112       old_markers = option (OPTMARKERS);
2113       old_PagerIndexLines = PagerIndexLines;
2114
2115       CurrentMenu = MENU_PAGER;
2116       mutt_enter_command ();
2117
2118       if (option (OPTNEEDRESORT)) {
2119         unset_option (OPTNEEDRESORT);
2120         CHECK_MODE (IsHeader (extra));
2121         set_option (OPTNEEDRESORT);
2122       }
2123
2124       if (old_PagerIndexLines != PagerIndexLines) {
2125         if (pager_index)
2126           mutt_menuDestroy (&pager_index);
2127         pager_index = NULL;
2128       }
2129
2130       if (option (OPTWRAP) != old_smart_wrap ||
2131           option (OPTMARKERS) != old_markers) {
2132         if (flags & M_PAGER_RETWINCH) {
2133           ch = -1;
2134           rc = OP_REFORMAT_WINCH;
2135           continue;
2136         }
2137
2138         /* count the real lines above */
2139         j = 0;
2140         for (i = 0; i <= topline; i++) {
2141           if (!lineInfo[i].continuation)
2142             j++;
2143         }
2144
2145         /* we need to restart the whole thing */
2146         for (i = 0; i < maxLine; i++) {
2147           lineInfo[i].offset = 0;
2148           lineInfo[i].type = -1;
2149           lineInfo[i].continuation = 0;
2150           lineInfo[i].chunks = 0;
2151           lineInfo[i].search_cnt = -1;
2152           lineInfo[i].quote = NULL;
2153
2154           p_realloc(&(lineInfo[i].syntax), 1);
2155           if (SearchCompiled && lineInfo[i].search)
2156             p_delete(&(lineInfo[i].search));
2157         }
2158
2159         if (SearchCompiled) {
2160           regfree (&SearchRE);
2161           SearchCompiled = 0;
2162         }
2163         SearchFlag = 0;
2164
2165         /* try to keep the old position */
2166         topline = 0;
2167         lastLine = 0;
2168         while (j > 0 && display_line (fp, &last_pos, &lineInfo, topline,
2169                                       &lastLine, &maxLine,
2170                                       (has_types ? M_TYPES : 0),
2171                                       &QuoteList, &q_level, &force_redraw,
2172                                       &SearchRE) == 0) {
2173           redraw |= REDRAW_SIDEBAR;
2174           if (!lineInfo[topline].continuation)
2175             j--;
2176           if (j > 0)
2177             topline++;
2178         }
2179
2180         ch = 0;
2181       }
2182
2183       if (option (OPTFORCEREDRAWPAGER))
2184         redraw = REDRAW_FULL;
2185       unset_option (OPTFORCEREDRAWINDEX);
2186       unset_option (OPTFORCEREDRAWPAGER);
2187       break;
2188
2189     case OP_FLAG_MESSAGE:
2190       CHECK_MODE (IsHeader (extra));
2191       CHECK_READONLY;
2192
2193       CHECK_MX_ACL (Context, ACL_WRITE, _("Flagging"));
2194
2195       mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged);
2196       redraw = REDRAW_STATUS | REDRAW_INDEX;
2197       if (option (OPTRESOLVE)) {
2198         ch = -1;
2199         rc = OP_MAIN_NEXT_UNDELETED;
2200       }
2201       break;
2202
2203     case OP_PIPE:
2204       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2205       if (IsAttach (extra))
2206         mutt_pipe_attachment_list (extra->fp, 0, extra->bdy, 0);
2207       else
2208         mutt_pipe_message (extra->hdr);
2209       MAYBE_REDRAW (redraw);
2210       break;
2211
2212     case OP_PRINT:
2213       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2214       if (IsAttach (extra))
2215         mutt_print_attachment_list (extra->fp, 0, extra->bdy);
2216       else
2217         mutt_print_message (extra->hdr);
2218       break;
2219
2220     case OP_MAIL:
2221       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2222       CHECK_ATTACH;
2223       ci_send_message (0, NULL, NULL, extra->ctx, NULL);
2224       redraw = REDRAW_FULL;
2225       break;
2226
2227 #ifdef USE_NNTP
2228     case OP_POST:
2229       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2230       CHECK_ATTACH;
2231       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2232           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2233           query_quadoption (OPT_TOMODERATED,
2234                             _
2235                             ("Posting to this group not allowed, may be moderated. Continue?"))
2236           != M_YES)
2237         break;
2238       ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL);
2239       redraw = REDRAW_FULL;
2240       break;
2241
2242     case OP_FORWARD_TO_GROUP:
2243       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2244       CHECK_ATTACH;
2245       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2246           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2247           query_quadoption (OPT_TOMODERATED,
2248                             _
2249                             ("Posting to this group not allowed, may be moderated. Continue?"))
2250           != M_YES)
2251         break;
2252       if (IsMsgAttach (extra))
2253         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2254                              extra->idxlen, extra->bdy, SENDNEWS);
2255       else
2256         ci_send_message (SENDNEWS | SENDFORWARD, NULL, NULL, extra->ctx,
2257                          extra->hdr);
2258       redraw = REDRAW_FULL;
2259       break;
2260
2261     case OP_FOLLOWUP:
2262       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2263       CHECK_ATTACH;
2264
2265       if (IsMsgAttach (extra))
2266         followup_to = extra->bdy->hdr->env->followup_to;
2267       else
2268         followup_to = extra->hdr->env->followup_to;
2269
2270       if (!followup_to || m_strcasecmp(followup_to, "poster") ||
2271           query_quadoption (OPT_FOLLOWUPTOPOSTER,
2272                             _("Reply by mail as poster prefers?")) != M_YES) {
2273         if (extra->ctx && extra->ctx->magic == M_NNTP
2274             && !((NNTP_DATA *) extra->ctx->data)->allowed
2275             && query_quadoption (OPT_TOMODERATED,
2276                                  _
2277                                  ("Posting to this group not allowed, may be moderated. Continue?"))
2278             != M_YES)
2279           break;
2280         if (IsMsgAttach (extra))
2281           mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2282                              extra->idxlen, extra->bdy, SENDNEWS | SENDREPLY);
2283         else
2284           ci_send_message (SENDNEWS | SENDREPLY, NULL, NULL,
2285                            extra->ctx, extra->hdr);
2286         redraw = REDRAW_FULL;
2287         break;
2288       }
2289 #endif
2290
2291     case OP_REPLY:
2292       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2293       CHECK_ATTACH;
2294       if (IsMsgAttach (extra))
2295         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2296                            extra->idxlen, extra->bdy, SENDREPLY);
2297       else
2298         ci_send_message (SENDREPLY, NULL, NULL, extra->ctx, extra->hdr);
2299       redraw = REDRAW_FULL;
2300       break;
2301
2302     case OP_RECALL_MESSAGE:
2303       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2304       CHECK_ATTACH;
2305       ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr);
2306       redraw = REDRAW_FULL;
2307       break;
2308
2309     case OP_GROUP_REPLY:
2310       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2311       CHECK_ATTACH;
2312       if (IsMsgAttach (extra))
2313         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2314                            extra->idxlen, extra->bdy,
2315                            SENDREPLY | SENDGROUPREPLY);
2316       else
2317         ci_send_message (SENDREPLY | SENDGROUPREPLY, NULL, NULL, extra->ctx,
2318                          extra->hdr);
2319       redraw = REDRAW_FULL;
2320       break;
2321
2322     case OP_LIST_REPLY:
2323       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2324       CHECK_ATTACH;
2325       if (IsMsgAttach (extra))
2326         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2327                            extra->idxlen, extra->bdy,
2328                            SENDREPLY | SENDLISTREPLY);
2329       else
2330         ci_send_message (SENDREPLY | SENDLISTREPLY, NULL, NULL, extra->ctx,
2331                          extra->hdr);
2332       redraw = REDRAW_FULL;
2333       break;
2334
2335     case OP_FORWARD_MESSAGE:
2336       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2337       CHECK_ATTACH;
2338       if (IsMsgAttach (extra))
2339         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2340                              extra->idxlen, extra->bdy, 0);
2341       else
2342         ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
2343       redraw = REDRAW_FULL;
2344       break;
2345
2346     case OP_DECRYPT_SAVE:
2347     case OP_SAVE:
2348       if (IsAttach (extra)) {
2349         mutt_save_attachment_list (extra->fp, 0, extra->bdy, extra->hdr,
2350                                    NULL);
2351         break;
2352       }
2353       /* fall through */
2354     case OP_COPY_MESSAGE:
2355     case OP_DECODE_SAVE:
2356     case OP_DECODE_COPY:
2357     case OP_DECRYPT_COPY:
2358       CHECK_MODE (IsHeader (extra));
2359       if (mutt_save_message (extra->hdr,
2360                              (ch == OP_DECRYPT_SAVE) ||
2361                              (ch == OP_SAVE) || (ch == OP_DECODE_SAVE),
2362                              (ch == OP_DECODE_SAVE) || (ch == OP_DECODE_COPY),
2363                              (ch == OP_DECRYPT_SAVE)
2364                              || (ch == OP_DECRYPT_COPY) || 0, &redraw) == 0
2365           && (ch == OP_SAVE || ch == OP_DECODE_SAVE
2366               || ch == OP_DECRYPT_SAVE)) {
2367         if (option (OPTRESOLVE)) {
2368           ch = -1;
2369           rc = OP_MAIN_NEXT_UNDELETED;
2370         }
2371         else
2372           redraw |= REDRAW_STATUS | REDRAW_INDEX;
2373       }
2374       MAYBE_REDRAW (redraw);
2375       break;
2376
2377     case OP_SHELL_ESCAPE:
2378       mutt_shell_escape ();
2379       MAYBE_REDRAW (redraw);
2380       break;
2381
2382     case OP_TAG:
2383       CHECK_MODE (IsHeader (extra));
2384       mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
2385
2386       Context->last_tag = extra->hdr->tagged ? extra->hdr :
2387         ((Context->last_tag == extra->hdr && !extra->hdr->tagged)
2388          ? NULL : Context->last_tag);
2389
2390       redraw = REDRAW_STATUS | REDRAW_INDEX;
2391       if (option (OPTRESOLVE)) {
2392         ch = -1;
2393         rc = OP_NEXT_ENTRY;
2394       }
2395       break;
2396
2397     case OP_TOGGLE_NEW:
2398       CHECK_MODE (IsHeader (extra));
2399       CHECK_READONLY;
2400
2401       CHECK_MX_ACL (Context, ACL_SEEN, _("Toggling"));
2402
2403       if (extra->hdr->read || extra->hdr->old)
2404         mutt_set_flag (Context, extra->hdr, M_NEW, 1);
2405       else if (!first)
2406         mutt_set_flag (Context, extra->hdr, M_READ, 1);
2407       first = 0;
2408       Context->msgnotreadyet = -1;
2409       redraw = REDRAW_STATUS | REDRAW_INDEX;
2410       if (option (OPTRESOLVE)) {
2411         ch = -1;
2412         rc = OP_MAIN_NEXT_UNDELETED;
2413       }
2414       break;
2415
2416     case OP_UNDELETE:
2417       CHECK_MODE (IsHeader (extra));
2418       CHECK_READONLY;
2419
2420       CHECK_MX_ACL (Context, ACL_DELETE, _("Undeletion"));
2421
2422       mutt_set_flag (Context, extra->hdr, M_DELETE, 0);
2423       mutt_set_flag (Context, extra->hdr, M_PURGED, 0);
2424       redraw = REDRAW_STATUS | REDRAW_INDEX;
2425       if (option (OPTRESOLVE)) {
2426         ch = -1;
2427         rc = OP_NEXT_ENTRY;
2428       }
2429       break;
2430
2431     case OP_UNDELETE_THREAD:
2432     case OP_UNDELETE_SUBTHREAD:
2433       CHECK_MODE (IsHeader (extra));
2434       CHECK_READONLY;
2435
2436       CHECK_MX_ACL (Context, ACL_DELETE, _("Undeletion"));
2437
2438       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0,
2439                                 ch == OP_UNDELETE_THREAD ? 0 : 1)
2440         + mutt_thread_set_flag (extra->hdr, M_PURGED, 0,
2441                                 ch == OP_UNDELETE_THREAD ? 0 : 1);
2442
2443       if (r > -1) {
2444         if (option (OPTRESOLVE)) {
2445           rc = (ch == OP_DELETE_THREAD) ?
2446             OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
2447           ch = -1;
2448         }
2449
2450         if (!option (OPTRESOLVE) && PagerIndexLines)
2451           redraw = REDRAW_FULL;
2452         else
2453           redraw = REDRAW_STATUS | REDRAW_INDEX;
2454       }
2455       break;
2456
2457     case OP_VERSION:
2458       mutt_version ();
2459       break;
2460
2461     case OP_BUFFY_LIST:
2462       if (option (OPTFORCEBUFFYCHECK))
2463         buffy_check (1);
2464       buffy_list ();
2465       redraw |= REDRAW_SIDEBAR;
2466       break;
2467
2468     case OP_VIEW_ATTACHMENTS:
2469       if (flags & M_PAGER_ATTACHMENT) {
2470         ch = -1;
2471         rc = OP_ATTACH_COLLAPSE;
2472         break;
2473       }
2474       CHECK_MODE (IsHeader (extra));
2475       mutt_view_attachments (extra->hdr);
2476       if (extra->hdr->attach_del)
2477         Context->changed = 1;
2478       redraw = REDRAW_FULL;
2479       break;
2480
2481
2482     case OP_MAIL_KEY:
2483       CHECK_MODE (IsHeader (extra));
2484       CHECK_ATTACH;
2485       ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr);
2486       redraw = REDRAW_FULL;
2487       break;
2488
2489
2490     case OP_FORGET_PASSPHRASE:
2491       crypt_forget_passphrase ();
2492       break;
2493
2494     case OP_EXTRACT_KEYS:
2495       CHECK_MODE (IsHeader (extra));
2496       crypt_extract_keys_from_messages (extra->hdr);
2497       redraw = REDRAW_FULL;
2498       break;
2499
2500     case OP_SIDEBAR_SCROLL_UP:
2501     case OP_SIDEBAR_SCROLL_DOWN:
2502     case OP_SIDEBAR_NEXT:
2503     case OP_SIDEBAR_NEXT_NEW:
2504     case OP_SIDEBAR_PREV:
2505     case OP_SIDEBAR_PREV_NEW:
2506       sidebar_scroll (ch);
2507       break;
2508     default:
2509       ch = -1;
2510       break;
2511     }
2512   }
2513
2514   m_fclose(&fp);
2515   if (IsHeader (extra)) {
2516     Context->msgnotreadyet = -1;
2517     if (rc == -1)
2518       OldHdr = NULL;
2519     else {
2520       TopLine = topline;
2521       OldHdr = extra->hdr;
2522     }
2523   }
2524
2525   cleanup_quote (&QuoteList);
2526
2527   for (i = 0; i < maxLine; i++) {
2528     p_delete(&(lineInfo[i].syntax));
2529     if (SearchCompiled && lineInfo[i].search)
2530       p_delete(&(lineInfo[i].search));
2531   }
2532   if (SearchCompiled) {
2533     regfree (&SearchRE);
2534     SearchCompiled = 0;
2535   }
2536   p_delete(&lineInfo);
2537   if (pager_index)
2538     mutt_menuDestroy (&pager_index);
2539   return (rc != -1 ? rc : 0);
2540 }