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