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