move browser in the lib-ui
[apps/madmutt.git] / lib-ui / browser.c
diff --git a/lib-ui/browser.c b/lib-ui/browser.c
new file mode 100644 (file)
index 0000000..466cf77
--- /dev/null
@@ -0,0 +1,1073 @@
+/*
+ * Copyright notice from original mutt:
+ * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This file is part of mutt-ng, see http://www.muttng.org/.
+ * It's licensed under the GNU General Public License,
+ * please see the file GPL in the top level source directory.
+ */
+
+#include <lib-ui/lib-ui.h>
+
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <lib-mx/mx.h>
+#include "menu.h"
+#include "browser.h"
+
+#include "mutt.h"
+#include "buffy.h"
+#include "sort.h"
+#include "attach.h"
+
+#include <imap/imap.h>
+
+typedef struct folder_t {
+  struct folder_file *ff;
+  int num;
+} FOLDER;
+
+static char LastDir[_POSIX_PATH_MAX] = "";
+static char LastDirBackup[_POSIX_PATH_MAX] = "";
+
+/* Frees up the memory allocated for the local-global variables.  */
+static void destroy_state (struct browser_state *state)
+{
+  int c;
+
+  for (c = 0; c < state->entrylen; c++) {
+    p_delete(&state->entry[c].name);
+    p_delete(&state->entry[c].desc);
+    p_delete(&state->entry[c].st);
+  }
+  p_delete(&state->folder);
+  p_delete(&state->entry);
+}
+
+static int browser_compare_subject (const void *a, const void *b)
+{
+  struct folder_file *pa = (struct folder_file *) a;
+  struct folder_file *pb = (struct folder_file *) b;
+
+  int r = strcoll(NONULL(pa->name), NONULL(pb->name));
+
+  return ((BrowserSort & SORT_REVERSE) ? -r : r);
+}
+
+static int browser_compare_date (const void *a, const void *b)
+{
+  struct folder_file *pa = (struct folder_file *) a;
+  struct folder_file *pb = (struct folder_file *) b;
+
+  int r = pa->mtime - pb->mtime;
+
+  return ((BrowserSort & SORT_REVERSE) ? -r : r);
+}
+
+static int browser_compare_size (const void *a, const void *b)
+{
+  struct folder_file *pa = (struct folder_file *) a;
+  struct folder_file *pb = (struct folder_file *) b;
+
+  int r = pa->size - pb->size;
+
+  return ((BrowserSort & SORT_REVERSE) ? -r : r);
+}
+
+static void browser_sort (struct browser_state *state)
+{
+  int (*f) (const void *, const void *);
+
+  switch (BrowserSort & SORT_MASK) {
+  case SORT_ORDER:
+    return;
+  case SORT_DATE:
+    f = browser_compare_date;
+    break;
+  case SORT_SIZE:
+    f = browser_compare_size;
+    break;
+  case SORT_SUBJECT:
+  default:
+    f = browser_compare_subject;
+    break;
+  }
+  qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
+}
+
+static int link_is_dir (const char *folder, const char *path)
+{
+  struct stat st;
+  char fullpath[_POSIX_PATH_MAX];
+
+  mutt_concat_path(fullpath, sizeof(fullpath), folder, path);
+
+  if (stat (fullpath, &st) == 0)
+    return (S_ISDIR (st.st_mode));
+  else
+    return 0;
+}
+
+static const char *
+folder_format_str(char *dest, ssize_t destlen, char op,
+                  const char *src, const char *fmt,
+                  const char *ifstr, const char *elstr,
+                  anytype data, format_flag flags)
+{
+  char fn[STRING], tmp[STRING], permission[11], date[16];
+  const char *t_fmt;
+  time_t tnow;
+  FOLDER *folder = data.ptr;
+  struct passwd *pw;
+  struct group *gr;
+  int optional = (flags & M_FORMAT_OPTIONAL);
+
+  switch (op) {
+  case 'C':
+    snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
+    snprintf (dest, destlen, tmp, folder->num + 1);
+    break;
+
+  case 'd':
+    if (folder->ff->st != NULL) {
+      tnow = time (NULL);
+      t_fmt =
+        tnow - folder->ff->st->st_mtime <
+        31536000 ? "%b %d %H:%M" : "%b %d  %Y";
+      strftime (date, sizeof (date), t_fmt,
+                localtime (&folder->ff->st->st_mtime));
+      mutt_format_s (dest, destlen, fmt, date);
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  case 'f':
+    {
+      const char *s;
+
+      if (folder->ff->imap)
+        s = NONULL(folder->ff->desc);
+      else
+        s = NONULL(folder->ff->name);
+
+      snprintf (fn, sizeof (fn), "%s%s", s,
+                folder->ff->st ? (S_ISLNK (folder->ff->st->st_mode) ? "@" :
+                                  (S_ISDIR (folder->ff->st->st_mode) ? "/" :
+                                   ((folder->ff->st->st_mode & S_IXUSR) !=
+                                    0 ? "*" : ""))) : "");
+
+      mutt_format_s (dest, destlen, fmt, fn);
+      break;
+    }
+  case 'F':
+    if (folder->ff->st != NULL) {
+      snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
+                S_ISDIR(folder->ff->st-> st_mode)
+                ? 'd' : (S_ISLNK(folder->ff->st-> st_mode) ? 'l' : '-'),
+                (folder->ff->st->st_mode & S_IRUSR) != 0 ? 'r' : '-',
+                (folder->ff->st->st_mode & S_IWUSR) != 0 ? 'w' : '-',
+                (folder->ff->st->st_mode & S_ISUID) !=
+                0 ? 's' : (folder->ff->st->st_mode & S_IXUSR) !=
+                0 ? 'x' : '-',
+                (folder->ff->st->st_mode & S_IRGRP) != 0 ? 'r' : '-',
+                (folder->ff->st->st_mode & S_IWGRP) != 0 ? 'w' : '-',
+                (folder->ff->st->st_mode & S_ISGID) !=
+                0 ? 's' : (folder->ff->st->st_mode & S_IXGRP) !=
+                0 ? 'x' : '-',
+                (folder->ff->st->st_mode & S_IROTH) != 0 ? 'r' : '-',
+                (folder->ff->st->st_mode & S_IWOTH) != 0 ? 'w' : '-',
+                (folder->ff->st->st_mode & S_ISVTX) !=
+                0 ? 't' : (folder->ff->st->st_mode & S_IXOTH) !=
+                0 ? 'x' : '-');
+      mutt_format_s (dest, destlen, fmt, permission);
+    }
+    else if (folder->ff->imap) {
+      /* mark folders with subfolders AND mail */
+      snprintf (permission, sizeof (permission), "IMAP %c",
+                (folder->ff->inferiors
+                 && folder->ff->selectable) ? '+' : ' ');
+      mutt_format_s (dest, destlen, fmt, permission);
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  case 'g':
+    if (folder->ff->st != NULL) {
+      if ((gr = getgrgid (folder->ff->st->st_gid)))
+        mutt_format_s (dest, destlen, fmt, gr->gr_name);
+      else {
+        snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
+        snprintf (dest, destlen, tmp, folder->ff->st->st_gid);
+      }
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  case 'l':
+    if (folder->ff->st != NULL) {
+      snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
+      snprintf (dest, destlen, tmp, folder->ff->st->st_nlink);
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  case 'N':
+    if (imap_is_magic (folder->ff->desc, NULL) == M_IMAP) {
+      if (!optional) {
+        snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
+        snprintf (dest, destlen, tmp, folder->ff->new);
+      }
+      else if (!folder->ff->new)
+        optional = 0;
+      break;
+    }
+    snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
+    snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
+    break;
+
+  case 's':
+    if (folder->ff->st != NULL) {
+      snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
+      snprintf (dest, destlen, tmp, (long) folder->ff->st->st_size);
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  case 't':
+    snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
+    snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
+    break;
+
+  case 'u':
+    if (folder->ff->st != NULL) {
+      if ((pw = getpwuid (folder->ff->st->st_uid)))
+        mutt_format_s (dest, destlen, fmt, pw->pw_name);
+      else {
+        snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
+        snprintf (dest, destlen, tmp, folder->ff->st->st_uid);
+      }
+    }
+    else
+      mutt_format_s (dest, destlen, fmt, "");
+    break;
+
+  default:
+    snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
+    snprintf (dest, destlen, tmp, op);
+    break;
+  }
+
+
+  if (flags & M_FORMAT_OPTIONAL)
+    m_strformat(dest, destlen, 0, optional ? ifstr : elstr,
+                folder_format_str, data, 0);
+
+  return src;
+}
+
+
+static void add_folder (MUTTMENU * m, struct browser_state *state,
+                        const char *name, const struct stat *s, int new)
+{
+  if (state->entrylen == state->entrymax) {
+    /* need to allocate more space */
+    p_realloc(&state->entry, state->entrymax += 256);
+    p_clear(&state->entry[state->entrylen], 256);
+    if (m)
+      m->data = state->entry;
+  }
+
+  if (s != NULL) {
+    (state->entry)[state->entrylen].mode  = s->st_mode;
+    (state->entry)[state->entrylen].mtime = s->st_mtime;
+    (state->entry)[state->entrylen].size  = s->st_size;
+    (state->entry)[state->entrylen].st    = p_dup(s, 1);
+  }
+
+  (state->entry)[state->entrylen].new = new;
+  (state->entry)[state->entrylen].name = m_strdup(name);
+  (state->entry)[state->entrylen].desc = m_strdup(name);
+  (state->entry)[state->entrylen].imap = 0;
+  (state->entrylen)++;
+}
+
+static void init_state (struct browser_state *state, MUTTMENU * menu)
+{
+  state->entrylen = 0;
+  state->entrymax = 256;
+  state->entry = p_new(struct folder_file, state->entrymax);
+  state->imap_browse = 0;
+  if (menu)
+    menu->data = state->entry;
+}
+
+/* get list of all files/newsgroups with mask */
+static int examine_directory (MUTTMENU * menu, struct browser_state *state,
+                              char *d, const char *prefix)
+{
+  struct stat s;
+  DIR *dp;
+  struct dirent *de;
+  char buffer[_POSIX_PATH_MAX + STRING];
+  int i = -1;
+
+  while (stat (d, &s) == -1) {
+    if (errno == ENOENT) {
+      /* The last used directory is deleted, try to use the parent dir. */
+      char *c = strrchr (d, '/');
+
+      if (c && (c > d)) {
+        *c = 0;
+        continue;
+      }
+    }
+    mutt_perror (d);
+    return (-1);
+  }
+
+  if (!S_ISDIR (s.st_mode)) {
+    mutt_error (_("%s is not a directory."), d);
+    return (-1);
+  }
+
+  buffy_check (0);
+
+  if ((dp = opendir (d)) == NULL) {
+    mutt_perror (d);
+    return (-1);
+  }
+
+  init_state (state, menu);
+
+  while ((de = readdir (dp)) != NULL) {
+    if (m_strcmp(de->d_name, ".") == 0)
+      continue;               /* we don't need . */
+
+    if (m_strncmp(prefix, de->d_name, m_strlen(prefix)) != 0)
+      continue;
+    if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.neg))
+      continue;
+
+    mutt_concat_path(buffer, sizeof(buffer), d, de->d_name);
+    if (lstat (buffer, &s) == -1)
+      continue;
+
+    if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
+        (!S_ISLNK (s.st_mode)))
+      continue;
+
+    i = buffy_lookup (buffer);
+    add_folder (menu, state, de->d_name, &s, i >= 0 ? Incoming.arr[i]->new : 0);
+  }
+  closedir (dp);
+  sidebar_draw ();
+  browser_sort (state);
+  return 0;
+}
+
+/* get list of mailboxes/subscribed newsgroups */
+static int examine_mailboxes (MUTTMENU * menu, struct browser_state *state)
+{
+  struct stat s;
+  char buffer[LONG_STRING];
+  int i = 0;
+  BUFFY* tmp;
+
+  if (!Incoming.len)
+    return (-1);
+  buffy_check (0);
+
+  init_state (state, menu);
+
+  for (i = 0; i < Incoming.len; i++) {
+    tmp = Incoming.arr[i];
+    tmp->magic = mx_get_magic (tmp->path);
+    if (tmp->magic == M_IMAP) {
+      add_folder (menu, state, tmp->path, NULL, tmp->new);
+      continue;
+    }
+    if (tmp->magic == M_POP) {
+      add_folder (menu, state, tmp->path, NULL, tmp->new);
+      continue;
+    }
+    if (lstat (tmp->path, &s) == -1)
+      continue;
+
+    if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
+        (!S_ISLNK (s.st_mode)))
+      continue;
+
+    m_strcpy(buffer, sizeof(buffer), NONULL(tmp->path));
+    mutt_pretty_mailbox (buffer);
+    add_folder (menu, state, buffer, &s, tmp->new);
+  }
+
+  browser_sort (state);
+  return 0;
+}
+
+static int select_file_search (MUTTMENU * menu, regex_t * re, int n)
+{
+  return (regexec
+          (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
+}
+
+static void folder_entry (char *s, ssize_t slen, MUTTMENU * menu, int num)
+{
+  FOLDER folder;
+
+  folder.ff = &((struct folder_file *) menu->data)[num];
+  folder.num = num;
+
+  m_strformat(s, slen, getmaxx(main_w), FolderFormat, folder_format_str,
+              &folder, 0);
+}
+
+static void init_menu (struct browser_state *state, MUTTMENU * menu,
+                       char *title, ssize_t titlelen, int buffy)
+{
+  char path[_POSIX_PATH_MAX];
+
+  menu->max = state->entrylen;
+
+  if (menu->current >= menu->max)
+    menu->current = menu->max - 1;
+  if (menu->current < 0)
+    menu->current = 0;
+  if (menu->top > menu->current)
+    menu->top = 0;
+
+  menu->tagged = 0;
+
+  if (buffy)
+    snprintf(title, titlelen, _("Mailboxes [%d]"), buffy_check(0));
+  else {
+    m_strcpy(path, sizeof(path), LastDir);
+    mutt_pretty_mailbox (path);
+    if (state->imap_browse && option (OPTIMAPLSUB))
+      snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
+                path, NONULL (Mask.pattern));
+    else
+      snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
+                path, NONULL (Mask.pattern));
+  }
+  menu->redraw = REDRAW_FULL;
+}
+
+static int file_tag (MUTTMENU * menu, int n, int m)
+{
+  struct folder_file *ff = &(((struct folder_file *) menu->data)[n]);
+  int ot;
+
+  if (S_ISDIR (ff->mode)
+      || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name))) {
+    mutt_error _("Can't attach a directory!");
+
+    return 0;
+  }
+
+  ot = ff->tagged;
+  ff->tagged = (m >= 0 ? m : !ff->tagged);
+
+  return ff->tagged - ot;
+}
+
+void mutt_select_file (char *f, ssize_t flen, int flags, char ***files,
+                       int *numfiles)
+{
+  char buf[_POSIX_PATH_MAX];
+  char prefix[_POSIX_PATH_MAX] = "";
+  char title[STRING];
+  struct browser_state state;
+  MUTTMENU *menu;
+  struct stat st;
+  int i, killPrefix = 0;
+  int multiple = (flags & M_SEL_MULTI) ? 1 : 0;
+  int folder = (flags & M_SEL_FOLDER) ? 1 : 0;
+  int buffy = (flags & M_SEL_BUFFY) ? 1 : 0;
+
+  buffy = buffy && folder;
+
+  p_clear(&state, 1);
+
+  if (!folder)
+    m_strcpy(LastDirBackup, sizeof(LastDirBackup), LastDir);
+
+  if (*f) {
+    mutt_expand_path (f, flen);
+    if (imap_is_magic (f, NULL) == M_IMAP) {
+      init_state (&state, NULL);
+      state.imap_browse = 1;
+      if (!imap_browse (f, &state))
+        m_strcpy(LastDir, sizeof(LastDir), state.folder);
+    }
+    else {
+      for (i = m_strlen(f) - 1; i > 0 && f[i] != '/'; i--);
+      if (i > 0) {
+        if (f[0] == '/') {
+          i = MIN(ssizeof(LastDir) - 1, i);
+          m_strcpy(LastDir, sizeof(LastDir), f);
+        }
+        else {
+          getcwd(LastDir, sizeof(LastDir));
+          m_strcat(LastDir, sizeof(LastDir), "/");
+          m_strncat(LastDir, sizeof(LastDir), f, i);
+        }
+      }
+      else {
+        if (f[0] == '/')
+          m_strcpy(LastDir, sizeof(LastDir), "/");
+        else
+          getcwd (LastDir, sizeof (LastDir));
+      }
+
+      if (i <= 0 && f[0] != '/')
+        m_strcpy(prefix, sizeof(prefix), f);
+      else
+        m_strcpy(prefix, sizeof(prefix), f + i + 1);
+      killPrefix = 1;
+    }
+  }
+  else {
+    if (!folder)
+      getcwd (LastDir, sizeof (LastDir));
+    else if (!LastDir[0])
+      m_strcpy(LastDir, sizeof(LastDir), NONULL(Maildir));
+
+    if (!buffy && imap_is_magic (LastDir, NULL) == M_IMAP) {
+      init_state (&state, NULL);
+      state.imap_browse = 1;
+      imap_browse (LastDir, &state);
+      browser_sort (&state);
+    }
+  }
+
+  *f = 0;
+
+  if (buffy) {
+    if (examine_mailboxes (NULL, &state) == -1)
+      goto bail;
+  }
+  else
+  if (!state.imap_browse)
+    if (examine_directory (NULL, &state, LastDir, prefix) == -1)
+      goto bail;
+
+  menu = mutt_new_menu ();
+  menu->menu = MENU_FOLDER;
+  menu->make_entry = folder_entry;
+  menu->search = select_file_search;
+  menu->title = title;
+  menu->data = state.entry;
+  if (multiple)
+    menu->tag = file_tag;
+  init_menu (&state, menu, title, sizeof (title), buffy);
+
+  for (;;) {
+    switch (i = mutt_menuLoop (menu)) {
+    case OP_GENERIC_SELECT_ENTRY:
+
+      if (!state.entrylen) {
+        mutt_error _("No files match the file mask");
+
+        break;
+      }
+
+      if (S_ISDIR (state.entry[menu->current].mode) ||
+          (S_ISLNK (state.entry[menu->current].mode) &&
+           link_is_dir (LastDir, state.entry[menu->current].name))
+          || state.entry[menu->current].inferiors
+        ) {
+        /* make sure this isn't a MH or maildir mailbox */
+        if (buffy) {
+          m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
+          mutt_expand_path (buf, sizeof (buf));
+        }
+        else if (state.imap_browse) {
+          m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
+        }
+        else
+          mutt_concat_path(buf, sizeof(buf), LastDir,
+                           state.entry[menu->current].name);
+
+        if ((mx_get_magic (buf) <= 0)
+            || state.entry[menu->current].inferiors)
+        {
+          char OldLastDir[_POSIX_PATH_MAX];
+
+          /* save the old directory */
+          m_strcpy(OldLastDir, sizeof(OldLastDir), LastDir);
+
+          if (m_strcmp(state.entry[menu->current].name, "..") == 0) {
+            if (m_strcmp("..", LastDir + m_strlen(LastDir) - 2) == 0)
+              m_strcat(LastDir, sizeof(LastDir), "/..");
+            else {
+              char *p = strrchr (LastDir + 1, '/');
+
+              if (p)
+                *p = 0;
+              else {
+                if (LastDir[0] == '/')
+                  LastDir[1] = 0;
+                else
+                  m_strcat(LastDir, sizeof(LastDir), "/..");
+              }
+            }
+          }
+          else if (buffy) {
+            m_strcpy(LastDir, sizeof(LastDir),
+                     state.entry[menu->current].name);
+            mutt_expand_path (LastDir, sizeof (LastDir));
+          }
+          else if (state.imap_browse) {
+            int n;
+            ciss_url_t url;
+
+            m_strcpy(LastDir, sizeof(LastDir),
+                     state.entry[menu->current].name);
+            /* tack on delimiter here */
+            n = m_strlen(LastDir) + 1;
+
+            /* special case "" needs no delimiter */
+            url_parse_ciss (&url, state.entry[menu->current].name);
+            if (url.path &&
+                (state.entry[menu->current].delim != '\0') &&
+                (n < ssizeof (LastDir))) {
+              LastDir[n] = '\0';
+              LastDir[n - 1] = state.entry[menu->current].delim;
+            }
+          }
+          else {
+            char tmp[_POSIX_PATH_MAX];
+
+            mutt_concat_path(tmp, sizeof(tmp), LastDir,
+                             state.entry[menu->current].name);
+            m_strcpy(LastDir, sizeof(LastDir), tmp);
+          }
+
+          destroy_state (&state);
+          if (killPrefix) {
+            prefix[0] = 0;
+            killPrefix = 0;
+          }
+          buffy = 0;
+          if (state.imap_browse) {
+            init_state (&state, NULL);
+            state.imap_browse = 1;
+            imap_browse (LastDir, &state);
+            browser_sort (&state);
+            menu->data = state.entry;
+          }
+          else
+          if (examine_directory (menu, &state, LastDir, prefix) == -1) {
+            /* try to restore the old values */
+            m_strcpy(LastDir, sizeof(LastDir), OldLastDir);
+            if (examine_directory (menu, &state, LastDir, prefix) == -1) {
+              m_strcpy(LastDir, sizeof(LastDir), NONULL(mod_core.homedir));
+              goto bail;
+            }
+          }
+          menu->current = 0;
+          menu->top = 0;
+          init_menu (&state, menu, title, sizeof (title), buffy);
+          break;
+        }
+      }
+
+      if (buffy) {
+        m_strcpy(f, flen, state.entry[menu->current].name);
+        mutt_expand_path (f, flen);
+      }
+      else if (state.imap_browse)
+        m_strcpy(f, flen, state.entry[menu->current].name);
+      else
+        mutt_concat_path(f, flen, LastDir, state.entry[menu->current].name);
+
+      /* Fall through to OP_EXIT */
+
+    case OP_EXIT:
+
+      if (multiple) {
+        char **tfiles;
+        int j;
+        int h;
+
+        if (menu->tagged) {
+          *numfiles = menu->tagged;
+          tfiles = p_new(char *, *numfiles);
+          for (h = 0, j = 0; h < state.entrylen; i++) {
+            struct folder_file ff = state.entry[i];
+            char full[_POSIX_PATH_MAX];
+
+            if (ff.tagged) {
+              mutt_concat_path(full, sizeof(full), LastDir, ff.name);
+              mutt_expand_path (full, sizeof (full));
+              tfiles[j++] = m_strdup(full);
+            }
+          }
+          *files = tfiles;
+        }
+        else if (f[0]) {        /* no tagged entries. return selected entry */
+          *numfiles = 1;
+          tfiles = p_new(char *, *numfiles);
+          mutt_expand_path (f, flen);
+          tfiles[0] = m_strdup(f);
+          *files = tfiles;
+        }
+      }
+
+      destroy_state (&state);
+      mutt_menuDestroy (&menu);
+      goto bail;
+
+    case OP_BROWSER_TELL:
+      if (state.entrylen)
+        mutt_message ("%s", state.entry[menu->current].name);
+      break;
+
+    case OP_BROWSER_TOGGLE_LSUB:
+      if (option (OPTIMAPLSUB)) {
+        unset_option (OPTIMAPLSUB);
+      }
+      else {
+        set_option (OPTIMAPLSUB);
+      }
+      mutt_ungetch (0, OP_CHECK_NEW);
+      break;
+
+    case OP_CREATE_MAILBOX:
+      if (!state.imap_browse)
+        mutt_error (_("Create is only supported for IMAP mailboxes"));
+      else {
+        imap_mailbox_create (LastDir);
+        /* TODO: find a way to detect if the new folder would appear in
+         *   this window, and insert it without starting over. */
+        destroy_state (&state);
+        init_state (&state, NULL);
+        state.imap_browse = 1;
+        imap_browse (LastDir, &state);
+        browser_sort (&state);
+        menu->data = state.entry;
+        menu->current = 0;
+        menu->top = 0;
+        init_menu (&state, menu, title, sizeof (title), buffy);
+        MAYBE_REDRAW (menu->redraw);
+      }
+      break;
+
+    case OP_RENAME_MAILBOX:
+      if (!state.entry[menu->current].imap)
+        mutt_error (_("Rename is only supported for IMAP mailboxes"));
+      else {
+        int nentry = menu->current;
+
+        if (imap_mailbox_rename (state.entry[nentry].name) >= 0) {
+          destroy_state (&state);
+          init_state (&state, NULL);
+          state.imap_browse = 1;
+          imap_browse (LastDir, &state);
+          browser_sort (&state);
+          menu->data = state.entry;
+          menu->current = 0;
+          menu->top = 0;
+          init_menu (&state, menu, title, sizeof (title), buffy);
+          MAYBE_REDRAW (menu->redraw);
+        }
+      }
+      break;
+
+    case OP_DELETE_MAILBOX:
+      if (!state.entry[menu->current].imap)
+        mutt_error (_("Delete is only supported for IMAP mailboxes"));
+      else {
+        char msg[STRING];
+        IMAP_MBOX mx;
+        int nentry = menu->current;
+
+        imap_parse_path (state.entry[nentry].name, &mx);
+        snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
+                  mx.mbox);
+        if (mutt_yesorno (msg, M_NO) == M_YES) {
+          if (!imap_delete_mailbox (Context, mx)) {
+            /* free the mailbox from the browser */
+            p_delete(&((state.entry)[nentry].name));
+            p_delete(&((state.entry)[nentry].desc));
+            /* and move all other entries up */
+            if (nentry + 1 < state.entrylen)
+              memmove (state.entry + nentry, state.entry + nentry + 1,
+                       sizeof (struct folder_file) * (state.entrylen -
+                                                      (nentry + 1)));
+            state.entrylen--;
+            mutt_message _("Mailbox deleted.");
+
+            init_menu (&state, menu, title, sizeof (title), buffy);
+            MAYBE_REDRAW (menu->redraw);
+          }
+        }
+        else
+          mutt_message _("Mailbox not deleted.");
+        p_delete(&mx.mbox);
+      }
+      break;
+
+    case OP_CHANGE_DIRECTORY:
+      m_strcpy(buf, sizeof(buf), LastDir);
+      if (!state.imap_browse)
+      {
+        /* add '/' at the end of the directory name if not already there */
+        ssize_t len = m_strlen(LastDir);
+
+        if (len && LastDir[len - 1] != '/' && ssizeof(buf) > len)
+          buf[len] = '/';
+      }
+
+      if (mutt_get_field (_("Chdir to: "), buf, sizeof (buf), M_FILE) == 0 &&
+          buf[0]) {
+        buffy = 0;
+        mutt_expand_path (buf, sizeof (buf));
+        if (imap_is_magic (buf, NULL) == M_IMAP) {
+          m_strcpy(LastDir, sizeof(LastDir), buf);
+          destroy_state (&state);
+          init_state (&state, NULL);
+          state.imap_browse = 1;
+          imap_browse (LastDir, &state);
+          browser_sort (&state);
+          menu->data = state.entry;
+          menu->current = 0;
+          menu->top = 0;
+          init_menu (&state, menu, title, sizeof (title), buffy);
+        }
+        else
+        if (stat (buf, &st) == 0) {
+          if (S_ISDIR (st.st_mode)) {
+            destroy_state (&state);
+            if (examine_directory (menu, &state, buf, prefix) == 0)
+              m_strcpy(LastDir, sizeof(LastDir), buf);
+            else {
+              mutt_error _("Error scanning directory.");
+
+              if (examine_directory (menu, &state, LastDir, prefix) == -1) {
+                mutt_menuDestroy (&menu);
+                goto bail;
+              }
+            }
+            menu->current = 0;
+            menu->top = 0;
+            init_menu (&state, menu, title, sizeof (title), buffy);
+          }
+          else
+            mutt_error (_("%s is not a directory."), buf);
+        }
+        else
+          mutt_perror (buf);
+      }
+      MAYBE_REDRAW (menu->redraw);
+      break;
+
+    case OP_ENTER_MASK:
+
+      m_strcpy(buf, sizeof(buf), NONULL(Mask.pattern));
+      if (mutt_get_field (_("File Mask: "), buf, sizeof (buf), 0) == 0) {
+        regex_t *rx = p_new(regex_t, 1);
+        char *s = buf;
+        int neg = 0, err;
+
+        buffy = 0;
+        /* assume that the user wants to see everything */
+        if (!buf[0])
+          m_strcpy(buf, sizeof(buf), ".");
+        s = vskipspaces(s);
+        if (*s == '!') {
+          s = vskipspaces(s + 1);
+          neg = 1;
+        }
+
+        if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
+          regerror (err, rx, buf, sizeof (buf));
+          regfree (rx);
+          p_delete(&rx);
+          mutt_error ("%s", buf);
+        }
+        else {
+          m_strreplace(&Mask.pattern, buf);
+          regfree (Mask.rx);
+          p_delete(&Mask.rx);
+          Mask.rx = rx;
+          Mask.neg = neg;
+
+          destroy_state (&state);
+          if (state.imap_browse) {
+            init_state (&state, NULL);
+            state.imap_browse = 1;
+            imap_browse (LastDir, &state);
+            browser_sort (&state);
+            menu->data = state.entry;
+            init_menu (&state, menu, title, sizeof (title), buffy);
+          }
+          else
+          if (examine_directory (menu, &state, LastDir, NULL) == 0)
+            init_menu (&state, menu, title, sizeof (title), buffy);
+          else {
+            mutt_error _("Error scanning directory.");
+
+            mutt_menuDestroy (&menu);
+            goto bail;
+          }
+          killPrefix = 0;
+          if (!state.entrylen) {
+            mutt_error _("No files match the file mask");
+
+            break;
+          }
+        }
+      }
+      MAYBE_REDRAW (menu->redraw);
+      break;
+
+    case OP_SORT:
+    case OP_SORT_REVERSE:
+
+      {
+        int resort = 1;
+        int reverse = (i == OP_SORT_REVERSE);
+
+        switch (mutt_multi_choice ((reverse) ?
+                                   _
+                                   ("Reverse sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? ")
+                                   :
+                                   _
+                                   ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "),
+                                   _("dazn"))) {
+        case -1:               /* abort */
+          resort = 0;
+          break;
+
+        case 1:                /* (d)ate */
+          BrowserSort = SORT_DATE;
+          break;
+
+        case 2:                /* (a)lpha */
+          BrowserSort = SORT_SUBJECT;
+          break;
+
+        case 3:                /* si(z)e */
+          BrowserSort = SORT_SIZE;
+          break;
+
+        case 4:                /* do(n)'t sort */
+          BrowserSort = SORT_ORDER;
+          resort = 0;
+          break;
+        }
+        if (resort) {
+          BrowserSort |= reverse ? SORT_REVERSE : 0;
+          browser_sort (&state);
+          menu->redraw = REDRAW_FULL;
+        }
+        break;
+      }
+
+    case OP_TOGGLE_MAILBOXES:
+      buffy = 1 - buffy;
+
+    case OP_CHECK_NEW:
+      destroy_state (&state);
+      prefix[0] = 0;
+      killPrefix = 0;
+
+      if (buffy) {
+        if (examine_mailboxes (menu, &state) == -1)
+          goto bail;
+      }
+      else if (imap_is_magic (LastDir, NULL) == M_IMAP) {
+        init_state (&state, NULL);
+        state.imap_browse = 1;
+        imap_browse (LastDir, &state);
+        browser_sort (&state);
+        menu->data = state.entry;
+      }
+      else if (examine_directory (menu, &state, LastDir, prefix) == -1)
+        goto bail;
+      init_menu (&state, menu, title, sizeof (title), buffy);
+      break;
+
+    case OP_BUFFY_LIST:
+      if (option (OPTFORCEBUFFYCHECK))
+        buffy_check (1);
+      buffy_list ();
+      break;
+
+    case OP_BROWSER_NEW_FILE:
+
+      snprintf (buf, sizeof (buf), "%s/", LastDir);
+      if (mutt_get_field (_("New file name: "), buf, sizeof (buf), M_FILE) ==
+          0) {
+        m_strcpy(f, flen, buf);
+        destroy_state (&state);
+        mutt_menuDestroy (&menu);
+        goto bail;
+      }
+      MAYBE_REDRAW (menu->redraw);
+      break;
+
+    case OP_BROWSER_VIEW_FILE:
+      if (!state.entrylen) {
+        mutt_error _("No files match the file mask");
+
+        break;
+      }
+
+      if (state.entry[menu->current].selectable) {
+        m_strcpy(f, flen, state.entry[menu->current].name);
+        destroy_state (&state);
+        mutt_menuDestroy (&menu);
+        goto bail;
+      }
+      else
+      if (S_ISDIR (state.entry[menu->current].mode) ||
+            (S_ISLNK (state.entry[menu->current].mode) &&
+               link_is_dir (LastDir, state.entry[menu->current].name))) {
+        mutt_error _("Can't view a directory");
+
+        break;
+      }
+      else {
+        BODY *b;
+        char nbuf[_POSIX_PATH_MAX];
+
+        mutt_concat_path(nbuf, sizeof(nbuf), LastDir,
+                         state.entry[menu->current].name);
+        b = mutt_make_file_attach (nbuf);
+        if (b != NULL) {
+          mutt_view_attachment (NULL, b, M_REGULAR, NULL, NULL, 0);
+          body_list_wipe(&b);
+          menu->redraw = REDRAW_FULL;
+        }
+        else
+          mutt_error _("Error trying to view file");
+      }
+      break;
+
+    case OP_BROWSER_SUBSCRIBE:
+    case OP_BROWSER_UNSUBSCRIBE:
+      if (i == OP_BROWSER_SUBSCRIBE)
+        imap_subscribe (state.entry[menu->current].name, 1);
+      else
+        imap_subscribe (state.entry[menu->current].name, 0);
+    }
+  }
+
+bail:
+
+  if (!folder)
+    m_strcpy(LastDir, sizeof(LastDir), LastDirBackup);
+
+}