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