f8fbb3dbaef00ad584625443fc39d998acfa07eb
[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 *ifstr,
151                                       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   if (optional)
304     m_strformat (dest, destlen, ifstr, folder_format_str, data, 0);
305   else if (flags & M_FORMAT_OPTIONAL)
306     m_strformat (dest, destlen, elstr, 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 *ifstr,
315                                          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, ifstr, newsgroup_format_str,
353                            data, flags);
354       else
355         m_strformat (dest, destlen, 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, NONULL (GroupFormat), newsgroup_format_str,
648                 (void*)&folder,
649                 option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
650   else
651 #endif
652     m_strformat(s, slen, NONULL (FolderFormat), folder_format_str,
653                (void *)&folder,
654                option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
655 }
656
657 static void init_menu (struct browser_state *state, MUTTMENU * menu,
658                        char *title, ssize_t titlelen, int buffy)
659 {
660   char path[_POSIX_PATH_MAX];
661
662   menu->max = state->entrylen;
663
664   if (menu->current >= menu->max)
665     menu->current = menu->max - 1;
666   if (menu->current < 0)
667     menu->current = 0;
668   if (menu->top > menu->current)
669     menu->top = 0;
670
671   menu->tagged = 0;
672
673 #ifdef USE_NNTP
674   if (option (OPTNEWS)) {
675     if (buffy)
676       snprintf (title, titlelen, "%s", _("Subscribed newsgroups"));
677     else
678       snprintf (title, titlelen, _("Newsgroups on server [%s]"),
679                 CurrentNewsSrv->conn->account.host);
680   }
681   else
682 #endif
683   if (buffy)
684     snprintf(title, titlelen, _("Mailboxes [%d]"), buffy_check(0));
685   else {
686     m_strcpy(path, sizeof(path), LastDir);
687     mutt_pretty_mailbox (path);
688     if (state->imap_browse && option (OPTIMAPLSUB))
689       snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
690                 path, NONULL (Mask.pattern));
691     else
692       snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
693                 path, NONULL (Mask.pattern));
694   }
695   menu->redraw = REDRAW_FULL;
696 }
697
698 static int file_tag (MUTTMENU * menu, int n, int m)
699 {
700   struct folder_file *ff = &(((struct folder_file *) menu->data)[n]);
701   int ot;
702
703   if (S_ISDIR (ff->mode)
704       || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name))) {
705     mutt_error _("Can't attach a directory!");
706
707     return 0;
708   }
709
710   ot = ff->tagged;
711   ff->tagged = (m >= 0 ? m : !ff->tagged);
712
713   return ff->tagged - ot;
714 }
715
716 void _mutt_select_file (char *f, ssize_t flen, int flags, char ***files,
717                         int *numfiles)
718 {
719   char buf[_POSIX_PATH_MAX];
720   char prefix[_POSIX_PATH_MAX] = "";
721   char helpstr[STRING];
722   char title[STRING];
723   struct browser_state state;
724   MUTTMENU *menu;
725   struct stat st;
726   int i, killPrefix = 0;
727   int multiple = (flags & M_SEL_MULTI) ? 1 : 0;
728   int folder = (flags & M_SEL_FOLDER) ? 1 : 0;
729   int buffy = (flags & M_SEL_BUFFY) ? 1 : 0;
730
731   buffy = buffy && folder;
732
733   p_clear(&state, 1);
734
735   if (!folder)
736     m_strcpy(LastDirBackup, sizeof(LastDirBackup), LastDir);
737
738 #ifdef USE_NNTP
739   if (option (OPTNEWS)) {
740     if (*f)
741       m_strcpy(prefix, sizeof(prefix), f);
742     else {
743       string_list_t *list;
744
745       /* default state for news reader mode is browse subscribed newsgroups */
746       buffy = 0;
747       for (list = CurrentNewsSrv->list; list; list = list->next) {
748         NNTP_DATA *data = (NNTP_DATA *) list->data;
749
750         if (data && data->subscribed) {
751           buffy = 1;
752           break;
753         }
754       }
755     }
756   }
757   else
758 #endif
759   if (*f) {
760     mutt_expand_path (f, flen);
761     if (imap_is_magic (f, NULL) == M_IMAP) {
762       init_state (&state, NULL);
763       state.imap_browse = 1;
764       if (!imap_browse (f, &state))
765         m_strcpy(LastDir, sizeof(LastDir), state.folder);
766     }
767     else {
768       for (i = m_strlen(f) - 1; i > 0 && f[i] != '/'; i--);
769       if (i > 0) {
770         if (f[0] == '/') {
771           i = MIN(ssizeof(LastDir) - 1, i);
772           m_strcpy(LastDir, sizeof(LastDir), f);
773         }
774         else {
775           getcwd(LastDir, sizeof(LastDir));
776           m_strcat(LastDir, sizeof(LastDir), "/");
777           m_strncat(LastDir, sizeof(LastDir), f, i);
778         }
779       }
780       else {
781         if (f[0] == '/')
782           m_strcpy(LastDir, sizeof(LastDir), "/");
783         else
784           getcwd (LastDir, sizeof (LastDir));
785       }
786
787       if (i <= 0 && f[0] != '/')
788         m_strcpy(prefix, sizeof(prefix), f);
789       else
790         m_strcpy(prefix, sizeof(prefix), f + i + 1);
791       killPrefix = 1;
792     }
793   }
794   else {
795     if (!folder)
796       getcwd (LastDir, sizeof (LastDir));
797     else if (!LastDir[0])
798       m_strcpy(LastDir, sizeof(LastDir), NONULL(Maildir));
799
800     if (!buffy && imap_is_magic (LastDir, NULL) == M_IMAP) {
801       init_state (&state, NULL);
802       state.imap_browse = 1;
803       imap_browse (LastDir, &state);
804       browser_sort (&state);
805     }
806   }
807
808   *f = 0;
809
810   if (buffy) {
811     if (examine_mailboxes (NULL, &state) == -1)
812       goto bail;
813   }
814   else
815   if (!state.imap_browse)
816     if (examine_directory (NULL, &state, LastDir, prefix) == -1)
817       goto bail;
818
819   menu = mutt_new_menu ();
820   menu->menu = MENU_FOLDER;
821   menu->make_entry = folder_entry;
822   menu->search = select_file_search;
823   menu->title = title;
824   menu->data = state.entry;
825   if (multiple)
826     menu->tag = file_tag;
827
828   menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER,
829 #ifdef USE_NNTP
830                                   (option (OPTNEWS)) ? FolderNewsHelp :
831 #endif
832                                   FolderHelp);
833
834   init_menu (&state, menu, title, sizeof (title), buffy);
835
836   for (;;) {
837     switch (i = mutt_menuLoop (menu)) {
838     case OP_GENERIC_SELECT_ENTRY:
839
840       if (!state.entrylen) {
841         mutt_error _("No files match the file mask");
842
843         break;
844       }
845
846       if (S_ISDIR (state.entry[menu->current].mode) ||
847           (S_ISLNK (state.entry[menu->current].mode) &&
848            link_is_dir (LastDir, state.entry[menu->current].name))
849           || state.entry[menu->current].inferiors
850         ) {
851         /* make sure this isn't a MH or maildir mailbox */
852         if (buffy) {
853           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
854           mutt_expand_path (buf, sizeof (buf));
855         }
856         else if (state.imap_browse) {
857           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
858         }
859         else
860           mutt_concat_path(buf, sizeof(buf), LastDir,
861                            state.entry[menu->current].name);
862
863         if ((mx_get_magic (buf) <= 0)
864             || state.entry[menu->current].inferiors)
865         {
866           char OldLastDir[_POSIX_PATH_MAX];
867
868           /* save the old directory */
869           m_strcpy(OldLastDir, sizeof(OldLastDir), LastDir);
870
871           if (m_strcmp(state.entry[menu->current].name, "..") == 0) {
872             if (m_strcmp("..", LastDir + m_strlen(LastDir) - 2) == 0)
873               m_strcat(LastDir, sizeof(LastDir), "/..");
874             else {
875               char *p = strrchr (LastDir + 1, '/');
876
877               if (p)
878                 *p = 0;
879               else {
880                 if (LastDir[0] == '/')
881                   LastDir[1] = 0;
882                 else
883                   m_strcat(LastDir, sizeof(LastDir), "/..");
884               }
885             }
886           }
887           else if (buffy) {
888             m_strcpy(LastDir, sizeof(LastDir),
889                      state.entry[menu->current].name);
890             mutt_expand_path (LastDir, sizeof (LastDir));
891           }
892           else if (state.imap_browse) {
893             int n;
894             ciss_url_t url;
895
896             m_strcpy(LastDir, sizeof(LastDir),
897                      state.entry[menu->current].name);
898             /* tack on delimiter here */
899             n = m_strlen(LastDir) + 1;
900
901             /* special case "" needs no delimiter */
902             url_parse_ciss (&url, state.entry[menu->current].name);
903             if (url.path &&
904                 (state.entry[menu->current].delim != '\0') &&
905                 (n < ssizeof (LastDir))) {
906               LastDir[n] = '\0';
907               LastDir[n - 1] = state.entry[menu->current].delim;
908             }
909           }
910           else {
911             char tmp[_POSIX_PATH_MAX];
912
913             mutt_concat_path(tmp, sizeof(tmp), LastDir,
914                              state.entry[menu->current].name);
915             m_strcpy(LastDir, sizeof(LastDir), tmp);
916           }
917
918           destroy_state (&state);
919           if (killPrefix) {
920             prefix[0] = 0;
921             killPrefix = 0;
922           }
923           buffy = 0;
924           if (state.imap_browse) {
925             init_state (&state, NULL);
926             state.imap_browse = 1;
927             imap_browse (LastDir, &state);
928             browser_sort (&state);
929             menu->data = state.entry;
930           }
931           else
932           if (examine_directory (menu, &state, LastDir, prefix) == -1) {
933             /* try to restore the old values */
934             m_strcpy(LastDir, sizeof(LastDir), OldLastDir);
935             if (examine_directory (menu, &state, LastDir, prefix) == -1) {
936               m_strcpy(LastDir, sizeof(LastDir), NONULL(Homedir));
937               goto bail;
938             }
939           }
940           menu->current = 0;
941           menu->top = 0;
942           init_menu (&state, menu, title, sizeof (title), buffy);
943           break;
944         }
945       }
946
947 #ifdef USE_NNTP
948       if (buffy || option (OPTNEWS))    /* news have not path */
949 #else
950       if (buffy)
951 #endif
952       {
953         m_strcpy(f, flen, state.entry[menu->current].name);
954         mutt_expand_path (f, flen);
955       }
956       else if (state.imap_browse)
957         m_strcpy(f, flen, state.entry[menu->current].name);
958       else
959         mutt_concat_path(f, flen, LastDir, state.entry[menu->current].name);
960
961       /* Fall through to OP_EXIT */
962
963     case OP_EXIT:
964
965       if (multiple) {
966         char **tfiles;
967         int j;
968         int h;
969
970         if (menu->tagged) {
971           *numfiles = menu->tagged;
972           tfiles = p_new(char *, *numfiles);
973           for (h = 0, j = 0; h < state.entrylen; i++) {
974             struct folder_file ff = state.entry[i];
975             char full[_POSIX_PATH_MAX];
976
977             if (ff.tagged) {
978               mutt_concat_path(full, sizeof(full), LastDir, ff.name);
979               mutt_expand_path (full, sizeof (full));
980               tfiles[j++] = m_strdup(full);
981             }
982           }
983           *files = tfiles;
984         }
985         else if (f[0]) {        /* no tagged entries. return selected entry */
986           *numfiles = 1;
987           tfiles = p_new(char *, *numfiles);
988           mutt_expand_path (f, flen);
989           tfiles[0] = m_strdup(f);
990           *files = tfiles;
991         }
992       }
993
994       destroy_state (&state);
995       mutt_menuDestroy (&menu);
996       goto bail;
997
998     case OP_BROWSER_TELL:
999       if (state.entrylen)
1000         mutt_message ("%s", state.entry[menu->current].name);
1001       break;
1002
1003     case OP_BROWSER_TOGGLE_LSUB:
1004       if (option (OPTIMAPLSUB)) {
1005         unset_option (OPTIMAPLSUB);
1006       }
1007       else {
1008         set_option (OPTIMAPLSUB);
1009       }
1010       mutt_ungetch (0, OP_CHECK_NEW);
1011       break;
1012
1013     case OP_CREATE_MAILBOX:
1014       if (!state.imap_browse)
1015         mutt_error (_("Create is only supported for IMAP mailboxes"));
1016       else {
1017         imap_mailbox_create (LastDir);
1018         /* TODO: find a way to detect if the new folder would appear in
1019          *   this window, and insert it without starting over. */
1020         destroy_state (&state);
1021         init_state (&state, NULL);
1022         state.imap_browse = 1;
1023         imap_browse (LastDir, &state);
1024         browser_sort (&state);
1025         menu->data = state.entry;
1026         menu->current = 0;
1027         menu->top = 0;
1028         init_menu (&state, menu, title, sizeof (title), buffy);
1029         MAYBE_REDRAW (menu->redraw);
1030       }
1031       break;
1032
1033     case OP_RENAME_MAILBOX:
1034       if (!state.entry[menu->current].imap)
1035         mutt_error (_("Rename is only supported for IMAP mailboxes"));
1036       else {
1037         int nentry = menu->current;
1038
1039         if (imap_mailbox_rename (state.entry[nentry].name) >= 0) {
1040           destroy_state (&state);
1041           init_state (&state, NULL);
1042           state.imap_browse = 1;
1043           imap_browse (LastDir, &state);
1044           browser_sort (&state);
1045           menu->data = state.entry;
1046           menu->current = 0;
1047           menu->top = 0;
1048           init_menu (&state, menu, title, sizeof (title), buffy);
1049           MAYBE_REDRAW (menu->redraw);
1050         }
1051       }
1052       break;
1053
1054     case OP_DELETE_MAILBOX:
1055       if (!state.entry[menu->current].imap)
1056         mutt_error (_("Delete is only supported for IMAP mailboxes"));
1057       else {
1058         char msg[STRING];
1059         IMAP_MBOX mx;
1060         int nentry = menu->current;
1061
1062         imap_parse_path (state.entry[nentry].name, &mx);
1063         snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
1064                   mx.mbox);
1065         if (mutt_yesorno (msg, M_NO) == M_YES) {
1066           if (!imap_delete_mailbox (Context, mx)) {
1067             /* free the mailbox from the browser */
1068             p_delete(&((state.entry)[nentry].name));
1069             p_delete(&((state.entry)[nentry].desc));
1070             /* and move all other entries up */
1071             if (nentry + 1 < state.entrylen)
1072               memmove (state.entry + nentry, state.entry + nentry + 1,
1073                        sizeof (struct folder_file) * (state.entrylen -
1074                                                       (nentry + 1)));
1075             state.entrylen--;
1076             mutt_message _("Mailbox deleted.");
1077
1078             init_menu (&state, menu, title, sizeof (title), buffy);
1079             MAYBE_REDRAW (menu->redraw);
1080           }
1081         }
1082         else
1083           mutt_message _("Mailbox not deleted.");
1084         p_delete(&mx.mbox);
1085       }
1086       break;
1087
1088     case OP_CHANGE_DIRECTORY:
1089
1090 #ifdef USE_NNTP
1091       if (option (OPTNEWS))
1092         break;
1093 #endif
1094
1095       m_strcpy(buf, sizeof(buf), LastDir);
1096       if (!state.imap_browse)
1097       {
1098         /* add '/' at the end of the directory name if not already there */
1099         ssize_t len = m_strlen(LastDir);
1100
1101         if (len && LastDir[len - 1] != '/' && ssizeof(buf) > len)
1102           buf[len] = '/';
1103       }
1104
1105       if (mutt_get_field (_("Chdir to: "), buf, sizeof (buf), M_FILE) == 0 &&
1106           buf[0]) {
1107         buffy = 0;
1108         mutt_expand_path (buf, sizeof (buf));
1109         if (imap_is_magic (buf, NULL) == M_IMAP) {
1110           m_strcpy(LastDir, sizeof(LastDir), buf);
1111           destroy_state (&state);
1112           init_state (&state, NULL);
1113           state.imap_browse = 1;
1114           imap_browse (LastDir, &state);
1115           browser_sort (&state);
1116           menu->data = state.entry;
1117           menu->current = 0;
1118           menu->top = 0;
1119           init_menu (&state, menu, title, sizeof (title), buffy);
1120         }
1121         else
1122         if (stat (buf, &st) == 0) {
1123           if (S_ISDIR (st.st_mode)) {
1124             destroy_state (&state);
1125             if (examine_directory (menu, &state, buf, prefix) == 0)
1126               m_strcpy(LastDir, sizeof(LastDir), buf);
1127             else {
1128               mutt_error _("Error scanning directory.");
1129
1130               if (examine_directory (menu, &state, LastDir, prefix) == -1) {
1131                 mutt_menuDestroy (&menu);
1132                 goto bail;
1133               }
1134             }
1135             menu->current = 0;
1136             menu->top = 0;
1137             init_menu (&state, menu, title, sizeof (title), buffy);
1138           }
1139           else
1140             mutt_error (_("%s is not a directory."), buf);
1141         }
1142         else
1143           mutt_perror (buf);
1144       }
1145       MAYBE_REDRAW (menu->redraw);
1146       break;
1147
1148     case OP_ENTER_MASK:
1149
1150       m_strcpy(buf, sizeof(buf), NONULL(Mask.pattern));
1151       if (mutt_get_field (_("File Mask: "), buf, sizeof (buf), 0) == 0) {
1152         regex_t *rx = p_new(regex_t, 1);
1153         char *s = buf;
1154         int not = 0, err;
1155
1156         buffy = 0;
1157         /* assume that the user wants to see everything */
1158         if (!buf[0])
1159           m_strcpy(buf, sizeof(buf), ".");
1160         s = vskipspaces(s);
1161         if (*s == '!') {
1162           s = vskipspaces(s + 1);
1163           not = 1;
1164         }
1165
1166         if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
1167           regerror (err, rx, buf, sizeof (buf));
1168           regfree (rx);
1169           p_delete(&rx);
1170           mutt_error ("%s", buf);
1171         }
1172         else {
1173           m_strreplace(&Mask.pattern, buf);
1174           regfree (Mask.rx);
1175           p_delete(&Mask.rx);
1176           Mask.rx = rx;
1177           Mask.not = not;
1178
1179           destroy_state (&state);
1180           if (state.imap_browse) {
1181             init_state (&state, NULL);
1182             state.imap_browse = 1;
1183             imap_browse (LastDir, &state);
1184             browser_sort (&state);
1185             menu->data = state.entry;
1186             init_menu (&state, menu, title, sizeof (title), buffy);
1187           }
1188           else
1189           if (examine_directory (menu, &state, LastDir, NULL) == 0)
1190             init_menu (&state, menu, title, sizeof (title), buffy);
1191           else {
1192             mutt_error _("Error scanning directory.");
1193
1194             mutt_menuDestroy (&menu);
1195             goto bail;
1196           }
1197           killPrefix = 0;
1198           if (!state.entrylen) {
1199             mutt_error _("No files match the file mask");
1200
1201             break;
1202           }
1203         }
1204       }
1205       MAYBE_REDRAW (menu->redraw);
1206       break;
1207
1208     case OP_SORT:
1209     case OP_SORT_REVERSE:
1210
1211       {
1212         int resort = 1;
1213         int reverse = (i == OP_SORT_REVERSE);
1214
1215         switch (mutt_multi_choice ((reverse) ?
1216                                    _
1217                                    ("Reverse sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? ")
1218                                    :
1219                                    _
1220                                    ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "),
1221                                    _("dazn"))) {
1222         case -1:               /* abort */
1223           resort = 0;
1224           break;
1225
1226         case 1:                /* (d)ate */
1227           BrowserSort = SORT_DATE;
1228           break;
1229
1230         case 2:                /* (a)lpha */
1231           BrowserSort = SORT_SUBJECT;
1232           break;
1233
1234         case 3:                /* si(z)e */
1235           BrowserSort = SORT_SIZE;
1236           break;
1237
1238         case 4:                /* do(n)'t sort */
1239           BrowserSort = SORT_ORDER;
1240           resort = 0;
1241           break;
1242         }
1243         if (resort) {
1244           BrowserSort |= reverse ? SORT_REVERSE : 0;
1245           browser_sort (&state);
1246           menu->redraw = REDRAW_FULL;
1247         }
1248         break;
1249       }
1250
1251     case OP_TOGGLE_MAILBOXES:
1252       buffy = 1 - buffy;
1253
1254     case OP_CHECK_NEW:
1255       destroy_state (&state);
1256       prefix[0] = 0;
1257       killPrefix = 0;
1258
1259       if (buffy) {
1260         if (examine_mailboxes (menu, &state) == -1)
1261           goto bail;
1262       }
1263       else if (imap_is_magic (LastDir, NULL) == M_IMAP) {
1264         init_state (&state, NULL);
1265         state.imap_browse = 1;
1266         imap_browse (LastDir, &state);
1267         browser_sort (&state);
1268         menu->data = state.entry;
1269       }
1270       else if (examine_directory (menu, &state, LastDir, prefix) == -1)
1271         goto bail;
1272       init_menu (&state, menu, title, sizeof (title), buffy);
1273       break;
1274
1275     case OP_BUFFY_LIST:
1276       if (option (OPTFORCEBUFFYCHECK))
1277         buffy_check (1);
1278       buffy_list ();
1279       break;
1280
1281     case OP_BROWSER_NEW_FILE:
1282
1283       snprintf (buf, sizeof (buf), "%s/", LastDir);
1284       if (mutt_get_field (_("New file name: "), buf, sizeof (buf), M_FILE) ==
1285           0) {
1286         m_strcpy(f, flen, buf);
1287         destroy_state (&state);
1288         mutt_menuDestroy (&menu);
1289         goto bail;
1290       }
1291       MAYBE_REDRAW (menu->redraw);
1292       break;
1293
1294     case OP_BROWSER_VIEW_FILE:
1295       if (!state.entrylen) {
1296         mutt_error _("No files match the file mask");
1297
1298         break;
1299       }
1300
1301       if (state.entry[menu->current].selectable) {
1302         m_strcpy(f, flen, state.entry[menu->current].name);
1303         destroy_state (&state);
1304         mutt_menuDestroy (&menu);
1305         goto bail;
1306       }
1307       else
1308       if (S_ISDIR (state.entry[menu->current].mode) ||
1309             (S_ISLNK (state.entry[menu->current].mode) &&
1310                link_is_dir (LastDir, state.entry[menu->current].name))) {
1311         mutt_error _("Can't view a directory");
1312
1313         break;
1314       }
1315       else {
1316         BODY *b;
1317         char nbuf[_POSIX_PATH_MAX];
1318
1319         mutt_concat_path(nbuf, sizeof(nbuf), LastDir,
1320                          state.entry[menu->current].name);
1321         b = mutt_make_file_attach (nbuf);
1322         if (b != NULL) {
1323           mutt_view_attachment (NULL, b, M_REGULAR, NULL, NULL, 0);
1324           body_list_wipe(&b);
1325           menu->redraw = REDRAW_FULL;
1326         }
1327         else
1328           mutt_error _("Error trying to view file");
1329       }
1330       break;
1331
1332 #ifdef USE_NNTP
1333     case OP_CATCHUP:
1334     case OP_UNCATCHUP:
1335       if (option (OPTNEWS)) {
1336         struct folder_file *folder_f = &state.entry[menu->current];
1337         NNTP_DATA *nd;
1338
1339         if (i == OP_CATCHUP)
1340           nd = mutt_newsgroup_catchup (CurrentNewsSrv, folder_f->name);
1341         else
1342           nd = mutt_newsgroup_uncatchup (CurrentNewsSrv, folder_f->name);
1343
1344         if (nd) {
1345           if (menu->current + 1 < menu->max)
1346             menu->current++;
1347           menu->redraw = REDRAW_MOTION_RESYNCH;
1348         }
1349       }
1350       break;
1351
1352     case OP_LOAD_ACTIVE:
1353       if (!option (OPTNEWS))
1354         break;
1355
1356       {
1357         string_list_t *tmp;
1358         NNTP_DATA *data;
1359
1360         for (tmp = CurrentNewsSrv->list; tmp; tmp = tmp->next) {
1361           if ((data = (NNTP_DATA *) tmp->data))
1362             data->deleted = 1;
1363         }
1364       }
1365       nntp_get_active (CurrentNewsSrv);
1366
1367       destroy_state (&state);
1368       if (buffy)
1369         examine_mailboxes (menu, &state);
1370       else
1371         examine_directory (menu, &state, NULL, NULL);
1372       init_menu (&state, menu, title, sizeof (title), buffy);
1373       break;
1374 #endif /* USE_NNTP */
1375
1376     case OP_BROWSER_SUBSCRIBE:
1377     case OP_BROWSER_UNSUBSCRIBE:
1378 #ifdef USE_NNTP
1379     case OP_SUBSCRIBE_PATTERN:
1380     case OP_UNSUBSCRIBE_PATTERN:
1381       if (option (OPTNEWS)) {
1382         regex_t *rx = p_new(regex_t, 1);
1383         char *s = buf;
1384         int j = menu->current;
1385         NNTP_DATA *nd;
1386         NNTP_SERVER *news = CurrentNewsSrv;
1387
1388         if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN) {
1389           char tmp[STRING];
1390           int err;
1391
1392           buf[0] = 0;
1393           if (i == OP_SUBSCRIBE_PATTERN)
1394             snprintf (tmp, sizeof (tmp), _("Subscribe pattern: "));
1395           else
1396             snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: "));
1397           if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0]) {
1398             p_delete(&rx);
1399             break;
1400           }
1401
1402           if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
1403             regerror (err, rx, buf, sizeof (buf));
1404             regfree (rx);
1405             p_delete(&rx);
1406             mutt_error ("%s", buf);
1407             break;
1408           }
1409           menu->redraw = REDRAW_FULL;
1410           j = 0;
1411         }
1412         else if (!state.entrylen) {
1413           mutt_error _("No newsgroups match the mask");
1414
1415           break;
1416         }
1417
1418         for (; j < state.entrylen; j++) {
1419           struct folder_file *folderf = &state.entry[j];
1420
1421           if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE ||
1422               regexec (rx, folderf->name, 0, NULL, 0) == 0) {
1423             if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN)
1424               nd = mutt_newsgroup_subscribe (news, folderf->name);
1425             else
1426               nd = mutt_newsgroup_unsubscribe (news, folderf->name);
1427           }
1428           if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE) {
1429             if (menu->current + 1 < menu->max)
1430               menu->current++;
1431             menu->redraw = REDRAW_MOTION_RESYNCH;
1432             break;
1433           }
1434         }
1435         if (i == OP_SUBSCRIBE_PATTERN) {
1436           string_list_t *grouplist = NULL;
1437
1438           if (news)
1439             grouplist = news->list;
1440           for (; grouplist; grouplist = grouplist->next) {
1441             nd = (NNTP_DATA *) grouplist->data;
1442             if (nd && nd->group && !nd->subscribed) {
1443               if (regexec (rx, nd->group, 0, NULL, 0) == 0) {
1444                 mutt_newsgroup_subscribe (news, nd->group);
1445                 add_folder (menu, &state, nd->group, NULL, nd, nd->new);
1446               }
1447             }
1448           }
1449           init_menu (&state, menu, title, sizeof (title), buffy);
1450         }
1451         mutt_newsrc_update (news);
1452         nntp_clear_cacheindex (news);
1453         if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE)
1454           regfree (rx);
1455         p_delete(&rx);
1456       }
1457       else
1458 #endif /* USE_NNTP */
1459       {
1460         if (i == OP_BROWSER_SUBSCRIBE)
1461           imap_subscribe (state.entry[menu->current].name, 1);
1462         else
1463           imap_subscribe (state.entry[menu->current].name, 0);
1464       }
1465     }
1466   }
1467
1468 bail:
1469
1470   if (!folder)
1471     m_strcpy(LastDir, sizeof(LastDir), LastDirBackup);
1472
1473 }