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 "keymap.h"
21 #include "mutt_menu.h"
22 #include "mapping.h"
23 #include "sort.h"
24 #include "pager.h"
25 #include "attach.h"
26 #include "mbyte.h"
27 #include "sidebar.h"
28
29 #include "mx.h"
30
31 #ifdef USE_IMAP
32 #include "imap_private.h"
33 #endif
34
35 #include "mutt_crypt.h"
36
37 #include "lib/mem.h"
38 #include "lib/intl.h"
39 #include "lib/str.h"
40 #include "lib/rx.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 (safe_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           && safe_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 (safe_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                 && safe_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 (safe_strncmp ("\033[0m", raw, 4) == 0)       /* a little hack... */
685     lineInfo[n].type = MT_COLOR_NORMAL;
686 #if 0
687   else if (safe_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 (safe_strcmp ("-- \n", buf) == 0
694            || safe_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       if (option(OPTSTATUSONTOP)) {
1634         move(0,0);
1635       }
1636       /*move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (InHelp?(LINES-2):(indexlen - 1))),
1637             option (OPTSTATUSONTOP) ? 0 : SidebarWidth);*/
1638       mutt_paddstr (COLS - 10 - (option(OPTSTATUSONTOP)?0:SidebarWidth), IsHeader (extra)
1639                     || IsMsgAttach (extra) ? buffer : banner);
1640
1641       addstr (" -- (");
1642       if (last_pos < sb.st_size - 1)
1643         printw ("%d%%)", (int) (100 * last_offset / sb.st_size));
1644       else
1645         addstr (topline == 0 ? "all)" : "end)");
1646       BKGDSET (MT_COLOR_NORMAL);
1647       SETCOLOR (MT_COLOR_NORMAL);
1648     }
1649
1650     if (redraw & REDRAW_SIDEBAR)
1651       sidebar_draw (MENU_PAGER);
1652
1653     if ((redraw & REDRAW_INDEX) && index) {
1654       /* redraw the pager_index indicator, because the
1655        * flags for this message might have changed. */
1656       menu_redraw_current (index);
1657       sidebar_draw (MENU_PAGER);
1658       /* print out the index status bar */
1659       menu_status_line (buffer, sizeof (buffer), index, NONULL (Status));
1660       move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)),
1661             option (OPTSTATUSONTOP) ? 0 : SidebarWidth);
1662       SETCOLOR (MT_COLOR_STATUS);
1663       mutt_paddstr (COLS - (option (OPTSTATUSONTOP) ? 0 : SidebarWidth),
1664                     buffer);
1665       SETCOLOR (MT_COLOR_NORMAL);
1666     }
1667     /* if we're not using the index, update every time */
1668     if (index == 0)
1669       sidebar_draw (MENU_PAGER);
1670
1671     redraw = 0;
1672
1673     move (statusoffset, COLS - 1);
1674     mutt_refresh ();
1675     ch = km_dokey (MENU_PAGER);
1676     if (ch != -1)
1677       mutt_clear_error ();
1678     mutt_curs_set (1);
1679
1680     if (SigInt) {
1681       mutt_query_exit ();
1682       continue;
1683     }
1684 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1685     else if (SigWinch) {
1686       mutt_resize_screen ();
1687
1688       /* Store current position. */
1689       lines = -1;
1690       for (i = 0; i <= topline; i++)
1691         if (!lineInfo[i].continuation)
1692           lines++;
1693
1694       if (flags & M_PAGER_RETWINCH) {
1695         Resize = safe_malloc (sizeof (struct resize));
1696
1697         Resize->line = lines;
1698         Resize->SearchCompiled = SearchCompiled;
1699         Resize->SearchBack = SearchBack;
1700
1701         ch = -1;
1702         rc = OP_REFORMAT_WINCH;
1703       }
1704       else {
1705         for (i = 0; i < maxLine; i++) {
1706           lineInfo[i].offset = 0;
1707           lineInfo[i].type = -1;
1708           lineInfo[i].continuation = 0;
1709           lineInfo[i].chunks = 0;
1710           lineInfo[i].search_cnt = -1;
1711           lineInfo[i].quote = NULL;
1712
1713           safe_realloc (&(lineInfo[i].syntax), sizeof (struct syntax_t));
1714           if (SearchCompiled && lineInfo[i].search)
1715             FREE (&(lineInfo[i].search));
1716         }
1717
1718         lastLine = 0;
1719         topline = 0;
1720
1721         redraw = REDRAW_FULL | REDRAW_SIGWINCH;
1722         ch = 0;
1723       }
1724
1725       SigWinch = 0;
1726       clearok (stdscr, TRUE);   /*force complete redraw */
1727       continue;
1728     }
1729 #endif
1730     else if (ch == -1) {
1731       ch = 0;
1732       continue;
1733     }
1734
1735     rc = ch;
1736
1737     switch (ch) {
1738     case OP_EXIT:
1739       rc = -1;
1740       ch = -1;
1741       break;
1742
1743     case OP_NEXT_PAGE:
1744       if (lineInfo[curline].offset < sb.st_size - 1) {
1745         topline = upNLines (PagerContext, lineInfo, curline, hideQuoted);
1746       }
1747       else if (option (OPTPAGERSTOP)) {
1748         /* emulate "less -q" and don't go on to the next message. */
1749         mutt_error _("Bottom of message is shown.");
1750       }
1751       else {
1752         /* end of the current message, so display the next message. */
1753         rc = OP_MAIN_NEXT_UNDELETED;
1754         ch = -1;
1755       }
1756       break;
1757
1758     case OP_PREV_PAGE:
1759       if (topline != 0) {
1760         topline =
1761           upNLines (bodylen - PagerContext, lineInfo, topline, hideQuoted);
1762       }
1763       else
1764         mutt_error _("Top of message is shown.");
1765       break;
1766
1767     case OP_NEXT_LINE:
1768       if (lineInfo[curline].offset < sb.st_size - 1) {
1769         topline++;
1770         if (hideQuoted) {
1771           while (lineInfo[topline].type == MT_COLOR_QUOTED &&
1772                  topline < lastLine)
1773             topline++;
1774         }
1775       }
1776       else
1777         mutt_error _("Bottom of message is shown.");
1778       break;
1779
1780     case OP_PREV_LINE:
1781       if (topline)
1782         topline = upNLines (1, lineInfo, topline, hideQuoted);
1783       else
1784         mutt_error _("Top of message is shown.");
1785       break;
1786
1787     case OP_PAGER_TOP:
1788       if (topline)
1789         topline = 0;
1790       else
1791         mutt_error _("Top of message is shown.");
1792       break;
1793
1794     case OP_HALF_UP:
1795       if (topline)
1796         topline = upNLines (bodylen / 2, lineInfo, topline, hideQuoted);
1797       else
1798         mutt_error _("Top of message is shown.");
1799       break;
1800
1801     case OP_HALF_DOWN:
1802       if (lineInfo[curline].offset < sb.st_size - 1) {
1803         topline = upNLines (bodylen / 2, lineInfo, curline, hideQuoted);
1804       }
1805       else if (option (OPTPAGERSTOP)) {
1806         /* emulate "less -q" and don't go on to the next message. */
1807         mutt_error _("Bottom of message is shown.");
1808       }
1809       else {
1810         /* end of the current message, so display the next message. */
1811         rc = OP_MAIN_NEXT_UNDELETED;
1812         ch = -1;
1813       }
1814       break;
1815
1816     case OP_SEARCH_NEXT:
1817     case OP_SEARCH_OPPOSITE:
1818       if (SearchCompiled) {
1819       search_next:
1820         if ((!SearchBack && ch == OP_SEARCH_NEXT) ||
1821             (SearchBack && ch == OP_SEARCH_OPPOSITE)) {
1822           /* searching forward */
1823           for (i = topline + 1; i < lastLine; i++) {
1824             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1825                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1826               break;
1827           }
1828
1829           if (i < lastLine)
1830             topline = i;
1831           else
1832             mutt_error _("Not found.");
1833         }
1834         else {
1835           /* searching backward */
1836           for (i = topline - 1; i >= 0; i--) {
1837             if ((!hideQuoted || (has_types &&
1838                                  lineInfo[i].type != MT_COLOR_QUOTED)) &&
1839                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1840               break;
1841           }
1842
1843           if (i >= 0)
1844             topline = i;
1845           else
1846             mutt_error _("Not found.");
1847         }
1848
1849         if (lineInfo[topline].search_cnt > 0)
1850           SearchFlag = M_SEARCH;
1851
1852         break;
1853       }
1854       /* no previous search pattern, so fall through to search */
1855
1856     case OP_SEARCH:
1857     case OP_SEARCH_REVERSE:
1858       strfcpy (buffer, searchbuf, sizeof (buffer));
1859       if (mutt_get_field ((SearchBack ? _("Reverse search: ") :
1860                            _("Search: ")), buffer, sizeof (buffer),
1861                           M_CLEAR) != 0)
1862         break;
1863
1864       if (!strcmp (buffer, searchbuf)) {
1865         if (SearchCompiled) {
1866           /* do an implicit search-next */
1867           if (ch == OP_SEARCH)
1868             ch = OP_SEARCH_NEXT;
1869           else
1870             ch = OP_SEARCH_OPPOSITE;
1871
1872           goto search_next;
1873         }
1874       }
1875
1876       if (!buffer[0])
1877         break;
1878
1879       strfcpy (searchbuf, buffer, sizeof (searchbuf));
1880
1881       /* leave SearchBack alone if ch == OP_SEARCH_NEXT */
1882       if (ch == OP_SEARCH)
1883         SearchBack = 0;
1884       else if (ch == OP_SEARCH_REVERSE)
1885         SearchBack = 1;
1886
1887       if (SearchCompiled) {
1888         regfree (&SearchRE);
1889         for (i = 0; i < lastLine; i++) {
1890           if (lineInfo[i].search)
1891             FREE (&(lineInfo[i].search));
1892           lineInfo[i].search_cnt = -1;
1893         }
1894       }
1895
1896       if ((err =
1897            REGCOMP (&SearchRE, searchbuf,
1898                     REG_NEWLINE | mutt_which_case (searchbuf))) != 0) {
1899         regerror (err, &SearchRE, buffer, sizeof (buffer));
1900         mutt_error ("%s", buffer);
1901         regfree (&SearchRE);
1902         for (i = 0; i < maxLine; i++) {
1903           /* cleanup */
1904           if (lineInfo[i].search)
1905             FREE (&(lineInfo[i].search));
1906           lineInfo[i].search_cnt = -1;
1907         }
1908         SearchFlag = 0;
1909         SearchCompiled = 0;
1910       }
1911       else {
1912         SearchCompiled = 1;
1913         /* update the search pointers */
1914         i = 0;
1915         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
1916                              &maxLine, M_SEARCH | (flags & M_PAGER_NSKIP),
1917                              &QuoteList, &q_level,
1918                              &force_redraw, &SearchRE) == 0) {
1919           i++;
1920           redraw |= REDRAW_SIDEBAR;
1921         }
1922
1923         if (!SearchBack) {
1924           /* searching forward */
1925           for (i = topline; i < lastLine; i++) {
1926             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1927                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1928               break;
1929           }
1930
1931           if (i < lastLine)
1932             topline = i;
1933         }
1934         else {
1935           /* searching backward */
1936           for (i = topline; i >= 0; i--) {
1937             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1938                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1939               break;
1940           }
1941
1942           if (i >= 0)
1943             topline = i;
1944         }
1945
1946         if (lineInfo[topline].search_cnt == 0) {
1947           SearchFlag = 0;
1948           mutt_error _("Not found.");
1949         }
1950         else
1951           SearchFlag = M_SEARCH;
1952       }
1953       redraw = REDRAW_BODY;
1954       break;
1955
1956     case OP_SEARCH_TOGGLE:
1957       if (SearchCompiled) {
1958         SearchFlag ^= M_SEARCH;
1959         redraw = REDRAW_BODY;
1960       }
1961       break;
1962
1963     case OP_HELP:
1964       /* don't let the user enter the help-menu from the help screen! */
1965       if (!InHelp) {
1966         InHelp = 1;
1967         mutt_help (MENU_PAGER);
1968         redraw = REDRAW_FULL;
1969         InHelp = 0;
1970       }
1971       else
1972         mutt_error _("Help is currently being shown.");
1973       break;
1974
1975     case OP_PAGER_HIDE_QUOTED:
1976       if (has_types) {
1977         hideQuoted ^= M_HIDE;
1978         if (hideQuoted && lineInfo[topline].type == MT_COLOR_QUOTED)
1979           topline = upNLines (1, lineInfo, topline, hideQuoted);
1980         else
1981           redraw = REDRAW_BODY;
1982       }
1983       break;
1984
1985     case OP_PAGER_SKIP_QUOTED:
1986       if (has_types) {
1987         int dretval = 0;
1988         int new_topline = topline;
1989
1990         while ((new_topline < lastLine ||
1991                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
1992                                                new_topline, &lastLine,
1993                                                &maxLine, M_TYPES, &QuoteList,
1994                                                &q_level, &force_redraw,
1995                                                &SearchRE))))
1996                && lineInfo[new_topline].type != MT_COLOR_QUOTED) {
1997           redraw |= REDRAW_SIDEBAR;
1998           new_topline++;
1999         }
2000
2001         if (dretval < 0) {
2002           mutt_error _("No more quoted text.");
2003
2004           break;
2005         }
2006
2007         while ((new_topline < lastLine ||
2008                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
2009                                                new_topline, &lastLine,
2010                                                &maxLine, M_TYPES, &QuoteList,
2011                                                &q_level, &force_redraw,
2012                                                &SearchRE))))
2013                && lineInfo[new_topline].type == MT_COLOR_QUOTED) {
2014           new_topline++;
2015           redraw |= REDRAW_SIDEBAR;
2016         }
2017
2018         if (dretval < 0) {
2019           mutt_error _("No more unquoted text after quoted text.");
2020
2021           break;
2022         }
2023         topline = new_topline;
2024       }
2025       break;
2026
2027     case OP_PAGER_BOTTOM:      /* move to the end of the file */
2028       if (lineInfo[curline].offset < sb.st_size - 1) {
2029         i = curline;
2030         /* make sure the types are defined to the end of file */
2031         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
2032                              &maxLine, has_types,
2033                              &QuoteList, &q_level, &force_redraw,
2034                              &SearchRE) == 0) {
2035           i++;
2036           redraw |= REDRAW_SIDEBAR;
2037         }
2038         topline = upNLines (bodylen, lineInfo, lastLine, hideQuoted);
2039       }
2040       else
2041         mutt_error _("Bottom of message is shown.");
2042       break;
2043
2044     case OP_REDRAW:
2045       clearok (stdscr, TRUE);
2046       redraw = REDRAW_FULL;
2047       break;
2048
2049     case OP_NULL:
2050       km_error_key (MENU_PAGER);
2051       break;
2052
2053       /* --------------------------------------------------------------------
2054        * The following are operations on the current message rather than
2055        * adjusting the view of the message.
2056        */
2057
2058     case OP_BOUNCE_MESSAGE:
2059       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2060         CHECK_ATTACH;
2061       if (IsMsgAttach (extra))
2062         mutt_attach_bounce (extra->fp, extra->hdr,
2063                             extra->idx, extra->idxlen, extra->bdy);
2064       else
2065         ci_bounce_message (extra->hdr, &redraw);
2066       break;
2067
2068     case OP_RESEND:
2069       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2070         CHECK_ATTACH;
2071       if (IsMsgAttach (extra))
2072         mutt_attach_resend (extra->fp, extra->hdr,
2073                             extra->idx, extra->idxlen, extra->bdy);
2074       else
2075         mutt_resend_message (NULL, extra->ctx, extra->hdr);
2076       redraw = REDRAW_FULL;
2077       break;
2078
2079     case OP_CHECK_TRADITIONAL:
2080       CHECK_MODE (IsHeader (extra));
2081       if (!(WithCrypto & APPLICATION_PGP))
2082         break;
2083       if (!(extra->hdr->security & PGP_TRADITIONAL_CHECKED)) {
2084         ch = -1;
2085         rc = OP_CHECK_TRADITIONAL;
2086       }
2087       break;
2088
2089     case OP_CREATE_ALIAS:
2090       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2091       if (IsMsgAttach (extra))
2092         mutt_create_alias (extra->bdy->hdr->env, NULL);
2093       else
2094         mutt_create_alias (extra->hdr->env, NULL);
2095       MAYBE_REDRAW (redraw);
2096       break;
2097
2098     case OP_PURGE_MESSAGE:
2099     case OP_DELETE:
2100       CHECK_MODE (IsHeader (extra));
2101       CHECK_READONLY;
2102
2103 #ifdef USE_IMAP
2104       CHECK_IMAP_ACL (IMAP_ACL_DELETE);
2105 #endif
2106
2107       mutt_set_flag (Context, extra->hdr, M_DELETE, 1);
2108       mutt_set_flag (Context, extra->hdr, M_PURGED,
2109                      ch != OP_PURGE_MESSAGE ? 0 : 1);
2110       if (option (OPTDELETEUNTAG))
2111         mutt_set_flag (Context, extra->hdr, M_TAG, 0);
2112       redraw = REDRAW_STATUS | REDRAW_INDEX;
2113       if (option (OPTRESOLVE)) {
2114         ch = -1;
2115         rc = OP_MAIN_NEXT_UNDELETED;
2116       }
2117       break;
2118
2119     case OP_DELETE_THREAD:
2120     case OP_DELETE_SUBTHREAD:
2121       CHECK_MODE (IsHeader (extra));
2122       CHECK_READONLY;
2123
2124 #ifdef USE_IMAP
2125       CHECK_IMAP_ACL (IMAP_ACL_DELETE);
2126 #endif
2127
2128       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 1,
2129                                 ch == OP_DELETE_THREAD ? 0 : 1);
2130
2131       if (r != -1) {
2132         if (option (OPTDELETEUNTAG))
2133           mutt_thread_set_flag (extra->hdr, M_TAG, 0,
2134                                 ch == OP_DELETE_THREAD ? 0 : 1);
2135         if (option (OPTRESOLVE)) {
2136           rc = OP_MAIN_NEXT_UNDELETED;
2137           ch = -1;
2138         }
2139
2140         if (!option (OPTRESOLVE) && PagerIndexLines)
2141           redraw = REDRAW_FULL;
2142         else
2143           redraw = REDRAW_STATUS | REDRAW_INDEX;
2144       }
2145       break;
2146
2147     case OP_DISPLAY_ADDRESS:
2148       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2149       if (IsMsgAttach (extra))
2150         mutt_display_address (extra->bdy->hdr->env);
2151       else
2152         mutt_display_address (extra->hdr->env);
2153       break;
2154
2155     case OP_ENTER_COMMAND:
2156       old_smart_wrap = option (OPTWRAP);
2157       old_markers = option (OPTMARKERS);
2158       old_PagerIndexLines = PagerIndexLines;
2159
2160       CurrentMenu = MENU_PAGER;
2161       mutt_enter_command ();
2162
2163       if (option (OPTNEEDRESORT)) {
2164         unset_option (OPTNEEDRESORT);
2165         CHECK_MODE (IsHeader (extra));
2166         set_option (OPTNEEDRESORT);
2167       }
2168
2169       if (old_PagerIndexLines != PagerIndexLines) {
2170         if (index)
2171           mutt_menuDestroy (&index);
2172         index = NULL;
2173       }
2174
2175       if (option (OPTWRAP) != old_smart_wrap ||
2176           option (OPTMARKERS) != old_markers) {
2177         if (flags & M_PAGER_RETWINCH) {
2178           ch = -1;
2179           rc = OP_REFORMAT_WINCH;
2180           continue;
2181         }
2182
2183         /* count the real lines above */
2184         j = 0;
2185         for (i = 0; i <= topline; i++) {
2186           if (!lineInfo[i].continuation)
2187             j++;
2188         }
2189
2190         /* we need to restart the whole thing */
2191         for (i = 0; i < maxLine; i++) {
2192           lineInfo[i].offset = 0;
2193           lineInfo[i].type = -1;
2194           lineInfo[i].continuation = 0;
2195           lineInfo[i].chunks = 0;
2196           lineInfo[i].search_cnt = -1;
2197           lineInfo[i].quote = NULL;
2198
2199           safe_realloc (&(lineInfo[i].syntax), sizeof (struct syntax_t));
2200           if (SearchCompiled && lineInfo[i].search)
2201             FREE (&(lineInfo[i].search));
2202         }
2203
2204         if (SearchCompiled) {
2205           regfree (&SearchRE);
2206           SearchCompiled = 0;
2207         }
2208         SearchFlag = 0;
2209
2210         /* try to keep the old position */
2211         topline = 0;
2212         lastLine = 0;
2213         while (j > 0 && display_line (fp, &last_pos, &lineInfo, topline,
2214                                       &lastLine, &maxLine,
2215                                       (has_types ? M_TYPES : 0),
2216                                       &QuoteList, &q_level, &force_redraw,
2217                                       &SearchRE) == 0) {
2218           redraw |= REDRAW_SIDEBAR;
2219           if (!lineInfo[topline].continuation)
2220             j--;
2221           if (j > 0)
2222             topline++;
2223         }
2224
2225         ch = 0;
2226       }
2227
2228       if (option (OPTFORCEREDRAWPAGER))
2229         redraw = REDRAW_FULL;
2230       unset_option (OPTFORCEREDRAWINDEX);
2231       unset_option (OPTFORCEREDRAWPAGER);
2232       break;
2233
2234     case OP_FLAG_MESSAGE:
2235       CHECK_MODE (IsHeader (extra));
2236       CHECK_READONLY;
2237
2238 #ifdef USE_POP
2239       if (Context->magic == M_POP) {
2240         mutt_flushinp ();
2241         mutt_error _("Can't change 'important' flag on POP server.");
2242
2243         break;
2244       }
2245 #endif
2246
2247 #ifdef USE_IMAP
2248       CHECK_IMAP_ACL (IMAP_ACL_WRITE);
2249 #endif
2250
2251 #ifdef USE_NNTP
2252       if (Context->magic == M_NNTP) {
2253         mutt_flushinp ();
2254         mutt_error _("Can't change 'important' flag on NNTP server.");
2255
2256         break;
2257       }
2258 #endif
2259
2260       mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged);
2261       redraw = REDRAW_STATUS | REDRAW_INDEX;
2262       if (option (OPTRESOLVE)) {
2263         ch = -1;
2264         rc = OP_MAIN_NEXT_UNDELETED;
2265       }
2266       break;
2267
2268     case OP_PIPE:
2269       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2270       if (IsAttach (extra))
2271         mutt_pipe_attachment_list (extra->fp, 0, extra->bdy, 0);
2272       else
2273         mutt_pipe_message (extra->hdr);
2274       MAYBE_REDRAW (redraw);
2275       break;
2276
2277     case OP_PRINT:
2278       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2279       if (IsAttach (extra))
2280         mutt_print_attachment_list (extra->fp, 0, extra->bdy);
2281       else
2282         mutt_print_message (extra->hdr);
2283       break;
2284
2285     case OP_MAIL:
2286       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2287       CHECK_ATTACH;
2288       ci_send_message (0, NULL, NULL, extra->ctx, NULL);
2289       redraw = REDRAW_FULL;
2290       break;
2291
2292 #ifdef USE_NNTP
2293     case OP_POST:
2294       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2295       CHECK_ATTACH;
2296       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2297           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2298           query_quadoption (OPT_TOMODERATED,
2299                             _
2300                             ("Posting to this group not allowed, may be moderated. Continue?"))
2301           != M_YES)
2302         break;
2303       ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL);
2304       redraw = REDRAW_FULL;
2305       break;
2306
2307     case OP_FORWARD_TO_GROUP:
2308       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2309       CHECK_ATTACH;
2310       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2311           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2312           query_quadoption (OPT_TOMODERATED,
2313                             _
2314                             ("Posting to this group not allowed, may be moderated. Continue?"))
2315           != M_YES)
2316         break;
2317       if (IsMsgAttach (extra))
2318         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2319                              extra->idxlen, extra->bdy, SENDNEWS);
2320       else
2321         ci_send_message (SENDNEWS | SENDFORWARD, NULL, NULL, extra->ctx,
2322                          extra->hdr);
2323       redraw = REDRAW_FULL;
2324       break;
2325
2326     case OP_FOLLOWUP:
2327       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2328       CHECK_ATTACH;
2329
2330       if (IsMsgAttach (extra))
2331         followup_to = extra->bdy->hdr->env->followup_to;
2332       else
2333         followup_to = extra->hdr->env->followup_to;
2334
2335       if (!followup_to || safe_strcasecmp (followup_to, "poster") ||
2336           query_quadoption (OPT_FOLLOWUPTOPOSTER,
2337                             _("Reply by mail as poster prefers?")) != M_YES) {
2338         if (extra->ctx && extra->ctx->magic == M_NNTP
2339             && !((NNTP_DATA *) extra->ctx->data)->allowed
2340             && query_quadoption (OPT_TOMODERATED,
2341                                  _
2342                                  ("Posting to this group not allowed, may be moderated. Continue?"))
2343             != M_YES)
2344           break;
2345         if (IsMsgAttach (extra))
2346           mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2347                              extra->idxlen, extra->bdy, SENDNEWS | SENDREPLY);
2348         else
2349           ci_send_message (SENDNEWS | SENDREPLY, NULL, NULL,
2350                            extra->ctx, extra->hdr);
2351         redraw = REDRAW_FULL;
2352         break;
2353       }
2354 #endif
2355
2356     case OP_REPLY:
2357       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2358       CHECK_ATTACH;
2359       if (IsMsgAttach (extra))
2360         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2361                            extra->idxlen, extra->bdy, SENDREPLY);
2362       else
2363         ci_send_message (SENDREPLY, NULL, NULL, extra->ctx, extra->hdr);
2364       redraw = REDRAW_FULL;
2365       break;
2366
2367     case OP_RECALL_MESSAGE:
2368       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2369       CHECK_ATTACH;
2370       ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr);
2371       redraw = REDRAW_FULL;
2372       break;
2373
2374     case OP_GROUP_REPLY:
2375       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2376       CHECK_ATTACH;
2377       if (IsMsgAttach (extra))
2378         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2379                            extra->idxlen, extra->bdy,
2380                            SENDREPLY | SENDGROUPREPLY);
2381       else
2382         ci_send_message (SENDREPLY | SENDGROUPREPLY, NULL, NULL, extra->ctx,
2383                          extra->hdr);
2384       redraw = REDRAW_FULL;
2385       break;
2386
2387     case OP_LIST_REPLY:
2388       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2389       CHECK_ATTACH;
2390       if (IsMsgAttach (extra))
2391         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2392                            extra->idxlen, extra->bdy,
2393                            SENDREPLY | SENDLISTREPLY);
2394       else
2395         ci_send_message (SENDREPLY | SENDLISTREPLY, NULL, NULL, extra->ctx,
2396                          extra->hdr);
2397       redraw = REDRAW_FULL;
2398       break;
2399
2400     case OP_FORWARD_MESSAGE:
2401       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2402       CHECK_ATTACH;
2403       if (IsMsgAttach (extra))
2404         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2405                              extra->idxlen, extra->bdy, 0);
2406       else
2407         ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
2408       redraw = REDRAW_FULL;
2409       break;
2410
2411     case OP_DECRYPT_SAVE:
2412       if (!WithCrypto) {
2413         ch = -1;
2414         break;
2415       }
2416       /* fall through */
2417     case OP_SAVE:
2418       if (IsAttach (extra)) {
2419         mutt_save_attachment_list (extra->fp, 0, extra->bdy, extra->hdr,
2420                                    NULL);
2421         break;
2422       }
2423       /* fall through */
2424     case OP_COPY_MESSAGE:
2425     case OP_DECODE_SAVE:
2426     case OP_DECODE_COPY:
2427     case OP_DECRYPT_COPY:
2428       if (!WithCrypto && ch == OP_DECRYPT_COPY) {
2429         ch = -1;
2430         break;
2431       }
2432       CHECK_MODE (IsHeader (extra));
2433       if (mutt_save_message (extra->hdr,
2434                              (ch == OP_DECRYPT_SAVE) ||
2435                              (ch == OP_SAVE) || (ch == OP_DECODE_SAVE),
2436                              (ch == OP_DECODE_SAVE) || (ch == OP_DECODE_COPY),
2437                              (ch == OP_DECRYPT_SAVE)
2438                              || (ch == OP_DECRYPT_COPY) || 0, &redraw) == 0
2439           && (ch == OP_SAVE || ch == OP_DECODE_SAVE
2440               || ch == OP_DECRYPT_SAVE)) {
2441         if (option (OPTRESOLVE)) {
2442           ch = -1;
2443           rc = OP_MAIN_NEXT_UNDELETED;
2444         }
2445         else
2446           redraw |= REDRAW_STATUS | REDRAW_INDEX;
2447       }
2448       MAYBE_REDRAW (redraw);
2449       break;
2450
2451     case OP_SHELL_ESCAPE:
2452       mutt_shell_escape ();
2453       MAYBE_REDRAW (redraw);
2454       break;
2455
2456     case OP_TAG:
2457       CHECK_MODE (IsHeader (extra));
2458       mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
2459
2460       Context->last_tag = extra->hdr->tagged ? extra->hdr :
2461         ((Context->last_tag == extra->hdr && !extra->hdr->tagged)
2462          ? NULL : Context->last_tag);
2463
2464       redraw = REDRAW_STATUS | REDRAW_INDEX;
2465       if (option (OPTRESOLVE)) {
2466         ch = -1;
2467         rc = OP_NEXT_ENTRY;
2468       }
2469       break;
2470
2471     case OP_TOGGLE_NEW:
2472       CHECK_MODE (IsHeader (extra));
2473       CHECK_READONLY;
2474
2475 #ifdef USE_IMAP
2476       CHECK_IMAP_ACL (IMAP_ACL_SEEN);
2477 #endif
2478
2479       if (extra->hdr->read || extra->hdr->old)
2480         mutt_set_flag (Context, extra->hdr, M_NEW, 1);
2481       else if (!first)
2482         mutt_set_flag (Context, extra->hdr, M_READ, 1);
2483       first = 0;
2484       Context->msgnotreadyet = -1;
2485       redraw = REDRAW_STATUS | REDRAW_INDEX;
2486       if (option (OPTRESOLVE)) {
2487         ch = -1;
2488         rc = OP_MAIN_NEXT_UNDELETED;
2489       }
2490       break;
2491
2492     case OP_UNDELETE:
2493       CHECK_MODE (IsHeader (extra));
2494       CHECK_READONLY;
2495
2496 #ifdef USE_IMAP
2497       CHECK_IMAP_ACL (IMAP_ACL_DELETE);
2498 #endif
2499
2500       mutt_set_flag (Context, extra->hdr, M_DELETE, 0);
2501       mutt_set_flag (Context, extra->hdr, M_PURGED, 0);
2502       redraw = REDRAW_STATUS | REDRAW_INDEX;
2503       if (option (OPTRESOLVE)) {
2504         ch = -1;
2505         rc = OP_NEXT_ENTRY;
2506       }
2507       break;
2508
2509     case OP_UNDELETE_THREAD:
2510     case OP_UNDELETE_SUBTHREAD:
2511       CHECK_MODE (IsHeader (extra));
2512       CHECK_READONLY;
2513
2514 #ifdef USE_IMAP
2515       CHECK_IMAP_ACL (IMAP_ACL_DELETE);
2516 #endif
2517
2518       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0,
2519                                 ch == OP_UNDELETE_THREAD ? 0 : 1)
2520         + mutt_thread_set_flag (extra->hdr, M_PURGED, 0,
2521                                 ch == OP_UNDELETE_THREAD ? 0 : 1);
2522
2523       if (r > -1) {
2524         if (option (OPTRESOLVE)) {
2525           rc = (ch == OP_DELETE_THREAD) ?
2526             OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
2527           ch = -1;
2528         }
2529
2530         if (!option (OPTRESOLVE) && PagerIndexLines)
2531           redraw = REDRAW_FULL;
2532         else
2533           redraw = REDRAW_STATUS | REDRAW_INDEX;
2534       }
2535       break;
2536
2537     case OP_VERSION:
2538       mutt_version ();
2539       break;
2540
2541     case OP_BUFFY_LIST:
2542       mutt_buffy_list ();
2543       redraw |= REDRAW_SIDEBAR;
2544       break;
2545
2546     case OP_VIEW_ATTACHMENTS:
2547       if (flags & M_PAGER_ATTACHMENT) {
2548         ch = -1;
2549         rc = OP_ATTACH_COLLAPSE;
2550         break;
2551       }
2552       CHECK_MODE (IsHeader (extra));
2553       mutt_view_attachments (extra->hdr);
2554       if (extra->hdr->attach_del)
2555         Context->changed = 1;
2556       redraw = REDRAW_FULL;
2557       break;
2558
2559
2560     case OP_MAIL_KEY:
2561       if (!(WithCrypto & APPLICATION_PGP)) {
2562         ch = -1;
2563         break;
2564       }
2565       CHECK_MODE (IsHeader (extra));
2566       CHECK_ATTACH;
2567       ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr);
2568       redraw = REDRAW_FULL;
2569       break;
2570
2571
2572     case OP_FORGET_PASSPHRASE:
2573       crypt_forget_passphrase ();
2574       break;
2575
2576     case OP_EXTRACT_KEYS:
2577       if (!WithCrypto) {
2578         ch = -1;
2579         break;
2580       }
2581       CHECK_MODE (IsHeader (extra));
2582       crypt_extract_keys_from_messages (extra->hdr);
2583       redraw = REDRAW_FULL;
2584       break;
2585
2586     case OP_SIDEBAR_SCROLL_UP:
2587     case OP_SIDEBAR_SCROLL_DOWN:
2588     case OP_SIDEBAR_NEXT:
2589     case OP_SIDEBAR_NEXT_NEW:
2590     case OP_SIDEBAR_PREV:
2591     case OP_SIDEBAR_PREV_NEW:
2592       sidebar_scroll (ch, MENU_PAGER);
2593       break;
2594     default:
2595       ch = -1;
2596       break;
2597     }
2598   }
2599
2600   fclose (fp);
2601   if (IsHeader (extra))
2602     Context->msgnotreadyet = -1;
2603
2604   cleanup_quote (&QuoteList);
2605
2606   for (i = 0; i < maxLine; i++) {
2607     FREE (&(lineInfo[i].syntax));
2608     if (SearchCompiled && lineInfo[i].search)
2609       FREE (&(lineInfo[i].search));
2610   }
2611   if (SearchCompiled) {
2612     regfree (&SearchRE);
2613     SearchCompiled = 0;
2614   }
2615   FREE (&lineInfo);
2616   if (index)
2617     mutt_menuDestroy (&index);
2618   return (rc != -1 ? rc : 0);
2619 }