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