Assume default colors are white on black for xterm, and use it if available.
[apps/madmutt.git] / lib-ui / browser.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-ui/lib-ui.h>
11
12 #include <dirent.h>
13 #include <pwd.h>
14 #include <grp.h>
15
16 #include <lib-mx/mx.h>
17 #include "menu.h"
18 #include "browser.h"
19
20 #include "mutt.h"
21 #include "buffy.h"
22 #include "sort.h"
23 #include "attach.h"
24
25 #include <imap/imap.h>
26
27 typedef struct folder_t {
28   struct folder_file *ff;
29   int num;
30 } FOLDER;
31
32 static char LastDir[_POSIX_PATH_MAX] = "";
33 static char LastDirBackup[_POSIX_PATH_MAX] = "";
34
35 /* Frees up the memory allocated for the local-global variables.  */
36 static void destroy_state (struct browser_state *state)
37 {
38   int c;
39
40   for (c = 0; c < state->entrylen; c++) {
41     p_delete(&state->entry[c].name);
42     p_delete(&state->entry[c].desc);
43     p_delete(&state->entry[c].st);
44   }
45   p_delete(&state->folder);
46   p_delete(&state->entry);
47 }
48
49 static int browser_compare_subject (const void *a, const void *b)
50 {
51   struct folder_file *pa = (struct folder_file *) a;
52   struct folder_file *pb = (struct folder_file *) b;
53
54   int r = strcoll(NONULL(pa->name), NONULL(pb->name));
55
56   return ((BrowserSort & SORT_REVERSE) ? -r : r);
57 }
58
59 static int browser_compare_date (const void *a, const void *b)
60 {
61   struct folder_file *pa = (struct folder_file *) a;
62   struct folder_file *pb = (struct folder_file *) b;
63
64   int r = pa->mtime - pb->mtime;
65
66   return ((BrowserSort & SORT_REVERSE) ? -r : r);
67 }
68
69 static int browser_compare_size (const void *a, const void *b)
70 {
71   struct folder_file *pa = (struct folder_file *) a;
72   struct folder_file *pb = (struct folder_file *) b;
73
74   int r = pa->size - pb->size;
75
76   return ((BrowserSort & SORT_REVERSE) ? -r : r);
77 }
78
79 static void browser_sort (struct browser_state *state)
80 {
81   int (*f) (const void *, const void *);
82
83   switch (BrowserSort & SORT_MASK) {
84   case SORT_ORDER:
85     return;
86   case SORT_DATE:
87     f = browser_compare_date;
88     break;
89   case SORT_SIZE:
90     f = browser_compare_size;
91     break;
92   case SORT_SUBJECT:
93   default:
94     f = browser_compare_subject;
95     break;
96   }
97   qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
98 }
99
100 static int link_is_dir (const char *folder, const char *path)
101 {
102   struct stat st;
103   char fullpath[_POSIX_PATH_MAX];
104
105   mutt_concat_path(fullpath, sizeof(fullpath), folder, path);
106
107   if (stat (fullpath, &st) == 0)
108     return (S_ISDIR (st.st_mode));
109   else
110     return 0;
111 }
112
113 static const char *
114 folder_format_str(char *dest, ssize_t destlen, char op,
115                   const char *src, const char *fmt,
116                   const char *ifstr, const char *elstr,
117                   anytype data, format_flag flags)
118 {
119   char fn[STRING], tmp[STRING], permission[11], date[16];
120   const char *t_fmt;
121   time_t tnow;
122   FOLDER *folder = data.ptr;
123   struct passwd *pw;
124   struct group *gr;
125   int optional = (flags & M_FORMAT_OPTIONAL);
126
127   switch (op) {
128   case 'C':
129     snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
130     snprintf (dest, destlen, tmp, folder->num + 1);
131     break;
132
133   case 'd':
134     if (folder->ff->st != NULL) {
135       tnow = time (NULL);
136       t_fmt =
137         tnow - folder->ff->st->st_mtime <
138         31536000 ? "%b %d %H:%M" : "%b %d  %Y";
139       strftime (date, sizeof (date), t_fmt,
140                 localtime (&folder->ff->st->st_mtime));
141       mutt_format_s (dest, destlen, fmt, date);
142     }
143     else
144       mutt_format_s (dest, destlen, fmt, "");
145     break;
146
147   case 'f':
148     {
149       const char *s;
150
151       if (folder->ff->imap)
152         s = NONULL(folder->ff->desc);
153       else
154         s = NONULL(folder->ff->name);
155
156       snprintf (fn, sizeof (fn), "%s%s", s,
157                 folder->ff->st ? (S_ISLNK (folder->ff->st->st_mode) ? "@" :
158                                   (S_ISDIR (folder->ff->st->st_mode) ? "/" :
159                                    ((folder->ff->st->st_mode & S_IXUSR) !=
160                                     0 ? "*" : ""))) : "");
161
162       mutt_format_s (dest, destlen, fmt, fn);
163       break;
164     }
165   case 'F':
166     if (folder->ff->st != NULL) {
167       snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
168                 S_ISDIR(folder->ff->st-> st_mode)
169                 ? 'd' : (S_ISLNK(folder->ff->st-> st_mode) ? 'l' : '-'),
170                 (folder->ff->st->st_mode & S_IRUSR) != 0 ? 'r' : '-',
171                 (folder->ff->st->st_mode & S_IWUSR) != 0 ? 'w' : '-',
172                 (folder->ff->st->st_mode & S_ISUID) !=
173                 0 ? 's' : (folder->ff->st->st_mode & S_IXUSR) !=
174                 0 ? 'x' : '-',
175                 (folder->ff->st->st_mode & S_IRGRP) != 0 ? 'r' : '-',
176                 (folder->ff->st->st_mode & S_IWGRP) != 0 ? 'w' : '-',
177                 (folder->ff->st->st_mode & S_ISGID) !=
178                 0 ? 's' : (folder->ff->st->st_mode & S_IXGRP) !=
179                 0 ? 'x' : '-',
180                 (folder->ff->st->st_mode & S_IROTH) != 0 ? 'r' : '-',
181                 (folder->ff->st->st_mode & S_IWOTH) != 0 ? 'w' : '-',
182                 (folder->ff->st->st_mode & S_ISVTX) !=
183                 0 ? 't' : (folder->ff->st->st_mode & S_IXOTH) !=
184                 0 ? 'x' : '-');
185       mutt_format_s (dest, destlen, fmt, permission);
186     }
187     else if (folder->ff->imap) {
188       /* mark folders with subfolders AND mail */
189       snprintf (permission, sizeof (permission), "IMAP %c",
190                 (folder->ff->inferiors
191                  && folder->ff->selectable) ? '+' : ' ');
192       mutt_format_s (dest, destlen, fmt, permission);
193     }
194     else
195       mutt_format_s (dest, destlen, fmt, "");
196     break;
197
198   case 'g':
199     if (folder->ff->st != NULL) {
200       if ((gr = getgrgid (folder->ff->st->st_gid)))
201         mutt_format_s (dest, destlen, fmt, gr->gr_name);
202       else {
203         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
204         snprintf (dest, destlen, tmp, folder->ff->st->st_gid);
205       }
206     }
207     else
208       mutt_format_s (dest, destlen, fmt, "");
209     break;
210
211   case 'l':
212     if (folder->ff->st != NULL) {
213       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
214       snprintf (dest, destlen, tmp, folder->ff->st->st_nlink);
215     }
216     else
217       mutt_format_s (dest, destlen, fmt, "");
218     break;
219
220   case 'N':
221     if (imap_is_magic (folder->ff->desc, NULL) == M_IMAP) {
222       if (!optional) {
223         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
224         snprintf (dest, destlen, tmp, folder->ff->new);
225       }
226       else if (!folder->ff->new)
227         optional = 0;
228       break;
229     }
230     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
231     snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
232     break;
233
234   case 's':
235     if (folder->ff->st != NULL) {
236       snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
237       snprintf (dest, destlen, tmp, (long) folder->ff->st->st_size);
238     }
239     else
240       mutt_format_s (dest, destlen, fmt, "");
241     break;
242
243   case 't':
244     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
245     snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
246     break;
247
248   case 'u':
249     if (folder->ff->st != NULL) {
250       if ((pw = getpwuid (folder->ff->st->st_uid)))
251         mutt_format_s (dest, destlen, fmt, pw->pw_name);
252       else {
253         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
254         snprintf (dest, destlen, tmp, folder->ff->st->st_uid);
255       }
256     }
257     else
258       mutt_format_s (dest, destlen, fmt, "");
259     break;
260
261   default:
262     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
263     snprintf (dest, destlen, tmp, op);
264     break;
265   }
266
267
268   if (flags & M_FORMAT_OPTIONAL)
269     m_strformat(dest, destlen, 0, optional ? ifstr : elstr,
270                 folder_format_str, data, 0);
271
272   return src;
273 }
274
275
276 static void add_folder (MUTTMENU * m, struct browser_state *state,
277                         const char *name, const struct stat *s, int new)
278 {
279   if (state->entrylen == state->entrymax) {
280     /* need to allocate more space */
281     p_realloc(&state->entry, state->entrymax += 256);
282     p_clear(&state->entry[state->entrylen], 256);
283     if (m)
284       m->data = state->entry;
285   }
286
287   if (s != NULL) {
288     (state->entry)[state->entrylen].mode  = s->st_mode;
289     (state->entry)[state->entrylen].mtime = s->st_mtime;
290     (state->entry)[state->entrylen].size  = s->st_size;
291     (state->entry)[state->entrylen].st    = p_dup(s, 1);
292   }
293
294   (state->entry)[state->entrylen].new = new;
295   (state->entry)[state->entrylen].name = m_strdup(name);
296   (state->entry)[state->entrylen].desc = m_strdup(name);
297   (state->entry)[state->entrylen].imap = 0;
298   (state->entrylen)++;
299 }
300
301 static void init_state (struct browser_state *state, MUTTMENU * menu)
302 {
303   state->entrylen = 0;
304   state->entrymax = 256;
305   state->entry = p_new(struct folder_file, state->entrymax);
306   state->imap_browse = 0;
307   if (menu)
308     menu->data = state->entry;
309 }
310
311 /* get list of all files/newsgroups with mask */
312 static int examine_directory (MUTTMENU * menu, struct browser_state *state,
313                               char *d, const char *prefix)
314 {
315  
316   struct stat s;
317   DIR *dp;
318   struct dirent *de;
319   char buffer[_POSIX_PATH_MAX + STRING];
320   int i = -1;
321
322   while (stat (d, &s) == -1) {
323     if (errno == ENOENT) {
324       /* The last used directory is deleted, try to use the parent dir. */
325       char *c = strrchr (d, '/');
326
327       if (c && (c > d)) {
328         *c = 0;
329         continue;
330       }
331     }
332     mutt_perror (d);
333     return (-1);
334   }
335
336   if (!S_ISDIR (s.st_mode)) {
337     mutt_error (_("%s is not a directory."), d);
338     return (-1);
339   }
340
341   buffy_check (0);
342
343   if ((dp = opendir (d)) == NULL) {
344     mutt_perror (d);
345     return (-1);
346   }
347
348   init_state (state, menu);
349
350   while ((de = readdir (dp)) != NULL) {
351     if (m_strcmp(de->d_name, ".") == 0)
352       continue;               /* we don't need . */
353
354     if (m_strncmp(prefix, de->d_name, m_strlen(prefix)) != 0)
355       continue;
356     if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.neg))
357       continue;
358
359     mutt_concat_path(buffer, sizeof(buffer), d, de->d_name);
360     if (lstat (buffer, &s) == -1)
361       continue;
362
363     if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
364         (!S_ISLNK (s.st_mode)))
365       continue;
366
367     i = buffy_lookup (buffer);
368     add_folder (menu, state, de->d_name, &s, i >= 0 ? Incoming.arr[i]->new : 0);
369   }
370   closedir (dp);
371   sidebar_draw ();
372   browser_sort (state);
373   return 0;
374 }
375
376 /* get list of mailboxes/subscribed newsgroups */
377 static int examine_mailboxes (MUTTMENU * menu, struct browser_state *state)
378 {
379   struct stat s;
380   char buffer[LONG_STRING];
381   int i = 0;
382   BUFFY* tmp;
383
384   if (!Incoming.len)
385     return (-1);
386   buffy_check (0);
387
388   init_state (state, menu);
389
390   for (i = 0; i < Incoming.len; i++) {
391     tmp = Incoming.arr[i];
392     tmp->magic = mx_get_magic (tmp->path);
393     if (tmp->magic == M_IMAP) {
394       add_folder (menu, state, tmp->path, NULL, tmp->new);
395       continue;
396     }
397     if (tmp->magic == M_POP) {
398       add_folder (menu, state, tmp->path, NULL, tmp->new);
399       continue;
400     }
401     if (lstat (tmp->path, &s) == -1)
402       continue;
403
404     if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
405         (!S_ISLNK (s.st_mode)))
406       continue;
407
408     m_strcpy(buffer, sizeof(buffer), NONULL(tmp->path));
409     mutt_pretty_mailbox (buffer);
410     add_folder (menu, state, buffer, &s, tmp->new);
411   }
412
413   browser_sort (state);
414   return 0;
415 }
416
417 static int select_file_search (MUTTMENU * menu, regex_t * re, int n)
418 {
419   return (regexec
420           (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
421 }
422
423 static void folder_entry (char *s, ssize_t slen, MUTTMENU * menu, int num)
424 {
425   FOLDER folder;
426
427   folder.ff = &((struct folder_file *) menu->data)[num];
428   folder.num = num;
429
430   m_strformat(s, slen, getmaxx(main_w), FolderFormat, folder_format_str,
431               &folder, 0);
432 }
433
434 static void init_menu (struct browser_state *state, MUTTMENU * menu,
435                        char *title, ssize_t titlelen, int buffy)
436 {
437   char path[_POSIX_PATH_MAX];
438
439   menu->max = state->entrylen;
440
441   if (menu->current >= menu->max)
442     menu->current = menu->max - 1;
443   if (menu->current < 0)
444     menu->current = 0;
445   if (menu->top > menu->current)
446     menu->top = 0;
447
448   menu->tagged = 0;
449
450   if (buffy)
451     snprintf(title, titlelen, _("Mailboxes [%d]"), buffy_check(0));
452   else {
453     m_strcpy(path, sizeof(path), LastDir);
454     mutt_pretty_mailbox (path);
455     if (state->imap_browse && option (OPTIMAPLSUB))
456       snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
457                 path, NONULL (Mask.pattern));
458     else
459       snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
460                 path, NONULL (Mask.pattern));
461   }
462   menu->redraw = REDRAW_FULL;
463 }
464
465 static int file_tag (MUTTMENU * menu, int n, int m)
466 {
467   struct folder_file *ff = &(((struct folder_file *) menu->data)[n]);
468   int ot;
469
470   if (S_ISDIR (ff->mode)
471       || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name))) {
472     mutt_error _("Can't attach a directory!");
473
474     return 0;
475   }
476
477   ot = ff->tagged;
478   ff->tagged = (m >= 0 ? m : !ff->tagged);
479
480   return ff->tagged - ot;
481 }
482
483 void mutt_select_file (char *f, ssize_t flen, int flags, char ***files,
484                        int *numfiles)
485 {
486   char buf[_POSIX_PATH_MAX];
487   char prefix[_POSIX_PATH_MAX] = "";
488   char title[STRING];
489   struct browser_state state;
490   MUTTMENU *menu;
491   struct stat st;
492   int i, killPrefix = 0;
493   int multiple = (flags & M_SEL_MULTI) ? 1 : 0;
494   int folder = (flags & M_SEL_FOLDER) ? 1 : 0;
495   int buffy = (flags & M_SEL_BUFFY) ? 1 : 0;
496
497   buffy = buffy && folder;
498
499   p_clear(&state, 1);
500
501   if (!folder)
502     m_strcpy(LastDirBackup, sizeof(LastDirBackup), LastDir);
503
504   if (*f) {
505     mutt_expand_path (f, flen);
506     if (imap_is_magic (f, NULL) == M_IMAP) {
507       init_state (&state, NULL);
508       state.imap_browse = 1;
509       if (!imap_browse (f, &state))
510         m_strcpy(LastDir, sizeof(LastDir), state.folder);
511     }
512     else {
513       for (i = m_strlen(f) - 1; i > 0 && f[i] != '/'; i--);
514       if (i > 0) {
515         if (f[0] == '/') {
516           i = MIN(ssizeof(LastDir) - 1, i);
517           m_strcpy(LastDir, sizeof(LastDir), f);
518         }
519         else {
520           getcwd(LastDir, sizeof(LastDir));
521           m_strcat(LastDir, sizeof(LastDir), "/");
522           m_strncat(LastDir, sizeof(LastDir), f, i);
523         }
524       }
525       else {
526         if (f[0] == '/')
527           m_strcpy(LastDir, sizeof(LastDir), "/");
528         else
529           getcwd (LastDir, sizeof (LastDir));
530       }
531
532       if (i <= 0 && f[0] != '/')
533         m_strcpy(prefix, sizeof(prefix), f);
534       else
535         m_strcpy(prefix, sizeof(prefix), f + i + 1);
536       killPrefix = 1;
537     }
538   }
539   else {
540     if (!folder)
541       getcwd (LastDir, sizeof (LastDir));
542     else if (!LastDir[0])
543       m_strcpy(LastDir, sizeof(LastDir), NONULL(Maildir));
544
545     if (!buffy && imap_is_magic (LastDir, NULL) == M_IMAP) {
546       init_state (&state, NULL);
547       state.imap_browse = 1;
548       imap_browse (LastDir, &state);
549       browser_sort (&state);
550     }
551   }
552
553   *f = 0;
554
555   if (buffy) {
556     if (examine_mailboxes (NULL, &state) == -1)
557       goto bail;
558   }
559   else
560   if (!state.imap_browse)
561     if (examine_directory (NULL, &state, LastDir, prefix) == -1)
562       goto bail;
563
564   menu = mutt_new_menu ();
565   menu->menu = MENU_FOLDER;
566   menu->make_entry = folder_entry;
567   menu->search = select_file_search;
568   menu->title = title;
569   menu->data = state.entry;
570   if (multiple)
571     menu->tag = file_tag;
572   init_menu (&state, menu, title, sizeof (title), buffy);
573
574   for (;;) {
575     switch (i = mutt_menuLoop (menu)) {
576     case OP_GENERIC_SELECT_ENTRY:
577
578       if (!state.entrylen) {
579         mutt_error _("No files match the file mask");
580
581         break;
582       }
583
584       if (S_ISDIR (state.entry[menu->current].mode) ||
585           (S_ISLNK (state.entry[menu->current].mode) &&
586            link_is_dir (LastDir, state.entry[menu->current].name))
587           || state.entry[menu->current].inferiors
588         ) {
589         /* make sure this isn't a MH or maildir mailbox */
590         if (buffy) {
591           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
592           mutt_expand_path (buf, sizeof (buf));
593         }
594         else if (state.imap_browse) {
595           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
596         }
597         else
598           mutt_concat_path(buf, sizeof(buf), LastDir,
599                            state.entry[menu->current].name);
600
601         if ((mx_get_magic (buf) <= 0)
602             || state.entry[menu->current].inferiors)
603         {
604           char OldLastDir[_POSIX_PATH_MAX];
605
606           /* save the old directory */
607           m_strcpy(OldLastDir, sizeof(OldLastDir), LastDir);
608
609           if (m_strcmp(state.entry[menu->current].name, "..") == 0) {
610             if (m_strcmp("..", LastDir + m_strlen(LastDir) - 2) == 0)
611               m_strcat(LastDir, sizeof(LastDir), "/..");
612             else {
613               char *p = strrchr (LastDir + 1, '/');
614
615               if (p)
616                 *p = 0;
617               else {
618                 if (LastDir[0] == '/')
619                   LastDir[1] = 0;
620                 else
621                   m_strcat(LastDir, sizeof(LastDir), "/..");
622               }
623             }
624           }
625           else if (buffy) {
626             m_strcpy(LastDir, sizeof(LastDir),
627                      state.entry[menu->current].name);
628             mutt_expand_path (LastDir, sizeof (LastDir));
629           }
630           else if (state.imap_browse) {
631             int n;
632             ciss_url_t url;
633
634             m_strcpy(LastDir, sizeof(LastDir),
635                      state.entry[menu->current].name);
636             /* tack on delimiter here */
637             n = m_strlen(LastDir) + 1;
638
639             /* special case "" needs no delimiter */
640             url_parse_ciss (&url, state.entry[menu->current].name);
641             if (url.path &&
642                 (state.entry[menu->current].delim != '\0') &&
643                 (n < ssizeof (LastDir))) {
644               LastDir[n] = '\0';
645               LastDir[n - 1] = state.entry[menu->current].delim;
646             }
647           }
648           else {
649             char tmp[_POSIX_PATH_MAX];
650
651             mutt_concat_path(tmp, sizeof(tmp), LastDir,
652                              state.entry[menu->current].name);
653             m_strcpy(LastDir, sizeof(LastDir), tmp);
654           }
655
656           destroy_state (&state);
657           if (killPrefix) {
658             prefix[0] = 0;
659             killPrefix = 0;
660           }
661           buffy = 0;
662           if (state.imap_browse) {
663             init_state (&state, NULL);
664             state.imap_browse = 1;
665             imap_browse (LastDir, &state);
666             browser_sort (&state);
667             menu->data = state.entry;
668           }
669           else
670           if (examine_directory (menu, &state, LastDir, prefix) == -1) {
671             /* try to restore the old values */
672             m_strcpy(LastDir, sizeof(LastDir), OldLastDir);
673             if (examine_directory (menu, &state, LastDir, prefix) == -1) {
674               m_strcpy(LastDir, sizeof(LastDir), NONULL(mod_core.homedir));
675               goto bail;
676             }
677           }
678           menu->current = 0;
679           menu->top = 0;
680           init_menu (&state, menu, title, sizeof (title), buffy);
681           break;
682         }
683       }
684
685       if (buffy) {
686         m_strcpy(f, flen, state.entry[menu->current].name);
687         mutt_expand_path (f, flen);
688       }
689       else if (state.imap_browse)
690         m_strcpy(f, flen, state.entry[menu->current].name);
691       else
692         mutt_concat_path(f, flen, LastDir, state.entry[menu->current].name);
693
694       /* Fall through to OP_EXIT */
695
696     case OP_EXIT:
697
698       if (multiple) {
699         char **tfiles;
700         int j;
701         int h;
702
703         if (menu->tagged) {
704           *numfiles = menu->tagged;
705           tfiles = p_new(char *, *numfiles);
706           for (h = 0, j = 0; h < state.entrylen; i++) {
707             struct folder_file ff = state.entry[i];
708             char full[_POSIX_PATH_MAX];
709
710             if (ff.tagged) {
711               mutt_concat_path(full, sizeof(full), LastDir, ff.name);
712               mutt_expand_path (full, sizeof (full));
713               tfiles[j++] = m_strdup(full);
714             }
715           }
716           *files = tfiles;
717         }
718         else if (f[0]) {        /* no tagged entries. return selected entry */
719           *numfiles = 1;
720           tfiles = p_new(char *, *numfiles);
721           mutt_expand_path (f, flen);
722           tfiles[0] = m_strdup(f);
723           *files = tfiles;
724         }
725       }
726
727       destroy_state (&state);
728       mutt_menuDestroy (&menu);
729       goto bail;
730
731     case OP_BROWSER_TELL:
732       if (state.entrylen)
733         mutt_message ("%s", state.entry[menu->current].name);
734       break;
735
736     case OP_BROWSER_TOGGLE_LSUB:
737       if (option (OPTIMAPLSUB)) {
738         unset_option (OPTIMAPLSUB);
739       }
740       else {
741         set_option (OPTIMAPLSUB);
742       }
743       mutt_ungetch (0, OP_CHECK_NEW);
744       break;
745
746     case OP_CREATE_MAILBOX:
747       if (!state.imap_browse)
748         mutt_error (_("Create is only supported for IMAP mailboxes"));
749       else {
750         imap_mailbox_create (LastDir);
751         /* TODO: find a way to detect if the new folder would appear in
752          *   this window, and insert it without starting over. */
753         destroy_state (&state);
754         init_state (&state, NULL);
755         state.imap_browse = 1;
756         imap_browse (LastDir, &state);
757         browser_sort (&state);
758         menu->data = state.entry;
759         menu->current = 0;
760         menu->top = 0;
761         init_menu (&state, menu, title, sizeof (title), buffy);
762         MAYBE_REDRAW (menu->redraw);
763       }
764       break;
765
766     case OP_RENAME_MAILBOX:
767       if (!state.entry[menu->current].imap)
768         mutt_error (_("Rename is only supported for IMAP mailboxes"));
769       else {
770         int nentry = menu->current;
771
772         if (imap_mailbox_rename (state.entry[nentry].name) >= 0) {
773           destroy_state (&state);
774           init_state (&state, NULL);
775           state.imap_browse = 1;
776           imap_browse (LastDir, &state);
777           browser_sort (&state);
778           menu->data = state.entry;
779           menu->current = 0;
780           menu->top = 0;
781           init_menu (&state, menu, title, sizeof (title), buffy);
782           MAYBE_REDRAW (menu->redraw);
783         }
784       }
785       break;
786
787     case OP_DELETE_MAILBOX:
788       if (!state.entry[menu->current].imap)
789         mutt_error (_("Delete is only supported for IMAP mailboxes"));
790       else {
791         char msg[STRING];
792         IMAP_MBOX mx;
793         int nentry = menu->current;
794
795         imap_parse_path (state.entry[nentry].name, &mx);
796         snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
797                   mx.mbox);
798         if (mutt_yesorno (msg, M_NO) == M_YES) {
799           if (!imap_delete_mailbox (Context, mx)) {
800             /* free the mailbox from the browser */
801             p_delete(&((state.entry)[nentry].name));
802             p_delete(&((state.entry)[nentry].desc));
803             /* and move all other entries up */
804             if (nentry + 1 < state.entrylen)
805               memmove (state.entry + nentry, state.entry + nentry + 1,
806                        sizeof (struct folder_file) * (state.entrylen -
807                                                       (nentry + 1)));
808             state.entrylen--;
809             mutt_message _("Mailbox deleted.");
810
811             init_menu (&state, menu, title, sizeof (title), buffy);
812             MAYBE_REDRAW (menu->redraw);
813           }
814         }
815         else
816           mutt_message _("Mailbox not deleted.");
817         p_delete(&mx.mbox);
818       }
819       break;
820
821     case OP_CHANGE_DIRECTORY:
822       m_strcpy(buf, sizeof(buf), LastDir);
823       if (!state.imap_browse)
824       {
825         /* add '/' at the end of the directory name if not already there */
826         ssize_t len = m_strlen(LastDir);
827
828         if (len && LastDir[len - 1] != '/' && ssizeof(buf) > len)
829           buf[len] = '/';
830       }
831
832       if (mutt_get_field (_("Chdir to: "), buf, sizeof (buf), M_FILE) == 0 &&
833           buf[0]) {
834         buffy = 0;
835         mutt_expand_path (buf, sizeof (buf));
836         if (imap_is_magic (buf, NULL) == M_IMAP) {
837           m_strcpy(LastDir, sizeof(LastDir), buf);
838           destroy_state (&state);
839           init_state (&state, NULL);
840           state.imap_browse = 1;
841           imap_browse (LastDir, &state);
842           browser_sort (&state);
843           menu->data = state.entry;
844           menu->current = 0;
845           menu->top = 0;
846           init_menu (&state, menu, title, sizeof (title), buffy);
847         }
848         else
849         if (stat (buf, &st) == 0) {
850           if (S_ISDIR (st.st_mode)) {
851             destroy_state (&state);
852             if (examine_directory (menu, &state, buf, prefix) == 0)
853               m_strcpy(LastDir, sizeof(LastDir), buf);
854             else {
855               mutt_error _("Error scanning directory.");
856
857               if (examine_directory (menu, &state, LastDir, prefix) == -1) {
858                 mutt_menuDestroy (&menu);
859                 goto bail;
860               }
861             }
862             menu->current = 0;
863             menu->top = 0;
864             init_menu (&state, menu, title, sizeof (title), buffy);
865           }
866           else
867             mutt_error (_("%s is not a directory."), buf);
868         }
869         else
870           mutt_perror (buf);
871       }
872       MAYBE_REDRAW (menu->redraw);
873       break;
874
875     case OP_ENTER_MASK:
876
877       m_strcpy(buf, sizeof(buf), NONULL(Mask.pattern));
878       if (mutt_get_field (_("File Mask: "), buf, sizeof (buf), 0) == 0) {
879         regex_t *rx = p_new(regex_t, 1);
880         char *s = buf;
881         int neg = 0, err;
882
883         buffy = 0;
884         /* assume that the user wants to see everything */
885         if (!buf[0])
886           m_strcpy(buf, sizeof(buf), ".");
887         s = vskipspaces(s);
888         if (*s == '!') {
889           s = vskipspaces(s + 1);
890           neg = 1;
891         }
892
893         if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
894           regerror (err, rx, buf, sizeof (buf));
895           regfree (rx);
896           p_delete(&rx);
897           mutt_error ("%s", buf);
898         }
899         else {
900           m_strreplace(&Mask.pattern, buf);
901           regfree (Mask.rx);
902           p_delete(&Mask.rx);
903           Mask.rx = rx;
904           Mask.neg = neg;
905
906           destroy_state (&state);
907           if (state.imap_browse) {
908             init_state (&state, NULL);
909             state.imap_browse = 1;
910             imap_browse (LastDir, &state);
911             browser_sort (&state);
912             menu->data = state.entry;
913             init_menu (&state, menu, title, sizeof (title), buffy);
914           }
915           else
916           if (examine_directory (menu, &state, LastDir, NULL) == 0)
917             init_menu (&state, menu, title, sizeof (title), buffy);
918           else {
919             mutt_error _("Error scanning directory.");
920
921             mutt_menuDestroy (&menu);
922             goto bail;
923           }
924           killPrefix = 0;
925           if (!state.entrylen) {
926             mutt_error _("No files match the file mask");
927
928             break;
929           }
930         }
931       }
932       MAYBE_REDRAW (menu->redraw);
933       break;
934
935     case OP_SORT:
936     case OP_SORT_REVERSE:
937
938       {
939         int resort = 1;
940         int reverse = (i == OP_SORT_REVERSE);
941
942         switch (mutt_multi_choice ((reverse) ?
943                                    _
944                                    ("Reverse sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? ")
945                                    :
946                                    _
947                                    ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "),
948                                    _("dazn"))) {
949         case -1:               /* abort */
950           resort = 0;
951           break;
952
953         case 1:                /* (d)ate */
954           BrowserSort = SORT_DATE;
955           break;
956
957         case 2:                /* (a)lpha */
958           BrowserSort = SORT_SUBJECT;
959           break;
960
961         case 3:                /* si(z)e */
962           BrowserSort = SORT_SIZE;
963           break;
964
965         case 4:                /* do(n)'t sort */
966           BrowserSort = SORT_ORDER;
967           resort = 0;
968           break;
969         }
970         if (resort) {
971           BrowserSort |= reverse ? SORT_REVERSE : 0;
972           browser_sort (&state);
973           menu->redraw = REDRAW_FULL;
974         }
975         break;
976       }
977
978     case OP_TOGGLE_MAILBOXES:
979       buffy = 1 - buffy;
980
981     case OP_CHECK_NEW:
982       destroy_state (&state);
983       prefix[0] = 0;
984       killPrefix = 0;
985
986       if (buffy) {
987         if (examine_mailboxes (menu, &state) == -1)
988           goto bail;
989       }
990       else if (imap_is_magic (LastDir, NULL) == M_IMAP) {
991         init_state (&state, NULL);
992         state.imap_browse = 1;
993         imap_browse (LastDir, &state);
994         browser_sort (&state);
995         menu->data = state.entry;
996       }
997       else if (examine_directory (menu, &state, LastDir, prefix) == -1)
998         goto bail;
999       init_menu (&state, menu, title, sizeof (title), buffy);
1000       break;
1001
1002     case OP_BUFFY_LIST:
1003       if (option (OPTFORCEBUFFYCHECK))
1004         buffy_check (1);
1005       buffy_list ();
1006       break;
1007
1008     case OP_BROWSER_NEW_FILE:
1009
1010       snprintf (buf, sizeof (buf), "%s/", LastDir);
1011       if (mutt_get_field (_("New file name: "), buf, sizeof (buf), M_FILE) ==
1012           0) {
1013         m_strcpy(f, flen, buf);
1014         destroy_state (&state);
1015         mutt_menuDestroy (&menu);
1016         goto bail;
1017       }
1018       MAYBE_REDRAW (menu->redraw);
1019       break;
1020
1021     case OP_BROWSER_VIEW_FILE:
1022       if (!state.entrylen) {
1023         mutt_error _("No files match the file mask");
1024
1025         break;
1026       }
1027
1028       if (state.entry[menu->current].selectable) {
1029         m_strcpy(f, flen, state.entry[menu->current].name);
1030         destroy_state (&state);
1031         mutt_menuDestroy (&menu);
1032         goto bail;
1033       }
1034       else
1035       if (S_ISDIR (state.entry[menu->current].mode) ||
1036             (S_ISLNK (state.entry[menu->current].mode) &&
1037                link_is_dir (LastDir, state.entry[menu->current].name))) {
1038         mutt_error _("Can't view a directory");
1039
1040         break;
1041       }
1042       else {
1043         BODY *b;
1044         char nbuf[_POSIX_PATH_MAX];
1045
1046         mutt_concat_path(nbuf, sizeof(nbuf), LastDir,
1047                          state.entry[menu->current].name);
1048         b = mutt_make_file_attach (nbuf);
1049         if (b != NULL) {
1050           mutt_view_attachment (NULL, b, M_REGULAR, NULL, NULL, 0);
1051           body_list_wipe(&b);
1052           menu->redraw = REDRAW_FULL;
1053         }
1054         else
1055           mutt_error _("Error trying to view file");
1056       }
1057       break;
1058
1059     case OP_BROWSER_SUBSCRIBE:
1060     case OP_BROWSER_UNSUBSCRIBE:
1061       if (i == OP_BROWSER_SUBSCRIBE)
1062         imap_subscribe (state.entry[menu->current].name, 1);
1063       else
1064         imap_subscribe (state.entry[menu->current].name, 0);
1065     }
1066   }
1067
1068 bail:
1069
1070   if (!folder)
1071     m_strcpy(LastDir, sizeof(LastDir), LastDirBackup);
1072
1073 }