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