2 * Copyright (C) 1996-2002,2007 Michael R. Elkins <me@mutt.org>
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.
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.
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.
24 #include "mutt_curses.h"
25 #include "mutt_regex.h"
27 #include "mutt_menu.h"
33 #include "mutt_crypt.h"
42 #define ISHEADER(x) ((x) == MT_COLOR_HEADER || (x) == MT_COLOR_HDEFAULT)
44 #define IsAttach(x) (x && (x)->bdy)
45 #define IsRecvAttach(x) (x && (x)->bdy && (x)->fp)
46 #define IsSendAttach(x) (x && (x)->bdy && !(x)->fp)
47 #define IsMsgAttach(x) (x && (x)->fp && (x)->bdy && (x)->bdy->hdr)
48 #define IsHeader(x) (x && (x)->hdr && !(x)->bdy)
50 static const char *Not_available_in_this_menu = N_("Not available in this menu.");
51 static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
52 static const char *Function_not_permitted_in_attach_message_mode = N_("Function not permitted in attach-message mode.");
54 /* hack to return to position when returning from index to same message */
55 static int TopLine = 0;
56 static HEADER *OldHdr = NULL;
58 #define CHECK_MODE(x) if (!(x)) \
61 mutt_error _(Not_available_in_this_menu); \
65 #define CHECK_READONLY if (Context->readonly) \
68 mutt_error _(Mailbox_is_read_only); \
72 #define CHECK_ATTACH if(option(OPTATTACHMSG)) \
75 mutt_error _(Function_not_permitted_in_attach_message_mode); \
79 #define CHECK_ACL(aclbit,action) \
80 if (!mutt_bit_isset(Context->rights,aclbit)) { \
82 mutt_error (_("Cannot %s: Operation not permitted by ACL"), action); \
92 struct q_class_t *next, *prev;
93 struct q_class_t *down, *up;
110 struct syntax_t *syntax;
111 struct syntax_t *search;
112 struct q_class_t *quote;
113 unsigned int is_cont_hdr; /* this line is a continuation of the previous header line */
116 #define ANSI_OFF (1<<0)
117 #define ANSI_BLINK (1<<1)
118 #define ANSI_BOLD (1<<2)
119 #define ANSI_UNDERLINE (1<<3)
120 #define ANSI_REVERSE (1<<4)
121 #define ANSI_COLOR (1<<5)
123 typedef struct _ansi_attr {
130 static short InHelp = 0;
132 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
133 static struct resize {
140 #define NumSigLines 4
142 static int check_sig (const char *s, struct line_t *info, int n)
146 while (n > 0 && count <= NumSigLines)
148 if (info[n].type != MT_COLOR_SIGNATURE)
157 if (count > NumSigLines)
159 /* check for a blank line */
174 resolve_color (struct line_t *lineInfo, int n, int cnt, int flags, int special,
177 int def_color; /* color without syntax hilight */
178 int color; /* final color */
179 static int last_color; /* last color set */
180 int search = 0, i, m;
183 last_color = -1; /* force attrset() */
185 if (lineInfo[n].continuation)
187 if (!cnt && option (OPTMARKERS))
189 SETCOLOR (MT_COLOR_MARKERS);
191 last_color = ColorDefs[MT_COLOR_MARKERS];
193 m = (lineInfo[n].syntax)[0].first;
194 cnt += (lineInfo[n].syntax)[0].last;
198 if (!(flags & M_SHOWCOLOR))
199 def_color = ColorDefs[MT_COLOR_NORMAL];
200 else if (lineInfo[m].type == MT_COLOR_HEADER)
201 def_color = (lineInfo[m].syntax)[0].color;
203 def_color = ColorDefs[lineInfo[m].type];
205 if ((flags & M_SHOWCOLOR) && lineInfo[m].type == MT_COLOR_QUOTED)
207 struct q_class_t *class = lineInfo[m].quote;
211 def_color = class->color;
213 while (class && class->length > cnt)
215 def_color = class->color;
222 if (flags & M_SHOWCOLOR)
224 for (i = 0; i < lineInfo[m].chunks; i++)
226 /* we assume the chunks are sorted */
227 if (cnt > (lineInfo[m].syntax)[i].last)
229 if (cnt < (lineInfo[m].syntax)[i].first)
231 if (cnt != (lineInfo[m].syntax)[i].last)
233 color = (lineInfo[m].syntax)[i].color;
236 /* don't break here, as cnt might be
237 * in the next chunk as well */
241 if (flags & M_SEARCH)
243 for (i = 0; i < lineInfo[m].search_cnt; i++)
245 if (cnt > (lineInfo[m].search)[i].last)
247 if (cnt < (lineInfo[m].search)[i].first)
249 if (cnt != (lineInfo[m].search)[i].last)
251 color = ColorDefs[MT_COLOR_SEARCH];
258 /* handle "special" bold & underlined characters */
259 if (special || a->attr)
262 if ((a->attr & ANSI_COLOR))
265 a->pair = mutt_alloc_color (a->fg, a->bg);
267 if (a->attr & ANSI_BOLD)
272 if ((special & A_BOLD) || (a->attr & ANSI_BOLD))
274 if (ColorDefs[MT_COLOR_BOLD] && !search)
275 color = ColorDefs[MT_COLOR_BOLD];
279 if ((special & A_UNDERLINE) || (a->attr & ANSI_UNDERLINE))
281 if (ColorDefs[MT_COLOR_UNDERLINE] && !search)
282 color = ColorDefs[MT_COLOR_UNDERLINE];
284 color ^= A_UNDERLINE;
286 else if (a->attr & ANSI_REVERSE)
290 else if (a->attr & ANSI_BLINK)
294 else if (a->attr & ANSI_OFF)
300 if (color != last_color)
308 append_line (struct line_t *lineInfo, int n, int cnt)
312 lineInfo[n+1].type = lineInfo[n].type;
313 (lineInfo[n+1].syntax)[0].color = (lineInfo[n].syntax)[0].color;
314 lineInfo[n+1].continuation = 1;
316 /* find the real start of the line */
317 for (m = n; m >= 0; m--)
318 if (lineInfo[m].continuation == 0) break;
320 (lineInfo[n+1].syntax)[0].first = m;
321 (lineInfo[n+1].syntax)[0].last = (lineInfo[n].continuation) ?
322 cnt + (lineInfo[n].syntax)[0].last : cnt;
326 new_class_color (struct q_class_t *class, int *q_level)
328 class->index = (*q_level)++;
329 class->color = ColorQuote[class->index % ColorQuoteUsed];
333 shift_class_colors (struct q_class_t *QuoteList, struct q_class_t *new_class,
334 int index, int *q_level)
336 struct q_class_t * q_list;
339 new_class->index = -1;
343 if (q_list->index >= index)
346 q_list->color = ColorQuote[q_list->index % ColorQuoteUsed];
349 q_list = q_list->down;
350 else if (q_list->next)
351 q_list = q_list->next;
354 while (!q_list->next)
361 q_list = q_list->next;
365 new_class->index = index;
366 new_class->color = ColorQuote[index % ColorQuoteUsed];
371 cleanup_quote (struct q_class_t **QuoteList)
373 struct q_class_t *ptr;
377 if ((*QuoteList)->down)
378 cleanup_quote (&((*QuoteList)->down));
379 ptr = (*QuoteList)->next;
380 if ((*QuoteList)->prefix)
381 FREE (&(*QuoteList)->prefix);
382 FREE (QuoteList); /* __FREE_CHECKED__ */
389 static struct q_class_t *
390 classify_quote (struct q_class_t **QuoteList, const char *qptr,
391 int length, int *force_redraw, int *q_level)
393 struct q_class_t *q_list = *QuoteList;
394 struct q_class_t *class = NULL, *tmp = NULL, *ptr, *save;
396 int offset, tail_lng;
399 if (ColorQuoteUsed <= 1)
401 /* not much point in classifying quotes... */
403 if (*QuoteList == NULL)
405 class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
406 class->color = ColorQuote[0];
412 /* Did I mention how much I like emulating Lisp in C? */
414 /* classify quoting prefix */
417 if (length <= q_list->length)
419 /* case 1: check the top level nodes */
421 if (mutt_strncmp (qptr, q_list->prefix, length) == 0)
423 if (length == q_list->length)
424 return q_list; /* same prefix: return the current class */
426 /* found shorter prefix */
429 /* add a node above q_list */
430 tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
431 tmp->prefix = (char *) safe_calloc (1, length + 1);
432 strncpy (tmp->prefix, qptr, length);
433 tmp->length = length;
435 /* replace q_list by tmp in the top level list */
438 tmp->next = q_list->next;
439 q_list->next->prev = tmp;
443 tmp->prev = q_list->prev;
444 q_list->prev->next = tmp;
447 /* make q_list a child of tmp */
451 /* q_list has no siblings for now */
455 /* update the root if necessary */
456 if (q_list == *QuoteList)
459 index = q_list->index;
461 /* tmp should be the return class too */
464 /* next class to test; if tmp is a shorter prefix for another
465 * node, that node can only be in the top level list, so don't
466 * go down after this point
472 /* found another branch for which tmp is a shorter prefix */
474 /* save the next sibling for later */
477 /* unlink q_list from the top level list */
479 q_list->next->prev = q_list->prev;
481 q_list->prev->next = q_list->next;
483 /* at this point, we have a tmp->down; link q_list to it */
485 /* sibling order is important here, q_list should be linked last */
493 index = q_list->index;
495 /* next class to test; as above, we shouldn't go down */
499 /* we found a shorter prefix, so certain quotes have changed classes */
505 /* shorter, but not a substring of the current class: try next */
506 q_list = q_list->next;
512 /* case 2: try subclassing the current top level node */
514 /* tmp != NULL means we already found a shorter prefix at case 1 */
515 if (tmp == NULL && mutt_strncmp (qptr, q_list->prefix, q_list->length) == 0)
517 /* ok, it's a subclass somewhere on this branch */
520 offset = q_list->length;
522 q_list = q_list->down;
523 tail_lng = length - offset;
524 tail_qptr = (char *) qptr + offset;
528 if (length <= q_list->length)
530 if (mutt_strncmp (tail_qptr, (q_list->prefix) + offset, tail_lng) == 0)
532 /* same prefix: return the current class */
533 if (length == q_list->length)
536 /* found shorter common prefix */
539 /* add a node above q_list */
540 tmp = (struct q_class_t *) safe_calloc (1,
541 sizeof (struct q_class_t));
542 tmp->prefix = (char *) safe_calloc (1, length + 1);
543 strncpy (tmp->prefix, qptr, length);
544 tmp->length = length;
546 /* replace q_list by tmp */
549 tmp->next = q_list->next;
550 q_list->next->prev = tmp;
554 tmp->prev = q_list->prev;
555 q_list->prev->next = tmp;
558 /* make q_list a child of tmp */
560 tmp->up = q_list->up;
562 if (tmp->up->down == q_list)
565 /* q_list has no siblings */
569 index = q_list->index;
571 /* tmp should be the return class too */
574 /* next class to test */
579 /* found another branch for which tmp is a shorter prefix */
581 /* save the next sibling for later */
584 /* unlink q_list from the top level list */
586 q_list->next->prev = q_list->prev;
588 q_list->prev->next = q_list->next;
590 /* at this point, we have a tmp->down; link q_list to it */
599 index = q_list->index;
601 /* next class to test */
605 /* we found a shorter prefix, so we need a redraw */
611 q_list = q_list->next;
617 /* longer than the current prefix: try subclassing it */
618 if (tmp == NULL && mutt_strncmp (tail_qptr, (q_list->prefix) + offset,
619 q_list->length - offset) == 0)
621 /* still a subclass: go down one level */
623 offset = q_list->length;
625 q_list = q_list->down;
626 tail_lng = length - offset;
627 tail_qptr = (char *) qptr + offset;
633 /* nope, try the next prefix */
634 q_list = q_list->next;
640 /* still not found so far: add it as a sibling to the current node */
643 tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
644 tmp->prefix = (char *) safe_calloc (1, length + 1);
645 strncpy (tmp->prefix, qptr, length);
646 tmp->length = length;
650 tmp->next = ptr->down;
651 ptr->down->prev = tmp;
656 new_class_color (tmp, q_level);
663 shift_class_colors (*QuoteList, tmp, index, q_level);
670 /* nope, try the next prefix */
671 q_list = q_list->next;
679 /* not found so far: add it as a top level class */
680 class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
681 class->prefix = (char *) safe_calloc (1, length + 1);
682 strncpy (class->prefix, qptr, length);
683 class->length = length;
684 new_class_color (class, q_level);
688 class->next = *QuoteList;
689 (*QuoteList)->prev = class;
695 shift_class_colors (*QuoteList, tmp, index, q_level);
700 static int brailleLine = -1;
701 static int brailleCol = -1;
703 static int check_attachment_marker (char *);
706 resolve_types (char *buf, char *raw, struct line_t *lineInfo, int n, int last,
707 struct q_class_t **QuoteList, int *q_level, int *force_redraw,
710 COLOR_LINE *color_line;
711 regmatch_t pmatch[1], smatch[1];
712 int found, offset, null_rx, i;
714 if (n == 0 || ISHEADER (lineInfo[n-1].type))
716 if (buf[0] == '\n') /* end of header */
718 lineInfo[n].type = MT_COLOR_NORMAL;
719 getyx(stdscr, brailleLine, brailleCol);
723 /* if this is a continuation of the previous line, use the previous
724 * line's color as default. */
725 if (n > 0 && (buf[0] == ' ' || buf[0] == '\t'))
727 lineInfo[n].type = lineInfo[n-1].type; /* wrapped line */
728 (lineInfo[n].syntax)[0].color = (lineInfo[n-1].syntax)[0].color;
729 lineInfo[n].is_cont_hdr = 1;
733 lineInfo[n].type = MT_COLOR_HDEFAULT;
736 for (color_line = ColorHdrList; color_line; color_line = color_line->next)
738 if (REGEXEC (color_line->rx, buf) == 0)
740 lineInfo[n].type = MT_COLOR_HEADER;
741 lineInfo[n].syntax[0].color = color_line->pair;
742 if (lineInfo[n].is_cont_hdr)
744 /* adjust the previous continuation lines to reflect the color of this continuation line */
746 for (j = n - 1; j >= 0 && lineInfo[j].is_cont_hdr; --j)
748 lineInfo[j].type = lineInfo[n].type;
749 lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color;
751 /* now adjust the first line of this header field */
754 lineInfo[j].type = lineInfo[n].type;
755 lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color;
757 *force_redraw = 1; /* the previous lines have already been drawn on the screen */
764 else if (mutt_strncmp ("\033[0m", raw, 4) == 0) /* a little hack... */
765 lineInfo[n].type = MT_COLOR_NORMAL;
767 else if (mutt_strncmp ("[-- ", buf, 4) == 0)
768 lineInfo[n].type = MT_COLOR_ATTACHMENT;
770 else if (check_attachment_marker ((char *) raw) == 0)
771 lineInfo[n].type = MT_COLOR_ATTACHMENT;
773 else if (mutt_strcmp ("-- \n", buf) == 0 || mutt_strcmp ("-- \r\n", buf) == 0)
777 lineInfo[n].type = MT_COLOR_SIGNATURE;
778 while (i < last && check_sig (buf, lineInfo, i - 1) == 0 &&
779 (lineInfo[i].type == MT_COLOR_NORMAL ||
780 lineInfo[i].type == MT_COLOR_QUOTED ||
781 lineInfo[i].type == MT_COLOR_HEADER))
784 if (lineInfo[i].chunks)
786 lineInfo[i].chunks = 0;
787 safe_realloc (&(lineInfo[n].syntax),
788 sizeof (struct syntax_t));
790 lineInfo[i++].type = MT_COLOR_SIGNATURE;
793 else if (check_sig (buf, lineInfo, n - 1) == 0)
794 lineInfo[n].type = MT_COLOR_SIGNATURE;
795 else if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0)
797 if (regexec ((regex_t *) Smileys.rx, buf, 1, smatch, 0) == 0)
799 if (smatch[0].rm_so > 0)
803 /* hack to avoid making an extra copy of buf */
804 c = buf[smatch[0].rm_so];
805 buf[smatch[0].rm_so] = 0;
807 if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0)
809 if (q_classify && lineInfo[n].quote == NULL)
810 lineInfo[n].quote = classify_quote (QuoteList,
811 buf + pmatch[0].rm_so,
812 pmatch[0].rm_eo - pmatch[0].rm_so,
813 force_redraw, q_level);
814 lineInfo[n].type = MT_COLOR_QUOTED;
817 lineInfo[n].type = MT_COLOR_NORMAL;
819 buf[smatch[0].rm_so] = c;
822 lineInfo[n].type = MT_COLOR_NORMAL;
826 if (q_classify && lineInfo[n].quote == NULL)
827 lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so,
828 pmatch[0].rm_eo - pmatch[0].rm_so,
829 force_redraw, q_level);
830 lineInfo[n].type = MT_COLOR_QUOTED;
834 lineInfo[n].type = MT_COLOR_NORMAL;
837 if (lineInfo[n].type == MT_COLOR_NORMAL ||
838 lineInfo[n].type == MT_COLOR_QUOTED)
842 /* don't consider line endings part of the buffer
843 * for regex matching */
844 if ((nl = mutt_strlen (buf)) > 0 && buf[nl-1] == '\n')
849 lineInfo[n].chunks = 0;
857 color_line = ColorBodyList;
860 if (regexec (&color_line->rx, buf + offset, 1, pmatch,
861 (offset ? REG_NOTBOL : 0)) == 0)
863 if (pmatch[0].rm_eo != pmatch[0].rm_so)
867 if (++(lineInfo[n].chunks) > 1)
868 safe_realloc (&(lineInfo[n].syntax),
869 (lineInfo[n].chunks) * sizeof (struct syntax_t));
871 i = lineInfo[n].chunks - 1;
872 pmatch[0].rm_so += offset;
873 pmatch[0].rm_eo += offset;
875 pmatch[0].rm_so < (lineInfo[n].syntax)[i].first ||
876 (pmatch[0].rm_so == (lineInfo[n].syntax)[i].first &&
877 pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last))
879 (lineInfo[n].syntax)[i].color = color_line->pair;
880 (lineInfo[n].syntax)[i].first = pmatch[0].rm_so;
881 (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo;
887 null_rx = 1; /* empty regexp; don't add it, but keep looking */
889 color_line = color_line->next;
893 offset++; /* avoid degenerate cases */
895 offset = (lineInfo[n].syntax)[i].last;
896 } while (found || null_rx);
902 static int is_ansi (unsigned char *buf)
904 while (*buf && (isdigit(*buf) || *buf == ';'))
906 return (*buf == 'm');
909 static int check_attachment_marker (char *p)
911 char *q = AttachmentMarker;
913 for (;*p == *q && *q && *p && *q != '\a' && *p != '\a'; p++, q++)
915 return (int) (*p - *q);
918 static int grok_ansi(unsigned char *buf, int pos, ansi_attr *a)
922 while (isdigit(buf[x]) || buf[x] == ';')
925 /* Character Attributes */
926 if (option (OPTALLOWANSI) && a != NULL && buf[x] == 'm')
932 mutt_free_color (a->fg, a->bg);
939 if (buf[pos] == '1' && (pos+1 == x || buf[pos+1] == ';'))
941 a->attr |= ANSI_BOLD;
944 else if (buf[pos] == '4' && (pos+1 == x || buf[pos+1] == ';'))
946 a->attr |= ANSI_UNDERLINE;
949 else if (buf[pos] == '5' && (pos+1 == x || buf[pos+1] == ';'))
951 a->attr |= ANSI_BLINK;
954 else if (buf[pos] == '7' && (pos+1 == x || buf[pos+1] == ';'))
956 a->attr |= ANSI_REVERSE;
959 else if (buf[pos] == '0' && (pos+1 == x || buf[pos+1] == ';'))
963 mutt_free_color(a->fg,a->bg);
969 else if (buf[pos] == '3' && isdigit(buf[pos+1]))
973 mutt_free_color(a->fg,a->bg);
976 a->attr |= ANSI_COLOR;
977 a->fg = buf[pos+1] - '0';
980 else if (buf[pos] == '4' && isdigit(buf[pos+1]))
984 mutt_free_color(a->fg,a->bg);
987 a->attr |= ANSI_COLOR;
988 a->bg = buf[pos+1] - '0';
993 while (pos < x && buf[pos] != ';') pos++;
1002 /* trim tail of buf so that it contains complete multibyte characters */
1004 trim_incomplete_mbyte(unsigned char *buf, size_t len)
1009 memset (&mbstate, 0, sizeof (mbstate));
1010 for (; len > 0; buf += k, len -= k)
1012 k = mbrtowc (NULL, (char *) buf, len, &mbstate);
1015 else if (k == -1 || k == 0)
1024 fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf,
1025 unsigned char **fmt, size_t *blen, int *buf_ready)
1027 unsigned char *p, *q;
1031 if (*buf_ready == 0)
1033 if (offset != *last_pos)
1034 fseeko (f, offset, 0);
1035 if ((*buf = (unsigned char *) mutt_read_line ((char *) *buf, blen, f, &l, M_EOL)) == NULL)
1040 *last_pos = ftello (f);
1041 b_read = (int) (*last_pos - offset);
1044 safe_realloc (fmt, *blen);
1046 /* incomplete mbyte characters trigger a segfault in regex processing for
1047 * certain versions of glibc. Trim them if necessary. */
1048 if (b_read == *blen - 2)
1049 b_read -= trim_incomplete_mbyte(*buf, b_read);
1051 /* copy "buf" to "fmt", but without bold and underline controls */
1056 if (*p == '\010' && (p > *buf))
1058 if (*(p+1) == '_') /* underline */
1060 else if (*(p+1) && q > *fmt) /* bold or overstrike */
1068 else if (*p == '\033' && *(p+1) == '[' && is_ansi (p + 2))
1070 while (*p++ != 'm') /* skip ANSI sequence */
1073 else if (*p == '\033' && *(p+1) == ']' && check_attachment_marker ((char *) p) == 0)
1075 dprint (2, (debugfile, "fill_buffer: Seen attachment marker.\n"));
1076 while (*p++ != '\a') /* skip pseudo-ANSI sequence */
1088 static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
1089 int flags, ansi_attr *pa, int cnt,
1090 int *pspace, int *pvch, int *pcol, int *pspecial)
1092 int space = -1; /* index of the last space or TAB */
1093 int col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0;
1094 int ch, vch, k, last_special = -1, special = 0, t;
1097 int wrap_cols = mutt_term_width ((flags & M_PAGER_NOWRAP) ? 0 : Wrap);
1099 if (check_attachment_marker ((char *)buf) == 0)
1102 /* FIXME: this should come from lineInfo */
1103 memset(&mbstate, 0, sizeof(mbstate));
1105 for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k)
1107 /* Handle ANSI sequences */
1108 while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == '[' &&
1110 ch = grok_ansi (buf, ch+2, pa) + 1;
1112 while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == ']' &&
1113 check_attachment_marker ((char *) buf+ch) == 0)
1115 while (buf[ch++] != '\a')
1120 /* is anything left to do? */
1124 k = mbrtowc (&wc, (char *)buf+ch, cnt-ch, &mbstate);
1125 if (k == -2 || k == -1)
1127 dprint (1, (debugfile, "%s:%d: mbrtowc returned %d; errno = %d.\n",
1128 __FILE__, __LINE__, k, errno));
1129 if (col + 4 > wrap_cols)
1133 printw ("\\%03o", buf[ch]);
1140 if (Charset_is_utf8 && (wc == 0x200B || wc == 0xFEFF))
1142 dprint (3, (debugfile, "skip zero-width character U+%04X\n", (unsigned short)wc));
1146 /* Handle backspace */
1154 while ((wc1 = 0, mbstate1 = mbstate,
1155 k1 = k + mbrtowc (&wc1, (char *)buf+ch+k, cnt-ch-k, &mbstate1),
1156 k1 - k > 0 && wc1 == '\b') &&
1158 k2 = mbrtowc (&wc1, (char *)buf+ch+k1, cnt-ch-k1, &mbstate1),
1159 k2 > 0 && IsWPrint (wc1)))
1163 special |= (wc == '_' && special & A_UNDERLINE)
1164 ? A_UNDERLINE : A_BOLD;
1166 else if (wc == '_' || wc1 == '_')
1168 special |= A_UNDERLINE;
1169 wc = (wc1 == '_') ? wc : wc1;
1173 /* special = 0; / * overstrike: nothing to do! */
1183 ((flags & (M_SHOWCOLOR | M_SEARCH | M_PAGER_MARKER)) ||
1184 special || last_special || pa->attr))
1186 resolve_color (*lineInfo, n, vch, flags, special, pa);
1187 last_special = special;
1195 if (col + t > wrap_cols)
1201 else if (wc == '\n')
1203 else if (wc == '\t')
1210 for (; col < t; col++)
1215 else if (wc < 0x20 || wc == 0x7f)
1217 if (col + 2 > wrap_cols)
1221 printw ("^%c", ('@' + wc) & 0x7f);
1223 else if (wc < 0x100)
1225 if (col + 4 > wrap_cols)
1229 printw ("\\%03o", wc);
1233 if (col + 1 > wrap_cols)
1237 addch (replacement_char ());
1243 *pspecial = special;
1249 * flags M_SHOWFLAT, show characters (used for displaying help)
1250 * M_SHOWCOLOR, show characters in color
1251 * otherwise don't show characters
1252 * M_HIDE, don't show quoted text
1253 * M_SEARCH, resolve search patterns
1254 * M_TYPES, compute line's type
1255 * M_PAGER_NSKIP, keeps leading whitespace
1256 * M_PAGER_MARKER, eventually show markers
1259 * -1 EOF was reached
1260 * 0 normal exit, line was not displayed
1261 * >0 normal exit, line was displayed
1265 display_line (FILE *f, LOFF_T *last_pos, struct line_t **lineInfo, int n,
1266 int *last, int *max, int flags, struct q_class_t **QuoteList,
1267 int *q_level, int *force_redraw, regex_t *SearchRE)
1269 unsigned char *buf = NULL, *fmt = NULL;
1271 unsigned char *buf_ptr = buf;
1272 int ch, vch, col, cnt, b_read;
1273 int buf_ready = 0, change_last = 0;
1279 ansi_attr a = {0,0,0,-1};
1280 regmatch_t pmatch[1];
1290 safe_realloc (lineInfo, sizeof (struct line_t) * (*max += LINES));
1291 for (ch = *last; ch < *max ; ch++)
1293 memset (&((*lineInfo)[ch]), 0, sizeof (struct line_t));
1294 (*lineInfo)[ch].type = -1;
1295 (*lineInfo)[ch].search_cnt = -1;
1296 (*lineInfo)[ch].syntax = safe_malloc (sizeof (struct syntax_t));
1297 ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last = -1;
1301 /* only do color hiliting if we are viewing a message */
1302 if (flags & (M_SHOWCOLOR | M_TYPES))
1304 if ((*lineInfo)[n].type == -1)
1306 /* determine the line class */
1307 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1314 resolve_types ((char *) fmt, (char *) buf, *lineInfo, n, *last,
1315 QuoteList, q_level, force_redraw, flags & M_SHOWCOLOR);
1317 /* avoid race condition for continuation lines when scrolling up */
1318 for (m = n + 1; m < *last && (*lineInfo)[m].offset && (*lineInfo)[m].continuation; m++)
1319 (*lineInfo)[m].type = (*lineInfo)[n].type;
1322 /* this also prevents searching through the hidden lines */
1323 if ((flags & M_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED)
1324 flags = 0; /* M_NOSHOW */
1327 /* At this point, (*lineInfo[n]).quote may still be undefined. We
1328 * don't want to compute it every time M_TYPES is set, since this
1329 * would slow down the "bottom" function unacceptably. A compromise
1330 * solution is hence to call regexec() again, just to find out the
1331 * length of the quote prefix.
1333 if ((flags & M_SHOWCOLOR) && !(*lineInfo)[n].continuation &&
1334 (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL)
1336 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1342 regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0);
1343 (*lineInfo)[n].quote = classify_quote (QuoteList,
1344 (char *) fmt + pmatch[0].rm_so,
1345 pmatch[0].rm_eo - pmatch[0].rm_so,
1346 force_redraw, q_level);
1349 if ((flags & M_SEARCH) && !(*lineInfo)[n].continuation && (*lineInfo)[n].search_cnt == -1)
1351 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1359 (*lineInfo)[n].search_cnt = 0;
1360 while (regexec (SearchRE, (char *) fmt + offset, 1, pmatch, (offset ? REG_NOTBOL : 0)) == 0)
1362 if (++((*lineInfo)[n].search_cnt) > 1)
1363 safe_realloc (&((*lineInfo)[n].search),
1364 ((*lineInfo)[n].search_cnt) * sizeof (struct syntax_t));
1366 (*lineInfo)[n].search = safe_malloc (sizeof (struct syntax_t));
1367 pmatch[0].rm_so += offset;
1368 pmatch[0].rm_eo += offset;
1369 ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first = pmatch[0].rm_so;
1370 ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last = pmatch[0].rm_eo;
1372 if (pmatch[0].rm_eo == pmatch[0].rm_so)
1373 offset++; /* avoid degenerate cases */
1375 offset = pmatch[0].rm_eo;
1381 if (!(flags & M_SHOW) && (*lineInfo)[n+1].offset > 0)
1383 /* we've already scanned this line, so just exit */
1387 if ((flags & M_SHOWCOLOR) && *force_redraw && (*lineInfo)[n+1].offset > 0)
1389 /* no need to try to display this line... */
1391 goto out; /* fake display */
1394 if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt,
1395 &buflen, &buf_ready)) < 0)
1402 /* now chose a good place to break the line */
1403 cnt = format_line (lineInfo, n, buf, flags, 0, b_read, &ch, &vch, &col, &special);
1404 buf_ptr = buf + cnt;
1406 /* move the break point only if smart_wrap is set */
1407 if (option (OPTWRAP))
1411 if (ch != -1 && buf[0] != ' ' && buf[0] != '\t' &&
1412 buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n' && buf[cnt] != '\r')
1415 /* skip trailing blanks */
1416 while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r'))
1418 /* a very long word with leading spaces causes infinite wrapping */
1419 if ((!ch) && (flags & M_PAGER_NSKIP))
1420 buf_ptr = buf + cnt;
1425 buf_ptr = buf + cnt; /* a very long word... */
1427 if (!(flags & M_PAGER_NSKIP))
1428 /* skip leading blanks on the next line too */
1429 while (*buf_ptr == ' ' || *buf_ptr == '\t')
1433 if (*buf_ptr == '\r')
1435 if (*buf_ptr == '\n')
1438 if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n+1].continuation)
1439 append_line (*lineInfo, n, (int) (buf_ptr - buf));
1440 (*lineInfo)[n+1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf);
1442 /* if we don't need to display the line we are done */
1443 if (!(flags & M_SHOW))
1449 /* display the line */
1450 format_line (lineInfo, n, buf, flags, &a, cnt, &ch, &vch, &col, &special);
1452 /* avoid a bug in ncurses... */
1453 #ifndef USE_SLANG_CURSES
1456 SETCOLOR (MT_COLOR_NORMAL);
1461 /* end the last color pattern (needed by S-Lang) */
1462 if (special || (col != COLS && (flags & (M_SHOWCOLOR | M_SEARCH))))
1463 resolve_color (*lineInfo, n, vch, flags, 0, &a);
1466 * Fill the blank space at the end of the line with the prevailing color.
1467 * ncurses does an implicit clrtoeol() when you do addch('\n') so we have
1468 * to make sure to reset the color *after* that
1470 if (flags & M_SHOWCOLOR)
1472 m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n;
1473 if ((*lineInfo)[m].type == MT_COLOR_HEADER)
1474 def_color = ((*lineInfo)[m].syntax)[0].color;
1476 def_color = ColorDefs[ (*lineInfo)[m].type ];
1478 attrset (def_color);
1480 bkgdset (def_color | ' ');
1484 /* ncurses always wraps lines when you get to the right side of the
1485 * screen, but S-Lang seems to only wrap if the next character is *not*
1488 #ifndef USE_SLANG_CURSES
1494 * reset the color back to normal. This *must* come after the
1495 * addch('\n'), otherwise the color for this line will not be
1496 * filled to the right margin.
1498 if (flags & M_SHOWCOLOR)
1500 SETCOLOR(MT_COLOR_NORMAL);
1501 BKGDSET(MT_COLOR_NORMAL);
1504 /* build a return code */
1505 if (!(flags & M_SHOW))
1517 upNLines (int nlines, struct line_t *info, int cur, int hiding)
1519 while (cur > 0 && nlines > 0)
1522 if (!hiding || info[cur].type != MT_COLOR_QUOTED)
1529 static struct mapping_t PagerHelp[] = {
1530 { N_("Exit"), OP_EXIT },
1531 { N_("PrevPg"), OP_PREV_PAGE },
1532 { N_("NextPg"), OP_NEXT_PAGE },
1535 static struct mapping_t PagerHelpExtra[] = {
1536 { N_("View Attachm."), OP_VIEW_ATTACHMENTS },
1537 { N_("Del"), OP_DELETE },
1538 { N_("Reply"), OP_REPLY },
1539 { N_("Next"), OP_MAIN_NEXT_UNDELETED },
1545 /* This pager is actually not so simple as it once was. It now operates in
1546 two modes: one for viewing messages and the other for viewing help. These
1547 can be distinguished by whether or not ``hdr'' is NULL. The ``hdr'' arg
1548 is there so that we can do operations on the current message without the
1549 need to pop back out to the main-menu. */
1551 mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
1553 static char searchbuf[STRING] = "";
1554 char buffer[LONG_STRING];
1555 char helpstr[SHORT_STRING*2];
1556 char tmphelp[SHORT_STRING*2];
1557 int maxLine, lastLine = 0;
1558 struct line_t *lineInfo;
1559 struct q_class_t *QuoteList = NULL;
1560 int i, j, ch = 0, rc = -1, hideQuoted = 0, q_level = 0, force_redraw = 0;
1561 int lines = 0, curline = 0, topline = 0, oldtopline = 0, err, first = 1;
1562 int r = -1, wrapped = 0, searchctx = 0;
1563 int redraw = REDRAW_FULL;
1565 LOFF_T last_pos = 0, last_offset = 0;
1566 int old_smart_wrap, old_markers;
1569 int SearchCompiled = 0, SearchFlag = 0, SearchBack = 0;
1570 int has_types = (IsHeader(extra) || (flags & M_SHOWCOLOR)) ? M_TYPES : 0; /* main message or rfc822 attachment */
1572 int bodyoffset = 1; /* offset of first line of real text */
1573 int statusoffset = 0; /* offset for the status bar */
1574 int helpoffset = LINES - 2; /* offset for the help bar. */
1575 int bodylen = LINES - 2 - bodyoffset; /* length of displayable area */
1577 MUTTMENU *index = NULL; /* the Pager Index (PI) */
1578 int indexoffset = 0; /* offset for the PI */
1579 int indexlen = PagerIndexLines; /* indexlen not always == PIL */
1580 int indicator = indexlen / 3; /* the indicator line of the PI */
1581 int old_PagerIndexLines; /* some people want to resize it
1582 * while inside the pager... */
1584 if (!(flags & M_SHOWCOLOR))
1585 flags |= M_SHOWFLAT;
1587 if ((fp = fopen (fname, "r")) == NULL)
1589 mutt_perror (fname);
1593 if (stat (fname, &sb) != 0)
1595 mutt_perror (fname);
1601 /* Initialize variables */
1603 if (IsHeader (extra) && !extra->hdr->read)
1605 Context->msgnotreadyet = extra->hdr->msgno;
1606 mutt_set_flag (Context, extra->hdr, M_READ, 1);
1609 lineInfo = safe_malloc (sizeof (struct line_t) * (maxLine = LINES));
1610 for (i = 0 ; i < maxLine ; i++)
1612 memset (&lineInfo[i], 0, sizeof (struct line_t));
1613 lineInfo[i].type = -1;
1614 lineInfo[i].search_cnt = -1;
1615 lineInfo[i].syntax = safe_malloc (sizeof (struct syntax_t));
1616 (lineInfo[i].syntax)[0].first = (lineInfo[i].syntax)[0].last = -1;
1619 mutt_compile_help (helpstr, sizeof (helpstr), MENU_PAGER, PagerHelp);
1620 if (IsHeader (extra))
1622 strfcpy (tmphelp, helpstr, sizeof (tmphelp));
1623 mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra);
1624 snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
1628 strfcpy (tmphelp, helpstr, sizeof (tmphelp));
1629 mutt_make_help (buffer, sizeof (buffer), _("Help"), MENU_PAGER, OP_HELP);
1630 snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
1637 if (redraw & REDRAW_FULL)
1639 SETCOLOR (MT_COLOR_NORMAL);
1640 /* clear() doesn't optimize screen redraws */
1644 if (IsHeader (extra) && Context->vcount + 1 < PagerIndexLines)
1645 indexlen = Context->vcount + 1;
1647 indexlen = PagerIndexLines;
1649 indicator = indexlen / 3;
1651 if (option (OPTSTATUSONTOP))
1654 statusoffset = IsHeader (extra) ? indexlen : 0;
1655 bodyoffset = statusoffset + 1;
1656 helpoffset = LINES - 2;
1657 bodylen = helpoffset - bodyoffset;
1658 if (!option (OPTHELP))
1665 statusoffset = LINES - 2;
1666 if (!option (OPTHELP))
1668 bodyoffset = indexoffset + (IsHeader (extra) ? indexlen : 0);
1669 bodylen = statusoffset - bodyoffset;
1672 if (option (OPTHELP))
1674 SETCOLOR (MT_COLOR_STATUS);
1675 move (helpoffset, 0);
1676 mutt_paddstr (COLS, helpstr);
1677 SETCOLOR (MT_COLOR_NORMAL);
1680 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1683 if ((SearchCompiled = Resize->SearchCompiled))
1686 (&SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf));
1687 SearchFlag = M_SEARCH;
1688 SearchBack = Resize->SearchBack;
1690 lines = Resize->line;
1691 redraw |= REDRAW_SIGWINCH;
1697 if (IsHeader (extra) && PagerIndexLines)
1701 /* only allocate the space if/when we need the index.
1702 Initialise the menu as per the main index */
1703 index = mutt_new_menu(MENU_MAIN);
1704 index->make_entry = index_make_entry;
1705 index->color = index_color;
1706 index->max = Context->vcount;
1707 index->current = extra->hdr->virtual;
1710 SETCOLOR (MT_COLOR_NORMAL);
1711 index->offset = indexoffset + (option (OPTSTATUSONTOP) ? 1 : 0);
1713 index->pagelen = indexlen - 1;
1715 /* some fudge to work out where abouts the indicator should go */
1716 if (index->current - indicator < 0)
1718 else if (index->max - index->current < index->pagelen - indicator)
1719 index->top = index->max - index->pagelen;
1721 index->top = index->current - indicator;
1723 menu_redraw_index(index);
1726 redraw |= REDRAW_BODY | REDRAW_INDEX | REDRAW_STATUS;
1730 if (redraw & REDRAW_SIGWINCH)
1734 while (display_line (fp, &last_pos, &lineInfo, ++i, &lastLine, &maxLine,
1735 has_types | SearchFlag | (flags & M_PAGER_NOWRAP), &QuoteList, &q_level, &force_redraw,
1737 if (!lineInfo[i].continuation && ++j == lines)
1745 if ((redraw & REDRAW_BODY) || topline != oldtopline)
1748 move (bodyoffset, 0);
1749 curline = oldtopline = topline;
1753 while (lines < bodylen && lineInfo[curline].offset <= sb.st_size - 1)
1755 if (display_line (fp, &last_pos, &lineInfo, curline, &lastLine,
1757 (flags & M_DISPLAYFLAGS) | hideQuoted | SearchFlag | (flags & M_PAGER_NOWRAP),
1758 &QuoteList, &q_level, &force_redraw, &SearchRE) > 0)
1762 last_offset = lineInfo[curline].offset;
1763 } while (force_redraw);
1765 SETCOLOR (MT_COLOR_TILDE);
1766 BKGDSET (MT_COLOR_TILDE);
1767 while (lines < bodylen)
1770 if (option (OPTTILDE))
1775 /* We are going to update the pager status bar, so it isn't
1776 * necessary to reset to normal color now. */
1778 redraw |= REDRAW_STATUS; /* need to update the % seen */
1781 if (redraw & REDRAW_STATUS)
1783 struct hdr_format_info hfi;
1784 char pager_progress_str[4];
1787 hfi.pager_progress = pager_progress_str;
1789 if (last_pos < sb.st_size - 1)
1790 snprintf(pager_progress_str, sizeof(pager_progress_str), OFF_T_FMT "%%", (100 * last_offset / sb.st_size));
1792 strfcpy(pager_progress_str, (topline == 0) ? "all" : "end", sizeof(pager_progress_str));
1794 /* print out the pager status bar */
1795 SETCOLOR (MT_COLOR_STATUS);
1796 BKGDSET (MT_COLOR_STATUS);
1797 CLEARLINE (statusoffset);
1799 if (IsHeader (extra) || IsMsgAttach (extra))
1801 size_t l1 = COLS * MB_LEN_MAX;
1802 size_t l2 = sizeof (buffer);
1803 hfi.hdr = (IsHeader (extra)) ? extra->hdr : extra->bdy->hdr;
1804 mutt_make_string_info (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt), &hfi, M_FORMAT_MAKEPRINT);
1805 mutt_paddstr (COLS, buffer);
1810 snprintf (bn, sizeof (bn), "%s (%s)", banner, pager_progress_str);
1811 mutt_paddstr (COLS, bn);
1813 BKGDSET (MT_COLOR_NORMAL);
1814 SETCOLOR (MT_COLOR_NORMAL);
1817 if ((redraw & REDRAW_INDEX) && index)
1819 /* redraw the pager_index indicator, because the
1820 * flags for this message might have changed. */
1821 menu_redraw_current (index);
1823 /* print out the index status bar */
1824 menu_status_line (buffer, sizeof (buffer), index, NONULL(Status));
1826 move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), 0);
1827 SETCOLOR (MT_COLOR_STATUS);
1828 BKGDSET (MT_COLOR_STATUS);
1829 mutt_paddstr (COLS, buffer);
1830 SETCOLOR (MT_COLOR_NORMAL);
1831 BKGDSET (MT_COLOR_NORMAL);
1836 if (option(OPTBRAILLEFRIENDLY)) {
1837 if (brailleLine!=-1) {
1838 move(brailleLine+1, 0);
1841 } else move (statusoffset, COLS-1);
1844 if (IsHeader (extra) && OldHdr == extra->hdr && TopLine != topline
1845 && lineInfo[curline].offset < sb.st_size-1)
1847 if (TopLine - topline > lines)
1856 ch = km_dokey (MENU_PAGER);
1858 mutt_clear_error ();
1866 #if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
1869 mutt_resize_screen ();
1871 /* Store current position. */
1873 for (i = 0; i <= topline; i++)
1874 if (!lineInfo[i].continuation)
1877 if (flags & M_PAGER_RETWINCH)
1879 Resize = safe_malloc (sizeof (struct resize));
1881 Resize->line = lines;
1882 Resize->SearchCompiled = SearchCompiled;
1883 Resize->SearchBack = SearchBack;
1886 rc = OP_REFORMAT_WINCH;
1890 for (i = 0; i < maxLine; i++)
1892 lineInfo[i].offset = 0;
1893 lineInfo[i].type = -1;
1894 lineInfo[i].continuation = 0;
1895 lineInfo[i].chunks = 0;
1896 lineInfo[i].search_cnt = -1;
1897 lineInfo[i].quote = NULL;
1899 safe_realloc (&(lineInfo[i].syntax),
1900 sizeof (struct syntax_t));
1901 if (SearchCompiled && lineInfo[i].search)
1902 FREE (&(lineInfo[i].search));
1908 redraw = REDRAW_FULL | REDRAW_SIGWINCH;
1913 clearok(stdscr,TRUE);/*force complete redraw*/
1933 if (lineInfo[curline].offset < sb.st_size-1)
1935 topline = upNLines (PagerContext, lineInfo, curline, hideQuoted);
1937 else if (option (OPTPAGERSTOP))
1939 /* emulate "less -q" and don't go on to the next message. */
1940 mutt_error _("Bottom of message is shown.");
1944 /* end of the current message, so display the next message. */
1945 rc = OP_MAIN_NEXT_UNDELETED;
1953 topline = upNLines (bodylen-PagerContext, lineInfo, topline, hideQuoted);
1956 mutt_error _("Top of message is shown.");
1960 if (lineInfo[curline].offset < sb.st_size-1)
1965 while (lineInfo[topline].type == MT_COLOR_QUOTED &&
1971 mutt_error _("Bottom of message is shown.");
1976 topline = upNLines (1, lineInfo, topline, hideQuoted);
1978 mutt_error _("Top of message is shown.");
1985 mutt_error _("Top of message is shown.");
1990 topline = upNLines (bodylen/2, lineInfo, topline, hideQuoted);
1992 mutt_error _("Top of message is shown.");
1996 if (lineInfo[curline].offset < sb.st_size-1)
1998 topline = upNLines (bodylen/2, lineInfo, curline, hideQuoted);
2000 else if (option (OPTPAGERSTOP))
2002 /* emulate "less -q" and don't go on to the next message. */
2003 mutt_error _("Bottom of message is shown.");
2007 /* end of the current message, so display the next message. */
2008 rc = OP_MAIN_NEXT_UNDELETED;
2013 case OP_SEARCH_NEXT:
2014 case OP_SEARCH_OPPOSITE:
2019 if (SearchContext > 0 && SearchContext < LINES - 2 - option (OPTHELP) ? 1 : 0)
2020 searchctx = SearchContext;
2025 if ((!SearchBack && ch==OP_SEARCH_NEXT) ||
2026 (SearchBack &&ch==OP_SEARCH_OPPOSITE))
2028 /* searching forward */
2029 for (i = wrapped ? 0 : topline + searchctx + 1; i < lastLine; i++)
2031 if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
2032 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
2038 else if (wrapped || !option (OPTWRAPSEARCH))
2039 mutt_error _("Not found.");
2042 mutt_message _("Search wrapped to top.");
2049 /* searching backward */
2050 for (i = wrapped ? lastLine : topline + searchctx - 1; i >= 0; i--)
2052 if ((!hideQuoted || (has_types &&
2053 lineInfo[i].type != MT_COLOR_QUOTED)) &&
2054 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
2060 else if (wrapped || !option (OPTWRAPSEARCH))
2061 mutt_error _("Not found.");
2064 mutt_message _("Search wrapped to bottom.");
2070 if (lineInfo[topline].search_cnt > 0)
2072 SearchFlag = M_SEARCH;
2073 /* give some context for search results */
2074 if (topline - searchctx > 0)
2075 topline -= searchctx;
2080 /* no previous search pattern, so fall through to search */
2083 case OP_SEARCH_REVERSE:
2084 strfcpy (buffer, searchbuf, sizeof (buffer));
2085 if (mutt_get_field ((ch == OP_SEARCH || ch == OP_SEARCH_NEXT) ?
2086 _("Search for: ") : _("Reverse search for: "),
2087 buffer, sizeof (buffer),
2091 if (!strcmp (buffer, searchbuf))
2095 /* do an implicit search-next */
2096 if (ch == OP_SEARCH)
2097 ch = OP_SEARCH_NEXT;
2099 ch = OP_SEARCH_OPPOSITE;
2109 strfcpy (searchbuf, buffer, sizeof (searchbuf));
2111 /* leave SearchBack alone if ch == OP_SEARCH_NEXT */
2112 if (ch == OP_SEARCH)
2114 else if (ch == OP_SEARCH_REVERSE)
2119 regfree (&SearchRE);
2120 for (i = 0; i < lastLine; i++)
2122 if (lineInfo[i].search)
2123 FREE (&(lineInfo[i].search));
2124 lineInfo[i].search_cnt = -1;
2128 if ((err = REGCOMP (&SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf))) != 0)
2130 regerror (err, &SearchRE, buffer, sizeof (buffer));
2131 mutt_error ("%s", buffer);
2132 for (i = 0; i < maxLine ; i++)
2135 if (lineInfo[i].search)
2136 FREE (&(lineInfo[i].search));
2137 lineInfo[i].search_cnt = -1;
2145 /* update the search pointers */
2147 while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
2148 &maxLine, M_SEARCH | (flags & M_PAGER_NSKIP) | (flags & M_PAGER_NOWRAP),
2149 &QuoteList, &q_level,
2150 &force_redraw, &SearchRE) == 0)
2155 /* searching forward */
2156 for (i = topline; i < lastLine; i++)
2158 if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
2159 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
2163 if (i < lastLine) topline = i;
2167 /* searching backward */
2168 for (i = topline; i >= 0; i--)
2170 if ((!hideQuoted || lineInfo[i].type != MT_COLOR_QUOTED) &&
2171 !lineInfo[i].continuation && lineInfo[i].search_cnt > 0)
2175 if (i >= 0) topline = i;
2178 if (lineInfo[topline].search_cnt == 0)
2181 mutt_error _("Not found.");
2185 SearchFlag = M_SEARCH;
2186 /* give some context for search results */
2187 if (SearchContext > 0 && SearchContext < LINES - 2 - option (OPTHELP) ? 1 : 0)
2188 searchctx = SearchContext;
2191 if (topline - searchctx > 0)
2192 topline -= searchctx;
2196 redraw = REDRAW_BODY;
2199 case OP_SEARCH_TOGGLE:
2202 SearchFlag ^= M_SEARCH;
2203 redraw = REDRAW_BODY;
2208 /* don't let the user enter the help-menu from the help screen! */
2212 mutt_help (MENU_PAGER);
2213 redraw = REDRAW_FULL;
2217 mutt_error _("Help is currently being shown.");
2220 case OP_PAGER_HIDE_QUOTED:
2223 hideQuoted ^= M_HIDE;
2224 if (hideQuoted && lineInfo[topline].type == MT_COLOR_QUOTED)
2225 topline = upNLines (1, lineInfo, topline, hideQuoted);
2227 redraw = REDRAW_BODY;
2231 case OP_PAGER_SKIP_QUOTED:
2235 int new_topline = topline;
2237 while ((new_topline < lastLine ||
2238 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
2239 new_topline, &lastLine, &maxLine, M_TYPES | (flags & M_PAGER_NOWRAP),
2240 &QuoteList, &q_level, &force_redraw, &SearchRE))))
2241 && lineInfo[new_topline].type != MT_COLOR_QUOTED)
2246 mutt_error _("No more quoted text.");
2250 while ((new_topline < lastLine ||
2251 (0 == (dretval = display_line (fp, &last_pos, &lineInfo,
2252 new_topline, &lastLine, &maxLine, M_TYPES | (flags & M_PAGER_NOWRAP),
2253 &QuoteList, &q_level, &force_redraw, &SearchRE))))
2254 && lineInfo[new_topline].type == MT_COLOR_QUOTED)
2259 mutt_error _("No more unquoted text after quoted text.");
2262 topline = new_topline;
2266 case OP_PAGER_BOTTOM: /* move to the end of the file */
2267 if (lineInfo[curline].offset < sb.st_size - 1)
2270 /* make sure the types are defined to the end of file */
2271 while (display_line (fp, &last_pos, &lineInfo, i, &lastLine,
2272 &maxLine, has_types | (flags & M_PAGER_NOWRAP),
2273 &QuoteList, &q_level, &force_redraw,
2276 topline = upNLines (bodylen, lineInfo, lastLine, hideQuoted);
2279 mutt_error _("Bottom of message is shown.");
2283 clearok (stdscr, TRUE);
2284 redraw = REDRAW_FULL;
2288 km_error_key (MENU_PAGER);
2291 /* --------------------------------------------------------------------
2292 * The following are operations on the current message rather than
2293 * adjusting the view of the message.
2296 case OP_BOUNCE_MESSAGE:
2297 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra))
2299 if (IsMsgAttach (extra))
2300 mutt_attach_bounce (extra->fp, extra->hdr,
2301 extra->idx, extra->idxlen,
2304 ci_bounce_message (extra->hdr, &redraw);
2308 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra))
2310 if (IsMsgAttach (extra))
2311 mutt_attach_resend (extra->fp, extra->hdr,
2312 extra->idx, extra->idxlen,
2315 mutt_resend_message (NULL, extra->ctx, extra->hdr);
2316 redraw = REDRAW_FULL;
2319 case OP_CHECK_TRADITIONAL:
2320 CHECK_MODE (IsHeader (extra));
2321 if (!(WithCrypto & APPLICATION_PGP))
2323 if (!(extra->hdr->security & PGP_TRADITIONAL_CHECKED))
2326 rc = OP_CHECK_TRADITIONAL;
2330 case OP_CREATE_ALIAS:
2331 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2332 if (IsMsgAttach (extra))
2333 mutt_create_alias (extra->bdy->hdr->env, NULL);
2335 mutt_create_alias (extra->hdr->env, NULL);
2336 MAYBE_REDRAW (redraw);
2340 CHECK_MODE(IsHeader (extra));
2342 CHECK_ACL(M_ACL_DELETE, _("delete message"));
2344 mutt_set_flag (Context, extra->hdr, M_DELETE, 1);
2345 if (option (OPTDELETEUNTAG))
2346 mutt_set_flag (Context, extra->hdr, M_TAG, 0);
2347 redraw = REDRAW_STATUS | REDRAW_INDEX;
2348 if (option (OPTRESOLVE))
2351 rc = OP_MAIN_NEXT_UNDELETED;
2355 case OP_MAIN_SET_FLAG:
2356 case OP_MAIN_CLEAR_FLAG:
2357 CHECK_MODE(IsHeader (extra));
2360 if (mutt_change_flag (extra->hdr, (ch == OP_MAIN_SET_FLAG)) == 0)
2361 redraw |= REDRAW_STATUS | REDRAW_INDEX;
2362 if (extra->hdr->deleted && option (OPTRESOLVE))
2365 rc = OP_MAIN_NEXT_UNDELETED;
2369 case OP_DELETE_THREAD:
2370 case OP_DELETE_SUBTHREAD:
2371 CHECK_MODE(IsHeader (extra));
2373 CHECK_ACL(M_ACL_DELETE, _("delete message(s)"));
2375 r = mutt_thread_set_flag (extra->hdr, M_DELETE, 1,
2376 ch == OP_DELETE_THREAD ? 0 : 1);
2380 if (option (OPTDELETEUNTAG))
2381 mutt_thread_set_flag (extra->hdr, M_TAG, 0,
2382 ch == OP_DELETE_THREAD ? 0 : 1);
2383 if (option (OPTRESOLVE))
2385 rc = OP_MAIN_NEXT_UNDELETED;
2389 if (!option (OPTRESOLVE) && PagerIndexLines)
2390 redraw = REDRAW_FULL;
2392 redraw = REDRAW_STATUS | REDRAW_INDEX;
2396 case OP_DISPLAY_ADDRESS:
2397 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2398 if (IsMsgAttach (extra))
2399 mutt_display_address (extra->bdy->hdr->env);
2401 mutt_display_address (extra->hdr->env);
2404 case OP_ENTER_COMMAND:
2405 old_smart_wrap = option (OPTWRAP);
2406 old_markers = option (OPTMARKERS);
2407 old_PagerIndexLines = PagerIndexLines;
2409 CurrentMenu = MENU_PAGER;
2410 mutt_enter_command ();
2412 if (option (OPTNEEDRESORT))
2414 unset_option (OPTNEEDRESORT);
2415 CHECK_MODE(IsHeader (extra));
2416 set_option (OPTNEEDRESORT);
2419 if (old_PagerIndexLines != PagerIndexLines)
2422 mutt_menuDestroy (&index);
2426 if (option (OPTWRAP) != old_smart_wrap ||
2427 option (OPTMARKERS) != old_markers)
2429 if (flags & M_PAGER_RETWINCH)
2432 rc = OP_REFORMAT_WINCH;
2436 /* count the real lines above */
2438 for (i = 0; i <= topline; i++)
2440 if (!lineInfo[i].continuation)
2444 /* we need to restart the whole thing */
2445 for (i = 0; i < maxLine; i++)
2447 lineInfo[i].offset = 0;
2448 lineInfo[i].type = -1;
2449 lineInfo[i].continuation = 0;
2450 lineInfo[i].chunks = 0;
2451 lineInfo[i].search_cnt = -1;
2452 lineInfo[i].quote = NULL;
2454 safe_realloc (&(lineInfo[i].syntax), sizeof (struct syntax_t));
2455 if (SearchCompiled && lineInfo[i].search)
2456 FREE (&(lineInfo[i].search));
2461 regfree (&SearchRE);
2466 /* try to keep the old position */
2469 while (j > 0 && display_line (fp, &last_pos, &lineInfo, topline,
2470 &lastLine, &maxLine,
2471 (has_types ? M_TYPES : 0) | (flags & M_PAGER_NOWRAP),
2472 &QuoteList, &q_level, &force_redraw,
2475 if (! lineInfo[topline].continuation)
2484 if (option (OPTFORCEREDRAWPAGER))
2485 redraw = REDRAW_FULL;
2486 unset_option (OPTFORCEREDRAWINDEX);
2487 unset_option (OPTFORCEREDRAWPAGER);
2490 case OP_FLAG_MESSAGE:
2491 CHECK_MODE(IsHeader (extra));
2493 CHECK_ACL(M_ACL_WRITE, "flag message");
2495 mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged);
2496 redraw = REDRAW_STATUS | REDRAW_INDEX;
2497 if (option (OPTRESOLVE))
2500 rc = OP_MAIN_NEXT_UNDELETED;
2505 CHECK_MODE(IsHeader (extra) || IsAttach (extra));
2506 if (IsAttach (extra))
2507 mutt_pipe_attachment_list (extra->fp, 0, extra->bdy, 0);
2509 mutt_pipe_message (extra->hdr);
2510 MAYBE_REDRAW (redraw);
2514 CHECK_MODE(IsHeader (extra) || IsAttach (extra));
2515 if (IsAttach (extra))
2516 mutt_print_attachment_list (extra->fp, 0, extra->bdy);
2518 mutt_print_message (extra->hdr);
2522 CHECK_MODE(IsHeader (extra) && !IsAttach (extra));
2524 ci_send_message (0, NULL, NULL, extra->ctx, NULL);
2525 redraw = REDRAW_FULL;
2529 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2531 if (IsMsgAttach (extra))
2532 mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2533 extra->idxlen, extra->bdy,
2536 ci_send_message (SENDREPLY, NULL, NULL, extra->ctx, extra->hdr);
2537 redraw = REDRAW_FULL;
2540 case OP_RECALL_MESSAGE:
2541 CHECK_MODE(IsHeader (extra) && !IsAttach(extra));
2543 ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr);
2544 redraw = REDRAW_FULL;
2547 case OP_GROUP_REPLY:
2548 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2550 if (IsMsgAttach (extra))
2551 mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2552 extra->idxlen, extra->bdy, SENDREPLY|SENDGROUPREPLY);
2554 ci_send_message (SENDREPLY | SENDGROUPREPLY, NULL, NULL, extra->ctx, extra->hdr);
2555 redraw = REDRAW_FULL;
2559 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2561 if (IsMsgAttach (extra))
2562 mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
2563 extra->idxlen, extra->bdy, SENDREPLY|SENDLISTREPLY);
2565 ci_send_message (SENDREPLY | SENDLISTREPLY, NULL, NULL, extra->ctx, extra->hdr);
2566 redraw = REDRAW_FULL;
2569 case OP_FORWARD_MESSAGE:
2570 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
2572 if (IsMsgAttach (extra))
2573 mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
2574 extra->idxlen, extra->bdy);
2576 ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
2577 redraw = REDRAW_FULL;
2580 case OP_DECRYPT_SAVE:
2588 if (IsAttach (extra))
2590 mutt_save_attachment_list (extra->fp, 0, extra->bdy, extra->hdr, NULL);
2594 case OP_COPY_MESSAGE:
2595 case OP_DECODE_SAVE:
2596 case OP_DECODE_COPY:
2597 case OP_DECRYPT_COPY:
2598 if (!WithCrypto && ch == OP_DECRYPT_COPY)
2603 CHECK_MODE(IsHeader (extra));
2604 if (mutt_save_message (extra->hdr,
2605 (ch == OP_DECRYPT_SAVE) ||
2606 (ch == OP_SAVE) || (ch == OP_DECODE_SAVE),
2607 (ch == OP_DECODE_SAVE) || (ch == OP_DECODE_COPY),
2608 (ch == OP_DECRYPT_SAVE) || (ch == OP_DECRYPT_COPY) ||
2610 &redraw) == 0 && (ch == OP_SAVE || ch == OP_DECODE_SAVE
2611 || ch == OP_DECRYPT_SAVE
2614 if (option (OPTRESOLVE))
2617 rc = OP_MAIN_NEXT_UNDELETED;
2620 redraw |= REDRAW_STATUS | REDRAW_INDEX;
2622 MAYBE_REDRAW (redraw);
2625 case OP_SHELL_ESCAPE:
2626 mutt_shell_escape ();
2627 MAYBE_REDRAW (redraw);
2631 CHECK_MODE(IsHeader (extra));
2632 mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
2634 Context->last_tag = extra->hdr->tagged ? extra->hdr :
2635 ((Context->last_tag == extra->hdr && !extra->hdr->tagged)
2636 ? NULL : Context->last_tag);
2638 redraw = REDRAW_STATUS | REDRAW_INDEX;
2639 if (option (OPTRESOLVE))
2647 CHECK_MODE(IsHeader (extra));
2649 CHECK_ACL(M_ACL_SEEN, _("toggle new"));
2651 if (extra->hdr->read || extra->hdr->old)
2652 mutt_set_flag (Context, extra->hdr, M_NEW, 1);
2654 mutt_set_flag (Context, extra->hdr, M_READ, 1);
2656 Context->msgnotreadyet = -1;
2657 redraw = REDRAW_STATUS | REDRAW_INDEX;
2658 if (option (OPTRESOLVE))
2661 rc = OP_MAIN_NEXT_UNDELETED;
2666 CHECK_MODE(IsHeader (extra));
2668 CHECK_ACL(M_ACL_DELETE, _("undelete message"));
2670 mutt_set_flag (Context, extra->hdr, M_DELETE, 0);
2671 redraw = REDRAW_STATUS | REDRAW_INDEX;
2672 if (option (OPTRESOLVE))
2679 case OP_UNDELETE_THREAD:
2680 case OP_UNDELETE_SUBTHREAD:
2681 CHECK_MODE(IsHeader (extra));
2683 CHECK_ACL(M_ACL_DELETE, _("undelete message(s)"));
2685 r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0,
2686 ch == OP_UNDELETE_THREAD ? 0 : 1);
2690 if (option (OPTRESOLVE))
2692 rc = (ch == OP_DELETE_THREAD) ?
2693 OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
2697 if (!option (OPTRESOLVE) && PagerIndexLines)
2698 redraw = REDRAW_FULL;
2700 redraw = REDRAW_STATUS | REDRAW_INDEX;
2712 case OP_VIEW_ATTACHMENTS:
2713 if (flags & M_PAGER_ATTACHMENT)
2716 rc = OP_ATTACH_COLLAPSE;
2719 CHECK_MODE(IsHeader (extra));
2720 mutt_view_attachments (extra->hdr);
2721 if (extra->hdr->attach_del)
2722 Context->changed = 1;
2723 redraw = REDRAW_FULL;
2728 if (!(WithCrypto & APPLICATION_PGP))
2733 CHECK_MODE(IsHeader(extra));
2735 ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr);
2736 redraw = REDRAW_FULL;
2740 case OP_FORGET_PASSPHRASE:
2741 crypt_forget_passphrase ();
2744 case OP_EXTRACT_KEYS:
2750 CHECK_MODE(IsHeader(extra));
2751 crypt_extract_keys_from_messages(extra->hdr);
2752 redraw = REDRAW_FULL;
2766 if (IsHeader (extra))
2768 Context->msgnotreadyet = -1;
2774 OldHdr = extra->hdr;
2778 cleanup_quote (&QuoteList);
2780 for (i = 0; i < maxLine ; i++)
2782 FREE (&(lineInfo[i].syntax));
2783 if (SearchCompiled && lineInfo[i].search)
2784 FREE (&(lineInfo[i].search));
2788 regfree (&SearchRE);
2793 mutt_menuDestroy(&index);
2794 return (rc != -1 ? rc : 0);