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