The current functions <collapse-thread> and <collapse-all> are in fact
toggles.  This makes it tricky to control how a macro behaves, for
instance.

Add functions <open-thread>, <close-thread>, <open-all-threads>,
<close-all-threads> to explicitly open or close threads.

Renaming the existing toggle functions is not practical due to how
long they have been in use.  I could add new <toggle-thread>
<toggle-all-threads> functions but that would require new OPS to work
properly with keybindings.  I don't think it's worth the extra
trouble.
---
The diff looks more complicated than it is due to indentation changes.
See:
https://gitlab.com/muttmua/mutt/-/commit/39cf6a25f89b391cbfe35de805ef6095cc31e93a
for a diff without whitespace changes.

 OPS         | 20 ++++++++++++++++
 curs_main.c | 66 +++++++++++++++++++++++++++++++++--------------------
 functions.h |  4 ++++
 3 files changed, 65 insertions(+), 25 deletions(-)

diff --git a/OPS b/OPS
index b4cd2512..aee20ac1 100644
--- a/OPS
+++ b/OPS
@@ -1194,6 +1194,26 @@ OP_MAIN_COLLAPSE_THREAD N_("collapse/uncollapse current 
thread")
  */
 OP_MAIN_COLLAPSE_ALL N_("collapse/uncollapse all threads")
 
+/* L10N: Help screen description for OP_MAIN_OPEN_THREAD
+   index menu: <open-thread>
+ */
+OP_MAIN_OPEN_THREAD N_("uncollapse current thread")
+
+/* L10N: Help screen description for OP_MAIN_OPEN_ALL_THREADS
+   index menu: <open-all-threads>
+ */
+OP_MAIN_OPEN_ALL_THREADS N_("uncollapse all threads")
+
+/* L10N: Help screen description for OP_MAIN_CLOSE_THREAD
+   index menu: <close-thread>
+ */
+OP_MAIN_CLOSE_THREAD N_("collapse current thread")
+
+/* L10N: Help screen description for OP_MAIN_CLOSE_ALL_THREADS
+   index menu: <close-all-threads>
+ */
+OP_MAIN_CLOSE_ALL_THREADS N_("collapse all threads")
+
 /* L10N: Help screen description for OP_DESCEND_DIRECTORY
    browser menu: <descend-directory>
  */
diff --git a/curs_main.c b/curs_main.c
index 9fbc6750..843efec9 100644
--- a/curs_main.c
+++ b/curs_main.c
@@ -2060,6 +2060,8 @@ int mutt_index_menu (void)
         break;
 
       case OP_MAIN_COLLAPSE_THREAD:
+      case OP_MAIN_CLOSE_THREAD:
+      case OP_MAIN_OPEN_THREAD:
         CHECK_MSGCOUNT;
         CHECK_VISIBLE;
 
@@ -2069,7 +2071,8 @@ int mutt_index_menu (void)
           break;
         }
 
-        if (CURHDR->collapsed)
+        if ((op == OP_MAIN_COLLAPSE_THREAD || op == OP_MAIN_OPEN_THREAD) &&
+            CURHDR->collapsed)
         {
           /* Note this returns the *old* virtual index of the root message.
            *
@@ -2081,29 +2084,32 @@ int mutt_index_menu (void)
           if (option (OPTUNCOLLAPSEJUMP))
             menu->current = mutt_thread_next_unread (Context, CURHDR);
         }
-        else if (option (OPTCOLLAPSEUNREAD) || !UNREAD (CURHDR))
+        else if (op == OP_MAIN_COLLAPSE_THREAD || op == OP_MAIN_CLOSE_THREAD)
         {
-          HEADER *base;
-          int final;
-          /* This also returns the *old* virtual index of the root, but now
-           * we have to find the new position of the root, which isn't
-           * the same for sort=reverse-threads. */
-          final = mutt_collapse_thread (Context, CURHDR);
-          base = Context->hdrs[Context->v2r[final]];
-          mutt_set_virtual (Context);
-          for (j = 0; j < Context->vcount; j++)
+          if (option (OPTCOLLAPSEUNREAD) || !UNREAD (CURHDR))
           {
-            if (Context->hdrs[Context->v2r[j]]->index == base->index)
+            HEADER *base;
+            int final;
+            /* This also returns the *old* virtual index of the root, but now
+             * we have to find the new position of the root, which isn't
+             * the same for sort=reverse-threads. */
+            final = mutt_collapse_thread (Context, CURHDR);
+            base = Context->hdrs[Context->v2r[final]];
+            mutt_set_virtual (Context);
+            for (j = 0; j < Context->vcount; j++)
             {
-              menu->current = j;
-              break;
+              if (Context->hdrs[Context->v2r[j]]->index == base->index)
+              {
+                menu->current = j;
+                break;
+              }
             }
           }
-        }
-        else
-        {
-          mutt_error _("Thread contains unread messages.");
-          break;
+          else
+          {
+            mutt_error _("Thread contains unread messages.");
+            break;
+          }
         }
 
         menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
@@ -2111,6 +2117,8 @@ int mutt_index_menu (void)
         break;
 
       case OP_MAIN_COLLAPSE_ALL:
+      case OP_MAIN_OPEN_ALL_THREADS:
+      case OP_MAIN_CLOSE_ALL_THREADS:
         CHECK_MSGCOUNT;
         CHECK_VISIBLE;
 
@@ -2125,17 +2133,25 @@ int mutt_index_menu (void)
           THREAD *thread, *top;
           int final;
 
-          if (CURHDR->collapsed)
-            final = mutt_uncollapse_thread (Context, CURHDR);
-          else if (option (OPTCOLLAPSEUNREAD) || !UNREAD (CURHDR))
-            final = mutt_collapse_thread (Context, CURHDR);
+          if (op == OP_MAIN_COLLAPSE_ALL)
+            Context->collapsed = !Context->collapsed;
+          else if (op == OP_MAIN_OPEN_ALL_THREADS)
+            Context->collapsed = 0;
           else
-            final = CURHDR->virtual;
+            Context->collapsed = 1;
+
+          final = CURHDR->virtual;
+          if (CURHDR->collapsed != Context->collapsed)
+          {
+            if (CURHDR->collapsed)
+              final = mutt_uncollapse_thread (Context, CURHDR);
+            else if (option (OPTCOLLAPSEUNREAD) || !UNREAD (CURHDR))
+              final = mutt_collapse_thread (Context, CURHDR);
+          }
 
           base = Context->hdrs[Context->v2r[final]];
 
           top = Context->tree;
-          Context->collapsed = !Context->collapsed;
           while ((thread = top) != NULL)
           {
             while (!thread->message)
diff --git a/functions.h b/functions.h
index 5c640afc..81705ddd 100644
--- a/functions.h
+++ b/functions.h
@@ -145,6 +145,8 @@ const struct menu_func_op_t OpMain[] = { /* map: index */
   { "change-folder-readonly",    OP_MAIN_CHANGE_FOLDER_READONLY },
   { "check-traditional-pgp",     OP_CHECK_TRADITIONAL },
   { "clear-flag",                OP_MAIN_CLEAR_FLAG },
+  { "close-thread",              OP_MAIN_CLOSE_THREAD },
+  { "close-all-threads",         OP_MAIN_CLOSE_ALL_THREADS },
   { "collapse-all",              OP_MAIN_COLLAPSE_ALL },
   { "collapse-thread",           OP_MAIN_COLLAPSE_THREAD },
   { "compose-to-sender",         OP_COMPOSE_TO_SENDER },
@@ -191,6 +193,8 @@ const struct menu_func_op_t OpMain[] = { /* map: index */
   { "next-undeleted",            OP_MAIN_NEXT_UNDELETED },
   { "next-unread",               OP_MAIN_NEXT_UNREAD },
   { "next-unread-mailbox",       OP_MAIN_NEXT_UNREAD_MAILBOX },
+  { "open-thread",               OP_MAIN_OPEN_THREAD },
+  { "open-all-threads",          OP_MAIN_OPEN_ALL_THREADS },
   { "parent-message",            OP_MAIN_PARENT_MESSAGE },
   { "pipe-message",              OP_PIPE },
   { "previous-new",              OP_MAIN_PREV_NEW },
-- 
2.53.0

Reply via email to