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