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