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