]> granicus.if.org Git - neomutt/commitdiff
Add virtual index to actx. (see #3728)
authorKevin McCarthy <kevin@8t8.us>
Fri, 11 Aug 2017 01:18:22 +0000 (18:18 -0700)
committerRichard Russon <rich@flatcap.org>
Fri, 1 Sep 2017 12:44:58 +0000 (13:44 +0100)
The virtual index is modeled after the CONTEXT.  Add a CURATTACH
helper to reduce code verbosity.  Store the actx as menu->data.

Simplify and consolidate the recvattach and compose menu update code
inside a function.

Because compose and recvattach share so much code, change compose to
use the virtual index even though it has no collapse/expand
functionality.

attach.c
attach.h
compose.c
recvattach.c

index 9698f177f90c41755fabdae1087f11a69ed75ee0..113768644beefba8bd082b6c817621b2d11c7971 100644 (file)
--- a/attach.c
+++ b/attach.c
@@ -1104,7 +1104,7 @@ bail0:
   }
 }
 
-void mutt_actx_add_attach(struct AttachCtx *actx, struct AttachPtr *attach, struct Menu *menu)
+void mutt_actx_add_attach(struct AttachCtx *actx, struct AttachPtr *attach)
 {
   int i;
 
@@ -1112,10 +1112,9 @@ void mutt_actx_add_attach(struct AttachCtx *actx, struct AttachPtr *attach, stru
   {
     actx->idxmax += 5;
     safe_realloc(&actx->idx, sizeof(struct AttachPtr *) * actx->idxmax);
+    safe_realloc(&actx->v2r, sizeof(short) * actx->idxmax);
     for (i = actx->idxlen; i < actx->idxmax; i++)
       actx->idx[i] = NULL;
-    if (menu)
-      menu->data = actx->idx;
   }
 
   actx->idx[actx->idxlen++] = attach;
@@ -1163,6 +1162,7 @@ void mutt_actx_free_entries(struct AttachCtx *actx)
     FREE(&actx->idx[i]);
   }
   actx->idxlen = 0;
+  actx->vcount = 0;
 
   for (i = 0; i < actx->fp_len; i++)
     safe_fclose(&actx->fp_idx[i]);
@@ -1183,6 +1183,7 @@ void mutt_free_attach_context(struct AttachCtx **pactx)
   actx = *pactx;
   mutt_actx_free_entries(actx);
   FREE(&actx->idx);
+  FREE(&actx->v2r);
   FREE(&actx->fp_idx);
   FREE(&actx->body_idx);
   FREE(pactx);
index 600f384dc89cc47c8863dd058a5defd267e7b349..151837e6c2cedc8889ad7e88535c53009db52eeb 100644 (file)
--- a/attach.h
+++ b/attach.h
@@ -56,6 +56,9 @@ struct AttachCtx
   short idxlen;
   short idxmax;
 
+  short *v2r;   /**< mapping from virtual to real attachment */
+  short vcount; /**< the number of virtual attachments */
+
   FILE **fp_idx; /**< Extra FILE* used for decryption */
   short fp_len;
   short fp_max;
@@ -83,7 +86,7 @@ void mutt_attach_resend(FILE *fp, struct Header *hdr, struct AttachCtx *actx, st
 void mutt_attach_forward(FILE *fp, struct Header *hdr, struct AttachCtx *actx, struct Body *cur, int flags);
 void mutt_attach_reply(FILE *fp, struct Header *hdr, struct AttachCtx *actx, struct Body *cur, int flags);
 
-void mutt_actx_add_attach (struct AttachCtx *actx, struct AttachPtr *attach, struct Menu *menu);
+void mutt_actx_add_attach (struct AttachCtx *actx, struct AttachPtr *attach);
 void mutt_actx_add_fp (struct AttachCtx *actx, FILE *new_fp);
 void mutt_actx_add_body (struct AttachCtx *actx, struct Body *new_body);
 void mutt_actx_free_entries (struct AttachCtx *actx);
index c5740a64b2891925ef75490c465e7cc1a7020c24..0266ddc42b4056d7ff7b5e5d847cc1313e69c682 100644 (file)
--- a/compose.c
+++ b/compose.c
@@ -78,6 +78,8 @@ static const char *There_are_no_attachments = N_("There are no attachments.");
     break;                                                                     \
   }
 
+#define CURATTACH actx->idx[actx->v2r[menu->current]]
+
 /**
  * enum HeaderField - Ordered list of headers for the compose screen
  */
@@ -228,9 +230,11 @@ static void init_header_padding(void)
 
 static void snd_entry(char *b, size_t blen, struct Menu *menu, int num)
 {
-  mutt_expando_format(b, blen, 0, MuttIndexWindow->cols, NONULL(AttachFormat), mutt_attach_fmt,
-                    (unsigned long) (((struct AttachPtr **) menu->data)[num]),
-                    MUTT_FORMAT_STAT_FILE | MUTT_FORMAT_ARROWCURSOR);
+  struct AttachCtx *actx = (struct AttachCtx *) menu->data;
+
+  mutt_expando_format(b, blen, 0, MuttIndexWindow->cols, NONULL(AttachFormat),
+                      mutt_attach_fmt, (unsigned long) (actx->idx[actx->v2r[num]]),
+                      MUTT_FORMAT_STAT_FILE | MUTT_FORMAT_ARROWCURSOR);
 }
 
 static void redraw_crypt_lines(struct Header *msg)
@@ -485,37 +489,36 @@ static void edit_address_list(int line, struct Address **addr)
   mutt_paddstr(W, buf);
 }
 
-static int delete_attachment(struct Menu *menu, short *idxlen, int x)
+static int delete_attachment(struct AttachCtx *actx, int x)
 {
-  struct AttachPtr **idx = (struct AttachPtr **) menu->data;
+  struct AttachPtr **idx = actx->idx;
+  int rindex = actx->v2r[x];
 
-  menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
-
-  if (x == 0 && menu->max == 1)
+  if (rindex == 0 && actx->idxlen == 1)
   {
     mutt_error(_("You may not delete the only attachment."));
-    idx[x]->content->tagged = false;
+    idx[rindex]->content->tagged = false;
     return -1;
   }
 
-  for (int y = 0; y < *idxlen; y++)
+  for (int y = 0; y < actx->idxlen; y++)
   {
-    if (idx[y]->content->next == idx[x]->content)
+    if (idx[y]->content->next == idx[rindex]->content)
     {
-      idx[y]->content->next = idx[x]->content->next;
+      idx[y]->content->next = idx[rindex]->content->next;
       break;
     }
   }
 
-  idx[x]->content->next = NULL;
-  idx[x]->content->parts = NULL;
-  mutt_free_body(&(idx[x]->content));
-  FREE(&idx[x]->tree);
-  FREE(&idx[x]);
-  for (; x < *idxlen - 1; x++)
-    idx[x] = idx[x + 1];
-  idx[*idxlen - 1] = NULL;
-  menu->max = --(*idxlen);
+  idx[rindex]->content->next = NULL;
+  idx[rindex]->content->parts = NULL;
+  mutt_free_body(&(idx[rindex]->content));
+  FREE(&idx[rindex]->tree);
+  FREE(&idx[rindex]);
+  for (; rindex < actx->idxlen - 1; rindex++)
+    idx[rindex] = idx[rindex + 1];
+  idx[actx->idxlen - 1] = NULL;
+  actx->idxlen--;
 
   return 0;
 }
@@ -535,7 +538,7 @@ static void mutt_gen_compose_attach_list(struct AttachCtx *actx, struct Body *m,
     else
     {
       new = (struct AttachPtr *) safe_calloc(1, sizeof(struct AttachPtr));
-      mutt_actx_add_attach(actx, new, NULL);
+      mutt_actx_add_attach(actx, new);
       new->content = m;
       m->aptr = new;
       new->parent_type = parent_type;
@@ -544,9 +547,29 @@ static void mutt_gen_compose_attach_list(struct AttachCtx *actx, struct Body *m,
       /* We don't support multipart messages in the compose menu yet */
     }
   }
+}
+
+static void mutt_update_compose_menu(struct AttachCtx *actx, struct Menu *menu, int init)
+{
+  if (init)
+  {
+    mutt_gen_compose_attach_list(actx, actx->hdr->content, -1, 0);
+    mutt_attach_init(actx);
+    menu->data = actx;
+  }
+
+  mutt_update_tree(actx);
+
+  menu->max = actx->vcount;
+  if (menu->max)
+  {
+    if (menu->current >= menu->max)
+      menu->current = menu->max - 1;
+  }
+  else
+    menu->current = 0;
 
-  if (level == 0)
-    mutt_update_tree(actx);
+  menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
 }
 
 static void update_idx(struct Menu *menu, struct AttachCtx *actx, struct AttachPtr *new)
@@ -555,10 +578,9 @@ static void update_idx(struct Menu *menu, struct AttachCtx *actx, struct AttachP
   if (actx->idxlen)
     actx->idx[actx->idxlen - 1]->content->next = new->content;
   new->content->aptr = new;
-  mutt_actx_add_attach(actx, new, menu);
-  menu->current = actx->idxlen - 1;
-  mutt_update_tree(actx);
-  menu->max = actx->idxlen;
+  mutt_actx_add_attach(actx, new);
+  mutt_update_compose_menu(actx, menu, 0);
+  menu->current = actx->vcount - 1;
 }
 
 /**
@@ -629,11 +651,12 @@ static unsigned long cum_attachs_size(struct Menu *menu)
 {
   size_t s;
   unsigned short i;
-  struct AttachPtr **idx = menu->data;
+  struct AttachCtx *actx = menu->data;
+  struct AttachPtr **idx = actx->idx;
   struct Content *info = NULL;
   struct Body *b = NULL;
 
-  for (i = 0, s = 0; i < menu->max; i++)
+  for (i = 0, s = 0; i < actx->idxlen; i++)
   {
     b = idx[i]->content;
 
@@ -766,16 +789,10 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
   rd.msg = msg;
   rd.fcc = fcc;
 
-  actx = safe_calloc(sizeof(struct AttachCtx), 1);
-  mutt_gen_compose_attach_list(actx, msg->content, -1, 0);
-  mutt_attach_init(actx);
-
   menu = mutt_new_menu(MENU_COMPOSE);
   menu->offset = HDR_ATTACH;
-  menu->max = actx->idxlen;
   menu->make_entry = snd_entry;
   menu->tag = mutt_tag_attach;
-  menu->data = actx->idx;
 #ifdef USE_NNTP
   if (news)
     menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_COMPOSE, ComposeNewsHelp);
@@ -786,6 +803,10 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
   menu->redraw_data = &rd;
   mutt_push_current_menu(menu);
 
+  actx = safe_calloc(sizeof(struct AttachCtx), 1);
+  actx->hdr = msg;
+  mutt_update_compose_menu(actx, menu, 1);
+
   while (loop)
   {
 #ifdef USE_NNTP
@@ -964,9 +985,7 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
         if (actx->idxlen && actx->idx[actx->idxlen - 1]->content->next)
         {
           mutt_actx_free_entries(actx);
-          mutt_gen_compose_attach_list(actx, msg->content, -1, 0);
-          menu->data = actx->idx;
-          menu->max = actx->idxlen;
+          mutt_update_compose_menu(actx, menu, 1);
         }
 
         menu->redraw = REDRAW_FULL;
@@ -1164,38 +1183,27 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_DELETE:
         CHECK_COUNT;
-        if (actx->idx[menu->current]->unowned)
-          actx->idx[menu->current]->content->unlink = false;
-        if (delete_attachment(menu, &actx->idxlen, menu->current) == -1)
+        if (CURATTACH->unowned)
+          CURATTACH->content->unlink = 0;
+        if (delete_attachment(actx, menu->current) == -1)
           break;
-        mutt_update_tree(actx);
-        if (actx->idxlen)
-        {
-          if (menu->current > actx->idxlen - 1)
-            menu->current = actx->idxlen - 1;
-        }
-        else
-          menu->current = 0;
-
+        mutt_update_compose_menu(actx, menu, 0);
         if (menu->current == 0)
           msg->content = actx->idx[0]->content;
 
-        menu->redraw |= REDRAW_STATUS;
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
         break;
 
-#define CURRENT actx->idx[menu->current]->content
-
       case OP_COMPOSE_TOGGLE_RECODE:
       {
         CHECK_COUNT;
-        if (!mutt_is_text_part(CURRENT))
+        if (!mutt_is_text_part(CURATTACH->content))
         {
           mutt_error(_("Recoding only affects text attachments."));
           break;
         }
-        CURRENT->noconv = !CURRENT->noconv;
-        if (CURRENT->noconv)
+        CURATTACH->content->noconv = !CURATTACH->content->noconv;
+        if (CURATTACH->content->noconv)
           mutt_message(_("The current attachment won't be converted."));
         else
           mutt_message(_("The current attachment will be converted."));
@@ -1203,19 +1211,15 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
         break;
       }
-#undef CURRENT
 
       case OP_COMPOSE_EDIT_DESCRIPTION:
         CHECK_COUNT;
-        strfcpy(buf,
-                actx->idx[menu->current]->content->description ?
-                    actx->idx[menu->current]->content->description :
-                    "",
+        strfcpy(buf, CURATTACH->content->description ? CURATTACH->content->description : "",
                 sizeof(buf));
         /* header names should not be translated */
         if (mutt_get_field("Description: ", buf, sizeof(buf), 0) == 0)
         {
-          mutt_str_replace(&actx->idx[menu->current]->content->description, buf);
+          mutt_str_replace(&CURATTACH->content->description, buf);
           menu->redraw = REDRAW_CURRENT;
         }
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
@@ -1235,7 +1239,7 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
         }
         else
         {
-          mutt_update_encoding(actx->idx[menu->current]->content);
+          mutt_update_encoding(CURATTACH->content);
           menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
         }
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
@@ -1243,18 +1247,18 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_COMPOSE_TOGGLE_DISPOSITION:
         /* toggle the content-disposition between inline/attachment */
-        actx->idx[menu->current]->content->disposition =
-            (actx->idx[menu->current]->content->disposition == DISPINLINE) ? DISPATTACH : DISPINLINE;
+        CURATTACH->content->disposition =
+            (CURATTACH->content->disposition == DISPINLINE) ? DISPATTACH : DISPINLINE;
         menu->redraw = REDRAW_CURRENT;
         break;
 
       case OP_EDIT_TYPE:
         CHECK_COUNT;
         {
-          mutt_edit_content_type(NULL, actx->idx[menu->current]->content, NULL);
+          mutt_edit_content_type(NULL, CURATTACH->content, NULL);
 
           /* this may have been a change to text/something */
-          mutt_update_encoding(actx->idx[menu->current]->content);
+          mutt_update_encoding(CURATTACH->content);
 
           menu->redraw = REDRAW_CURRENT;
         }
@@ -1263,12 +1267,12 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_COMPOSE_EDIT_ENCODING:
         CHECK_COUNT;
-        strfcpy(buf, ENCODING(actx->idx[menu->current]->content->encoding), sizeof(buf));
+        strfcpy(buf, ENCODING(CURATTACH->content->encoding), sizeof(buf));
         if (mutt_get_field("Content-Transfer-Encoding: ", buf, sizeof(buf), 0) == 0 && buf[0])
         {
           if ((i = mutt_check_encoding(buf)) != ENCOTHER && i != ENCUUENCODED)
           {
-            actx->idx[menu->current]->content->encoding = i;
+            CURATTACH->content->encoding = i;
             menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
             mutt_clear_error();
           }
@@ -1310,16 +1314,15 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_COMPOSE_EDIT_FILE:
         CHECK_COUNT;
-        mutt_edit_file(NONULL(Editor), actx->idx[menu->current]->content->filename);
-        mutt_update_encoding(actx->idx[menu->current]->content);
+        mutt_edit_file(NONULL(Editor), CURATTACH->content->filename);
+        mutt_update_encoding(CURATTACH->content);
         menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
         break;
 
       case OP_COMPOSE_TOGGLE_UNLINK:
         CHECK_COUNT;
-        actx->idx[menu->current]->content->unlink =
-            !actx->idx[menu->current]->content->unlink;
+        CURATTACH->content->unlink = !CURATTACH->content->unlink;
 
         menu->redraw = REDRAW_INDEX;
         /* No send2hook since this doesn't change the message. */
@@ -1337,7 +1340,7 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
           }
           menu->redraw = REDRAW_FULL;
         }
-        else if (mutt_get_tmp_attachment(actx->idx[menu->current]->content) == 0)
+        else if (mutt_get_tmp_attachment(CURATTACH->content) == 0)
           menu->redraw = REDRAW_CURRENT;
 
         /* No send2hook since this doesn't change the message. */
@@ -1349,10 +1352,10 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
         int ret;
 
         CHECK_COUNT;
-        if (actx->idx[menu->current]->content->d_filename)
-          src = actx->idx[menu->current]->content->d_filename;
+        if (CURATTACH->content->d_filename)
+          src = CURATTACH->content->d_filename;
         else
-          src = actx->idx[menu->current]->content->filename;
+          src = CURATTACH->content->filename;
         strfcpy(fname, mutt_basename(NONULL(src)), sizeof(fname));
         ret = mutt_get_field(_("Send attachment with name: "), fname, sizeof(fname), MUTT_FILE);
         if (ret == 0)
@@ -1361,7 +1364,7 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
              * As opposed to RENAME_FILE, we don't check fname[0] because it's
              * valid to set an empty string here, to erase what was set
              */
-          mutt_str_replace(&actx->idx[menu->current]->content->d_filename, fname);
+          mutt_str_replace(&CURATTACH->content->d_filename, fname);
           menu->redraw = REDRAW_CURRENT;
         }
       }
@@ -1369,12 +1372,12 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_COMPOSE_RENAME_FILE:
         CHECK_COUNT;
-        strfcpy(fname, actx->idx[menu->current]->content->filename, sizeof(fname));
+        strfcpy(fname, CURATTACH->content->filename, sizeof(fname));
         mutt_pretty_mailbox(fname, sizeof(fname));
         if (mutt_get_field(_("Rename to: "), fname, sizeof(fname), MUTT_FILE) == 0 &&
             fname[0])
         {
-          if (stat(actx->idx[menu->current]->content->filename, &st) == -1)
+          if (stat(CURATTACH->content->filename, &st) == -1)
           {
             /* L10N:
                "stat" is a system call. Do "man 2 stat" for more information. */
@@ -1383,14 +1386,14 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
           }
 
           mutt_expand_path(fname, sizeof(fname));
-          if (mutt_rename_file(actx->idx[menu->current]->content->filename, fname))
+          if (mutt_rename_file(CURATTACH->content->filename, fname))
             break;
 
-          mutt_str_replace(&actx->idx[menu->current]->content->filename, fname);
+          mutt_str_replace(&CURATTACH->content->filename, fname);
           menu->redraw = REDRAW_CURRENT;
 
-          if (actx->idx[menu->current]->content->stamp >= st.st_mtime)
-            mutt_stamp_attachment(actx->idx[menu->current]->content);
+          if (CURATTACH->content->stamp >= st.st_mtime)
+            mutt_stamp_attachment(CURATTACH->content);
         }
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
         break;
@@ -1443,14 +1446,14 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
         }
         update_idx(menu, actx, new);
 
-        actx->idx[menu->current]->content->type = itype;
-        mutt_str_replace(&actx->idx[menu->current]->content->subtype, p);
-        actx->idx[menu->current]->content->unlink = true;
+        CURATTACH->content->type = itype;
+        mutt_str_replace(&CURATTACH->content->subtype, p);
+        CURATTACH->content->unlink = true;
         menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
 
-        if (mutt_compose_attachment(actx->idx[menu->current]->content))
+        if (mutt_compose_attachment(CURATTACH->content))
         {
-          mutt_update_encoding(actx->idx[menu->current]->content);
+          mutt_update_encoding(CURATTACH->content);
           menu->redraw = REDRAW_FULL;
         }
       }
@@ -1459,9 +1462,9 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
 
       case OP_COMPOSE_EDIT_MIME:
         CHECK_COUNT;
-        if (mutt_edit_attachment(actx->idx[menu->current]->content))
+        if (mutt_edit_attachment(CURATTACH->content))
         {
-          mutt_update_encoding(actx->idx[menu->current]->content);
+          mutt_update_encoding(CURATTACH->content);
           menu->redraw = REDRAW_FULL;
         }
         mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
@@ -1478,17 +1481,15 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
       case OP_SAVE:
         CHECK_COUNT;
         mutt_save_attachment_list(NULL, menu->tagprefix,
-                                  menu->tagprefix ? msg->content :
-                                                    actx->idx[menu->current]->content,
+                                  menu->tagprefix ? msg->content : CURATTACH->content,
                                   NULL, menu);
         /* no send2hook, since this doesn't modify the message */
         break;
 
       case OP_PRINT:
         CHECK_COUNT;
-        mutt_print_attachment_list(
-            NULL, menu->tagprefix,
-            menu->tagprefix ? msg->content : actx->idx[menu->current]->content);
+        mutt_print_attachment_list(NULL, menu->tagprefix,
+                                   menu->tagprefix ? msg->content : CURATTACH->content);
         /* no send2hook, since this doesn't modify the message */
         break;
 
@@ -1496,8 +1497,7 @@ int mutt_compose_menu(struct Header *msg, /* structure for new message */
       case OP_FILTER:
         CHECK_COUNT;
         mutt_pipe_attachment_list(NULL, menu->tagprefix,
-                                  menu->tagprefix ? msg->content :
-                                                    actx->idx[menu->current]->content,
+                                  menu->tagprefix ? msg->content : CURATTACH->content,
                                   op == OP_FILTER);
         if (op == OP_FILTER) /* cte might have changed */
           menu->redraw = menu->tagprefix ? REDRAW_FULL : REDRAW_CURRENT;
index c736b2dcbbdd324d753d3d05a1b2f3d3c1647c2e..770ab111e366504d335f0758cfa9d8fd39954dee 100644 (file)
@@ -67,25 +67,54 @@ static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
     break;                                                                     \
   }
 
+#define CURATTACH actx->idx[actx->v2r[menu->current]]
+
 static const struct Mapping AttachHelp[] = {
   { N_("Exit"), OP_EXIT },   { N_("Save"), OP_SAVE }, { N_("Pipe"), OP_PIPE },
   { N_("Print"), OP_PRINT }, { N_("Help"), OP_HELP }, { NULL, 0 },
 };
 
+static void mutt_update_v2r(struct AttachCtx *actx)
+{
+  int vindex, rindex, curlevel;
+
+  vindex = rindex = 0;
+
+  while (rindex < actx->idxlen)
+  {
+    actx->v2r[vindex++] = rindex;
+    if (actx->idx[rindex]->content->collapsed)
+    {
+      curlevel = actx->idx[rindex]->level;
+      do
+        rindex++;
+      while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel));
+    }
+    else
+      rindex++;
+  }
+
+  actx->vcount = vindex;
+}
+
 void mutt_update_tree(struct AttachCtx *actx)
 {
   char buf[STRING];
   char *s = NULL;
+  int rindex, vindex;
 
-  for (int x = 0; x < actx->idxlen; x++)
+  mutt_update_v2r(actx);
+
+  for (vindex = 0; vindex < actx->vcount; vindex++)
   {
-    actx->idx[x]->num = x;
-    if (2 * (actx->idx[x]->level + 2) < sizeof(buf))
+    rindex = actx->v2r[vindex];
+    actx->idx[rindex]->num = vindex;
+    if (2 * (actx->idx[rindex]->level + 2) < sizeof(buf))
     {
-      if (actx->idx[x]->level)
+      if (actx->idx[rindex]->level)
       {
-        s = buf + 2 * (actx->idx[x]->level - 1);
-        *s++ = (actx->idx[x]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER;
+        s = buf + 2 * (actx->idx[rindex]->level - 1);
+        *s++ = (actx->idx[rindex]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER;
         *s++ = MUTT_TREE_HLINE;
         *s++ = MUTT_TREE_RARROW;
       }
@@ -94,18 +123,18 @@ void mutt_update_tree(struct AttachCtx *actx)
       *s = 0;
     }
 
-    if (actx->idx[x]->tree)
+    if (actx->idx[rindex]->tree)
     {
-      if (mutt_strcmp(actx->idx[x]->tree, buf) != 0)
-        mutt_str_replace(&actx->idx[x]->tree, buf);
+      if (mutt_strcmp(actx->idx[rindex]->tree, buf) != 0)
+        mutt_str_replace(&actx->idx[rindex]->tree, buf);
     }
     else
-      actx->idx[x]->tree = safe_strdup(buf);
+      actx->idx[rindex]->tree = safe_strdup(buf);
 
-    if (2 * (actx->idx[x]->level + 2) < sizeof(buf) && actx->idx[x]->level)
+    if (2 * (actx->idx[rindex]->level + 2) < sizeof(buf) && actx->idx[rindex]->level)
     {
-      s = buf + 2 * (actx->idx[x]->level - 1);
-      *s++ = (actx->idx[x]->content->next) ? '\005' : '\006';
+      s = buf + 2 * (actx->idx[rindex]->level - 1);
+      *s++ = (actx->idx[rindex]->content->next) ? '\005' : '\006';
       *s++ = '\006';
     }
   }
@@ -345,14 +374,17 @@ const char *mutt_attach_fmt(char *dest, size_t destlen, size_t col, int cols,
 
 static void attach_entry(char *b, size_t blen, struct Menu *menu, int num)
 {
-  mutt_expando_format(b, blen, 0, MuttIndexWindow->cols, NONULL(AttachFormat), mutt_attach_fmt,
-                    (unsigned long) (((struct AttachPtr **) menu->data)[num]),
-                    MUTT_FORMAT_ARROWCURSOR);
+  struct AttachCtx *actx = (struct AttachCtx *) menu->data;
+
+  mutt_expando_format(b, blen, 0, MuttIndexWindow->cols, NONULL(AttachFormat),
+                      mutt_attach_fmt, (unsigned long) (actx->idx[actx->v2r[num]]),
+                      MUTT_FORMAT_ARROWCURSOR);
 }
 
 int mutt_tag_attach(struct Menu *menu, int n, int m)
 {
-  struct Body *cur = ((struct AttachPtr **) menu->data)[n]->content;
+  struct AttachCtx *actx = (struct AttachCtx *) menu->data;
+  struct Body *cur = actx->idx[actx->v2r[n]]->content;
   bool ot = cur->tagged;
 
   cur->tagged = (m >= 0 ? m : !cur->tagged);
@@ -785,8 +817,7 @@ int mutt_attach_display_loop(struct Menu *menu, int op, FILE *fp, struct Header
       /* fall through */
 
       case OP_VIEW_ATTACH:
-        op = mutt_view_attachment(fp, actx->idx[menu->current]->content,
-                                  MUTT_REGULAR, hdr, actx);
+        op = mutt_view_attachment(fp, CURATTACH->content, MUTT_REGULAR, hdr, actx);
         break;
 
       case OP_NEXT_ENTRY:
@@ -812,7 +843,7 @@ int mutt_attach_display_loop(struct Menu *menu, int op, FILE *fp, struct Header
       case OP_EDIT_TYPE:
         /* when we edit the content-type, we should redisplay the attachment
            immediately */
-        mutt_edit_content_type(hdr, actx->idx[menu->current]->content, fp);
+        mutt_edit_content_type(hdr, CURATTACH->content, fp);
         if (recv)
         {
           /* Editing the content type can rewrite the body structure. */
@@ -923,7 +954,7 @@ static void mutt_generate_recvattach_list(struct AttachCtx *actx, struct Header
     else
     {
       new = (struct AttachPtr *) safe_calloc(1, sizeof(struct AttachPtr));
-      mutt_actx_add_attach(actx, new, NULL);
+      mutt_actx_add_attach(actx, new);
 
       new->content = m;
       new->fp = fp;
@@ -954,14 +985,16 @@ void mutt_attach_init(struct AttachCtx *actx)
 static void mutt_update_recvattach_menu(struct AttachCtx *actx, struct Menu *menu, int init)
 {
   if (init)
+  {
     mutt_generate_recvattach_list(actx, actx->hdr, actx->hdr->content,
                                   actx->root_fp, -1, 0, 0);
+    mutt_attach_init(actx);
+    menu->data = actx;
+  }
 
-  /* TODO: regenerate virtual index */
   mutt_update_tree(actx);
 
-  menu->max = actx->idxlen;
-  menu->data = actx->idx;
+  menu->max = actx->vcount;
 
   if (menu->current >= menu->max)
     menu->current = menu->max - 1;
@@ -1029,7 +1062,6 @@ void mutt_view_attachments(struct Header *hdr)
   actx->hdr = hdr;
   actx->root_fp = msg->fp;
   mutt_update_recvattach_menu(actx, menu, 1);
-  mutt_attach_init(actx);
 
   while (true)
   {
@@ -1040,12 +1072,12 @@ void mutt_view_attachments(struct Header *hdr)
     switch (op)
     {
       case OP_ATTACH_VIEW_MAILCAP:
-        mutt_view_attachment(fp, actx->idx[menu->current]->content, MUTT_MAILCAP, hdr, actx);
+        mutt_view_attachment(fp, CURATTACH->content, MUTT_MAILCAP, hdr, actx);
         menu->redraw = REDRAW_FULL;
         break;
 
       case OP_ATTACH_VIEW_TEXT:
-        mutt_view_attachment(fp, actx->idx[menu->current]->content, MUTT_AS_TEXT, hdr, actx);
+        mutt_view_attachment(fp, CURATTACH->content, MUTT_AS_TEXT, hdr, actx);
         menu->redraw = REDRAW_FULL;
         break;
 
@@ -1056,15 +1088,15 @@ void mutt_view_attachments(struct Header *hdr)
         continue;
 
       case OP_ATTACH_COLLAPSE:
-        if (!actx->idx[menu->current]->content->parts)
+        if (!CURATTACH->content->parts)
         {
           mutt_error(_("There are no subparts to show!"));
           break;
         }
-        if (!actx->idx[menu->current]->content->collapsed)
-          attach_collapse(actx->idx[menu->current]->content, 1, 0, 1);
+        if (!CURATTACH->content->collapsed)
+          attach_collapse(CURATTACH->content, 1, 0, 1);
         else
-          attach_collapse(actx->idx[menu->current]->content, 0, 1, 1);
+          attach_collapse(CURATTACH->content, 0, 1, 1);
         mutt_update_recvattach_menu(actx, menu, 0);
         break;
 
@@ -1076,16 +1108,15 @@ void mutt_view_attachments(struct Header *hdr)
         if ((WithCrypto & APPLICATION_PGP))
         {
           crypt_pgp_extract_keys_from_attachment_list(
-              fp, menu->tagprefix,
-              menu->tagprefix ? cur : actx->idx[menu->current]->content);
+              fp, menu->tagprefix, menu->tagprefix ? cur : CURATTACH->content);
           menu->redraw = REDRAW_FULL;
         }
         break;
 
       case OP_CHECK_TRADITIONAL:
         if ((WithCrypto & APPLICATION_PGP) &&
-            crypt_pgp_check_traditional(
-                fp, menu->tagprefix ? cur : actx->idx[menu->current]->content, menu->tagprefix))
+            crypt_pgp_check_traditional(fp, menu->tagprefix ? cur : CURATTACH->content,
+                                        menu->tagprefix))
         {
           hdr->security = crypt_query(cur);
           menu->redraw = REDRAW_FULL;
@@ -1093,21 +1124,18 @@ void mutt_view_attachments(struct Header *hdr)
         break;
 
       case OP_PRINT:
-        mutt_print_attachment_list(
-            fp, menu->tagprefix,
-            menu->tagprefix ? cur : actx->idx[menu->current]->content);
+        mutt_print_attachment_list(fp, menu->tagprefix,
+                                   menu->tagprefix ? cur : CURATTACH->content);
         break;
 
       case OP_PIPE:
-        mutt_pipe_attachment_list(
-            fp, menu->tagprefix,
-            menu->tagprefix ? cur : actx->idx[menu->current]->content, 0);
+        mutt_pipe_attachment_list(fp, menu->tagprefix,
+                                  menu->tagprefix ? cur : CURATTACH->content, 0);
         break;
 
       case OP_SAVE:
-        mutt_save_attachment_list(
-            fp, menu->tagprefix,
-            menu->tagprefix ? cur : actx->idx[menu->current]->content, hdr, menu);
+        mutt_save_attachment_list(fp, menu->tagprefix,
+                                  menu->tagprefix ? cur : CURATTACH->content, hdr, menu);
 
         if (!menu->tagprefix && option(OPT_RESOLVE) && menu->current < menu->max - 1)
           menu->current++;
@@ -1149,9 +1177,9 @@ void mutt_view_attachments(struct Header *hdr)
         }
         if (!menu->tagprefix)
         {
-          if (actx->idx[menu->current]->parent_type == TYPEMULTIPART)
+          if (CURATTACH->parent_type == TYPEMULTIPART)
           {
-            actx->idx[menu->current]->content->deleted = true;
+            CURATTACH->content->deleted = true;
             if (option(OPT_RESOLVE) && menu->current < menu->max - 1)
             {
               menu->current++;
@@ -1189,7 +1217,7 @@ void mutt_view_attachments(struct Header *hdr)
         CHECK_READONLY;
         if (!menu->tagprefix)
         {
-          actx->idx[menu->current]->content->deleted = false;
+          CURATTACH->content->deleted = false;
           if (option(OPT_RESOLVE) && menu->current < menu->max - 1)
           {
             menu->current++;
@@ -1215,38 +1243,38 @@ void mutt_view_attachments(struct Header *hdr)
 
       case OP_RESEND:
         CHECK_ATTACH;
-        mutt_attach_resend(fp, hdr, actx, menu->tagprefix ? NULL : actx->idx[menu->current]->content);
+        mutt_attach_resend(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content);
         menu->redraw = REDRAW_FULL;
         break;
 
       case OP_BOUNCE_MESSAGE:
         CHECK_ATTACH;
-        mutt_attach_bounce(fp, hdr, actx, menu->tagprefix ? NULL : actx->idx[menu->current]->content);
+        mutt_attach_bounce(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content);
         menu->redraw = REDRAW_FULL;
         break;
 
       case OP_FORWARD_MESSAGE:
         CHECK_ATTACH;
-        mutt_attach_forward(fp, hdr, actx, menu->tagprefix ? NULL : actx->idx[menu->current]->content, 0);
+        mutt_attach_forward(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content, 0);
         menu->redraw = REDRAW_FULL;
         break;
 
 #ifdef USE_NNTP
       case OP_FORWARD_TO_GROUP:
         CHECK_ATTACH;
-        mutt_attach_forward(fp, hdr, actx, menu->tagprefix ? NULL : actx->idx[menu->current]->content, SENDNEWS);
+        mutt_attach_forward(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content, SENDNEWS);
         menu->redraw = REDRAW_FULL;
         break;
 
       case OP_FOLLOWUP:
         CHECK_ATTACH;
 
-        if (!actx->idx[menu->current]->content->hdr->env->followup_to ||
-            (mutt_strcasecmp(actx->idx[menu->current]->content->hdr->env->followup_to, "poster") != 0) ||
+        if (!CURATTACH->content->hdr->env->followup_to ||
+            (mutt_strcasecmp(CURATTACH->content->hdr->env->followup_to, "poster") != 0) ||
             query_quadoption(OPT_FOLLOW_UP_TO_POSTER,
                              _("Reply by mail as poster prefers?")) != MUTT_YES)
         {
-          mutt_attach_reply(fp, hdr, actx, menu->tagprefix ? NULL : actx->idx[menu->current]->content,
+          mutt_attach_reply(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content,
                             SENDNEWS | SENDREPLY);
           menu->redraw = REDRAW_FULL;
           break;
@@ -1261,13 +1289,12 @@ void mutt_view_attachments(struct Header *hdr)
 
         flags = SENDREPLY | (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
                 (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
-        mutt_attach_reply(fp, hdr, actx,
-                          menu->tagprefix ? NULL : actx->idx[menu->current]->content, flags);
+        mutt_attach_reply(fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content, flags);
         menu->redraw = REDRAW_FULL;
         break;
 
       case OP_EDIT_TYPE:
-        mutt_edit_content_type(hdr, actx->idx[menu->current]->content, fp);
+        mutt_edit_content_type(hdr, CURATTACH->content, fp);
         /* Editing the content type can rewrite the body structure. */
         for (i = 0; i < actx->idxlen; i++)
           actx->idx[i]->content = NULL;