rework m_strformat so that it takes the cols number to use in case of
[apps/madmutt.git] / 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-lib/lib-lib.h>
11
12 #include <dirent.h>
13 #include <pwd.h>
14 #include <grp.h>
15
16 #include <lib-ui/curses.h>
17 #include <lib-ui/enter.h>
18 #include <lib-ui/menu.h>
19 #include <lib-ui/sidebar.h>
20 #include <lib-mx/mx.h>
21
22 #include "mutt.h"
23 #include "buffy.h"
24 #include "sort.h"
25 #include "browser.h"
26 #include "attach.h"
27
28 #include <imap/imap.h>
29 #ifdef USE_NNTP
30 #include <nntp/nntp.h>
31 #endif
32
33 static struct mapping_t FolderHelp[] = {
34   {N_("Exit"), OP_EXIT},
35   {N_("Chdir"), OP_CHANGE_DIRECTORY},
36   {N_("Mask"), OP_ENTER_MASK},
37   {N_("Help"), OP_HELP},
38   {NULL, OP_NULL}
39 };
40
41 #ifdef USE_NNTP
42 static struct mapping_t FolderNewsHelp[] = {
43   {N_("Exit"), OP_EXIT},
44   {N_("List"), OP_TOGGLE_MAILBOXES},
45   {N_("Subscribe"), OP_BROWSER_SUBSCRIBE},
46   {N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE},
47   {N_("Catchup"), OP_CATCHUP},
48   {N_("Mask"), OP_ENTER_MASK},
49   {N_("Help"), OP_HELP},
50   {NULL, OP_NULL}
51 };
52 #endif
53
54 typedef struct folder_t {
55   struct folder_file *ff;
56   int num;
57 } FOLDER;
58
59 static char LastDir[_POSIX_PATH_MAX] = "";
60 static char LastDirBackup[_POSIX_PATH_MAX] = "";
61
62 /* Frees up the memory allocated for the local-global variables.  */
63 static void destroy_state (struct browser_state *state)
64 {
65   int c;
66
67   for (c = 0; c < state->entrylen; c++) {
68     p_delete(&((state->entry)[c].name));
69     p_delete(&((state->entry)[c].desc));
70     p_delete(&((state->entry)[c].st));
71   }
72   p_delete(&state->folder);
73   p_delete(&state->entry);
74 }
75
76 static int browser_compare_subject (const void *a, const void *b)
77 {
78   struct folder_file *pa = (struct folder_file *) a;
79   struct folder_file *pb = (struct folder_file *) b;
80
81   int r = strcoll(NONULL(pa->name), NONULL(pb->name));
82
83   return ((BrowserSort & SORT_REVERSE) ? -r : r);
84 }
85
86 static int browser_compare_date (const void *a, const void *b)
87 {
88   struct folder_file *pa = (struct folder_file *) a;
89   struct folder_file *pb = (struct folder_file *) b;
90
91   int r = pa->mtime - pb->mtime;
92
93   return ((BrowserSort & SORT_REVERSE) ? -r : r);
94 }
95
96 static int browser_compare_size (const void *a, const void *b)
97 {
98   struct folder_file *pa = (struct folder_file *) a;
99   struct folder_file *pb = (struct folder_file *) b;
100
101   int r = pa->size - pb->size;
102
103   return ((BrowserSort & SORT_REVERSE) ? -r : r);
104 }
105
106 static void browser_sort (struct browser_state *state)
107 {
108   int (*f) (const void *, const void *);
109
110   switch (BrowserSort & SORT_MASK) {
111   case SORT_ORDER:
112     return;
113   case SORT_DATE:
114 #ifdef USE_NNTP
115     if (option (OPTNEWS))
116       return;
117 #endif
118     f = browser_compare_date;
119     break;
120   case SORT_SIZE:
121 #ifdef USE_NNTP
122     if (option (OPTNEWS))
123       return;
124 #endif
125     f = browser_compare_size;
126     break;
127   case SORT_SUBJECT:
128   default:
129     f = browser_compare_subject;
130     break;
131   }
132   qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
133 }
134
135 static int link_is_dir (const char *folder, const char *path)
136 {
137   struct stat st;
138   char fullpath[_POSIX_PATH_MAX];
139
140   mutt_concat_path(fullpath, sizeof(fullpath), folder, path);
141
142   if (stat (fullpath, &st) == 0)
143     return (S_ISDIR (st.st_mode));
144   else
145     return 0;
146 }
147
148 static const char *
149 folder_format_str(char *dest, ssize_t destlen, char op,
150                   const char *src, const char *fmt,
151                   const char *ifstr, const char *elstr,
152                   anytype data, format_flag flags)
153 {
154   char fn[STRING], tmp[STRING], permission[11], date[16];
155   const char *t_fmt;
156   time_t tnow;
157   FOLDER *folder = data.ptr;
158   struct passwd *pw;
159   struct group *gr;
160   int optional = (flags & M_FORMAT_OPTIONAL);
161
162   switch (op) {
163   case 'C':
164     snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
165     snprintf (dest, destlen, tmp, folder->num + 1);
166     break;
167
168   case 'd':
169     if (folder->ff->st != NULL) {
170       tnow = time (NULL);
171       t_fmt =
172         tnow - folder->ff->st->st_mtime <
173         31536000 ? "%b %d %H:%M" : "%b %d  %Y";
174       strftime (date, sizeof (date), t_fmt,
175                 localtime (&folder->ff->st->st_mtime));
176       mutt_format_s (dest, destlen, fmt, date);
177     }
178     else
179       mutt_format_s (dest, destlen, fmt, "");
180     break;
181
182   case 'f':
183     {
184       const char *s;
185
186       if (folder->ff->imap)
187         s = NONULL (folder->ff->desc);
188       else
189         s = NONULL (folder->ff->name);
190
191       snprintf (fn, sizeof (fn), "%s%s", s,
192                 folder->ff->st ? (S_ISLNK (folder->ff->st->st_mode) ? "@" :
193                                   (S_ISDIR (folder->ff->st->st_mode) ? "/" :
194                                    ((folder->ff->st->st_mode & S_IXUSR) !=
195                                     0 ? "*" : ""))) : "");
196
197       mutt_format_s (dest, destlen, fmt, fn);
198       break;
199     }
200   case 'F':
201     if (folder->ff->st != NULL) {
202       snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
203                 S_ISDIR (folder->ff->st->
204                          st_mode) ? 'd' : (S_ISLNK (folder->ff->st->
205                                                     st_mode) ? 'l' : '-'),
206                 (folder->ff->st->st_mode & S_IRUSR) != 0 ? 'r' : '-',
207                 (folder->ff->st->st_mode & S_IWUSR) != 0 ? 'w' : '-',
208                 (folder->ff->st->st_mode & S_ISUID) !=
209                 0 ? 's' : (folder->ff->st->st_mode & S_IXUSR) !=
210                 0 ? 'x' : '-',
211                 (folder->ff->st->st_mode & S_IRGRP) != 0 ? 'r' : '-',
212                 (folder->ff->st->st_mode & S_IWGRP) != 0 ? 'w' : '-',
213                 (folder->ff->st->st_mode & S_ISGID) !=
214                 0 ? 's' : (folder->ff->st->st_mode & S_IXGRP) !=
215                 0 ? 'x' : '-',
216                 (folder->ff->st->st_mode & S_IROTH) != 0 ? 'r' : '-',
217                 (folder->ff->st->st_mode & S_IWOTH) != 0 ? 'w' : '-',
218                 (folder->ff->st->st_mode & S_ISVTX) !=
219                 0 ? 't' : (folder->ff->st->st_mode & S_IXOTH) !=
220                 0 ? 'x' : '-');
221       mutt_format_s (dest, destlen, fmt, permission);
222     }
223     else if (folder->ff->imap) {
224       /* mark folders with subfolders AND mail */
225       snprintf (permission, sizeof (permission), "IMAP %c",
226                 (folder->ff->inferiors
227                  && folder->ff->selectable) ? '+' : ' ');
228       mutt_format_s (dest, destlen, fmt, permission);
229     }
230     else
231       mutt_format_s (dest, destlen, fmt, "");
232     break;
233
234   case 'g':
235     if (folder->ff->st != NULL) {
236       if ((gr = getgrgid (folder->ff->st->st_gid)))
237         mutt_format_s (dest, destlen, fmt, gr->gr_name);
238       else {
239         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
240         snprintf (dest, destlen, tmp, folder->ff->st->st_gid);
241       }
242     }
243     else
244       mutt_format_s (dest, destlen, fmt, "");
245     break;
246
247   case 'l':
248     if (folder->ff->st != NULL) {
249       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
250       snprintf (dest, destlen, tmp, folder->ff->st->st_nlink);
251     }
252     else
253       mutt_format_s (dest, destlen, fmt, "");
254     break;
255
256   case 'N':
257     if (imap_is_magic (folder->ff->desc, NULL) == M_IMAP) {
258       if (!optional) {
259         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
260         snprintf (dest, destlen, tmp, folder->ff->new);
261       }
262       else if (!folder->ff->new)
263         optional = 0;
264       break;
265     }
266     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
267     snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
268     break;
269
270   case 's':
271     if (folder->ff->st != NULL) {
272       snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
273       snprintf (dest, destlen, tmp, (long) folder->ff->st->st_size);
274     }
275     else
276       mutt_format_s (dest, destlen, fmt, "");
277     break;
278
279   case 't':
280     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
281     snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
282     break;
283
284   case 'u':
285     if (folder->ff->st != NULL) {
286       if ((pw = getpwuid (folder->ff->st->st_uid)))
287         mutt_format_s (dest, destlen, fmt, pw->pw_name);
288       else {
289         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
290         snprintf (dest, destlen, tmp, folder->ff->st->st_uid);
291       }
292     }
293     else
294       mutt_format_s (dest, destlen, fmt, "");
295     break;
296
297   default:
298     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
299     snprintf (dest, destlen, tmp, op);
300     break;
301   }
302
303
304   if (flags & M_FORMAT_OPTIONAL)
305     m_strformat(dest, destlen, 0, optional ? ifstr : elstr,
306                 folder_format_str, data, 0);
307
308   return src;
309 }
310
311 #ifdef USE_NNTP
312 static const char *
313 newsgroup_format_str(char *dest, ssize_t destlen, char op,
314                      const char *src, const char *fmt,
315                      const char *ifstr, const char *elstr,
316                      anytype data, format_flag flags)
317 {
318   char fn[STRING], tmp[STRING];
319   FOLDER *folder = data.ptr;
320
321   switch (op) {
322   case 'C':
323     snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
324     snprintf (dest, destlen, tmp, folder->num + 1);
325     break;
326
327   case 'f':
328     m_strcpy(fn, sizeof(fn), folder->ff->name);
329     snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
330     snprintf (dest, destlen, tmp, fn);
331     break;
332
333   case 'N':
334     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
335     if (folder->ff->nd->subscribed)
336       snprintf (dest, destlen, tmp, ' ');
337     else
338       snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : 'u');
339     break;
340
341   case 'M':
342     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
343     if (folder->ff->nd->deleted)
344       snprintf (dest, destlen, tmp, 'D');
345     else
346       snprintf (dest, destlen, tmp, folder->ff->nd->allowed ? ' ' : '-');
347     break;
348
349   case 's':
350     if (flags & M_FORMAT_OPTIONAL) {
351       if (folder->ff->nd->unread != 0)
352         m_strformat(dest, destlen, 0, ifstr, newsgroup_format_str,
353                     data, flags);
354       else
355         m_strformat (dest, destlen, 0, elstr, newsgroup_format_str,
356                      data, flags);
357     }
358     else if (Context && Context->data == folder->ff->nd) {
359       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
360       snprintf (dest, destlen, tmp, Context->unread);
361     }
362     else {
363       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
364       snprintf (dest, destlen, tmp, folder->ff->nd->unread);
365     }
366     break;
367
368   case 'n':
369     if (Context && Context->data == folder->ff->nd) {
370       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
371       snprintf (dest, destlen, tmp, Context->new);
372     }
373     else if (option (OPTMARKOLD) &&
374              folder->ff->nd->lastCached >= folder->ff->nd->firstMessage &&
375              folder->ff->nd->lastCached <= folder->ff->nd->lastMessage) {
376       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
377       snprintf (dest, destlen, tmp,
378                 folder->ff->nd->lastMessage - folder->ff->nd->lastCached);
379     }
380     else {
381       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
382       snprintf (dest, destlen, tmp, folder->ff->nd->unread);
383     }
384     break;
385
386   case 'd':
387     if (folder->ff->nd->desc != NULL) {
388       snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
389       snprintf (dest, destlen, tmp, folder->ff->nd->desc);
390     }
391     else {
392       snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
393       snprintf (dest, destlen, tmp, "");
394     }
395     break;
396   }
397   return (src);
398 }
399 #endif /* USE_NNTP */
400
401 #ifdef USE_NNTP
402 static void add_folder (MUTTMENU * m, struct browser_state *state,
403                         const char *name, const struct stat *s,
404                         void *data, int new)
405 #else
406 static void add_folder (MUTTMENU * m, struct browser_state *state,
407                         const char *name, const struct stat *s,
408                         int new)
409 #endif
410 {
411   if (state->entrylen == state->entrymax) {
412     /* need to allocate more space */
413     p_realloc(&state->entry, state->entrymax += 256);
414     p_clear(&state->entry[state->entrylen], 256);
415     if (m)
416       m->data = state->entry;
417   }
418
419   if (s != NULL) {
420     (state->entry)[state->entrylen].mode  = s->st_mode;
421     (state->entry)[state->entrylen].mtime = s->st_mtime;
422     (state->entry)[state->entrylen].size  = s->st_size;
423     (state->entry)[state->entrylen].st    = p_dup(s, 1);
424   }
425
426   (state->entry)[state->entrylen].new = new;
427   (state->entry)[state->entrylen].name = m_strdup(name);
428   (state->entry)[state->entrylen].desc = m_strdup(name);
429   (state->entry)[state->entrylen].imap = 0;
430 #ifdef USE_NNTP
431   if (option (OPTNEWS))
432     (state->entry)[state->entrylen].nd = (NNTP_DATA *) data;
433 #endif
434   (state->entrylen)++;
435 }
436
437 static void init_state (struct browser_state *state, MUTTMENU * menu)
438 {
439   state->entrylen = 0;
440   state->entrymax = 256;
441   state->entry = p_new(struct folder_file, state->entrymax);
442   state->imap_browse = 0;
443   if (menu)
444     menu->data = state->entry;
445 }
446
447 /* get list of all files/newsgroups with mask */
448 static int examine_directory (MUTTMENU * menu, struct browser_state *state,
449                               char *d, const char *prefix)
450 {
451 #ifdef USE_NNTP
452   if (option (OPTNEWS)) {
453     string_list_t *tmp;
454     NNTP_DATA *data;
455     NNTP_SERVER *news = CurrentNewsSrv;
456
457 /*  buffy_check (0); */
458     init_state (state, menu);
459
460     for (tmp = news->list; tmp; tmp = tmp->next) {
461       if (!(data = (NNTP_DATA *) tmp->data))
462         continue;
463       nntp_sync_sidebar (data);
464       if (m_strncmp (prefix, data->group, m_strlen(prefix)) != 0)
465         continue;
466       if (!((regexec (Mask.rx, data->group, 0, NULL, 0) == 0) ^ Mask.not))
467         continue;
468 #ifdef USE_NNTP
469       add_folder (menu, state, data->group, NULL, data, data->new);
470 #else
471       add_folder (menu, state, data->group, NULL, data->new);
472 #endif
473     }
474     sidebar_draw ();
475   }
476   else
477 #endif /* USE_NNTP */
478   {
479     struct stat s;
480     DIR *dp;
481     struct dirent *de;
482     char buffer[_POSIX_PATH_MAX + STRING];
483     int i = -1;
484
485     while (stat (d, &s) == -1) {
486       if (errno == ENOENT) {
487         /* The last used directory is deleted, try to use the parent dir. */
488         char *c = strrchr (d, '/');
489
490         if (c && (c > d)) {
491           *c = 0;
492           continue;
493         }
494       }
495       mutt_perror (d);
496       return (-1);
497     }
498
499     if (!S_ISDIR (s.st_mode)) {
500       mutt_error (_("%s is not a directory."), d);
501       return (-1);
502     }
503
504     buffy_check (0);
505
506     if ((dp = opendir (d)) == NULL) {
507       mutt_perror (d);
508       return (-1);
509     }
510
511     init_state (state, menu);
512
513     while ((de = readdir (dp)) != NULL) {
514       if (m_strcmp(de->d_name, ".") == 0)
515         continue;               /* we don't need . */
516
517       if (m_strncmp(prefix, de->d_name, m_strlen(prefix)) != 0)
518         continue;
519       if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.not))
520         continue;
521
522       mutt_concat_path(buffer, sizeof(buffer), d, de->d_name);
523       if (lstat (buffer, &s) == -1)
524         continue;
525
526       if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
527           (!S_ISLNK (s.st_mode)))
528         continue;
529
530       i = buffy_lookup (buffer);
531 #ifdef USE_NNTP
532       add_folder (menu, state, de->d_name, &s, NULL, i >= 0 ? Incoming.arr[i]->new : 0);
533 #else
534       add_folder (menu, state, de->d_name, &s, i >= 0 ? Incoming.arr[i]->new : 0);
535 #endif
536     }
537     closedir (dp);
538   }
539   sidebar_draw ();
540   browser_sort (state);
541   return 0;
542 }
543
544 /* get list of mailboxes/subscribed newsgroups */
545 static int examine_mailboxes (MUTTMENU * menu, struct browser_state *state)
546 {
547   struct stat s;
548   char buffer[LONG_STRING];
549
550 #ifdef USE_NNTP
551   if (option (OPTNEWS)) {
552     string_list_t *tmp;
553     NNTP_DATA *data;
554     NNTP_SERVER *news = CurrentNewsSrv;
555
556 /*  buffy_check (0); */
557     init_state (state, menu);
558
559     for (tmp = news->list; tmp; tmp = tmp->next) {
560       if ((data = (NNTP_DATA*) tmp->data) == NULL)
561         continue;
562       nntp_sync_sidebar (data);
563       if ((data->new || (data->subscribed && 
564                          (!option (OPTSHOWONLYUNREAD)|| data->unread))))
565         add_folder (menu, state, data->group, NULL, data, data->new);
566     }
567     sidebar_draw ();
568   }
569   else
570 #endif
571   {
572     int i = 0;
573     BUFFY* tmp;
574
575     if (!Incoming.len)
576       return (-1);
577     buffy_check (0);
578
579     init_state (state, menu);
580
581     for (i = 0; i < Incoming.len; i++) {
582       tmp = Incoming.arr[i];
583       tmp->magic = mx_get_magic (tmp->path);
584       if (tmp->magic == M_IMAP) {
585 #ifdef USE_NNTP
586         add_folder (menu, state, tmp->path, NULL, NULL, tmp->new);
587 #else
588         add_folder (menu, state, tmp->path, NULL, tmp->new);
589 #endif
590         continue;
591       }
592       if (tmp->magic == M_POP) {
593 #ifdef USE_NNTP
594         add_folder (menu, state, tmp->path, NULL, NULL, tmp->new);
595 #else
596         add_folder (menu, state, tmp->path, NULL, tmp->new);
597 #endif
598         continue;
599       }
600 #ifdef USE_NNTP
601       if (tmp->magic == M_NNTP) {
602         add_folder (menu, state, tmp->path, NULL, NULL, tmp->new);
603         continue;
604       }
605 #endif
606       if (lstat (tmp->path, &s) == -1)
607         continue;
608
609       if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
610           (!S_ISLNK (s.st_mode)))
611         continue;
612
613       m_strcpy(buffer, sizeof(buffer), NONULL(tmp->path));
614       mutt_pretty_mailbox (buffer);
615
616 #ifdef USE_NNTP
617       add_folder (menu, state, buffer, &s, NULL, tmp->new);
618 #else
619       add_folder (menu, state, buffer, &s, tmp->new);
620 #endif
621     }
622   }
623   browser_sort (state);
624   return 0;
625 }
626
627 static int select_file_search (MUTTMENU * menu, regex_t * re, int n)
628 {
629 #ifdef USE_NNTP
630   if (option (OPTNEWS))
631     return (regexec
632             (re, ((struct folder_file *) menu->data)[n].desc, 0, NULL, 0));
633 #endif
634   return (regexec
635           (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
636 }
637
638 static void folder_entry (char *s, ssize_t slen, MUTTMENU * menu, int num)
639 {
640   FOLDER folder;
641
642   folder.ff = &((struct folder_file *) menu->data)[num];
643   folder.num = num;
644
645 #ifdef USE_NNTP
646   if (option (OPTNEWS))
647     m_strformat(s, slen, COLS - SW, GroupFormat, newsgroup_format_str, &folder,
648                 option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
649   else
650 #endif
651     m_strformat(s, slen, COLS - SW, FolderFormat, folder_format_str, &folder,
652                option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
653 }
654
655 static void init_menu (struct browser_state *state, MUTTMENU * menu,
656                        char *title, ssize_t titlelen, int buffy)
657 {
658   char path[_POSIX_PATH_MAX];
659
660   menu->max = state->entrylen;
661
662   if (menu->current >= menu->max)
663     menu->current = menu->max - 1;
664   if (menu->current < 0)
665     menu->current = 0;
666   if (menu->top > menu->current)
667     menu->top = 0;
668
669   menu->tagged = 0;
670
671 #ifdef USE_NNTP
672   if (option (OPTNEWS)) {
673     if (buffy)
674       snprintf (title, titlelen, "%s", _("Subscribed newsgroups"));
675     else
676       snprintf (title, titlelen, _("Newsgroups on server [%s]"),
677                 CurrentNewsSrv->conn->account.host);
678   }
679   else
680 #endif
681   if (buffy)
682     snprintf(title, titlelen, _("Mailboxes [%d]"), buffy_check(0));
683   else {
684     m_strcpy(path, sizeof(path), LastDir);
685     mutt_pretty_mailbox (path);
686     if (state->imap_browse && option (OPTIMAPLSUB))
687       snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
688                 path, NONULL (Mask.pattern));
689     else
690       snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
691                 path, NONULL (Mask.pattern));
692   }
693   menu->redraw = REDRAW_FULL;
694 }
695
696 static int file_tag (MUTTMENU * menu, int n, int m)
697 {
698   struct folder_file *ff = &(((struct folder_file *) menu->data)[n]);
699   int ot;
700
701   if (S_ISDIR (ff->mode)
702       || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name))) {
703     mutt_error _("Can't attach a directory!");
704
705     return 0;
706   }
707
708   ot = ff->tagged;
709   ff->tagged = (m >= 0 ? m : !ff->tagged);
710
711   return ff->tagged - ot;
712 }
713
714 void _mutt_select_file (char *f, ssize_t flen, int flags, char ***files,
715                         int *numfiles)
716 {
717   char buf[_POSIX_PATH_MAX];
718   char prefix[_POSIX_PATH_MAX] = "";
719   char helpstr[STRING];
720   char title[STRING];
721   struct browser_state state;
722   MUTTMENU *menu;
723   struct stat st;
724   int i, killPrefix = 0;
725   int multiple = (flags & M_SEL_MULTI) ? 1 : 0;
726   int folder = (flags & M_SEL_FOLDER) ? 1 : 0;
727   int buffy = (flags & M_SEL_BUFFY) ? 1 : 0;
728
729   buffy = buffy && folder;
730
731   p_clear(&state, 1);
732
733   if (!folder)
734     m_strcpy(LastDirBackup, sizeof(LastDirBackup), LastDir);
735
736 #ifdef USE_NNTP
737   if (option (OPTNEWS)) {
738     if (*f)
739       m_strcpy(prefix, sizeof(prefix), f);
740     else {
741       string_list_t *list;
742
743       /* default state for news reader mode is browse subscribed newsgroups */
744       buffy = 0;
745       for (list = CurrentNewsSrv->list; list; list = list->next) {
746         NNTP_DATA *data = (NNTP_DATA *) list->data;
747
748         if (data && data->subscribed) {
749           buffy = 1;
750           break;
751         }
752       }
753     }
754   }
755   else
756 #endif
757   if (*f) {
758     mutt_expand_path (f, flen);
759     if (imap_is_magic (f, NULL) == M_IMAP) {
760       init_state (&state, NULL);
761       state.imap_browse = 1;
762       if (!imap_browse (f, &state))
763         m_strcpy(LastDir, sizeof(LastDir), state.folder);
764     }
765     else {
766       for (i = m_strlen(f) - 1; i > 0 && f[i] != '/'; i--);
767       if (i > 0) {
768         if (f[0] == '/') {
769           i = MIN(ssizeof(LastDir) - 1, i);
770           m_strcpy(LastDir, sizeof(LastDir), f);
771         }
772         else {
773           getcwd(LastDir, sizeof(LastDir));
774           m_strcat(LastDir, sizeof(LastDir), "/");
775           m_strncat(LastDir, sizeof(LastDir), f, i);
776         }
777       }
778       else {
779         if (f[0] == '/')
780           m_strcpy(LastDir, sizeof(LastDir), "/");
781         else
782           getcwd (LastDir, sizeof (LastDir));
783       }
784
785       if (i <= 0 && f[0] != '/')
786         m_strcpy(prefix, sizeof(prefix), f);
787       else
788         m_strcpy(prefix, sizeof(prefix), f + i + 1);
789       killPrefix = 1;
790     }
791   }
792   else {
793     if (!folder)
794       getcwd (LastDir, sizeof (LastDir));
795     else if (!LastDir[0])
796       m_strcpy(LastDir, sizeof(LastDir), NONULL(Maildir));
797
798     if (!buffy && imap_is_magic (LastDir, NULL) == M_IMAP) {
799       init_state (&state, NULL);
800       state.imap_browse = 1;
801       imap_browse (LastDir, &state);
802       browser_sort (&state);
803     }
804   }
805
806   *f = 0;
807
808   if (buffy) {
809     if (examine_mailboxes (NULL, &state) == -1)
810       goto bail;
811   }
812   else
813   if (!state.imap_browse)
814     if (examine_directory (NULL, &state, LastDir, prefix) == -1)
815       goto bail;
816
817   menu = mutt_new_menu ();
818   menu->menu = MENU_FOLDER;
819   menu->make_entry = folder_entry;
820   menu->search = select_file_search;
821   menu->title = title;
822   menu->data = state.entry;
823   if (multiple)
824     menu->tag = file_tag;
825
826   menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER,
827 #ifdef USE_NNTP
828                                   (option (OPTNEWS)) ? FolderNewsHelp :
829 #endif
830                                   FolderHelp);
831
832   init_menu (&state, menu, title, sizeof (title), buffy);
833
834   for (;;) {
835     switch (i = mutt_menuLoop (menu)) {
836     case OP_GENERIC_SELECT_ENTRY:
837
838       if (!state.entrylen) {
839         mutt_error _("No files match the file mask");
840
841         break;
842       }
843
844       if (S_ISDIR (state.entry[menu->current].mode) ||
845           (S_ISLNK (state.entry[menu->current].mode) &&
846            link_is_dir (LastDir, state.entry[menu->current].name))
847           || state.entry[menu->current].inferiors
848         ) {
849         /* make sure this isn't a MH or maildir mailbox */
850         if (buffy) {
851           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
852           mutt_expand_path (buf, sizeof (buf));
853         }
854         else if (state.imap_browse) {
855           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
856         }
857         else
858           mutt_concat_path(buf, sizeof(buf), LastDir,
859                            state.entry[menu->current].name);
860
861         if ((mx_get_magic (buf) <= 0)
862             || state.entry[menu->current].inferiors)
863         {
864           char OldLastDir[_POSIX_PATH_MAX];
865
866           /* save the old directory */
867           m_strcpy(OldLastDir, sizeof(OldLastDir), LastDir);
868
869           if (m_strcmp(state.entry[menu->current].name, "..") == 0) {
870             if (m_strcmp("..", LastDir + m_strlen(LastDir) - 2) == 0)
871               m_strcat(LastDir, sizeof(LastDir), "/..");
872             else {
873               char *p = strrchr (LastDir + 1, '/');
874
875               if (p)
876                 *p = 0;
877               else {
878                 if (LastDir[0] == '/')
879                   LastDir[1] = 0;
880                 else
881                   m_strcat(LastDir, sizeof(LastDir), "/..");
882               }
883             }
884           }
885           else if (buffy) {
886             m_strcpy(LastDir, sizeof(LastDir),
887                      state.entry[menu->current].name);
888             mutt_expand_path (LastDir, sizeof (LastDir));
889           }
890           else if (state.imap_browse) {
891             int n;
892             ciss_url_t url;
893
894             m_strcpy(LastDir, sizeof(LastDir),
895                      state.entry[menu->current].name);
896             /* tack on delimiter here */
897             n = m_strlen(LastDir) + 1;
898
899             /* special case "" needs no delimiter */
900             url_parse_ciss (&url, state.entry[menu->current].name);
901             if (url.path &&
902                 (state.entry[menu->current].delim != '\0') &&
903                 (n < ssizeof (LastDir))) {
904               LastDir[n] = '\0';
905               LastDir[n - 1] = state.entry[menu->current].delim;
906             }
907           }
908           else {
909             char tmp[_POSIX_PATH_MAX];
910
911             mutt_concat_path(tmp, sizeof(tmp), LastDir,
912                              state.entry[menu->current].name);
913             m_strcpy(LastDir, sizeof(LastDir), tmp);
914           }
915
916           destroy_state (&state);
917           if (killPrefix) {
918             prefix[0] = 0;
919             killPrefix = 0;
920           }
921           buffy = 0;
922           if (state.imap_browse) {
923             init_state (&state, NULL);
924             state.imap_browse = 1;
925             imap_browse (LastDir, &state);
926             browser_sort (&state);
927             menu->data = state.entry;
928           }
929           else
930           if (examine_directory (menu, &state, LastDir, prefix) == -1) {
931             /* try to restore the old values */
932             m_strcpy(LastDir, sizeof(LastDir), OldLastDir);
933             if (examine_directory (menu, &state, LastDir, prefix) == -1) {
934               m_strcpy(LastDir, sizeof(LastDir), NONULL(Homedir));
935               goto bail;
936             }
937           }
938           menu->current = 0;
939           menu->top = 0;
940           init_menu (&state, menu, title, sizeof (title), buffy);
941           break;
942         }
943       }
944
945 #ifdef USE_NNTP
946       if (buffy || option (OPTNEWS))    /* news have not path */
947 #else
948       if (buffy)
949 #endif
950       {
951         m_strcpy(f, flen, state.entry[menu->current].name);
952         mutt_expand_path (f, flen);
953       }
954       else if (state.imap_browse)
955         m_strcpy(f, flen, state.entry[menu->current].name);
956       else
957         mutt_concat_path(f, flen, LastDir, state.entry[menu->current].name);
958
959       /* Fall through to OP_EXIT */
960
961     case OP_EXIT:
962
963       if (multiple) {
964         char **tfiles;
965         int j;
966         int h;
967
968         if (menu->tagged) {
969           *numfiles = menu->tagged;
970           tfiles = p_new(char *, *numfiles);
971           for (h = 0, j = 0; h < state.entrylen; i++) {
972             struct folder_file ff = state.entry[i];
973             char full[_POSIX_PATH_MAX];
974
975             if (ff.tagged) {
976               mutt_concat_path(full, sizeof(full), LastDir, ff.name);
977               mutt_expand_path (full, sizeof (full));
978               tfiles[j++] = m_strdup(full);
979             }
980           }
981           *files = tfiles;
982         }
983         else if (f[0]) {        /* no tagged entries. return selected entry */
984           *numfiles = 1;
985           tfiles = p_new(char *, *numfiles);
986           mutt_expand_path (f, flen);
987           tfiles[0] = m_strdup(f);
988           *files = tfiles;
989         }
990       }
991
992       destroy_state (&state);
993       mutt_menuDestroy (&menu);
994       goto bail;
995
996     case OP_BROWSER_TELL:
997       if (state.entrylen)
998         mutt_message ("%s", state.entry[menu->current].name);
999       break;
1000
1001     case OP_BROWSER_TOGGLE_LSUB:
1002       if (option (OPTIMAPLSUB)) {
1003         unset_option (OPTIMAPLSUB);
1004       }
1005       else {
1006         set_option (OPTIMAPLSUB);
1007       }
1008       mutt_ungetch (0, OP_CHECK_NEW);
1009       break;
1010
1011     case OP_CREATE_MAILBOX:
1012       if (!state.imap_browse)
1013         mutt_error (_("Create is only supported for IMAP mailboxes"));
1014       else {
1015         imap_mailbox_create (LastDir);
1016         /* TODO: find a way to detect if the new folder would appear in
1017          *   this window, and insert it without starting over. */
1018         destroy_state (&state);
1019         init_state (&state, NULL);
1020         state.imap_browse = 1;
1021         imap_browse (LastDir, &state);
1022         browser_sort (&state);
1023         menu->data = state.entry;
1024         menu->current = 0;
1025         menu->top = 0;
1026         init_menu (&state, menu, title, sizeof (title), buffy);
1027         MAYBE_REDRAW (menu->redraw);
1028       }
1029       break;
1030
1031     case OP_RENAME_MAILBOX:
1032       if (!state.entry[menu->current].imap)
1033         mutt_error (_("Rename is only supported for IMAP mailboxes"));
1034       else {
1035         int nentry = menu->current;
1036
1037         if (imap_mailbox_rename (state.entry[nentry].name) >= 0) {
1038           destroy_state (&state);
1039           init_state (&state, NULL);
1040           state.imap_browse = 1;
1041           imap_browse (LastDir, &state);
1042           browser_sort (&state);
1043           menu->data = state.entry;
1044           menu->current = 0;
1045           menu->top = 0;
1046           init_menu (&state, menu, title, sizeof (title), buffy);
1047           MAYBE_REDRAW (menu->redraw);
1048         }
1049       }
1050       break;
1051
1052     case OP_DELETE_MAILBOX:
1053       if (!state.entry[menu->current].imap)
1054         mutt_error (_("Delete is only supported for IMAP mailboxes"));
1055       else {
1056         char msg[STRING];
1057         IMAP_MBOX mx;
1058         int nentry = menu->current;
1059
1060         imap_parse_path (state.entry[nentry].name, &mx);
1061         snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
1062                   mx.mbox);
1063         if (mutt_yesorno (msg, M_NO) == M_YES) {
1064           if (!imap_delete_mailbox (Context, mx)) {
1065             /* free the mailbox from the browser */
1066             p_delete(&((state.entry)[nentry].name));
1067             p_delete(&((state.entry)[nentry].desc));
1068             /* and move all other entries up */
1069             if (nentry + 1 < state.entrylen)
1070               memmove (state.entry + nentry, state.entry + nentry + 1,
1071                        sizeof (struct folder_file) * (state.entrylen -
1072                                                       (nentry + 1)));
1073             state.entrylen--;
1074             mutt_message _("Mailbox deleted.");
1075
1076             init_menu (&state, menu, title, sizeof (title), buffy);
1077             MAYBE_REDRAW (menu->redraw);
1078           }
1079         }
1080         else
1081           mutt_message _("Mailbox not deleted.");
1082         p_delete(&mx.mbox);
1083       }
1084       break;
1085
1086     case OP_CHANGE_DIRECTORY:
1087
1088 #ifdef USE_NNTP
1089       if (option (OPTNEWS))
1090         break;
1091 #endif
1092
1093       m_strcpy(buf, sizeof(buf), LastDir);
1094       if (!state.imap_browse)
1095       {
1096         /* add '/' at the end of the directory name if not already there */
1097         ssize_t len = m_strlen(LastDir);
1098
1099         if (len && LastDir[len - 1] != '/' && ssizeof(buf) > len)
1100           buf[len] = '/';
1101       }
1102
1103       if (mutt_get_field (_("Chdir to: "), buf, sizeof (buf), M_FILE) == 0 &&
1104           buf[0]) {
1105         buffy = 0;
1106         mutt_expand_path (buf, sizeof (buf));
1107         if (imap_is_magic (buf, NULL) == M_IMAP) {
1108           m_strcpy(LastDir, sizeof(LastDir), buf);
1109           destroy_state (&state);
1110           init_state (&state, NULL);
1111           state.imap_browse = 1;
1112           imap_browse (LastDir, &state);
1113           browser_sort (&state);
1114           menu->data = state.entry;
1115           menu->current = 0;
1116           menu->top = 0;
1117           init_menu (&state, menu, title, sizeof (title), buffy);
1118         }
1119         else
1120         if (stat (buf, &st) == 0) {
1121           if (S_ISDIR (st.st_mode)) {
1122             destroy_state (&state);
1123             if (examine_directory (menu, &state, buf, prefix) == 0)
1124               m_strcpy(LastDir, sizeof(LastDir), buf);
1125             else {
1126               mutt_error _("Error scanning directory.");
1127
1128               if (examine_directory (menu, &state, LastDir, prefix) == -1) {
1129                 mutt_menuDestroy (&menu);
1130                 goto bail;
1131               }
1132             }
1133             menu->current = 0;
1134             menu->top = 0;
1135             init_menu (&state, menu, title, sizeof (title), buffy);
1136           }
1137           else
1138             mutt_error (_("%s is not a directory."), buf);
1139         }
1140         else
1141           mutt_perror (buf);
1142       }
1143       MAYBE_REDRAW (menu->redraw);
1144       break;
1145
1146     case OP_ENTER_MASK:
1147
1148       m_strcpy(buf, sizeof(buf), NONULL(Mask.pattern));
1149       if (mutt_get_field (_("File Mask: "), buf, sizeof (buf), 0) == 0) {
1150         regex_t *rx = p_new(regex_t, 1);
1151         char *s = buf;
1152         int not = 0, err;
1153
1154         buffy = 0;
1155         /* assume that the user wants to see everything */
1156         if (!buf[0])
1157           m_strcpy(buf, sizeof(buf), ".");
1158         s = vskipspaces(s);
1159         if (*s == '!') {
1160           s = vskipspaces(s + 1);
1161           not = 1;
1162         }
1163
1164         if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
1165           regerror (err, rx, buf, sizeof (buf));
1166           regfree (rx);
1167           p_delete(&rx);
1168           mutt_error ("%s", buf);
1169         }
1170         else {
1171           m_strreplace(&Mask.pattern, buf);
1172           regfree (Mask.rx);
1173           p_delete(&Mask.rx);
1174           Mask.rx = rx;
1175           Mask.not = not;
1176
1177           destroy_state (&state);
1178           if (state.imap_browse) {
1179             init_state (&state, NULL);
1180             state.imap_browse = 1;
1181             imap_browse (LastDir, &state);
1182             browser_sort (&state);
1183             menu->data = state.entry;
1184             init_menu (&state, menu, title, sizeof (title), buffy);
1185           }
1186           else
1187           if (examine_directory (menu, &state, LastDir, NULL) == 0)
1188             init_menu (&state, menu, title, sizeof (title), buffy);
1189           else {
1190             mutt_error _("Error scanning directory.");
1191
1192             mutt_menuDestroy (&menu);
1193             goto bail;
1194           }
1195           killPrefix = 0;
1196           if (!state.entrylen) {
1197             mutt_error _("No files match the file mask");
1198
1199             break;
1200           }
1201         }
1202       }
1203       MAYBE_REDRAW (menu->redraw);
1204       break;
1205
1206     case OP_SORT:
1207     case OP_SORT_REVERSE:
1208
1209       {
1210         int resort = 1;
1211         int reverse = (i == OP_SORT_REVERSE);
1212
1213         switch (mutt_multi_choice ((reverse) ?
1214                                    _
1215                                    ("Reverse sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? ")
1216                                    :
1217                                    _
1218                                    ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "),
1219                                    _("dazn"))) {
1220         case -1:               /* abort */
1221           resort = 0;
1222           break;
1223
1224         case 1:                /* (d)ate */
1225           BrowserSort = SORT_DATE;
1226           break;
1227
1228         case 2:                /* (a)lpha */
1229           BrowserSort = SORT_SUBJECT;
1230           break;
1231
1232         case 3:                /* si(z)e */
1233           BrowserSort = SORT_SIZE;
1234           break;
1235
1236         case 4:                /* do(n)'t sort */
1237           BrowserSort = SORT_ORDER;
1238           resort = 0;
1239           break;
1240         }
1241         if (resort) {
1242           BrowserSort |= reverse ? SORT_REVERSE : 0;
1243           browser_sort (&state);
1244           menu->redraw = REDRAW_FULL;
1245         }
1246         break;
1247       }
1248
1249     case OP_TOGGLE_MAILBOXES:
1250       buffy = 1 - buffy;
1251
1252     case OP_CHECK_NEW:
1253       destroy_state (&state);
1254       prefix[0] = 0;
1255       killPrefix = 0;
1256
1257       if (buffy) {
1258         if (examine_mailboxes (menu, &state) == -1)
1259           goto bail;
1260       }
1261       else if (imap_is_magic (LastDir, NULL) == M_IMAP) {
1262         init_state (&state, NULL);
1263         state.imap_browse = 1;
1264         imap_browse (LastDir, &state);
1265         browser_sort (&state);
1266         menu->data = state.entry;
1267       }
1268       else if (examine_directory (menu, &state, LastDir, prefix) == -1)
1269         goto bail;
1270       init_menu (&state, menu, title, sizeof (title), buffy);
1271       break;
1272
1273     case OP_BUFFY_LIST:
1274       if (option (OPTFORCEBUFFYCHECK))
1275         buffy_check (1);
1276       buffy_list ();
1277       break;
1278
1279     case OP_BROWSER_NEW_FILE:
1280
1281       snprintf (buf, sizeof (buf), "%s/", LastDir);
1282       if (mutt_get_field (_("New file name: "), buf, sizeof (buf), M_FILE) ==
1283           0) {
1284         m_strcpy(f, flen, buf);
1285         destroy_state (&state);
1286         mutt_menuDestroy (&menu);
1287         goto bail;
1288       }
1289       MAYBE_REDRAW (menu->redraw);
1290       break;
1291
1292     case OP_BROWSER_VIEW_FILE:
1293       if (!state.entrylen) {
1294         mutt_error _("No files match the file mask");
1295
1296         break;
1297       }
1298
1299       if (state.entry[menu->current].selectable) {
1300         m_strcpy(f, flen, state.entry[menu->current].name);
1301         destroy_state (&state);
1302         mutt_menuDestroy (&menu);
1303         goto bail;
1304       }
1305       else
1306       if (S_ISDIR (state.entry[menu->current].mode) ||
1307             (S_ISLNK (state.entry[menu->current].mode) &&
1308                link_is_dir (LastDir, state.entry[menu->current].name))) {
1309         mutt_error _("Can't view a directory");
1310
1311         break;
1312       }
1313       else {
1314         BODY *b;
1315         char nbuf[_POSIX_PATH_MAX];
1316
1317         mutt_concat_path(nbuf, sizeof(nbuf), LastDir,
1318                          state.entry[menu->current].name);
1319         b = mutt_make_file_attach (nbuf);
1320         if (b != NULL) {
1321           mutt_view_attachment (NULL, b, M_REGULAR, NULL, NULL, 0);
1322           body_list_wipe(&b);
1323           menu->redraw = REDRAW_FULL;
1324         }
1325         else
1326           mutt_error _("Error trying to view file");
1327       }
1328       break;
1329
1330 #ifdef USE_NNTP
1331     case OP_CATCHUP:
1332     case OP_UNCATCHUP:
1333       if (option (OPTNEWS)) {
1334         struct folder_file *folder_f = &state.entry[menu->current];
1335         NNTP_DATA *nd;
1336
1337         if (i == OP_CATCHUP)
1338           nd = mutt_newsgroup_catchup (CurrentNewsSrv, folder_f->name);
1339         else
1340           nd = mutt_newsgroup_uncatchup (CurrentNewsSrv, folder_f->name);
1341
1342         if (nd) {
1343           if (menu->current + 1 < menu->max)
1344             menu->current++;
1345           menu->redraw = REDRAW_MOTION_RESYNCH;
1346         }
1347       }
1348       break;
1349
1350     case OP_LOAD_ACTIVE:
1351       if (!option (OPTNEWS))
1352         break;
1353
1354       {
1355         string_list_t *tmp;
1356         NNTP_DATA *data;
1357
1358         for (tmp = CurrentNewsSrv->list; tmp; tmp = tmp->next) {
1359           if ((data = (NNTP_DATA *) tmp->data))
1360             data->deleted = 1;
1361         }
1362       }
1363       nntp_get_active (CurrentNewsSrv);
1364
1365       destroy_state (&state);
1366       if (buffy)
1367         examine_mailboxes (menu, &state);
1368       else
1369         examine_directory (menu, &state, NULL, NULL);
1370       init_menu (&state, menu, title, sizeof (title), buffy);
1371       break;
1372 #endif /* USE_NNTP */
1373
1374     case OP_BROWSER_SUBSCRIBE:
1375     case OP_BROWSER_UNSUBSCRIBE:
1376 #ifdef USE_NNTP
1377     case OP_SUBSCRIBE_PATTERN:
1378     case OP_UNSUBSCRIBE_PATTERN:
1379       if (option (OPTNEWS)) {
1380         regex_t *rx = p_new(regex_t, 1);
1381         char *s = buf;
1382         int j = menu->current;
1383         NNTP_DATA *nd;
1384         NNTP_SERVER *news = CurrentNewsSrv;
1385
1386         if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN) {
1387           char tmp[STRING];
1388           int err;
1389
1390           buf[0] = 0;
1391           if (i == OP_SUBSCRIBE_PATTERN)
1392             snprintf (tmp, sizeof (tmp), _("Subscribe pattern: "));
1393           else
1394             snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: "));
1395           if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0]) {
1396             p_delete(&rx);
1397             break;
1398           }
1399
1400           if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
1401             regerror (err, rx, buf, sizeof (buf));
1402             regfree (rx);
1403             p_delete(&rx);
1404             mutt_error ("%s", buf);
1405             break;
1406           }
1407           menu->redraw = REDRAW_FULL;
1408           j = 0;
1409         }
1410         else if (!state.entrylen) {
1411           mutt_error _("No newsgroups match the mask");
1412
1413           break;
1414         }
1415
1416         for (; j < state.entrylen; j++) {
1417           struct folder_file *folderf = &state.entry[j];
1418
1419           if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE ||
1420               regexec (rx, folderf->name, 0, NULL, 0) == 0) {
1421             if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN)
1422               nd = mutt_newsgroup_subscribe (news, folderf->name);
1423             else
1424               nd = mutt_newsgroup_unsubscribe (news, folderf->name);
1425           }
1426           if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE) {
1427             if (menu->current + 1 < menu->max)
1428               menu->current++;
1429             menu->redraw = REDRAW_MOTION_RESYNCH;
1430             break;
1431           }
1432         }
1433         if (i == OP_SUBSCRIBE_PATTERN) {
1434           string_list_t *grouplist = NULL;
1435
1436           if (news)
1437             grouplist = news->list;
1438           for (; grouplist; grouplist = grouplist->next) {
1439             nd = (NNTP_DATA *) grouplist->data;
1440             if (nd && nd->group && !nd->subscribed) {
1441               if (regexec (rx, nd->group, 0, NULL, 0) == 0) {
1442                 mutt_newsgroup_subscribe (news, nd->group);
1443                 add_folder (menu, &state, nd->group, NULL, nd, nd->new);
1444               }
1445             }
1446           }
1447           init_menu (&state, menu, title, sizeof (title), buffy);
1448         }
1449         mutt_newsrc_update (news);
1450         nntp_clear_cacheindex (news);
1451         if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE)
1452           regfree (rx);
1453         p_delete(&rx);
1454       }
1455       else
1456 #endif /* USE_NNTP */
1457       {
1458         if (i == OP_BROWSER_SUBSCRIBE)
1459           imap_subscribe (state.entry[menu->current].name, 1);
1460         else
1461           imap_subscribe (state.entry[menu->current].name, 0);
1462       }
1463     }
1464   }
1465
1466 bail:
1467
1468   if (!folder)
1469     m_strcpy(LastDir, sizeof(LastDir), LastDirBackup);
1470
1471 }