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