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