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