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
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   LOFF_T 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 brailleLine = -1;
651 static int brailleCol = -1;
652
653 static int check_attachment_marker (char *);
654
655 static void
656 resolve_types (char *buf, char *raw, struct line_t *lineInfo, int n, int last,
657                struct q_class_t **QuoteList, int *q_level, int *force_redraw,
658                int q_classify)
659 {
660   COLOR_LINE *color_line;
661   regmatch_t pmatch[1], smatch[1];
662   int found, offset, null_rx, i;
663
664   if (n == 0 || ISHEADER (lineInfo[n - 1].type)) {
665     if (buf[0] == '\n') {
666       lineInfo[n].type = MT_COLOR_NORMAL;
667       getyx(stdscr, brailleLine, brailleCol);
668     }
669     else if (n > 0 && (buf[0] == ' ' || buf[0] == '\t')) {
670       lineInfo[n].type = lineInfo[n - 1].type;  /* wrapped line */
671       (lineInfo[n].syntax)[0].color = (lineInfo[n - 1].syntax)[0].color;
672     }
673     else {
674       lineInfo[n].type = MT_COLOR_HDEFAULT;
675       color_line = ColorHdrList;
676       while (color_line) {
677         if (REGEXEC (&color_line->rx, buf) == 0) {
678           lineInfo[n].type = MT_COLOR_HEADER;
679           lineInfo[n].syntax[0].color = color_line->pair;
680           break;
681         }
682         color_line = color_line->next;
683       }
684     }
685   }
686   else if (str_ncmp ("\033[0m", raw, 4) == 0)       /* a little hack... */
687     lineInfo[n].type = MT_COLOR_NORMAL;
688 #if 0
689   else if (str_ncmp ("[-- ", buf, 4) == 0)
690     lineInfo[n].type = MT_COLOR_ATTACHMENT;
691 #else
692   else if (check_attachment_marker ((char *) raw) == 0)
693     lineInfo[n].type = MT_COLOR_ATTACHMENT;
694 #endif
695   else if (str_cmp ("-- \n", buf) == 0
696            || str_cmp ("-- \r\n", buf) == 0) {
697     i = n + 1;
698
699     lineInfo[n].type = MT_COLOR_SIGNATURE;
700     while (i < last && check_sig (buf, lineInfo, i - 1) == 0 &&
701            (lineInfo[i].type == MT_COLOR_NORMAL ||
702             lineInfo[i].type == MT_COLOR_QUOTED ||
703             lineInfo[i].type == MT_COLOR_HEADER)) {
704       /* oops... */
705       if (lineInfo[i].chunks) {
706         lineInfo[i].chunks = 0;
707         mem_realloc (&(lineInfo[n].syntax), sizeof (struct syntax_t));
708       }
709       lineInfo[i++].type = MT_COLOR_SIGNATURE;
710     }
711   }
712   else if (check_sig (buf, lineInfo, n - 1) == 0)
713     lineInfo[n].type = MT_COLOR_SIGNATURE;
714   else if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) {
715     if (regexec ((regex_t *) Smileys.rx, buf, 1, smatch, 0) == 0) {
716       if (smatch[0].rm_so > 0) {
717         char c;
718
719         /* hack to avoid making an extra copy of buf */
720         c = buf[smatch[0].rm_so];
721         buf[smatch[0].rm_so] = 0;
722
723         if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) {
724           if (q_classify && lineInfo[n].quote == NULL)
725             lineInfo[n].quote = classify_quote (QuoteList,
726                                                 buf + pmatch[0].rm_so,
727                                                 pmatch[0].rm_eo -
728                                                 pmatch[0].rm_so, force_redraw,
729                                                 q_level);
730           lineInfo[n].type = MT_COLOR_QUOTED;
731         }
732         else
733           lineInfo[n].type = MT_COLOR_NORMAL;
734
735         buf[smatch[0].rm_so] = c;
736       }
737       else
738         lineInfo[n].type = MT_COLOR_NORMAL;
739     }
740     else {
741       if (q_classify && lineInfo[n].quote == NULL)
742         lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so,
743                                             pmatch[0].rm_eo - pmatch[0].rm_so,
744                                             force_redraw, q_level);
745       lineInfo[n].type = MT_COLOR_QUOTED;
746     }
747   }
748   else
749     lineInfo[n].type = MT_COLOR_NORMAL;
750
751   /* body patterns */
752   if (lineInfo[n].type == MT_COLOR_NORMAL ||
753       lineInfo[n].type == MT_COLOR_QUOTED) {
754     i = 0;
755
756     offset = 0;
757     lineInfo[n].chunks = 0;
758     do {
759       if (!buf[offset])
760         break;
761
762       found = 0;
763       null_rx = 0;
764       color_line = ColorBodyList;
765       while (color_line) {
766         if (regexec (&color_line->rx, buf + offset, 1, pmatch,
767                      (offset ? REG_NOTBOL : 0)) == 0) {
768           if (pmatch[0].rm_eo != pmatch[0].rm_so) {
769             if (!found) {
770               if (++(lineInfo[n].chunks) > 1)
771                 mem_realloc (&(lineInfo[n].syntax),
772                               (lineInfo[n].chunks) *
773                               sizeof (struct syntax_t));
774             }
775             i = lineInfo[n].chunks - 1;
776             pmatch[0].rm_so += offset;
777             pmatch[0].rm_eo += offset;
778             if (!found ||
779                 pmatch[0].rm_so < (lineInfo[n].syntax)[i].first ||
780                 (pmatch[0].rm_so == (lineInfo[n].syntax)[i].first &&
781                  pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last)) {
782               (lineInfo[n].syntax)[i].color = color_line->pair;
783               (lineInfo[n].syntax)[i].first = pmatch[0].rm_so;
784               (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo;
785             }
786             found = 1;
787             null_rx = 0;
788           }
789           else
790             null_rx = 1;        /* empty regexp; don't add it, but keep looking */
791         }
792         color_line = color_line->next;
793       }
794
795       if (null_rx)
796         offset++;               /* avoid degenerate cases */
797       else
798         offset = (lineInfo[n].syntax)[i].last;
799     } while (found || null_rx);
800   }
801 }
802
803 static int is_ansi (unsigned char *buf)
804 {
805   while (*buf && (isdigit (*buf) || *buf == ';'))
806     buf++;
807   return (*buf == 'm');
808 }
809
810 static int check_attachment_marker (char *p)
811 {
812   char *q = AttachmentMarker;
813
814   for (; *p == *q && *q && *p && *q != '\a' && *p != '\a'; p++, q++);
815   return (int) (*p - *q);
816 }
817
818 static int grok_ansi (unsigned char *buf, int pos, ansi_attr * a)
819 {
820   int x = pos;
821
822   while (isdigit (buf[x]) || buf[x] == ';')
823     x++;
824
825   /* Character Attributes */
826   if (option (OPTALLOWANSI) && a != NULL && buf[x] == 'm') {
827     if (pos == x) {
828 #ifdef HAVE_COLOR
829       if (a->pair != -1)
830         mutt_free_color (a->fg, a->bg);
831 #endif
832       a->attr = ANSI_OFF;
833       a->pair = -1;
834     }
835     while (pos < x) {
836       if (buf[pos] == '1' && (pos + 1 == x || buf[pos + 1] == ';')) {
837         a->attr |= ANSI_BOLD;
838         pos += 2;
839       }
840       else if (buf[pos] == '4' && (pos + 1 == x || buf[pos + 1] == ';')) {
841         a->attr |= ANSI_UNDERLINE;
842         pos += 2;
843       }
844       else if (buf[pos] == '5' && (pos + 1 == x || buf[pos + 1] == ';')) {
845         a->attr |= ANSI_BLINK;
846         pos += 2;
847       }
848       else if (buf[pos] == '7' && (pos + 1 == x || buf[pos + 1] == ';')) {
849         a->attr |= ANSI_REVERSE;
850         pos += 2;
851       }
852       else if (buf[pos] == '0' && (pos + 1 == x || buf[pos + 1] == ';')) {
853 #ifdef HAVE_COLOR
854         if (a->pair != -1)
855           mutt_free_color (a->fg, a->bg);
856 #endif
857         a->attr = ANSI_OFF;
858         a->pair = -1;
859         pos += 2;
860       }
861       else if (buf[pos] == '3' && isdigit (buf[pos + 1])) {
862 #ifdef HAVE_COLOR
863         if (a->pair != -1)
864           mutt_free_color (a->fg, a->bg);
865 #endif
866         a->pair = -1;
867         a->attr |= ANSI_COLOR;
868         a->fg = buf[pos + 1] - '0';
869         pos += 3;
870       }
871       else if (buf[pos] == '4' && isdigit (buf[pos + 1])) {
872 #ifdef HAVE_COLOR
873         if (a->pair != -1)
874           mutt_free_color (a->fg, a->bg);
875 #endif
876         a->pair = -1;
877         a->attr |= ANSI_COLOR;
878         a->bg = buf[pos + 1] - '0';
879         pos += 3;
880       }
881       else {
882         while (pos < x && buf[pos] != ';')
883           pos++;
884         pos++;
885       }
886     }
887   }
888   pos = x;
889   return pos;
890 }
891
892 /* trim tail of buf so that it contains complete multibyte characters */
893 static int trim_incomplete_mbyte(unsigned char *buf, size_t len) {
894   mbstate_t mbstate;
895   size_t k;
896
897   memset (&mbstate, 0, sizeof (mbstate));
898   for (; len > 0; buf += k, len -= k) {
899     k = mbrtowc (NULL, (char *) buf, len, &mbstate);
900     if (k == -2) 
901       break; 
902     else if (k == -1 || k == 0) 
903       k = 1;
904   }
905   *buf = '\0';
906
907   return len;
908 }
909
910 static int
911 fill_buffer (FILE * f, LOFF_T *last_pos, LOFF_T offset, unsigned char *buf,
912              unsigned char *fmt, size_t blen, int *buf_ready)
913 {
914   unsigned char *p;
915   static int b_read = 0;
916
917   if (*buf_ready == 0) {
918     buf[blen - 1] = 0;
919     if (offset != *last_pos)
920       fseeko (f, offset, 0);
921     if (fgets ((char *) buf, blen - 1, f) == NULL) {
922       fmt[0] = 0;
923       return (-1);
924     }
925     *last_pos = ftello (f);
926     b_read = (int) (*last_pos - offset);
927     *buf_ready = 1;
928
929     /* incomplete mbyte characters trigger a segfault in regex processing for
930      * certain versions of glibc. Trim them if necessary. */
931     if (b_read == blen - 2)
932       b_read -= trim_incomplete_mbyte(buf, b_read);
933
934     /* copy "buf" to "fmt", but without bold and underline controls */
935     p = buf;
936     while (*p) {
937       if (*p == '\010' && (p > buf)) {
938         if (*(p + 1) == '_')    /* underline */
939           p += 2;
940         else if (*(p + 1)) {    /* bold or overstrike */
941           *(fmt - 1) = *(p + 1);
942           p += 2;
943         }
944         else                    /* ^H */
945           *fmt++ = *p++;
946       }
947       else if (*p == '\033' && *(p + 1) == '[' && is_ansi (p + 2)) {
948         while (*p++ != 'm')     /* skip ANSI sequence */
949           ;
950       }
951       else if (*p == '\033' && *(p + 1) == ']'
952                && check_attachment_marker ((char *) p) == 0) {
953         debug_print (2, ("seen attachment marker.\n"));
954         while (*p++ != '\a')    /* skip pseudo-ANSI sequence */
955           ;
956       }
957       else
958         *fmt++ = *p++;
959     }
960     *fmt = 0;
961   }
962   return b_read;
963 }
964
965 #ifdef USE_NNTP
966 #include "mx.h"
967 #include "nntp.h"
968 #endif
969
970
971 static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
972                         int flags, ansi_attr * pa, int cnt,
973                         int *pspace, int *pvch, int *pcol, int *pspecial)
974 {
975   int space = -1;               /* index of the last space or TAB */
976   int col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0;
977   int ch, vch, k, last_special = -1, special = 0, t;
978   wchar_t wc;
979   mbstate_t mbstate;
980
981   int wrap_cols = COLS;
982
983   if (!(flags & (M_SHOWFLAT)))
984     wrap_cols -= WrapMargin;
985   wrap_cols -= SidebarWidth;
986
987   if (wrap_cols <= 0)
988     wrap_cols = COLS;
989
990   /* FIXME: this should come from lineInfo */
991   memset (&mbstate, 0, sizeof (mbstate));
992
993   for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k) {
994     /* Handle ANSI sequences */
995     while (cnt - ch >= 2 && buf[ch] == '\033' && buf[ch + 1] == '[' &&
996            is_ansi (buf + ch + 2))
997       ch = grok_ansi (buf, ch + 2, pa) + 1;
998
999     while (cnt - ch >= 2 && buf[ch] == '\033' && buf[ch + 1] == ']' &&
1000            check_attachment_marker ((char *) buf + ch) == 0) {
1001       while (buf[ch++] != '\a')
1002         if (ch >= cnt)
1003           break;
1004     }
1005
1006     /* is anything left to do? */
1007     if (ch >= cnt)
1008       break;
1009
1010     k = mbrtowc (&wc, (char *) buf + ch, cnt - ch, &mbstate);
1011     if (k == -2 || k == -1) {
1012       debug_print (1, ("mbrtowc returned %d; errno = %d.\n", k, errno));
1013       if (col + 4 > wrap_cols)
1014         break;
1015       col += 4;
1016       if (pa)
1017         printw ("\\%03o", buf[ch]);
1018       k = 1;
1019       continue;
1020     }
1021     if (k == 0)
1022       k = 1;
1023
1024     /* Handle backspace */
1025     special = 0;
1026     if (IsWPrint (wc)) {
1027       wchar_t wc1;
1028       mbstate_t mbstate1;
1029       int k1, k2;
1030
1031       while ((wc1 = 0, mbstate1 = mbstate,
1032               k1 =
1033               k + mbrtowc (&wc1, (char *) buf + ch + k, cnt - ch - k,
1034                            &mbstate1), k1 - k > 0 && wc1 == '\b')
1035              && (wc1 = 0, k2 =
1036                  mbrtowc (&wc1, (char *) buf + ch + k1, cnt - ch - k1,
1037                           &mbstate1), k2 > 0 && IsWPrint (wc1))) {
1038         if (wc == wc1) {
1039           special |= (wc == '_' && special & A_UNDERLINE)
1040             ? A_UNDERLINE : A_BOLD;
1041         }
1042         else if (wc == '_' || wc1 == '_') {
1043           special |= A_UNDERLINE;
1044           wc = (wc1 == '_') ? wc : wc1;
1045         }
1046         else {
1047           /* special = 0; / * overstrike: nothing to do! */
1048           wc = wc1;
1049         }
1050         ch += k1;
1051         k = k2;
1052         mbstate = mbstate1;
1053       }
1054     }
1055
1056     if (pa &&
1057         ((flags & (M_SHOWCOLOR | M_SEARCH | M_PAGER_MARKER)) ||
1058          special || last_special || pa->attr)) {
1059       resolve_color (*lineInfo, n, vch, flags, special, pa);
1060       last_special = special;
1061     }
1062
1063     if (IsWPrint (wc)) {
1064       if (wc == ' ')
1065         space = ch;
1066       t = wcwidth (wc);
1067       if (col + t > wrap_cols)
1068         break;
1069       col += t;
1070       if (pa)
1071         mutt_addwch (wc);
1072     }
1073     else if (wc == '\n')
1074       break;
1075     else if (wc == '\t') {
1076       space = ch;
1077       t = (col & ~7) + 8;
1078       if (t > wrap_cols)
1079         break;
1080       if (pa)
1081         for (; col < t; col++)
1082           addch (' ');
1083       else
1084         col = t;
1085     }
1086     else if (wc < 0x20 || wc == 0x7f) {
1087       if (col + 2 > wrap_cols)
1088         break;
1089       col += 2;
1090       if (pa)
1091         printw ("^%c", ('@' + wc) & 0x7f);
1092     }
1093     else if (wc < 0x100) {
1094       if (col + 4 > wrap_cols)
1095         break;
1096       col += 4;
1097       if (pa)
1098         printw ("\\%03o", wc);
1099     }
1100     else {
1101       if (col + 1 > wrap_cols)
1102         break;
1103       ++col;
1104       if (pa)
1105         addch (replacement_char ());
1106     }
1107   }
1108   *pspace = space;
1109   *pcol = col;
1110   *pvch = vch;
1111   *pspecial = special;
1112   return ch;
1113 }
1114
1115 /*
1116  * Args:
1117  *      flags   M_SHOWFLAT, show characters (used for displaying help)
1118  *              M_SHOWCOLOR, show characters in color
1119  *                      otherwise don't show characters
1120  *              M_HIDE, don't show quoted text
1121  *              M_SEARCH, resolve search patterns
1122  *              M_TYPES, compute line's type
1123  *              M_PAGER_NSKIP, keeps leading whitespace
1124  *              M_PAGER_MARKER, eventually show markers
1125  *
1126  * Return values:
1127  *      -1      EOF was reached
1128  *      0       normal exit, line was not displayed
1129  *      >0      normal exit, line was displayed
1130  */
1131
1132 static int
1133 display_line (FILE * f, LOFF_T *last_pos, struct line_t **lineInfo, int n,
1134               int *last, int *max, int flags, struct q_class_t **QuoteList,
1135               int *q_level, int *force_redraw, regex_t * SearchRE)
1136 {
1137   unsigned char buf[LONG_STRING], fmt[LONG_STRING];
1138   unsigned char *buf_ptr = buf;
1139   int ch, vch, col, cnt, b_read;
1140   int buf_ready = 0, change_last = 0;
1141   int special;
1142   int offset;
1143   int def_color;
1144   int m;
1145   ansi_attr a = { 0, 0, 0, -1 };
1146   regmatch_t pmatch[1];
1147
1148   if (n == *last) {
1149     (*last)++;
1150     change_last = 1;
1151   }
1152
1153   if (*last == *max) {
1154     mem_realloc (lineInfo, sizeof (struct line_t) * (*max += LINES));
1155     for (ch = *last; ch < *max; ch++) {
1156       memset (&((*lineInfo)[ch]), 0, sizeof (struct line_t));
1157       (*lineInfo)[ch].type = -1;
1158       (*lineInfo)[ch].search_cnt = -1;
1159       (*lineInfo)[ch].syntax = mem_malloc (sizeof (struct syntax_t));
1160       ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last =
1161         -1;
1162     }
1163   }
1164
1165   /* only do color hiliting if we are viewing a message */
1166   if (flags & (M_SHOWCOLOR | M_TYPES)) {
1167     if ((*lineInfo)[n].type == -1) {
1168       /* determine the line class */
1169       if (fill_buffer
1170           (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1171            &buf_ready) < 0) {
1172         if (change_last)
1173           (*last)--;
1174         return (-1);
1175       }
1176
1177       resolve_types ((char *) fmt, (char *) buf, *lineInfo, n, *last,
1178                      QuoteList, q_level, force_redraw, flags & M_SHOWCOLOR);
1179
1180       /* avoid race condition for continuation lines when scrolling up */
1181       for (m = n + 1;
1182            m < *last && (*lineInfo)[m].offset && (*lineInfo)[m].continuation;
1183            m++)
1184         (*lineInfo)[m].type = (*lineInfo)[n].type;
1185     }
1186
1187     /* this also prevents searching through the hidden lines */
1188     if ((flags & M_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED)
1189       flags = 0;                /* M_NOSHOW */
1190   }
1191
1192   /* At this point, (*lineInfo[n]).quote may still be undefined. We 
1193    * don't want to compute it every time M_TYPES is set, since this
1194    * would slow down the "bottom" function unacceptably. A compromise
1195    * solution is hence to call regexec() again, just to find out the
1196    * length of the quote prefix.
1197    */
1198   if ((flags & M_SHOWCOLOR) && !(*lineInfo)[n].continuation &&
1199       (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL)
1200   {
1201     if (fill_buffer
1202         (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1203          &buf_ready) < 0) {
1204       if (change_last)
1205         (*last)--;
1206       return (-1);
1207     }
1208     regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0);
1209     (*lineInfo)[n].quote = classify_quote (QuoteList,
1210                                            (char *) fmt + pmatch[0].rm_so,
1211                                            pmatch[0].rm_eo - pmatch[0].rm_so,
1212                                            force_redraw, q_level);
1213   }
1214
1215   if ((flags & M_SEARCH) && !(*lineInfo)[n].continuation
1216       && (*lineInfo)[n].search_cnt == -1) {
1217     if (fill_buffer
1218         (f, last_pos, (*lineInfo)[n].offset, buf, fmt, sizeof (buf),
1219          &buf_ready) < 0) {
1220       if (change_last)
1221         (*last)--;
1222       return (-1);
1223     }
1224
1225     offset = 0;
1226     (*lineInfo)[n].search_cnt = 0;
1227     while (regexec
1228            (SearchRE, (char *) fmt + offset, 1, pmatch,
1229             (offset ? REG_NOTBOL : 0)) == 0) {
1230       if (++((*lineInfo)[n].search_cnt) > 1)
1231         mem_realloc (&((*lineInfo)[n].search),
1232                       ((*lineInfo)[n].search_cnt) * sizeof (struct syntax_t));
1233       else
1234         (*lineInfo)[n].search = mem_malloc (sizeof (struct syntax_t));
1235       pmatch[0].rm_so += offset;
1236       pmatch[0].rm_eo += offset;
1237       ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first =
1238         pmatch[0].rm_so;
1239       ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last =
1240         pmatch[0].rm_eo;
1241
1242       if (pmatch[0].rm_eo == pmatch[0].rm_so)
1243         offset++;               /* avoid degenerate cases */
1244       else
1245         offset = pmatch[0].rm_eo;
1246       if (!fmt[offset])
1247         break;
1248     }
1249   }
1250
1251   if (!(flags & M_SHOW) && (*lineInfo)[n + 1].offset > 0) {
1252     /* we've already scanned this line, so just exit */
1253     return (0);
1254   }
1255   if ((flags & M_SHOWCOLOR) && *force_redraw && (*lineInfo)[n + 1].offset > 0) {
1256     /* no need to try to display this line... */
1257     return (1);                 /* fake display */
1258   }
1259
1260   if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, buf, fmt,
1261                              sizeof (buf), &buf_ready)) < 0) {
1262     if (change_last)
1263       (*last)--;
1264     return (-1);
1265   }
1266
1267   /* now chose a good place to break the line */
1268   cnt =
1269     format_line (lineInfo, n, buf, flags, 0, b_read, &ch, &vch, &col,
1270                  &special);
1271   buf_ptr = buf + cnt;
1272
1273   /* move the break point only if smart_wrap is set */
1274   if (option (OPTWRAP)) {
1275     if (cnt < b_read) {
1276       if (ch != -1 && buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n'
1277           && buf[cnt] != '\r') {
1278         buf_ptr = buf + ch;
1279         /* skip trailing blanks */
1280         while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r'))
1281           ch--;
1282         /* a very long word with leading spaces causes infinite wrapping */
1283         if ((!ch) && (flags & M_PAGER_NSKIP))
1284           buf_ptr = buf + cnt;
1285         else
1286           cnt = ch + 1;
1287       }
1288       else
1289         buf_ptr = buf + cnt;    /* a very long word... */
1290     }
1291     if (!(flags & M_PAGER_NSKIP))
1292       /* skip leading blanks on the next line too */
1293       while (*buf_ptr == ' ' || *buf_ptr == '\t')
1294         buf_ptr++;
1295   }
1296
1297   if (*buf_ptr == '\r')
1298     buf_ptr++;
1299   if (*buf_ptr == '\n')
1300     buf_ptr++;
1301
1302   if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n + 1].continuation)
1303     append_line (*lineInfo, n, (int) (buf_ptr - buf));
1304   (*lineInfo)[n + 1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf);
1305
1306   /* if we don't need to display the line we are done */
1307   if (!(flags & M_SHOW))
1308     return 0;
1309
1310   /* display the line */
1311   format_line (lineInfo, n, buf, flags, &a, cnt, &ch, &vch, &col, &special);
1312
1313   /* avoid a bug in ncurses... */
1314 #ifndef USE_SLANG_CURSES
1315   if (col == 0) {
1316     SETCOLOR (MT_COLOR_NORMAL);
1317     addch (' ');
1318   }
1319 #endif
1320
1321   /* end the last color pattern (needed by S-Lang) */
1322   if (special || (col != COLS && (flags & (M_SHOWCOLOR | M_SEARCH))))
1323     resolve_color (*lineInfo, n, vch, flags, 0, &a);
1324
1325   /*
1326    * Fill the blank space at the end of the line with the prevailing color.
1327    * ncurses does an implicit clrtoeol() when you do addch('\n') so we have
1328    * to make sure to reset the color *after* that
1329    */
1330   if (flags & M_SHOWCOLOR) {
1331     m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n;
1332     if ((*lineInfo)[m].type == MT_COLOR_HEADER)
1333       def_color = ((*lineInfo)[m].syntax)[0].color;
1334     else
1335       def_color = ColorDefs[(*lineInfo)[m].type];
1336
1337     attrset (def_color);
1338 #ifdef HAVE_BKGDSET
1339     bkgdset (def_color | ' ');
1340 #endif
1341   }
1342
1343   /* ncurses always wraps lines when you get to the right side of the
1344    * screen, but S-Lang seems to only wrap if the next character is *not*
1345    * a newline (grr!).
1346    */
1347 #ifndef USE_SLANG_CURSES
1348   if (col < COLS)
1349 #endif
1350     addch ('\n');
1351
1352   /*
1353    * reset the color back to normal.  This *must* come after the
1354    * addch('\n'), otherwise the color for this line will not be
1355    * filled to the right margin.
1356    */
1357   if (flags & M_SHOWCOLOR) {
1358     SETCOLOR (MT_COLOR_NORMAL);
1359     BKGDSET (MT_COLOR_NORMAL);
1360   }
1361
1362   /* build a return code */
1363   if (!(flags & M_SHOW))
1364     flags = 0;
1365
1366   return (flags);
1367 }
1368
1369 static int upNLines (int nlines, struct line_t *info, int cur, int hiding)
1370 {
1371   while (cur > 0 && nlines > 0) {
1372     cur--;
1373     if (!hiding || info[cur].type != MT_COLOR_QUOTED)
1374       nlines--;
1375   }
1376
1377   return cur;
1378 }
1379
1380 static struct mapping_t PagerHelp[] = {
1381   {N_("Exit"), OP_EXIT},
1382   {N_("PrevPg"), OP_PREV_PAGE},
1383   {N_("NextPg"), OP_NEXT_PAGE},
1384   {NULL, 0}
1385 };
1386 static struct mapping_t PagerHelpExtra[] = {
1387   {N_("View Attachm."), OP_VIEW_ATTACHMENTS},
1388   {N_("Del"), OP_DELETE},
1389   {N_("Reply"), OP_REPLY},
1390   {N_("Next"), OP_MAIN_NEXT_UNDELETED},
1391   {NULL, 0}
1392 };
1393
1394 #ifdef USE_NNTP
1395 static struct mapping_t PagerNewsHelpExtra[] = {
1396   {N_("Post"), OP_POST},
1397   {N_("Followup"), OP_FOLLOWUP},
1398   {N_("Del"), OP_DELETE},
1399   {N_("Next"), OP_MAIN_NEXT_UNDELETED},
1400   {NULL, 0}
1401 };
1402 #endif
1403
1404
1405
1406 /* This pager is actually not so simple as it once was.  It now operates in
1407    two modes: one for viewing messages and the other for viewing help.  These
1408    can be distinguished by whether or not ``hdr'' is NULL.  The ``hdr'' arg
1409    is there so that we can do operations on the current message without the
1410    need to pop back out to the main-menu.  */
1411 int
1412 mutt_pager (const char *banner, const char *fname, int flags, pager_t * extra)
1413 {
1414   static char searchbuf[STRING];
1415   char buffer[LONG_STRING];
1416   char helpstr[SHORT_STRING * 2];
1417   char tmphelp[SHORT_STRING * 2];
1418   int maxLine, lastLine = 0;
1419   struct line_t *lineInfo;
1420   struct q_class_t *QuoteList = NULL;
1421   int i, j, ch = 0, rc = -1, hideQuoted = 0, q_level = 0, force_redraw = 0;
1422   int lines = 0, curline = 0, topline = 0, oldtopline = 0, err, first = 1;
1423   int r = -1;
1424   int redraw = REDRAW_FULL;
1425   FILE *fp = NULL;
1426   LOFF_T last_pos = 0, last_offset = 0;
1427   int old_smart_wrap, old_markers;
1428   struct stat sb;
1429   regex_t SearchRE;
1430   int SearchCompiled = 0, SearchFlag = 0, SearchBack = 0;
1431   int has_types = (IsHeader (extra) || (flags & M_SHOWCOLOR)) ? M_TYPES : 0;    /* main message or rfc822 attachment */
1432
1433   int bodyoffset = 1;           /* offset of first line of real text */
1434   int statusoffset = 0;         /* offset for the status bar */
1435   int helpoffset = LINES - 2;   /* offset for the help bar. */
1436   int bodylen = LINES - 2 - bodyoffset; /* length of displayable area */
1437
1438   MUTTMENU *index = NULL;       /* the Pager Index (PI) */
1439   int indexoffset = 0;          /* offset for the PI */
1440   int indexlen = PagerIndexLines;       /* indexlen not always == PIL */
1441   int indicator = indexlen / 3; /* the indicator line of the PI */
1442   int old_PagerIndexLines;      /* some people want to resize it
1443                                  * while inside the pager... */
1444
1445 #ifdef USE_NNTP
1446   char *followup_to;
1447 #endif
1448
1449   if (!(flags & M_SHOWCOLOR))
1450     flags |= M_SHOWFLAT;
1451
1452   if ((fp = fopen (fname, "r")) == NULL) {
1453     mutt_perror (fname);
1454     return (-1);
1455   }
1456
1457   if (stat (fname, &sb) != 0) {
1458     mutt_perror (fname);
1459     fclose (fp);
1460     return (-1);
1461   }
1462   unlink (fname);
1463
1464   /* Initialize variables */
1465
1466   if (IsHeader (extra) && !extra->hdr->read) {
1467     Context->msgnotreadyet = extra->hdr->msgno;
1468     mutt_set_flag (Context, extra->hdr, M_READ, 1);
1469   }
1470
1471   lineInfo = mem_malloc (sizeof (struct line_t) * (maxLine = LINES));
1472   for (i = 0; i < maxLine; i++) {
1473     memset (&lineInfo[i], 0, sizeof (struct line_t));
1474     lineInfo[i].type = -1;
1475     lineInfo[i].search_cnt = -1;
1476     lineInfo[i].syntax = mem_malloc (sizeof (struct syntax_t));
1477     (lineInfo[i].syntax)[0].first = (lineInfo[i].syntax)[0].last = -1;
1478   }
1479
1480   mutt_compile_help (helpstr, sizeof (helpstr), MENU_PAGER, PagerHelp);
1481   if (IsHeader (extra)) {
1482     strfcpy (tmphelp, helpstr, sizeof (tmphelp));
1483     mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER,
1484 #ifdef USE_NNTP
1485                        (Context
1486                         && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra :
1487 #endif
1488                        PagerHelpExtra);
1489     snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
1490   }
1491   if (!InHelp) {
1492     strfcpy (tmphelp, helpstr, sizeof (tmphelp));
1493     mutt_make_help (buffer, sizeof (buffer), _("Help"), MENU_PAGER, OP_HELP);
1494     snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
1495   }
1496
1497   while (ch != -1) {
1498     mutt_curs_set (0);
1499
1500 #ifdef USE_IMAP
1501     imap_keepalive ();
1502 #endif
1503
1504     if (redraw & REDRAW_FULL) {
1505       SETCOLOR (MT_COLOR_NORMAL);
1506       /* clear() doesn't optimize screen redraws */
1507       move (0, 0);
1508       clrtobot ();
1509
1510       if (IsHeader (extra) && Context->vcount + 1 < PagerIndexLines)
1511         indexlen = Context->vcount + 1;
1512       else
1513         indexlen = PagerIndexLines;
1514
1515       indicator = indexlen / 3;
1516
1517       if (option (OPTSTATUSONTOP)) {
1518         indexoffset = 0;
1519         statusoffset = IsHeader (extra) ? indexlen : 0;
1520         bodyoffset = statusoffset + 1;
1521         helpoffset = LINES - 2;
1522         bodylen = helpoffset - bodyoffset;
1523         if (!option (OPTHELP))
1524           bodylen++;
1525       }
1526       else {
1527         helpoffset = 0;
1528         indexoffset = 1;
1529         statusoffset = LINES - 2;
1530         if (!option (OPTHELP))
1531           indexoffset = 0;
1532         bodyoffset = indexoffset + (IsHeader (extra) ? indexlen : 0);
1533         bodylen = statusoffset - bodyoffset;
1534       }
1535
1536       if (option (OPTHELP)) {
1537         SETCOLOR (MT_COLOR_STATUS);
1538         move (helpoffset, 0);
1539         mutt_paddstr (COLS, helpstr);
1540         SETCOLOR (MT_COLOR_NORMAL);
1541       }
1542
1543 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1544       if (Resize != NULL) {
1545         if ((SearchCompiled = Resize->SearchCompiled)) {
1546           REGCOMP
1547             (&SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf));
1548           SearchFlag = M_SEARCH;
1549           SearchBack = Resize->SearchBack;
1550         }
1551         lines = Resize->line;
1552         redraw |= REDRAW_SIGWINCH;
1553
1554         mem_free (&Resize);
1555       }
1556 #endif
1557
1558       if (IsHeader (extra) && PagerIndexLines) {
1559         if (index == NULL) {
1560           /* only allocate the space if/when we need the index.
1561              Initialise the menu as per the main index */
1562           index = mutt_new_menu ();
1563           index->menu = MENU_MAIN;
1564           index->make_entry = index_make_entry;
1565           index->color = index_color;
1566           index->max = Context->vcount;
1567           index->current = extra->hdr->virtual;
1568         }
1569
1570         SETCOLOR (MT_COLOR_NORMAL);
1571         index->offset = indexoffset + (option (OPTSTATUSONTOP) ? 1 : 0);
1572
1573         index->pagelen = indexlen - 1;
1574
1575         /* some fudge to work out where abouts the indicator should go */
1576         if (index->current - indicator < 0)
1577           index->top = 0;
1578         else if (index->max - index->current < index->pagelen - indicator)
1579           index->top = index->max - index->pagelen;
1580         else
1581           index->top = index->current - indicator;
1582
1583         menu_redraw_index (index);
1584       }
1585
1586       redraw |= REDRAW_BODY | REDRAW_INDEX | REDRAW_STATUS;
1587       mutt_show_error ();
1588     }
1589
1590     if (redraw & REDRAW_SIGWINCH) {
1591       i = -1;
1592       j = -1;
1593       while (display_line (fp, &last_pos, &lineInfo, ++i, &lastLine, &maxLine,
1594                            has_types | SearchFlag, &QuoteList, &q_level,
1595                            &force_redraw, &SearchRE) == 0) {
1596         if (!lineInfo[i].continuation && ++j == lines) {
1597           topline = i;
1598           if (!SearchFlag)
1599             break;
1600         }
1601         redraw |= REDRAW_SIDEBAR;
1602       }                         /* while */
1603     }
1604
1605     if ((redraw & REDRAW_BODY) || topline != oldtopline) {
1606       do {
1607         move (bodyoffset, SidebarWidth);
1608         curline = oldtopline = topline;
1609         lines = 0;
1610         force_redraw = 0;
1611
1612         while (lines < bodylen && lineInfo[curline].offset <= sb.st_size - 1) {
1613           if (display_line (fp, &last_pos, &lineInfo, curline, &lastLine,
1614                             &maxLine,
1615                             (flags & M_DISPLAYFLAGS) | hideQuoted |
1616                             SearchFlag, &QuoteList, &q_level, &force_redraw,
1617                             &SearchRE) > 0)
1618             lines++;
1619           curline++;
1620           move (lines + bodyoffset, SidebarWidth);
1621           redraw |= REDRAW_SIDEBAR;
1622         }
1623         last_offset = lineInfo[curline].offset;
1624       } while (force_redraw);
1625
1626       SETCOLOR (MT_COLOR_TILDE);
1627       BKGDSET (MT_COLOR_TILDE);
1628       while (lines < bodylen) {
1629         clrtoeol ();
1630         if (option (OPTTILDE))
1631           addch ('~');
1632         addch ('\n');
1633         lines++;
1634         move (lines + bodyoffset, SidebarWidth);
1635       }
1636       /* We are going to update the pager status bar, so it isn't
1637        * necessary to reset to normal color now. */
1638
1639       redraw |= REDRAW_STATUS;  /* need to update the % seen */
1640     }
1641
1642     if (redraw & REDRAW_STATUS) {
1643       /* print out the pager status bar */
1644       SETCOLOR (MT_COLOR_STATUS);
1645       BKGDSET (MT_COLOR_STATUS);
1646       CLEARLINE_WIN (statusoffset);
1647       if (IsHeader (extra)) {
1648         size_t l1 = (COLS - 9) * MB_LEN_MAX;
1649         size_t l2 = sizeof (buffer);
1650
1651         _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt),
1652                            Context, extra->hdr, M_FORMAT_MAKEPRINT);
1653       }
1654       else if (IsMsgAttach (extra)) {
1655         size_t l1 = (COLS - 9) * MB_LEN_MAX;
1656         size_t l2 = sizeof (buffer);
1657
1658         _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt),
1659                            Context, extra->bdy->hdr, M_FORMAT_MAKEPRINT);
1660       }
1661       if (option(OPTSTATUSONTOP)) {
1662         move(0,0);
1663       }
1664       /*move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (InHelp?(LINES-2):(indexlen - 1))),
1665             option (OPTSTATUSONTOP) ? 0 : SidebarWidth);*/
1666       mutt_paddstr (COLS - 10 - (option(OPTSTATUSONTOP)?0:SidebarWidth), IsHeader (extra)
1667                     || IsMsgAttach (extra) ? buffer : banner);
1668
1669       addstr (" -- (");
1670       if (last_pos < sb.st_size - 1)
1671         printw ("%d%%)", (int) (100 * last_offset / sb.st_size));
1672       else
1673         addstr (topline == 0 ? "all)" : "end)");
1674       BKGDSET (MT_COLOR_NORMAL);
1675       SETCOLOR (MT_COLOR_NORMAL);
1676     }
1677
1678     if (redraw & REDRAW_SIDEBAR)
1679       sidebar_draw (MENU_PAGER);
1680
1681     if ((redraw & REDRAW_INDEX) && index) {
1682       /* redraw the pager_index indicator, because the
1683        * flags for this message might have changed. */
1684       menu_redraw_current (index);
1685       sidebar_draw (MENU_PAGER);
1686       /* print out the index status bar */
1687       menu_status_line (buffer, sizeof (buffer), index, NONULL (Status));
1688       move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)),
1689             option (OPTSTATUSONTOP) ? 0 : SidebarWidth);
1690       SETCOLOR (MT_COLOR_STATUS);
1691       BKGDSET (MT_COLOR_STATUS);
1692       mutt_paddstr (COLS - (option (OPTSTATUSONTOP) ? 0 : SidebarWidth),
1693                     buffer);
1694       SETCOLOR (MT_COLOR_NORMAL);
1695       BKGDSET (MT_COLOR_NORMAL);
1696     }
1697     /* if we're not using the index, update every time */
1698     if (index == 0)
1699       sidebar_draw (MENU_PAGER);
1700
1701     redraw = 0;
1702
1703     if (option(OPTBRAILLEFRIENDLY)) {
1704       if (brailleLine!=-1) {
1705         move(brailleLine+1, 0);
1706         brailleLine = -1;
1707       }
1708     } else
1709       move (statusoffset, COLS-1);
1710     mutt_refresh ();
1711
1712     if (IsHeader (extra) && OldHdr == extra->hdr && TopLine != topline
1713         && lineInfo[curline].offset < sb.st_size-1) {
1714       if (TopLine - topline > lines)
1715         topline += lines;
1716       else
1717         topline = TopLine;
1718       continue;
1719     }
1720     else
1721       OldHdr = NULL;
1722
1723     ch = km_dokey (MENU_PAGER);
1724     if (ch != -1)
1725       mutt_clear_error ();
1726     mutt_curs_set (1);
1727
1728     if (SigInt) {
1729       mutt_query_exit ();
1730       continue;
1731     }
1732 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1733     else if (SigWinch) {
1734       mutt_resize_screen ();
1735
1736       /* Store current position. */
1737       lines = -1;
1738       for (i = 0; i <= topline; i++)
1739         if (!lineInfo[i].continuation)
1740           lines++;
1741
1742       if (flags & M_PAGER_RETWINCH) {
1743         Resize = mem_malloc (sizeof (struct resize));
1744
1745         Resize->line = lines;
1746         Resize->SearchCompiled = SearchCompiled;
1747         Resize->SearchBack = SearchBack;
1748
1749         ch = -1;
1750         rc = OP_REFORMAT_WINCH;
1751       }
1752       else {
1753         for (i = 0; i < maxLine; i++) {
1754           lineInfo[i].offset = 0;
1755           lineInfo[i].type = -1;
1756           lineInfo[i].continuation = 0;
1757           lineInfo[i].chunks = 0;
1758           lineInfo[i].search_cnt = -1;
1759           lineInfo[i].quote = NULL;
1760
1761           mem_realloc (&(lineInfo[i].syntax), sizeof (struct syntax_t));
1762           if (SearchCompiled && lineInfo[i].search)
1763             mem_free (&(lineInfo[i].search));
1764         }
1765
1766         lastLine = 0;
1767         topline = 0;
1768
1769         redraw = REDRAW_FULL | REDRAW_SIGWINCH;
1770         ch = 0;
1771       }
1772
1773       SigWinch = 0;
1774       clearok (stdscr, TRUE);   /*force complete redraw */
1775       continue;
1776     }
1777 #endif
1778     else if (ch == -1) {
1779       ch = 0;
1780       continue;
1781     }
1782
1783     rc = ch;
1784
1785     switch (ch) {
1786     case OP_EXIT:
1787       rc = -1;
1788       ch = -1;
1789       break;
1790
1791     case OP_NEXT_PAGE:
1792       if (lineInfo[curline].offset < sb.st_size - 1) {
1793         topline = upNLines (PagerContext, lineInfo, curline, hideQuoted);
1794       }
1795       else if (option (OPTPAGERSTOP)) {
1796         /* emulate "less -q" and don't go on to the next message. */
1797         mutt_error _("Bottom of message is shown.");
1798       }
1799       else {
1800         /* end of the current message, so display the next message. */
1801         rc = OP_MAIN_NEXT_UNDELETED;
1802         ch = -1;
1803       }
1804       break;
1805
1806     case OP_PREV_PAGE:
1807       if (topline != 0) {
1808         topline =
1809           upNLines (bodylen - PagerContext, lineInfo, topline, hideQuoted);
1810       }
1811       else
1812         mutt_error _("Top of message is shown.");
1813       break;
1814
1815     case OP_NEXT_LINE:
1816       if (lineInfo[curline].offset < sb.st_size - 1) {
1817         topline++;
1818         if (hideQuoted) {
1819           while (lineInfo[topline].type == MT_COLOR_QUOTED &&
1820                  topline < lastLine)
1821             topline++;
1822         }
1823       }
1824       else
1825         mutt_error _("Bottom of message is shown.");
1826       break;
1827
1828     case OP_PREV_LINE:
1829       if (topline)
1830         topline = upNLines (1, lineInfo, topline, hideQuoted);
1831       else
1832         mutt_error _("Top of message is shown.");
1833       break;
1834
1835     case OP_PAGER_TOP:
1836       if (topline)
1837         topline = 0;
1838       else
1839         mutt_error _("Top of message is shown.");
1840       break;
1841
1842     case OP_HALF_UP:
1843       if (topline)
1844         topline = upNLines (bodylen / 2, lineInfo, topline, hideQuoted);
1845       else
1846         mutt_error _("Top of message is shown.");
1847       break;
1848
1849     case OP_HALF_DOWN:
1850       if (lineInfo[curline].offset < sb.st_size - 1) {
1851         topline = upNLines (bodylen / 2, lineInfo, curline, hideQuoted);
1852       }
1853       else if (option (OPTPAGERSTOP)) {
1854         /* emulate "less -q" and don't go on to the next message. */
1855         mutt_error _("Bottom of message is shown.");
1856       }
1857       else {
1858         /* end of the current message, so display the next message. */
1859         rc = OP_MAIN_NEXT_UNDELETED;
1860         ch = -1;
1861       }
1862       break;
1863
1864     case OP_SEARCH_NEXT:
1865     case OP_SEARCH_OPPOSITE:
1866       if (SearchCompiled) {
1867       search_next:
1868         if ((!SearchBack && ch == OP_SEARCH_NEXT) ||
1869             (SearchBack && ch == OP_SEARCH_OPPOSITE)) {
1870           /* searching forward */
1871           for (i = topline + 1; i < lastLine; i++) {
1872             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1873                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1874               break;
1875           }
1876
1877           if (i < lastLine)
1878             topline = i;
1879           else
1880             mutt_error _("Not found.");
1881         }
1882         else {
1883           /* searching backward */
1884           for (i = topline - 1; i >= 0; i--) {
1885             if ((!hideQuoted || (has_types &&
1886                                  lineInfo[i].type != MT_COLOR_QUOTED)) &&
1887                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1888               break;
1889           }
1890
1891           if (i >= 0)
1892             topline = i;
1893           else
1894             mutt_error _("Not found.");
1895         }
1896
1897         if (lineInfo[topline].search_cnt > 0)
1898           SearchFlag = M_SEARCH;
1899
1900         break;
1901       }
1902       /* no previous search pattern, so fall through to search */
1903
1904     case OP_SEARCH:
1905     case OP_SEARCH_REVERSE:
1906       strfcpy (buffer, searchbuf, sizeof (buffer));
1907       if (mutt_get_field ((SearchBack ? _("Reverse search: ") :
1908                            _("Search: ")), buffer, sizeof (buffer),
1909                           M_CLEAR) != 0)
1910         break;
1911
1912       if (!strcmp (buffer, searchbuf)) {
1913         if (SearchCompiled) {
1914           /* do an implicit search-next */
1915           if (ch == OP_SEARCH)
1916             ch = OP_SEARCH_NEXT;
1917           else
1918             ch = OP_SEARCH_OPPOSITE;
1919
1920           goto search_next;
1921         }
1922       }
1923
1924       if (!buffer[0])
1925         break;
1926
1927       strfcpy (searchbuf, buffer, sizeof (searchbuf));
1928
1929       /* leave SearchBack alone if ch == OP_SEARCH_NEXT */
1930       if (ch == OP_SEARCH)
1931         SearchBack = 0;
1932       else if (ch == OP_SEARCH_REVERSE)
1933         SearchBack = 1;
1934
1935       if (SearchCompiled) {
1936         regfree (&SearchRE);
1937         for (i = 0; i < lastLine; i++) {
1938           if (lineInfo[i].search)
1939             mem_free (&(lineInfo[i].search));
1940           lineInfo[i].search_cnt = -1;
1941         }
1942       }
1943
1944       if ((err =
1945            REGCOMP (&SearchRE, searchbuf,
1946                     REG_NEWLINE | mutt_which_case (searchbuf))) != 0) {
1947         regerror (err, &SearchRE, buffer, sizeof (buffer));
1948         mutt_error ("%s", buffer);
1949         regfree (&SearchRE);
1950         for (i = 0; i < maxLine; i++) {
1951           /* cleanup */
1952           if (lineInfo[i].search)
1953             mem_free (&(lineInfo[i].search));
1954           lineInfo[i].search_cnt = -1;
1955         }
1956         SearchFlag = 0;
1957         SearchCompiled = 0;
1958       }
1959       else {
1960         SearchCompiled = 1;
1961         /* update the search pointers */
1962         i = 0;
1963         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
1964                              &maxLine, M_SEARCH | (flags & M_PAGER_NSKIP),
1965                              &QuoteList, &q_level,
1966                              &force_redraw, &SearchRE) == 0) {
1967           i++;
1968           redraw |= REDRAW_SIDEBAR;
1969         }
1970
1971         if (!SearchBack) {
1972           /* searching forward */
1973           for (i = topline; i < lastLine; 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 < lastLine)
1980             topline = i;
1981         }
1982         else {
1983           /* searching backward */
1984           for (i = topline; i >= 0; i--) {
1985             if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
1986                 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
1987               break;
1988           }
1989
1990           if (i >= 0)
1991             topline = i;
1992         }
1993
1994         if (lineInfo[topline].search_cnt == 0) {
1995           SearchFlag = 0;
1996           mutt_error _("Not found.");
1997         }
1998         else
1999           SearchFlag = M_SEARCH;
2000       }
2001       redraw = REDRAW_BODY;
2002       break;
2003
2004     case OP_SEARCH_TOGGLE:
2005       if (SearchCompiled) {
2006         SearchFlag ^= M_SEARCH;
2007         redraw = REDRAW_BODY;
2008       }
2009       break;
2010
2011     case OP_HELP:
2012       /* don't let the user enter the help-menu from the help screen! */
2013       if (!InHelp) {
2014         InHelp = 1;
2015         mutt_help (MENU_PAGER);
2016         redraw = REDRAW_FULL;
2017         InHelp = 0;
2018       }
2019       else
2020         mutt_error _("Help is currently being shown.");
2021       break;
2022
2023     case OP_PAGER_HIDE_QUOTED:
2024       if (has_types) {
2025         hideQuoted ^= M_HIDE;
2026         if (hideQuoted && lineInfo[topline].type == MT_COLOR_QUOTED)
2027           topline = upNLines (1, lineInfo, topline, hideQuoted);
2028         else
2029           redraw = REDRAW_BODY;
2030       }
2031       break;
2032
2033     case OP_PAGER_SKIP_QUOTED:
2034       if (has_types) {
2035         int dretval = 0;
2036         int new_topline = topline;
2037
2038         while ((new_topline < lastLine ||
2039                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
2040                                                new_topline, &lastLine,
2041                                                &maxLine, M_TYPES, &QuoteList,
2042                                                &q_level, &force_redraw,
2043                                                &SearchRE))))
2044                && lineInfo[new_topline].type != MT_COLOR_QUOTED) {
2045           redraw |= REDRAW_SIDEBAR;
2046           new_topline++;
2047         }
2048
2049         if (dretval < 0) {
2050           mutt_error _("No more quoted text.");
2051
2052           break;
2053         }
2054
2055         while ((new_topline < lastLine ||
2056                 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
2057                                                new_topline, &lastLine,
2058                                                &maxLine, M_TYPES, &QuoteList,
2059                                                &q_level, &force_redraw,
2060                                                &SearchRE))))
2061                && lineInfo[new_topline].type == MT_COLOR_QUOTED) {
2062           new_topline++;
2063           redraw |= REDRAW_SIDEBAR;
2064         }
2065
2066         if (dretval < 0) {
2067           mutt_error _("No more unquoted text after quoted text.");
2068
2069           break;
2070         }
2071         topline = new_topline;
2072       }
2073       break;
2074
2075     case OP_PAGER_BOTTOM:      /* move to the end of the file */
2076       if (lineInfo[curline].offset < sb.st_size - 1) {
2077         i = curline;
2078         /* make sure the types are defined to the end of file */
2079         while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
2080                              &maxLine, has_types,
2081                              &QuoteList, &q_level, &force_redraw,
2082                              &SearchRE) == 0) {
2083           i++;
2084           redraw |= REDRAW_SIDEBAR;
2085         }
2086         topline = upNLines (bodylen, lineInfo, lastLine, hideQuoted);
2087       }
2088       else
2089         mutt_error _("Bottom of message is shown.");
2090       break;
2091
2092     case OP_REDRAW:
2093       clearok (stdscr, TRUE);
2094       redraw = REDRAW_FULL;
2095       break;
2096
2097     case OP_NULL:
2098       km_error_key (MENU_PAGER);
2099       break;
2100
2101       /* --------------------------------------------------------------------
2102        * The following are operations on the current message rather than
2103        * adjusting the view of the message.
2104        */
2105
2106     case OP_BOUNCE_MESSAGE:
2107       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2108         CHECK_ATTACH;
2109       if (IsMsgAttach (extra))
2110         mutt_attach_bounce (extra->fp, extra->hdr,
2111                             extra->idx, extra->idxlen, extra->bdy);
2112       else
2113         ci_bounce_message (extra->hdr, &redraw);
2114       break;
2115
2116     case OP_RESEND:
2117       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra))
2118         CHECK_ATTACH;
2119       if (IsMsgAttach (extra))
2120         mutt_attach_resend (extra->fp, extra->hdr,
2121                             extra->idx, extra->idxlen, extra->bdy);
2122       else
2123         mutt_resend_message (NULL, extra->ctx, extra->hdr);
2124       redraw = REDRAW_FULL;
2125       break;
2126
2127     case OP_CHECK_TRADITIONAL:
2128       CHECK_MODE (IsHeader (extra));
2129       if (!(WithCrypto & APPLICATION_PGP))
2130         break;
2131       if (!(extra->hdr->security & PGP_TRADITIONAL_CHECKED)) {
2132         ch = -1;
2133         rc = OP_CHECK_TRADITIONAL;
2134       }
2135       break;
2136
2137     case OP_CREATE_ALIAS:
2138       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2139       if (IsMsgAttach (extra))
2140         mutt_create_alias (extra->bdy->hdr->env, NULL);
2141       else
2142         mutt_create_alias (extra->hdr->env, NULL);
2143       MAYBE_REDRAW (redraw);
2144       break;
2145
2146     case OP_PURGE_MESSAGE:
2147     case OP_DELETE:
2148       CHECK_MODE (IsHeader (extra));
2149       CHECK_READONLY;
2150
2151       CHECK_MX_ACL (Context, ACL_DELETE, _("Deletion"));
2152
2153       mutt_set_flag (Context, extra->hdr, M_DELETE, 1);
2154       mutt_set_flag (Context, extra->hdr, M_PURGED,
2155                      ch != OP_PURGE_MESSAGE ? 0 : 1);
2156       if (option (OPTDELETEUNTAG))
2157         mutt_set_flag (Context, extra->hdr, M_TAG, 0);
2158       redraw = REDRAW_STATUS | REDRAW_INDEX;
2159       if (option (OPTRESOLVE)) {
2160         ch = -1;
2161         rc = OP_MAIN_NEXT_UNDELETED;
2162       }
2163       break;
2164
2165     case OP_DELETE_THREAD:
2166     case OP_DELETE_SUBTHREAD:
2167       CHECK_MODE (IsHeader (extra));
2168       CHECK_READONLY;
2169
2170       CHECK_MX_ACL (Context, ACL_DELETE, _("Deletion"));
2171
2172       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 1,
2173                                 ch == OP_DELETE_THREAD ? 0 : 1);
2174
2175       if (r != -1) {
2176         if (option (OPTDELETEUNTAG))
2177           mutt_thread_set_flag (extra->hdr, M_TAG, 0,
2178                                 ch == OP_DELETE_THREAD ? 0 : 1);
2179         if (option (OPTRESOLVE)) {
2180           rc = OP_MAIN_NEXT_UNDELETED;
2181           ch = -1;
2182         }
2183
2184         if (!option (OPTRESOLVE) && PagerIndexLines)
2185           redraw = REDRAW_FULL;
2186         else
2187           redraw = REDRAW_STATUS | REDRAW_INDEX;
2188       }
2189       break;
2190
2191     case OP_DISPLAY_ADDRESS:
2192       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2193       if (IsMsgAttach (extra))
2194         mutt_display_address (extra->bdy->hdr->env);
2195       else
2196         mutt_display_address (extra->hdr->env);
2197       break;
2198
2199     case OP_ENTER_COMMAND:
2200       old_smart_wrap = option (OPTWRAP);
2201       old_markers = option (OPTMARKERS);
2202       old_PagerIndexLines = PagerIndexLines;
2203
2204       CurrentMenu = MENU_PAGER;
2205       mutt_enter_command ();
2206
2207       if (option (OPTNEEDRESORT)) {
2208         unset_option (OPTNEEDRESORT);
2209         CHECK_MODE (IsHeader (extra));
2210         set_option (OPTNEEDRESORT);
2211       }
2212
2213       if (old_PagerIndexLines != PagerIndexLines) {
2214         if (index)
2215           mutt_menuDestroy (&index);
2216         index = NULL;
2217       }
2218
2219       if (option (OPTWRAP) != old_smart_wrap ||
2220           option (OPTMARKERS) != old_markers) {
2221         if (flags & M_PAGER_RETWINCH) {
2222           ch = -1;
2223           rc = OP_REFORMAT_WINCH;
2224           continue;
2225         }
2226
2227         /* count the real lines above */
2228         j = 0;
2229         for (i = 0; i <= topline; i++) {
2230           if (!lineInfo[i].continuation)
2231             j++;
2232         }
2233
2234         /* we need to restart the whole thing */
2235         for (i = 0; i < maxLine; i++) {
2236           lineInfo[i].offset = 0;
2237           lineInfo[i].type = -1;
2238           lineInfo[i].continuation = 0;
2239           lineInfo[i].chunks = 0;
2240           lineInfo[i].search_cnt = -1;
2241           lineInfo[i].quote = NULL;
2242
2243           mem_realloc (&(lineInfo[i].syntax), sizeof (struct syntax_t));
2244           if (SearchCompiled && lineInfo[i].search)
2245             mem_free (&(lineInfo[i].search));
2246         }
2247
2248         if (SearchCompiled) {
2249           regfree (&SearchRE);
2250           SearchCompiled = 0;
2251         }
2252         SearchFlag = 0;
2253
2254         /* try to keep the old position */
2255         topline = 0;
2256         lastLine = 0;
2257         while (j > 0 && display_line (fp, &last_pos, &lineInfo, topline,
2258                                       &lastLine, &maxLine,
2259                                       (has_types ? M_TYPES : 0),
2260                                       &QuoteList, &q_level, &force_redraw,
2261                                       &SearchRE) == 0) {
2262           redraw |= REDRAW_SIDEBAR;
2263           if (!lineInfo[topline].continuation)
2264             j--;
2265           if (j > 0)
2266             topline++;
2267         }
2268
2269         ch = 0;
2270       }
2271
2272       if (option (OPTFORCEREDRAWPAGER))
2273         redraw = REDRAW_FULL;
2274       unset_option (OPTFORCEREDRAWINDEX);
2275       unset_option (OPTFORCEREDRAWPAGER);
2276       break;
2277
2278     case OP_FLAG_MESSAGE:
2279       CHECK_MODE (IsHeader (extra));
2280       CHECK_READONLY;
2281
2282       CHECK_MX_ACL (Context, ACL_WRITE, _("Flagging"));
2283
2284       mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged);
2285       redraw = REDRAW_STATUS | REDRAW_INDEX;
2286       if (option (OPTRESOLVE)) {
2287         ch = -1;
2288         rc = OP_MAIN_NEXT_UNDELETED;
2289       }
2290       break;
2291
2292     case OP_PIPE:
2293       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2294       if (IsAttach (extra))
2295         mutt_pipe_attachment_list (extra->fp, 0, extra->bdy, 0);
2296       else
2297         mutt_pipe_message (extra->hdr);
2298       MAYBE_REDRAW (redraw);
2299       break;
2300
2301     case OP_PRINT:
2302       CHECK_MODE (IsHeader (extra) || IsAttach (extra));
2303       if (IsAttach (extra))
2304         mutt_print_attachment_list (extra->fp, 0, extra->bdy);
2305       else
2306         mutt_print_message (extra->hdr);
2307       break;
2308
2309     case OP_MAIL:
2310       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2311       CHECK_ATTACH;
2312       ci_send_message (0, NULL, NULL, extra->ctx, NULL);
2313       redraw = REDRAW_FULL;
2314       break;
2315
2316 #ifdef USE_NNTP
2317     case OP_POST:
2318       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2319       CHECK_ATTACH;
2320       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2321           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2322           query_quadoption (OPT_TOMODERATED,
2323                             _
2324                             ("Posting to this group not allowed, may be moderated. Continue?"))
2325           != M_YES)
2326         break;
2327       ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL);
2328       redraw = REDRAW_FULL;
2329       break;
2330
2331     case OP_FORWARD_TO_GROUP:
2332       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2333       CHECK_ATTACH;
2334       if (extra->ctx && extra->ctx->magic == M_NNTP &&
2335           !((NNTP_DATA *) extra->ctx->data)->allowed &&
2336           query_quadoption (OPT_TOMODERATED,
2337                             _
2338                             ("Posting to this group not allowed, may be moderated. Continue?"))
2339           != M_YES)
2340         break;
2341       if (IsMsgAttach (extra))
2342         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2343                              extra->idxlen, extra->bdy, SENDNEWS);
2344       else
2345         ci_send_message (SENDNEWS | SENDFORWARD, NULL, NULL, extra->ctx,
2346                          extra->hdr);
2347       redraw = REDRAW_FULL;
2348       break;
2349
2350     case OP_FOLLOWUP:
2351       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2352       CHECK_ATTACH;
2353
2354       if (IsMsgAttach (extra))
2355         followup_to = extra->bdy->hdr->env->followup_to;
2356       else
2357         followup_to = extra->hdr->env->followup_to;
2358
2359       if (!followup_to || str_casecmp (followup_to, "poster") ||
2360           query_quadoption (OPT_FOLLOWUPTOPOSTER,
2361                             _("Reply by mail as poster prefers?")) != M_YES) {
2362         if (extra->ctx && extra->ctx->magic == M_NNTP
2363             && !((NNTP_DATA *) extra->ctx->data)->allowed
2364             && query_quadoption (OPT_TOMODERATED,
2365                                  _
2366                                  ("Posting to this group not allowed, may be moderated. Continue?"))
2367             != M_YES)
2368           break;
2369         if (IsMsgAttach (extra))
2370           mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2371                              extra->idxlen, extra->bdy, SENDNEWS | SENDREPLY);
2372         else
2373           ci_send_message (SENDNEWS | SENDREPLY, NULL, NULL,
2374                            extra->ctx, extra->hdr);
2375         redraw = REDRAW_FULL;
2376         break;
2377       }
2378 #endif
2379
2380     case OP_REPLY:
2381       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2382       CHECK_ATTACH;
2383       if (IsMsgAttach (extra))
2384         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2385                            extra->idxlen, extra->bdy, SENDREPLY);
2386       else
2387         ci_send_message (SENDREPLY, NULL, NULL, extra->ctx, extra->hdr);
2388       redraw = REDRAW_FULL;
2389       break;
2390
2391     case OP_RECALL_MESSAGE:
2392       CHECK_MODE (IsHeader (extra) && !IsAttach (extra));
2393       CHECK_ATTACH;
2394       ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr);
2395       redraw = REDRAW_FULL;
2396       break;
2397
2398     case OP_GROUP_REPLY:
2399       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2400       CHECK_ATTACH;
2401       if (IsMsgAttach (extra))
2402         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2403                            extra->idxlen, extra->bdy,
2404                            SENDREPLY | SENDGROUPREPLY);
2405       else
2406         ci_send_message (SENDREPLY | SENDGROUPREPLY, NULL, NULL, extra->ctx,
2407                          extra->hdr);
2408       redraw = REDRAW_FULL;
2409       break;
2410
2411     case OP_LIST_REPLY:
2412       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2413       CHECK_ATTACH;
2414       if (IsMsgAttach (extra))
2415         mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2416                            extra->idxlen, extra->bdy,
2417                            SENDREPLY | SENDLISTREPLY);
2418       else
2419         ci_send_message (SENDREPLY | SENDLISTREPLY, NULL, NULL, extra->ctx,
2420                          extra->hdr);
2421       redraw = REDRAW_FULL;
2422       break;
2423
2424     case OP_FORWARD_MESSAGE:
2425       CHECK_MODE (IsHeader (extra) || IsMsgAttach (extra));
2426       CHECK_ATTACH;
2427       if (IsMsgAttach (extra))
2428         mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2429                              extra->idxlen, extra->bdy, 0);
2430       else
2431         ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
2432       redraw = REDRAW_FULL;
2433       break;
2434
2435     case OP_DECRYPT_SAVE:
2436       if (!WithCrypto) {
2437         ch = -1;
2438         break;
2439       }
2440       /* fall through */
2441     case OP_SAVE:
2442       if (IsAttach (extra)) {
2443         mutt_save_attachment_list (extra->fp, 0, extra->bdy, extra->hdr,
2444                                    NULL);
2445         break;
2446       }
2447       /* fall through */
2448     case OP_COPY_MESSAGE:
2449     case OP_DECODE_SAVE:
2450     case OP_DECODE_COPY:
2451     case OP_DECRYPT_COPY:
2452       if (!WithCrypto && ch == OP_DECRYPT_COPY) {
2453         ch = -1;
2454         break;
2455       }
2456       CHECK_MODE (IsHeader (extra));
2457       if (mutt_save_message (extra->hdr,
2458                              (ch == OP_DECRYPT_SAVE) ||
2459                              (ch == OP_SAVE) || (ch == OP_DECODE_SAVE),
2460                              (ch == OP_DECODE_SAVE) || (ch == OP_DECODE_COPY),
2461                              (ch == OP_DECRYPT_SAVE)
2462                              || (ch == OP_DECRYPT_COPY) || 0, &redraw) == 0
2463           && (ch == OP_SAVE || ch == OP_DECODE_SAVE
2464               || ch == OP_DECRYPT_SAVE)) {
2465         if (option (OPTRESOLVE)) {
2466           ch = -1;
2467           rc = OP_MAIN_NEXT_UNDELETED;
2468         }
2469         else
2470           redraw |= REDRAW_STATUS | REDRAW_INDEX;
2471       }
2472       MAYBE_REDRAW (redraw);
2473       break;
2474
2475     case OP_SHELL_ESCAPE:
2476       mutt_shell_escape ();
2477       MAYBE_REDRAW (redraw);
2478       break;
2479
2480     case OP_TAG:
2481       CHECK_MODE (IsHeader (extra));
2482       mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
2483
2484       Context->last_tag = extra->hdr->tagged ? extra->hdr :
2485         ((Context->last_tag == extra->hdr && !extra->hdr->tagged)
2486          ? NULL : Context->last_tag);
2487
2488       redraw = REDRAW_STATUS | REDRAW_INDEX;
2489       if (option (OPTRESOLVE)) {
2490         ch = -1;
2491         rc = OP_NEXT_ENTRY;
2492       }
2493       break;
2494
2495     case OP_TOGGLE_NEW:
2496       CHECK_MODE (IsHeader (extra));
2497       CHECK_READONLY;
2498
2499       CHECK_MX_ACL (Context, ACL_SEEN, _("Toggling"));
2500
2501       if (extra->hdr->read || extra->hdr->old)
2502         mutt_set_flag (Context, extra->hdr, M_NEW, 1);
2503       else if (!first)
2504         mutt_set_flag (Context, extra->hdr, M_READ, 1);
2505       first = 0;
2506       Context->msgnotreadyet = -1;
2507       redraw = REDRAW_STATUS | REDRAW_INDEX;
2508       if (option (OPTRESOLVE)) {
2509         ch = -1;
2510         rc = OP_MAIN_NEXT_UNDELETED;
2511       }
2512       break;
2513
2514     case OP_UNDELETE:
2515       CHECK_MODE (IsHeader (extra));
2516       CHECK_READONLY;
2517
2518       CHECK_MX_ACL (Context, ACL_DELETE, _("Undeletion"));
2519
2520       mutt_set_flag (Context, extra->hdr, M_DELETE, 0);
2521       mutt_set_flag (Context, extra->hdr, M_PURGED, 0);
2522       redraw = REDRAW_STATUS | REDRAW_INDEX;
2523       if (option (OPTRESOLVE)) {
2524         ch = -1;
2525         rc = OP_NEXT_ENTRY;
2526       }
2527       break;
2528
2529     case OP_UNDELETE_THREAD:
2530     case OP_UNDELETE_SUBTHREAD:
2531       CHECK_MODE (IsHeader (extra));
2532       CHECK_READONLY;
2533
2534       CHECK_MX_ACL (Context, ACL_DELETE, _("Undeletion"));
2535
2536       r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0,
2537                                 ch == OP_UNDELETE_THREAD ? 0 : 1)
2538         + mutt_thread_set_flag (extra->hdr, M_PURGED, 0,
2539                                 ch == OP_UNDELETE_THREAD ? 0 : 1);
2540
2541       if (r > -1) {
2542         if (option (OPTRESOLVE)) {
2543           rc = (ch == OP_DELETE_THREAD) ?
2544             OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
2545           ch = -1;
2546         }
2547
2548         if (!option (OPTRESOLVE) && PagerIndexLines)
2549           redraw = REDRAW_FULL;
2550         else
2551           redraw = REDRAW_STATUS | REDRAW_INDEX;
2552       }
2553       break;
2554
2555     case OP_VERSION:
2556       mutt_version ();
2557       break;
2558
2559     case OP_BUFFY_LIST:
2560       if (option (OPTFORCEBUFFYCHECK))
2561         buffy_check (1);
2562       buffy_list ();
2563       redraw |= REDRAW_SIDEBAR;
2564       break;
2565
2566     case OP_VIEW_ATTACHMENTS:
2567       if (flags & M_PAGER_ATTACHMENT) {
2568         ch = -1;
2569         rc = OP_ATTACH_COLLAPSE;
2570         break;
2571       }
2572       CHECK_MODE (IsHeader (extra));
2573       mutt_view_attachments (extra->hdr);
2574       if (extra->hdr->attach_del)
2575         Context->changed = 1;
2576       redraw = REDRAW_FULL;
2577       break;
2578
2579
2580     case OP_MAIL_KEY:
2581       if (!(WithCrypto & APPLICATION_PGP)) {
2582         ch = -1;
2583         break;
2584       }
2585       CHECK_MODE (IsHeader (extra));
2586       CHECK_ATTACH;
2587       ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr);
2588       redraw = REDRAW_FULL;
2589       break;
2590
2591
2592     case OP_FORGET_PASSPHRASE:
2593       crypt_forget_passphrase ();
2594       break;
2595
2596     case OP_EXTRACT_KEYS:
2597       if (!WithCrypto) {
2598         ch = -1;
2599         break;
2600       }
2601       CHECK_MODE (IsHeader (extra));
2602       crypt_extract_keys_from_messages (extra->hdr);
2603       redraw = REDRAW_FULL;
2604       break;
2605
2606     case OP_SIDEBAR_SCROLL_UP:
2607     case OP_SIDEBAR_SCROLL_DOWN:
2608     case OP_SIDEBAR_NEXT:
2609     case OP_SIDEBAR_NEXT_NEW:
2610     case OP_SIDEBAR_PREV:
2611     case OP_SIDEBAR_PREV_NEW:
2612       sidebar_scroll (ch, MENU_PAGER);
2613       break;
2614     default:
2615       ch = -1;
2616       break;
2617     }
2618   }
2619
2620   fclose (fp);
2621   if (IsHeader (extra)) {
2622     Context->msgnotreadyet = -1;
2623     if (rc == -1)
2624       OldHdr = NULL;
2625     else {
2626       TopLine = topline;
2627       OldHdr = extra->hdr;
2628     }
2629   }
2630
2631   cleanup_quote (&QuoteList);
2632
2633   for (i = 0; i < maxLine; i++) {
2634     mem_free (&(lineInfo[i].syntax));
2635     if (SearchCompiled && lineInfo[i].search)
2636       mem_free (&(lineInfo[i].search));
2637   }
2638   if (SearchCompiled) {
2639     regfree (&SearchRE);
2640     SearchCompiled = 0;
2641   }
2642   mem_free (&lineInfo);
2643   if (index)
2644     mutt_menuDestroy (&index);
2645   return (rc != -1 ? rc : 0);
2646 }