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