NeoMutt  2021-02-05-329-g9e03b7
Teaching an old dog new tricks
DOXYGEN
browser.c
Go to the documentation of this file.
1 
30 #include "config.h"
31 #include <dirent.h>
32 #include <errno.h>
33 #include <grp.h>
34 #include <limits.h>
35 #include <locale.h>
36 #include <pwd.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <sys/stat.h>
41 #include "mutt/lib.h"
42 #include "config/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "conn/lib.h"
46 #include "gui/lib.h"
47 #include "mutt.h"
48 #include "browser.h"
49 #include "menu/lib.h"
50 #include "send/lib.h"
51 #include "format_flags.h"
52 #include "keymap.h"
53 #include "mutt_attach.h"
54 #include "mutt_globals.h"
55 #include "mutt_mailbox.h"
56 #include "muttlib.h"
57 #include "mx.h"
58 #include "opcodes.h"
59 #include "options.h"
60 #ifdef USE_IMAP
61 #include "imap/lib.h"
62 #endif
63 #ifdef USE_NNTP
64 #include "nntp/lib.h"
65 #include "nntp/adata.h" // IWYU pragma: keep
66 #include "nntp/mdata.h" // IWYU pragma: keep
67 #endif
68 
70 static const struct Mapping FolderHelp[] = {
71  // clang-format off
72  { N_("Exit"), OP_EXIT },
73  { N_("Chdir"), OP_CHANGE_DIRECTORY },
74  { N_("Goto"), OP_BROWSER_GOTO_FOLDER },
75  { N_("Mask"), OP_ENTER_MASK },
76  { N_("Help"), OP_HELP },
77  { NULL, 0 },
78  // clang-format on
79 };
80 
81 #ifdef USE_NNTP
82 static const struct Mapping FolderNewsHelp[] = {
84  // clang-format off
85  { N_("Exit"), OP_EXIT },
86  { N_("List"), OP_TOGGLE_MAILBOXES },
87  { N_("Subscribe"), OP_BROWSER_SUBSCRIBE },
88  { N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE },
89  { N_("Catchup"), OP_CATCHUP },
90  { N_("Mask"), OP_ENTER_MASK },
91  { N_("Help"), OP_HELP },
92  { NULL, 0 },
93  // clang-format on
94 };
95 #endif
96 
97 static struct Buffer LastDir = { 0 };
98 static struct Buffer LastDirBackup = { 0 };
99 
105 static void init_lastdir(void)
106 {
107  static bool done = false;
108  if (!done)
109  {
110  mutt_buffer_alloc(&LastDir, PATH_MAX);
111  mutt_buffer_alloc(&LastDirBackup, PATH_MAX);
112  done = true;
113  }
114 }
115 
120 {
121  mutt_buffer_dealloc(&LastDir);
122  mutt_buffer_dealloc(&LastDirBackup);
123 }
124 
131 static void destroy_state(struct BrowserState *state)
132 {
133  struct FolderFile *ff = NULL;
134  ARRAY_FOREACH(ff, &state->entry)
135  {
136  FREE(&ff->name);
137  FREE(&ff->desc);
138  }
139  ARRAY_FREE(&state->entry);
140 
141 #ifdef USE_IMAP
142  FREE(&state->folder);
143 #endif
144 }
145 
149 static int browser_compare_subject(const void *a, const void *b)
150 {
151  const struct FolderFile *pa = (const struct FolderFile *) a;
152  const struct FolderFile *pb = (const struct FolderFile *) b;
153 
154  /* inbox should be sorted ahead of its siblings */
155  int r = mutt_inbox_cmp(pa->name, pb->name);
156  if (r == 0)
157  r = mutt_str_coll(pa->name, pb->name);
158  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
159  return (c_sort_browser & SORT_REVERSE) ? -r : r;
160 }
161 
167 static int browser_compare_order(const void *a, const void *b)
168 {
169  const struct FolderFile *pa = (const struct FolderFile *) a;
170  const struct FolderFile *pb = (const struct FolderFile *) b;
171 
172  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
173  return ((c_sort_browser & SORT_REVERSE) ? -1 : 1) * (pa->gen - pb->gen);
174 }
175 
179 static int browser_compare_desc(const void *a, const void *b)
180 {
181  const struct FolderFile *pa = (const struct FolderFile *) a;
182  const struct FolderFile *pb = (const struct FolderFile *) b;
183 
184  int r = mutt_str_coll(pa->desc, pb->desc);
185 
186  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
187  return (c_sort_browser & SORT_REVERSE) ? -r : r;
188 }
189 
193 static int browser_compare_date(const void *a, const void *b)
194 {
195  const struct FolderFile *pa = (const struct FolderFile *) a;
196  const struct FolderFile *pb = (const struct FolderFile *) b;
197 
198  int r = pa->mtime - pb->mtime;
199 
200  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
201  return (c_sort_browser & SORT_REVERSE) ? -r : r;
202 }
203 
207 static int browser_compare_size(const void *a, const void *b)
208 {
209  const struct FolderFile *pa = (const struct FolderFile *) a;
210  const struct FolderFile *pb = (const struct FolderFile *) b;
211 
212  int r = pa->size - pb->size;
213 
214  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
215  return (c_sort_browser & SORT_REVERSE) ? -r : r;
216 }
217 
221 static int browser_compare_count(const void *a, const void *b)
222 {
223  const struct FolderFile *pa = (const struct FolderFile *) a;
224  const struct FolderFile *pb = (const struct FolderFile *) b;
225 
226  int r = 0;
227  if (pa->has_mailbox && pb->has_mailbox)
228  r = pa->msg_count - pb->msg_count;
229  else if (pa->has_mailbox)
230  r = -1;
231  else
232  r = 1;
233 
234  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
235  return (c_sort_browser & SORT_REVERSE) ? -r : r;
236 }
237 
241 static int browser_compare_count_new(const void *a, const void *b)
242 {
243  const struct FolderFile *pa = (const struct FolderFile *) a;
244  const struct FolderFile *pb = (const struct FolderFile *) b;
245 
246  int r = 0;
247  if (pa->has_mailbox && pb->has_mailbox)
248  r = pa->msg_unread - pb->msg_unread;
249  else if (pa->has_mailbox)
250  r = -1;
251  else
252  r = 1;
253 
254  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
255  return (c_sort_browser & SORT_REVERSE) ? -r : r;
256 }
257 
265 static int browser_compare(const void *a, const void *b)
266 {
267  const struct FolderFile *pa = (const struct FolderFile *) a;
268  const struct FolderFile *pb = (const struct FolderFile *) b;
269 
270  if ((mutt_str_coll(pa->desc, "../") == 0) || (mutt_str_coll(pa->desc, "..") == 0))
271  return -1;
272  if ((mutt_str_coll(pb->desc, "../") == 0) || (mutt_str_coll(pb->desc, "..") == 0))
273  return 1;
274 
275  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
276  switch (c_sort_browser & SORT_MASK)
277  {
278  case SORT_COUNT:
279  return browser_compare_count(a, b);
280  case SORT_DATE:
281  return browser_compare_date(a, b);
282  case SORT_DESC:
283  return browser_compare_desc(a, b);
284  case SORT_SIZE:
285  return browser_compare_size(a, b);
286  case SORT_UNREAD:
287  return browser_compare_count_new(a, b);
288  case SORT_SUBJECT:
289  return browser_compare_subject(a, b);
290  default:
291  case SORT_ORDER:
292  return browser_compare_order(a, b);
293  }
294 }
295 
303 static void browser_sort(struct BrowserState *state)
304 {
305  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
306  switch (c_sort_browser & SORT_MASK)
307  {
308 #ifdef USE_NNTP
309  case SORT_SIZE:
310  case SORT_DATE:
311  if (OptNews)
312  return;
313 #endif
314  default:
315  break;
316  }
317 
318  ARRAY_SORT(&state->entry, browser_compare);
319 }
320 
328 static bool link_is_dir(const char *folder, const char *path)
329 {
330  struct stat st;
331  bool retval = false;
332 
333  struct Buffer *fullpath = mutt_buffer_pool_get();
334  mutt_buffer_concat_path(fullpath, folder, path);
335 
336  if (stat(mutt_buffer_string(fullpath), &st) == 0)
337  retval = S_ISDIR(st.st_mode);
338 
339  mutt_buffer_pool_release(&fullpath);
340 
341  return retval;
342 }
343 
364 static const char *folder_format_str(char *buf, size_t buflen, size_t col, int cols,
365  char op, const char *src, const char *prec,
366  const char *if_str, const char *else_str,
367  intptr_t data, MuttFormatFlags flags)
368 {
369  char fn[128], fmt[128];
370  struct Folder *folder = (struct Folder *) data;
371  bool optional = (flags & MUTT_FORMAT_OPTIONAL);
372 
373  switch (op)
374  {
375  case 'C':
376  snprintf(fmt, sizeof(fmt), "%%%sd", prec);
377  snprintf(buf, buflen, fmt, folder->num + 1);
378  break;
379 
380  case 'd':
381  case 'D':
382  if (folder->ff->local)
383  {
384  bool do_locales = true;
385 
386  const char *t_fmt = NULL;
387  if (op == 'D')
388  {
389  const char *const c_date_format =
390  cs_subset_string(NeoMutt->sub, "date_format");
391  t_fmt = NONULL(c_date_format);
392  if (*t_fmt == '!')
393  {
394  t_fmt++;
395  do_locales = false;
396  }
397  }
398  else
399  {
400  static const time_t one_year = 31536000;
401  t_fmt = ((mutt_date_epoch() - folder->ff->mtime) < one_year) ?
402  "%b %d %H:%M" :
403  "%b %d %Y";
404  }
405 
406  if (!do_locales)
407  setlocale(LC_TIME, "C");
408  char date[128];
409  mutt_date_localtime_format(date, sizeof(date), t_fmt, folder->ff->mtime);
410  if (!do_locales)
411  setlocale(LC_TIME, "");
412 
413  mutt_format_s(buf, buflen, prec, date);
414  }
415  else
416  mutt_format_s(buf, buflen, prec, "");
417  break;
418 
419  case 'f':
420  {
421  char *s = NULL;
422 
423  s = NONULL(folder->ff->name);
424 
425  snprintf(fn, sizeof(fn), "%s%s", s,
426  folder->ff->local ?
427  (S_ISLNK(folder->ff->mode) ?
428  "@" :
429  (S_ISDIR(folder->ff->mode) ?
430  "/" :
431  (((folder->ff->mode & S_IXUSR) != 0) ? "*" : ""))) :
432  "");
433 
434  mutt_format_s(buf, buflen, prec, fn);
435  break;
436  }
437  case 'F':
438  {
439  if (folder->ff->local)
440  {
441  char permission[11];
442  snprintf(permission, sizeof(permission), "%c%c%c%c%c%c%c%c%c%c",
443  S_ISDIR(folder->ff->mode) ? 'd' : (S_ISLNK(folder->ff->mode) ? 'l' : '-'),
444  ((folder->ff->mode & S_IRUSR) != 0) ? 'r' : '-',
445  ((folder->ff->mode & S_IWUSR) != 0) ? 'w' : '-',
446  ((folder->ff->mode & S_ISUID) != 0) ? 's' :
447  ((folder->ff->mode & S_IXUSR) != 0) ? 'x' :
448  '-',
449  ((folder->ff->mode & S_IRGRP) != 0) ? 'r' : '-',
450  ((folder->ff->mode & S_IWGRP) != 0) ? 'w' : '-',
451  ((folder->ff->mode & S_ISGID) != 0) ? 's' :
452  ((folder->ff->mode & S_IXGRP) != 0) ? 'x' :
453  '-',
454  ((folder->ff->mode & S_IROTH) != 0) ? 'r' : '-',
455  ((folder->ff->mode & S_IWOTH) != 0) ? 'w' : '-',
456  ((folder->ff->mode & S_ISVTX) != 0) ? 't' :
457  ((folder->ff->mode & S_IXOTH) != 0) ? 'x' :
458  '-');
459  mutt_format_s(buf, buflen, prec, permission);
460  }
461 #ifdef USE_IMAP
462  else if (folder->ff->imap)
463  {
464  char permission[11];
465  /* mark folders with subfolders AND mail */
466  snprintf(permission, sizeof(permission), "IMAP %c",
467  (folder->ff->inferiors && folder->ff->selectable) ? '+' : ' ');
468  mutt_format_s(buf, buflen, prec, permission);
469  }
470 #endif
471  else
472  mutt_format_s(buf, buflen, prec, "");
473  break;
474  }
475 
476  case 'g':
477  if (folder->ff->local)
478  {
479  struct group *gr = getgrgid(folder->ff->gid);
480  if (gr)
481  mutt_format_s(buf, buflen, prec, gr->gr_name);
482  else
483  {
484  snprintf(fmt, sizeof(fmt), "%%%sld", prec);
485  snprintf(buf, buflen, fmt, folder->ff->gid);
486  }
487  }
488  else
489  mutt_format_s(buf, buflen, prec, "");
490  break;
491 
492  case 'i':
493  {
494  char *s = NULL;
495  if (folder->ff->desc)
496  s = folder->ff->desc;
497  else
498  s = folder->ff->name;
499 
500  snprintf(fn, sizeof(fn), "%s%s", s,
501  folder->ff->local ?
502  (S_ISLNK(folder->ff->mode) ?
503  "@" :
504  (S_ISDIR(folder->ff->mode) ?
505  "/" :
506  (((folder->ff->mode & S_IXUSR) != 0) ? "*" : ""))) :
507  "");
508 
509  mutt_format_s(buf, buflen, prec, fn);
510  break;
511  }
512 
513  case 'l':
514  if (folder->ff->local)
515  {
516  snprintf(fmt, sizeof(fmt), "%%%sd", prec);
517  snprintf(buf, buflen, fmt, folder->ff->nlink);
518  }
519  else
520  mutt_format_s(buf, buflen, prec, "");
521  break;
522 
523  case 'm':
524  if (!optional)
525  {
526  if (folder->ff->has_mailbox)
527  {
528  snprintf(fmt, sizeof(fmt), "%%%sd", prec);
529  snprintf(buf, buflen, fmt, folder->ff->msg_count);
530  }
531  else
532  mutt_format_s(buf, buflen, prec, "");
533  }
534  else if (folder->ff->msg_count == 0)
535  optional = false;
536  break;
537 
538  case 'N':
539  snprintf(fmt, sizeof(fmt), "%%%sc", prec);
540  snprintf(buf, buflen, fmt, folder->ff->has_new_mail ? 'N' : ' ');
541  break;
542 
543  case 'n':
544  if (!optional)
545  {
546  if (folder->ff->has_mailbox)
547  {
548  snprintf(fmt, sizeof(fmt), "%%%sd", prec);
549  snprintf(buf, buflen, fmt, folder->ff->msg_unread);
550  }
551  else
552  mutt_format_s(buf, buflen, prec, "");
553  }
554  else if (folder->ff->msg_unread == 0)
555  optional = false;
556  break;
557 
558  case 's':
559  if (folder->ff->local)
560  {
561  mutt_str_pretty_size(fn, sizeof(fn), folder->ff->size);
562  snprintf(fmt, sizeof(fmt), "%%%ss", prec);
563  snprintf(buf, buflen, fmt, fn);
564  }
565  else
566  mutt_format_s(buf, buflen, prec, "");
567  break;
568 
569  case 't':
570  snprintf(fmt, sizeof(fmt), "%%%sc", prec);
571  snprintf(buf, buflen, fmt, folder->ff->tagged ? '*' : ' ');
572  break;
573 
574  case 'u':
575  if (folder->ff->local)
576  {
577  struct passwd *pw = getpwuid(folder->ff->uid);
578  if (pw)
579  mutt_format_s(buf, buflen, prec, pw->pw_name);
580  else
581  {
582  snprintf(fmt, sizeof(fmt), "%%%sld", prec);
583  snprintf(buf, buflen, fmt, folder->ff->uid);
584  }
585  }
586  else
587  mutt_format_s(buf, buflen, prec, "");
588  break;
589 
590  default:
591  snprintf(fmt, sizeof(fmt), "%%%sc", prec);
592  snprintf(buf, buflen, fmt, op);
593  break;
594  }
595 
596  if (optional)
597  {
598  mutt_expando_format(buf, buflen, col, cols, if_str, folder_format_str, data,
600  }
601  else if (flags & MUTT_FORMAT_OPTIONAL)
602  {
603  mutt_expando_format(buf, buflen, col, cols, else_str, folder_format_str,
604  data, MUTT_FORMAT_NO_FLAGS);
605  }
606 
607  /* We return the format string, unchanged */
608  return src;
609 }
610 
621 static void add_folder(struct Menu *menu, struct BrowserState *state,
622  const char *name, const char *desc, const struct stat *s,
623  struct Mailbox *m, void *data)
624 {
625  if ((!menu || menu->is_mailbox_list) && m && (m->flags & MB_HIDDEN))
626  {
627  return;
628  }
629 
630  struct FolderFile ff = { 0 };
631 
632  if (s)
633  {
634  ff.mode = s->st_mode;
635  ff.mtime = s->st_mtime;
636  ff.size = s->st_size;
637  ff.gid = s->st_gid;
638  ff.uid = s->st_uid;
639  ff.nlink = s->st_nlink;
640  ff.local = true;
641  }
642  else
643  ff.local = false;
644 
645  if (m)
646  {
647  ff.has_mailbox = true;
648  ff.gen = m->gen;
649  ff.has_new_mail = m->has_new;
650  ff.msg_count = m->msg_count;
651  ff.msg_unread = m->msg_unread;
652  }
653 
654  ff.name = mutt_str_dup(name);
655  ff.desc = mutt_str_dup(desc ? desc : name);
656 #ifdef USE_IMAP
657  ff.imap = false;
658 #endif
659 #ifdef USE_NNTP
660  if (OptNews)
661  ff.nd = data;
662 #endif
663 
664  ARRAY_ADD(&state->entry, ff);
665 }
666 
672 static void init_state(struct BrowserState *state, struct Menu *menu)
673 {
674  ARRAY_INIT(&state->entry);
675  ARRAY_RESERVE(&state->entry, 256);
676 #ifdef USE_IMAP
677  state->imap_browse = false;
678 #endif
679  if (menu)
680  menu->mdata = &state->entry;
681 }
682 
693 static int examine_directory(struct Mailbox *m, struct Menu *menu,
694  struct BrowserState *state, const char *d, const char *prefix)
695 {
696  int rc = -1;
697  struct Buffer *buf = mutt_buffer_pool_get();
698 #ifdef USE_NNTP
699  if (OptNews)
700  {
702 
703  init_state(state, menu);
704 
705  for (unsigned int i = 0; i < adata->groups_num; i++)
706  {
707  struct NntpMboxData *mdata = adata->groups_list[i];
708  if (!mdata)
709  continue;
710  if (prefix && *prefix && !mutt_str_startswith(mdata->group, prefix))
711  continue;
712  const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
713  if (!mutt_regex_match(c_mask, mdata->group))
714  {
715  continue;
716  }
717  add_folder(menu, state, mdata->group, NULL, NULL, NULL, mdata);
718  }
719  }
720  else
721 #endif /* USE_NNTP */
722  {
723  struct stat s;
724  DIR *dp = NULL;
725  struct dirent *de = NULL;
726 
727  while (stat(d, &s) == -1)
728  {
729  if (errno == ENOENT)
730  {
731  /* The last used directory is deleted, try to use the parent dir. */
732  char *c = strrchr(d, '/');
733 
734  if (c && (c > d))
735  {
736  *c = '\0';
737  continue;
738  }
739  }
740  mutt_perror(d);
741  goto ed_out;
742  }
743 
744  if (!S_ISDIR(s.st_mode))
745  {
746  mutt_error(_("%s is not a directory"), d);
747  goto ed_out;
748  }
749 
750  if (m)
751  mutt_mailbox_check(m, 0);
752 
753  dp = opendir(d);
754  if (!dp)
755  {
756  mutt_perror(d);
757  goto ed_out;
758  }
759 
760  init_state(state, menu);
761 
762  struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
764  while ((de = readdir(dp)))
765  {
766  if (mutt_str_equal(de->d_name, "."))
767  continue; /* we don't need . */
768 
769  if (prefix && *prefix && !mutt_str_startswith(de->d_name, prefix))
770  {
771  continue;
772  }
773  const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
774  if (!mutt_regex_match(c_mask, de->d_name))
775  {
776  continue;
777  }
778 
779  mutt_buffer_concat_path(buf, d, de->d_name);
780  if (lstat(mutt_buffer_string(buf), &s) == -1)
781  continue;
782 
783  /* No size for directories or symlinks */
784  if (S_ISDIR(s.st_mode) || S_ISLNK(s.st_mode))
785  s.st_size = 0;
786  else if (!S_ISREG(s.st_mode))
787  continue;
788 
789  struct MailboxNode *np = NULL;
790  STAILQ_FOREACH(np, &ml, entries)
791  {
793  break;
794  }
795 
796  if (np && m && mutt_str_equal(np->mailbox->realpath, m->realpath))
797  {
798  np->mailbox->msg_count = m->msg_count;
799  np->mailbox->msg_unread = m->msg_unread;
800  }
801  add_folder(menu, state, de->d_name, NULL, &s, np ? np->mailbox : NULL, NULL);
802  }
804  closedir(dp);
805  }
806  browser_sort(state);
807  rc = 0;
808 ed_out:
810  return rc;
811 }
812 
821 static int examine_mailboxes(struct Mailbox *m, struct Menu *menu, struct BrowserState *state)
822 {
823  struct stat s;
824  struct Buffer *md = NULL;
825  struct Buffer *mailbox = NULL;
826 
827 #ifdef USE_NNTP
828  if (OptNews)
829  {
831 
832  init_state(state, menu);
833 
834  for (unsigned int i = 0; i < adata->groups_num; i++)
835  {
836  const bool c_show_only_unread =
837  cs_subset_bool(NeoMutt->sub, "show_only_unread");
838  struct NntpMboxData *mdata = adata->groups_list[i];
839  if (mdata && (mdata->has_new_mail ||
840  (mdata->subscribed && (mdata->unread || !c_show_only_unread))))
841  {
842  add_folder(menu, state, mdata->group, NULL, NULL, NULL, mdata);
843  }
844  }
845  }
846  else
847 #endif
848  {
849  init_state(state, menu);
850 
852  return -1;
853  mailbox = mutt_buffer_pool_get();
854  md = mutt_buffer_pool_get();
855 
856  mutt_mailbox_check(m, 0);
857 
858  struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
860  struct MailboxNode *np = NULL;
861  STAILQ_FOREACH(np, &ml, entries)
862  {
863  if (!np->mailbox)
864  continue;
865 
866  if (m && mutt_str_equal(np->mailbox->realpath, m->realpath))
867  {
868  np->mailbox->msg_count = m->msg_count;
869  np->mailbox->msg_unread = m->msg_unread;
870  }
871 
872  mutt_buffer_strcpy(mailbox, mailbox_path(np->mailbox));
873  const bool c_browser_abbreviate_mailboxes =
874  cs_subset_bool(NeoMutt->sub, "browser_abbreviate_mailboxes");
875  if (c_browser_abbreviate_mailboxes)
877 
878  switch (np->mailbox->type)
879  {
880  case MUTT_IMAP:
881  case MUTT_POP:
882  add_folder(menu, state, mutt_buffer_string(mailbox),
883  np->mailbox->name, NULL, np->mailbox, NULL);
884  continue;
885  case MUTT_NOTMUCH:
886  case MUTT_NNTP:
887  add_folder(menu, state, mailbox_path(np->mailbox), np->mailbox->name,
888  NULL, np->mailbox, NULL);
889  continue;
890  default: /* Continue */
891  break;
892  }
893 
894  if (lstat(mailbox_path(np->mailbox), &s) == -1)
895  continue;
896 
897  if ((!S_ISREG(s.st_mode)) && (!S_ISDIR(s.st_mode)) && (!S_ISLNK(s.st_mode)))
898  continue;
899 
900  if (np->mailbox->type == MUTT_MAILDIR)
901  {
902  struct stat st2;
903 
904  mutt_buffer_printf(md, "%s/new", mailbox_path(np->mailbox));
905  if (stat(mutt_buffer_string(md), &s) < 0)
906  s.st_mtime = 0;
907  mutt_buffer_printf(md, "%s/cur", mailbox_path(np->mailbox));
908  if (stat(mutt_buffer_string(md), &st2) < 0)
909  st2.st_mtime = 0;
910  if (st2.st_mtime > s.st_mtime)
911  s.st_mtime = st2.st_mtime;
912  }
913 
914  add_folder(menu, state, mutt_buffer_string(mailbox), np->mailbox->name,
915  &s, np->mailbox, NULL);
916  }
918  }
919  browser_sort(state);
920 
921  mutt_buffer_pool_release(&mailbox);
923  return 0;
924 }
925 
929 static int select_file_search(struct Menu *menu, regex_t *rx, int line)
930 {
931  struct BrowserStateEntry *entry = menu->mdata;
932 #ifdef USE_NNTP
933  if (OptNews)
934  return regexec(rx, ARRAY_GET(entry, line)->desc, 0, NULL, 0);
935 #endif
936  struct FolderFile *ff = ARRAY_GET(entry, line);
937  char *search_on = ff->desc ? ff->desc : ff->name;
938 
939  return regexec(rx, search_on, 0, NULL, 0);
940 }
941 
945 static void folder_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
946 {
947  struct BrowserStateEntry *entry = menu->mdata;
948  struct Folder folder = {
949  .ff = ARRAY_GET(entry, line),
950  .num = line,
951  };
952 
953 #ifdef USE_NNTP
954  if (OptNews)
955  {
956  const char *const c_group_index_format =
957  cs_subset_string(NeoMutt->sub, "group_index_format");
958  mutt_expando_format(buf, buflen, 0, menu->win_index->state.cols,
959  NONULL(c_group_index_format), group_index_format_str,
960  (intptr_t) &folder, MUTT_FORMAT_ARROWCURSOR);
961  }
962  else
963 #endif
964  {
965  const char *const c_folder_format =
966  cs_subset_string(NeoMutt->sub, "folder_format");
967  mutt_expando_format(buf, buflen, 0, menu->win_index->state.cols,
968  NONULL(c_folder_format), folder_format_str,
969  (intptr_t) &folder, MUTT_FORMAT_ARROWCURSOR);
970  }
971 }
972 
981 static void browser_highlight_default(struct BrowserState *state, struct Menu *menu)
982 {
983  menu->top = 0;
984  /* Reset menu position to 1.
985  * We do not risk overflow as the init_menu function changes
986  * current if it is bigger than state->entrylen. */
987  if (!ARRAY_EMPTY(&state->entry) &&
988  (mutt_str_equal(ARRAY_FIRST(&state->entry)->desc, "..") ||
989  mutt_str_equal(ARRAY_FIRST(&state->entry)->desc, "../")))
990  {
991  /* Skip the first entry, unless there's only one entry. */
992  menu_set_index(menu, (menu->max > 1));
993  }
994  else
995  {
996  menu_set_index(menu, 0);
997  }
998 }
999 
1009 static void init_menu(struct BrowserState *state, struct Menu *menu, char *title,
1010  size_t titlelen, bool mailbox, struct Mailbox *m)
1011 {
1012  menu->max = ARRAY_SIZE(&state->entry);
1013 
1014  int index = menu_get_index(menu);
1015  if (index >= menu->max)
1016  menu_set_index(menu, menu->max - 1);
1017  if (index < 0)
1018  menu_set_index(menu, 0);
1019  if (menu->top > index)
1020  menu->top = 0;
1021 
1022  menu->tagged = 0;
1023 
1024 #ifdef USE_NNTP
1025  if (OptNews)
1026  {
1027  if (mailbox)
1028  snprintf(title, titlelen, _("Subscribed newsgroups"));
1029  else
1030  {
1031  snprintf(title, titlelen, _("Newsgroups on server [%s]"),
1033  }
1034  }
1035  else
1036 #endif
1037  {
1038  if (mailbox)
1039  {
1040  menu->is_mailbox_list = true;
1041  snprintf(title, titlelen, _("Mailboxes [%d]"), mutt_mailbox_check(m, 0));
1042  }
1043  else
1044  {
1045  struct Buffer *path = mutt_buffer_pool_get();
1046  menu->is_mailbox_list = false;
1047  mutt_buffer_copy(path, &LastDir);
1049  const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
1050 #ifdef USE_IMAP
1051  const bool c_imap_list_subscribed =
1052  cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
1053  if (state->imap_browse && c_imap_list_subscribed)
1054  {
1055  snprintf(title, titlelen, _("Subscribed [%s], File mask: %s"),
1056  mutt_buffer_string(path), NONULL(c_mask ? c_mask->pattern : NULL));
1057  }
1058  else
1059 #endif
1060  {
1061  snprintf(title, titlelen, _("Directory [%s], File mask: %s"),
1062  mutt_buffer_string(path), NONULL(c_mask ? c_mask->pattern : NULL));
1063  }
1064  mutt_buffer_pool_release(&path);
1065  }
1066  }
1067 
1068  /* Browser tracking feature.
1069  * The goal is to highlight the good directory if LastDir is the parent dir
1070  * of LastDirBackup (this occurs mostly when one hit "../"). It should also work
1071  * properly when the user is in examine_mailboxes-mode. */
1072  if (mutt_str_startswith(mutt_buffer_string(&LastDirBackup), mutt_buffer_string(&LastDir)))
1073  {
1074  char target_dir[PATH_MAX] = { 0 };
1075 
1076 #ifdef USE_IMAP
1077  /* Check what kind of dir LastDirBackup is. */
1078  if (imap_path_probe(mutt_buffer_string(&LastDirBackup), NULL) == MUTT_IMAP)
1079  {
1080  mutt_str_copy(target_dir, mutt_buffer_string(&LastDirBackup), sizeof(target_dir));
1081  imap_clean_path(target_dir, sizeof(target_dir));
1082  }
1083  else
1084 #endif
1085  mutt_str_copy(target_dir, strrchr(mutt_buffer_string(&LastDirBackup), '/') + 1,
1086  sizeof(target_dir));
1087 
1088  /* If we get here, it means that LastDir is the parent directory of
1089  * LastDirBackup. I.e., we're returning from a subdirectory, and we want
1090  * to position the cursor on the directory we're returning from. */
1091  bool matched = false;
1092  struct FolderFile *ff = NULL;
1093  ARRAY_FOREACH(ff, &state->entry)
1094  {
1095  if (mutt_str_equal(ff->name, target_dir))
1096  {
1097  menu_set_index(menu, ARRAY_FOREACH_IDX);
1098  matched = true;
1099  break;
1100  }
1101  }
1102  if (!matched)
1103  browser_highlight_default(state, menu);
1104  }
1105  else
1106  browser_highlight_default(state, menu);
1107 
1109 }
1110 
1114 static int file_tag(struct Menu *menu, int sel, int act)
1115 {
1116  struct BrowserStateEntry *entry = menu->mdata;
1117  struct FolderFile *ff = ARRAY_GET(entry, sel);
1118  if (S_ISDIR(ff->mode) ||
1119  (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name)))
1120  {
1121  mutt_error(_("Can't attach a directory"));
1122  return 0;
1123  }
1124 
1125  bool ot = ff->tagged;
1126  ff->tagged = ((act >= 0) ? act : !ff->tagged);
1127 
1128  return ff->tagged - ot;
1129 }
1130 
1141 void mutt_browser_select_dir(const char *f)
1142 {
1143  init_lastdir();
1144 
1145  mutt_buffer_strcpy(&LastDirBackup, f);
1146 
1147  /* Method that will fetch the parent path depending on the type of the path. */
1148  char buf[PATH_MAX];
1149  mutt_get_parent_path(mutt_buffer_string(&LastDirBackup), buf, sizeof(buf));
1150  mutt_buffer_strcpy(&LastDir, buf);
1151 }
1152 
1162  struct Mailbox *m, char ***files, int *numfiles)
1163 {
1164  char title[256];
1165  struct BrowserState state = { { 0 } };
1166  struct Menu *menu = NULL;
1167  bool kill_prefix = false;
1168  bool multiple = (flags & MUTT_SEL_MULTI);
1169  bool folder = (flags & MUTT_SEL_FOLDER);
1170  bool mailbox = (flags & MUTT_SEL_MAILBOX);
1171 
1172  /* Keeps in memory the directory we were in when hitting '='
1173  * to go directly to $folder (`$folder`) */
1174  char goto_swapper[PATH_MAX] = { 0 };
1175 
1176  mailbox = mailbox && folder;
1177 
1178  struct Buffer *OldLastDir = mutt_buffer_pool_get();
1179  struct Buffer *tmp = mutt_buffer_pool_get();
1180  struct Buffer *buf = mutt_buffer_pool_get();
1181  struct Buffer *prefix = mutt_buffer_pool_get();
1182 
1183  init_lastdir();
1184 
1185 #ifdef USE_NNTP
1186  if (OptNews)
1187  {
1188  if (mutt_buffer_is_empty(file))
1189  {
1191 
1192  /* default state for news reader mode is browse subscribed newsgroups */
1193  mailbox = false;
1194  for (size_t i = 0; i < adata->groups_num; i++)
1195  {
1196  struct NntpMboxData *mdata = adata->groups_list[i];
1197  if (mdata && mdata->subscribed)
1198  {
1199  mailbox = true;
1200  break;
1201  }
1202  }
1203  }
1204  else
1205  {
1206  mutt_buffer_copy(prefix, file);
1207  }
1208  }
1209  else
1210 #endif
1211  if (!mutt_buffer_is_empty(file))
1212  {
1214 #ifdef USE_IMAP
1215  if (imap_path_probe(mutt_buffer_string(file), NULL) == MUTT_IMAP)
1216  {
1217  init_state(&state, NULL);
1218  state.imap_browse = true;
1219  if (imap_browse(mutt_buffer_string(file), &state) == 0)
1220  {
1221  mutt_buffer_strcpy(&LastDir, state.folder);
1222  browser_sort(&state);
1223  }
1224  }
1225  else
1226  {
1227 #endif
1228  int i;
1229  for (i = mutt_buffer_len(file) - 1;
1230  (i > 0) && ((mutt_buffer_string(file))[i] != '/'); i--)
1231  {
1232  ; // do nothing
1233  }
1234 
1235  if (i > 0)
1236  {
1237  if ((mutt_buffer_string(file))[0] == '/')
1238  mutt_buffer_strcpy_n(&LastDir, mutt_buffer_string(file), i);
1239  else
1240  {
1241  mutt_path_getcwd(&LastDir);
1242  mutt_buffer_addch(&LastDir, '/');
1243  mutt_buffer_addstr_n(&LastDir, mutt_buffer_string(file), i);
1244  }
1245  }
1246  else
1247  {
1248  if ((mutt_buffer_string(file))[0] == '/')
1249  mutt_buffer_strcpy(&LastDir, "/");
1250  else
1251  mutt_path_getcwd(&LastDir);
1252  }
1253 
1254  if ((i <= 0) && (mutt_buffer_string(file)[0] != '/'))
1255  mutt_buffer_copy(prefix, file);
1256  else
1257  mutt_buffer_strcpy(prefix, mutt_buffer_string(file) + i + 1);
1258  kill_prefix = true;
1259 #ifdef USE_IMAP
1260  }
1261 #endif
1262  }
1263  else
1264  {
1265  if (!folder)
1266  mutt_path_getcwd(&LastDir);
1267  else
1268  {
1269  /* Whether we use the tracking feature of the browser depends
1270  * on which sort method we chose to use. This variable is defined
1271  * only to help readability of the code. */
1272  bool browser_track = false;
1273 
1274  const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
1275  switch (c_sort_browser & SORT_MASK)
1276  {
1277  case SORT_DESC:
1278  case SORT_SUBJECT:
1279  case SORT_ORDER:
1280  browser_track = true;
1281  break;
1282  }
1283 
1284  /* We use mutt_browser_select_dir to initialize the two
1285  * variables (LastDir, LastDirBackup) at the appropriate
1286  * values.
1287  *
1288  * We do it only when LastDir is not set (first pass there)
1289  * or when CurrentFolder and LastDirBackup are not the same.
1290  * This code is executed only when we list files, not when
1291  * we press up/down keys to navigate in a displayed list.
1292  *
1293  * We only do this when CurrentFolder has been set (ie, not
1294  * when listing folders on startup with "neomutt -y").
1295  *
1296  * This tracker is only used when browser_track is true,
1297  * meaning only with sort methods SUBJECT/DESC for now. */
1298  if (CurrentFolder)
1299  {
1300  if (mutt_buffer_is_empty(&LastDir))
1301  {
1302  /* If browsing in "local"-mode, than we chose to define LastDir to
1303  * MailDir */
1304  switch (mx_path_probe(CurrentFolder))
1305  {
1306  case MUTT_IMAP:
1307  case MUTT_MAILDIR:
1308  case MUTT_MBOX:
1309  case MUTT_MH:
1310  case MUTT_MMDF:
1311  {
1312  const char *const c_folder =
1313  cs_subset_string(NeoMutt->sub, "folder");
1314  const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
1315  if (c_folder)
1316  mutt_buffer_strcpy(&LastDir, c_folder);
1317  else if (c_spool_file)
1318  mutt_browser_select_dir(c_spool_file);
1319  break;
1320  }
1321  default:
1323  break;
1324  }
1325  }
1326  else if (!mutt_str_equal(CurrentFolder, mutt_buffer_string(&LastDirBackup)))
1327  {
1329  }
1330  }
1331 
1332  /* When browser tracking feature is disabled, clear LastDirBackup */
1333  if (!browser_track)
1334  mutt_buffer_reset(&LastDirBackup);
1335  }
1336 
1337 #ifdef USE_IMAP
1338  if (!mailbox && (imap_path_probe(mutt_buffer_string(&LastDir), NULL) == MUTT_IMAP))
1339  {
1340  init_state(&state, NULL);
1341  state.imap_browse = true;
1342  imap_browse(mutt_buffer_string(&LastDir), &state);
1343  browser_sort(&state);
1344  }
1345  else
1346 #endif
1347  {
1348  size_t i = mutt_buffer_len(&LastDir);
1349  while ((i > 0) && (mutt_buffer_string(&LastDir)[--i] == '/'))
1350  LastDir.data[i] = '\0';
1351  mutt_buffer_fix_dptr(&LastDir);
1352  if (mutt_buffer_is_empty(&LastDir))
1353  mutt_path_getcwd(&LastDir);
1354  }
1355  }
1356 
1357  mutt_buffer_reset(file);
1358 
1359  const struct Mapping *help_data = NULL;
1360 #ifdef USE_NNTP
1361  if (OptNews)
1362  help_data = FolderNewsHelp;
1363  else
1364 #endif
1365  help_data = FolderHelp;
1366 
1367  struct MuttWindow *dlg =
1369 
1370  menu = dlg->wdata;
1371  menu->make_entry = folder_make_entry;
1372  menu->search = select_file_search;
1373  if (multiple)
1374  menu->tag = file_tag;
1375 
1376  struct MuttWindow *sbar = mutt_window_find(dlg, WT_INDEX_BAR);
1377  sbar_set_title(sbar, title);
1378 
1379  if (mailbox)
1380  {
1381  examine_mailboxes(m, NULL, &state);
1382  }
1383  else
1384 #ifdef USE_IMAP
1385  if (!state.imap_browse)
1386 #endif
1387  {
1388  // examine_directory() calls add_folder() which needs the menu
1389  if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1390  mutt_buffer_string(prefix)) == -1)
1391  {
1392  goto bail;
1393  }
1394  }
1395 
1396  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1397  // only now do we have a valid state to attach
1398  menu->mdata = &state.entry;
1399 
1400  while (true)
1401  {
1402  int op = menu_loop(menu);
1403  if (op >= 0)
1404  mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", OpStrings[op][0], op);
1405  int index = menu_get_index(menu);
1406  struct FolderFile *ff = ARRAY_GET(&state.entry, index);
1407  switch (op)
1408  {
1409  case OP_DESCEND_DIRECTORY:
1410  case OP_GENERIC_SELECT_ENTRY:
1411  {
1412  if (ARRAY_EMPTY(&state.entry))
1413  {
1414  mutt_error(_("No files match the file mask"));
1415  break;
1416  }
1417 
1418  if (S_ISDIR(ff->mode) ||
1419  (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name))
1420 #ifdef USE_IMAP
1421  || ff->inferiors
1422 #endif
1423  )
1424  {
1425  /* make sure this isn't a MH or maildir mailbox */
1426  if (mailbox)
1427  {
1428  mutt_buffer_strcpy(buf, ff->name);
1430  }
1431 #ifdef USE_IMAP
1432  else if (state.imap_browse)
1433  {
1434  mutt_buffer_strcpy(buf, ff->name);
1435  }
1436 #endif
1437  else
1438  {
1439  mutt_buffer_concat_path(buf, mutt_buffer_string(&LastDir), ff->name);
1440  }
1441 
1442  enum MailboxType type = mx_path_probe(mutt_buffer_string(buf));
1443  if ((op == OP_DESCEND_DIRECTORY) || (type == MUTT_MAILBOX_ERROR) ||
1444  (type == MUTT_UNKNOWN)
1445 #ifdef USE_IMAP
1446  || ff->inferiors
1447 #endif
1448  )
1449  {
1450  /* save the old directory */
1451  mutt_buffer_copy(OldLastDir, &LastDir);
1452 
1453  if (mutt_str_equal(ff->name, ".."))
1454  {
1455  size_t lastdirlen = mutt_buffer_len(&LastDir);
1456  if ((lastdirlen > 1) &&
1457  mutt_str_equal("..", mutt_buffer_string(&LastDir) + lastdirlen - 2))
1458  {
1459  mutt_buffer_addstr(&LastDir, "/..");
1460  }
1461  else
1462  {
1463  char *p = NULL;
1464  if (lastdirlen > 1)
1465  p = strrchr(mutt_buffer_string(&LastDir) + 1, '/');
1466 
1467  if (p)
1468  {
1469  *p = '\0';
1470  mutt_buffer_fix_dptr(&LastDir);
1471  }
1472  else
1473  {
1474  if (mutt_buffer_string(&LastDir)[0] == '/')
1475  mutt_buffer_strcpy(&LastDir, "/");
1476  else
1477  mutt_buffer_addstr(&LastDir, "/..");
1478  }
1479  }
1480  }
1481  else if (mailbox)
1482  {
1483  mutt_buffer_strcpy(&LastDir, ff->name);
1484  mutt_buffer_expand_path(&LastDir);
1485  }
1486 #ifdef USE_IMAP
1487  else if (state.imap_browse)
1488  {
1489  mutt_buffer_strcpy(&LastDir, ff->name);
1490  /* tack on delimiter here */
1491 
1492  /* special case "" needs no delimiter */
1493  struct Url *url = url_parse(ff->name);
1494  if (url && url->path && (ff->delim != '\0'))
1495  {
1496  mutt_buffer_addch(&LastDir, ff->delim);
1497  }
1498  url_free(&url);
1499  }
1500 #endif
1501  else
1502  {
1503  mutt_buffer_concat_path(tmp, mutt_buffer_string(&LastDir), ff->name);
1504  mutt_buffer_copy(&LastDir, tmp);
1505  }
1506 
1507  destroy_state(&state);
1508  if (kill_prefix)
1509  {
1510  mutt_buffer_reset(prefix);
1511  kill_prefix = false;
1512  }
1513  mailbox = false;
1514 #ifdef USE_IMAP
1515  if (state.imap_browse)
1516  {
1517  init_state(&state, NULL);
1518  state.imap_browse = true;
1519  imap_browse(mutt_buffer_string(&LastDir), &state);
1520  browser_sort(&state);
1521  menu->mdata = &state.entry;
1522  }
1523  else
1524 #endif
1525  {
1526  if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1527  mutt_buffer_string(prefix)) == -1)
1528  {
1529  /* try to restore the old values */
1530  mutt_buffer_copy(&LastDir, OldLastDir);
1531  if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1532  mutt_buffer_string(prefix)) == -1)
1533  {
1534  mutt_buffer_strcpy(&LastDir, NONULL(HomeDir));
1535  goto bail;
1536  }
1537  }
1538  /* resolve paths navigated from GUI */
1539  if (mutt_path_realpath(LastDir.data) == 0)
1540  break;
1541  }
1542 
1543  browser_highlight_default(&state, menu);
1544  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1545  goto_swapper[0] = '\0';
1546  break;
1547  }
1548  }
1549  else if (op == OP_DESCEND_DIRECTORY)
1550  {
1551  mutt_error(_("%s is not a directory"), ARRAY_GET(&state.entry, index)->name);
1552  break;
1553  }
1554 
1555  if (mailbox || OptNews) /* USE_NNTP */
1556  {
1557  mutt_buffer_strcpy(file, ff->name);
1559  }
1560 #ifdef USE_IMAP
1561  else if (state.imap_browse)
1562  mutt_buffer_strcpy(file, ff->name);
1563 #endif
1564  else
1565  {
1566  mutt_buffer_concat_path(file, mutt_buffer_string(&LastDir), ff->name);
1567  }
1568  }
1569  /* fallthrough */
1570 
1571  case OP_EXIT:
1572 
1573  if (multiple)
1574  {
1575  char **tfiles = NULL;
1576 
1577  if (menu->tagged)
1578  {
1579  *numfiles = menu->tagged;
1580  tfiles = mutt_mem_calloc(*numfiles, sizeof(char *));
1581  size_t j = 0;
1582  ARRAY_FOREACH(ff, &state.entry)
1583  {
1584  if (ff->tagged)
1585  {
1586  mutt_buffer_concat_path(tmp, mutt_buffer_string(&LastDir), ff->name);
1588  tfiles[j++] = mutt_buffer_strdup(tmp);
1589  }
1590  }
1591  *files = tfiles;
1592  }
1593  else if (!mutt_buffer_is_empty(file)) /* no tagged entries. return selected entry */
1594  {
1595  *numfiles = 1;
1596  tfiles = mutt_mem_calloc(*numfiles, sizeof(char *));
1598  tfiles[0] = mutt_buffer_strdup(file);
1599  *files = tfiles;
1600  }
1601  }
1602 
1603  destroy_state(&state);
1604  goto bail;
1605 
1606  case OP_BROWSER_TELL:
1607  if (!ARRAY_EMPTY(&state.entry))
1608  mutt_message("%s", ARRAY_GET(&state.entry, index)->name);
1609  break;
1610 
1611 #ifdef USE_IMAP
1612  case OP_BROWSER_TOGGLE_LSUB:
1613  bool_str_toggle(NeoMutt->sub, "imap_list_subscribed", NULL);
1614 
1615  mutt_unget_event(0, OP_CHECK_NEW);
1616  break;
1617 
1618  case OP_CREATE_MAILBOX:
1619  if (!state.imap_browse)
1620  {
1621  mutt_error(_("Create is only supported for IMAP mailboxes"));
1622  break;
1623  }
1624 
1625  if (imap_mailbox_create(mutt_buffer_string(&LastDir)) == 0)
1626  {
1627  /* TODO: find a way to detect if the new folder would appear in
1628  * this window, and insert it without starting over. */
1629  destroy_state(&state);
1630  init_state(&state, NULL);
1631  state.imap_browse = true;
1632  imap_browse(mutt_buffer_string(&LastDir), &state);
1633  browser_sort(&state);
1634  menu->mdata = &state.entry;
1635  browser_highlight_default(&state, menu);
1636  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1637  }
1638  /* else leave error on screen */
1639  break;
1640 
1641  case OP_RENAME_MAILBOX:
1642  if (!ff->imap)
1643  mutt_error(_("Rename is only supported for IMAP mailboxes"));
1644  else
1645  {
1646  if (imap_mailbox_rename(ff->name) >= 0)
1647  {
1648  destroy_state(&state);
1649  init_state(&state, NULL);
1650  state.imap_browse = true;
1651  imap_browse(mutt_buffer_string(&LastDir), &state);
1652  browser_sort(&state);
1653  menu->mdata = &state.entry;
1654  browser_highlight_default(&state, menu);
1655  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1656  }
1657  }
1658  break;
1659 
1660  case OP_DELETE_MAILBOX:
1661  if (!ff->imap)
1662  mutt_error(_("Delete is only supported for IMAP mailboxes"));
1663  else
1664  {
1665  char msg[128];
1666 
1667  // TODO(sileht): It could be better to select INBOX instead. But I
1668  // don't want to manipulate Context/Mailboxes/mailbox->account here for now.
1669  // Let's just protect neomutt against crash for now. #1417
1670  if (mutt_str_equal(mailbox_path(m), ff->name))
1671  {
1672  mutt_error(_("Can't delete currently selected mailbox"));
1673  break;
1674  }
1675 
1676  snprintf(msg, sizeof(msg), _("Really delete mailbox \"%s\"?"), ff->name);
1677  if (mutt_yesorno(msg, MUTT_NO) == MUTT_YES)
1678  {
1679  if (imap_delete_mailbox(m, ff->name) == 0)
1680  {
1681  /* free the mailbox from the browser */
1682  FREE(&ff->name);
1683  FREE(&ff->desc);
1684  /* and move all other entries up */
1685  ARRAY_REMOVE(&state.entry, ff);
1686  mutt_message(_("Mailbox deleted"));
1687  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1688  }
1689  else
1690  mutt_error(_("Mailbox deletion failed"));
1691  }
1692  else
1693  mutt_message(_("Mailbox not deleted"));
1694  }
1695  break;
1696 #endif
1697 
1698  case OP_GOTO_PARENT:
1699  case OP_CHANGE_DIRECTORY:
1700 
1701 #ifdef USE_NNTP
1702  if (OptNews)
1703  break;
1704 #endif
1705 
1706  mutt_buffer_copy(buf, &LastDir);
1707 #ifdef USE_IMAP
1708  if (!state.imap_browse)
1709 #endif
1710  {
1711  /* add '/' at the end of the directory name if not already there */
1712  size_t len = mutt_buffer_len(buf);
1713  if ((len > 0) && (mutt_buffer_string(&LastDir)[len - 1] != '/'))
1714  mutt_buffer_addch(buf, '/');
1715  }
1716 
1717  if (op == OP_CHANGE_DIRECTORY)
1718  {
1719  /* buf comes from the buffer pool, so defaults to size 1024 */
1720  int ret = mutt_buffer_get_field(_("Chdir to: "), buf, MUTT_FILE,
1721  false, NULL, NULL, NULL);
1722  if ((ret != 0) && mutt_buffer_is_empty(buf))
1723  break;
1724  }
1725  else if (op == OP_GOTO_PARENT)
1727 
1728  if (!mutt_buffer_is_empty(buf))
1729  {
1730  mailbox = false;
1732 #ifdef USE_IMAP
1733  if (imap_path_probe(mutt_buffer_string(buf), NULL) == MUTT_IMAP)
1734  {
1735  mutt_buffer_copy(&LastDir, buf);
1736  destroy_state(&state);
1737  init_state(&state, NULL);
1738  state.imap_browse = true;
1739  imap_browse(mutt_buffer_string(&LastDir), &state);
1740  browser_sort(&state);
1741  menu->mdata = &state.entry;
1742  browser_highlight_default(&state, menu);
1743  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1744  }
1745  else
1746 #endif
1747  {
1748  if (mutt_buffer_string(buf)[0] != '/')
1749  {
1750  /* in case dir is relative, make it relative to LastDir,
1751  * not current working dir */
1753  mutt_buffer_string(buf));
1754  mutt_buffer_copy(buf, tmp);
1755  }
1756  /* Resolve path from <chdir>
1757  * Avoids buildup such as /a/b/../../c
1758  * Symlinks are always unraveled to keep code simple */
1759  if (mutt_path_realpath(buf->data) == 0)
1760  break;
1761 
1762  struct stat st;
1763  if (stat(mutt_buffer_string(buf), &st) == 0)
1764  {
1765  if (S_ISDIR(st.st_mode))
1766  {
1767  destroy_state(&state);
1768  if (examine_directory(m, menu, &state, mutt_buffer_string(buf),
1769  mutt_buffer_string(prefix)) == 0)
1770  {
1771  mutt_buffer_copy(&LastDir, buf);
1772  }
1773  else
1774  {
1775  mutt_error(_("Error scanning directory"));
1776  if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1777  mutt_buffer_string(prefix)) == -1)
1778  {
1779  goto bail;
1780  }
1781  }
1782  browser_highlight_default(&state, menu);
1783  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1784  }
1785  else
1786  mutt_error(_("%s is not a directory"), mutt_buffer_string(buf));
1787  }
1788  else
1790  }
1791  }
1792  break;
1793 
1794  case OP_ENTER_MASK:
1795  {
1796  const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
1797  mutt_buffer_strcpy(buf, c_mask ? c_mask->pattern : NULL);
1798  if (mutt_get_field(_("File Mask: "), buf->data, buf->dsize,
1799  MUTT_COMP_NO_FLAGS, false, NULL, NULL) != 0)
1800  {
1801  break;
1802  }
1803 
1804  mutt_buffer_fix_dptr(buf);
1805 
1806  mailbox = false;
1807  /* assume that the user wants to see everything */
1808  if (mutt_buffer_is_empty(buf))
1809  mutt_buffer_strcpy(buf, ".");
1810 
1811  struct Buffer errmsg = mutt_buffer_make(256);
1812  int rc = cs_subset_str_string_set(NeoMutt->sub, "mask",
1813  mutt_buffer_string(buf), &errmsg);
1814  if (CSR_RESULT(rc) != CSR_SUCCESS)
1815  {
1816  if (!mutt_buffer_is_empty(&errmsg))
1817  {
1818  mutt_error("%s", mutt_buffer_string(&errmsg));
1819  mutt_buffer_dealloc(&errmsg);
1820  }
1821  break;
1822  }
1823  mutt_buffer_dealloc(&errmsg);
1824 
1825  destroy_state(&state);
1826 #ifdef USE_IMAP
1827  if (state.imap_browse)
1828  {
1829  init_state(&state, NULL);
1830  state.imap_browse = true;
1831  imap_browse(mutt_buffer_string(&LastDir), &state);
1832  browser_sort(&state);
1833  menu->mdata = &state.entry;
1834  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1835  }
1836  else
1837 #endif
1838  if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir), NULL) == 0)
1839  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1840  else
1841  {
1842  mutt_error(_("Error scanning directory"));
1843  goto bail;
1844  }
1845  kill_prefix = false;
1846  if (ARRAY_EMPTY(&state.entry))
1847  {
1848  mutt_error(_("No files match the file mask"));
1849  break;
1850  }
1851  break;
1852  }
1853 
1854  case OP_SORT:
1855  case OP_SORT_REVERSE:
1856 
1857  {
1858  bool resort = true;
1859  int sort = -1;
1860  int reverse = (op == OP_SORT_REVERSE);
1861 
1862  switch (mutt_multi_choice(
1863  (reverse) ?
1864  /* L10N: The highlighted letters must match the "Sort" options */
1865  _("Reverse sort by (d)ate, (a)lpha, si(z)e, d(e)scription, "
1866  "(c)ount, ne(w) count, or do(n)'t sort?") :
1867  /* L10N: The highlighted letters must match the "Reverse Sort" options */
1868  _("Sort by (d)ate, (a)lpha, si(z)e, d(e)scription, (c)ount, "
1869  "ne(w) count, or do(n)'t sort?"),
1870  /* L10N: These must match the highlighted letters from "Sort" and "Reverse Sort" */
1871  _("dazecwn")))
1872  {
1873  case -1: /* abort */
1874  resort = false;
1875  break;
1876 
1877  case 1: /* (d)ate */
1878  sort = SORT_DATE;
1879  break;
1880 
1881  case 2: /* (a)lpha */
1882  sort = SORT_SUBJECT;
1883  break;
1884 
1885  case 3: /* si(z)e */
1886  sort = SORT_SIZE;
1887  break;
1888 
1889  case 4: /* d(e)scription */
1890  sort = SORT_DESC;
1891  break;
1892 
1893  case 5: /* (c)ount */
1894  sort = SORT_COUNT;
1895  break;
1896 
1897  case 6: /* ne(w) count */
1898  sort = SORT_UNREAD;
1899  break;
1900 
1901  case 7: /* do(n)'t sort */
1902  sort = SORT_ORDER;
1903  break;
1904  }
1905  if (resort)
1906  {
1907  sort |= reverse ? SORT_REVERSE : 0;
1908  cs_subset_str_native_set(NeoMutt->sub, "sort_browser", sort, NULL);
1909  browser_sort(&state);
1910  browser_highlight_default(&state, menu);
1912  }
1913  else
1914  {
1915  cs_subset_str_native_set(NeoMutt->sub, "sort_browser", sort, NULL);
1916  }
1917  break;
1918  }
1919 
1920  case OP_TOGGLE_MAILBOXES:
1921  case OP_BROWSER_GOTO_FOLDER:
1922  case OP_CHECK_NEW:
1923  if (op == OP_TOGGLE_MAILBOXES)
1924  {
1925  mailbox = !mailbox;
1926  menu->is_mailbox_list = mailbox;
1927  }
1928 
1929  if (op == OP_BROWSER_GOTO_FOLDER)
1930  {
1931  /* When in mailboxes mode, disables this feature */
1932  const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
1933  if (c_folder)
1934  {
1935  mutt_debug(LL_DEBUG3, "= hit! Folder: %s, LastDir: %s\n", c_folder,
1936  mutt_buffer_string(&LastDir));
1937  if (goto_swapper[0] == '\0')
1938  {
1939  if (!mutt_str_equal(mutt_buffer_string(&LastDir), c_folder))
1940  {
1941  /* Stores into goto_swapper LastDir, and swaps to `$folder` */
1942  mutt_str_copy(goto_swapper, mutt_buffer_string(&LastDir),
1943  sizeof(goto_swapper));
1944  mutt_buffer_copy(&LastDirBackup, &LastDir);
1945  mutt_buffer_strcpy(&LastDir, c_folder);
1946  }
1947  }
1948  else
1949  {
1950  mutt_buffer_copy(&LastDirBackup, &LastDir);
1951  mutt_buffer_strcpy(&LastDir, goto_swapper);
1952  goto_swapper[0] = '\0';
1953  }
1954  }
1955  }
1956  destroy_state(&state);
1957  mutt_buffer_reset(prefix);
1958  kill_prefix = false;
1959 
1960  if (mailbox)
1961  {
1962  examine_mailboxes(m, menu, &state);
1963  }
1964 #ifdef USE_IMAP
1965  else if (imap_path_probe(mutt_buffer_string(&LastDir), NULL) == MUTT_IMAP)
1966  {
1967  init_state(&state, NULL);
1968  state.imap_browse = true;
1969  imap_browse(mutt_buffer_string(&LastDir), &state);
1970  browser_sort(&state);
1971  menu->mdata = &state.entry;
1972  }
1973 #endif
1974  else if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1975  mutt_buffer_string(prefix)) == -1)
1976  {
1977  goto bail;
1978  }
1979  init_menu(&state, menu, title, sizeof(title), mailbox, m);
1980  break;
1981 
1982  case OP_MAILBOX_LIST:
1984  break;
1985 
1986  case OP_BROWSER_NEW_FILE:
1987  mutt_buffer_printf(buf, "%s/", mutt_buffer_string(&LastDir));
1988  /* buf comes from the buffer pool, so defaults to size 1024 */
1989  if (mutt_buffer_get_field(_("New file name: "), buf, MUTT_FILE, false,
1990  NULL, NULL, NULL) == 0)
1991  {
1992  mutt_buffer_copy(file, buf);
1993  destroy_state(&state);
1994  goto bail;
1995  }
1996  break;
1997 
1998  case OP_BROWSER_VIEW_FILE:
1999  if (ARRAY_EMPTY(&state.entry))
2000  {
2001  mutt_error(_("No files match the file mask"));
2002  break;
2003  }
2004 
2005 #ifdef USE_IMAP
2006  if (ff->selectable)
2007  {
2008  mutt_buffer_strcpy(file, ff->name);
2009  destroy_state(&state);
2010  goto bail;
2011  }
2012  else
2013 #endif
2014  if (S_ISDIR(ff->mode) ||
2015  (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name)))
2016  {
2017  mutt_error(_("Can't view a directory"));
2018  break;
2019  }
2020  else
2021  {
2022  char buf2[PATH_MAX];
2023 
2024  mutt_path_concat(buf2, mutt_buffer_string(&LastDir), ff->name, sizeof(buf2));
2025  struct Body *b = mutt_make_file_attach(buf2, NeoMutt->sub);
2026  if (b)
2027  {
2028  mutt_view_attachment(NULL, b, MUTT_VA_REGULAR, NULL, NULL, menu->win_index);
2029  mutt_body_free(&b);
2031  }
2032  else
2033  mutt_error(_("Error trying to view file"));
2034  }
2035  break;
2036 
2037 #ifdef USE_NNTP
2038  case OP_CATCHUP:
2039  case OP_UNCATCHUP:
2040  {
2041  if (!OptNews)
2042  break;
2043 
2044  struct NntpMboxData *mdata = NULL;
2045 
2047  if (rc < 0)
2048  break;
2049 
2050  if (op == OP_CATCHUP)
2051  mdata = mutt_newsgroup_catchup(m, CurrentNewsSrv, ff->name);
2052  else
2053  mdata = mutt_newsgroup_uncatchup(m, CurrentNewsSrv, ff->name);
2054 
2055  if (mdata)
2056  {
2058  index = menu_get_index(menu) + 1;
2059  if (index < menu->max)
2060  menu_set_index(menu, index);
2061  }
2062  if (rc)
2065  break;
2066  }
2067 
2068  case OP_LOAD_ACTIVE:
2069  {
2070  if (!OptNews)
2071  break;
2072 
2074 
2075  if (nntp_newsrc_parse(adata) < 0)
2076  break;
2077 
2078  for (size_t i = 0; i < adata->groups_num; i++)
2079  {
2080  struct NntpMboxData *mdata = adata->groups_list[i];
2081  if (mdata)
2082  mdata->deleted = true;
2083  }
2084  nntp_active_fetch(adata, true);
2085  nntp_newsrc_update(adata);
2086  nntp_newsrc_close(adata);
2087 
2088  destroy_state(&state);
2089  if (mailbox)
2090  examine_mailboxes(m, menu, &state);
2091  else
2092  {
2093  if (examine_directory(m, menu, &state, NULL, NULL) == -1)
2094  break;
2095  }
2096  init_menu(&state, menu, title, sizeof(title), mailbox, m);
2097  break;
2098  }
2099 #endif /* USE_NNTP */
2100 
2101 #if defined(USE_IMAP) || defined(USE_NNTP)
2102  case OP_BROWSER_SUBSCRIBE:
2103  case OP_BROWSER_UNSUBSCRIBE:
2104 #endif
2105 #ifdef USE_NNTP
2106  case OP_SUBSCRIBE_PATTERN:
2107  case OP_UNSUBSCRIBE_PATTERN:
2108  {
2109  if (OptNews)
2110  {
2112  regex_t rx;
2113  memset(&rx, 0, sizeof(rx));
2114  char *s = buf->data;
2115  index = menu_get_index(menu);
2116 
2117  if ((op == OP_SUBSCRIBE_PATTERN) || (op == OP_UNSUBSCRIBE_PATTERN))
2118  {
2119  char tmp2[256];
2120 
2121  mutt_buffer_reset(buf);
2122  if (op == OP_SUBSCRIBE_PATTERN)
2123  snprintf(tmp2, sizeof(tmp2), _("Subscribe pattern: "));
2124  else
2125  snprintf(tmp2, sizeof(tmp2), _("Unsubscribe pattern: "));
2126  /* buf comes from the buffer pool, so defaults to size 1024 */
2127  if ((mutt_buffer_get_field(tmp2, buf, MUTT_PATTERN, false, NULL, NULL, NULL) != 0) ||
2128  mutt_buffer_is_empty(buf))
2129  {
2130  break;
2131  }
2132 
2133  int err = REG_COMP(&rx, s, REG_NOSUB);
2134  if (err != 0)
2135  {
2136  regerror(err, &rx, buf->data, buf->dsize);
2137  regfree(&rx);
2138  mutt_error("%s", mutt_buffer_string(buf));
2139  break;
2140  }
2142  index = 0;
2143  }
2144  else if (ARRAY_EMPTY(&state.entry))
2145  {
2146  mutt_error(_("No newsgroups match the mask"));
2147  break;
2148  }
2149 
2150  int rc = nntp_newsrc_parse(adata);
2151  if (rc < 0)
2152  break;
2153 
2154  ARRAY_FOREACH_FROM(ff, &state.entry, index)
2155  {
2156  if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_BROWSER_UNSUBSCRIBE) ||
2157  (regexec(&rx, ff->name, 0, NULL, 0) == 0))
2158  {
2159  if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_SUBSCRIBE_PATTERN))
2160  mutt_newsgroup_subscribe(adata, ff->name);
2161  else
2162  mutt_newsgroup_unsubscribe(adata, ff->name);
2163  }
2164  if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_BROWSER_UNSUBSCRIBE))
2165  {
2166  if ((index + 1) < menu->max)
2167  menu_set_index(menu, index + 1);
2168  break;
2169  }
2170  }
2171 
2172  if (op == OP_SUBSCRIBE_PATTERN)
2173  {
2174  for (size_t j = 0; adata && (j < adata->groups_num); j++)
2175  {
2176  struct NntpMboxData *mdata = adata->groups_list[j];
2177  if (mdata && mdata->group && !mdata->subscribed)
2178  {
2179  if (regexec(&rx, mdata->group, 0, NULL, 0) == 0)
2180  {
2181  mutt_newsgroup_subscribe(adata, mdata->group);
2182  add_folder(menu, &state, mdata->group, NULL, NULL, NULL, mdata);
2183  }
2184  }
2185  }
2186  init_menu(&state, menu, title, sizeof(title), mailbox, m);
2187  }
2188  if (rc > 0)
2190  nntp_newsrc_update(adata);
2191  nntp_clear_cache(adata);
2192  nntp_newsrc_close(adata);
2193  if ((op != OP_BROWSER_SUBSCRIBE) && (op != OP_BROWSER_UNSUBSCRIBE))
2194  regfree(&rx);
2195  }
2196 #ifdef USE_IMAP
2197  else
2198 #endif /* USE_IMAP && USE_NNTP */
2199 #endif /* USE_NNTP */
2200 #ifdef USE_IMAP
2201  {
2202  char tmp2[256];
2203  mutt_str_copy(tmp2, ff->name, sizeof(tmp2));
2204  mutt_expand_path(tmp2, sizeof(tmp2));
2205  imap_subscribe(tmp2, (op == OP_BROWSER_SUBSCRIBE));
2206  }
2207 #endif /* USE_IMAP */
2208  }
2209  }
2210  }
2211 
2212 bail:
2213  mutt_buffer_pool_release(&OldLastDir);
2216  mutt_buffer_pool_release(&prefix);
2217 
2218  if (menu)
2220 
2221  goto_swapper[0] = '\0';
2222 }
2223 
2233 void mutt_select_file(char *file, size_t filelen, SelectFileFlags flags,
2234  struct Mailbox *m, char ***files, int *numfiles)
2235 {
2236  struct Buffer *f_buf = mutt_buffer_pool_get();
2237 
2238  mutt_buffer_strcpy(f_buf, NONULL(file));
2239  mutt_buffer_select_file(f_buf, flags, m, files, numfiles);
2240  mutt_str_copy(file, mutt_buffer_string(f_buf), filelen);
2241 
2242  mutt_buffer_pool_release(&f_buf);
2243 }
#define CSR_SUCCESS
Action completed successfully.
Definition: set.h:35
int msg_unread
number of unread messages
Definition: browser.h:67
static const char * folder_format_str(char *buf, size_t buflen, size_t col, int cols, char op, const char *src, const char *prec, const char *if_str, const char *else_str, intptr_t data, MuttFormatFlags flags)
Format a string for the folder browser - Implements format_t.
Definition: browser.c:364
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
Convenience wrapper for the gui headers.
mode_t mode
Definition: browser.h:55
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:416
bool has_new_mail
Definition: mdata.h:42
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:206
#define ARRAY_RESERVE(head, num)
Reserve memory for the array.
Definition: array.h:185
static int browser_compare_date(const void *a, const void *b)
Compare the date of two browser entries - Implements sort_t.
Definition: browser.c:193
time_t mtime
Definition: browser.h:57
Manage keymappings.
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
uint8_t MuttFormatFlags
Flags for mutt_expando_format(), e.g. MUTT_FORMAT_FORCESUBJ.
Definition: format_flags.h:29
bool mutt_mailbox_list(void)
List the mailboxes with new mail.
Definition: mutt_mailbox.c:222
#define NONULL(x)
Definition: string2.h:37
bool has_mailbox
Definition: browser.h:76
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
static bool link_is_dir(const char *folder, const char *path)
Does this symlink point to a directory?
Definition: browser.c:328
int msg_count
Total number of messages.
Definition: mailbox.h:91
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:71
size_t mutt_date_localtime_format(char *buf, size_t buflen, const char *format, time_t t)
Format localtime.
Definition: date.c:680
IMAP network mailbox.
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
#define mutt_perror(...)
Definition: logging.h:85
GUI selectable list of items.
Definition: lib.h:56
enum MailboxType imap_path_probe(const char *path, const struct stat *st)
Is this an IMAP Mailbox? - Implements MxOps::path_probe()
Definition: imap.c:2400
static int examine_directory(struct Mailbox *m, struct Menu *menu, struct BrowserState *state, const char *d, const char *prefix)
Get list of all files/newsgroups with mask.
Definition: browser.c:693
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:36
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
void mutt_browser_cleanup(void)
Clean up working Buffers.
Definition: browser.c:119
static void destroy_state(struct BrowserState *state)
Free the BrowserState.
Definition: browser.c:131
&#39;NNTP&#39; (Usenet) Mailbox type
Definition: mailbox.h:52
Structs that make up an email.
size_t mutt_buffer_copy(struct Buffer *dst, const struct Buffer *src)
Copy a Buffer&#39;s contents to another Buffer.
Definition: buffer.c:445
char * desc
Definition: browser.h:63
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer&#39;s string.
Definition: buffer.c:432
Convenience wrapper for the send headers.
int mutt_buffer_get_field(const char *field, struct Buffer *buf, CompletionFlags complete, bool multiple, struct Mailbox *m, char ***files, int *numfiles)
Ask the user for a string.
Definition: curs_lib.c:255
#define mutt_message(...)
Definition: logging.h:83
int imap_delete_mailbox(struct Mailbox *m, char *path)
Delete a mailbox.
Definition: imap.c:513
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:68
struct AccountList accounts
List of all Accounts.
Definition: neomutt.h:40
void nntp_clear_cache(struct NntpAccountData *adata)
Clear the NNTP cache.
Definition: newsrc.c:846
void neomutt_mailboxlist_clear(struct MailboxList *ml)
Free a Mailbox List.
Definition: neomutt.c:137
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:306
#define ARRAY_FIRST(head)
Convenience method to get the first element.
Definition: array.h:131
static int file_tag(struct Menu *menu, int sel, int act)
Tag an entry in the menu - Implements Menu::tag()
Definition: browser.c:1114
struct NntpMboxData * mutt_newsgroup_uncatchup(struct Mailbox *m, struct NntpAccountData *adata, char *group)
Uncatchup newsgroup.
Definition: newsrc.c:1347
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:198
struct MuttWindow * mutt_window_find(struct MuttWindow *root, enum WindowType type)
Find a Window of a given type.
Definition: mutt_window.c:675
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:206
gid_t gid
Definition: browser.h:59
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
State of the file/mailbox browser.
Definition: browser.h:91
#define MUTT_FORMAT_ARROWCURSOR
Reserve space for arrow_cursor.
Definition: format_flags.h:35
Match any Mailbox type.
Definition: mailbox.h:45
size_t neomutt_mailboxlist_get_all(struct MailboxList *head, struct NeoMutt *n, enum MailboxType type)
Get a List of all Mailboxes.
Definition: neomutt.c:160
#define ARRAY_GET(head, idx)
Return the element at index.
Definition: array.h:105
General file/mailbox browser.
Definition: keymap.h:78
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:61
static int browser_compare_order(const void *a, const void *b)
Compare the order of creation of two browser entries - Implements sort_t.
Definition: browser.c:167
static void add_folder(struct Menu *menu, struct BrowserState *state, const char *name, const char *desc, const struct stat *s, struct Mailbox *m, void *data)
Add a folder to the browser list.
Definition: browser.c:621
String manipulation buffer.
Definition: buffer.h:33
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
struct NntpMboxData * mutt_newsgroup_subscribe(struct NntpAccountData *adata, char *group)
Subscribe newsgroup.
Definition: newsrc.c:1256
void mutt_unget_event(int ch, int op)
Return a keystroke to the input buffer.
Definition: curs_lib.c:725
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
struct BrowserStateEntry entry
Definition: browser.h:93
Error occurred examining Mailbox.
Definition: mailbox.h:46
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition: regex3.h:54
int imap_mailbox_create(const char *path)
Create a new IMAP mailbox.
Definition: browse.c:384
void dialog_destroy_simple_index(struct MuttWindow **ptr)
Destroy a simple index Dialog.
Definition: simple.c:111
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:563
A division of the screen.
Definition: mutt_window.h:117
int gen
Definition: browser.h:83
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:118
static int browser_compare_desc(const void *a, const void *b)
Compare the descriptions of two browser entries - Implements sort_t.
Definition: browser.c:179
static void init_lastdir(void)
Initialise the browser directories.
Definition: browser.c:105
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
struct MuttWindow * dialog_create_simple_index(enum MenuType mtype, enum WindowType wtype, const struct Mapping *help_data)
Create a simple index Dialog.
Definition: simple.c:76
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
View using default method.
Definition: mutt_attach.h:43
static int browser_compare_count(const void *a, const void *b)
Compare the message count of two browser entries - Implements sort_t.
Definition: browser.c:221
static int examine_mailboxes(struct Mailbox *m, struct Menu *menu, struct BrowserState *state)
Get list of mailboxes/subscribed newsgroups.
Definition: browser.c:821
static int select_file_search(struct Menu *menu, regex_t *rx, int line)
Menu search callback for matching files - Implements Menu::search()
Definition: browser.c:929
Flags to control mutt_expando_format()
All user-callable functions.
int mutt_str_coll(const char *a, const char *b)
Collate two strings (compare using locale), safely.
Definition: string.c:644
Container for Accounts, Notifications.
Definition: neomutt.h:36
void mutt_buffer_pretty_mailbox(struct Buffer *buf)
Shorten a mailbox path using &#39;~&#39; or &#39;=&#39;.
Definition: muttlib.c:602
#define MUTT_PATTERN
Pattern mode - only used for history classes.
Definition: mutt.h:60
NNTP-specific Account data -.
Definition: adata.h:36
#define MUTT_FORMAT_NO_FLAGS
No flags are set.
Definition: format_flags.h:30
bool subscribed
Definition: mdata.h:41
The body of an email.
Definition: body.h:34
Convenience wrapper for the config headers.
struct FolderFile * ff
Definition: browser.h:46
GUI component for displaying/selecting items from a list.
void(* make_entry)(struct Menu *menu, char *buf, size_t buflen, int line)
Format a item for a menu.
Definition: lib.h:91
#define MUTT_SEL_FOLDER
Select a local directory.
Definition: browser.h:39
off_t size
Definition: browser.h:56
#define CSR_RESULT(x)
Definition: set.h:52
char * HomeDir
User&#39;s home directory.
Definition: mutt_globals.h:45
char * folder
Definition: browser.h:96
bool selectable
Definition: browser.h:73
char host[128]
Server to login to.
Definition: connaccount.h:53
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
Some miscellaneous functions.
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:227
bool has_new
Mailbox has new mail.
Definition: mailbox.h:88
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:292
Sort by the size of the email.
Definition: sort2.h:44
size_t dsize
Length of data.
Definition: buffer.h:37
char * mutt_expand_path(char *buf, size_t buflen)
Create the canonical path.
Definition: muttlib.c:121
int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
Fetch list of all newsgroups from server.
Definition: nntp.c:1959
char * name
A short name for the Mailbox.
Definition: mailbox.h:85
int cs_subset_str_string_set(const struct ConfigSubset *sub, const char *name, const char *value, struct Buffer *err)
Set a config item by string.
Definition: subset.c:395
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
Ask the user a Yes/No question.
Definition: curs_lib.c:375
Many unsorted constants and some structs.
API for mailboxes.
uid_t uid
Definition: browser.h:58
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:52
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:48
nlink_t nlink
Definition: browser.h:60
Convenience wrapper for the core headers.
struct Body * mutt_make_file_attach(const char *path, struct ConfigSubset *sub)
Create a file attachment.
Definition: sendlib.c:1088
char * name
Definition: browser.h:62
char * group
Definition: mdata.h:34
int num
Definition: browser.h:47
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
size_t mutt_path_realpath(char *buf)
resolve path, unraveling symlinks
Definition: path.c:440
bool imap
Definition: browser.h:72
int bool_str_toggle(struct ConfigSubset *sub, const char *name, struct Buffer *err)
Toggle the value of a bool.
Definition: bool.c:213
const char * group_index_format_str(char *buf, size_t buflen, size_t col, int cols, char op, const char *src, const char *prec, const char *if_str, const char *else_str, intptr_t data, MuttFormatFlags flags)
Format a string for the newsgroup menu - Implements format_t.
Definition: browse.c:55
const char * OpStrings[][2]
Definition: opcodes.c:34
void * mdata
Driver specific data.
Definition: mailbox.h:136
Browser Dialog, mutt_buffer_select_file()
Definition: mutt_window.h:78
&#39;Maildir&#39; Mailbox type
Definition: mailbox.h:51
Usenet network mailbox type; talk to an NNTP server.
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
void mutt_buffer_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition: buffer.c:181
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:272
short cols
Number of columns, can be MUTT_WIN_SIZE_UNLIMITED.
Definition: mutt_window.h:58
int mutt_multi_choice(const char *prompt, const char *letters)
Offer the user a multiple choice question.
Definition: curs_lib.c:824
A folder/dir in the browser.
Definition: browser.h:44
Browser entry representing a folder/dir.
Definition: browser.h:53
static int browser_compare_count_new(const void *a, const void *b)
Compare the new count of two browser entries - Implements sort_t.
Definition: browser.c:241
static const struct Mapping FolderNewsHelp[]
Help Bar for the NNTP Mailbox browser dialog.
Definition: browser.c:83
#define MB_HIDDEN
Definition: mailbox.h:38
struct WindowState state
Current state of the Window.
Definition: mutt_window.h:122
void * mdata
Private data.
Definition: lib.h:129
&#39;POP3&#39; Mailbox type
Definition: mailbox.h:55
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
size_t mutt_buffer_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:356
A mailbox.
Definition: mailbox.h:81
#define PATH_MAX
Definition: mutt.h:40
int mutt_mailbox_check(struct Mailbox *m_cur, int force)
Check all all Mailboxes for new mail.
Definition: mutt_mailbox.c:137
int top
Entry that is the top of the current page.
Definition: lib.h:78
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition: array.h:70
size_t mutt_buffer_strcpy_n(struct Buffer *buf, const char *s, size_t len)
Copy a string into a Buffer.
Definition: buffer.c:327
void mutt_str_pretty_size(char *buf, size_t buflen, size_t num)
Display an abbreviated size, like 3.4K.
Definition: muttlib.c:1679
size_t mutt_buffer_addstr_n(struct Buffer *buf, const char *s, size_t len)
Add a string to a Buffer, expanding it if necessary.
Definition: buffer.c:99
int mutt_inbox_cmp(const char *a, const char *b)
do two folders share the same path and one is an inbox
Definition: muttlib.c:1611
char * data
Pointer to data.
Definition: buffer.h:35
unsigned int groups_num
Definition: adata.h:59
static void browser_sort(struct BrowserState *state)
Sort the entries in the browser.
Definition: browser.c:303
Nntp-specific Mailbox data.
static int browser_compare_size(const void *a, const void *b)
Compare the size of two browser entries - Implements sort_t.
Definition: browser.c:207
&#39;mmdf&#39; Mailbox type
Definition: mailbox.h:49
bool is_mailbox_list
Definition: lib.h:65
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:162
static void init_menu(struct BrowserState *state, struct Menu *menu, char *title, size_t titlelen, bool mailbox, struct Mailbox *m)
Set up a new menu.
Definition: browser.c:1009
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:295
&#39;MH&#39; Mailbox type
Definition: mailbox.h:50
int msg_count
total number of messages
Definition: browser.h:66
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition: body.c:57
struct NntpMboxData * mutt_newsgroup_unsubscribe(struct NntpAccountData *adata, char *group)
Unsubscribe newsgroup.
Definition: newsrc.c:1280
Handling of email attachments.
void sbar_set_title(struct MuttWindow *win, const char *title)
Set the title for the Simple Bar.
Definition: sbar.c:159
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
int(* tag)(struct Menu *menu, int sel, int act)
Tag some menu items.
Definition: lib.h:110
void mutt_select_file(char *file, size_t filelen, SelectFileFlags flags, struct Mailbox *m, char ***files, int *numfiles)
Let the user select a file.
Definition: browser.c:2233
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
static int browser_compare_subject(const void *a, const void *b)
Compare the subject of two browser entries - Implements sort_t.
Definition: browser.c:149
#define MUTT_SEL_MULTI
Multi-selection is enabled.
Definition: browser.h:38
NNTP-specific Mailbox data -.
Definition: mdata.h:32
int tagged
Number of tagged entries.
Definition: lib.h:81
#define MUTT_FILE
Do file completion.
Definition: mutt.h:54
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
char * path
Path.
Definition: url.h:75
int max
Number of entries in the menu.
Definition: lib.h:60
&#39;Notmuch&#39; (virtual) Mailbox type
Definition: mailbox.h:54
&#39;mbox&#39; Mailbox type
Definition: mailbox.h:48
#define ARRAY_FOREACH_FROM(elem, head, from)
Iterate from an index to the end.
Definition: array.h:217
#define MUTT_SEL_MAILBOX
Select a mailbox.
Definition: browser.h:37
uint8_t flags
e.g. MB_NORMAL
Definition: mailbox.h:134
struct Connection * conn
Definition: adata.h:63
static void browser_highlight_default(struct BrowserState *state, struct Menu *menu)
Decide which browser item should be highlighted.
Definition: browser.c:981
enum MailboxType mx_path_probe(const char *path)
Find a mailbox that understands a path.
Definition: mx.c:1316
void mutt_buffer_expand_path(struct Buffer *buf)
Create the canonical path.
Definition: muttlib.c:321
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
void mutt_format_s(char *buf, size_t buflen, const char *prec, const char *s)
Format a simple string.
Definition: curs_lib.c:1134
Log at debug level 1.
Definition: logging.h:40
bool deleted
Definition: mdata.h:44
Sort by the number of unread emails.
Definition: sort2.h:59
void mutt_browser_select_dir(const char *f)
Remember the last directory selected.
Definition: browser.c:1141
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:716
void imap_clean_path(char *path, size_t plen)
Cleans an IMAP path using imap_fix_path.
Definition: util.c:188
#define ARRAY_REMOVE(head, elem)
Remove an entry from the array, shifting down the subsequent entries.
Definition: array.h:261
Cached regular expression.
Definition: regex3.h:89
anum_t unread
Definition: mdata.h:40
void mutt_expando_format(char *buf, size_t buflen, size_t col, int cols, const char *src, format_t callback, intptr_t data, MuttFormatFlags flags)
Expand expandos (x) in a string.
Definition: muttlib.c:779
#define mutt_error(...)
Definition: logging.h:84
int imap_browse(const char *path, struct BrowserState *state)
IMAP hook into the folder browser.
Definition: browse.c:182
Sort by the folder&#39;s description.
Definition: sort2.h:63
WHERE char * CurrentFolder
Currently selected mailbox.
Definition: mutt_globals.h:50
bool mutt_regex_match(const struct Regex *regex, const char *str)
Shorthand to mutt_regex_capture()
Definition: regex.c:613
Connection Library.
void ** groups_list
Definition: adata.h:61
void mutt_buffer_select_file(struct Buffer *file, SelectFileFlags flags, struct Mailbox *m, char ***files, int *numfiles)
Let the user select a file.
Definition: browser.c:1161
#define FREE(x)
Definition: memory.h:40
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:152
size_t mutt_buffer_concat_path(struct Buffer *buf, const char *dir, const char *fname)
Join a directory name and a filename.
Definition: buffer.c:374
int imap_mailbox_rename(const char *path)
Rename a mailbox.
Definition: browse.c:437
Mapping between user-readable string and a constant.
Definition: mapping.h:31
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:321
struct NntpAccountData * CurrentNewsSrv
Current NNTP news server.
Definition: nntp.c:77
User answered &#39;No&#39;, or assume &#39;No&#39;.
Definition: quad.h:38
#define ARRAY_SORT(head, fn)
Sort an array.
Definition: array.h:271
#define MUTT_FORMAT_OPTIONAL
Allow optional field processing.
Definition: format_flags.h:33
Sort by number of emails in a folder.
Definition: sort2.h:58
char * mutt_path_concat(char *d, const char *dir, const char *fname, size_t l)
Join a directory name and a filename.
Definition: path.c:351
List of Mailboxes.
Definition: mailbox.h:152
static int browser_compare(const void *a, const void *b)
Sort the items in the browser - Implements sort_t.
Definition: browser.c:265
Hundreds of global variables to back the user variables.
Handling of global boolean variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
bool tagged
Definition: browser.h:78
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
struct NntpMboxData * nd
Definition: browser.h:80
#define TAILQ_EMPTY(head)
Definition: queue.h:714
int nntp_newsrc_update(struct NntpAccountData *adata)
Update .newsrc file.
Definition: newsrc.c:439
char * pattern
printable version
Definition: regex3.h:91
bool local
folder is on local filesystem
Definition: browser.h:77
static void init_state(struct BrowserState *state, struct Menu *menu)
Initialise a browser state.
Definition: browser.c:672
Sort by the date the email was sent.
Definition: sort2.h:43
struct NntpMboxData * mutt_newsgroup_catchup(struct Mailbox *m, struct NntpAccountData *adata, char *group)
Catchup newsgroup.
Definition: newsrc.c:1308
Nntp-specific Account data.
struct MuttWindow * win_index
Definition: lib.h:66
Convenience wrapper for the library headers.
bool inferiors
Definition: browser.h:74
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:79
static const struct Mapping FolderHelp[]
Help Bar for the File/Dir/Mailbox browser dialog.
Definition: browser.c:70
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:252
void * wdata
Private data.
Definition: mutt_window.h:140
&#39;IMAP&#39; Mailbox type
Definition: mailbox.h:53
bool imap_browse
Definition: browser.h:95
#define N_(a)
Definition: message.h:32
Index Bar containing status info about the Index.
Definition: mutt_window.h:96
int(* search)(struct Menu *menu, regex_t *rx, int line)
Search a menu for a item matching a regex.
Definition: lib.h:101
void mutt_get_parent_path(const char *path, char *buf, size_t buflen)
Find the parent of a path (or mailbox)
Definition: muttlib.c:1555
WHERE bool OptNews
(pseudo) used to change reader mode
Definition: options.h:45
char delim
Definition: browser.h:70
int gen
Generation number, for sorting.
Definition: mailbox.h:146
Mailbox helper functions.
Sort by the email&#39;s subject.
Definition: sort2.h:46
Log at debug level 3.
Definition: logging.h:42
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:78
#define ARRAY_SIZE(head)
The number of elements stored.
Definition: array.h:83
static void folder_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
Format a menu item for the folder browser - Implements Menu::make_entry()
Definition: browser.c:945
User answered &#39;Yes&#39;, or assume &#39;Yes&#39;.
Definition: quad.h:39
int imap_subscribe(char *path, bool subscribe)
Subscribe to a mailbox.
Definition: imap.c:1283
#define ARRAY_INIT(head)
Initialize an array.
Definition: array.h:61
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
int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode, struct Email *e, struct AttachCtx *actx, struct MuttWindow *win)
View an attachment.
Definition: mutt_attach.c:414
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:154
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
bool has_new_mail
true if mailbox has "new mail"
Definition: browser.h:65
uint8_t SelectFileFlags
Flags for mutt_select_file(), e.g. MUTT_SEL_MAILBOX.
Definition: browser.h:33