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