[prev in list] [next in list] [prev in thread] [next in thread] 

List:       mutt-dev
Subject:    [PATCH] Introduce mailbox groups for improved management of mailboxes.
From:       Mono DHS <monodhs () arcor ! de>
Date:       2020-07-27 23:53:48
Message-ID: 20200727235348.GF396528 () airlock
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


This one is somewhat of a proof of concept at the moment.

The basic notion is that with many different mailboxes, the sidebar
is fairly close to unusable when having to rely on the "folder" and
"sidebar_use_mailbox_shortcuts" configuration variables, and a higher
stratum of hierarchy is needed.  This is where mailbox groups come
into play.

With a configuration of

mailbox_group 'IMAP'      'imaps://example.com/' 'imaps://example.com/INBOX.CCC'
mailbox_group 'Local' '/home/joe/emails'

and

mailboxes 'imaps://example.com/INBOX'
mailboxes 'imaps://example.com/INBOX.AAA'
mailboxes 'imaps://example.com/INBOX.BBB'
mailboxes 'imaps://example.com/INBOX.BBB.CCC'

mailboxes 'imaps://example.com/INBOX.CCC.DDD'
mailboxes 'imaps://example.com/INBOX.CCC.DDD.EEE'

mailboxes '/home/joe/emails/XXX'
mailboxes '/home/joe/emails/YYY'
mailboxes '/home/joe/emails/YYY/ZZZ'

and

# This is important.  We want "folder" to not mess with our paths.
set folder = '/some/where'

set sidebar_short_path = yes
set sidebar_folder_indent = yes
set sidebar_relative_shortpath_indent = no
set sidebar_indent_string = '  '
set sidebar_delim_chars = '/.'
set sidebar_mailbox_group_indent = yes
set sidebar_sort_method = mailbox-order
set sidebar_use_mailbox_shortcuts = no

the sidebar will display something along the lines of

IMAP
  INBOX
    AAA
    BBB
      CCC
  DDD
    EEE
Local
  XXX
  YYY
    ZZZ

WITHOUT the mailbox names ever changing when switching to another mailbox.

["patch-1.14.6.mdhs.mailbox_groups.1" (text/plain)]

From a002662ac2ea5ccc2e42eea2fe334b40301f927e Mon Sep 17 00:00:00 2001
From: Mono DHS <monodhs@arcor.de>
Date: Tue, 21 Jul 2020 04:49:42 +0200
Subject: [PATCH] Introduce mailbox groups for improved management of
 mailboxes.

---
 buffy.c             |   4 +-
 color.c             |   1 +
 doc/manual.xml.head |  45 ++++++++
 doc/muttrc.man.head |   3 +-
 globals.h           |   2 +
 init.c              | 113 +++++++++++++++++++
 init.h              |  11 ++
 mutt.h              |  11 ++
 mutt_curses.h       |   1 +
 muttlib.c           |  56 ++++++++++
 sidebar.c           | 306 ++++++++++++++++++++++++++++++++++++----------------
 sidebar.h           |   2 +-
 12 files changed, 461 insertions(+), 94 deletions(-)

diff --git a/buffy.c b/buffy.c
index edb09e332035c411ef7e2d01ab5451dda27d2f7b..0ad4bff935151c3cce282300ef0d7e1286788b3c 100644
--- a/buffy.c
+++ b/buffy.c
@@ -285,7 +285,7 @@ static void buffy_add (BUFFER *path, const char *label, int nopoll)
     new = 1;
     *tmp = buffy_new (mutt_b2s (path));
 #ifdef USE_SIDEBAR
-    mutt_sb_notify_mailbox (*tmp, 1);
+    mutt_sb_notify_mailbox (*tmp, 0, 1);
 #endif
   }
 
@@ -336,7 +336,7 @@ static void buffy_remove (BUFFY **pbuffy)
   next = (*pbuffy)->next;
 
 #ifdef USE_SIDEBAR
-  mutt_sb_notify_mailbox (*pbuffy, 0);
+  mutt_sb_notify_mailbox (*pbuffy, 0, 0);
 #endif
 #ifdef USE_INOTIFY
   if (!(*pbuffy)->nopoll)
diff --git a/color.c b/color.c
index c5fc9ce22ce452fbc997b996d5070e62f833c4ab..afd0a9c42dc57bcc10153065cbacbfe6b0b7e11c 100644
--- a/color.c
+++ b/color.c
@@ -100,6 +100,7 @@ static const struct mapping_t Fields[] =
   { "sidebar_flagged",	MT_COLOR_FLAGGED },
   { "sidebar_highlight",MT_COLOR_HIGHLIGHT },
   { "sidebar_indicator",MT_COLOR_SB_INDICATOR },
+  { "sidebar_mailbox_group", MT_COLOR_SB_MAILBOX_GROUP },
   { "sidebar_new",	MT_COLOR_NEW },
   { "sidebar_spoolfile",MT_COLOR_SB_SPOOLFILE },
 #endif
diff --git a/doc/manual.xml.head b/doc/manual.xml.head
index 5276953059e453592c2832db5abed4ec6a500395..918a112fb5ad814672a4b3b8a3e596785e64c2bf 100644
--- a/doc/manual.xml.head
+++ b/doc/manual.xml.head
@@ -3723,6 +3723,14 @@ to save read mail in more than a single mailbox).
 <replaceable class="parameter">mailbox</replaceable>
 </arg>
 </group>
+
+<command>mailbox_group</command>
+<arg choice="plain">
+  <replaceable class="parameter">name</replaceable>
+</arg>
+<arg choice="plain" rep="repeat">
+  <replaceable class="parameter">path-prefix</replaceable>
+</arg>
 </cmdsynopsis>
 
 <para>
@@ -3770,6 +3778,25 @@ the list of folders which receive mail. Use <quote>unmailboxes *</quote>
 to remove all tokens.
 </para>
 
+<para>
+The <command>mailbox_group</command> command is used to associate
+distinct mailboxes within one logical unit. Currently, this is taken
+advantage of in the <link linkend="sidebar">sidebar</link>, where
+<emphasis>mailbox groups</emphasis> generalise the notion introduced
+with the <link linkend="folder">$folder</link> and
+<link linkend="sidebar-use-mailbox-shortcuts">$sidebar_use_mailbox_shortcuts</link>
+variables for a single prefix to arbitrarily many prefixes for
+<emphasis>all</emphasis> mailboxes. Due to the way this is
+currently implemented, however, only a
+<link linkend="sidebar-sort-method">$sidebar_sort_method</link>
+value of <quote>order</quote> is compatible with mailbox groups,
+to the extent that a value other than <quote>order</quote>
+will prevent mailbox groups from being shown at all.
+Conversely, once mailbox groups have been shown,
+<link linkend="sidebar-sort-method">$sidebar_sort_method</link>
+has no effect and any request at sorting in the sidebar is ignored.
+</para>
+
 <note>
 <para>
 The folders in the <command>mailboxes</command> command are resolved
@@ -9361,6 +9388,11 @@ please have a look at the mixmaster documentation.
 	    <entry><literal>&nbsp;&nbsp;</literal> (two spaces)</entry>
 	  </row>
 	  <row>
+	    <entry><literal>sidebar_mailbox_group_indent</literal></entry>
+	    <entry>boolean</entry>
+	    <entry><literal>yes</literal></entry>
+	  </row>
+	  <row>
 	    <entry><literal>sidebar_new_mail_only</literal></entry>
 	    <entry>boolean</entry>
 	    <entry><literal>no</literal></entry>
@@ -9530,6 +9562,11 @@ please have a look at the mixmaster documentation.
 	    <entry>The mailbox open in the Index panel</entry>
 	  </row>
 	  <row>
+	    <entry><literal>sidebar_mailbox_group</literal></entry>
+	    <entry>default</entry>
+	    <entry>Head entry for a mailbox group</entry>
+	  </row>
+	  <row>
 	    <entry><literal>sidebar_new</literal></entry>
 	    <entry>default</entry>
 	    <entry>Mailboxes containing new mail</entry>
@@ -11195,6 +11232,14 @@ The following are the commands understood by Mutt:
 <replaceable class="parameter">mailbox</replaceable>
 </arg>
 </group>
+
+<command><link linkend="mailboxes">mailbox_group</link></command>
+<arg choice="plain">
+  <replaceable class="parameter">name</replaceable>
+</arg>
+<arg choice="plain" rep="repeat">
+  <replaceable class="parameter">path-prefix</replaceable>
+</arg>
 </cmdsynopsis>
 </listitem>
 
diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head
index 7fd1b6e69faeae23f4cb43e84f56d6eeb479391c..572e8e8fd0e9799e3873c22f13b7a29505afb29d 100644
--- a/doc/muttrc.man.head
+++ b/doc/muttrc.man.head
@@ -237,7 +237,8 @@ objects.  Valid objects are:
 .BR underline .
 If the sidebar is enabled the following objects are also valid:
 .BR sidebar_divider ", " sidebar_flagged ", " sidebar_highlight ", "
-.BR sidebar_indicator ", " sidebar_new ", " sidebar_spoolfile .
+.BR sidebar_indicator ", " sidebar_new ", " sidebar_spoolfile ", "
+.BR sidebar_mailbox_group .
 The
 .BR body " and " header
 objects allow you to restrict the colorization to a regular
diff --git a/globals.h b/globals.h
index ec31f6b729c3976e68e545742a895915a3d8e541..be49e053aed8b60437cdc8d234b487c1e67dc9e8 100644
--- a/globals.h
+++ b/globals.h
@@ -207,6 +207,8 @@ WHERE REPLACE_LIST *SpamList;
 WHERE RX_LIST *NoSpamList;
 WHERE REPLACE_LIST *SubjectRxList;
 
+WHERE LIST * MailboxGroups;
+WHERE MAP *  MailboxGroupMap;
 
 /* bit vector for boolean variables */
 #ifdef MAIN_C
diff --git a/init.c b/init.c
index 9c0a394675b23fc7269c1522bd11efacdcde8a6e..7d29a2e3244d9f718b09b1b25ce727d037c6e435 100644
--- a/init.c
+++ b/init.c
@@ -33,6 +33,7 @@
 #include "mutt_crypt.h"
 #include "mutt_idna.h"
 #include "group.h"
+#include "sidebar.h"
 
 #if defined(USE_SSL)
 #include "mutt_ssl.h"
@@ -440,6 +441,69 @@ int mutt_add_to_rx_list (RX_LIST **list, const char *s, int flags, BUFFER *err)
   return 0;
 }
 
+/* This one was inspired by  add_to_list() .  It assumes that all
+ * objects of type  MAP  are constructed in such a way that their
+ *  .next  member's value differs from the respective object's
+ * address.
+ */
+static void add_to_or_replace_in_map (MAP ** map,
+                                      const char * key, const char * value)
+{
+  MAP * n, * m, * p;
+
+  if (!map || !key || (*key) == '\0' || !value || (*value) == '\0')
+    return;
+
+  p = NULL;
+  m = (*map);
+  while (m) {
+
+    /* If a mapping with the same key already
+     * exists, replace it with the new one.
+     */
+    if (strcmp (key, m->key) == 0)
+      break;
+
+    /* Making the assignment here instead of after
+     * the end-of-list test facilitates to distinguish
+     * between replacing the current list element and
+     * appending to it without the introduction
+     * of a separate flag to that effect.
+     */
+    p = m;
+
+    if (!m->next)
+      break;
+
+    m = m->next;
+  }
+
+  n = mutt_new_map();
+  n->key   = safe_strdup (key);
+  n->value = value;
+
+  if (m) {
+    if (p == m) {
+      /* append */
+      m->next = n;
+    } else {
+      /* replace */
+      if (p)
+        /* not the first element */
+        p->next = n;
+      else
+        /* first element */
+        (*map) = n;
+      n->next = m->next;
+      FREE (&m->key);
+      FREE (&m);
+    }
+  } else {
+    /* initialise */
+    (*map) = n;
+  }
+}
+
 static int remove_from_replace_list (REPLACE_LIST **list, const char *pat);
 
 static int add_to_replace_list (REPLACE_LIST **list, const char *pat, const char *templ, BUFFER *err)
@@ -2842,6 +2906,55 @@ static int parse_source (BUFFER *tmp, BUFFER *s, union pointer_long_t udata, BUF
   return rc;
 }
 
+#define mailbox_group_snprintf(b, s, a)                             \
+    snprintf((b)->data, (b)->dsize, _("mailbox_group: " s), (a));
+
+static int mutt_parse_mailbox_group (BUFFER * tb, BUFFER * ln,
+                                     union pointer_long_t udata, BUFFER * e)
+{
+  int rv;
+  MAP ** map = udata.p;
+  BUFFER name;
+  const LIST * n;
+
+  mutt_buffer_init(&name);
+  rv = mutt_extract_token(&name, ln, 0);
+  if (rv != 0 || strlen(name.data) == 0) {
+    mailbox_group_snprintf(e, "expected a name (%s)", ln->dptr);
+    rv = -1;
+    goto bail;
+  }
+
+  add_to_list(&MailboxGroups, name.data);
+  if ((n = mutt_find_list(MailboxGroups, name.data)) == NULL) {
+    mailbox_group_snprintf(e, "failed to add mailbox group \"%s\"", name.data);
+    rv = -1;
+    goto bail;
+  }
+
+  do {
+
+    rv = mutt_extract_token(tb, ln, 0);
+    if (rv != 0 || strlen(tb->data) == 0) {
+      mailbox_group_snprintf(e, "expected a path prefix (%s)", ln->dptr);
+      rv = -1;
+      goto bail;
+    }
+
+    add_to_or_replace_in_map(map, tb->data, n->data);
+
+  } while (MoreArgs(ln));
+
+  mutt_sb_notify_mailbox(n->data, 1, 1);
+  rv = 0;
+
+bail:
+
+  FREE (&name.data);
+
+  return rv;
+}
+
 int mutt_parse_rc_line (const char *line, BUFFER *err)
 {
   BUFFER *line_buffer = NULL, *token = NULL;
diff --git a/init.h b/init.h
index 6cbc30a3ca4423a053b69427cb6e906e33618d60..3902aad3ac814738902b528711f9cd7bc0eea251 100644
--- a/init.h
+++ b/init.h
@@ -3333,6 +3333,14 @@ struct option_t MuttVars[] = {
   ** .pp
   ** \fBSee also:\fP $$sidebar_short_path, $$sidebar_folder_indent, $$sidebar_delim_chars.
   */
+  { "sidebar_mailbox_group_indent", DT_BOOL, R_SIDEBAR, {.l=OPTSIDEBARMAILBOXGROUPINDENT}, {.l=1} },
+  /*
+  ** .pp
+  ** Set this to add an additional indentation for mailboxes
+  ** in the sidebar that are members of a mailbox group.
+  ** .pp
+  ** \fBSee also:\fP $$sidebar_folder_indent, $$sidebar_indent_string.
+  */
   { "sidebar_new_mail_only", DT_BOOL, R_SIDEBAR, {.l=OPTSIDEBARNEWMAILONLY}, {.l=0} },
   /*
   ** .pp
@@ -4602,6 +4610,8 @@ static int parse_unsubjectrx_list (BUFFER *, BUFFER *, union pointer_long_t, BUF
 static int parse_alternates (BUFFER *, BUFFER *, union pointer_long_t, BUFFER *);
 static int parse_unalternates (BUFFER *, BUFFER *, union pointer_long_t, BUFFER *);
 
+static int mutt_parse_mailbox_group (BUFFER *, BUFFER *, union pointer_long_t, BUFFER *);
+
 /* Parse -group arguments */
 static int parse_group_context (group_context_t **ctx, BUFFER *buf, BUFFER *s, BUFFER *err);
 
@@ -4652,6 +4662,7 @@ const struct command_t Commands[] = {
   { "macro",		mutt_parse_macro,	{.l=0} },
   { "mailboxes",	mutt_parse_mailboxes,	{.l=0} },
   { "unmailboxes",	mutt_parse_unmailboxes,	{.l=0} },
+  { "mailbox_group",	mutt_parse_mailbox_group, {.p=&MailboxGroupMap} },
   { "mailto_allow",	parse_list,		{.p=&MailtoAllow} },
   { "unmailto_allow",	parse_unlist,		{.p=&MailtoAllow} },
   { "message-hook",	mutt_parse_hook,	{.l=MUTT_MESSAGEHOOK} },
diff --git a/mutt.h b/mutt.h
index 4413739ff0748215807e8a7d3e9d803bdeabd26e..30079071eaa352a9d98a9ea4d73c5a66b03f0ae7 100644
--- a/mutt.h
+++ b/mutt.h
@@ -519,6 +519,7 @@ enum
 #ifdef USE_SIDEBAR
   OPTSIDEBAR,
   OPTSIDEBARFOLDERINDENT,
+  OPTSIDEBARMAILBOXGROUPINDENT,
   OPTSIDEBARNEWMAILONLY,
   OPTSIDEBARNEXTNEWWRAP,
   OPTSIDEBARRELSPINDENT,
@@ -660,13 +661,22 @@ typedef struct replace_list_t
   struct replace_list_t *next;
 } REPLACE_LIST;
 
+typedef struct map_t
+{
+  const char *   key;
+  const char *   value;
+  struct map_t * next;
+} MAP;
+
 #define mutt_new_list() safe_calloc (1, sizeof (LIST))
 #define mutt_new_rx_list() safe_calloc (1, sizeof (RX_LIST))
 #define mutt_new_replace_list() safe_calloc (1, sizeof (REPLACE_LIST))
+#define mutt_new_map() ((MAP * ) safe_calloc (1, sizeof (MAP)))
 void mutt_free_list (LIST **);
 void mutt_free_list_generic (LIST **list, void (*data_free)(char **));
 void mutt_free_rx_list (RX_LIST **);
 void mutt_free_replace_list (REPLACE_LIST **);
+void mutt_free_map (MAP **);
 LIST *mutt_copy_list (LIST *);
 int mutt_matches_ignore (const char *, LIST *);
 
@@ -675,6 +685,7 @@ LIST *mutt_add_list (LIST *, const char *);
 LIST *mutt_add_list_n (LIST*, const void *, size_t);
 LIST *mutt_find_list (LIST *, const char *);
 int mutt_remove_from_rx_list (RX_LIST **l, const char *str);
+const MAP * mutt_find_longest_prefix_map (const MAP *, const char *, size_t *);
 
 void mutt_init (int, LIST *);
 
diff --git a/mutt_curses.h b/mutt_curses.h
index f21e0eabb9699d0801dc88675015dcbd1e908693..2c01a524cac1237a57087e2904e03da91ab84e44 100644
--- a/mutt_curses.h
+++ b/mutt_curses.h
@@ -133,6 +133,7 @@ enum
   MT_COLOR_HIGHLIGHT,
   MT_COLOR_NEW,
   MT_COLOR_SB_INDICATOR,
+  MT_COLOR_SB_MAILBOX_GROUP,
   MT_COLOR_SB_SPOOLFILE,
 #endif
   MT_COLOR_COMPOSE_HEADER,
diff --git a/muttlib.c b/muttlib.c
index 6efa0452d682e3ec8405fe0c8e470bc26bb1a400..31f7da7b28c3218b754b2526cd20a1595fb9b4bd 100644
--- a/muttlib.c
+++ b/muttlib.c
@@ -2283,6 +2283,62 @@ void mutt_free_replace_list (REPLACE_LIST **list)
   }
 }
 
+/* This is shamelessly compied from  mutt_free_list() . */
+void mutt_free_map (MAP ** map)
+{
+  MAP * m;
+
+  if (!map)
+    return;
+
+  while (*map) {
+
+    m = (*map);
+    (*map) = m->next;
+
+    FREE (&m->key);
+    FREE (&m);
+  }
+}
+
+static size_t startswithlen (const char * str, const char * pfx)
+{
+  size_t len;
+
+  for ( len = 0; (*str) != '\0' && (*pfx) != '\0'; str++, pfx++, len++)
+    /* If both prefix and string differ before the end of
+     * the prefix has been reached, the string does not
+     * begin with the prefix.
+     */
+    if ((*str) != (*pfx))
+      return 0;
+
+  /* If the prefix turns out to be longer than the string
+   * itself, the string does not begin with the prefix.
+   */
+  if ((*pfx) != '\0')
+    return 0;
+
+  return len;
+}
+
+const MAP * mutt_find_longest_prefix_map (const MAP * map,
+                                          const char * str, size_t * len)
+{
+  const MAP * m, * fav = NULL;
+  size_t l;
+
+  for ((*len) = 0, m = map; m; m = m->next) {
+    l = startswithlen(str, m->key);
+    if (l > (*len)) {
+      fav = m;
+      (*len) = l;
+    }
+  }
+
+  return fav;
+}
+
 int mutt_match_rx_list (const char *s, RX_LIST *l)
 {
   if (!s)  return 0;
diff --git a/sidebar.c b/sidebar.c
index 1a3490f84e8e2ea02ebb7dc58bc6ffa58c2d6fc6..79f7847bb6dfe49099d1a7cc5d7e7e0779474f1b 100644
--- a/sidebar.c
+++ b/sidebar.c
@@ -33,23 +33,32 @@
 static short PreviousSort = SORT_ORDER;  /* sidebar_sort_method */
 
 /**
- * struct sidebar_entry - Info about folders in the sidebar
+ * struct sidebar_entry - Info about entries in the sidebar
  */
+
+union sbentry_data {
+  BUFFY      * buffy;
+  const char * group;
+};
+
 typedef struct sidebar_entry
 {
-  char         box[STRING];     /* formatted mailbox name */
-  BUFFY       *buffy;
-  short        is_hidden;
+  char               label[STRING];     /* formatted entry label */
+  union sbentry_data data;
+  unsigned int       is_hidden : 1;
+  unsigned int       is_group : 1;
 } SBENTRY;
 
 static int EntryCount = 0;
 static int EntryLen   = 0;
 static SBENTRY **Entries = NULL;
 
-static int TopIndex = -1;    /* First mailbox visible in sidebar */
+static int TopIndex = -1;    /* First entry visible in sidebar */
 static int OpnIndex = -1;    /* Current (open) mailbox */
 static int HilIndex = -1;    /* Highlighted mailbox */
-static int BotIndex = -1;    /* Last mailbox visible in sidebar */
+static int BotIndex = -1;    /* Last entry visible in sidebar */
+
+static int have_mailbox_groups = 0;
 
 static int select_next (void);
 static int select_prev (void);
@@ -89,7 +98,7 @@ static const char *cb_format_str(char *dest, size_t destlen, size_t col, int col
 
   dest[0] = 0;	/* Just in case there's nothing to do */
 
-  BUFFY *b = sbe->buffy;
+  BUFFY *b = sbe->data.buffy;
   if (!b)
     return src;
 
@@ -100,7 +109,7 @@ static const char *cb_format_str(char *dest, size_t destlen, size_t col, int col
   switch (op)
   {
     case 'B':
-      mutt_format_s (dest, destlen, prefix, sbe->box);
+      mutt_format_s (dest, destlen, prefix, sbe->label);
       break;
 
     case 'd':
@@ -198,26 +207,29 @@ static const char *cb_format_str(char *dest, size_t destlen, size_t col, int col
 }
 
 /**
- * make_sidebar_entry - Turn mailbox data into a sidebar string
+ * make_sidebar_entry - Turn sidebar entry data into a string for the sidebar label
  * @buf:     Buffer in which to save string
  * @buflen:  Buffer length
  * @width:   Desired width in screen cells
- * @box:     Mailbox name
- * @b:       Mailbox object
+ * @label:   Entry label
+ * @sbe:     Entry object
  *
- * Take all the relevant mailbox data and the desired screen width and then get
+ * Take all the relevant entry data and the desired screen width and then get
  * mutt_FormatString to do the actual work. mutt_FormatString will callback to
  * us using cb_format_str() for the sidebar specific formatting characters.
  */
-static void make_sidebar_entry (char *buf, unsigned int buflen, int width, const char *box,
+static void make_sidebar_entry (char *buf, unsigned int buflen, int width, const char *label,
                                 SBENTRY *sbe)
 {
-  if (!buf || !box || !sbe)
+  if (!buf || !label || !sbe)
     return;
 
-  strfcpy (sbe->box, box, sizeof (sbe->box));
+  strfcpy (sbe->label, label, sizeof (sbe->label));
 
-  mutt_FormatString (buf, buflen, 0, width, NONULL(SidebarFormat), cb_format_str, sbe, 0);
+  if (sbe->is_group)
+    strfcpy(buf, sbe->label, buflen);
+  else
+    mutt_FormatString (buf, buflen, 0, width, NONULL(SidebarFormat), cb_format_str, sbe, 0);
 
   /* Force string to be exactly the right width */
   int w = mutt_strwidth (buf);
@@ -238,7 +250,7 @@ static void make_sidebar_entry (char *buf, unsigned int buflen, int width, const
 }
 
 /**
- * cb_qsort_sbe - qsort callback to sort SBENTRYs
+ * cb_qsort_sbe - qsort callback to sort mail folder SBENTRYs
  * @a: First  SBENTRY to compare
  * @b: Second SBENTRY to compare
  *
@@ -251,8 +263,8 @@ static int cb_qsort_sbe (const void *a, const void *b)
 {
   const SBENTRY *sbe1 = *(const SBENTRY **) a;
   const SBENTRY *sbe2 = *(const SBENTRY **) b;
-  BUFFY *b1 = sbe1->buffy;
-  BUFFY *b2 = sbe2->buffy;
+  BUFFY *b1 = sbe1->data.buffy;
+  BUFFY *b2 = sbe2->data.buffy;
 
   int result = 0;
 
@@ -283,10 +295,11 @@ static int cb_qsort_sbe (const void *a, const void *b)
 }
 
 /**
- * update_entries_visibility - Should a sidebar_entry be displayed in the sidebar
+ * update_entries_visibility - Should a sidebar entry be displayed in the sidebar
  *
  * For each SBENTRY in the Entries array, check whether we should display it.
- * This is determined by several criteria.  If the BUFFY:
+ * This is determined by several criteria.  If the entry is a mailbox group or
+ * a BUFFY that
  *	is the currently open mailbox
  *	is the currently highlighted mailbox
  *	has unread messages
@@ -303,20 +316,24 @@ static void update_entries_visibility (void)
   {
     sbe = Entries[i];
 
+    if (sbe->is_group)
+      continue;
+
     sbe->is_hidden = 0;
 
     if (!new_only)
       continue;
 
-    if ((i == OpnIndex) || (sbe->buffy->msg_unread  > 0) || sbe->buffy->new ||
-        (sbe->buffy->msg_flagged > 0))
+    if ((i == OpnIndex) || (sbe->data.buffy->msg_unread  > 0) ||
+        sbe->data.buffy->new || (sbe->data.buffy->msg_flagged > 0))
       continue;
 
-    if (Context && (mutt_strcmp (sbe->buffy->realpath, Context->realpath) == 0))
+    if (Context &&
+        (mutt_strcmp (sbe->data.buffy->realpath, Context->realpath) == 0))
       /* Spool directory */
       continue;
 
-    if (mutt_find_list (SidebarWhitelist, mutt_b2s (sbe->buffy->pathbuf)))
+    if (mutt_find_list (SidebarWhitelist, mutt_b2s (sbe->data.buffy->pathbuf)))
       /* Explicitly asked to be visible */
       continue;
 
@@ -337,7 +354,7 @@ static void unsort_entries (void)
   {
     j = i;
     while ((j < EntryCount) &&
-           (Entries[j]->buffy != cur))
+           (Entries[j]->data.buffy != cur))
       j++;
     if (j < EntryCount)
     {
@@ -366,6 +383,9 @@ static void sort_entries (void)
 {
   short ssm = (SidebarSortMethod & SORT_MASK);
 
+  if (have_mailbox_groups)
+    return;
+
   /* These are the only sort methods we understand */
   if ((ssm == SORT_COUNT)     ||
       (ssm == SORT_UNREAD)    ||
@@ -426,17 +446,24 @@ static int prepare_sidebar (int page_size)
     {
       HilIndex = 0;
       /* Note is_hidden will only be set when OPTSIDEBARNEWMAILONLY */
-      if (Entries[HilIndex]->is_hidden)
+      if (Entries[HilIndex]->is_group || Entries[HilIndex]->is_hidden)
         if (!select_next ())
+          /* There are no unhidden mailboxes to be displayed in this sidebar. */
           HilIndex = -1;
     }
   }
 
-  /* Set the Top and Bottom to frame the HilIndex in groups of page_size */
+  /* Set the Top and Bottom to frame the HilIndex in groups of page_size.
+   * When there are no mailboxes to display in the sidebar at all, either
+   * because none were specified by the user in the first place or none
+   * of the specified ones have new mail,  HilIndex  will be set to  -1 .
+   */
 
   /* If OPTSIDEBARNEMAILONLY is set, some entries may be hidden so we
-   * need to scan for the framing interval */
-  if (option (OPTSIDEBARNEWMAILONLY))
+   * need to scan for the framing interval.  If all mailbox entries are
+   * hidden, there is nothing to do for us here.
+   */
+  if (option (OPTSIDEBARNEWMAILONLY) && (HilIndex >= 0))
   {
     TopIndex = BotIndex = -1;
     while (BotIndex < HilIndex)
@@ -453,7 +480,9 @@ static int prepare_sidebar (int page_size)
       }
     }
   }
-  /* Otherwise we can just calculate the interval */
+  /* Otherwise we can just calculate the interval.
+   * This also works when no mailboxes are to be displayed.
+   */
   else
   {
     TopIndex = (HilIndex / page_size) * page_size;
@@ -603,25 +632,26 @@ static void calculate_depth (const char *path, const char *lastpath,
 #define SIDEBAR_MAX_INDENT 32
 
 /**
- * draw_sidebar - Write out a list of mailboxes, on the left
+ * draw_sidebar - Write out a list of sidebar entries, on the left
  * @num_rows:   Height of the Sidebar
  * @num_cols:   Width of the Sidebar
  * @div_width:  Width in screen characters taken by the divider
  *
- * Display a list of mailboxes in a panel on the left.  What's displayed will
- * depend on our index markers: TopBuffy, OpnBuffy, HilBuffy, BotBuffy.
+ * Display a list of sidebar entries in a panel on the left.  What's displayed
+ * will depend on our index markers: TopIndex, OpnIndex, HilIndex, BotIndex.
  * On the first run they'll be NULL, so we display the top of Mutt's list
  * (Incoming).
  *
- * TopBuffy - first visible mailbox
- * BotBuffy - last  visible mailbox
- * OpnBuffy - mailbox shown in Mutt's Index Panel
- * HilBuffy - Unselected mailbox (the paging follows this)
+ * TopIndex - first visible sidebar entry index
+ * BotIndex - last  visible sidebar entry index
+ * OpnIndex - index of the mailbox shown in Mutt's Index Panel
+ * HilIndex - index of the selected mailbox (the paging follows this)
  *
  * The entries are formatted using "sidebar_format" and may be abbreviated:
  * "sidebar_short_path", indented: "sidebar_folder_indent",
- * "sidebar_indent_string" and sorted: "sidebar_sort_method".  Finally, they're
- * trimmed to fit the available space.
+ * "sidebar_mailbox_group_indent", "sidebar_indent_string" and
+ * sorted: "sidebar_sort_method".  Finally, they're trimmed to
+ * fit the available space.
  */
 static void draw_sidebar (int num_rows, int num_cols, int div_width)
 {
@@ -631,8 +661,11 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
   int maildir_is_prefix;
   int indent_width = -1;
   int indent_depths[SIDEBAR_MAX_INDENT];
-  const char *sidebar_folder_name;
+  const char *sidebar_entry_name;
+  const char * current_mailbox_group;
+  unsigned int mailbox_group_indent;
   BUFFER *pretty_folder_name, *last_folder_name, *indent_folder_name;
+  char str[STRING];
 
   if (TopIndex < 0)
     return;
@@ -641,14 +674,29 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
   last_folder_name = mutt_buffer_pool_get ();
   indent_folder_name = mutt_buffer_pool_get ();
 
+  current_mailbox_group = "";
   int w = MIN(num_cols, (SidebarWidth - div_width));
   int row = 0;
   for (entryidx = TopIndex; (entryidx < EntryCount) && (row < num_rows); entryidx++)
   {
     entry = Entries[entryidx];
+
+    mailbox_group_indent = 0;
+    if (entry->is_group) {
+
+      current_mailbox_group = entry->data.group;
+
+      SETCOLOR(MT_COLOR_SB_MAILBOX_GROUP);
+      mutt_window_move(MuttSidebarWindow, row, 0);
+
+      sidebar_entry_name = entry->data.group;
+
+      goto make_entry;
+    }
+
     if (entry->is_hidden)
       continue;
-    b = entry->buffy;
+    b = entry->data.buffy;
 
     if (entryidx == OpnIndex)
     {
@@ -683,10 +731,63 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
     {
       mutt_buffer_strcpy (pretty_folder_name, mutt_b2s (b->pathbuf));
       mutt_buffer_pretty_mailbox (pretty_folder_name);
-      sidebar_folder_name = mutt_b2s (pretty_folder_name);
-      if (sidebar_folder_name[0] == '=')
+      sidebar_entry_name = mutt_b2s (pretty_folder_name);
+      if (sidebar_entry_name[0] == '=')
         maildir_is_prefix = 1;
     }
+    else if (have_mailbox_groups) {
+
+      size_t mbg_pfx_len;
+      const MAP * mbg;
+
+      mbg = mutt_find_longest_prefix_map(MailboxGroupMap,
+                                         mutt_b2s(b->pathbuf),
+                                         &mbg_pfx_len);
+      if (!mbg || !SidebarDelimChars) {
+
+        /* The current entry is neither a mailbox group nor a
+         * mailbox that is a member of any such group, ending
+         * the run of any such group that might have been in
+         * effect until this point.
+         */
+        current_mailbox_group = "";
+
+        sidebar_entry_name = mutt_b2s(b->pathbuf);
+
+      } else {
+
+        /* If the current mailbox is also a member of the mailbox
+         * group currently in effect, we visualise this fact by an
+         * additional indentation, since there is no requirement
+         * for a mailbox to be a member of any mailbox group.
+         * If, however, the current mailbox is a member of a
+         * different mailbox group from the currently active
+         * one, we end the latter's run here.
+         */
+        if (strcmp(current_mailbox_group, mbg->value) == 0) {
+          if (option(OPTSIDEBARMAILBOXGROUPINDENT))
+            mailbox_group_indent = 1;
+        } else {
+          current_mailbox_group = "";
+        }
+
+        /* Account for a trailing separator in the mailbox path prefix. */
+        if (strchr(SidebarDelimChars, mbg->key[mbg_pfx_len - 1]))
+          mbg_pfx_len -= 1;
+
+        if ((mutt_buffer_len(b->pathbuf) > mbg_pfx_len) &&
+            strchr(SidebarDelimChars, mutt_b2s(b->pathbuf)[mbg_pfx_len])) {
+
+          sidebar_entry_name = mutt_b2s(b->pathbuf) + (mbg_pfx_len + 1);
+          maildir_is_prefix = 1;
+
+        } else {
+
+          sidebar_entry_name = mutt_b2s(b->pathbuf);
+
+        }
+      }
+    }
     else
     {
       /* compute length of Maildir without trailing separator */
@@ -702,11 +803,11 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
           SidebarDelimChars &&
           strchr (SidebarDelimChars, mutt_b2s (b->pathbuf)[maildirlen]))
       {
-        sidebar_folder_name = mutt_b2s (b->pathbuf) + (maildirlen + 1);
+        sidebar_entry_name = mutt_b2s (b->pathbuf) + (maildirlen + 1);
         maildir_is_prefix = 1;
       }
       else
-        sidebar_folder_name = mutt_b2s (b->pathbuf);
+        sidebar_entry_name = mutt_b2s (b->pathbuf);
     }
 
     if (SidebarDelimChars)
@@ -718,12 +819,12 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
       {
         int depth = 0, common_depth = 0;
 
-        calculate_depth (sidebar_folder_name, mutt_b2s (last_folder_name),
+        calculate_depth (sidebar_entry_name, mutt_b2s (last_folder_name),
                          &depth, &common_depth);
 
         if (option(OPTSIDEBARRELSPINDENT))
         {
-          mutt_buffer_strcpy (last_folder_name, sidebar_folder_name);
+          mutt_buffer_strcpy (last_folder_name, sidebar_entry_name);
 
           if (indent_width < SIDEBAR_MAX_INDENT)
             indent_width++;
@@ -755,36 +856,38 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
         else
         {
           parent_depth = depth - 1;
-          indent_width = maildir_is_prefix ? depth - 1 : 0;
+          indent_width = mailbox_group_indent +
+                         (maildir_is_prefix ? depth - 1 : 0);
         }
       }
 
       if (option (OPTSIDEBARSHORTPATH) && (parent_depth > 0))
       {
-        for (i = 0; parent_depth && sidebar_folder_name[i]; i++)
-          if (strchr (SidebarDelimChars, sidebar_folder_name[i]))
+        for (i = 0; parent_depth && sidebar_entry_name[i]; i++)
+          if (strchr (SidebarDelimChars, sidebar_entry_name[i]))
             parent_depth--;
-        sidebar_folder_name += i;
+        sidebar_entry_name += i;
       }
 
       /* For labels, ignore shortpath, but allow indentation */
       if (b->label)
-        sidebar_folder_name = b->label;
+        sidebar_entry_name = b->label;
 
       if (option (OPTSIDEBARFOLDERINDENT) && (indent_width > 0))
       {
         mutt_buffer_clear (indent_folder_name);
         for (i = 0; i < indent_width; i++)
           mutt_buffer_addstr (indent_folder_name, NONULL(SidebarIndentString));
-        mutt_buffer_addstr (indent_folder_name, sidebar_folder_name);
-        sidebar_folder_name = mutt_b2s (indent_folder_name);
+        mutt_buffer_addstr (indent_folder_name, sidebar_entry_name);
+        sidebar_entry_name = mutt_b2s (indent_folder_name);
       }
     }
     else if (b->label)
-      sidebar_folder_name = b->label;
+      sidebar_entry_name = b->label;
+
+make_entry:
 
-    char str[STRING];
-    make_sidebar_entry (str, sizeof (str), w, sidebar_folder_name, entry);
+    make_sidebar_entry (str, sizeof (str), w, sidebar_entry_name, entry);
     printw ("%s", str);
     row++;
   }
@@ -800,8 +903,9 @@ static void draw_sidebar (int num_rows, int num_cols, int div_width)
 /**
  * mutt_sb_draw - Completely redraw the sidebar
  *
- * Completely refresh the sidebar region.  First draw the divider; then, for
- * each BUFFY, call make_sidebar_entry; finally blank out any remaining space.
+ * Completely refresh the sidebar region.  First draw the divider; then,
+ * for each sidebar entry, call make_sidebar_entry; finally blank out any
+ * remaining space.
  */
 void mutt_sb_draw (void)
 {
@@ -836,7 +940,7 @@ static int select_first (void)
     return 0;
 
   HilIndex = 0;
-  if (Entries[HilIndex]->is_hidden)
+  if (Entries[HilIndex]->is_group || Entries[HilIndex]->is_hidden)
     if (!select_next ())
       HilIndex = orig_hil_index;
 
@@ -883,7 +987,7 @@ static int select_next (void)
     entry++;
     if (entry == EntryCount)
       return 0;
-  } while (Entries[entry]->is_hidden);
+  } while (Entries[entry]->is_group || Entries[entry]->is_hidden);
 
   HilIndex = entry;
   return 1;
@@ -892,7 +996,7 @@ static int select_next (void)
 /**
  * select_next_new - Selects the next new mailbox
  *
- * Search down the list of mail folders for one containing new mail.
+ * Search down the list of sidebar entries for a mail folder containing new mail.
  *
  * Returns:
  *	1: Success
@@ -917,8 +1021,9 @@ static int select_next_new (void)
     }
     if (entry == HilIndex)
       return 0;
-  } while (!Entries[entry]->buffy->new &&
-           !Entries[entry]->buffy->msg_unread);
+  } while (Entries[entry]->is_group ||
+           (!Entries[entry]->data.buffy->new &&
+            !Entries[entry]->data.buffy->msg_unread));
 
   HilIndex = entry;
   return 1;
@@ -943,7 +1048,7 @@ static int select_prev (void)
     entry--;
     if (entry < 0)
       return 0;
-  } while (Entries[entry]->is_hidden);
+  } while (Entries[entry]->is_group || Entries[entry]->is_hidden);
 
   HilIndex = entry;
   return 1;
@@ -952,7 +1057,7 @@ static int select_prev (void)
 /**
  * select_prev_new - Selects the previous new mailbox
  *
- * Search up the list of mail folders for one containing new mail.
+ * Search up the list of sidebar entries for a mail folder containing new mail.
  *
  * Returns:
  *	1: Success
@@ -977,15 +1082,16 @@ static int select_prev_new (void)
     }
     if (entry == HilIndex)
       return 0;
-  } while (!Entries[entry]->buffy->new &&
-           !Entries[entry]->buffy->msg_unread);
+  } while (Entries[entry]->is_group ||
+           (!Entries[entry]->data.buffy->new &&
+            !Entries[entry]->data.buffy->msg_unread));
 
   HilIndex = entry;
   return 1;
 }
 
 /**
- * select_page_down - Selects the first entry in the next page of mailboxes
+ * select_page_down - Selects the first mailbox in the next page of sidebar entries
  *
  * Returns:
  *      1: Success
@@ -1000,15 +1106,17 @@ static int select_page_down (void)
 
   HilIndex = BotIndex;
   select_next ();
-  /* If the rest of the entries are hidden, go up to the last unhidden one */
-  if (Entries[HilIndex]->is_hidden)
+  /* If the rest of the entries are mailbox groups or hidden mailboxes,
+   * go up to the last entry that is neither of the two.
+   */
+  if (Entries[HilIndex]->is_group || Entries[HilIndex]->is_hidden)
     select_prev ();
 
   return (orig_hil_index != HilIndex);
 }
 
 /**
- * select_page_up - Selects the last entry in the previous page of mailboxes
+ * select_page_up - Selects the last mailbox in the previous page of sidebar entries
  *
  * Returns:
  *      1: Success
@@ -1023,8 +1131,10 @@ static int select_page_up (void)
 
   HilIndex = TopIndex;
   select_prev ();
-  /* If the rest of the entries are hidden, go down to the last unhidden one */
-  if (Entries[HilIndex]->is_hidden)
+  /* If the rest of the entries are mailbox groups or hidden mailboxes,
+   * go down to the last entry that is neither of the two.
+   */
+  if (Entries[HilIndex]->is_group || Entries[HilIndex]->is_hidden)
     select_next ();
 
   return (orig_hil_index != HilIndex);
@@ -1038,7 +1148,7 @@ static int select_page_up (void)
  * with new mail". The operations are listed OPS.SIDEBAR which is built
  * into an enum in keymap_defs.h.
  *
- * If the operation is successful, HilBuffy will be set to the new mailbox.
+ * If the operation is successful, HilIndex will be set to the new mailbox.
  * This function only *selects* the mailbox, doesn't *open* it.
  *
  * Allowed values are: OP_SIDEBAR_FIRST, OP_SIDEBAR_LAST,
@@ -1140,14 +1250,14 @@ const char *mutt_sb_get_highlight (void)
   if (!EntryCount || HilIndex < 0)
     return NULL;
 
-  return mutt_b2s (Entries[HilIndex]->buffy->pathbuf);
+  return mutt_b2s (Entries[HilIndex]->data.buffy->pathbuf);
 }
 
 /**
- * mutt_sb_set_open_buffy - Set the OpnBuffy based on the global Context
+ * mutt_sb_set_open_buffy - Set the OpnIndex based on the global Context
  *
- * Search through the list of mailboxes.  If a BUFFY has a matching path, set
- * OpnBuffy to it.
+ * Search through the list of sidebar entries.  If a BUFFY has a matching path,
+ * set OpnIndex to it.
  */
 void mutt_sb_set_open_buffy (void)
 {
@@ -1160,7 +1270,10 @@ void mutt_sb_set_open_buffy (void)
 
   for (entry = 0; entry < EntryCount; entry++)
   {
-    if (!mutt_strcmp (Entries[entry]->buffy->realpath, Context->realpath))
+    if (Entries[entry]->is_group)
+      continue;
+
+    if (!mutt_strcmp (Entries[entry]->data.buffy->realpath, Context->realpath))
     {
       OpnIndex = entry;
       HilIndex = entry;
@@ -1170,19 +1283,19 @@ void mutt_sb_set_open_buffy (void)
 }
 
 /**
- * mutt_sb_notify_mailbox - The state of a BUFFY is about to change
+ * mutt_sb_notify_mailbox - The state of a BUFFY or mailbox group is about to change
  *
  * We receive a notification:
- *	After a new BUFFY has been created
- *	Before a BUFFY is deleted
+ *	After a new BUFFY or mailbox group has been created
+ *	Before a BUFFY is deleted (mailbox groups shall not be deleted)
  *
  * Before a deletion, check that our pointers won't be invalidated.
  */
-void mutt_sb_notify_mailbox (BUFFY *b, int created)
+void mutt_sb_notify_mailbox (void * data, int is_group, int created)
 {
   int del_index;
 
-  if (!b)
+  if (!data)
     return;
 
   /* Any new/deleted mailboxes will cause a refresh.  As long as
@@ -1190,20 +1303,33 @@ void mutt_sb_notify_mailbox (BUFFY *b, int created)
 
   if (created)
   {
+    if (is_group &&
+        (!MailboxGroups || !MailboxGroupMap ||
+         ((SidebarSortMethod & SORT_MASK) != SORT_ORDER)))
+      return;
+
     if (EntryCount >= EntryLen)
     {
       EntryLen += 10;
       safe_realloc (&Entries, EntryLen * sizeof (SBENTRY *));
     }
     Entries[EntryCount] = safe_calloc (1, sizeof(SBENTRY));
-    Entries[EntryCount]->buffy = b;
+    if (is_group) {
+      Entries[EntryCount]->data.group = (const char * )data;
+      Entries[EntryCount]->is_group   = 1;
+      have_mailbox_groups = 1;
+    } else {
+      Entries[EntryCount]->data.buffy = (BUFFY * )data;
+      Entries[EntryCount]->is_group   = 0;
+    }
 
     if (TopIndex < 0)
       TopIndex = EntryCount;
     if (BotIndex < 0)
       BotIndex = EntryCount;
-    if ((OpnIndex < 0) && Context &&
-        (mutt_strcmp (b->realpath, Context->realpath) == 0))
+    if (!is_group &&
+        (OpnIndex < 0) && Context &&
+        (mutt_strcmp (((BUFFY * )data)->realpath, Context->realpath) == 0))
       OpnIndex = EntryCount;
 
     EntryCount++;
@@ -1211,7 +1337,7 @@ void mutt_sb_notify_mailbox (BUFFY *b, int created)
   else
   {
     for (del_index = 0; del_index < EntryCount; del_index++)
-      if (Entries[del_index]->buffy == b)
+      if (Entries[del_index]->data.buffy == ((BUFFY * )data))
         break;
     if (del_index == EntryCount)
       return;
diff --git a/sidebar.h b/sidebar.h
index 3a04c5ed7840a30fc8fa2c01158c0ab0f7847de1..456eb3fe0c1bcbe9d0983684024943f2857e4bca 100644
--- a/sidebar.h
+++ b/sidebar.h
@@ -26,7 +26,7 @@
 void         mutt_sb_change_mailbox (int op);
 void         mutt_sb_draw (void);
 const char * mutt_sb_get_highlight (void);
-void         mutt_sb_notify_mailbox (BUFFY *b, int created);
+void         mutt_sb_notify_mailbox (void * data, int is_mbg, int created);
 void         mutt_sb_set_buffystats (const CONTEXT *ctx);
 BUFFY *      mutt_sb_set_open_buffy (void);
 
-- 
2.11.0


["signature.asc" (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic