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