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