]> granicus.if.org Git - neomutt/blob - sidebar.c
Fix mutt_write_mime_body() application/pgp-encrypted handling
[neomutt] / sidebar.c
1 /**
2  * @file
3  * GUI display the mailboxes in a side panel
4  *
5  * @authors
6  * Copyright (C) 2004 Justin Hibbits <jrh29@po.cwru.edu>
7  * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
8  * Copyright (C) 2015-2016 Richard Russon <rich@flatcap.org>
9  * Copyright (C) 2016-2017 Kevin J. McCarthy <kevin@8t8.us>
10  *
11  * @copyright
12  * This program is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation, either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25
26 /**
27  * @page sidebar GUI display the mailboxes in a side panel
28  *
29  * GUI display the mailboxes in a side panel
30  */
31
32 #include "config.h"
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include "mutt/mutt.h"
38 #include "config/lib.h"
39 #include "core/lib.h"
40 #include "sidebar.h"
41 #include "color.h"
42 #include "context.h"
43 #include "curs_lib.h"
44 #include "format_flags.h"
45 #include "globals.h"
46 #include "mutt_curses.h"
47 #include "mutt_menu.h"
48 #include "mutt_window.h"
49 #include "muttlib.h"
50 #include "opcodes.h"
51
52 /* These Config Variables are only used in sidebar.c */
53 short C_SidebarComponentDepth; ///< Config: (sidebar) Strip leading path components from sidebar folders
54 char *C_SidebarDelimChars; ///< Config: (sidebar) Characters that separate nested folders
55 char *C_SidebarDividerChar; ///< Config: (sidebar) Character to draw between the sidebar and index
56 bool C_SidebarFolderIndent; ///< Config: (sidebar) Indent nested folders
57 char *C_SidebarFormat; ///< Config: (sidebar) printf-like format string for the sidebar panel
58 char *C_SidebarIndentString; ///< Config: (sidebar) Indent nested folders using this string
59 bool C_SidebarNewMailOnly; ///< Config: (sidebar) Only show folders with new/flagged mail
60 bool C_SidebarNonEmptyMailboxOnly; ///< Config: (sidebar) Only show folders with a non-zero number of mail
61 bool C_SidebarNextNewWrap; ///< Config: (sidebar) Wrap around when searching for the next mailbox with new mail
62 bool C_SidebarShortPath; ///< Config: (sidebar) Abbreviate the paths using the #C_Folder variable
63 short C_SidebarSortMethod; ///< Config: (sidebar) Method to sort the sidebar
64
65 /* Previous values for some sidebar config */
66 static short PreviousSort = SORT_ORDER; /* sidebar_sort_method */
67
68 /**
69  * struct SbEntry - Info about folders in the sidebar
70  */
71 struct SbEntry
72 {
73   char box[256];           /**< formatted mailbox name */
74   struct Mailbox *mailbox; /**< Mailbox this represents */
75   bool is_hidden;          /**< Don't show, e.g. $sidebar_new_mail_only */
76 };
77
78 static int EntryCount = 0;
79 static int EntryLen = 0;
80 static struct SbEntry **Entries = NULL;
81
82 static int TopIndex = -1; /**< First mailbox visible in sidebar */
83 static int OpnIndex = -1; /**< Current (open) mailbox */
84 static int HilIndex = -1; /**< Highlighted mailbox */
85 static int BotIndex = -1; /**< Last mailbox visible in sidebar */
86
87 /**
88  * enum DivType - Source of the sidebar divider character
89  */
90 enum DivType
91 {
92   SB_DIV_USER,  ///< User configured using $sidebar_divider_char
93   SB_DIV_ASCII, ///< An ASCII vertical bar (pipe)
94   SB_DIV_UTF8,  ///< A unicode line-drawing character
95 };
96
97 /**
98  * sidebar_format_str - Format a string for the sidebar - Implements ::format_t
99  *
100  * | Expando | Description
101  * |:--------|:--------------------------------------------------------
102  * | \%!     | 'n!' Flagged messages
103  * | \%B     | Name of the mailbox
104  * | \%D     | Description of the mailbox
105  * | \%d     | Number of deleted messages
106  * | \%F     | Number of Flagged messages in the mailbox
107  * | \%L     | Number of messages after limiting
108  * | \%n     | N if mailbox has new mail, blank otherwise
109  * | \%N     | Number of unread messages in the mailbox
110  * | \%S     | Size of mailbox (total number of messages)
111  * | \%t     | Number of tagged messages
112  */
113 static const char *sidebar_format_str(char *buf, size_t buflen, size_t col, int cols,
114                                       char op, const char *src, const char *prec,
115                                       const char *if_str, const char *else_str,
116                                       unsigned long data, MuttFormatFlags flags)
117 {
118   struct SbEntry *sbe = (struct SbEntry *) data;
119   char fmt[256];
120
121   if (!sbe || !buf)
122     return src;
123
124   buf[0] = '\0'; /* Just in case there's nothing to do */
125
126   struct Mailbox *m = sbe->mailbox;
127   if (!m)
128     return src;
129
130   bool c = Context && Context->mailbox &&
131            (mutt_str_strcmp(Context->mailbox->realpath, m->realpath) == 0);
132
133   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
134
135   switch (op)
136   {
137     case 'B':
138       mutt_format_s(buf, buflen, prec, sbe->box);
139       break;
140
141     case 'd':
142       if (!optional)
143       {
144         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
145         snprintf(buf, buflen, fmt, c ? Context->mailbox->msg_deleted : 0);
146       }
147       else if ((c && (Context->mailbox->msg_deleted == 0)) || !c)
148         optional = false;
149       break;
150
151     case 'D':
152       if (sbe->mailbox->name)
153         mutt_format_s(buf, buflen, prec, sbe->mailbox->name);
154       else
155         mutt_format_s(buf, buflen, prec, sbe->box);
156       break;
157
158     case 'F':
159       if (!optional)
160       {
161         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
162         snprintf(buf, buflen, fmt, m->msg_flagged);
163       }
164       else if (m->msg_flagged == 0)
165         optional = false;
166       break;
167
168     case 'L':
169       if (!optional)
170       {
171         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
172         snprintf(buf, buflen, fmt, c ? Context->mailbox->vcount : m->msg_count);
173       }
174       else if ((c && (Context->mailbox->vcount == m->msg_count)) || !c)
175         optional = false;
176       break;
177
178     case 'N':
179       if (!optional)
180       {
181         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
182         snprintf(buf, buflen, fmt, m->msg_unread);
183       }
184       else if (m->msg_unread == 0)
185         optional = false;
186       break;
187
188     case 'n':
189       if (!optional)
190       {
191         snprintf(fmt, sizeof(fmt), "%%%sc", prec);
192         snprintf(buf, buflen, fmt, m->has_new ? 'N' : ' ');
193       }
194       else if (m->has_new == false)
195         optional = false;
196       break;
197
198     case 'S':
199       if (!optional)
200       {
201         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
202         snprintf(buf, buflen, fmt, m->msg_count);
203       }
204       else if (m->msg_count == 0)
205         optional = false;
206       break;
207
208     case 't':
209       if (!optional)
210       {
211         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
212         snprintf(buf, buflen, fmt, c ? Context->mailbox->msg_tagged : 0);
213       }
214       else if ((c && (Context->mailbox->msg_tagged == 0)) || !c)
215         optional = false;
216       break;
217
218     case '!':
219       if (m->msg_flagged == 0)
220         mutt_format_s(buf, buflen, prec, "");
221       else if (m->msg_flagged == 1)
222         mutt_format_s(buf, buflen, prec, "!");
223       else if (m->msg_flagged == 2)
224         mutt_format_s(buf, buflen, prec, "!!");
225       else
226       {
227         snprintf(fmt, sizeof(fmt), "%d!", m->msg_flagged);
228         mutt_format_s(buf, buflen, prec, fmt);
229       }
230       break;
231   }
232
233   if (optional)
234   {
235     mutt_expando_format(buf, buflen, col, C_SidebarWidth, if_str,
236                         sidebar_format_str, (unsigned long) sbe, flags);
237   }
238   else if (flags & MUTT_FORMAT_OPTIONAL)
239   {
240     mutt_expando_format(buf, buflen, col, C_SidebarWidth, else_str,
241                         sidebar_format_str, (unsigned long) sbe, flags);
242   }
243
244   /* We return the format string, unchanged */
245   return src;
246 }
247
248 /**
249  * make_sidebar_entry - Turn mailbox data into a sidebar string
250  * @param[out] buf     Buffer in which to save string
251  * @param[in]  buflen  Buffer length
252  * @param[in]  width   Desired width in screen cells
253  * @param[in]  box     Mailbox name
254  * @param[in]  sbe     Mailbox object
255  *
256  * Take all the relevant mailbox data and the desired screen width and then get
257  * mutt_expando_format to do the actual work. mutt_expando_format will callback to
258  * us using sidebar_format_str() for the sidebar specific formatting characters.
259  */
260 static void make_sidebar_entry(char *buf, size_t buflen, int width,
261                                const char *box, struct SbEntry *sbe)
262 {
263   if (!buf || !box || !sbe)
264     return;
265
266   mutt_str_strfcpy(sbe->box, box, sizeof(sbe->box));
267
268   mutt_expando_format(buf, buflen, 0, width, NONULL(C_SidebarFormat),
269                       sidebar_format_str, (unsigned long) sbe, MUTT_FORMAT_NO_FLAGS);
270
271   /* Force string to be exactly the right width */
272   int w = mutt_strwidth(buf);
273   int s = mutt_str_strlen(buf);
274   width = MIN(buflen, width);
275   if (w < width)
276   {
277     /* Pad with spaces */
278     memset(buf + s, ' ', width - w);
279     buf[s + width - w] = '\0';
280   }
281   else if (w > width)
282   {
283     /* Truncate to fit */
284     size_t len = mutt_wstr_trunc(buf, buflen, width, NULL);
285     buf[len] = '\0';
286   }
287 }
288
289 /**
290  * cb_qsort_sbe - qsort callback to sort SbEntry's
291  * @param a First  SbEntry to compare
292  * @param b Second SbEntry to compare
293  * @retval -1 a precedes b
294  * @retval  0 a and b are identical
295  * @retval  1 b precedes a
296  */
297 static int cb_qsort_sbe(const void *a, const void *b)
298 {
299   const struct SbEntry *sbe1 = *(struct SbEntry const *const *) a;
300   const struct SbEntry *sbe2 = *(struct SbEntry const *const *) b;
301   const struct Mailbox *m1 = sbe1->mailbox;
302   const struct Mailbox *m2 = sbe2->mailbox;
303
304   int rc = 0;
305
306   switch ((C_SidebarSortMethod & SORT_MASK))
307   {
308     case SORT_COUNT:
309       if (m2->msg_count == m1->msg_count)
310         rc = mutt_str_strcoll(mailbox_path(m1), mailbox_path(m2));
311       else
312         rc = (m2->msg_count - m1->msg_count);
313       break;
314     case SORT_UNREAD:
315       if (m2->msg_unread == m1->msg_unread)
316         rc = mutt_str_strcoll(mailbox_path(m1), mailbox_path(m2));
317       else
318         rc = (m2->msg_unread - m1->msg_unread);
319       break;
320     case SORT_DESC:
321       rc = mutt_str_strcmp(m1->name, m2->name);
322       break;
323     case SORT_FLAGGED:
324       if (m2->msg_flagged == m1->msg_flagged)
325         rc = mutt_str_strcoll(mailbox_path(m1), mailbox_path(m2));
326       else
327         rc = (m2->msg_flagged - m1->msg_flagged);
328       break;
329     case SORT_PATH:
330     {
331       rc = mutt_inbox_cmp(mailbox_path(m1), mailbox_path(m2));
332       if (rc == 0)
333         rc = mutt_str_strcoll(mailbox_path(m1), mailbox_path(m2));
334       break;
335     }
336   }
337
338   if (C_SidebarSortMethod & SORT_REVERSE)
339     rc = -rc;
340
341   return rc;
342 }
343
344 /**
345  * update_entries_visibility - Should a sidebar_entry be displayed in the sidebar
346  *
347  * For each SbEntry in the Entries array, check whether we should display it.
348  * This is determined by several criteria.  If the Mailbox:
349  * * is the currently open mailbox
350  * * is the currently highlighted mailbox
351  * * has unread messages
352  * * has flagged messages
353  * * is whitelisted
354  */
355 static void update_entries_visibility(void)
356 {
357   /* Aliases for readability */
358   const bool new_only = C_SidebarNewMailOnly;
359   const bool non_empty_only = C_SidebarNonEmptyMailboxOnly;
360   struct SbEntry *sbe = NULL;
361
362   /* Take the fast path if there is no need to test visibilities */
363   if (!new_only && !non_empty_only)
364   {
365     for (int i = 0; i < EntryCount; i++)
366     {
367       Entries[i]->is_hidden = false;
368     }
369     return;
370   }
371
372   for (int i = 0; i < EntryCount; i++)
373   {
374     sbe = Entries[i];
375
376     sbe->is_hidden = false;
377
378     if (Context && (mutt_str_strcmp(sbe->mailbox->realpath, Context->mailbox->realpath) == 0))
379     {
380       /* Spool directories are always visible */
381       continue;
382     }
383
384     if (mutt_list_find(&SidebarWhitelist, mailbox_path(sbe->mailbox)) ||
385         mutt_list_find(&SidebarWhitelist, sbe->mailbox->name))
386     {
387       /* Explicitly asked to be visible */
388       continue;
389     }
390
391     if (non_empty_only && (i != OpnIndex) && (sbe->mailbox->msg_count == 0))
392     {
393       sbe->is_hidden = true;
394     }
395
396     if (new_only && (i != OpnIndex) && (sbe->mailbox->msg_unread == 0) &&
397         (sbe->mailbox->msg_flagged == 0) && !sbe->mailbox->has_new)
398     {
399       sbe->is_hidden = true;
400     }
401   }
402 }
403
404 /**
405  * unsort_entries - Restore Entries array order to match Mailbox list order
406  */
407 static void unsort_entries(void)
408 {
409   int i = 0;
410
411   struct MailboxList ml = neomutt_mailboxlist_get_all(NeoMutt, MUTT_MAILBOX_ANY);
412   struct MailboxNode *np = NULL;
413   STAILQ_FOREACH(np, &ml, entries)
414   {
415     if (i >= EntryCount)
416       break;
417
418     int j = i;
419     while ((j < EntryCount) && (Entries[j]->mailbox != np->mailbox))
420       j++;
421     if (j < EntryCount)
422     {
423       if (j != i)
424       {
425         struct SbEntry *tmp = Entries[i];
426         Entries[i] = Entries[j];
427         Entries[j] = tmp;
428       }
429       i++;
430     }
431   }
432   neomutt_mailboxlist_clear(&ml);
433 }
434
435 /**
436  * sort_entries - Sort Entries array
437  *
438  * Sort the Entries array according to the current sort config
439  * option "sidebar_sort_method". This calls qsort to do the work which calls our
440  * callback function "cb_qsort_sbe".
441  *
442  * Once sorted, the prev/next links will be reconstructed.
443  */
444 static void sort_entries(void)
445 {
446   enum SortType ssm = (C_SidebarSortMethod & SORT_MASK);
447
448   /* These are the only sort methods we understand */
449   if ((ssm == SORT_COUNT) || (ssm == SORT_UNREAD) || (ssm == SORT_FLAGGED) || (ssm == SORT_PATH))
450     qsort(Entries, EntryCount, sizeof(*Entries), cb_qsort_sbe);
451   else if ((ssm == SORT_ORDER) && (C_SidebarSortMethod != PreviousSort))
452     unsort_entries();
453 }
454
455 /**
456  * select_next - Selects the next unhidden mailbox
457  * @retval true  Success
458  * @retval false Failure
459  */
460 static bool select_next(void)
461 {
462   int entry = HilIndex;
463
464   if (!EntryCount || (HilIndex < 0))
465     return false;
466
467   do
468   {
469     entry++;
470     if (entry == EntryCount)
471       return false;
472   } while (Entries[entry]->is_hidden);
473
474   HilIndex = entry;
475   return true;
476 }
477
478 /**
479  * select_next_new - Selects the next new mailbox
480  * @retval true  Success
481  * @retval false Failure
482  *
483  * Search down the list of mail folders for one containing new mail.
484  */
485 static int select_next_new(void)
486 {
487   int entry = HilIndex;
488
489   if (!EntryCount || (HilIndex < 0))
490     return false;
491
492   do
493   {
494     entry++;
495     if (entry == EntryCount)
496     {
497       if (C_SidebarNextNewWrap)
498         entry = 0;
499       else
500         return false;
501     }
502     if (entry == HilIndex)
503       return false;
504   } while (!Entries[entry]->mailbox->has_new && (Entries[entry]->mailbox->msg_unread == 0));
505
506   HilIndex = entry;
507   return true;
508 }
509
510 /**
511  * select_prev - Selects the previous unhidden mailbox
512  * @retval true  Success
513  * @retval false Failure
514  */
515 static bool select_prev(void)
516 {
517   int entry = HilIndex;
518
519   if (!EntryCount || (HilIndex < 0))
520     return false;
521
522   do
523   {
524     entry--;
525     if (entry < 0)
526       return false;
527   } while (Entries[entry]->is_hidden);
528
529   HilIndex = entry;
530   return true;
531 }
532
533 /**
534  * select_prev_new - Selects the previous new mailbox
535  * @retval true  Success
536  * @retval false Failure
537  *
538  * Search up the list of mail folders for one containing new mail.
539  */
540 static bool select_prev_new(void)
541 {
542   int entry = HilIndex;
543
544   if (!EntryCount || (HilIndex < 0))
545     return false;
546
547   do
548   {
549     entry--;
550     if (entry < 0)
551     {
552       if (C_SidebarNextNewWrap)
553         entry = EntryCount - 1;
554       else
555         return false;
556     }
557     if (entry == HilIndex)
558       return false;
559   } while (!Entries[entry]->mailbox->has_new && (Entries[entry]->mailbox->msg_unread == 0));
560
561   HilIndex = entry;
562   return true;
563 }
564
565 /**
566  * select_page_down - Selects the first entry in the next page of mailboxes
567  * @retval true  Success
568  * @retval false Failure
569  */
570 static int select_page_down(void)
571 {
572   int orig_hil_index = HilIndex;
573
574   if (!EntryCount || (BotIndex < 0))
575     return 0;
576
577   HilIndex = BotIndex;
578   select_next();
579   /* If the rest of the entries are hidden, go up to the last unhidden one */
580   if (Entries[HilIndex]->is_hidden)
581     select_prev();
582
583   return orig_hil_index != HilIndex;
584 }
585
586 /**
587  * select_page_up - Selects the last entry in the previous page of mailboxes
588  * @retval true  Success
589  * @retval false Failure
590  */
591 static int select_page_up(void)
592 {
593   int orig_hil_index = HilIndex;
594
595   if (!EntryCount || (TopIndex < 0))
596     return 0;
597
598   HilIndex = TopIndex;
599   select_prev();
600   /* If the rest of the entries are hidden, go down to the last unhidden one */
601   if (Entries[HilIndex]->is_hidden)
602     select_next();
603
604   return orig_hil_index != HilIndex;
605 }
606
607 /**
608  * prepare_sidebar - Prepare the list of SbEntry's for the sidebar display
609  * @param page_size  The number of lines on a page
610  * @retval false No, don't draw the sidebar
611  * @retval true  Yes, draw the sidebar
612  *
613  * Before painting the sidebar, we determine which are visible, sort
614  * them and set up our page pointers.
615  *
616  * This is a lot of work to do each refresh, but there are many things that
617  * can change outside of the sidebar that we don't hear about.
618  */
619 static bool prepare_sidebar(int page_size)
620 {
621   if (!EntryCount || (page_size <= 0))
622     return false;
623
624   const struct SbEntry *opn_entry = (OpnIndex >= 0) ? Entries[OpnIndex] : NULL;
625   const struct SbEntry *hil_entry = (HilIndex >= 0) ? Entries[HilIndex] : NULL;
626
627   update_entries_visibility();
628   sort_entries();
629
630   for (int i = 0; i < EntryCount; i++)
631   {
632     if (opn_entry == Entries[i])
633       OpnIndex = i;
634     if (hil_entry == Entries[i])
635       HilIndex = i;
636   }
637
638   if ((HilIndex < 0) || Entries[HilIndex]->is_hidden || (C_SidebarSortMethod != PreviousSort))
639   {
640     if (OpnIndex >= 0)
641       HilIndex = OpnIndex;
642     else
643     {
644       HilIndex = 0;
645       if (Entries[HilIndex]->is_hidden)
646         select_next();
647     }
648   }
649
650   /* Set the Top and Bottom to frame the HilIndex in groups of page_size */
651
652   /* If C_SidebarNewMailOnly or C_SidebarNonEmptyMailboxOnly is set, some entries
653    * may be hidden so we need to scan for the framing interval */
654   if (C_SidebarNewMailOnly || C_SidebarNonEmptyMailboxOnly)
655   {
656     TopIndex = -1;
657     BotIndex = -1;
658     while (BotIndex < HilIndex)
659     {
660       TopIndex = BotIndex + 1;
661       int page_entries = 0;
662       while (page_entries < page_size)
663       {
664         BotIndex++;
665         if (BotIndex >= EntryCount)
666           break;
667         if (!Entries[BotIndex]->is_hidden)
668           page_entries++;
669       }
670     }
671   }
672   /* Otherwise we can just calculate the interval */
673   else
674   {
675     TopIndex = (HilIndex / page_size) * page_size;
676     BotIndex = TopIndex + page_size - 1;
677   }
678
679   if (BotIndex > (EntryCount - 1))
680     BotIndex = EntryCount - 1;
681
682   PreviousSort = C_SidebarSortMethod;
683   return true;
684 }
685
686 /**
687  * draw_divider - Draw a line between the sidebar and the rest of neomutt
688  * @param num_rows   Height of the Sidebar
689  * @param num_cols   Width of the Sidebar
690  * @retval 0   Empty string
691  * @retval num Character occupies n screen columns
692  *
693  * Draw a divider using characters from the config option "sidebar_divider_char".
694  * This can be an ASCII or Unicode character.
695  * We calculate these characters' width in screen columns.
696  *
697  * If the user hasn't set $sidebar_divider_char we pick a character for them,
698  * respecting the value of $ascii_chars.
699  */
700 static int draw_divider(int num_rows, int num_cols)
701 {
702   if ((num_rows < 1) || (num_cols < 1))
703     return 0;
704
705   int delim_len;
706   enum DivType altchar = SB_DIV_UTF8;
707
708   /* Calculate the width of the delimiter in screen cells */
709   delim_len = mutt_strwidth(C_SidebarDividerChar);
710   if (delim_len < 0)
711   {
712     delim_len = 1; /* Bad character */
713   }
714   else if (delim_len == 0)
715   {
716     if (C_SidebarDividerChar)
717       return 0; /* User has set empty string */
718
719     delim_len = 1; /* Unset variable */
720   }
721   else
722   {
723     altchar = SB_DIV_USER; /* User config */
724   }
725
726   if (C_AsciiChars && (altchar != SB_DIV_ASCII))
727   {
728     /* $ascii_chars overrides Unicode divider chars */
729     if (altchar == SB_DIV_UTF8)
730     {
731       altchar = SB_DIV_ASCII;
732     }
733     else if (C_SidebarDividerChar)
734     {
735       for (int i = 0; i < delim_len; i++)
736       {
737         if (C_SidebarDividerChar[i] & ~0x7F) /* high-bit is set */
738         {
739           altchar = SB_DIV_ASCII;
740           delim_len = 1;
741           break;
742         }
743       }
744     }
745   }
746
747   if (delim_len > num_cols)
748     return 0;
749
750   mutt_curses_set_color(MT_COLOR_DIVIDER);
751
752   int col = C_SidebarOnRight ? 0 : (C_SidebarWidth - delim_len);
753
754   for (int i = 0; i < num_rows; i++)
755   {
756     mutt_window_move(MuttSidebarWindow, i, col);
757
758     switch (altchar)
759     {
760       case SB_DIV_USER:
761         mutt_window_addstr(NONULL(C_SidebarDividerChar));
762         break;
763       case SB_DIV_ASCII:
764         mutt_window_addch('|');
765         break;
766       case SB_DIV_UTF8:
767         mutt_window_addch(ACS_VLINE);
768         break;
769     }
770   }
771
772   return delim_len;
773 }
774
775 /**
776  * fill_empty_space - Wipe the remaining Sidebar space
777  * @param first_row  Window line to start (0-based)
778  * @param num_rows   Number of rows to fill
779  * @param div_width  Width in screen characters taken by the divider
780  * @param num_cols   Number of columns to fill
781  *
782  * Write spaces over the area the sidebar isn't using.
783  */
784 static void fill_empty_space(int first_row, int num_rows, int div_width, int num_cols)
785 {
786   /* Fill the remaining rows with blank space */
787   mutt_curses_set_color(MT_COLOR_NORMAL);
788
789   if (!C_SidebarOnRight)
790     div_width = 0;
791   for (int r = 0; r < num_rows; r++)
792   {
793     mutt_window_move(MuttSidebarWindow, first_row + r, div_width);
794
795     for (int i = 0; i < num_cols; i++)
796       mutt_window_addch(' ');
797   }
798 }
799
800 /**
801  * draw_sidebar - Write out a list of mailboxes, in a panel
802  * @param num_rows   Height of the Sidebar
803  * @param num_cols   Width of the Sidebar
804  * @param div_width  Width in screen characters taken by the divider
805  *
806  * Display a list of mailboxes in a panel on the left.  What's displayed will
807  * depend on our index markers: TopMailbox, OpnMailbox, HilMailbox, BotMailbox.
808  * On the first run they'll be NULL, so we display the top of NeoMutt's list.
809  *
810  * * TopMailbox - first visible mailbox
811  * * BotMailbox - last  visible mailbox
812  * * OpnMailbox - mailbox shown in NeoMutt's Index Panel
813  * * HilMailbox - Unselected mailbox (the paging follows this)
814  *
815  * The entries are formatted using "sidebar_format" and may be abbreviated:
816  * "sidebar_short_path", indented: "sidebar_folder_indent",
817  * "sidebar_indent_string" and sorted: "sidebar_sort_method".  Finally, they're
818  * trimmed to fit the available space.
819  */
820 static void draw_sidebar(int num_rows, int num_cols, int div_width)
821 {
822   struct SbEntry *entry = NULL;
823   struct Mailbox *m = NULL;
824   if (TopIndex < 0)
825     return;
826
827   int w = MIN(num_cols, (C_SidebarWidth - div_width));
828   int row = 0;
829   for (int entryidx = TopIndex; (entryidx < EntryCount) && (row < num_rows); entryidx++)
830   {
831     entry = Entries[entryidx];
832     if (entry->is_hidden)
833       continue;
834     m = entry->mailbox;
835
836     if (entryidx == OpnIndex)
837     {
838       if ((ColorDefs[MT_COLOR_SB_INDICATOR] != 0))
839         mutt_curses_set_color(MT_COLOR_SB_INDICATOR);
840       else
841         mutt_curses_set_color(MT_COLOR_INDICATOR);
842     }
843     else if (entryidx == HilIndex)
844       mutt_curses_set_color(MT_COLOR_HIGHLIGHT);
845     else if (m->has_new)
846       mutt_curses_set_color(MT_COLOR_NEW);
847     else if (m->msg_flagged > 0)
848       mutt_curses_set_color(MT_COLOR_FLAGGED);
849     else if ((ColorDefs[MT_COLOR_SB_SPOOLFILE] != 0) &&
850              (mutt_str_strcmp(mailbox_path(m), C_Spoolfile) == 0))
851     {
852       mutt_curses_set_color(MT_COLOR_SB_SPOOLFILE);
853     }
854     else
855     {
856       if (ColorDefs[MT_COLOR_ORDINARY] != 0)
857         mutt_curses_set_color(MT_COLOR_ORDINARY);
858       else
859         mutt_curses_set_color(MT_COLOR_NORMAL);
860     }
861
862     int col = 0;
863     if (C_SidebarOnRight)
864       col = div_width;
865
866     mutt_window_move(MuttSidebarWindow, row, col);
867     if (Context && Context->mailbox && (Context->mailbox->realpath[0] != '\0') &&
868         (mutt_str_strcmp(m->realpath, Context->mailbox->realpath) == 0))
869     {
870       m->msg_unread = Context->mailbox->msg_unread;
871       m->msg_count = Context->mailbox->msg_count;
872       m->msg_flagged = Context->mailbox->msg_flagged;
873     }
874
875     /* compute length of C_Folder without trailing separator */
876     size_t maildirlen = mutt_str_strlen(C_Folder);
877     if (maildirlen && C_SidebarDelimChars &&
878         strchr(C_SidebarDelimChars, C_Folder[maildirlen - 1]))
879       maildirlen--;
880
881     /* check whether C_Folder is a prefix of the current folder's path */
882     bool maildir_is_prefix = false;
883     if ((mutt_buffer_len(&m->pathbuf) > maildirlen) &&
884         (mutt_str_strncmp(C_Folder, mailbox_path(m), maildirlen) == 0) &&
885         C_SidebarDelimChars && strchr(C_SidebarDelimChars, mailbox_path(m)[maildirlen]))
886     {
887       maildir_is_prefix = true;
888     }
889
890     /* calculate depth of current folder and generate its display name with indented spaces */
891     int sidebar_folder_depth = 0;
892     const char *sidebar_folder_name = NULL;
893     struct Buffer *short_folder_name = NULL;
894     if (C_SidebarShortPath)
895     {
896       /* disregard a trailing separator, so strlen() - 2 */
897       sidebar_folder_name = mailbox_path(m);
898       for (int i = mutt_str_strlen(sidebar_folder_name) - 2; i >= 0; i--)
899       {
900         if (C_SidebarDelimChars && strchr(C_SidebarDelimChars, sidebar_folder_name[i]))
901         {
902           sidebar_folder_name += (i + 1);
903           break;
904         }
905       }
906     }
907     else if ((C_SidebarComponentDepth > 0) && C_SidebarDelimChars)
908     {
909       sidebar_folder_name = mailbox_path(m) + maildir_is_prefix * (maildirlen + 1);
910       for (int i = 0; i < C_SidebarComponentDepth; i++)
911       {
912         char *chars_after_delim = strpbrk(sidebar_folder_name, C_SidebarDelimChars);
913         if (!chars_after_delim)
914           break;
915         else
916           sidebar_folder_name = chars_after_delim + 1;
917       }
918     }
919     else
920       sidebar_folder_name = mailbox_path(m) + maildir_is_prefix * (maildirlen + 1);
921
922     if (m->name)
923     {
924       sidebar_folder_name = m->name;
925     }
926     else if (maildir_is_prefix && C_SidebarFolderIndent)
927     {
928       int lastsep = 0;
929       const char *tmp_folder_name = mailbox_path(m) + maildirlen + 1;
930       int tmplen = (int) mutt_str_strlen(tmp_folder_name) - 1;
931       for (int i = 0; i < tmplen; i++)
932       {
933         if (C_SidebarDelimChars && strchr(C_SidebarDelimChars, tmp_folder_name[i]))
934         {
935           sidebar_folder_depth++;
936           lastsep = i + 1;
937         }
938       }
939       if (sidebar_folder_depth > 0)
940       {
941         if (C_SidebarShortPath)
942           tmp_folder_name += lastsep; /* basename */
943         short_folder_name = mutt_buffer_pool_get();
944         for (int i = 0; i < sidebar_folder_depth; i++)
945           mutt_buffer_addstr(short_folder_name, NONULL(C_SidebarIndentString));
946         mutt_buffer_addstr(short_folder_name, tmp_folder_name);
947         sidebar_folder_name = mutt_b2s(short_folder_name);
948       }
949     }
950     char str[256];
951     make_sidebar_entry(str, sizeof(str), w, sidebar_folder_name, entry);
952     mutt_window_printf("%s", str);
953     mutt_buffer_pool_release(&short_folder_name);
954     row++;
955   }
956
957   fill_empty_space(row, num_rows - row, div_width, w);
958 }
959
960 /**
961  * mutt_sb_draw - Completely redraw the sidebar
962  *
963  * Completely refresh the sidebar region.  First draw the divider; then, for
964  * each Mailbox, call make_sidebar_entry; finally blank out any remaining space.
965  */
966 void mutt_sb_draw(void)
967 {
968   if (!C_SidebarVisible)
969     return;
970
971   int row = 0, col = 0;
972   mutt_window_get_coords(MuttSidebarWindow, &row, &col);
973
974   int num_rows = MuttSidebarWindow->rows;
975   int num_cols = MuttSidebarWindow->cols;
976
977   int div_width = draw_divider(num_rows, num_cols);
978
979   if (!Entries)
980   {
981     struct MailboxList ml = neomutt_mailboxlist_get_all(NeoMutt, MUTT_MAILBOX_ANY);
982     struct MailboxNode *np = NULL;
983     STAILQ_FOREACH(np, &ml, entries)
984     {
985       mutt_sb_notify_mailbox(np->mailbox, true);
986     }
987     neomutt_mailboxlist_clear(&ml);
988   }
989
990   if (!prepare_sidebar(num_rows))
991   {
992     fill_empty_space(0, num_rows, div_width, num_cols - div_width);
993     return;
994   }
995
996   draw_sidebar(num_rows, num_cols, div_width);
997   mutt_window_move(MuttSidebarWindow, row, col);
998 }
999
1000 /**
1001  * mutt_sb_change_mailbox - Change the selected mailbox
1002  * @param op Operation code
1003  *
1004  * Change the selected mailbox, e.g. "Next mailbox", "Previous Mailbox
1005  * with new mail". The operations are listed in opcodes.h.
1006  *
1007  * If the operation is successful, HilMailbox will be set to the new mailbox.
1008  * This function only *selects* the mailbox, doesn't *open* it.
1009  *
1010  * Allowed values are: OP_SIDEBAR_NEXT, OP_SIDEBAR_NEXT_NEW,
1011  * OP_SIDEBAR_PAGE_DOWN, OP_SIDEBAR_PAGE_UP, OP_SIDEBAR_PREV,
1012  * OP_SIDEBAR_PREV_NEW.
1013  */
1014 void mutt_sb_change_mailbox(int op)
1015 {
1016   if (!C_SidebarVisible)
1017     return;
1018
1019   if (HilIndex < 0) /* It'll get reset on the next draw */
1020     return;
1021
1022   switch (op)
1023   {
1024     case OP_SIDEBAR_NEXT:
1025       if (!select_next())
1026         return;
1027       break;
1028     case OP_SIDEBAR_NEXT_NEW:
1029       if (!select_next_new())
1030         return;
1031       break;
1032     case OP_SIDEBAR_PAGE_DOWN:
1033       if (!select_page_down())
1034         return;
1035       break;
1036     case OP_SIDEBAR_PAGE_UP:
1037       if (!select_page_up())
1038         return;
1039       break;
1040     case OP_SIDEBAR_PREV:
1041       if (!select_prev())
1042         return;
1043       break;
1044     case OP_SIDEBAR_PREV_NEW:
1045       if (!select_prev_new())
1046         return;
1047       break;
1048     default:
1049       return;
1050   }
1051   mutt_menu_set_current_redraw(REDRAW_SIDEBAR);
1052 }
1053
1054 /**
1055  * mutt_sb_get_highlight - Get the Mailbox that's highlighted in the sidebar
1056  * @retval ptr Mailbox
1057  */
1058 struct Mailbox *mutt_sb_get_highlight(void)
1059 {
1060   if (!C_SidebarVisible)
1061     return NULL;
1062
1063   if (!EntryCount || (HilIndex < 0))
1064     return NULL;
1065
1066   return Entries[HilIndex]->mailbox;
1067 }
1068
1069 /**
1070  * mutt_sb_set_open_mailbox - Set the 'open' Mailbox
1071  * @param m Mailbox
1072  *
1073  * Search through the list of mailboxes.
1074  * If a Mailbox has a matching path, set OpnMailbox to it.
1075  */
1076 void mutt_sb_set_open_mailbox(struct Mailbox *m)
1077 {
1078   OpnIndex = -1;
1079
1080   if (!m)
1081     return;
1082
1083   for (int entry = 0; entry < EntryCount; entry++)
1084   {
1085     if (mutt_str_strcmp(Entries[entry]->mailbox->realpath, m->realpath) == 0)
1086     {
1087       OpnIndex = entry;
1088       HilIndex = entry;
1089       break;
1090     }
1091   }
1092 }
1093
1094 /**
1095  * mutt_sb_notify_mailbox - The state of a Mailbox is about to change
1096  * @param m       Folder
1097  * @param created True if folder created, false if deleted
1098  *
1099  * We receive a notification:
1100  * - After a new Mailbox has been created
1101  * - Before a Mailbox is deleted
1102  *
1103  * Before a deletion, check that our pointers won't be invalidated.
1104  */
1105 void mutt_sb_notify_mailbox(struct Mailbox *m, bool created)
1106 {
1107   if (!m)
1108     return;
1109
1110   /* Any new/deleted mailboxes will cause a refresh.  As long as
1111    * they're valid, our pointers will be updated in prepare_sidebar() */
1112
1113   if (created)
1114   {
1115     if (EntryCount >= EntryLen)
1116     {
1117       EntryLen += 10;
1118       mutt_mem_realloc(&Entries, EntryLen * sizeof(struct SbEntry *));
1119     }
1120     Entries[EntryCount] = mutt_mem_calloc(1, sizeof(struct SbEntry));
1121     Entries[EntryCount]->mailbox = m;
1122
1123     if (TopIndex < 0)
1124       TopIndex = EntryCount;
1125     if (BotIndex < 0)
1126       BotIndex = EntryCount;
1127     if ((OpnIndex < 0) && Context &&
1128         (mutt_str_strcmp(m->realpath, Context->mailbox->realpath) == 0))
1129       OpnIndex = EntryCount;
1130
1131     EntryCount++;
1132   }
1133   else
1134   {
1135     int del_index;
1136     for (del_index = 0; del_index < EntryCount; del_index++)
1137       if (Entries[del_index]->mailbox == m)
1138         break;
1139     if (del_index == EntryCount)
1140       return;
1141     FREE(&Entries[del_index]);
1142     EntryCount--;
1143
1144     if ((TopIndex > del_index) || (TopIndex == EntryCount))
1145       TopIndex--;
1146     if (OpnIndex == del_index)
1147       OpnIndex = -1;
1148     else if (OpnIndex > del_index)
1149       OpnIndex--;
1150     if ((HilIndex > del_index) || (HilIndex == EntryCount))
1151       HilIndex--;
1152     if ((BotIndex > del_index) || (BotIndex == EntryCount))
1153       BotIndex--;
1154
1155     for (; del_index < EntryCount; del_index++)
1156       Entries[del_index] = Entries[del_index + 1];
1157   }
1158
1159   mutt_menu_set_current_redraw(REDRAW_SIDEBAR);
1160 }