NeoMutt  2021-02-05-666-ge300cd
Teaching an old dog new tricks
DOXYGEN
dlg_pager.c
Go to the documentation of this file.
1 
36 #include "config.h"
37 #include <assert.h>
38 #include <ctype.h>
39 #include <errno.h>
40 #include <inttypes.h> // IWYU pragma: keep
41 #include <limits.h>
42 #include <stdbool.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
49 #include <wchar.h>
50 #include "mutt/lib.h"
51 #include "config/lib.h"
52 #include "email/lib.h"
53 #include "core/lib.h"
54 #include "alias/lib.h"
55 #include "gui/lib.h"
56 #include "mutt.h"
57 #include "lib.h"
58 #include "index/lib.h"
59 #include "menu/lib.h"
60 #include "ncrypt/lib.h"
61 #include "question/lib.h"
62 #include "send/lib.h"
63 #include "commands.h"
64 #include "context.h"
65 #include "format_flags.h"
66 #include "hdrline.h"
67 #include "hook.h"
68 #include "keymap.h"
69 #include "mutt_attach.h"
70 #include "mutt_globals.h"
71 #include "mutt_header.h"
72 #include "mutt_logging.h"
73 #include "mutt_mailbox.h"
74 #include "muttlib.h"
75 #include "mx.h"
76 #include "opcodes.h"
77 #include "options.h"
78 #include "private_data.h"
79 #include "protos.h"
80 #include "recvattach.h"
81 #include "recvcmd.h"
82 #include "status.h"
83 #ifdef USE_SIDEBAR
84 #include "sidebar/lib.h"
85 #endif
86 #ifdef USE_NNTP
87 #include "nntp/lib.h"
88 #include "nntp/mdata.h" // IWYU pragma: keep
89 #endif
90 #ifdef ENABLE_NLS
91 #include <libintl.h>
92 #endif
93 
94 // clang-format off
95 typedef uint8_t AnsiFlags;
96 #define ANSI_NO_FLAGS 0
97 #define ANSI_OFF (1 << 0)
98 #define ANSI_BLINK (1 << 1)
99 #define ANSI_BOLD (1 << 2)
100 #define ANSI_UNDERLINE (1 << 3)
101 #define ANSI_REVERSE (1 << 4)
102 #define ANSI_COLOR (1 << 5)
103 // clang-format on
104 
108 struct QClass
109 {
110  size_t length;
111  int index;
112  int color;
113  char *prefix;
114  struct QClass *next, *prev;
115  struct QClass *down, *up;
116 };
117 
122 {
123  int color;
124  int first;
125  int last;
126 };
127 
131 struct Line
132 {
133  LOFF_T offset;
134  short type;
136  short chunks;
137  short search_cnt;
140  struct QClass *quote;
141  unsigned int is_cont_hdr;
142 };
143 
147 struct AnsiAttr
148 {
150  int fg;
151  int bg;
152  int pair;
153 };
154 
158 struct Resize
159 {
160  int line;
163 };
164 
165 /* hack to return to position when returning from index to same message */
166 static int TopLine = 0;
167 static struct Email *OldEmail = NULL;
168 
169 static int braille_row = -1;
170 static int braille_col = -1;
171 
172 static struct Resize *Resize = NULL;
173 
174 static const char *Not_available_in_this_menu =
175  N_("Not available in this menu");
176 static const char *Mailbox_is_read_only = N_("Mailbox is read-only");
178  N_("Function not permitted in attach-message mode");
179 
181 static const struct Mapping PagerHelp[] = {
182  // clang-format off
183  { N_("Exit"), OP_EXIT },
184  { N_("PrevPg"), OP_PREV_PAGE },
185  { N_("NextPg"), OP_NEXT_PAGE },
186  { N_("Help"), OP_HELP },
187  { NULL, 0 },
188  // clang-format on
189 };
190 
192 static const struct Mapping PagerHelpHelp[] = {
193  // clang-format off
194  { N_("Exit"), OP_EXIT },
195  { N_("PrevPg"), OP_PREV_PAGE },
196  { N_("NextPg"), OP_NEXT_PAGE },
197  { NULL, 0 },
198  // clang-format on
199 };
200 
202 static const struct Mapping PagerNormalHelp[] = {
203  // clang-format off
204  { N_("Exit"), OP_EXIT },
205  { N_("PrevPg"), OP_PREV_PAGE },
206  { N_("NextPg"), OP_NEXT_PAGE },
207  { N_("View Attachm."), OP_VIEW_ATTACHMENTS },
208  { N_("Del"), OP_DELETE },
209  { N_("Reply"), OP_REPLY },
210  { N_("Next"), OP_MAIN_NEXT_UNDELETED },
211  { N_("Help"), OP_HELP },
212  { NULL, 0 },
213  // clang-format on
214 };
215 
216 #ifdef USE_NNTP
217 static const struct Mapping PagerNewsHelp[] = {
219  // clang-format off
220  { N_("Exit"), OP_EXIT },
221  { N_("PrevPg"), OP_PREV_PAGE },
222  { N_("NextPg"), OP_NEXT_PAGE },
223  { N_("Post"), OP_POST },
224  { N_("Followup"), OP_FOLLOWUP },
225  { N_("Del"), OP_DELETE },
226  { N_("Next"), OP_MAIN_NEXT_UNDELETED },
227  { N_("Help"), OP_HELP },
228  { NULL, 0 },
229  // clang-format on
230 };
231 #endif
232 
241 static inline bool assert_pager_mode(bool test)
242 {
243  if (test)
244  return true;
245 
246  mutt_flushinp();
248  return false;
249 }
250 
259 static inline bool assert_mailbox_writable(struct Mailbox *mailbox)
260 {
261  assert(mailbox);
262  if (mailbox->readonly)
263  {
264  mutt_flushinp();
266  return false;
267  }
268  return true;
269 }
270 
279 static inline bool assert_attach_msg_mode(bool attach_msg)
280 {
281  if (attach_msg)
282  {
283  mutt_flushinp();
285  return true;
286  }
287  return false;
288 }
289 
300 static inline bool assert_mailbox_permissions(struct Mailbox *m, AclFlags acl, char *action)
301 {
302  assert(m);
303  assert(action);
304  if (m->rights & acl)
305  {
306  return true;
307  }
308  mutt_flushinp();
309  /* L10N: %s is one of the CHECK_ACL entries below. */
310  mutt_error(_("%s: Operation not permitted by ACL"), action);
311  return false;
312 }
313 
322 static int check_sig(const char *s, struct Line *info, int offset)
323 {
324  const unsigned int NUM_SIG_LINES = 4; // The amount of lines a signature takes
325  unsigned int count = 0;
326 
327  while ((offset > 0) && (count <= NUM_SIG_LINES))
328  {
329  if (info[offset].type != MT_COLOR_SIGNATURE)
330  break;
331  count++;
332  offset--;
333  }
334 
335  if (count == 0)
336  return -1;
337 
338  if (count > NUM_SIG_LINES)
339  {
340  /* check for a blank line */
341  while (*s)
342  {
343  if (!IS_SPACE(*s))
344  return 0;
345  s++;
346  }
347 
348  return -1;
349  }
350 
351  return 0;
352 }
353 
362 static int comp_syntax_t(const void *m1, const void *m2)
363 {
364  const int *cnt = (const int *) m1;
365  const struct TextSyntax *stx = (const struct TextSyntax *) m2;
366 
367  if (*cnt < stx->first)
368  return -1;
369  if (*cnt >= stx->last)
370  return 1;
371  return 0;
372 }
373 
384 static void resolve_color(struct MuttWindow *win, struct Line *line_info, int n,
385  int cnt, PagerFlags flags, int special, struct AnsiAttr *a)
386 {
387  int def_color; /* color without syntax highlight */
388  int color; /* final color */
389  static int last_color; /* last color set */
390  bool search = false;
391  int m;
392  struct TextSyntax *matching_chunk = NULL;
393 
394  if (cnt == 0)
395  last_color = -1; /* force attrset() */
396 
397  if (line_info[n].continuation)
398  {
399  const bool c_markers = cs_subset_bool(NeoMutt->sub, "markers");
400  if (!cnt && c_markers)
401  {
403  mutt_window_addch(win, '+');
404  last_color = mutt_color(MT_COLOR_MARKERS);
405  }
406  m = (line_info[n].syntax)[0].first;
407  cnt += (line_info[n].syntax)[0].last;
408  }
409  else
410  m = n;
411  if (flags & MUTT_PAGER_LOGS)
412  {
413  def_color = mutt_color(line_info[n].syntax[0].color);
414  }
415  else if (!(flags & MUTT_SHOWCOLOR))
416  def_color = mutt_color(MT_COLOR_NORMAL);
417  else if (line_info[m].type == MT_COLOR_HEADER)
418  def_color = line_info[m].syntax[0].color;
419  else
420  def_color = mutt_color(line_info[m].type);
421 
422  if ((flags & MUTT_SHOWCOLOR) && (line_info[m].type == MT_COLOR_QUOTED))
423  {
424  struct QClass *qc = line_info[m].quote;
425 
426  if (qc)
427  {
428  def_color = qc->color;
429 
430  while (qc && (qc->length > cnt))
431  {
432  def_color = qc->color;
433  qc = qc->up;
434  }
435  }
436  }
437 
438  color = def_color;
439  if ((flags & MUTT_SHOWCOLOR) && line_info[m].chunks)
440  {
441  matching_chunk = bsearch(&cnt, line_info[m].syntax, line_info[m].chunks,
442  sizeof(struct TextSyntax), comp_syntax_t);
443  if (matching_chunk && (cnt >= matching_chunk->first) &&
444  (cnt < matching_chunk->last))
445  {
446  color = matching_chunk->color;
447  }
448  }
449 
450  if ((flags & MUTT_SEARCH) && line_info[m].search_cnt)
451  {
452  matching_chunk = bsearch(&cnt, line_info[m].search, line_info[m].search_cnt,
453  sizeof(struct TextSyntax), comp_syntax_t);
454  if (matching_chunk && (cnt >= matching_chunk->first) &&
455  (cnt < matching_chunk->last))
456  {
457  color = mutt_color(MT_COLOR_SEARCH);
458  search = 1;
459  }
460  }
461 
462  /* handle "special" bold & underlined characters */
463  if (special || a->attr)
464  {
465 #ifdef HAVE_COLOR
466  if ((a->attr & ANSI_COLOR))
467  {
468  if (a->pair == -1)
469  a->pair = mutt_color_alloc(a->fg, a->bg);
470  color = a->pair;
471  if (a->attr & ANSI_BOLD)
472  color |= A_BOLD;
473  }
474  else
475 #endif
476  if ((special & A_BOLD) || (a->attr & ANSI_BOLD))
477  {
478  if (mutt_color(MT_COLOR_BOLD) && !search)
479  color = mutt_color(MT_COLOR_BOLD);
480  else
481  color ^= A_BOLD;
482  }
483  if ((special & A_UNDERLINE) || (a->attr & ANSI_UNDERLINE))
484  {
485  if (mutt_color(MT_COLOR_UNDERLINE) && !search)
487  else
488  color ^= A_UNDERLINE;
489  }
490  else if (a->attr & ANSI_REVERSE)
491  {
492  color ^= A_REVERSE;
493  }
494  else if (a->attr & ANSI_BLINK)
495  {
496  color ^= A_BLINK;
497  }
498  else if (a->attr == ANSI_OFF)
499  {
500  a->attr = 0;
501  }
502  }
503 
504  if (color != last_color)
505  {
506  mutt_curses_set_attr(color);
507  last_color = color;
508  }
509 }
510 
517 static void append_line(struct Line *line_info, int n, int cnt)
518 {
519  int m;
520 
521  line_info[n + 1].type = line_info[n].type;
522  (line_info[n + 1].syntax)[0].color = (line_info[n].syntax)[0].color;
523  line_info[n + 1].continuation = 1;
524 
525  /* find the real start of the line */
526  for (m = n; m >= 0; m--)
527  if (line_info[m].continuation == 0)
528  break;
529 
530  (line_info[n + 1].syntax)[0].first = m;
531  (line_info[n + 1].syntax)[0].last =
532  (line_info[n].continuation) ? cnt + (line_info[n].syntax)[0].last : cnt;
533 }
534 
540 static void class_color_new(struct QClass *qc, int *q_level)
541 {
542  qc->index = (*q_level)++;
543  qc->color = mutt_color_quote(qc->index);
544 }
545 
553 static void shift_class_colors(struct QClass *quote_list,
554  struct QClass *new_class, int index, int *q_level)
555 {
556  struct QClass *q_list = quote_list;
557  new_class->index = -1;
558 
559  while (q_list)
560  {
561  if (q_list->index >= index)
562  {
563  q_list->index++;
564  q_list->color = mutt_color_quote(q_list->index);
565  }
566  if (q_list->down)
567  q_list = q_list->down;
568  else if (q_list->next)
569  q_list = q_list->next;
570  else
571  {
572  while (!q_list->next)
573  {
574  q_list = q_list->up;
575  if (!q_list)
576  break;
577  }
578  if (q_list)
579  q_list = q_list->next;
580  }
581  }
582 
583  new_class->index = index;
584  new_class->color = mutt_color_quote(index);
585  (*q_level)++;
586 }
587 
592 static void cleanup_quote(struct QClass **quote_list)
593 {
594  struct QClass *ptr = NULL;
595 
596  while (*quote_list)
597  {
598  if ((*quote_list)->down)
599  cleanup_quote(&((*quote_list)->down));
600  ptr = (*quote_list)->next;
601  FREE(&(*quote_list)->prefix);
602  FREE(quote_list);
603  *quote_list = ptr;
604  }
605 }
606 
616 static struct QClass *classify_quote(struct QClass **quote_list, const char *qptr,
617  size_t length, bool *force_redraw, int *q_level)
618 {
619  struct QClass *q_list = *quote_list;
620  struct QClass *qc = NULL, *tmp = NULL, *ptr = NULL, *save = NULL;
621  const char *tail_qptr = NULL;
622  size_t offset, tail_lng;
623  int index = -1;
624 
625  if (mutt_color_quotes_used() <= 1)
626  {
627  /* not much point in classifying quotes... */
628 
629  if (!*quote_list)
630  {
631  qc = mutt_mem_calloc(1, sizeof(struct QClass));
632  qc->color = mutt_color_quote(0);
633  *quote_list = qc;
634  }
635  return *quote_list;
636  }
637 
638  /* classify quoting prefix */
639  while (q_list)
640  {
641  if (length <= q_list->length)
642  {
643  /* case 1: check the top level nodes */
644 
645  if (mutt_strn_equal(qptr, q_list->prefix, length))
646  {
647  if (length == q_list->length)
648  return q_list; /* same prefix: return the current class */
649 
650  /* found shorter prefix */
651  if (!tmp)
652  {
653  /* add a node above q_list */
654  tmp = mutt_mem_calloc(1, sizeof(struct QClass));
655  tmp->prefix = mutt_mem_calloc(1, length + 1);
656  strncpy(tmp->prefix, qptr, length);
657  tmp->length = length;
658 
659  /* replace q_list by tmp in the top level list */
660  if (q_list->next)
661  {
662  tmp->next = q_list->next;
663  q_list->next->prev = tmp;
664  }
665  if (q_list->prev)
666  {
667  tmp->prev = q_list->prev;
668  q_list->prev->next = tmp;
669  }
670 
671  /* make q_list a child of tmp */
672  tmp->down = q_list;
673  q_list->up = tmp;
674 
675  /* q_list has no siblings for now */
676  q_list->next = NULL;
677  q_list->prev = NULL;
678 
679  /* update the root if necessary */
680  if (q_list == *quote_list)
681  *quote_list = tmp;
682 
683  index = q_list->index;
684 
685  /* tmp should be the return class too */
686  qc = tmp;
687 
688  /* next class to test; if tmp is a shorter prefix for another
689  * node, that node can only be in the top level list, so don't
690  * go down after this point */
691  q_list = tmp->next;
692  }
693  else
694  {
695  /* found another branch for which tmp is a shorter prefix */
696 
697  /* save the next sibling for later */
698  save = q_list->next;
699 
700  /* unlink q_list from the top level list */
701  if (q_list->next)
702  q_list->next->prev = q_list->prev;
703  if (q_list->prev)
704  q_list->prev->next = q_list->next;
705 
706  /* at this point, we have a tmp->down; link q_list to it */
707  ptr = tmp->down;
708  /* sibling order is important here, q_list should be linked last */
709  while (ptr->next)
710  ptr = ptr->next;
711  ptr->next = q_list;
712  q_list->next = NULL;
713  q_list->prev = ptr;
714  q_list->up = tmp;
715 
716  index = q_list->index;
717 
718  /* next class to test; as above, we shouldn't go down */
719  q_list = save;
720  }
721 
722  /* we found a shorter prefix, so certain quotes have changed classes */
723  *force_redraw = true;
724  continue;
725  }
726  else
727  {
728  /* shorter, but not a substring of the current class: try next */
729  q_list = q_list->next;
730  continue;
731  }
732  }
733  else
734  {
735  /* case 2: try subclassing the current top level node */
736 
737  /* tmp != NULL means we already found a shorter prefix at case 1 */
738  if (!tmp && mutt_strn_equal(qptr, q_list->prefix, q_list->length))
739  {
740  /* ok, it's a subclass somewhere on this branch */
741 
742  ptr = q_list;
743  offset = q_list->length;
744 
745  q_list = q_list->down;
746  tail_lng = length - offset;
747  tail_qptr = qptr + offset;
748 
749  while (q_list)
750  {
751  if (length <= q_list->length)
752  {
753  if (mutt_strn_equal(tail_qptr, (q_list->prefix) + offset, tail_lng))
754  {
755  /* same prefix: return the current class */
756  if (length == q_list->length)
757  return q_list;
758 
759  /* found shorter common prefix */
760  if (!tmp)
761  {
762  /* add a node above q_list */
763  tmp = mutt_mem_calloc(1, sizeof(struct QClass));
764  tmp->prefix = mutt_mem_calloc(1, length + 1);
765  strncpy(tmp->prefix, qptr, length);
766  tmp->length = length;
767 
768  /* replace q_list by tmp */
769  if (q_list->next)
770  {
771  tmp->next = q_list->next;
772  q_list->next->prev = tmp;
773  }
774  if (q_list->prev)
775  {
776  tmp->prev = q_list->prev;
777  q_list->prev->next = tmp;
778  }
779 
780  /* make q_list a child of tmp */
781  tmp->down = q_list;
782  tmp->up = q_list->up;
783  q_list->up = tmp;
784  if (tmp->up->down == q_list)
785  tmp->up->down = tmp;
786 
787  /* q_list has no siblings */
788  q_list->next = NULL;
789  q_list->prev = NULL;
790 
791  index = q_list->index;
792 
793  /* tmp should be the return class too */
794  qc = tmp;
795 
796  /* next class to test */
797  q_list = tmp->next;
798  }
799  else
800  {
801  /* found another branch for which tmp is a shorter prefix */
802 
803  /* save the next sibling for later */
804  save = q_list->next;
805 
806  /* unlink q_list from the top level list */
807  if (q_list->next)
808  q_list->next->prev = q_list->prev;
809  if (q_list->prev)
810  q_list->prev->next = q_list->next;
811 
812  /* at this point, we have a tmp->down; link q_list to it */
813  ptr = tmp->down;
814  while (ptr->next)
815  ptr = ptr->next;
816  ptr->next = q_list;
817  q_list->next = NULL;
818  q_list->prev = ptr;
819  q_list->up = tmp;
820 
821  index = q_list->index;
822 
823  /* next class to test */
824  q_list = save;
825  }
826 
827  /* we found a shorter prefix, so we need a redraw */
828  *force_redraw = true;
829  continue;
830  }
831  else
832  {
833  q_list = q_list->next;
834  continue;
835  }
836  }
837  else
838  {
839  /* longer than the current prefix: try subclassing it */
840  if (!tmp && mutt_strn_equal(tail_qptr, (q_list->prefix) + offset,
841  q_list->length - offset))
842  {
843  /* still a subclass: go down one level */
844  ptr = q_list;
845  offset = q_list->length;
846 
847  q_list = q_list->down;
848  tail_lng = length - offset;
849  tail_qptr = qptr + offset;
850 
851  continue;
852  }
853  else
854  {
855  /* nope, try the next prefix */
856  q_list = q_list->next;
857  continue;
858  }
859  }
860  }
861 
862  /* still not found so far: add it as a sibling to the current node */
863  if (!qc)
864  {
865  tmp = mutt_mem_calloc(1, sizeof(struct QClass));
866  tmp->prefix = mutt_mem_calloc(1, length + 1);
867  strncpy(tmp->prefix, qptr, length);
868  tmp->length = length;
869 
870  if (ptr->down)
871  {
872  tmp->next = ptr->down;
873  ptr->down->prev = tmp;
874  }
875  ptr->down = tmp;
876  tmp->up = ptr;
877 
878  class_color_new(tmp, q_level);
879 
880  return tmp;
881  }
882  else
883  {
884  if (index != -1)
885  shift_class_colors(*quote_list, tmp, index, q_level);
886 
887  return qc;
888  }
889  }
890  else
891  {
892  /* nope, try the next prefix */
893  q_list = q_list->next;
894  continue;
895  }
896  }
897  }
898 
899  if (!qc)
900  {
901  /* not found so far: add it as a top level class */
902  qc = mutt_mem_calloc(1, sizeof(struct QClass));
903  qc->prefix = mutt_mem_calloc(1, length + 1);
904  strncpy(qc->prefix, qptr, length);
905  qc->length = length;
906  class_color_new(qc, q_level);
907 
908  if (*quote_list)
909  {
910  qc->next = *quote_list;
911  (*quote_list)->prev = qc;
912  }
913  *quote_list = qc;
914  }
915 
916  if (index != -1)
917  shift_class_colors(*quote_list, tmp, index, q_level);
918 
919  return qc;
920 }
921 
928 static int check_marker(const char *q, const char *p)
929 {
930  for (; (p[0] == q[0]) && (q[0] != '\0') && (p[0] != '\0') && (q[0] != '\a') &&
931  (p[0] != '\a');
932  p++, q++)
933  {
934  }
935 
936  return (int) (*p - *q);
937 }
938 
944 static int check_attachment_marker(const char *p)
945 {
947 }
948 
954 static int check_protected_header_marker(const char *p)
955 {
957 }
958 
968 int mutt_is_quote_line(char *line, regmatch_t *pmatch)
969 {
970  bool is_quote = false;
971  const struct Regex *c_smileys = cs_subset_regex(NeoMutt->sub, "smileys");
972  regmatch_t pmatch_internal[1], smatch[1];
973 
974  if (!pmatch)
975  pmatch = pmatch_internal;
976 
977  const struct Regex *c_quote_regex =
978  cs_subset_regex(NeoMutt->sub, "quote_regex");
979  if (mutt_regex_capture(c_quote_regex, line, 1, pmatch))
980  {
981  if (mutt_regex_capture(c_smileys, line, 1, smatch))
982  {
983  if (smatch[0].rm_so > 0)
984  {
985  char c = line[smatch[0].rm_so];
986  line[smatch[0].rm_so] = 0;
987 
988  if (mutt_regex_capture(c_quote_regex, line, 1, pmatch))
989  is_quote = true;
990 
991  line[smatch[0].rm_so] = c;
992  }
993  }
994  else
995  is_quote = true;
996  }
997 
998  return is_quote;
999 }
1000 
1014 static void resolve_types(struct MuttWindow *win, char *buf, char *raw,
1015  struct Line *line_info, int n, int last, struct QClass **quote_list,
1016  int *q_level, bool *force_redraw, bool q_classify)
1017 {
1018  struct ColorLine *color_line = NULL;
1019  struct ColorLineList *head = NULL;
1020  regmatch_t pmatch[1];
1021  bool found;
1022  bool null_rx;
1023  const bool c_header_color_partial =
1024  cs_subset_bool(NeoMutt->sub, "header_color_partial");
1025  int offset, i = 0;
1026 
1027  if ((n == 0) || mutt_color_is_header(line_info[n - 1].type) ||
1028  (check_protected_header_marker(raw) == 0))
1029  {
1030  if (buf[0] == '\n') /* end of header */
1031  {
1032  line_info[n].type = MT_COLOR_NORMAL;
1034  }
1035  else
1036  {
1037  /* if this is a continuation of the previous line, use the previous
1038  * line's color as default. */
1039  if ((n > 0) && ((buf[0] == ' ') || (buf[0] == '\t')))
1040  {
1041  line_info[n].type = line_info[n - 1].type; /* wrapped line */
1042  if (!c_header_color_partial)
1043  {
1044  (line_info[n].syntax)[0].color = (line_info[n - 1].syntax)[0].color;
1045  line_info[n].is_cont_hdr = 1;
1046  }
1047  }
1048  else
1049  {
1050  line_info[n].type = MT_COLOR_HDRDEFAULT;
1051  }
1052 
1053  /* When this option is unset, we color the entire header the
1054  * same color. Otherwise, we handle the header patterns just
1055  * like body patterns (further below). */
1056  if (!c_header_color_partial)
1057  {
1058  STAILQ_FOREACH(color_line, mutt_color_headers(), entries)
1059  {
1060  if (regexec(&color_line->regex, buf, 0, NULL, 0) == 0)
1061  {
1062  line_info[n].type = MT_COLOR_HEADER;
1063  line_info[n].syntax[0].color = color_line->pair;
1064  if (line_info[n].is_cont_hdr)
1065  {
1066  /* adjust the previous continuation lines to reflect the color of this continuation line */
1067  int j;
1068  for (j = n - 1; j >= 0 && line_info[j].is_cont_hdr; --j)
1069  {
1070  line_info[j].type = line_info[n].type;
1071  line_info[j].syntax[0].color = line_info[n].syntax[0].color;
1072  }
1073  /* now adjust the first line of this header field */
1074  if (j >= 0)
1075  {
1076  line_info[j].type = line_info[n].type;
1077  line_info[j].syntax[0].color = line_info[n].syntax[0].color;
1078  }
1079  *force_redraw = true; /* the previous lines have already been drawn on the screen */
1080  }
1081  break;
1082  }
1083  }
1084  }
1085  }
1086  }
1087  else if (mutt_str_startswith(raw, "\033[0m")) // Escape: a little hack...
1088  line_info[n].type = MT_COLOR_NORMAL;
1089  else if (check_attachment_marker((char *) raw) == 0)
1090  line_info[n].type = MT_COLOR_ATTACHMENT;
1091  else if (mutt_str_equal("-- \n", buf) || mutt_str_equal("-- \r\n", buf))
1092  {
1093  i = n + 1;
1094 
1095  line_info[n].type = MT_COLOR_SIGNATURE;
1096  while ((i < last) && (check_sig(buf, line_info, i - 1) == 0) &&
1097  ((line_info[i].type == MT_COLOR_NORMAL) || (line_info[i].type == MT_COLOR_QUOTED) ||
1098  (line_info[i].type == MT_COLOR_HEADER)))
1099  {
1100  /* oops... */
1101  if (line_info[i].chunks)
1102  {
1103  line_info[i].chunks = 0;
1104  mutt_mem_realloc(&(line_info[n].syntax), sizeof(struct TextSyntax));
1105  }
1106  line_info[i++].type = MT_COLOR_SIGNATURE;
1107  }
1108  }
1109  else if (check_sig(buf, line_info, n - 1) == 0)
1110  line_info[n].type = MT_COLOR_SIGNATURE;
1111  else if (mutt_is_quote_line(buf, pmatch))
1112 
1113  {
1114  if (q_classify && (line_info[n].quote == NULL))
1115  {
1116  line_info[n].quote = classify_quote(quote_list, buf + pmatch[0].rm_so,
1117  pmatch[0].rm_eo - pmatch[0].rm_so,
1118  force_redraw, q_level);
1119  }
1120  line_info[n].type = MT_COLOR_QUOTED;
1121  }
1122  else
1123  line_info[n].type = MT_COLOR_NORMAL;
1124 
1125  /* body patterns */
1126  if ((line_info[n].type == MT_COLOR_NORMAL) || (line_info[n].type == MT_COLOR_QUOTED) ||
1127  ((line_info[n].type == MT_COLOR_HDRDEFAULT) && c_header_color_partial))
1128  {
1129  size_t nl;
1130 
1131  /* don't consider line endings part of the buffer
1132  * for regex matching */
1133  nl = mutt_str_len(buf);
1134  if ((nl > 0) && (buf[nl - 1] == '\n'))
1135  buf[nl - 1] = '\0';
1136 
1137  i = 0;
1138  offset = 0;
1139  line_info[n].chunks = 0;
1140  if (line_info[n].type == MT_COLOR_HDRDEFAULT)
1141  head = mutt_color_headers();
1142  else
1143  head = mutt_color_body();
1144  STAILQ_FOREACH(color_line, head, entries)
1145  {
1146  color_line->stop_matching = false;
1147  }
1148  do
1149  {
1150  if (!buf[offset])
1151  break;
1152 
1153  found = false;
1154  null_rx = false;
1155  STAILQ_FOREACH(color_line, head, entries)
1156  {
1157  if (!color_line->stop_matching &&
1158  (regexec(&color_line->regex, buf + offset, 1, pmatch,
1159  ((offset != 0) ? REG_NOTBOL : 0)) == 0))
1160  {
1161  if (pmatch[0].rm_eo != pmatch[0].rm_so)
1162  {
1163  if (!found)
1164  {
1165  /* Abort if we fill up chunks.
1166  * Yes, this really happened. */
1167  if (line_info[n].chunks == SHRT_MAX)
1168  {
1169  null_rx = false;
1170  break;
1171  }
1172  if (++(line_info[n].chunks) > 1)
1173  {
1174  mutt_mem_realloc(&(line_info[n].syntax),
1175  (line_info[n].chunks) * sizeof(struct TextSyntax));
1176  }
1177  }
1178  i = line_info[n].chunks - 1;
1179  pmatch[0].rm_so += offset;
1180  pmatch[0].rm_eo += offset;
1181  if (!found || (pmatch[0].rm_so < (line_info[n].syntax)[i].first) ||
1182  ((pmatch[0].rm_so == (line_info[n].syntax)[i].first) &&
1183  (pmatch[0].rm_eo > (line_info[n].syntax)[i].last)))
1184  {
1185  (line_info[n].syntax)[i].color = color_line->pair;
1186  (line_info[n].syntax)[i].first = pmatch[0].rm_so;
1187  (line_info[n].syntax)[i].last = pmatch[0].rm_eo;
1188  }
1189  found = true;
1190  null_rx = false;
1191  }
1192  else
1193  null_rx = true; /* empty regex; don't add it, but keep looking */
1194  }
1195  else
1196  {
1197  /* Once a regexp fails to match, don't try matching it again.
1198  * On very long lines this can cause a performance issue if there
1199  * are other regexps that have many matches. */
1200  color_line->stop_matching = true;
1201  }
1202  }
1203 
1204  if (null_rx)
1205  offset++; /* avoid degenerate cases */
1206  else
1207  offset = (line_info[n].syntax)[i].last;
1208  } while (found || null_rx);
1209  if (nl > 0)
1210  buf[nl] = '\n';
1211  }
1212 
1213  /* attachment patterns */
1214  if (line_info[n].type == MT_COLOR_ATTACHMENT)
1215  {
1216  size_t nl;
1217 
1218  /* don't consider line endings part of the buffer for regex matching */
1219  nl = mutt_str_len(buf);
1220  if ((nl > 0) && (buf[nl - 1] == '\n'))
1221  buf[nl - 1] = '\0';
1222 
1223  i = 0;
1224  offset = 0;
1225  line_info[n].chunks = 0;
1226  do
1227  {
1228  if (!buf[offset])
1229  break;
1230 
1231  found = false;
1232  null_rx = false;
1233  STAILQ_FOREACH(color_line, mutt_color_attachments(), entries)
1234  {
1235  if (regexec(&color_line->regex, buf + offset, 1, pmatch,
1236  ((offset != 0) ? REG_NOTBOL : 0)) == 0)
1237  {
1238  if (pmatch[0].rm_eo != pmatch[0].rm_so)
1239  {
1240  if (!found)
1241  {
1242  if (++(line_info[n].chunks) > 1)
1243  {
1244  mutt_mem_realloc(&(line_info[n].syntax),
1245  (line_info[n].chunks) * sizeof(struct TextSyntax));
1246  }
1247  }
1248  i = line_info[n].chunks - 1;
1249  pmatch[0].rm_so += offset;
1250  pmatch[0].rm_eo += offset;
1251  if (!found || (pmatch[0].rm_so < (line_info[n].syntax)[i].first) ||
1252  ((pmatch[0].rm_so == (line_info[n].syntax)[i].first) &&
1253  (pmatch[0].rm_eo > (line_info[n].syntax)[i].last)))
1254  {
1255  (line_info[n].syntax)[i].color = color_line->pair;
1256  (line_info[n].syntax)[i].first = pmatch[0].rm_so;
1257  (line_info[n].syntax)[i].last = pmatch[0].rm_eo;
1258  }
1259  found = 1;
1260  null_rx = 0;
1261  }
1262  else
1263  null_rx = 1; /* empty regex; don't add it, but keep looking */
1264  }
1265  }
1266 
1267  if (null_rx)
1268  offset++; /* avoid degenerate cases */
1269  else
1270  offset = (line_info[n].syntax)[i].last;
1271  } while (found || null_rx);
1272  if (nl > 0)
1273  buf[nl] = '\n';
1274  }
1275 }
1276 
1282 static bool is_ansi(const char *str)
1283 {
1284  while (*str && (isdigit(*str) || (*str == ';')))
1285  str++;
1286  return (*str == 'm');
1287 }
1288 
1296 static int grok_ansi(const unsigned char *buf, int pos, struct AnsiAttr *a)
1297 {
1298  int x = pos;
1299 
1300  while (isdigit(buf[x]) || (buf[x] == ';'))
1301  x++;
1302 
1303  /* Character Attributes */
1304  const bool c_allow_ansi = cs_subset_bool(NeoMutt->sub, "allow_ansi");
1305  if (c_allow_ansi && a && (buf[x] == 'm'))
1306  {
1307  if (pos == x)
1308  {
1309 #ifdef HAVE_COLOR
1310  if (a->pair != -1)
1311  mutt_color_free(a->fg, a->bg);
1312 #endif
1313  a->attr = ANSI_OFF;
1314  a->pair = -1;
1315  }
1316  while (pos < x)
1317  {
1318  if ((buf[pos] == '1') && ((pos + 1 == x) || (buf[pos + 1] == ';')))
1319  {
1320  a->attr |= ANSI_BOLD;
1321  pos += 2;
1322  }
1323  else if ((buf[pos] == '4') && ((pos + 1 == x) || (buf[pos + 1] == ';')))
1324  {
1325  a->attr |= ANSI_UNDERLINE;
1326  pos += 2;
1327  }
1328  else if ((buf[pos] == '5') && ((pos + 1 == x) || (buf[pos + 1] == ';')))
1329  {
1330  a->attr |= ANSI_BLINK;
1331  pos += 2;
1332  }
1333  else if ((buf[pos] == '7') && ((pos + 1 == x) || (buf[pos + 1] == ';')))
1334  {
1335  a->attr |= ANSI_REVERSE;
1336  pos += 2;
1337  }
1338  else if ((buf[pos] == '0') && ((pos + 1 == x) || (buf[pos + 1] == ';')))
1339  {
1340 #ifdef HAVE_COLOR
1341  if (a->pair != -1)
1342  mutt_color_free(a->fg, a->bg);
1343 #endif
1344  a->attr = ANSI_OFF;
1345  a->pair = -1;
1346  pos += 2;
1347  }
1348  else if ((buf[pos] == '3') && isdigit(buf[pos + 1]))
1349  {
1350 #ifdef HAVE_COLOR
1351  if (a->pair != -1)
1352  mutt_color_free(a->fg, a->bg);
1353 #endif
1354  a->pair = -1;
1355  a->attr |= ANSI_COLOR;
1356  a->fg = buf[pos + 1] - '0';
1357  pos += 3;
1358  }
1359  else if ((buf[pos] == '4') && isdigit(buf[pos + 1]))
1360  {
1361 #ifdef HAVE_COLOR
1362  if (a->pair != -1)
1363  mutt_color_free(a->fg, a->bg);
1364 #endif
1365  a->pair = -1;
1366  a->attr |= ANSI_COLOR;
1367  a->bg = buf[pos + 1] - '0';
1368  pos += 3;
1369  }
1370  else
1371  {
1372  while ((pos < x) && (buf[pos] != ';'))
1373  pos++;
1374  pos++;
1375  }
1376  }
1377  }
1378  pos = x;
1379  return pos;
1380 }
1381 
1395 void mutt_buffer_strip_formatting(struct Buffer *dest, const char *src, bool strip_markers)
1396 {
1397  const char *s = src;
1398 
1399  mutt_buffer_reset(dest);
1400 
1401  if (!s)
1402  return;
1403 
1404  while (s[0] != '\0')
1405  {
1406  if ((s[0] == '\010') && (s > src))
1407  {
1408  if (s[1] == '_') /* underline */
1409  s += 2;
1410  else if (s[1] && mutt_buffer_len(dest)) /* bold or overstrike */
1411  {
1412  dest->dptr--;
1413  mutt_buffer_addch(dest, s[1]);
1414  s += 2;
1415  }
1416  else /* ^H */
1417  mutt_buffer_addch(dest, *s++);
1418  }
1419  else if ((s[0] == '\033') && (s[1] == '[') && is_ansi(s + 2))
1420  {
1421  while (*s++ != 'm')
1422  ; /* skip ANSI sequence */
1423  }
1424  else if (strip_markers && (s[0] == '\033') && (s[1] == ']') &&
1425  ((check_attachment_marker(s) == 0) || (check_protected_header_marker(s) == 0)))
1426  {
1427  mutt_debug(LL_DEBUG2, "Seen attachment marker\n");
1428  while (*s++ != '\a')
1429  ; /* skip pseudo-ANSI sequence */
1430  }
1431  else
1432  mutt_buffer_addch(dest, *s++);
1433  }
1434 }
1435 
1448 static int fill_buffer(FILE *fp, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf,
1449  unsigned char **fmt, size_t *blen, int *buf_ready)
1450 {
1451  static int b_read;
1452  struct Buffer stripped;
1453 
1454  if (*buf_ready == 0)
1455  {
1456  if (offset != *last_pos)
1457  fseeko(fp, offset, SEEK_SET);
1458 
1459  *buf = (unsigned char *) mutt_file_read_line((char *) *buf, blen, fp, NULL, MUTT_RL_EOL);
1460  if (!*buf)
1461  {
1462  fmt[0] = NULL;
1463  return -1;
1464  }
1465 
1466  *last_pos = ftello(fp);
1467  b_read = (int) (*last_pos - offset);
1468  *buf_ready = 1;
1469 
1470  mutt_buffer_init(&stripped);
1471  mutt_buffer_alloc(&stripped, *blen);
1472  mutt_buffer_strip_formatting(&stripped, (const char *) *buf, 1);
1473  /* This should be a noop, because *fmt should be NULL */
1474  FREE(fmt);
1475  *fmt = (unsigned char *) stripped.data;
1476  }
1477 
1478  return b_read;
1479 }
1480 
1497 static int format_line(struct MuttWindow *win, struct Line **line_info, int n,
1498  unsigned char *buf, PagerFlags flags, struct AnsiAttr *pa,
1499  int cnt, int *pspace, int *pvch, int *pcol, int *pspecial, int width)
1500 {
1501  int space = -1; /* index of the last space or TAB */
1502  const bool c_markers = cs_subset_bool(NeoMutt->sub, "markers");
1503  size_t col = c_markers ? (*line_info)[n].continuation : 0;
1504  size_t k;
1505  int ch, vch, last_special = -1, special = 0, t;
1506  wchar_t wc;
1507  mbstate_t mbstate;
1508  const size_t c_wrap = cs_subset_number(NeoMutt->sub, "wrap");
1509  size_t wrap_cols = mutt_window_wrap_cols(width, (flags & MUTT_PAGER_NOWRAP) ? 0 : c_wrap);
1510 
1511  if (check_attachment_marker((char *) buf) == 0)
1512  wrap_cols = width;
1513 
1514  /* FIXME: this should come from line_info */
1515  memset(&mbstate, 0, sizeof(mbstate));
1516 
1517  for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k)
1518  {
1519  /* Handle ANSI sequences */
1520  while ((cnt - ch >= 2) && (buf[ch] == '\033') && (buf[ch + 1] == '[') && // Escape
1521  is_ansi((char *) buf + ch + 2))
1522  {
1523  ch = grok_ansi(buf, ch + 2, pa) + 1;
1524  }
1525 
1526  while ((cnt - ch >= 2) && (buf[ch] == '\033') && (buf[ch + 1] == ']') && // Escape
1527  ((check_attachment_marker((char *) buf + ch) == 0) ||
1528  (check_protected_header_marker((char *) buf + ch) == 0)))
1529  {
1530  while (buf[ch++] != '\a')
1531  if (ch >= cnt)
1532  break;
1533  }
1534 
1535  /* is anything left to do? */
1536  if (ch >= cnt)
1537  break;
1538 
1539  k = mbrtowc(&wc, (char *) buf + ch, cnt - ch, &mbstate);
1540  if ((k == (size_t) (-2)) || (k == (size_t) (-1)))
1541  {
1542  if (k == (size_t) (-1))
1543  memset(&mbstate, 0, sizeof(mbstate));
1544  mutt_debug(LL_DEBUG1, "mbrtowc returned %lu; errno = %d\n", k, errno);
1545  if (col + 4 > wrap_cols)
1546  break;
1547  col += 4;
1548  if (pa)
1549  mutt_window_printf(win, "\\%03o", buf[ch]);
1550  k = 1;
1551  continue;
1552  }
1553  if (k == 0)
1554  k = 1;
1555 
1556  if (CharsetIsUtf8)
1557  {
1558  /* zero width space, zero width no-break space */
1559  if ((wc == 0x200B) || (wc == 0xFEFF))
1560  {
1561  mutt_debug(LL_DEBUG3, "skip zero-width character U+%04X\n", (unsigned short) wc);
1562  continue;
1563  }
1565  {
1566  mutt_debug(LL_DEBUG3, "filtered U+%04X\n", (unsigned short) wc);
1567  continue;
1568  }
1569  }
1570 
1571  /* Handle backspace */
1572  special = 0;
1573  if (IsWPrint(wc))
1574  {
1575  wchar_t wc1;
1576  mbstate_t mbstate1 = mbstate;
1577  size_t k1 = mbrtowc(&wc1, (char *) buf + ch + k, cnt - ch - k, &mbstate1);
1578  while ((k1 != (size_t) (-2)) && (k1 != (size_t) (-1)) && (k1 > 0) && (wc1 == '\b'))
1579  {
1580  const size_t k2 =
1581  mbrtowc(&wc1, (char *) buf + ch + k + k1, cnt - ch - k - k1, &mbstate1);
1582  if ((k2 == (size_t) (-2)) || (k2 == (size_t) (-1)) || (k2 == 0) || (!IsWPrint(wc1)))
1583  break;
1584 
1585  if (wc == wc1)
1586  {
1587  special |= ((wc == '_') && (special & A_UNDERLINE)) ? A_UNDERLINE : A_BOLD;
1588  }
1589  else if ((wc == '_') || (wc1 == '_'))
1590  {
1591  special |= A_UNDERLINE;
1592  wc = (wc1 == '_') ? wc : wc1;
1593  }
1594  else
1595  {
1596  /* special = 0; / * overstrike: nothing to do! */
1597  wc = wc1;
1598  }
1599 
1600  ch += k + k1;
1601  k = k2;
1602  mbstate = mbstate1;
1603  k1 = mbrtowc(&wc1, (char *) buf + ch + k, cnt - ch - k, &mbstate1);
1604  }
1605  }
1606 
1607  if (pa && ((flags & (MUTT_SHOWCOLOR | MUTT_SEARCH | MUTT_PAGER_MARKER)) ||
1608  special || last_special || pa->attr))
1609  {
1610  resolve_color(win, *line_info, n, vch, flags, special, pa);
1611  last_special = special;
1612  }
1613 
1614  /* no-break space, narrow no-break space */
1615  if (IsWPrint(wc) || (CharsetIsUtf8 && ((wc == 0x00A0) || (wc == 0x202F))))
1616  {
1617  if (wc == ' ')
1618  {
1619  space = ch;
1620  }
1621  t = wcwidth(wc);
1622  if (col + t > wrap_cols)
1623  break;
1624  col += t;
1625  if (pa)
1626  mutt_addwch(win, wc);
1627  }
1628  else if (wc == '\n')
1629  break;
1630  else if (wc == '\t')
1631  {
1632  space = ch;
1633  t = (col & ~7) + 8;
1634  if (t > wrap_cols)
1635  break;
1636  if (pa)
1637  for (; col < t; col++)
1638  mutt_window_addch(win, ' ');
1639  else
1640  col = t;
1641  }
1642  else if ((wc < 0x20) || (wc == 0x7f))
1643  {
1644  if (col + 2 > wrap_cols)
1645  break;
1646  col += 2;
1647  if (pa)
1648  mutt_window_printf(win, "^%c", ('@' + wc) & 0x7f);
1649  }
1650  else if (wc < 0x100)
1651  {
1652  if (col + 4 > wrap_cols)
1653  break;
1654  col += 4;
1655  if (pa)
1656  mutt_window_printf(win, "\\%03o", wc);
1657  }
1658  else
1659  {
1660  if (col + 1 > wrap_cols)
1661  break;
1662  col += k;
1663  if (pa)
1665  }
1666  }
1667  *pspace = space;
1668  *pcol = col;
1669  *pvch = vch;
1670  *pspecial = special;
1671  return ch;
1672 }
1673 
1692 static int display_line(FILE *fp, LOFF_T *last_pos, struct Line **line_info,
1693  int n, int *last, int *max, PagerFlags flags,
1694  struct QClass **quote_list, int *q_level, bool *force_redraw,
1695  regex_t *search_re, struct MuttWindow *win_pager)
1696 {
1697  unsigned char *buf = NULL, *fmt = NULL;
1698  size_t buflen = 0;
1699  unsigned char *buf_ptr = NULL;
1700  int ch, vch, col, cnt, b_read;
1701  int buf_ready = 0;
1702  bool change_last = false;
1703  const bool c_smart_wrap = cs_subset_bool(NeoMutt->sub, "smart_wrap");
1704  int special;
1705  int offset;
1706  int def_color;
1707  int m;
1708  int rc = -1;
1709  struct AnsiAttr a = { 0, 0, 0, -1 };
1710  regmatch_t pmatch[1];
1711 
1712  if (n == *last)
1713  {
1714  (*last)++;
1715  change_last = true;
1716  }
1717 
1718  if (*last == *max)
1719  {
1720  mutt_mem_realloc(line_info, sizeof(struct Line) * (*max += LINES));
1721  for (ch = *last; ch < *max; ch++)
1722  {
1723  memset(&((*line_info)[ch]), 0, sizeof(struct Line));
1724  (*line_info)[ch].type = -1;
1725  (*line_info)[ch].search_cnt = -1;
1726  (*line_info)[ch].syntax = mutt_mem_malloc(sizeof(struct TextSyntax));
1727  ((*line_info)[ch].syntax)[0].first = -1;
1728  ((*line_info)[ch].syntax)[0].last = -1;
1729  }
1730  }
1731 
1732  struct Line *const curr_line = &(*line_info)[n];
1733 
1734  if (flags & MUTT_PAGER_LOGS)
1735  {
1736  /* determine the line class */
1737  if (fill_buffer(fp, last_pos, curr_line->offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1738  {
1739  if (change_last)
1740  (*last)--;
1741  goto out;
1742  }
1743 
1744  curr_line->type = MT_COLOR_MESSAGE_LOG;
1745  if (buf[11] == 'M')
1746  curr_line->syntax[0].color = MT_COLOR_MESSAGE;
1747  else if (buf[11] == 'W')
1748  curr_line->syntax[0].color = MT_COLOR_WARNING;
1749  else if (buf[11] == 'E')
1750  curr_line->syntax[0].color = MT_COLOR_ERROR;
1751  else
1752  curr_line->syntax[0].color = MT_COLOR_NORMAL;
1753  }
1754 
1755  /* only do color highlighting if we are viewing a message */
1756  if (flags & (MUTT_SHOWCOLOR | MUTT_TYPES))
1757  {
1758  if (curr_line->type == -1)
1759  {
1760  /* determine the line class */
1761  if (fill_buffer(fp, last_pos, curr_line->offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1762  {
1763  if (change_last)
1764  (*last)--;
1765  goto out;
1766  }
1767 
1768  resolve_types(win_pager, (char *) fmt, (char *) buf, *line_info, n, *last,
1769  quote_list, q_level, force_redraw, flags & MUTT_SHOWCOLOR);
1770 
1771  /* avoid race condition for continuation lines when scrolling up */
1772  for (m = n + 1; m < *last && (*line_info)[m].offset && (*line_info)[m].continuation; m++)
1773  (*line_info)[m].type = curr_line->type;
1774  }
1775 
1776  /* this also prevents searching through the hidden lines */
1777  const short c_toggle_quoted_show_levels =
1778  cs_subset_number(NeoMutt->sub, "toggle_quoted_show_levels");
1779  if ((flags & MUTT_HIDE) && (curr_line->type == MT_COLOR_QUOTED) &&
1780  ((curr_line->quote == NULL) || (curr_line->quote->index >= c_toggle_quoted_show_levels)))
1781  {
1782  flags = 0; /* MUTT_NOSHOW */
1783  }
1784  }
1785 
1786  /* At this point, (*line_info[n]).quote may still be undefined. We
1787  * don't want to compute it every time MUTT_TYPES is set, since this
1788  * would slow down the "bottom" function unacceptably. A compromise
1789  * solution is hence to call regexec() again, just to find out the
1790  * length of the quote prefix. */
1791  if ((flags & MUTT_SHOWCOLOR) && !curr_line->continuation &&
1792  (curr_line->type == MT_COLOR_QUOTED) && !curr_line->quote)
1793  {
1794  if (fill_buffer(fp, last_pos, curr_line->offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1795  {
1796  if (change_last)
1797  (*last)--;
1798  goto out;
1799  }
1800 
1801  const struct Regex *c_quote_regex =
1802  cs_subset_regex(NeoMutt->sub, "quote_regex");
1803  if (mutt_regex_capture(c_quote_regex, (char *) fmt, 1, pmatch))
1804  {
1805  curr_line->quote =
1806  classify_quote(quote_list, (char *) fmt + pmatch[0].rm_so,
1807  pmatch[0].rm_eo - pmatch[0].rm_so, force_redraw, q_level);
1808  }
1809  else
1810  {
1811  goto out;
1812  }
1813  }
1814 
1815  if ((flags & MUTT_SEARCH) && !curr_line->continuation && (curr_line->search_cnt == -1))
1816  {
1817  if (fill_buffer(fp, last_pos, curr_line->offset, &buf, &fmt, &buflen, &buf_ready) < 0)
1818  {
1819  if (change_last)
1820  (*last)--;
1821  goto out;
1822  }
1823 
1824  offset = 0;
1825  curr_line->search_cnt = 0;
1826  while (regexec(search_re, (char *) fmt + offset, 1, pmatch,
1827  (offset ? REG_NOTBOL : 0)) == 0)
1828  {
1829  if (++(curr_line->search_cnt) > 1)
1830  {
1831  mutt_mem_realloc(&(curr_line->search),
1832  (curr_line->search_cnt) * sizeof(struct TextSyntax));
1833  }
1834  else
1835  curr_line->search = mutt_mem_malloc(sizeof(struct TextSyntax));
1836  pmatch[0].rm_so += offset;
1837  pmatch[0].rm_eo += offset;
1838  (curr_line->search)[curr_line->search_cnt - 1].first = pmatch[0].rm_so;
1839  (curr_line->search)[curr_line->search_cnt - 1].last = pmatch[0].rm_eo;
1840 
1841  if (pmatch[0].rm_eo == pmatch[0].rm_so)
1842  offset++; /* avoid degenerate cases */
1843  else
1844  offset = pmatch[0].rm_eo;
1845  if (!fmt[offset])
1846  break;
1847  }
1848  }
1849 
1850  if (!(flags & MUTT_SHOW) && ((*line_info)[n + 1].offset > 0))
1851  {
1852  /* we've already scanned this line, so just exit */
1853  rc = 0;
1854  goto out;
1855  }
1856  if ((flags & MUTT_SHOWCOLOR) && *force_redraw && ((*line_info)[n + 1].offset > 0))
1857  {
1858  /* no need to try to display this line... */
1859  rc = 1;
1860  goto out; /* fake display */
1861  }
1862 
1863  b_read = fill_buffer(fp, last_pos, curr_line->offset, &buf, &fmt, &buflen, &buf_ready);
1864  if (b_read < 0)
1865  {
1866  if (change_last)
1867  (*last)--;
1868  goto out;
1869  }
1870 
1871  /* now chose a good place to break the line */
1872  cnt = format_line(win_pager, line_info, n, buf, flags, NULL, b_read, &ch,
1873  &vch, &col, &special, win_pager->state.cols);
1874  buf_ptr = buf + cnt;
1875 
1876  /* move the break point only if smart_wrap is set */
1877  if (c_smart_wrap)
1878  {
1879  if ((cnt < b_read) && (ch != -1) &&
1880  !mutt_color_is_header(curr_line->type) && !IS_SPACE(buf[cnt]))
1881  {
1882  buf_ptr = buf + ch;
1883  /* skip trailing blanks */
1884  while (ch && ((buf[ch] == ' ') || (buf[ch] == '\t') || (buf[ch] == '\r')))
1885  ch--;
1886  /* A very long word with leading spaces causes infinite
1887  * wrapping when MUTT_PAGER_NSKIP is set. A folded header
1888  * with a single long word shouldn't be smartwrapped
1889  * either. So just disable smart_wrap if it would wrap at the
1890  * beginning of the line. */
1891  if (ch == 0)
1892  buf_ptr = buf + cnt;
1893  else
1894  cnt = ch + 1;
1895  }
1896  if (!(flags & MUTT_PAGER_NSKIP))
1897  {
1898  /* skip leading blanks on the next line too */
1899  while ((*buf_ptr == ' ') || (*buf_ptr == '\t'))
1900  buf_ptr++;
1901  }
1902  }
1903 
1904  if (*buf_ptr == '\r')
1905  buf_ptr++;
1906  if (*buf_ptr == '\n')
1907  buf_ptr++;
1908 
1909  if (((int) (buf_ptr - buf) < b_read) && !(*line_info)[n + 1].continuation)
1910  append_line(*line_info, n, (int) (buf_ptr - buf));
1911  (*line_info)[n + 1].offset = curr_line->offset + (long) (buf_ptr - buf);
1912 
1913  /* if we don't need to display the line we are done */
1914  if (!(flags & MUTT_SHOW))
1915  {
1916  rc = 0;
1917  goto out;
1918  }
1919 
1920  /* display the line */
1921  format_line(win_pager, line_info, n, buf, flags, &a, cnt, &ch, &vch, &col,
1922  &special, win_pager->state.cols);
1923 
1924 /* avoid a bug in ncurses... */
1925 #ifndef USE_SLANG_CURSES
1926  if (col == 0)
1927  {
1929  mutt_window_addch(win_pager, ' ');
1930  }
1931 #endif
1932 
1933  /* end the last color pattern (needed by S-Lang) */
1934  if (special || ((col != win_pager->state.cols) && (flags & (MUTT_SHOWCOLOR | MUTT_SEARCH))))
1935  resolve_color(win_pager, *line_info, n, vch, flags, 0, &a);
1936 
1937  /* Fill the blank space at the end of the line with the prevailing color.
1938  * ncurses does an implicit clrtoeol() when you do mutt_window_addch('\n') so we have
1939  * to make sure to reset the color *after* that */
1940  if (flags & MUTT_SHOWCOLOR)
1941  {
1942  m = (curr_line->continuation) ? (curr_line->syntax)[0].first : n;
1943  if ((*line_info)[m].type == MT_COLOR_HEADER)
1944  def_color = ((*line_info)[m].syntax)[0].color;
1945  else
1946  def_color = mutt_color((*line_info)[m].type);
1947 
1948  mutt_curses_set_attr(def_color);
1949  }
1950 
1951  if (col < win_pager->state.cols)
1952  mutt_window_clrtoeol(win_pager);
1953 
1954  /* reset the color back to normal. This *must* come after the
1955  * clrtoeol, otherwise the color for this line will not be
1956  * filled to the right margin. */
1957  if (flags & MUTT_SHOWCOLOR)
1959 
1960  /* build a return code */
1961  if (!(flags & MUTT_SHOW))
1962  flags = 0;
1963 
1964  rc = flags;
1965 
1966 out:
1967  FREE(&buf);
1968  FREE(&fmt);
1969  return rc;
1970 }
1971 
1980 static int up_n_lines(int nlines, struct Line *info, int cur, bool hiding)
1981 {
1982  while ((cur > 0) && (nlines > 0))
1983  {
1984  cur--;
1985  if (!hiding || (info[cur].type != MT_COLOR_QUOTED))
1986  nlines--;
1987  }
1988 
1989  return cur;
1990 }
1991 
1996 {
1997  TopLine = 0;
1998  OldEmail = NULL;
1999 }
2000 
2007 {
2008  priv->redraw |= redraw;
2009  // win_pager->actions |= WA_RECALC;
2010 }
2011 
2016 static void pager_custom_redraw(struct PagerPrivateData *priv)
2017 {
2018  //---------------------------------------------------------------------------
2019  // ASSUMPTIONS & SANITY CHECKS
2020  //---------------------------------------------------------------------------
2021  // Since pager_custom_redraw() is a static function and it is always called
2022  // after mutt_pager() we can rely on a series of sanity checks in
2023  // mutt_pager(), namely:
2024  // - PAGER_MODE_EMAIL guarantees ( data->email) and (!data->body)
2025  // - PAGER_MODE_ATTACH guarantees ( data->email) and ( data->body)
2026  // - PAGER_MODE_OTHER guarantees (!data->email) and (!data->body)
2027  //
2028  // Additionally, while refactoring is still in progress the following checks
2029  // are still here to ensure data model consistency.
2030  assert(priv); // Redraw function can't be called without its data.
2031  assert(priv->pview); // Redraw data can't exist separately without the view.
2032  assert(priv->pview->pdata); // View can't exist without its data
2033  //---------------------------------------------------------------------------
2034 
2035  char buf[1024] = { 0 };
2036  struct IndexSharedData *shared = dialog_find(priv->pview->win_pager)->wdata;
2037 
2038  const bool c_tilde = cs_subset_bool(NeoMutt->sub, "tilde");
2039  const short c_pager_index_lines =
2040  cs_subset_number(NeoMutt->sub, "pager_index_lines");
2041 
2042  if (priv->redraw & MENU_REDRAW_FULL)
2043  {
2046 
2047  if ((priv->pview->mode == PAGER_MODE_EMAIL) &&
2048  ((shared->mailbox->vcount + 1) < c_pager_index_lines))
2049  {
2050  priv->indexlen = shared->mailbox->vcount + 1;
2051  }
2052  else
2053  {
2054  priv->indexlen = c_pager_index_lines;
2055  }
2056 
2057  priv->indicator = priv->indexlen / 3;
2058 
2059  if (Resize)
2060  {
2061  priv->search_compiled = Resize->search_compiled;
2062  if (priv->search_compiled)
2063  {
2064  uint16_t flags = mutt_mb_is_lower(priv->searchbuf) ? REG_ICASE : 0;
2065  const int err = REG_COMP(&priv->search_re, priv->searchbuf, REG_NEWLINE | flags);
2066  if (err == 0)
2067  {
2068  priv->search_flag = MUTT_SEARCH;
2069  priv->search_back = Resize->search_back;
2070  }
2071  else
2072  {
2073  regerror(err, &priv->search_re, buf, sizeof(buf));
2074  mutt_error("%s", buf);
2075  priv->search_compiled = false;
2076  }
2077  }
2078  priv->lines = Resize->line;
2080 
2081  FREE(&Resize);
2082  }
2083 
2084  if ((priv->pview->mode == PAGER_MODE_EMAIL) && (c_pager_index_lines != 0) && priv->menu)
2085  {
2087 
2088  /* some fudge to work out whereabouts the indicator should go */
2089  const int index = menu_get_index(priv->menu);
2090  if ((index - priv->indicator) < 0)
2091  priv->menu->top = 0;
2092  else if ((priv->menu->max - index) < (priv->menu->pagelen - priv->indicator))
2093  priv->menu->top = priv->menu->max - priv->menu->pagelen;
2094  else
2095  priv->menu->top = index - priv->indicator;
2096 
2097  menu_redraw_index(priv->menu);
2098  }
2099 
2101  }
2102 
2103  if (priv->redraw & MENU_REDRAW_FLOW)
2104  {
2105  if (!(priv->pview->flags & MUTT_PAGER_RETWINCH))
2106  {
2107  priv->lines = -1;
2108  for (int i = 0; i <= priv->topline; i++)
2109  if (!priv->line_info[i].continuation)
2110  priv->lines++;
2111  for (int i = 0; i < priv->max_line; i++)
2112  {
2113  priv->line_info[i].offset = 0;
2114  priv->line_info[i].type = -1;
2115  priv->line_info[i].continuation = 0;
2116  priv->line_info[i].chunks = 0;
2117  priv->line_info[i].search_cnt = -1;
2118  priv->line_info[i].quote = NULL;
2119 
2120  mutt_mem_realloc(&(priv->line_info[i].syntax), sizeof(struct TextSyntax));
2121  if (priv->search_compiled && priv->line_info[i].search)
2122  FREE(&(priv->line_info[i].search));
2123  }
2124 
2125  priv->last_line = 0;
2126  priv->topline = 0;
2127  }
2128  int i = -1;
2129  int j = -1;
2130  while (display_line(priv->fp, &priv->last_pos, &priv->line_info, ++i,
2131  &priv->last_line, &priv->max_line,
2132  priv->has_types | priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP),
2133  &priv->quote_list, &priv->q_level, &priv->force_redraw,
2134  &priv->search_re, priv->pview->win_pager) == 0)
2135  {
2136  if (!priv->line_info[i].continuation && (++j == priv->lines))
2137  {
2138  priv->topline = i;
2139  if (!priv->search_flag)
2140  break;
2141  }
2142  }
2143  }
2144 
2145  if ((priv->redraw & MENU_REDRAW_BODY) || (priv->topline != priv->oldtopline))
2146  {
2147  do
2148  {
2149  mutt_window_move(priv->pview->win_pager, 0, 0);
2150  priv->curline = priv->topline;
2151  priv->oldtopline = priv->topline;
2152  priv->lines = 0;
2153  priv->force_redraw = false;
2154 
2155  while ((priv->lines < priv->pview->win_pager->state.rows) &&
2156  (priv->line_info[priv->curline].offset <= priv->sb.st_size - 1))
2157  {
2158  if (display_line(priv->fp, &priv->last_pos, &priv->line_info,
2159  priv->curline, &priv->last_line, &priv->max_line,
2160  (priv->pview->flags & MUTT_DISPLAYFLAGS) | priv->hide_quoted |
2161  priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP),
2162  &priv->quote_list, &priv->q_level, &priv->force_redraw,
2163  &priv->search_re, priv->pview->win_pager) > 0)
2164  {
2165  priv->lines++;
2166  }
2167  priv->curline++;
2168  mutt_window_move(priv->pview->win_pager, 0, priv->lines);
2169  }
2170  priv->last_offset = priv->line_info[priv->curline].offset;
2171  } while (priv->force_redraw);
2172 
2174  while (priv->lines < priv->pview->win_pager->state.rows)
2175  {
2177  if (c_tilde)
2178  mutt_window_addch(priv->pview->win_pager, '~');
2179  priv->lines++;
2180  mutt_window_move(priv->pview->win_pager, 0, priv->lines);
2181  }
2183 
2184  /* We are going to update the pager status bar, so it isn't
2185  * necessary to reset to normal color now. */
2186 
2187  pager_queue_redraw(priv, MENU_REDRAW_STATUS); /* need to update the % seen */
2188  }
2189 
2190  if (priv->redraw & MENU_REDRAW_STATUS)
2191  {
2192  char pager_progress_str[65]; /* Lots of space for translations */
2193 
2194  if (priv->last_pos < priv->sb.st_size - 1)
2195  {
2196  snprintf(pager_progress_str, sizeof(pager_progress_str), OFF_T_FMT "%%",
2197  (100 * priv->last_offset / priv->sb.st_size));
2198  }
2199  else
2200  {
2201  const char *msg = (priv->topline == 0) ?
2202  /* L10N: Status bar message: the entire email is visible in the pager */
2203  _("all") :
2204  /* L10N: Status bar message: the end of the email is visible in the pager */
2205  _("end");
2206  mutt_str_copy(pager_progress_str, msg, sizeof(pager_progress_str));
2207  }
2208 
2209  /* print out the pager status bar */
2210  mutt_window_move(priv->pview->win_pbar, 0, 0);
2212 
2213  if (priv->pview->mode == PAGER_MODE_EMAIL || priv->pview->mode == PAGER_MODE_ATTACH_E)
2214  {
2215  const size_t l1 = priv->pview->win_pbar->state.cols * MB_LEN_MAX;
2216  const size_t l2 = sizeof(buf);
2217  const size_t buflen = (l1 < l2) ? l1 : l2;
2218  struct Email *e = (priv->pview->mode == PAGER_MODE_EMAIL) ? shared->email : // PAGER_MODE_EMAIL
2219  priv->pview->pdata->body->email; // PAGER_MODE_ATTACH_E
2220 
2221  const char *const c_pager_format =
2222  cs_subset_string(NeoMutt->sub, "pager_format");
2223  mutt_make_string(buf, buflen, priv->pview->win_pbar->state.cols,
2224  NONULL(c_pager_format), shared->mailbox, shared->ctx->msg_in_pager,
2225  e, MUTT_FORMAT_NO_FLAGS, pager_progress_str);
2227  priv->pview->win_pbar->state.cols, buf, l2);
2228  }
2229  else
2230  {
2231  char bn[256];
2232  snprintf(bn, sizeof(bn), "%s (%s)", priv->pview->banner, pager_progress_str);
2234  priv->pview->win_pbar->state.cols, bn, sizeof(bn));
2235  }
2237  const bool c_ts_enabled = cs_subset_bool(NeoMutt->sub, "ts_enabled");
2238  if (c_ts_enabled && TsSupported && priv->menu)
2239  {
2240  const char *const c_ts_status_format =
2241  cs_subset_string(NeoMutt->sub, "ts_status_format");
2242  menu_status_line(buf, sizeof(buf), shared, priv->menu, sizeof(buf),
2243  NONULL(c_ts_status_format));
2244  mutt_ts_status(buf);
2245  const char *const c_ts_icon_format =
2246  cs_subset_string(NeoMutt->sub, "ts_icon_format");
2247  menu_status_line(buf, sizeof(buf), shared, priv->menu, sizeof(buf),
2248  NONULL(c_ts_icon_format));
2249  mutt_ts_icon(buf);
2250  }
2251  }
2252 
2253  priv->redraw = MENU_REDRAW_NO_FLAGS;
2254  mutt_debug(LL_DEBUG5, "repaint done\n");
2255 }
2256 
2263 static const struct Mapping *pager_resolve_help_mapping(enum PagerMode mode, enum MailboxType type)
2264 {
2265  const struct Mapping *result;
2266  switch (mode)
2267  {
2268  case PAGER_MODE_EMAIL:
2269  case PAGER_MODE_ATTACH:
2270  case PAGER_MODE_ATTACH_E:
2271  if (type == MUTT_NNTP)
2272  result = PagerNewsHelp;
2273  else
2274  result = PagerNormalHelp;
2275  break;
2276 
2277  case PAGER_MODE_HELP:
2278  result = PagerHelpHelp;
2279  break;
2280 
2281  case PAGER_MODE_OTHER:
2282  result = PagerHelp;
2283  break;
2284 
2285  case PAGER_MODE_UNKNOWN:
2286  case PAGER_MODE_MAX:
2287  default:
2288  assert(false); // something went really wrong
2289  }
2290  assert(result);
2291  return result;
2292 }
2293 
2301 static bool jump_to_bottom(struct PagerPrivateData *priv, struct PagerView *pview)
2302 {
2303  if (!(priv->line_info[priv->curline].offset < (priv->sb.st_size - 1)))
2304  {
2305  return false;
2306  }
2307 
2308  int line_num = priv->curline;
2309  /* make sure the types are defined to the end of file */
2310  while (display_line(priv->fp, &priv->last_pos, &priv->line_info, line_num,
2311  &priv->last_line, &priv->max_line,
2312  priv->has_types | (pview->flags & MUTT_PAGER_NOWRAP),
2313  &priv->quote_list, &priv->q_level, &priv->force_redraw,
2314  &priv->search_re, priv->pview->win_pager) == 0)
2315  {
2316  line_num++;
2317  }
2318  priv->topline = up_n_lines(priv->pview->win_pager->state.rows, priv->line_info,
2319  priv->last_line, priv->hide_quoted);
2320  return true;
2321 }
2322 
2329 static bool check_read_delay(uint64_t *timestamp)
2330 {
2331  if ((*timestamp != 0) && (mutt_date_epoch_ms() > *timestamp))
2332  {
2333  *timestamp = 0;
2334  return true;
2335  }
2336  return false;
2337 }
2338 
2360 int mutt_pager(struct PagerView *pview)
2361 {
2362  //===========================================================================
2363  // ACT 1 - Ensure sanity of the caller and determine the mode
2364  //===========================================================================
2365  assert(pview);
2366  assert((pview->mode > PAGER_MODE_UNKNOWN) && (pview->mode < PAGER_MODE_MAX));
2367  assert(pview->pdata); // view can't exist in a vacuum
2368  assert(pview->win_pager);
2369  assert(pview->win_pbar);
2370 
2371  struct MuttWindow *dlg = dialog_find(pview->win_pager);
2372  struct IndexSharedData *shared = dlg->wdata;
2373 
2374  switch (pview->mode)
2375  {
2376  case PAGER_MODE_EMAIL:
2377  // This case was previously identified by IsEmail macro
2378  // we expect data to contain email and not contain body
2379  // We also expect email to always belong to some mailbox
2380  assert(shared->ctx);
2381  assert(shared->mailbox);
2382  assert(shared->email);
2383  assert(!pview->pdata->body);
2384  break;
2385 
2386  case PAGER_MODE_ATTACH:
2387  // this case was previously identified by IsAttach and IsMsgAttach
2388  // macros, we expect data to contain:
2389  // - body (viewing regular attachment)
2390  // - email
2391  // - fp and body->email in special case of viewing an attached email.
2392  assert(shared->email); // This should point to the top level email
2393  assert(pview->pdata->body);
2394  if (pview->pdata->fp && pview->pdata->body->email)
2395  {
2396  // Special case: attachment is a full-blown email message.
2397  // Yes, emails can contain other emails.
2398  pview->mode = PAGER_MODE_ATTACH_E;
2399  }
2400  break;
2401 
2402  case PAGER_MODE_HELP:
2403  case PAGER_MODE_OTHER:
2404  assert(!shared->ctx);
2405  assert(!shared->email);
2406  assert(!pview->pdata->body);
2407  break;
2408 
2409  case PAGER_MODE_UNKNOWN:
2410  case PAGER_MODE_MAX:
2411  default:
2412  // Unexpected mode. Catch fire and explode.
2413  // This *should* happen if mode is PAGER_MODE_ATTACH_E, since
2414  // we do not expect any caller to pass it to us.
2415  assert(false);
2416  break;
2417  }
2418 
2419  //===========================================================================
2420  // ACT 2 - Declare, initialize local variables, read config, etc.
2421  //===========================================================================
2422 
2423  //---------- reading config values needed now--------------------------------
2424  const short c_pager_index_lines =
2425  cs_subset_number(NeoMutt->sub, "pager_index_lines");
2426  const short c_pager_read_delay =
2427  cs_subset_number(NeoMutt->sub, "pager_read_delay");
2428 
2429  //---------- local variables ------------------------------------------------
2430  char buf[1024] = { 0 };
2431  int op = 0;
2432  int rc = -1;
2433  int searchctx = 0;
2434  int index_space = shared->mailbox ? MIN(c_pager_index_lines, shared->mailbox->vcount) :
2435  c_pager_index_lines;
2436  bool first = true;
2437  bool wrapped = false;
2438  enum MailboxType mailbox_type = shared->mailbox ? shared->mailbox->type : MUTT_UNKNOWN;
2439  uint64_t delay_read_timestamp = 0;
2440 
2441  struct PagerPrivateData *priv = pview->win_pager->parent->wdata;
2442  {
2443  // Wipe any previous state info
2444  struct Menu *menu = priv->menu;
2445  memset(priv, 0, sizeof(*priv));
2446  priv->menu = menu;
2447  priv->win_pbar = pview->win_pbar;
2448  }
2449 
2450  //---------- setup flags ----------------------------------------------------
2451  if (!(pview->flags & MUTT_SHOWCOLOR))
2452  pview->flags |= MUTT_SHOWFLAT;
2453 
2454  if (pview->mode == PAGER_MODE_EMAIL && !shared->email->read)
2455  {
2456  shared->ctx->msg_in_pager = shared->email->msgno;
2457  priv->win_pbar->actions |= WA_RECALC;
2458  if (c_pager_read_delay == 0)
2459  {
2460  mutt_set_flag(shared->mailbox, shared->email, MUTT_READ, true);
2461  }
2462  else
2463  {
2464  delay_read_timestamp = mutt_date_epoch_ms() + (1000 * c_pager_read_delay);
2465  }
2466  }
2467  //---------- setup help menu ------------------------------------------------
2468  pview->win_pager->help_data = pager_resolve_help_mapping(pview->mode, mailbox_type);
2469  pview->win_pager->help_menu = MENU_PAGER;
2470 
2471  //---------- initialize redraw pdata -----------------------------------------
2473  priv->pview = pview;
2474  priv->indexlen = c_pager_index_lines;
2475  priv->indicator = priv->indexlen / 3;
2476  priv->max_line = LINES; // number of lines on screen, from curses
2477  priv->line_info = mutt_mem_calloc(priv->max_line, sizeof(struct Line));
2478  priv->fp = fopen(pview->pdata->fname, "r");
2479  priv->has_types =
2480  ((pview->mode == PAGER_MODE_EMAIL) || (pview->flags & MUTT_SHOWCOLOR)) ?
2481  MUTT_TYPES :
2482  0; // main message or rfc822 attachment
2483 
2484  for (size_t i = 0; i < priv->max_line; i++)
2485  {
2486  priv->line_info[i].type = -1;
2487  priv->line_info[i].search_cnt = -1;
2488  priv->line_info[i].syntax = mutt_mem_malloc(sizeof(struct TextSyntax));
2489  (priv->line_info[i].syntax)[0].first = -1;
2490  (priv->line_info[i].syntax)[0].last = -1;
2491  }
2492 
2493  // ---------- try to open the pdata file -------------------------------------
2494  if (!priv->fp)
2495  {
2496  mutt_perror(pview->pdata->fname);
2497  return -1;
2498  }
2499 
2500  if (stat(pview->pdata->fname, &priv->sb) != 0)
2501  {
2502  mutt_perror(pview->pdata->fname);
2503  mutt_file_fclose(&priv->fp);
2504  return -1;
2505  }
2506  unlink(pview->pdata->fname);
2507 
2508  //---------- restore global state if needed ---------------------------------
2509  while ((pview->mode == PAGER_MODE_EMAIL) && (OldEmail == shared->email) && // are we "resuming" to the same Email?
2510  (TopLine != priv->topline) && // is saved offset different?
2511  (priv->line_info[priv->curline].offset < (priv->sb.st_size - 1)))
2512  {
2514  pager_custom_redraw(priv);
2515  // trick user, as if nothing happened
2516  // scroll down to previosly saved offset
2517  priv->topline =
2518  ((TopLine - priv->topline) > priv->lines) ? priv->topline + priv->lines : TopLine;
2519  }
2520 
2521  TopLine = 0;
2522  OldEmail = NULL;
2523 
2524  //---------- show windows, set focus and visibility --------------------------
2525  if (priv->pview->win_index)
2526  {
2527  priv->menu = priv->pview->win_index->wdata;
2529  priv->pview->win_index->req_rows = index_space;
2531  window_set_visible(priv->pview->win_index->parent, (index_space > 0));
2532  }
2533  window_set_visible(priv->pview->win_pager->parent, true);
2534  mutt_window_reflow(dlg);
2535  window_set_focus(pview->win_pager);
2536 
2537  //---------- jump to the bottom if requested ------------------------------
2538  if (pview->flags & MUTT_PAGER_BOTTOM)
2539  {
2540  jump_to_bottom(priv, pview);
2541  }
2542 
2543  //-------------------------------------------------------------------------
2544  // ACT 3: Read user input and decide what to do with it
2545  // ...but also do a whole lot of other things.
2546  //-------------------------------------------------------------------------
2547  while (op != -1)
2548  {
2549  if (check_read_delay(&delay_read_timestamp))
2550  {
2551  mutt_set_flag(shared->mailbox, shared->email, MUTT_READ, true);
2552  }
2554 
2556  window_redraw(NULL);
2557  pager_custom_redraw(priv);
2558 
2559  const bool c_braille_friendly =
2560  cs_subset_bool(NeoMutt->sub, "braille_friendly");
2561  if (c_braille_friendly)
2562  {
2563  if (braille_row != -1)
2564  {
2566  braille_row = -1;
2567  }
2568  }
2569  else
2570  mutt_window_move(priv->pview->win_pbar, priv->pview->win_pager->state.cols - 1, 0);
2571 
2572  // force redraw of the screen at every iteration of the event loop
2573  mutt_refresh();
2574 
2575  //-------------------------------------------------------------------------
2576  // Check if information in the status bar needs an update
2577  // This is done because pager is a single-threaded application, which
2578  // tries to emulate concurrency.
2579  //-------------------------------------------------------------------------
2580  bool do_new_mail = false;
2581  if (shared->mailbox && !OptAttachMsg)
2582  {
2583  int oldcount = shared->mailbox->msg_count;
2584  /* check for new mail */
2585  enum MxStatus check = mx_mbox_check(shared->mailbox);
2586  if (check == MX_STATUS_ERROR)
2587  {
2588  if (!shared->mailbox || mutt_buffer_is_empty(&shared->mailbox->pathbuf))
2589  {
2590  /* fatal error occurred */
2592  break;
2593  }
2594  }
2595  else if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED) ||
2596  (check == MX_STATUS_FLAGS))
2597  {
2598  /* notify user of newly arrived mail */
2599  if (check == MX_STATUS_NEW_MAIL)
2600  {
2601  for (size_t i = oldcount; i < shared->mailbox->msg_count; i++)
2602  {
2603  struct Email *e = shared->mailbox->emails[i];
2604 
2605  if (e && !e->read)
2606  {
2607  mutt_message(_("New mail in this mailbox"));
2608  do_new_mail = true;
2609  break;
2610  }
2611  }
2612  }
2613 
2614  if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
2615  {
2616  if (priv->menu && shared->mailbox)
2617  {
2618  /* After the mailbox has been updated, selection might be invalid */
2619  int index = menu_get_index(priv->menu);
2620  menu_set_index(priv->menu, MIN(index, MAX(shared->mailbox->msg_count - 1, 0)));
2621  index = menu_get_index(priv->menu);
2622  struct Email *e = mutt_get_virt_email(shared->mailbox, index);
2623  if (!e)
2624  continue;
2625 
2626  bool verbose = shared->mailbox->verbose;
2627  shared->mailbox->verbose = false;
2628  mutt_update_index(priv->menu, shared->ctx, check, oldcount, shared);
2629  shared->mailbox->verbose = verbose;
2630 
2631  priv->menu->max = shared->mailbox->vcount;
2632 
2633  /* If these header pointers don't match, then our email may have
2634  * been deleted. Make the pointer safe, then leave the pager.
2635  * This have a unpleasant behaviour to close the pager even the
2636  * deleted message is not the opened one, but at least it's safe. */
2637  e = mutt_get_virt_email(shared->mailbox, index);
2638  if (shared->email != e)
2639  {
2640  shared->email = e;
2641  break;
2642  }
2643  }
2644 
2646  OptSearchInvalid = true;
2647  }
2648  }
2649 
2650  if (mutt_mailbox_notify(shared->mailbox) || do_new_mail)
2651  {
2652  const bool c_beep_new = cs_subset_bool(NeoMutt->sub, "beep_new");
2653  if (c_beep_new)
2654  mutt_beep(true);
2655  const char *const c_new_mail_command =
2656  cs_subset_string(NeoMutt->sub, "new_mail_command");
2657  if (c_new_mail_command)
2658  {
2659  char cmd[1024];
2660  menu_status_line(cmd, sizeof(cmd), shared, priv->menu, sizeof(cmd),
2661  NONULL(c_new_mail_command));
2662  if (mutt_system(cmd) != 0)
2663  mutt_error(_("Error running \"%s\""), cmd);
2664  }
2665  }
2666  }
2667  //-------------------------------------------------------------------------
2668 
2669  if (SigWinch)
2670  {
2671  SigWinch = false;
2673  clearok(stdscr, true); /* force complete redraw */
2675 
2676  if (pview->flags & MUTT_PAGER_RETWINCH)
2677  {
2678  /* Store current position. */
2679  priv->lines = -1;
2680  for (size_t i = 0; i <= priv->topline; i++)
2681  if (!priv->line_info[i].continuation)
2682  priv->lines++;
2683 
2684  Resize = mutt_mem_malloc(sizeof(struct Resize));
2685 
2686  Resize->line = priv->lines;
2687  Resize->search_compiled = priv->search_compiled;
2688  Resize->search_back = priv->search_back;
2689 
2690  op = -1;
2691  rc = OP_REFORMAT_WINCH;
2692  }
2693  else
2694  {
2695  /* note: mutt_resize_screen() -> mutt_window_reflow() sets
2696  * MENU_REDRAW_FULL and MENU_REDRAW_FLOW */
2697  op = 0;
2698  }
2699  continue;
2700  }
2701  //-------------------------------------------------------------------------
2702  // Finally, read user's key press
2703  //-------------------------------------------------------------------------
2704  // km_dokey() reads not only user's key strokes, but also a MacroBuffer
2705  // MacroBuffer may contain OP codes of the operations.
2706  // MacroBuffer is global
2707  // OP codes inserted into the MacroBuffer by various functions.
2708  // One of such functions is `mutt_enter_command()`
2709  // Some OP codes are not handled by pager, they cause pager to quit returning
2710  // OP code to index. Index handles the operation and then restarts pager
2711  op = km_dokey(MENU_PAGER);
2712 
2713  if (op >= 0)
2714  {
2715  mutt_clear_error();
2716  mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", OpStrings[op][0], op);
2717  }
2719 
2720  if (op < 0)
2721  {
2722  op = 0;
2724  continue;
2725  }
2726 
2727  rc = op;
2728 
2729  switch (op)
2730  {
2731  //=======================================================================
2732 
2733  case OP_EXIT:
2734  rc = -1;
2735  op = -1;
2736  break;
2737 
2738  //=======================================================================
2739 
2740  case OP_QUIT:
2741  {
2742  const enum QuadOption c_quit = cs_subset_quad(NeoMutt->sub, "quit");
2743  if (query_quadoption(c_quit, _("Quit NeoMutt?")) == MUTT_YES)
2744  {
2745  /* avoid prompting again in the index menu */
2746  cs_subset_str_native_set(NeoMutt->sub, "quit", MUTT_YES, NULL);
2747  op = -1;
2748  }
2749  break;
2750  }
2751 
2752  //=======================================================================
2753 
2754  case OP_NEXT_PAGE:
2755  {
2756  const bool c_pager_stop = cs_subset_bool(NeoMutt->sub, "pager_stop");
2757  if (priv->line_info[priv->curline].offset < (priv->sb.st_size - 1))
2758  {
2759  const short c_pager_context =
2760  cs_subset_number(NeoMutt->sub, "pager_context");
2761  priv->topline = up_n_lines(c_pager_context, priv->line_info,
2762  priv->curline, priv->hide_quoted);
2763  }
2764  else if (c_pager_stop)
2765  {
2766  /* emulate "less -q" and don't go on to the next message. */
2767  mutt_message(_("Bottom of message is shown"));
2768  }
2769  else
2770  {
2771  /* end of the current message, so display the next message. */
2772  rc = OP_MAIN_NEXT_UNDELETED;
2773  op = -1;
2774  }
2775  break;
2776  }
2777 
2778  //=======================================================================
2779 
2780  case OP_PREV_PAGE:
2781  if (priv->topline == 0)
2782  {
2783  mutt_message(_("Top of message is shown"));
2784  }
2785  else
2786  {
2787  const short c_pager_context =
2788  cs_subset_number(NeoMutt->sub, "pager_context");
2789  priv->topline =
2790  up_n_lines(priv->pview->win_pager->state.rows - c_pager_context,
2791  priv->line_info, priv->topline, priv->hide_quoted);
2792  }
2793  break;
2794 
2795  //=======================================================================
2796 
2797  case OP_NEXT_LINE:
2798  if (priv->line_info[priv->curline].offset < (priv->sb.st_size - 1))
2799  {
2800  priv->topline++;
2801  if (priv->hide_quoted)
2802  {
2803  while ((priv->line_info[priv->topline].type == MT_COLOR_QUOTED) &&
2804  (priv->topline < priv->last_line))
2805  {
2806  priv->topline++;
2807  }
2808  }
2809  }
2810  else
2811  mutt_message(_("Bottom of message is shown"));
2812  break;
2813 
2814  //=======================================================================
2815 
2816  case OP_PREV_LINE:
2817  if (priv->topline)
2818  priv->topline = up_n_lines(1, priv->line_info, priv->topline, priv->hide_quoted);
2819  else
2820  mutt_message(_("Top of message is shown"));
2821  break;
2822 
2823  //=======================================================================
2824 
2825  case OP_PAGER_TOP:
2826  if (priv->topline)
2827  priv->topline = 0;
2828  else
2829  mutt_message(_("Top of message is shown"));
2830  break;
2831 
2832  //=======================================================================
2833 
2834  case OP_HALF_UP:
2835  if (priv->topline)
2836  {
2837  priv->topline = up_n_lines(priv->pview->win_pager->state.rows / 2 +
2838  (priv->pview->win_pager->state.rows % 2),
2839  priv->line_info, priv->topline, priv->hide_quoted);
2840  }
2841  else
2842  mutt_message(_("Top of message is shown"));
2843  break;
2844 
2845  //=======================================================================
2846 
2847  case OP_HALF_DOWN:
2848  {
2849  const bool c_pager_stop = cs_subset_bool(NeoMutt->sub, "pager_stop");
2850  if (priv->line_info[priv->curline].offset < (priv->sb.st_size - 1))
2851  {
2852  priv->topline = up_n_lines(priv->pview->win_pager->state.rows / 2,
2853  priv->line_info, priv->curline, priv->hide_quoted);
2854  }
2855  else if (c_pager_stop)
2856  {
2857  /* emulate "less -q" and don't go on to the next message. */
2858  mutt_message(_("Bottom of message is shown"));
2859  }
2860  else
2861  {
2862  /* end of the current message, so display the next message. */
2863  rc = OP_MAIN_NEXT_UNDELETED;
2864  op = -1;
2865  }
2866  break;
2867  }
2868 
2869  //=======================================================================
2870 
2871  case OP_SEARCH_NEXT:
2872  case OP_SEARCH_OPPOSITE:
2873  if (priv->search_compiled)
2874  {
2875  const short c_search_context =
2876  cs_subset_number(NeoMutt->sub, "search_context");
2877  wrapped = false;
2878 
2879  if (c_search_context < priv->pview->win_pager->state.rows)
2880  searchctx = c_search_context;
2881  else
2882  searchctx = 0;
2883 
2884  search_next:
2885  if ((!priv->search_back && (op == OP_SEARCH_NEXT)) ||
2886  (priv->search_back && (op == OP_SEARCH_OPPOSITE)))
2887  {
2888  /* searching forward */
2889  int i;
2890  for (i = wrapped ? 0 : priv->topline + searchctx + 1; i < priv->last_line; i++)
2891  {
2892  if ((!priv->hide_quoted || (priv->line_info[i].type != MT_COLOR_QUOTED)) &&
2893  !priv->line_info[i].continuation && (priv->line_info[i].search_cnt > 0))
2894  {
2895  break;
2896  }
2897  }
2898 
2899  const bool c_wrap_search =
2900  cs_subset_bool(NeoMutt->sub, "wrap_search");
2901  if (i < priv->last_line)
2902  priv->topline = i;
2903  else if (wrapped || !c_wrap_search)
2904  mutt_error(_("Not found"));
2905  else
2906  {
2907  mutt_message(_("Search wrapped to top"));
2908  wrapped = true;
2909  goto search_next;
2910  }
2911  }
2912  else
2913  {
2914  /* searching backward */
2915  int i;
2916  for (i = wrapped ? priv->last_line : priv->topline + searchctx - 1; i >= 0; i--)
2917  {
2918  if ((!priv->hide_quoted ||
2919  (priv->has_types && (priv->line_info[i].type != MT_COLOR_QUOTED))) &&
2920  !priv->line_info[i].continuation && (priv->line_info[i].search_cnt > 0))
2921  {
2922  break;
2923  }
2924  }
2925 
2926  const bool c_wrap_search =
2927  cs_subset_bool(NeoMutt->sub, "wrap_search");
2928  if (i >= 0)
2929  priv->topline = i;
2930  else if (wrapped || !c_wrap_search)
2931  mutt_error(_("Not found"));
2932  else
2933  {
2934  mutt_message(_("Search wrapped to bottom"));
2935  wrapped = true;
2936  goto search_next;
2937  }
2938  }
2939 
2940  if (priv->line_info[priv->topline].search_cnt > 0)
2941  {
2942  priv->search_flag = MUTT_SEARCH;
2943  /* give some context for search results */
2944  if (priv->topline - searchctx > 0)
2945  priv->topline -= searchctx;
2946  }
2947 
2948  break;
2949  }
2950  /* no previous search pattern */
2951  /* fallthrough */
2952  //=======================================================================
2953 
2954  case OP_SEARCH:
2955  case OP_SEARCH_REVERSE:
2956  mutt_str_copy(buf, priv->searchbuf, sizeof(buf));
2957  if (mutt_get_field(((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ?
2958  _("Search for: ") :
2959  _("Reverse search for: "),
2960  buf, sizeof(buf), MUTT_CLEAR | MUTT_PATTERN, false,
2961  NULL, NULL) != 0)
2962  {
2963  break;
2964  }
2965 
2966  if (strcmp(buf, priv->searchbuf) == 0)
2967  {
2968  if (priv->search_compiled)
2969  {
2970  /* do an implicit search-next */
2971  if (op == OP_SEARCH)
2972  op = OP_SEARCH_NEXT;
2973  else
2974  op = OP_SEARCH_OPPOSITE;
2975 
2976  wrapped = false;
2977  goto search_next;
2978  }
2979  }
2980 
2981  if (buf[0] == '\0')
2982  break;
2983 
2984  mutt_str_copy(priv->searchbuf, buf, sizeof(priv->searchbuf));
2985 
2986  /* leave search_back alone if op == OP_SEARCH_NEXT */
2987  if (op == OP_SEARCH)
2988  priv->search_back = false;
2989  else if (op == OP_SEARCH_REVERSE)
2990  priv->search_back = true;
2991 
2992  if (priv->search_compiled)
2993  {
2994  regfree(&priv->search_re);
2995  for (size_t i = 0; i < priv->last_line; i++)
2996  {
2997  FREE(&(priv->line_info[i].search));
2998  priv->line_info[i].search_cnt = -1;
2999  }
3000  }
3001 
3002  uint16_t rflags = mutt_mb_is_lower(priv->searchbuf) ? REG_ICASE : 0;
3003  int err = REG_COMP(&priv->search_re, priv->searchbuf, REG_NEWLINE | rflags);
3004  if (err != 0)
3005  {
3006  regerror(err, &priv->search_re, buf, sizeof(buf));
3007  mutt_error("%s", buf);
3008  for (size_t i = 0; i < priv->max_line; i++)
3009  {
3010  /* cleanup */
3011  FREE(&(priv->line_info[i].search));
3012  priv->line_info[i].search_cnt = -1;
3013  }
3014  priv->search_flag = 0;
3015  priv->search_compiled = false;
3016  }
3017  else
3018  {
3019  priv->search_compiled = true;
3020  /* update the search pointers */
3021  int line_num = 0;
3022  while (display_line(priv->fp, &priv->last_pos, &priv->line_info,
3023  line_num, &priv->last_line, &priv->max_line,
3024  MUTT_SEARCH | (pview->flags & MUTT_PAGER_NSKIP) |
3025  (pview->flags & MUTT_PAGER_NOWRAP),
3026  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3027  &priv->search_re, priv->pview->win_pager) == 0)
3028  {
3029  line_num++;
3030  }
3031 
3032  if (!priv->search_back)
3033  {
3034  /* searching forward */
3035  int i;
3036  for (i = priv->topline; i < priv->last_line; i++)
3037  {
3038  if ((!priv->hide_quoted || (priv->line_info[i].type != MT_COLOR_QUOTED)) &&
3039  !priv->line_info[i].continuation && (priv->line_info[i].search_cnt > 0))
3040  {
3041  break;
3042  }
3043  }
3044 
3045  if (i < priv->last_line)
3046  priv->topline = i;
3047  }
3048  else
3049  {
3050  /* searching backward */
3051  int i;
3052  for (i = priv->topline; i >= 0; i--)
3053  {
3054  if ((!priv->hide_quoted || (priv->line_info[i].type != MT_COLOR_QUOTED)) &&
3055  !priv->line_info[i].continuation && (priv->line_info[i].search_cnt > 0))
3056  {
3057  break;
3058  }
3059  }
3060 
3061  if (i >= 0)
3062  priv->topline = i;
3063  }
3064 
3065  if (priv->line_info[priv->topline].search_cnt == 0)
3066  {
3067  priv->search_flag = 0;
3068  mutt_error(_("Not found"));
3069  }
3070  else
3071  {
3072  const short c_search_context =
3073  cs_subset_number(NeoMutt->sub, "search_context");
3074  priv->search_flag = MUTT_SEARCH;
3075  /* give some context for search results */
3076  if (c_search_context < priv->pview->win_pager->state.rows)
3077  searchctx = c_search_context;
3078  else
3079  searchctx = 0;
3080  if (priv->topline - searchctx > 0)
3081  priv->topline -= searchctx;
3082  }
3083  }
3085  break;
3086 
3087  //=======================================================================
3088 
3089  case OP_SEARCH_TOGGLE:
3090  if (priv->search_compiled)
3091  {
3092  priv->search_flag ^= MUTT_SEARCH;
3094  }
3095  break;
3096 
3097  //=======================================================================
3098 
3099  case OP_SORT:
3100  case OP_SORT_REVERSE:
3101  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3102  break;
3103  if (mutt_select_sort(op == OP_SORT_REVERSE))
3104  {
3105  OptNeedResort = true;
3106  op = -1;
3107  rc = OP_DISPLAY_MESSAGE;
3108  }
3109  break;
3110 
3111  //=======================================================================
3112 
3113  case OP_HELP:
3114  if (pview->mode == PAGER_MODE_HELP)
3115  {
3116  /* don't let the user enter the help-menu from the help screen! */
3117  mutt_error(_("Help is currently being shown"));
3118  break;
3119  }
3122  break;
3123 
3124  //=======================================================================
3125 
3126  case OP_PAGER_HIDE_QUOTED:
3127  if (!priv->has_types)
3128  break;
3129 
3130  priv->hide_quoted ^= MUTT_HIDE;
3131  if (priv->hide_quoted && (priv->line_info[priv->topline].type == MT_COLOR_QUOTED))
3132  priv->topline = up_n_lines(1, priv->line_info, priv->topline, priv->hide_quoted);
3133  else
3135  break;
3136 
3137  //=======================================================================
3138 
3139  case OP_PAGER_SKIP_QUOTED:
3140  {
3141  if (!priv->has_types)
3142  break;
3143 
3144  const short c_skip_quoted_context =
3145  cs_subset_number(NeoMutt->sub, "pager_skip_quoted_context");
3146  int dretval = 0;
3147  int new_topline = priv->topline;
3148  int num_quoted = 0;
3149 
3150  /* In a header? Skip all the email headers, and done */
3151  if (mutt_color_is_header(priv->line_info[new_topline].type))
3152  {
3153  while (((new_topline < priv->last_line) ||
3154  (0 == (dretval = display_line(
3155  priv->fp, &priv->last_pos, &priv->line_info,
3156  new_topline, &priv->last_line, &priv->max_line,
3157  MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
3158  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3159  &priv->search_re, priv->pview->win_pager)))) &&
3160  mutt_color_is_header(priv->line_info[new_topline].type))
3161  {
3162  new_topline++;
3163  }
3164  priv->topline = new_topline;
3165  break;
3166  }
3167 
3168  /* Already in the body? Skip past previous "context" quoted lines */
3169  if (c_skip_quoted_context > 0)
3170  {
3171  while (((new_topline < priv->last_line) ||
3172  (0 == (dretval = display_line(
3173  priv->fp, &priv->last_pos, &priv->line_info,
3174  new_topline, &priv->last_line, &priv->max_line,
3175  MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
3176  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3177  &priv->search_re, priv->pview->win_pager)))) &&
3178  (priv->line_info[new_topline].type == MT_COLOR_QUOTED))
3179  {
3180  new_topline++;
3181  num_quoted++;
3182  }
3183 
3184  if (dretval < 0)
3185  {
3186  mutt_error(_("No more unquoted text after quoted text"));
3187  break;
3188  }
3189  }
3190 
3191  if (num_quoted <= c_skip_quoted_context)
3192  {
3193  num_quoted = 0;
3194 
3195  while (((new_topline < priv->last_line) ||
3196  (0 == (dretval = display_line(
3197  priv->fp, &priv->last_pos, &priv->line_info,
3198  new_topline, &priv->last_line, &priv->max_line,
3199  MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
3200  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3201  &priv->search_re, priv->pview->win_pager)))) &&
3202  (priv->line_info[new_topline].type != MT_COLOR_QUOTED))
3203  {
3204  new_topline++;
3205  }
3206 
3207  if (dretval < 0)
3208  {
3209  mutt_error(_("No more quoted text"));
3210  break;
3211  }
3212 
3213  while (((new_topline < priv->last_line) ||
3214  (0 == (dretval = display_line(
3215  priv->fp, &priv->last_pos, &priv->line_info,
3216  new_topline, &priv->last_line, &priv->max_line,
3217  MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
3218  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3219  &priv->search_re, priv->pview->win_pager)))) &&
3220  (priv->line_info[new_topline].type == MT_COLOR_QUOTED))
3221  {
3222  new_topline++;
3223  num_quoted++;
3224  }
3225 
3226  if (dretval < 0)
3227  {
3228  mutt_error(_("No more unquoted text after quoted text"));
3229  break;
3230  }
3231  }
3232  priv->topline = new_topline - MIN(c_skip_quoted_context, num_quoted);
3233  break;
3234  }
3235 
3236  //=======================================================================
3237 
3238  case OP_PAGER_SKIP_HEADERS:
3239  {
3240  if (!priv->has_types)
3241  break;
3242 
3243  int dretval = 0;
3244  int new_topline = 0;
3245 
3246  while (((new_topline < priv->last_line) ||
3247  (0 == (dretval = display_line(
3248  priv->fp, &priv->last_pos, &priv->line_info, new_topline, &priv->last_line,
3249  &priv->max_line, MUTT_TYPES | (pview->flags & MUTT_PAGER_NOWRAP),
3250  &priv->quote_list, &priv->q_level, &priv->force_redraw,
3251  &priv->search_re, priv->pview->win_pager)))) &&
3252  mutt_color_is_header(priv->line_info[new_topline].type))
3253  {
3254  new_topline++;
3255  }
3256 
3257  if (dretval < 0)
3258  {
3259  /* L10N: Displayed if <skip-headers> is invoked in the pager, but
3260  there is no text past the headers.
3261  (I don't think this is actually possible in Mutt's code, but
3262  display some kind of message in case it somehow occurs.) */
3263  mutt_warning(_("No text past headers"));
3264  break;
3265  }
3266  priv->topline = new_topline;
3267  break;
3268  }
3269 
3270  //=======================================================================
3271 
3272  case OP_PAGER_BOTTOM: /* move to the end of the file */
3273  if (!jump_to_bottom(priv, pview))
3274  {
3275  mutt_message(_("Bottom of message is shown"));
3276  }
3277  break;
3278 
3279  //=======================================================================
3280 
3281  case OP_REDRAW:
3282  mutt_window_reflow(NULL);
3283  clearok(stdscr, true);
3285  break;
3286 
3287  //=======================================================================
3288 
3289  case OP_NULL:
3291  break;
3292 
3293  //=======================================================================
3294  // The following are operations on the current message rather than
3295  // adjusting the view of the message.
3296  //=======================================================================
3297  case OP_BOUNCE_MESSAGE:
3298  {
3299  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3300  (pview->mode == PAGER_MODE_ATTACH_E)))
3301  {
3302  break;
3303  }
3305  break;
3306  if (pview->mode == PAGER_MODE_ATTACH_E)
3307  {
3308  mutt_attach_bounce(shared->mailbox, pview->pdata->fp,
3309  pview->pdata->actx, pview->pdata->body);
3310  }
3311  else
3312  {
3313  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3314  emaillist_add_email(&el, shared->email);
3315  ci_bounce_message(shared->mailbox, &el);
3316  emaillist_clear(&el);
3317  }
3318  break;
3319  }
3320 
3321  //=======================================================================
3322 
3323  case OP_RESEND:
3324  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3325  (pview->mode == PAGER_MODE_ATTACH_E)))
3326  {
3327  break;
3328  }
3330  break;
3331  if (pview->mode == PAGER_MODE_ATTACH_E)
3332  {
3333  mutt_attach_resend(pview->pdata->fp, ctx_mailbox(shared->ctx),
3334  pview->pdata->actx, pview->pdata->body);
3335  }
3336  else
3337  {
3338  mutt_resend_message(NULL, ctx_mailbox(shared->ctx), shared->email,
3339  NeoMutt->sub);
3340  }
3342  break;
3343 
3344  //=======================================================================
3345 
3346  case OP_COMPOSE_TO_SENDER:
3347  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3348  (pview->mode == PAGER_MODE_ATTACH_E)))
3349  {
3350  break;
3351  }
3353  break;
3354  if (pview->mode == PAGER_MODE_ATTACH_E)
3355  {
3356  mutt_attach_mail_sender(pview->pdata->fp, shared->email,
3357  pview->pdata->actx, pview->pdata->body);
3358  }
3359  else
3360  {
3361  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3362  emaillist_add_email(&el, shared->email);
3363 
3364  mutt_send_message(SEND_TO_SENDER, NULL, NULL,
3365  ctx_mailbox(shared->ctx), &el, NeoMutt->sub);
3366  emaillist_clear(&el);
3367  }
3369  break;
3370 
3371  //=======================================================================
3372 
3373  case OP_CHECK_TRADITIONAL:
3374  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3375  break;
3376  if (!(WithCrypto & APPLICATION_PGP))
3377  break;
3378  if (!(shared->email->security & PGP_TRADITIONAL_CHECKED))
3379  {
3380  op = -1;
3381  rc = OP_CHECK_TRADITIONAL;
3382  }
3383  break;
3384 
3385  //=======================================================================
3386 
3387  case OP_CREATE_ALIAS:
3388  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3389  (pview->mode == PAGER_MODE_ATTACH_E)))
3390  {
3391  break;
3392  }
3393  struct AddressList *al = NULL;
3394  if (pview->mode == PAGER_MODE_ATTACH_E)
3395  al = mutt_get_address(pview->pdata->body->email->env, NULL);
3396  else
3397  al = mutt_get_address(shared->email->env, NULL);
3398  alias_create(al, NeoMutt->sub);
3399  break;
3400 
3401  //=======================================================================
3402 
3403  case OP_PURGE_MESSAGE:
3404  case OP_DELETE:
3405  {
3406  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3407  break;
3408  if (!assert_mailbox_writable(shared->mailbox))
3409  break;
3410  /* L10N: CHECK_ACL */
3412  _("Can't delete message")))
3413  {
3414  break;
3415  }
3416 
3417  mutt_set_flag(shared->mailbox, shared->email, MUTT_DELETE, true);
3418  mutt_set_flag(shared->mailbox, shared->email, MUTT_PURGE, (op == OP_PURGE_MESSAGE));
3419  const bool c_delete_untag =
3420  cs_subset_bool(NeoMutt->sub, "delete_untag");
3421  if (c_delete_untag)
3422  mutt_set_flag(shared->mailbox, shared->email, MUTT_TAG, false);
3424  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3425  if (c_resolve)
3426  {
3427  op = -1;
3428  rc = OP_MAIN_NEXT_UNDELETED;
3429  }
3430  break;
3431  }
3432 
3433  //=======================================================================
3434 
3435  case OP_MAIN_SET_FLAG:
3436  case OP_MAIN_CLEAR_FLAG:
3437  {
3438  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3439  break;
3440  if (!assert_mailbox_writable(shared->mailbox))
3441  break;
3442 
3443  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3444  emaillist_add_email(&el, shared->email);
3445 
3446  if (mutt_change_flag(shared->mailbox, &el, (op == OP_MAIN_SET_FLAG)) == 0)
3448  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3449  if (shared->email->deleted && c_resolve)
3450  {
3451  op = -1;
3452  rc = OP_MAIN_NEXT_UNDELETED;
3453  }
3454  emaillist_clear(&el);
3455  break;
3456  }
3457 
3458  //=======================================================================
3459 
3460  case OP_DELETE_THREAD:
3461  case OP_DELETE_SUBTHREAD:
3462  case OP_PURGE_THREAD:
3463  {
3464  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3465  break;
3466  if (!assert_mailbox_writable(shared->mailbox))
3467  break;
3468  /* L10N: CHECK_ACL */
3469  /* L10N: Due to the implementation details we do not know whether we
3470  delete zero, 1, 12, ... messages. So in English we use
3471  "messages". Your language might have other means to express this. */
3473  _("Can't delete messages")))
3474  {
3475  break;
3476  }
3477 
3478  int subthread = (op == OP_DELETE_SUBTHREAD);
3479  int r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE, 1, subthread);
3480  if (r == -1)
3481  break;
3482  if (op == OP_PURGE_THREAD)
3483  {
3484  r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE, true, subthread);
3485  if (r == -1)
3486  break;
3487  }
3488 
3489  const bool c_delete_untag =
3490  cs_subset_bool(NeoMutt->sub, "delete_untag");
3491  if (c_delete_untag)
3492  mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_TAG, 0, subthread);
3493  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3494  if (c_resolve)
3495  {
3496  rc = OP_MAIN_NEXT_UNDELETED;
3497  op = -1;
3498  }
3499 
3500  if (!c_resolve &&
3501  (cs_subset_number(NeoMutt->sub, "pager_index_lines") != 0))
3503  else
3505 
3506  break;
3507  }
3508 
3509  //=======================================================================
3510 
3511  case OP_DISPLAY_ADDRESS:
3512  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3513  (pview->mode == PAGER_MODE_ATTACH_E)))
3514  {
3515  break;
3516  }
3517  if (pview->mode == PAGER_MODE_ATTACH_E)
3519  else
3520  mutt_display_address(shared->email->env);
3521  break;
3522 
3523  //=======================================================================
3524 
3525  case OP_ENTER_COMMAND:
3528 
3529  if (OptNeedResort)
3530  {
3531  OptNeedResort = false;
3532  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3533  break;
3534  OptNeedResort = true;
3535  }
3536 
3537  if ((priv->redraw & MENU_REDRAW_FLOW) && (pview->flags & MUTT_PAGER_RETWINCH))
3538  {
3539  op = -1;
3540  rc = OP_REFORMAT_WINCH;
3541  continue;
3542  }
3543 
3544  op = 0;
3545  break;
3546 
3547  //=======================================================================
3548 
3549  case OP_FLAG_MESSAGE:
3550  {
3551  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3552  break;
3553  if (!assert_mailbox_writable(shared->mailbox))
3554  break;
3555  /* L10N: CHECK_ACL */
3556  if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_WRITE, "Can't flag message"))
3557  break;
3558 
3559  mutt_set_flag(shared->mailbox, shared->email, MUTT_FLAG, !shared->email->flagged);
3561  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3562  if (c_resolve)
3563  {
3564  op = -1;
3565  rc = OP_MAIN_NEXT_UNDELETED;
3566  }
3567  break;
3568  }
3569 
3570  //=======================================================================
3571 
3572  case OP_PIPE:
3573  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3574  (pview->mode == PAGER_MODE_ATTACH)))
3575  {
3576  break;
3577  }
3578  if (pview->mode == PAGER_MODE_ATTACH)
3579  {
3580  mutt_pipe_attachment_list(pview->pdata->actx, pview->pdata->fp, false,
3581  pview->pdata->body, false);
3582  }
3583  else
3584  {
3585  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3586  el_add_tagged(&el, shared->ctx, shared->email, false);
3587  mutt_pipe_message(shared->mailbox, &el);
3588  emaillist_clear(&el);
3589  }
3590  break;
3591 
3592  //=======================================================================
3593 
3594  case OP_PRINT:
3595  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3596  (pview->mode == PAGER_MODE_ATTACH)))
3597  {
3598  break;
3599  }
3600  if (pview->mode == PAGER_MODE_ATTACH)
3601  {
3602  mutt_print_attachment_list(pview->pdata->actx, pview->pdata->fp,
3603  false, pview->pdata->body);
3604  }
3605  else
3606  {
3607  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3608  el_add_tagged(&el, shared->ctx, shared->email, false);
3609  mutt_print_message(shared->mailbox, &el);
3610  emaillist_clear(&el);
3611  }
3612  break;
3613 
3614  //=======================================================================
3615 
3616  case OP_MAIL:
3617  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3618  break;
3620  break;
3621 
3622  mutt_send_message(SEND_NO_FLAGS, NULL, NULL, ctx_mailbox(shared->ctx),
3623  NULL, NeoMutt->sub);
3625  break;
3626 
3627  //=======================================================================
3628 
3629 #ifdef USE_NNTP
3630  case OP_POST:
3631  {
3632  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3633  break;
3635  break;
3636  const enum QuadOption c_post_moderated =
3637  cs_subset_quad(NeoMutt->sub, "post_moderated");
3638  if ((shared->mailbox->type == MUTT_NNTP) &&
3639  !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
3640  {
3641  break;
3642  }
3643 
3644  mutt_send_message(SEND_NEWS, NULL, NULL, ctx_mailbox(shared->ctx), NULL,
3645  NeoMutt->sub);
3647  break;
3648  }
3649 
3650  //=======================================================================
3651 
3652  case OP_FORWARD_TO_GROUP:
3653  {
3654  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3655  (pview->mode == PAGER_MODE_ATTACH_E)))
3656  {
3657  break;
3658  }
3660  break;
3661  const enum QuadOption c_post_moderated =
3662  cs_subset_quad(NeoMutt->sub, "post_moderated");
3663  if ((shared->mailbox->type == MUTT_NNTP) &&
3664  !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
3665  {
3666  break;
3667  }
3668  if (pview->mode == PAGER_MODE_ATTACH_E)
3669  {
3670  mutt_attach_forward(pview->pdata->fp, shared->email,
3671  pview->pdata->actx, pview->pdata->body, SEND_NEWS);
3672  }
3673  else
3674  {
3675  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3676  emaillist_add_email(&el, shared->email);
3677 
3679  ctx_mailbox(shared->ctx), &el, NeoMutt->sub);
3680  emaillist_clear(&el);
3681  }
3683  break;
3684  }
3685 
3686  //=======================================================================
3687 
3688  case OP_FOLLOWUP:
3689  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3690  (pview->mode == PAGER_MODE_ATTACH_E)))
3691  {
3692  break;
3693  }
3695  break;
3696 
3697  char *followup_to = NULL;
3698  if (pview->mode == PAGER_MODE_ATTACH_E)
3699  followup_to = pview->pdata->body->email->env->followup_to;
3700  else
3701  followup_to = shared->email->env->followup_to;
3702 
3703  const enum QuadOption c_followup_to_poster =
3704  cs_subset_quad(NeoMutt->sub, "followup_to_poster");
3705  if (!followup_to || !mutt_istr_equal(followup_to, "poster") ||
3706  (query_quadoption(c_followup_to_poster,
3707  _("Reply by mail as poster prefers?")) != MUTT_YES))
3708  {
3709  const enum QuadOption c_post_moderated =
3710  cs_subset_quad(NeoMutt->sub, "post_moderated");
3711  if ((shared->mailbox->type == MUTT_NNTP) &&
3712  !((struct NntpMboxData *) shared->mailbox->mdata)->allowed && (query_quadoption(c_post_moderated, _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES))
3713  {
3714  break;
3715  }
3716  if (pview->mode == PAGER_MODE_ATTACH_E)
3717  {
3718  mutt_attach_reply(pview->pdata->fp, shared->mailbox, shared->email,
3719  pview->pdata->actx, pview->pdata->body, SEND_NEWS | SEND_REPLY);
3720  }
3721  else
3722  {
3723  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3724  emaillist_add_email(&el, shared->email);
3725  mutt_send_message(SEND_NEWS | SEND_REPLY, NULL, NULL,
3726  ctx_mailbox(shared->ctx), &el, NeoMutt->sub);
3727  emaillist_clear(&el);
3728  }
3730  break;
3731  }
3732 
3733  //=======================================================================
3734 
3735 #endif
3736  /* fallthrough */
3737  case OP_REPLY:
3738  case OP_GROUP_REPLY:
3739  case OP_GROUP_CHAT_REPLY:
3740  case OP_LIST_REPLY:
3741  {
3742  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3743  (pview->mode == PAGER_MODE_ATTACH_E)))
3744  {
3745  break;
3746  }
3748  break;
3749 
3750  SendFlags replyflags = SEND_REPLY;
3751  if (op == OP_GROUP_REPLY)
3752  replyflags |= SEND_GROUP_REPLY;
3753  else if (op == OP_GROUP_CHAT_REPLY)
3754  replyflags |= SEND_GROUP_CHAT_REPLY;
3755  else if (op == OP_LIST_REPLY)
3756  replyflags |= SEND_LIST_REPLY;
3757 
3758  if (pview->mode == PAGER_MODE_ATTACH_E)
3759  {
3760  mutt_attach_reply(pview->pdata->fp, shared->mailbox, shared->email,
3761  pview->pdata->actx, pview->pdata->body, replyflags);
3762  }
3763  else
3764  {
3765  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3766  emaillist_add_email(&el, shared->email);
3767  mutt_send_message(replyflags, NULL, NULL, ctx_mailbox(shared->ctx),
3768  &el, NeoMutt->sub);
3769  emaillist_clear(&el);
3770  }
3772  break;
3773  }
3774 
3775  //=======================================================================
3776 
3777  case OP_RECALL_MESSAGE:
3778  {
3779  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3780  break;
3782  break;
3783  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3784  emaillist_add_email(&el, shared->email);
3785 
3786  mutt_send_message(SEND_POSTPONED, NULL, NULL, ctx_mailbox(shared->ctx),
3787  &el, NeoMutt->sub);
3788  emaillist_clear(&el);
3790  break;
3791  }
3792 
3793  //=======================================================================
3794 
3795  case OP_FORWARD_MESSAGE:
3796  if (!assert_pager_mode((pview->mode == PAGER_MODE_EMAIL) ||
3797  (pview->mode == PAGER_MODE_ATTACH_E)))
3798  {
3799  break;
3800  }
3802  break;
3803  if (pview->mode == PAGER_MODE_ATTACH_E)
3804  {
3805  mutt_attach_forward(pview->pdata->fp, shared->email, pview->pdata->actx,
3806  pview->pdata->body, SEND_NO_FLAGS);
3807  }
3808  else
3809  {
3810  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3811  emaillist_add_email(&el, shared->email);
3812 
3813  mutt_send_message(SEND_FORWARD, NULL, NULL, ctx_mailbox(shared->ctx),
3814  &el, NeoMutt->sub);
3815  emaillist_clear(&el);
3816  }
3818  break;
3819 
3820  //=======================================================================
3821 
3822  case OP_DECRYPT_SAVE:
3823  if (!WithCrypto)
3824  {
3825  op = -1;
3826  break;
3827  }
3828  /* fallthrough */
3829  //=======================================================================
3830 
3831  case OP_SAVE:
3832  if (pview->mode == PAGER_MODE_ATTACH)
3833  {
3834  mutt_save_attachment_list(pview->pdata->actx, pview->pdata->fp, false,
3835  pview->pdata->body, shared->email, NULL);
3836  break;
3837  }
3838  /* fallthrough */
3839  //=======================================================================
3840 
3841  case OP_COPY_MESSAGE:
3842  case OP_DECODE_SAVE:
3843  case OP_DECODE_COPY:
3844  case OP_DECRYPT_COPY:
3845  {
3846  if (!(WithCrypto != 0) && (op == OP_DECRYPT_COPY))
3847  {
3848  op = -1;
3849  break;
3850  }
3851  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3852  break;
3853  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
3854  emaillist_add_email(&el, shared->email);
3855 
3856  const enum MessageSaveOpt save_opt =
3857  ((op == OP_SAVE) || (op == OP_DECODE_SAVE) || (op == OP_DECRYPT_SAVE)) ?
3858  SAVE_MOVE :
3859  SAVE_COPY;
3860 
3861  enum MessageTransformOpt transform_opt =
3862  ((op == OP_DECODE_SAVE) || (op == OP_DECODE_COPY)) ? TRANSFORM_DECODE :
3863  ((op == OP_DECRYPT_SAVE) || (op == OP_DECRYPT_COPY)) ? TRANSFORM_DECRYPT :
3865 
3866  const int rc2 = mutt_save_message(shared->mailbox, &el, save_opt, transform_opt);
3867  if ((rc2 == 0) && (save_opt == SAVE_MOVE))
3868  {
3869  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3870  if (c_resolve)
3871  {
3872  op = -1;
3873  rc = OP_MAIN_NEXT_UNDELETED;
3874  }
3875  else
3877  }
3878  emaillist_clear(&el);
3879  break;
3880  }
3881 
3882  //=======================================================================
3883 
3884  case OP_SHELL_ESCAPE:
3885  if (mutt_shell_escape())
3886  {
3888  }
3889  break;
3890 
3891  //=======================================================================
3892 
3893  case OP_TAG:
3894  {
3895  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3896  break;
3897  mutt_set_flag(shared->mailbox, shared->email, MUTT_TAG, !shared->email->tagged);
3898 
3900  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3901  if (c_resolve)
3902  {
3903  op = -1;
3904  rc = OP_NEXT_ENTRY;
3905  }
3906  break;
3907  }
3908 
3909  //=======================================================================
3910 
3911  case OP_TOGGLE_NEW:
3912  {
3913  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3914  break;
3915  if (!assert_mailbox_writable(shared->mailbox))
3916  break;
3917  /* L10N: CHECK_ACL */
3918  if (!assert_mailbox_permissions(shared->mailbox, MUTT_ACL_SEEN, _("Can't toggle new")))
3919  break;
3920 
3921  if (shared->email->read || shared->email->old)
3922  mutt_set_flag(shared->mailbox, shared->email, MUTT_NEW, true);
3923  else if (!first || (delay_read_timestamp != 0))
3924  mutt_set_flag(shared->mailbox, shared->email, MUTT_READ, true);
3925  delay_read_timestamp = 0;
3926  first = false;
3927  shared->ctx->msg_in_pager = -1;
3928  priv->win_pbar->actions |= WA_RECALC;
3930  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3931  if (c_resolve)
3932  {
3933  op = -1;
3934  rc = OP_MAIN_NEXT_UNDELETED;
3935  }
3936  break;
3937  }
3938 
3939  //=======================================================================
3940 
3941  case OP_UNDELETE:
3942  {
3943  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3944  break;
3945  if (!assert_mailbox_writable(shared->mailbox))
3946  break;
3947  /* L10N: CHECK_ACL */
3949  _("Can't undelete message")))
3950  {
3951  break;
3952  }
3953 
3954  mutt_set_flag(shared->mailbox, shared->email, MUTT_DELETE, false);
3955  mutt_set_flag(shared->mailbox, shared->email, MUTT_PURGE, false);
3957  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3958  if (c_resolve)
3959  {
3960  op = -1;
3961  rc = OP_NEXT_ENTRY;
3962  }
3963  break;
3964  }
3965 
3966  //=======================================================================
3967 
3968  case OP_UNDELETE_THREAD:
3969  case OP_UNDELETE_SUBTHREAD:
3970  {
3971  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
3972  break;
3973  if (!assert_mailbox_writable(shared->mailbox))
3974  break;
3975  /* L10N: CHECK_ACL */
3976  /* L10N: Due to the implementation details we do not know whether we
3977  undelete zero, 1, 12, ... messages. So in English we use
3978  "messages". Your language might have other means to express this. */
3980  _("Can't undelete messages")))
3981  {
3982  break;
3983  }
3984 
3985  int r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_DELETE,
3986  false, (op != OP_UNDELETE_THREAD));
3987  if (r != -1)
3988  {
3989  r = mutt_thread_set_flag(shared->mailbox, shared->email, MUTT_PURGE,
3990  false, (op != OP_UNDELETE_THREAD));
3991  }
3992  if (r != -1)
3993  {
3994  const bool c_resolve = cs_subset_bool(NeoMutt->sub, "resolve");
3995  if (c_resolve)
3996  {
3997  rc = (op == OP_DELETE_THREAD) ? OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD;
3998  op = -1;
3999  }
4000 
4001  if (!c_resolve &&
4002  (cs_subset_number(NeoMutt->sub, "pager_index_lines") != 0))
4004  else
4006  }
4007  break;
4008  }
4009 
4010  //=======================================================================
4011 
4012  case OP_VERSION:
4014  break;
4015 
4016  //=======================================================================
4017 
4018  case OP_MAILBOX_LIST:
4020  break;
4021 
4022  //=======================================================================
4023 
4024  case OP_VIEW_ATTACHMENTS:
4025  if (pview->flags & MUTT_PAGER_ATTACHMENT)
4026  {
4027  op = -1;
4028  rc = OP_ATTACH_COLLAPSE;
4029  break;
4030  }
4031  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
4032  break;
4034  pview->pdata->fp);
4035  if (shared->email->attach_del)
4036  shared->mailbox->changed = true;
4038  break;
4039 
4040  //=======================================================================
4041 
4042  case OP_MAIL_KEY:
4043  {
4044  if (!(WithCrypto & APPLICATION_PGP))
4045  {
4046  op = -1;
4047  break;
4048  }
4049  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
4050  break;
4052  break;
4053  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
4054  emaillist_add_email(&el, shared->email);
4055 
4056  mutt_send_message(SEND_KEY, NULL, NULL, ctx_mailbox(shared->ctx), &el,
4057  NeoMutt->sub);
4058  emaillist_clear(&el);
4060  break;
4061  }
4062 
4063  //=======================================================================
4064 
4065  case OP_EDIT_LABEL:
4066  {
4067  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
4068  break;
4069 
4070  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
4071  emaillist_add_email(&el, shared->email);
4072  rc = mutt_label_message(shared->mailbox, &el);
4073  emaillist_clear(&el);
4074 
4075  if (rc > 0)
4076  {
4077  shared->mailbox->changed = true;
4079  mutt_message(ngettext("%d label changed", "%d labels changed", rc), rc);
4080  }
4081  else
4082  {
4083  mutt_message(_("No labels changed"));
4084  }
4085  break;
4086  }
4087 
4088  //=======================================================================
4089 
4090  case OP_FORGET_PASSPHRASE:
4092  break;
4093 
4094  //=======================================================================
4095 
4096  case OP_EXTRACT_KEYS:
4097  {
4098  if (!WithCrypto)
4099  {
4100  op = -1;
4101  break;
4102  }
4103  if (!assert_pager_mode(pview->mode == PAGER_MODE_EMAIL))
4104  break;
4105  struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
4106  emaillist_add_email(&el, shared->email);
4108  emaillist_clear(&el);
4110  break;
4111  }
4112 
4113  //=======================================================================
4114 
4115  case OP_WHAT_KEY:
4116  mutt_what_key();
4117  break;
4118 
4119  //=======================================================================
4120 
4121  case OP_CHECK_STATS:
4122  mutt_check_stats(shared->mailbox);
4123  break;
4124 
4125  //=======================================================================
4126 
4127 #ifdef USE_SIDEBAR
4128  case OP_SIDEBAR_FIRST:
4129  case OP_SIDEBAR_LAST:
4130  case OP_SIDEBAR_NEXT:
4131  case OP_SIDEBAR_NEXT_NEW:
4132  case OP_SIDEBAR_PAGE_DOWN:
4133  case OP_SIDEBAR_PAGE_UP:
4134  case OP_SIDEBAR_PREV:
4135  case OP_SIDEBAR_PREV_NEW:
4136  {
4137  struct MuttWindow *win_sidebar = window_find_child(dlg, WT_SIDEBAR);
4138  if (!win_sidebar)
4139  break;
4140  sb_change_mailbox(win_sidebar, op);
4141  break;
4142  }
4143 
4144  //=======================================================================
4145 
4146  case OP_SIDEBAR_TOGGLE_VISIBLE:
4147  bool_str_toggle(NeoMutt->sub, "sidebar_visible", NULL);
4148  mutt_window_reflow(dlg);
4149  break;
4150 
4151  //=======================================================================
4152 #endif
4153 
4154  default:
4155  op = -1;
4156  break;
4157  }
4158  }
4159  //-------------------------------------------------------------------------
4160  // END OF ACT 3: Read user input loop - while (op != -1)
4161  //-------------------------------------------------------------------------
4162 
4163  if (check_read_delay(&delay_read_timestamp))
4164  {
4165  mutt_set_flag(shared->mailbox, shared->email, MUTT_READ, true);
4166  }
4167  mutt_file_fclose(&priv->fp);
4168  if (pview->mode == PAGER_MODE_EMAIL)
4169  {
4170  shared->ctx->msg_in_pager = -1;
4171  priv->win_pbar->actions |= WA_RECALC;
4172  switch (rc)
4173  {
4174  case -1:
4175  case OP_DISPLAY_HEADERS:
4177  break;
4178  default:
4179  TopLine = priv->topline;
4180  OldEmail = shared->email;
4181  break;
4182  }
4183  }
4184 
4185  cleanup_quote(&priv->quote_list);
4186 
4187  for (size_t i = 0; i < priv->max_line; i++)
4188  {
4189  FREE(&(priv->line_info[i].syntax));
4190  if (priv->search_compiled && priv->line_info[i].search)
4191  FREE(&(priv->line_info[i].search));
4192  }
4193  if (priv->search_compiled)
4194  {
4195  regfree(&priv->search_re);
4196  priv->search_compiled = false;
4197  }
4198  FREE(&priv->line_info);
4199 
4200  if (priv->pview->win_index)
4201  {
4206  window_set_visible(priv->pview->win_index->parent, true);
4207  }
4208  window_set_visible(priv->pview->win_pager->parent, false);
4209  mutt_window_reflow(dlg);
4210 
4211  return (rc != -1) ? rc : 0;
4212 }
uint16_t PagerFlags
Flags for mutt_pager(), e.g. MUTT_SHOWFLAT.
Definition: lib.h:51
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:904
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
Convenience wrapper for the gui headers.
static bool assert_pager_mode(bool test)
Check that pager is in correct mode.
Definition: dlg_pager.c:241
The "current" mailbox.
Definition: context.h:37
struct MuttWindow * window_find_child(struct MuttWindow *win, enum WindowType type)
Recursively find a child Window of a given type.
Definition: mutt_window.c:550
#define ANSI_BOLD
Bold text.
Definition: dlg_pager.c:99
struct PagerData * pdata
Data that pager displays. NOTNULL.
Definition: lib.h:156
Bold text.
Definition: color.h:45
#define MUTT_PAGER_RETWINCH
Need reformatting on SIGWINCH.
Definition: lib.h:65
Header default colour.
Definition: color.h:52
Underlined text.
Definition: color.h:78
Manage keymappings.
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
Decrypt message.
Definition: commands.h:42
bool mutt_mailbox_list(void)
List the mailboxes with new mail.
Definition: mutt_mailbox.c:222
#define NONULL(x)
Definition: string2.h:37
#define ANSI_BLINK
Blinking text.
Definition: dlg_pager.c:98
void mutt_buffer_strip_formatting(struct Buffer *dest, const char *src, bool strip_markers)
Removes ANSI and backspace formatting.
Definition: dlg_pager.c:1395
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
int msg_count
Total number of messages.
Definition: mailbox.h:91
static int up_n_lines(int nlines, struct Line *info, int cur, bool hiding)
Reposition the pager&#39;s view up by n lines.
Definition: dlg_pager.c:1980
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:66
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
uint64_t mutt_date_epoch_ms(void)
Return the number of milliseconds since the Unix epoch.
Definition: date.c:436
Representation of the email&#39;s header.
#define ANSI_UNDERLINE
Underlined text.
Definition: dlg_pager.c:100
#define WithCrypto
Definition: lib.h:113
int mutt_color_alloc(uint32_t fg, uint32_t bg)
Allocate a colour pair.
Definition: color.c:478
#define SEND_TO_SENDER
Compose new email to sender.
Definition: send.h:51
int msg_in_pager
Message currently shown in the pager.
Definition: context.h:43
void mutt_pipe_message(struct Mailbox *m, struct EmailList *el)
Pipe a message.
Definition: commands.c:760
struct stat sb
Definition: private_data.h:67
A default and invalid mode, should never be used.
Definition: lib.h:129
The envelope/body of an email.
Definition: email.h:37
static void class_color_new(struct QClass *qc, int *q_level)
Create a new quoting colour.
Definition: dlg_pager.c:540
#define MUTT_CLEAR
Clear input if printable character is pressed.
Definition: mutt.h:58
struct Email * mutt_get_virt_email(struct Mailbox *m, int vnum)
Get a virtual Email.
Definition: context.c:412
#define MIN(a, b)
Definition: memory.h:31
bool mutt_mb_is_display_corrupting_utf8(wchar_t wc)
Will this character corrupt the display?
Definition: mbyte.c:389
Definition: lib.h:67
#define IsWPrint(wc)
Definition: mbyte.h:39
#define ANSI_OFF
Turn off colours and attributes.
Definition: dlg_pager.c:97
#define MUTT_PAGER_NSKIP
Preserve whitespace with smartwrap.
Definition: lib.h:63
static void cleanup_quote(struct QClass **quote_list)
Free a quote list.
Definition: dlg_pager.c:592
struct Mailbox * ctx_mailbox(struct Context *ctx)
wrapper to get the mailbox in a Context, or NULL
Definition: context.c:444
Informational message.
Definition: color.h:56
void mutt_clear_pager_position(void)
Reset the pager&#39;s viewing position.
Definition: dlg_pager.c:1995
&#39;NNTP&#39; (Usenet) Mailbox type
Definition: mailbox.h:52
Structs that make up an email.
struct Email * email
Currently selected Email.
Definition: shared_data.h:42
String processing routines to generate the mail index.
#define mutt_error(...)
Definition: logging.h:88
The "currently-open" mailbox.
static int braille_col
Definition: dlg_pager.c:170
short chunks
Definition: dlg_pager.c:136
bool mutt_color_is_header(enum ColorId color_id)
Colour is for an Email header.
Definition: color.c:1560
Convenience wrapper for the send headers.
Private state data for the Pager.
static const struct Mapping PagerNormalHelp[]
Help Bar for the Pager of a normal Mailbox.
Definition: dlg_pager.c:202
int help_menu
Menu for key bindings, e.g. MENU_PAGER.
Definition: mutt_window.h:141
int mutt_get_field(const char *field, char *buf, size_t buflen, CompletionFlags complete, bool multiple, char ***files, int *numfiles)
Ask the user for a string.
Definition: curs_lib.c:335
void mutt_display_address(struct Envelope *env)
Display the address of a message.
Definition: commands.c:1026
void mutt_curses_set_color(enum ColorId color)
Set the current colour for text.
Definition: mutt_curses.c:56
size_t length
Definition: dlg_pager.c:110
int pair
Curses colour pair.
Definition: dlg_pager.c:152
static struct QClass * classify_quote(struct QClass **quote_list, const char *qptr, size_t length, bool *force_redraw, int *q_level)
Find a style for a string.
Definition: dlg_pager.c:616
bool mutt_shell_escape(void)
invoke a command in a subshell
Definition: commands.c:943
void mutt_update_index(struct Menu *menu, struct Context *ctx, enum MxStatus check, int oldcount, struct IndexSharedData *shared)
Update the index.
Definition: dlg_index.c:541
PagerFlags hide_quoted
Definition: private_data.h:55
struct Body * body
Current attachment.
Definition: lib.h:145
void mutt_flushinp(void)
Empty all the keyboard buffers.
Definition: curs_lib.c:668
void mutt_save_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, struct Body *top, struct Email *e, struct Menu *menu)
Save a list of attachments.
Definition: recvattach.c:788
bool attach_del
Has an attachment marked for deletion.
Definition: email.h:49
#define SEND_FORWARD
Forward email.
Definition: send.h:43
void mutt_window_clrtoeol(struct MuttWindow *win)
Clear to the end of the line.
Definition: mutt_window.c:259
void mutt_resize_screen(void)
Update NeoMutt&#39;s opinion about the window size (CURSES)
Definition: resize.c:101
NeoMutt Logging.
static int fill_buffer(FILE *fp, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf, unsigned char **fmt, size_t *blen, int *buf_ready)
Fill a buffer from a file.
Definition: dlg_pager.c:1448
WHERE bool OptNeedResort
(pseudo) used to force a re-sort
Definition: options.h:42
void crypt_forget_passphrase(void)
Forget a passphrase and display a message.
Definition: crypt.c:93
No transformation.
Definition: commands.h:41
int pair
Colour pair index.
Definition: color.h:107
void mutt_enter_command(void)
enter a neomutt command
Definition: commands.c:978
String manipulation buffer.
Definition: buffer.h:33
Nondestructive flags change (IMAP)
Definition: mxapi.h:82
New mail received in Mailbox.
Definition: mxapi.h:79
#define MUTT_HIDE
Don&#39;t show quoted text.
Definition: lib.h:57
struct MuttWindow * window_set_focus(struct MuttWindow *win)
Set the Window focus.
Definition: mutt_window.c:680
Paged view into some data.
Definition: lib.h:154
Flagged messages.
Definition: mutt.h:98
#define MUTT_SHOWFLAT
Show characters (used for displaying help)
Definition: lib.h:55
const char * state_attachment_marker(void)
Get a unique (per-run) ANSI string to mark PGP messages in an email.
Definition: state.c:44
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
static const char * timestamp(time_t stamp)
Create a YYYY-MM-DD HH:MM:SS timestamp.
Definition: logging.c:77
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition: regex3.h:54
void window_redraw(struct MuttWindow *win)
Reflow, recalc and repaint a tree of Windows.
Definition: mutt_window.c:632
Pager: signature lines.
Definition: color.h:74
Data shared between Index, Pager and Sidebar.
Definition: shared_data.h:36
Messages to be purged (bypass trash)
Definition: mutt.h:96
static int braille_row
Definition: dlg_pager.c:169
void emaillist_clear(struct EmailList *el)
Drop a private list of Emails.
Definition: email.c:138
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition: file.c:667
static bool assert_mailbox_permissions(struct Mailbox *m, AclFlags acl, char *action)
checks that mailbox is has requested acl flags set
Definition: dlg_pager.c:300
bool search_back
Definition: dlg_pager.c:162
An ANSI escape sequence.
Definition: dlg_pager.c:147
static bool is_ansi(const char *str)
Is this an ANSI escape sequence?
Definition: dlg_pager.c:1282
static const char * Mailbox_is_read_only
Definition: dlg_pager.c:176
Copy message, making a duplicate in another mailbox.
Definition: commands.h:51
int indicator
the indicator line of the PI
Definition: private_data.h:46
bool search_compiled
Definition: dlg_pager.c:161
short type
Definition: dlg_pager.c:134
struct QClass * quote_list
Definition: private_data.h:57
uint16_t SendFlags
Flags for mutt_send_message(), e.g. SEND_REPLY.
Definition: send.h:36
static int format_line(struct MuttWindow *win, struct Line **line_info, int n, unsigned char *buf, PagerFlags flags, struct AnsiAttr *pa, int cnt, int *pspace, int *pvch, int *pcol, int *pspecial, int width)
Display a line of text in the pager.
Definition: dlg_pager.c:1497
static int display_line(FILE *fp, LOFF_T *last_pos, struct Line **line_info, int n, int *last, int *max, PagerFlags flags, struct QClass **quote_list, int *q_level, bool *force_redraw, regex_t *search_re, struct MuttWindow *win_pager)
Print a line on screen.
Definition: dlg_pager.c:1692
Pager: quoted text.
Definition: color.h:62
A special case of PAGER_MODE_ATTACH - attachment is a full-blown email message.
Definition: lib.h:133
#define MUTT_SHOW
Definition: lib.h:60
Flags to control mutt_expando_format()
All user-callable functions.
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
#define SEND_POSTPONED
Recall a postponed email.
Definition: send.h:44
void mutt_help(enum MenuType menu)
Display the help menu.
Definition: help.c:389
void msgwin_clear_text(void)
Clear the text in the Message Window.
Definition: msgwin.c:242
#define mutt_perror(...)
Definition: logging.h:89
struct MuttWindow * win_index
Definition: lib.h:162
Container for Accounts, Notifications.
Definition: neomutt.h:36
#define MUTT_PATTERN
Pattern mode - only used for history classes.
Definition: mutt.h:60
#define MUTT_SEARCH
Resolve search patterns.
Definition: lib.h:58
#define MUTT_ACL_DELETE
Delete a message.
Definition: mailbox.h:66
#define MUTT_FORMAT_NO_FLAGS
No flags are set.
Definition: format_flags.h:30
int vcount
The number of virtual messages.
Definition: mailbox.h:102
Mailbox was reopened.
Definition: mxapi.h:81
QuadOption
Possible values for a quad-option.
Definition: quad.h:35
Convenience wrapper for the config headers.
Menu showing log messages.
Definition: color.h:57
int mutt_window_move(struct MuttWindow *win, int col, int row)
Move the cursor in a Window.
Definition: mutt_window.c:310
#define ANSI_COLOR
Use colours.
Definition: dlg_pager.c:102
void mutt_print_message(struct Mailbox *m, struct EmailList *el)
Print a message.
Definition: commands.c:791
static bool check_read_delay(uint64_t *timestamp)
Is it time to mark the message read?
Definition: dlg_pager.c:2329
#define SEND_NO_FLAGS
No flags are set.
Definition: send.h:39
Private state data for the Pager.
Definition: private_data.h:39
void mutt_beep(bool force)
Irritate the user.
Definition: curs_lib.c:105
Some miscellaneous functions.
#define MAX(a, b)
Definition: memory.h:30
int mutt_pager(struct PagerView *pview)
Display an email, attachment, or help, in a window.
Definition: dlg_pager.c:2360
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:243
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:305
AnsiFlags attr
Attributes, e.g. underline, bold, etc.
Definition: dlg_pager.c:149
int line
Definition: dlg_pager.c:160
enum MxStatus mx_mbox_check(struct Mailbox *m)
Check for new mail - Wrapper for MxOps::mbox_check()
Definition: mx.c:1119
WindowActionFlags actions
Actions to be performed, e.g. WA_RECALC.
Definition: mutt_window.h:132
Pager pager (email viewer)
Definition: type.h:54
bool tagged
Email is tagged.
Definition: email.h:44
bool read
Email is read.
Definition: email.h:51
void mutt_print_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, struct Body *top)
Print a list of Attachments.
Definition: recvattach.c:1240
void mutt_window_clear(struct MuttWindow *win)
Clear a Window.
Definition: mutt_window.c:716
int mutt_is_quote_line(char *line, regmatch_t *pmatch)
Is a line of message text a quote?
Definition: dlg_pager.c:968
Pager: empty lines after message.
Definition: color.h:76
void mutt_curses_set_cursor(enum MuttCursorState state)
Set the cursor state.
Definition: mutt_curses.c:71
#define MUTT_PAGER_BOTTOM
Start at the bottom.
Definition: lib.h:69
#define MUTT_RL_EOL
don&#39;t strip \n / \r\n
Definition: file.h:40
Parse and execute user-defined hooks.
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:50
Message headers (takes a pattern)
Definition: color.h:53
bool readonly
Don&#39;t allow changes to the mailbox.
Definition: mailbox.h:119
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
void mutt_make_string(char *buf, size_t buflen, int cols, const char *s, struct Mailbox *m, int inpgr, struct Email *e, MuttFormatFlags flags, const char *progress)
Create formatted strings using mailbox expandos.
Definition: hdrline.c:1409
void mutt_what_key(void)
Ask the user to press a key.
Definition: keymap.c:1718
struct Envelope * env
Envelope information.
Definition: email.h:90
Display a normal cursor.
Definition: mutt_curses.h:81
Convenience wrapper for the core headers.
void km_error_key(enum MenuType mtype)
Handle an unbound key sequence.
Definition: keymap.c:1144
struct Menu * menu
Definition: private_data.h:41
void alias_create(struct AddressList *al, const struct ConfigSubset *sub)
Create a new Alias from an Address.
Definition: alias.c:372
bool mutt_mailbox_notify(struct Mailbox *m_cur)
Notify the user if there&#39;s new mail.
Definition: mutt_mailbox.c:209
#define MUTT_PAGER_MARKER
Use markers if option is set.
Definition: lib.h:64
short continuation
Definition: dlg_pager.c:135
int mutt_send_message(SendFlags flags, struct Email *e_templ, const char *tempfile, struct Mailbox *m, struct EmailList *el, struct ConfigSubset *sub)
Send an email.
Definition: send.c:2125
void pager_queue_redraw(struct PagerPrivateData *priv, MenuRedrawFlags redraw)
Queue a request for a redraw.
Definition: dlg_pager.c:2006
uint8_t AnsiFlags
Flags, e.g. ANSI_OFF.
Definition: dlg_pager.c:95
static int check_sig(const char *s, struct Line *info, int offset)
Check for an email signature.
Definition: dlg_pager.c:322
Pager: markers, line continuation.
Definition: color.h:55
int bool_str_toggle(struct ConfigSubset *sub, const char *name, struct Buffer *err)
Toggle the value of a bool.
Definition: bool.c:214
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
Ask the user a quad-question.
Definition: question.c:347
#define SEND_LIST_REPLY
Reply to mailing list.
Definition: send.h:42
Email Aliases.
MessageTransformOpt
Message transformation option.
Definition: commands.h:39
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:112
static bool assert_mailbox_writable(struct Mailbox *mailbox)
checks that mailbox is writable
Definition: dlg_pager.c:259
const char * OpStrings[][2]
Definition: opcodes.c:34
static const char * Function_not_permitted_in_attach_message_mode
Definition: dlg_pager.c:177
void * mdata
Driver specific data.
Definition: mailbox.h:136
uint16_t AclFlags
ACL Rights - These show permission to...
Definition: mailbox.h:62
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition: mailbox.h:74
int mutt_color_quote(int q)
Return the color of a quote, cycling through the used quotes.
Definition: color.c:1518
#define mutt_warning(...)
Definition: logging.h:86
Usenet network mailbox type; talk to an NNTP server.
static bool jump_to_bottom(struct PagerPrivateData *priv, struct PagerView *pview)
make sure the bottom line is displayed
Definition: dlg_pager.c:2301
Pager is invoked via 3rd path to show help.
Definition: lib.h:134
#define SEND_KEY
Mail a PGP public key.
Definition: send.h:46
Window has a fixed size.
Definition: mutt_window.h:47
Plain text.
Definition: color.h:58
bool mutt_select_sort(bool reverse)
Ask the user for a sort method.
Definition: commands.c:835
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:916
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
struct ColorLineList * mutt_color_attachments(void)
Return the ColorLineList for the attachments.
Definition: color.c:1472
struct Context * ctx
Current Mailbox view.
Definition: shared_data.h:39
enum PagerMode mode
Pager mode.
Definition: lib.h:157
short cols
Number of columns, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:60
int mutt_save_message(struct Mailbox *m, struct EmailList *el, enum MessageSaveOpt save_opt, enum MessageTransformOpt transform_opt)
Save an email.
Definition: commands.c:1144
void mutt_refresh(void)
Force a refresh of the screen.
Definition: curs_lib.c:115
Side panel containing Accounts or groups of data.
Definition: mutt_window.h:101
struct QClass * quote
Definition: dlg_pager.c:140
Pager is invoked via 1st path. The mime part is selected automatically.
Definition: lib.h:131
Window size depends on its children.
Definition: mutt_window.h:49
Prototypes for many functions.
void mutt_attach_bounce(struct Mailbox *m, FILE *fp, struct AttachCtx *actx, struct Body *cur)
Bounce function, from the attachment menu.
Definition: recvcmd.c:161
void mutt_window_get_coords(struct MuttWindow *win, int *col, int *row)
Get the cursor position in the Window.
Definition: mutt_window.c:290
short search_cnt
Definition: dlg_pager.c:137
int bg
Background colour.
Definition: dlg_pager.c:151
struct TextSyntax * search
Definition: dlg_pager.c:139
struct WindowState state
Current state of the Window.
Definition: mutt_window.h:127
#define APPLICATION_PGP
Use PGP to encrypt/sign.
Definition: lib.h:87
WHERE bool OptAttachMsg
(pseudo) used by attach-message
Definition: options.h:31
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
struct AddressList * mutt_get_address(struct Envelope *env, const char **prefix)
Get an Address from an Envelope.
Definition: alias.c:332
bool stop_matching
Used by the pager for body patterns, to prevent the color from being retried once it fails...
Definition: color.h:109
void dlg_select_attachment(struct ConfigSubset *sub, struct Mailbox *m, struct Email *e, FILE *fp)
Show the attachments in a Menu.
Definition: recvattach.c:1713
Status bar (takes a pattern)
Definition: color.h:75
size_t mutt_buffer_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:356
void mutt_attach_reply(FILE *fp, struct Mailbox *m, struct Email *e, struct AttachCtx *actx, struct Body *e_cur, SendFlags flags)
Attach a reply.
Definition: recvcmd.c:938
Messages to be deleted.
Definition: mutt.h:94
A mailbox.
Definition: mailbox.h:81
int mutt_mailbox_check(struct Mailbox *m_cur, int force)
Check all all Mailboxes for new mail.
Definition: mutt_mailbox.c:137
#define MUTT_PAGER_ATTACHMENT
Attachments may exist.
Definition: lib.h:66
#define MUTT_WIN_SIZE_UNLIMITED
Use as much space as possible.
Definition: mutt_window.h:52
short rows
Number of rows, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:61
int mutt_window_printf(struct MuttWindow *win, const char *fmt,...)
Write a formatted string to a Window.
Definition: mutt_window.c:453
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
int top
Entry that is the top of the current page.
Definition: lib.h:89
struct Line * line_info
Definition: private_data.h:65
Manage where the email is piped to external commands.
char * dptr
Current read/write position.
Definition: buffer.h:36
struct QClass * down
Definition: dlg_pager.c:115
const struct Mapping * help_data
Data for the Help Bar.
Definition: mutt_window.h:142
Tagged messages.
Definition: mutt.h:99
void mutt_draw_statusline(struct MuttWindow *win, int cols, const char *buf, size_t buflen)
Draw a highlighted status bar.
Definition: dlg_index.c:875
char * data
Pointer to data.
Definition: buffer.h:35
static const struct Mapping PagerNewsHelp[]
Help Bar for the Pager of an NNTP Mailbox.
Definition: dlg_pager.c:218
wchar_t ReplacementChar
When a Unicode character can&#39;t be displayed, use this instead.
Definition: charset.c:57
Nntp-specific Mailbox data.
A line of text in the pager.
Definition: dlg_pager.c:131
New messages.
Definition: mutt.h:89
Messages that have been read.
Definition: mutt.h:92
char * prefix
Definition: dlg_pager.c:113
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
#define MUTT_SHOWCOLOR
Show characters in color otherwise don&#39;t show characters.
Definition: lib.h:56
API for encryption/signing of emails.
int mutt_window_wrap_cols(int width, short wrap)
Calculate the wrap column for a given screen width.
Definition: mutt_window.c:386
static int TopLine
Definition: dlg_pager.c:166
bool verbose
Display status messages?
Definition: mailbox.h:118
Ask the user a question.
struct MuttWindow * win_pbar
Definition: private_data.h:42
static bool assert_attach_msg_mode(bool attach_msg)
Check that attach message mode is on.
Definition: dlg_pager.c:279
struct QClass * next
Definition: dlg_pager.c:114
#define SEND_NEWS
Reply to a news article.
Definition: send.h:53
Handling of email attachments.
int mutt_addwch(struct MuttWindow *win, wchar_t wc)
addwch would be provided by an up-to-date curses library
Definition: curs_lib.c:682
WHERE bool OptSearchInvalid
(pseudo) used to invalidate the search pattern
Definition: options.h:51
enum MuttWindowSize size
Type of Window, e.g. MUTT_WIN_SIZE_FIXED.
Definition: mutt_window.h:131
AclFlags rights
ACL bits, see AclFlags.
Definition: mailbox.h:121
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
PagerFlags flags
Additional settings to tweak pager&#39;s function.
Definition: lib.h:158
int pagelen
Number of entries per screen.
Definition: lib.h:74
struct ColorLineList * mutt_color_body(void)
Return the ColorLineList for the body.
Definition: color.c:1463
void menu_status_line(char *buf, size_t buflen, struct IndexSharedData *shared, struct Menu *menu, int cols, const char *fmt)
Create the status line.
Definition: status.c:445
struct PagerView * pview
Definition: private_data.h:44
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
bool TsSupported
Terminal Setting is supported.
Definition: terminal.c:43
#define mutt_debug(LEVEL,...)
Definition: logging.h:85
#define MUTT_PAGER_LOGS
Logview mode.
Definition: lib.h:68
int mutt_color(enum ColorId id)
Return the color of an object.
Definition: color.c:1427
static const char * Not_available_in_this_menu
Definition: dlg_pager.c:174
#define MUTT_TYPES
Compute line&#39;s type.
Definition: lib.h:59
#define ANSI_REVERSE
Reverse video.
Definition: dlg_pager.c:101
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/lib.h pgplib...
Definition: email.h:39
int emaillist_add_email(struct EmailList *el, struct Email *e)
Add an Email to a list.
Definition: email.c:159
#define SEND_REPLY
Reply to sender.
Definition: send.h:40
NNTP-specific Mailbox data -.
Definition: mdata.h:32
enum QuadOption cs_subset_quad(const struct ConfigSubset *sub, const char *name)
Get a quad-value config item by name.
Definition: helpers.c:218
static void resolve_color(struct MuttWindow *win, struct Line *line_info, int n, int cnt, PagerFlags flags, int special, struct AnsiAttr *a)
Set the colour for a line of text.
Definition: dlg_pager.c:384
bool CharsetIsUtf8
Is the user&#39;s current character set utf-8?
Definition: charset.c:62
static int check_attachment_marker(const char *p)
Check that the unique marker is present.
Definition: dlg_pager.c:944
#define MUTT_MAILBOX_CHECK_FORCE
Definition: mutt_mailbox.h:32
int max
Number of entries in the menu.
Definition: lib.h:71
void mutt_attach_resend(FILE *fp, struct Mailbox *m, struct AttachCtx *actx, struct Body *cur)
resend-message, from the attachment menu
Definition: recvcmd.c:294
GUI display a user-configurable status line.
struct AttachCtx * actx
Attachment information.
Definition: lib.h:147
void mutt_attach_forward(FILE *fp, struct Email *e, struct AttachCtx *actx, struct Body *cur, SendFlags flags)
Forward an Attachment.
Definition: recvcmd.c:784
bool mutt_regex_capture(const struct Regex *regex, const char *str, size_t nmatch, regmatch_t matches[])
match a regex against a string, with provided options
Definition: regex.c:596
#define IS_SPACE(ch)
Definition: string2.h:38
regex_t regex
Compiled regex.
Definition: color.h:101
void mutt_pipe_attachment_list(struct AttachCtx *actx, FILE *fp, bool tag, struct Body *top, bool filter)
Pipe a list of attachments to a command.
Definition: recvattach.c:1081
int fg
Foreground colour.
Definition: dlg_pager.c:150
Highlighting for a line of text.
Definition: dlg_pager.c:121
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:664
void ci_bounce_message(struct Mailbox *m, struct EmailList *el)
Bounce an email.
Definition: commands.c:434
#define MUTT_DISPLAYFLAGS
Definition: lib.h:72
#define SEND_GROUP_REPLY
Reply to all.
Definition: send.h:41
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
int mutt_label_message(struct Mailbox *m, struct EmailList *el)
Let the user label a message.
Definition: mutt_header.c:124
Move message to another mailbox, removing the original.
Definition: commands.h:52
Error message.
Definition: color.h:51
Routines for managing attachments.
Log at debug level 1.
Definition: logging.h:40
Send/reply with an attachment.
int mutt_color_quotes_used(void)
Return the number of used quotes.
Definition: color.c:1530
bool flagged
Marked important?
Definition: email.h:43
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:749
static int grok_ansi(const unsigned char *buf, int pos, struct AnsiAttr *a)
Parse an ANSI escape sequence.
Definition: dlg_pager.c:1296
FILE * fp
Source stream.
Definition: lib.h:146
const char * state_protected_header_marker(void)
Get a unique (per-run) ANSI string to mark protected headers in an email.
Definition: state.c:59
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:593
void window_set_visible(struct MuttWindow *win, bool visible)
Set a Window visible or hidden.
Definition: mutt_window.c:163
bool deleted
Email is deleted.
Definition: email.h:45
void mutt_window_reflow(struct MuttWindow *win)
Resize a Window and its children.
Definition: mutt_window.c:361
Cached regular expression.
Definition: regex3.h:89
int color
Definition: dlg_pager.c:112
struct MuttWindow * parent
Parent Window.
Definition: mutt_window.h:135
Another invalid mode, should never be used.
Definition: lib.h:137
MIME attachments text (entire line)
Definition: color.h:42
char * followup_to
List of &#39;followup-to&#39; fields.
Definition: envelope.h:77
void menu_redraw_index(struct Menu *menu)
Force the redraw of the index.
Definition: draw.c:371
void mutt_timeout_hook(void)
Execute any timeout hooks.
Definition: hook.c:827
Pager is invoked via 2nd path. A user-selected attachment (mime part or a nested email) will be shown...
Definition: lib.h:132
MessageSaveOpt
Message save option.
Definition: commands.h:49
void mutt_check_stats(struct Mailbox *m)
Forcibly update mailbox stats.
Definition: commands.c:1590
#define mutt_message(...)
Definition: logging.h:87
#define FREE(x)
Definition: memory.h:40
static void pager_custom_redraw(struct PagerPrivateData *priv)
Redraw the pager window.
Definition: dlg_pager.c:2016
Mapping between user-readable string and a constant.
Definition: mapping.h:31
static int comp_syntax_t(const void *m1, const void *m2)
Search for a Syntax using bsearch.
Definition: dlg_pager.c:362
void crypt_extract_keys_from_messages(struct Mailbox *m, struct EmailList *el)
Extract keys from a message.
Definition: crypt.c:858
#define MUTT_ACL_SEEN
Change the &#39;seen&#39; status of a message.
Definition: mailbox.h:73
Hide the cursor.
Definition: mutt_curses.h:80
int index
Definition: dlg_pager.c:111
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
Decode message.
Definition: commands.h:43
struct MuttWindow * win_pager
Definition: lib.h:164
void mutt_attach_mail_sender(FILE *fp, struct Email *e, struct AttachCtx *actx, struct Body *cur)
Compose an email to the sender in the email attachment.
Definition: recvcmd.c:1115
short req_rows
Number of rows required.
Definition: mutt_window.h:125
void mutt_ts_icon(char *str)
Set the icon in the terminal title bar.
Definition: terminal.c:118
Pager: search matches.
Definition: color.h:63
struct MuttWindow * win_pbar
Definition: lib.h:163
struct TextSyntax * syntax
Definition: dlg_pager.c:138
PagerMode
Determine the behaviour of the Pager.
Definition: lib.h:127
#define MUTT_PAGER_NOWRAP
Format for term width, ignore $wrap.
Definition: lib.h:67
static int check_protected_header_marker(const char *p)
Check that the unique marker is present.
Definition: dlg_pager.c:954
#define PGP_TRADITIONAL_CHECKED
Email has a traditional (inline) signature.
Definition: lib.h:89
bool changed
Mailbox has been modified.
Definition: mailbox.h:114
int mutt_change_flag(struct Mailbox *m, struct EmailList *el, bool bf)
Change the flag on a Message.
Definition: flags.c:434
Hundreds of global variables to back the user variables.
Handling of global boolean variables.
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
int mutt_resend_message(FILE *fp, struct Mailbox *m, struct Email *e_cur, struct ConfigSubset *sub)
Resend an email.
Definition: send.c:1632
struct QClass * prev
Definition: dlg_pager.c:114
void mutt_ts_status(char *str)
Set the text of the terminal title bar.
Definition: terminal.c:104
PagerFlags search_flag
Definition: private_data.h:62
const char * fname
Name of the file to read.
Definition: lib.h:148
struct Buffer * mutt_buffer_init(struct Buffer *buf)
Initialise a new Buffer.
Definition: buffer.c:46
#define SEND_GROUP_CHAT_REPLY
Reply to all recipients preserving To/Cc.
Definition: send.h:52
struct Mailbox * mailbox
Current Mailbox.
Definition: shared_data.h:41
void mutt_color_free(uint32_t fg, uint32_t bg)
Free a colour.
Definition: color.c:284
static void append_line(struct Line *line_info, int n, int cnt)
Add a new Line to the array.
Definition: dlg_pager.c:517
int km_dokey(enum MenuType mtype)
Determine what a keypress should do.
Definition: keymap.c:635
int el_add_tagged(struct EmailList *el, struct Context *ctx, struct Email *e, bool use_tagged)
Get a list of the tagged Emails.
Definition: context.c:364
static void shift_class_colors(struct QClass *quote_list, struct QClass *new_class, int index, int *q_level)
Insert a new quote colour class into a list.
Definition: dlg_pager.c:553
Log at debug level 5.
Definition: logging.h:44
struct Buffer pathbuf
Definition: mailbox.h:83
Convenience wrapper for the library headers.
void mutt_curses_set_attr(int attr)
Set the attributes for text.
Definition: mutt_curses.c:39
GUI manage the main index (list of emails)
const char * banner
Title to display in status bar.
Definition: lib.h:159
Window wants as much space as possible.
Definition: mutt_window.h:48
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:252
struct ColorLineList * mutt_color_headers(void)
Return the ColorLineList for headers.
Definition: color.c:1454
LOFF_T offset
Definition: dlg_pager.c:133
static const struct Mapping PagerHelp[]
Help Bar for the Pager&#39;s Help Page.
Definition: dlg_pager.c:181
Pager is invoked via 3rd path. Non-email content is likely to be shown.
Definition: lib.h:135
void * wdata
Private data.
Definition: mutt_window.h:145
static const struct Mapping * pager_resolve_help_mapping(enum PagerMode mode, enum MailboxType type)
determine help mapping based on pager mode and mailbox type
Definition: dlg_pager.c:2263
#define N_(a)
Definition: message.h:32
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition: mbyte.c:357
WHERE SIG_ATOMIC_VOLATILE_T SigWinch
true after SIGWINCH is received
Definition: mutt_globals.h:68
struct Email * email
header information for message/rfc822
Definition: body.h:55
static void resolve_types(struct MuttWindow *win, char *buf, char *raw, struct Line *line_info, int n, int last, struct QClass **quote_list, int *q_level, bool *force_redraw, bool q_classify)
Determine the style for a line of text.
Definition: dlg_pager.c:1014
struct MuttWindow * dialog_find(struct MuttWindow *win)
Find the parent Dialog of a Window.
Definition: dialog.c:85
static int check_marker(const char *q, const char *p)
Check that the unique marker is present.
Definition: dlg_pager.c:928
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close() ...
Definition: mxapi.h:75
Mailbox helper functions.
int mutt_system(const char *cmd)
Run an external command.
Definition: system.c:51
Log at debug level 3.
Definition: logging.h:42
unsigned int is_cont_hdr
this line is a continuation of the previous header line
Definition: dlg_pager.c:141
struct QClass * up
Definition: dlg_pager.c:115
int mutt_thread_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool subthread)
Set a flag on an entire thread.
Definition: flags.c:375
Style of quoted text.
Definition: dlg_pager.c:108
Warning messages.
Definition: color.h:79
int mutt_window_addch(struct MuttWindow *win, int ch)
Write one character to a Window.
Definition: mutt_window.c:402
A regular expression and a color to highlight a line.
Definition: color.h:99
static const struct Mapping PagerHelpHelp[]
Help Bar for the Help Page itself.
Definition: dlg_pager.c:192
MenuRedrawFlags redraw
When to redraw the screen.
Definition: private_data.h:69
char searchbuf[256]
Definition: private_data.h:64
int msgno
Number displayed to the user.
Definition: email.h:87
const char * mutt_make_version(void)
Generate the NeoMutt version string.
Definition: muttlib.c:1476
User answered &#39;Yes&#39;, or assume &#39;Yes&#39;.
Definition: quad.h:39
void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:265
An error occurred.
Definition: mxapi.h:77
#define WA_RECALC
Recalculate the contents of the Window.
Definition: mutt_window.h:110