]> granicus.if.org Git - mutt/commitdiff
Advanced threading, v 5.1. From Daniel Eisenbud
authorThomas Roessler <roessler@does-not-exist.org>
Wed, 31 Oct 2001 09:20:38 +0000 (09:20 +0000)
committerThomas Roessler <roessler@does-not-exist.org>
Wed, 31 Oct 2001 09:20:38 +0000 (09:20 +0000)
<eisenbud@cs.swarthmore.edu>.

19 files changed:
commands.c
curs_main.c
flags.c
hdrline.c
imap/message.c
init.h
mbox.c
menu.c
mh.c
mutt.h
mx.c
mx.h
parse.c
pop.c
postpone.c
protos.h
sort.c
sort.h
thread.c

index 0880cc4b6ed25b840440373b725d4971b644e1b4..115c3367db4a129f43d00ff5737e2dca015e3b91 100644 (file)
@@ -535,8 +535,10 @@ void mutt_enter_command (void)
   BUFFER err, token;
   char buffer[LONG_STRING], errbuf[SHORT_STRING];
   int r;
-  int old_strictthreads = option (OPTSTRICTTHREADS);
-  int old_sortre       = option (OPTSORTRE);
+  int old_strictthreads  = option (OPTSTRICTTHREADS);
+  int old_sortre        = option (OPTSORTRE);
+  int old_hidemissing   = option (OPTHIDEMISSING);
+  int old_threadreceived = option (OPTTHREADRECEIVED);
 
   buffer[0] = 0;
   if (mutt_get_field (":", buffer, sizeof (buffer), M_COMMAND) != 0 || !buffer[0])
@@ -557,7 +559,9 @@ void mutt_enter_command (void)
       mutt_error ("%s", errbuf);
   }
   if (option (OPTSTRICTTHREADS) != old_strictthreads ||
-      option (OPTSORTRE) != old_sortre)
+      option (OPTSORTRE) != old_sortre ||
+      option (OPTHIDEMISSING) != old_hidemissing ||
+      option (OPTTHREADRECEIVED) != old_threadreceived)
     set_option (OPTNEEDRESORT);
 }
 
index 01b1e6ea03da48ae321934bfd06c6cfa5fddebf4..32a122110648175a4d8f4ff70acd409a72886f7e 100644 (file)
@@ -107,12 +107,12 @@ void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num)
 {
   format_flag flag = M_FORMAT_MAKEPRINT | M_FORMAT_ARROWCURSOR | M_FORMAT_INDEX;
   int edgemsgno, reverse = Sort & SORT_REVERSE;
-  HEADER *tmp, *h = Context->hdrs[Context->v2r[num]];
+  HEADER *h = Context->hdrs[Context->v2r[num]];
+  THREAD *tmp;
 
   if ((Sort & SORT_MASK) == SORT_THREADS && h->tree)
   {
     flag |= M_FORMAT_TREE; /* display the thread tree */
-
     if (h->display_subject)
       flag |= M_FORMAT_FORCESUBJ;
     else
@@ -127,26 +127,32 @@ void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num)
       else
        edgemsgno = Context->v2r[menu->top];
 
-      for (tmp = h->parent; tmp; tmp = tmp->parent)
+      for (tmp = h->thread->parent; tmp; tmp = tmp->parent)
       {
+       if (!tmp->message)
+         continue;
+
        /* if no ancestor is visible on current screen, provisionally force
         * subject... */
-       if (reverse ? tmp->msgno > edgemsgno : tmp->msgno < edgemsgno)
+       if (reverse ? tmp->message->msgno > edgemsgno : tmp->message->msgno < edgemsgno)
        {
          flag |= M_FORMAT_FORCESUBJ;
          break;
        }
-       else if (tmp->virtual >= 0)
+       else if (tmp->message->virtual >= 0)
          break;
       }
-      if ((flag & M_FORMAT_FORCESUBJ) && h->prev)
+      if (flag & M_FORMAT_FORCESUBJ)
       {
-       for (tmp = h->prev; tmp; tmp = tmp->prev)
+       for (tmp = h->thread->prev; tmp; tmp = tmp->prev)
        {
+         if (!tmp->message)
+           continue;
+
          /* ...but if a previous sibling is available, don't force it */
-         if (reverse ? tmp->msgno > edgemsgno : tmp->msgno < edgemsgno)
+         if (reverse ? tmp->message->msgno > edgemsgno : tmp->message->msgno < edgemsgno)
            break;
-         else if (tmp->virtual >= 0)
+         else if (tmp->message->virtual >= 0)
          {
            flag &= ~M_FORMAT_FORCESUBJ;
            break;
@@ -305,21 +311,21 @@ static void update_index (MUTTMENU *menu, CONTEXT *ctx, int check,
   
   /* if the mailbox was reopened, need to rethread from scratch */
   mutt_sort_headers (Context, (check == M_REOPENED));
-  
+
   /* uncollapse threads with new mail */
   if ((Sort & SORT_MASK) == SORT_THREADS)
   {
     if (check == M_REOPENED)
     {
-      HEADER *h;
+      THREAD *h, *j;
       
-      h = Context->tree;
       Context->collapsed = 0;
       
-      while (h)
+      for (h = Context->tree; h; h = h->next)
       {
-       mutt_uncollapse_thread (Context, h);
-       h = h->next;
+       for (j = h; !j->message; j = j->child)
+         ;
+       mutt_uncollapse_thread (Context, j->message);
       }
       mutt_set_virtual (Context);
     }
@@ -793,7 +799,8 @@ int mutt_index_menu (void)
          else
            menu->current = 0;
          menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
-         mutt_linearize_tree (Context, 0);
+         if (Sort & SORT_THREADS)
+           mutt_linearize_tree (Context, 0);
        }
        break;    
 
@@ -1537,6 +1544,7 @@ int mutt_index_menu (void)
 
         {
          HEADER *h, *base;
+         THREAD *thread, *top;
          int final;
          
          if (CURHDR->collapsed)
@@ -1548,10 +1556,14 @@ int mutt_index_menu (void)
          
          base = Context->hdrs[Context->v2r[final]];
          
-         h = Context->tree;
+         top = Context->tree;
          Context->collapsed = !Context->collapsed;
-         while (h)
+         while ((thread = top) != NULL)
          {
+           while (!thread->message)
+             thread = thread->child;
+           h = thread->message;
+
            if (h->collapsed != Context->collapsed)
            {
              if (h->collapsed)
@@ -1559,7 +1571,7 @@ int mutt_index_menu (void)
              else if (option (OPTCOLLAPSEUNREAD) || !UNREAD (h))
                mutt_collapse_thread (Context, h);
            }
-           h = h->next;
+           top = top->next;
          }
          
          mutt_set_virtual (Context);
@@ -1573,8 +1585,8 @@ int mutt_index_menu (void)
          }
          
          menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
-         break;
        }
+       break;
       
       /* --------------------------------------------------------------------
        * These functions are invoked directly from the internal-pager
diff --git a/flags.c b/flags.c
index 60ae4c7e274fc6b0e4c6ed978c7e0cc5b956a162..a231eca0eb833c2c38bd2f88b7c683ae68584014 100644 (file)
--- a/flags.c
+++ b/flags.c
@@ -240,10 +240,9 @@ void mutt_tag_set_flag (int flag, int bf)
     if (Context->hdrs[Context->v2r[j]]->tagged)
       mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], flag, bf);
 }
-
-int mutt_thread_set_flag (HEADER *cur, int flag, int bf, int subthread)
+int mutt_thread_set_flag (HEADER *hdr, int flag, int bf, int subthread)
 {
-  HEADER *start;
+  THREAD *start, *cur = hdr->thread;
   
   if ((Sort & SORT_MASK) != SORT_THREADS)
   {
@@ -256,12 +255,17 @@ int mutt_thread_set_flag (HEADER *cur, int flag, int bf, int subthread)
       cur = cur->parent;
   start = cur;
   
-  mutt_set_flag (Context, cur, flag, bf);
+  if (cur->message)
+    mutt_set_flag (Context, cur->message, flag, bf);
+
   if ((cur = cur->child) == NULL)
     return (0);
+
   FOREVER
   {
-    mutt_set_flag (Context, cur, flag, bf);
+    if (cur->message)
+      mutt_set_flag (Context, cur->message, flag, bf);
+
     if (cur->child)
       cur = cur->child;
     else if (cur->next)
index 0717261a0af77d806d2148366696b4510c65bd28..54fae0fed0d6fb70e05cb84f256005bf3cc53824 100644 (file)
--- a/hdrline.c
+++ b/hdrline.c
@@ -411,12 +411,12 @@ hdr_format_str (char *dest,
 
     case 'e':
       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
-      snprintf (dest, destlen, fmt, mutt_msgno_in_thread(hdr) + 1);
+      snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr, 1));
       break;
 
     case 'E':
       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
-      snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr));
+      snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr, 0));
       break;
 
     case 'f':
@@ -653,11 +653,13 @@ hdr_format_str (char *dest,
        i = 1;  /* reduce reuse recycle */
        htmp = NULL;
        if (flags & M_FORMAT_TREE
-           && (hdr->prev && hdr->prev->env->x_label))
-         htmp = hdr->prev;
+           && (hdr->thread->prev && hdr->thread->prev->message
+               && hdr->thread->prev->message->env->x_label))
+         htmp = hdr->thread->prev->message;
        else if (flags & M_FORMAT_TREE
-                && (hdr->parent && hdr->parent->env->x_label))
-         htmp = hdr->parent;
+                && (hdr->thread->parent && hdr->thread->parent->message
+                    && hdr->thread->parent->message->env->x_label))
+         htmp = hdr->thread->parent->message;
        if (htmp && mutt_strcasecmp (hdr->env->x_label,
                                     htmp->env->x_label) == 0)
          i = 0;
@@ -674,7 +676,7 @@ hdr_format_str (char *dest,
         mutt_format_s (dest, destlen, prefix, "");
 
       break;
-      
+
     default:
       snprintf (dest, destlen, "%%%s%c", prefix, op);
       break;
index a77c44d42a3780a590bc272afb29129365fedf41..62510b0c18b6bf7a7a1d33d8735ede674573f8be 100644 (file)
@@ -53,7 +53,7 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend)
   char tempfile[_POSIX_PATH_MAX];
   int msgno;
   IMAP_HEADER h;
-  int rc, mfhrc;
+  int rc, mfhrc, oldmsgcount;
   int fetchlast = 0;
   const char *want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO LINES X-LABEL";
 
@@ -120,6 +120,8 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend)
     memset (&h, 0, sizeof (h));
     h.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA));
 
+    oldmsgcount = ctx->msgcount;
+
     /* this DO loop does two things:
      * 1. handles untagged messages, so we can try again on the same msg
      * 2. fetches the tagged response at the end of the last message.
@@ -167,11 +169,14 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend)
       /* content built as a side-effect of mutt_read_rfc822_header */
       ctx->hdrs[msgno]->content->length = h.content_length;
 
-      mx_update_context (ctx); /* increments ->msgcount */
+      ctx->msgcount++;
     }
     while ((rc != IMAP_CMD_OK) && ((mfhrc == -1) ||
       ((msgno + 1) >= fetchlast)));
 
+    if (ctx->msgcount > oldmsgcount)
+      mx_update_context (ctx, ctx->msgcount - oldmsgcount);
+
     if ((mfhrc < -1) || ((rc != IMAP_CMD_CONTINUE) && (rc != IMAP_CMD_OK)))
     {
       imap_free_header_data ((void**) &h.data);
@@ -343,15 +348,15 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno)
    * mutt_free_envelope gets called, but keep their spots in the hash. This
    * confuses threading. Alternatively we could try to merge the new
    * envelope into the old one. Also messy and lowlevel. */
-  if (h->env->message_id)
+  if (ctx->id_hash && h->env->message_id)
     hash_delete (ctx->id_hash, h->env->message_id, h, NULL);
-  if (h->env->real_subj)
+  if (ctx->subj_hash && h->env->real_subj)
     hash_delete (ctx->subj_hash, h->env->real_subj, h, NULL);
   mutt_free_envelope (&h->env);
   h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0);
-  if (h->env->message_id)
+  if (ctx->id_hash && h->env->message_id)
     hash_insert (ctx->id_hash, h->env->message_id, h, 0);
-  if (h->env->real_subj)
+  if (ctx->subj_hash && h->env->real_subj)
     hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
 
   /* see above. We want the new status in h->read, so we unset it manually
diff --git a/init.h b/init.h
index 96c6f024a7ebcd4921221ca824aa30ad2dd7dc1f..228d275757d7b5d932579d7436372c5ca3c908af 100644 (file)
--- a/init.h
+++ b/init.h
@@ -640,6 +640,13 @@ struct option_t MuttVars[] = {
   ** affect the generation of Message-IDs, and it will not lead to the 
   ** cut-off of first-level domains.
   */
+  { "hide_missing",    DT_BOOL, R_NONE, OPTHIDEMISSING, 1 },
+  /*
+  ** .pp
+  ** When set, mutt will not indicate the presence of missing messages
+  ** whose ancestors neither have siblings nor are in the current mailbox,
+  ** in the thread tree.
+  */
   { "history",         DT_NUM,  R_NONE, UL &HistSize, 10 },
   /*
   ** .pp
@@ -2160,6 +2167,12 @@ struct option_t MuttVars[] = {
   ** .pp
   ** Note that $$indent_string is ignored when this option is set.
   */
+  { "thread_received", DT_BOOL, R_NONE, OPTTHREADRECEIVED, 0 },
+  /*
+  ** .pp
+  ** When set, mutt uses the date received rather than the date sent
+  ** to thread messages by subject.
+  */
   { "thorough_search", DT_BOOL, R_NONE, OPTTHOROUGHSRC, 0 },
   /*
   ** .pp
diff --git a/mbox.c b/mbox.c
index 4a00d761ab7ba7f8db6fa79304e37b289c0176d8..9c72c3246921933886476fb82174d26c7fd0a2ce 100644 (file)
--- a/mbox.c
+++ b/mbox.c
@@ -82,7 +82,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx)
 {
   char buf[HUGE_STRING];
   char return_path[LONG_STRING];
-  int count = 0;
+  int count = 0, oldmsgcount = ctx->msgcount;
   int lines;
   time_t t, tz;
   long loc, tmploc;
@@ -138,6 +138,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx)
 
       if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL)
       {
+       /* TODO: memory leak??? */
        dprint (1, (debugfile, "mmdf_parse_mailbox: unexpected EOF\n"));
        break;
       }
@@ -201,7 +202,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx)
       if (!hdr->env->from)
        hdr->env->from = rfc822_cpy_adr (hdr->env->return_path);
 
-      mx_update_context (ctx);
+      ctx->msgcount++;
       if(ctx->magic == M_KENDRA && feof(ctx->fp))
        break;
     }
@@ -213,7 +214,10 @@ int mmdf_parse_mailbox (CONTEXT *ctx)
     }
   }
 
-  return 0;
+  if (ctx->msgcount > oldmsgcount)
+    mx_update_context (ctx, ctx->msgcount - oldmsgcount);
+
+  return (0);
 }
 
 /* Note that this function is also called when new mail is appended to the
@@ -359,7 +363,7 @@ int mbox_parse_mailbox (CONTEXT *ctx)
        }
       }
 
-      mx_update_context (ctx);
+      ctx->msgcount++;
 
       if (!curhdr->env->return_path && return_path[0])
        curhdr->env->return_path = rfc822_parse_adrlist (curhdr->env->return_path, return_path);
@@ -392,6 +396,8 @@ int mbox_parse_mailbox (CONTEXT *ctx)
 
     if (!PREV->lines)
       PREV->lines = lines ? lines - 1 : 0;
+
+    mx_update_context (ctx, count);
   }
 
   return (0);
@@ -1074,8 +1080,11 @@ int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint)
   old_msgcount = 0;
   
   /* simulate a close */
-  hash_destroy (&ctx->id_hash, NULL);
-  hash_destroy (&ctx->subj_hash, NULL);
+  if (ctx->id_hash)
+    hash_destroy (&ctx->id_hash, NULL);
+  if (ctx->subj_hash)
+    hash_destroy (&ctx->subj_hash, NULL);
+  mutt_clear_threads (ctx);
   safe_free ((void **) &ctx->v2r);
   if (ctx->readonly)
   {
@@ -1100,8 +1109,8 @@ int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint)
   ctx->unread = 0;
   ctx->flagged = 0;
   ctx->changed = 0;
-  ctx->id_hash = hash_create (1031);
-  ctx->subj_hash = hash_create (1031);
+  ctx->id_hash = NULL;
+  ctx->subj_hash = NULL;
 
   switch (ctx->magic)
   {
diff --git a/menu.c b/menu.c
index bccf2c285c688d6581784f07028264c79b491717..7bea43ca7587a19ce4a2f14224b8654454a16f55 100644 (file)
--- a/menu.c
+++ b/menu.c
@@ -100,6 +100,9 @@ static void print_enriched_string (int attr, unsigned char *s, int do_color)
          case M_TREE_HIDDEN:
            addch ('&');
            break;
+         case M_TREE_MISSING:
+           addch ('?');
+           break;
        }
        s++, n--;
       }
diff --git a/mh.c b/mh.c
index b65aa831156ed09792086835c4bc474da5a7eb57..e6983d7ad4adb8a8bd922cf6c2f85b453b6a56a4 100644 (file)
--- a/mh.c
+++ b/mh.c
@@ -668,6 +668,8 @@ static int maildir_parse_dir(CONTEXT *ctx, struct maildir ***last,
 
 static void maildir_add_to_context(CONTEXT *ctx, struct maildir *md)
 {
+  int oldmsgcount = ctx->msgcount;
+
   while(md)
   {
     
@@ -692,10 +694,13 @@ static void maildir_add_to_context(CONTEXT *ctx, struct maildir *md)
        md->h->content->length + md->h->content->offset - md->h->content->hdr_offset;
       
       md->h = NULL;
-      mx_update_context(ctx);
+      ctx->msgcount++;
     }
     md = md->next;
   }
+
+  if (ctx->msgcount > oldmsgcount)
+    mx_update_context (ctx, ctx->msgcount - oldmsgcount);
 }
 
 static void maildir_move_to_context(CONTEXT *ctx, struct maildir **md)
diff --git a/mutt.h b/mutt.h
index 40d36520905dbef5f83ccd1609af44233f9e08b2..6b2821215dc198adaf27661fad3931139db44403 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -154,7 +154,8 @@ typedef enum
 #define M_TREE_RARROW          7
 #define M_TREE_STAR            8
 #define M_TREE_HIDDEN          9
-#define M_TREE_MAX             10
+#define M_TREE_MISSING         10
+#define M_TREE_MAX             11
 
 #define M_THREAD_COLLAPSE      (1<<0)
 #define M_THREAD_UNCOLLAPSE    (1<<1)
@@ -329,6 +330,7 @@ enum
   OPTHEADER,
   OPTHELP,
   OPTHIDDENHOST,
+  OPTHIDEMISSING,
   OPTIGNORELISTREPLYTO,
 #ifdef USE_IMAP
   OPTIMAPLSUB,
@@ -385,6 +387,7 @@ enum
   OPTSUSPEND,
   OPTTEXTFLOWED,
   OPTTHOROUGHSRC,
+  OPTTHREADRECEIVED,
   OPTTILDE,
   OPTUNCOLLAPSEJUMP,
   OPTUSE8BITMIME,
@@ -617,8 +620,6 @@ typedef struct header
   unsigned int replied : 1;
   unsigned int subject_changed : 1; /* used for threading */
   unsigned int display_subject : 1; /* used for threading */
-  unsigned int fake_thread : 1;     /* no ref matched, but subject did */
-  unsigned int threaded : 1;        /* message has been threaded */
   unsigned int recip_valid : 1;  /* is_recipient is valid */
   unsigned int active : 1;         /* message is not to be removed */
   
@@ -652,13 +653,8 @@ typedef struct header
   BODY *content;       /* list of MIME parts */
   char *path;
   
-  /* the following are used for threading support */
-  struct header *parent;
-  struct header *child;  /* decendants of this message */
-  struct header *next;   /* next message in this thread */
-  struct header *prev;   /* previous message in thread */
-  struct header *last_sort; /* last message in subthread, for secondary SORT_LAST */
   char *tree;            /* character string to print thread tree */
+  struct thread *thread;
 
 #ifdef MIXMASTER
   LIST *chain;
@@ -673,6 +669,17 @@ typedef struct header
 #endif
 } HEADER;
 
+typedef struct thread
+{
+  unsigned int fake_thread : 1;
+  struct thread *parent;
+  struct thread *child;
+  struct thread *next;
+  struct thread *prev;
+  HEADER *message;
+  HEADER *sort_key;
+} THREAD;
+
 #include "mutt_regex.h"
 
 /* flag to mutt_pattern_comp() */
@@ -705,9 +712,10 @@ typedef struct
   char *pattern;                /* limit pattern string */
   pattern_t *limit_pattern;     /* compiled limit pattern */
   HEADER **hdrs;
-  HEADER *tree;                        /* top of thread tree */
+  THREAD *tree;                        /* top of thread tree */
   HASH *id_hash;               /* hash table by msg id */
   HASH *subj_hash;             /* hash table by subject */
+  HASH *thread_hash;           /* hash table for threading */
   int *v2r;                    /* mapping from virtual to real msgno */
   int hdrmax;                  /* number of pointers in hdrs */
   int msgcount;                        /* number of messages in the mailbox */
diff --git a/mx.c b/mx.c
index 16de23ed7a8f7c4d73d86d29f44a961b901b44ce..5d394fe18d40938cfc2139c044ae8af5a8ede890 100644 (file)
--- a/mx.c
+++ b/mx.c
@@ -671,10 +671,6 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
    */
   set_option (OPTFORCEREFRESH);
 
-  /* create hash tables */
-  ctx->id_hash = hash_create (1031);
-  ctx->subj_hash = hash_create (1031);
-
   if (!ctx->quiet)
     mutt_message (_("Reading %s..."), ctx->path);
 
@@ -754,6 +750,7 @@ void mx_fastclose_mailbox (CONTEXT *ctx)
     hash_destroy (&ctx->subj_hash, NULL);
   if (ctx->id_hash)
     hash_destroy (&ctx->id_hash, NULL);
+  mutt_clear_threads (ctx);
   for (i = 0; i < ctx->msgcount; i++)
     mutt_free_header (&ctx->hdrs[i]);
   safe_free ((void **) &ctx->hdrs);
@@ -1090,9 +1087,9 @@ void mx_update_tables(CONTEXT *ctx, int committing)
                      ctx->hdrs[i]->content->offset -
                      ctx->hdrs[i]->content->hdr_offset);
       /* remove message from the hash tables */
-      if (ctx->hdrs[i]->env->real_subj)
+      if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
        hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL);
-      if (ctx->hdrs[i]->env->message_id)
+      if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
        hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL);
       mutt_free_header (&ctx->hdrs[i]);
     }
@@ -1552,60 +1549,69 @@ void mx_alloc_memory (CONTEXT *ctx)
 /* this routine is called to update the counts in the context structure for
  * the last message header parsed.
  */
-void mx_update_context (CONTEXT *ctx)
+void mx_update_context (CONTEXT *ctx, int new_messages)
 {
-  HEADER *h = ctx->hdrs[ctx->msgcount];
+  HEADER *h;
+  int msgno;
 
+  for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++)
+  {
+    h = ctx->hdrs[msgno];
 
 
 
 #ifdef HAVE_PGP
-  /* NOTE: this _must_ be done before the check for mailcap! */
-  h->pgp = pgp_query (h->content);
+    /* NOTE: this _must_ be done before the check for mailcap! */
+    h->pgp = pgp_query (h->content);
 #endif /* HAVE_PGP */
 
-  if (!ctx->pattern)
-  {
-    ctx->v2r[ctx->vcount] = ctx->msgcount;
-    h->virtual = ctx->vcount++;
-  }
-  else
-    h->virtual = -1;
-  h->msgno = ctx->msgcount;
-  ctx->msgcount++;
-
-  if (h->env->supersedes)
-  {
-    HEADER *h2 = hash_find (ctx->id_hash, h->env->supersedes);
-
-    /* safe_free (&h->env->supersedes); should I ? */
-    if (h2)
+    if (!ctx->pattern)
     {
-      h2->superseded = 1;
-      if (option (OPTSCORE)) 
-       mutt_score_message (ctx, h2, 1);
+      ctx->v2r[ctx->vcount] = msgno;
+      h->virtual = ctx->vcount++;
     }
-  }
+    else
+      h->virtual = -1;
+    h->msgno = msgno;
 
-  /* add this message to the hash tables */
-  if (h->env->message_id)
-    hash_insert (ctx->id_hash, h->env->message_id, h, 0);
-  if (h->env->real_subj)
-    hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
+    if (h->env->supersedes)
+    {
+      HEADER *h2;
 
-  if (option (OPTSCORE)) 
-    mutt_score_message (ctx, h, 0);
-  
-  if (h->changed)
-    ctx->changed = 1;
-  if (h->flagged)
-    ctx->flagged++;
-  if (h->deleted)
-    ctx->deleted++;
-  if (!h->read)
-  {
-    ctx->unread++;
-    if (!h->old)
-      ctx->new++;
+      if (!ctx->id_hash)       
+       ctx->id_hash = mutt_make_id_hash (ctx);
+
+      h2 = hash_find (ctx->id_hash, h->env->supersedes);
+
+      /* safe_free (&h->env->supersedes); should I ? */
+      if (h2)
+      {
+       h2->superseded = 1;
+       if (option (OPTSCORE)) 
+         mutt_score_message (ctx, h2, 1);
+      }
+    }
+
+    /* add this message to the hash tables */
+    if (ctx->id_hash && h->env->message_id)
+      hash_insert (ctx->id_hash, h->env->message_id, h, 0);
+    if (ctx->subj_hash && h->env->real_subj)
+      hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
+
+    if (option (OPTSCORE)) 
+      mutt_score_message (ctx, h, 0);
+
+    if (h->changed)
+      ctx->changed = 1;
+    if (h->flagged)
+      ctx->flagged++;
+    if (h->deleted)
+      ctx->deleted++;
+    if (!h->read)
+    {
+      ctx->unread++;
+      if (!h->old)
+       ctx->new++;
+    }
   }
 }
diff --git a/mx.h b/mx.h
index f1548651c55a807b4d9886840f50210531552ab3..08c444d768fc14d4cb0fe8260cbfd20148e484be 100644 (file)
--- a/mx.h
+++ b/mx.h
@@ -77,7 +77,7 @@ int mbox_strict_cmp_headers (const HEADER *, const HEADER *);
 int mutt_reopen_mailbox (CONTEXT *, int *);
 
 void mx_alloc_memory (CONTEXT *);
-void mx_update_context (CONTEXT *);
+void mx_update_context (CONTEXT *, int);
 void mx_update_tables (CONTEXT *, int);
 
 
diff --git a/parse.c b/parse.c
index fd51f14f8be999baa743311b7e7e952faf67458c..4aa3173ec668a8f4d2b9333c2a8979e0da5c18e0 100644 (file)
--- a/parse.c
+++ b/parse.c
@@ -90,23 +90,65 @@ static char *read_rfc822_line (FILE *f, char *line, size_t *linelen)
   /* not reached */
 }
 
-static LIST *mutt_parse_references (char *s)
+static LIST *mutt_parse_references (char *s, int in_reply_to)
 {
   LIST *t, *lst = NULL;
+  int m, n = 0;
+  char *o = NULL, *new, *at;
 
-  while ((s = strtok (s, " \t")) != NULL)
+  while ((s = strtok (s, " \t;")) != NULL)
   {
     /*
      * some mail clients add other garbage besides message-ids, so do a quick
      * check to make sure this looks like a valid message-id
+     * some idiotic clients also break their message-ids between lines, deal
+     * with that too (give up if it's more than two lines, though)
      */
+    t = NULL;
+    new = NULL;
+
     if (*s == '<')
     {
-      t = (LIST *)safe_malloc (sizeof (LIST));
-      t->data = safe_strdup (s);
-      t->next = lst;
-      lst = t;
+      n = strlen (s);
+      if (s[n-1] != '>')
+      {
+       o = s;
+       s = NULL;
+       continue;
+      }
+
+      new = safe_strdup (s);
+    }
+    else if (o)
+    {
+      m = strlen (s);
+      if (s[m - 1] == '>')
+      {
+       new = safe_malloc (sizeof (char) * (n + m + 1));
+       strcpy (new, o);        /* __STRCPY_CHECKED__ */
+       strcpy (new + n, s);    /* __STRCPY_CHECKED__ */
+      }
+    }
+    if (new)
+    {
+      /* make sure that this really does look like a message-id.
+       * it should have exactly one @, and if we're looking at
+       * an in-reply-to header, make sure that the part before
+       * the @ has more than eight characters or it's probably
+       * an email address
+       */
+      if (!(at = strchr (new, '@')) || strchr (at + 1, '@')
+         || (in_reply_to && at - new <= 8))
+       safe_free ((void **) &new);
+      else
+      {
+       t = (LIST *) safe_malloc (sizeof (LIST));
+       t->data = new;
+       t->next = lst;
+       lst = t;
+      }
     }
+    o = NULL;
     s = NULL;
   }
 
@@ -1009,7 +1051,7 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short
     if (!ascii_strcasecmp (line+1, "n-reply-to"))
     {
       mutt_free_list (&e->in_reply_to);
-      e->in_reply_to = mutt_parse_references (p);
+      e->in_reply_to = mutt_parse_references (p, 1);
       matched = 1;
     }
     break;
@@ -1058,7 +1100,7 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short
     if (!ascii_strcasecmp (line + 1, "eferences"))
     {
       mutt_free_list (&e->references);
-      e->references = mutt_parse_references (p);
+      e->references = mutt_parse_references (p, 0);
       matched = 1;
     }
     else if (!ascii_strcasecmp (line + 1, "eply-to"))
diff --git a/pop.c b/pop.c
index 6d7289d824b515eee469229f68ecf48f4a404d53..a0ff55b702c81a140cb0512c3e31f1f189ae33f6 100644 (file)
--- a/pop.c
+++ b/pop.c
@@ -213,8 +213,11 @@ static int pop_fetch_headers (CONTEXT *ctx)
       if (ret < 0)
        break;
 
-      mx_update_context (ctx);
+      ctx->msgcount++;
     }
+
+    if (i > old_count)
+      mx_update_context (ctx, i - old_count);
   }
 
   if (ret < 0)
index b78e4c080742250a08593ddfec369e883f2f2880..5ed79e855a57b5d4fb33a7ed671e3e6be8cc8723 100644 (file)
@@ -291,6 +291,8 @@ int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, char *fcc, size
           the user attempted to reply to is in this mailbox */
        p = tmp->data + 18;
        SKIPWS (p);
+       if (!ctx->id_hash)
+         ctx->id_hash = mutt_make_id_hash (ctx);
        *cur = hash_find (ctx->id_hash, p);
       }
 
index 85c7c388bfd0b6ec0115df0eeb9db0220f508eb8..33eb48157000594062856851091e0199e7acd3dd 100644 (file)
--- a/protos.h
+++ b/protos.h
@@ -96,6 +96,9 @@ BODY *mutt_read_mime_header (FILE *, int);
 
 CONTENT *mutt_get_content_info (const char *fname, BODY *b);
 
+HASH *mutt_make_id_hash (CONTEXT *);
+HASH *mutt_make_subj_hash (CONTEXT *);
+
 LIST *mutt_make_references(ENVELOPE *e);
 
 ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short);
@@ -280,8 +283,7 @@ int mutt_is_list_recipient (int, ADDRESS *, ADDRESS *);
 int mutt_is_subscribed_list (ADDRESS *);
 int mutt_is_text_type (int, char *);
 int mutt_is_valid_mailbox (const char *);
-int mutt_messages_in_thread (HEADER *);
-int mutt_msgno_in_thread (HEADER *);
+int mutt_messages_in_thread (HEADER *, int);
 int mutt_multi_choice (char *prompt, char *letters);
 int mutt_needs_mailcap (BODY *);
 int mutt_num_postponed (int);
diff --git a/sort.c b/sort.c
index 9c5c0a451e0f6af7106c89eabdc94ebb0f2659ee..2cff933953435ae958a15a5223592408a69fb35d 100644 (file)
--- a/sort.c
+++ b/sort.c
@@ -178,6 +178,7 @@ void mutt_sort_headers (CONTEXT *ctx, int init)
 {
   int i;
   HEADER *h;
+  THREAD *thread, *top;
   sort_t *sortfunc;
   
   unset_option (OPTNEEDRESORT);
@@ -200,8 +201,8 @@ void mutt_sort_headers (CONTEXT *ctx, int init)
     mutt_message _("Sorting mailbox...");
 
   /* threads may be bogus, so clear the links */
-  if (init)
-    mutt_clear_threads (ctx);
+  /* XXX do this always, for the moment */
+  mutt_clear_threads (ctx);
 
   if (option (OPTNEEDRESCORE) && option (OPTSCORE))
   {
@@ -214,16 +215,18 @@ void mutt_sort_headers (CONTEXT *ctx, int init)
   if ((Sort & SORT_MASK) == SORT_THREADS)
   {
     AuxSort = NULL;
+#if 0 /*XXX*/
     /* if $sort_aux changed after the mailbox is sorted, then all the
        subthreads need to be resorted */
     if (option (OPTSORTSUBTHREADS))
     {
       i = Sort;
       Sort = SortAux;
-      ctx->tree = mutt_sort_subthreads (ctx->tree, mutt_get_sort_func (SortAux));
+      ctx->tree = mutt_sort_subthreads (ctx->tree);
       Sort = i;
       unset_option (OPTSORTSUBTHREADS);
     }
+#endif
     mutt_sort_threads (ctx, init);
   }
   else if ((sortfunc = mutt_get_sort_func (Sort)) == NULL ||
@@ -253,12 +256,16 @@ void mutt_sort_headers (CONTEXT *ctx, int init)
   /* re-collapse threads marked as collapsed */
   if ((Sort & SORT_MASK) == SORT_THREADS)
   {
-    h = ctx->tree;
-    while (h)
+    top = ctx->tree;
+    while ((thread = top) != NULL)
     {
+      while (!thread->message)
+       thread = thread->child;
+      h = thread->message;
+
       if (h->collapsed)
        mutt_collapse_thread (ctx, h);
-      h = h->next;
+      top = top->next;
     }
     mutt_set_virtual (ctx);
   }
diff --git a/sort.h b/sort.h
index da32278ea86d4ae779a22cb65eeb8b75be7388b8..f6f7466e54427635d60e16e4d0849c7e611af757 100644 (file)
--- a/sort.h
+++ b/sort.h
@@ -40,7 +40,7 @@ void mutt_clear_threads (CONTEXT *);
 void mutt_sort_headers (CONTEXT *, int);
 void mutt_sort_threads (CONTEXT *, int);
 int mutt_select_sort (int);
-HEADER *mutt_sort_subthreads (HEADER *, sort_t *);
+THREAD *mutt_sort_subthreads (THREAD *);
 
 WHERE short BrowserSort INITVAL (SORT_SUBJECT);
 WHERE short Sort INITVAL (SORT_DATE);
index a729baebfd8889f24dda1edef04b269ea9daf4c4..9a9aca34fd57a6e690a52860f957646a7088cf10 100644 (file)
--- a/thread.c
+++ b/thread.c
 #include <string.h>
 #include <ctype.h>
 
-/* returns 1 if `a' is a descendant (child) of thread `b' */
-static int is_descendant (HEADER *a, HEADER *b)
-{
-  /* find the top parent of the thread */
-  while (a->parent)
-    a = a->parent;
-  return (a == b);
-}
-
-/* This function makes use of the fact that Mutt stores message references in
- * reverse order (i.e., last to first).  This is optiminal since we would like
- * to find the most recent message to which "cur" refers itself.  
- */
+#define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited)))
 
-static HEADER *find_ref (LIST *refs, HEADER *cur, CONTEXT *ctx)
+/* determine whether a is a descendant of b */
+static int is_descendant (THREAD *a, THREAD *b)
 {
-  HEADER *ptr;
-
-  for (; refs; refs = refs->next)
+  while (a)
   {
-    /* ups, this message is in a reference loop. bad. */
-    if (cur->env->message_id && !strcmp (cur->env->message_id, refs->data))
-      continue;
-
-    if ((ptr = hash_find (ctx->id_hash, refs->data)))
-    {
-      if (is_descendant (ptr, cur))
-       continue;
-
-      return ptr;
-    }
+    if (a == b)
+      return (1);
+    a = a->parent;
   }
-  
-  return NULL;
-}
-
-/*
- * In-Reply-To contains the direct parents according to RFC 2822.
- * References is second best, since it may contain indirect parents,
- * too.
- */
-
-static HEADER *find_reference (HEADER *cur, CONTEXT *ctx)
-{
-  HEADER *ptr;
-  
-  if ((ptr = find_ref (cur->env->in_reply_to, cur, ctx)))
-    return ptr;
-  if ((ptr = find_ref (cur->env->references, cur, ctx)))
-    return ptr;
-  
-  return NULL;
+  return (0);
 }
 
 /* Determines whether to display a message's subject. */
-static int need_display_subject (CONTEXT *ctx, HEADER *tree)
+static int need_display_subject (CONTEXT *ctx, HEADER *hdr)
 {
-  HEADER *tmp;
+  THREAD *tmp, *tree = hdr->thread;
 
   /* if our subject is different from our parent's, display it */
-  if (tree->subject_changed)
+  if (hdr->subject_changed)
     return (1);
 
   /* if our subject is different from that of our closest previously displayed
    * sibling, display the subject */
   for (tmp = tree->prev; tmp; tmp = tmp->prev)
   {
-    if (tmp->virtual >= 0 || (tmp->collapsed && (!ctx->pattern || tmp->limited)))
+    hdr = tmp->message;
+    if (hdr && VISIBLE (hdr, ctx))
     {
-      if (tmp->subject_changed)
+      if (hdr->subject_changed)
        return (1);
       else
        break;
@@ -102,10 +63,14 @@ static int need_display_subject (CONTEXT *ctx, HEADER *tree)
    * closest displayed ancestor, display the subject */
   for (tmp = tree->parent; tmp; tmp = tmp->parent)
   {
-    if (tmp->virtual >= 0 || (tmp->collapsed && (!ctx->pattern || tmp->limited)))
-      return (0);
-    else if (tmp->subject_changed)
-      return (1);
+    hdr = tmp->message;
+    if (hdr)
+    {
+      if (VISIBLE (hdr, ctx))
+       return (0);
+      else if (hdr->subject_changed)
+       return (1);
+    }
   }
   
   /* if we have no visible parent or previous sibling, display the subject */
@@ -116,16 +81,18 @@ static int need_display_subject (CONTEXT *ctx, HEADER *tree)
  * sibling is displayed.  
  */
 
-static int is_next_displayed (CONTEXT *ctx, HEADER *tree)
+static int is_next_displayed (CONTEXT *ctx, THREAD *tree)
 {
   int depth = 0;
+  HEADER *hdr;
 
   if ((tree = tree->next) == NULL)
     return (0);
 
   FOREVER
   {
-    if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited)))
+    hdr = tree->message;
+    if (hdr && VISIBLE (hdr, ctx))
       return (1);
 
     if (tree->child)
@@ -162,19 +129,23 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
   char corner = Sort & SORT_REVERSE ? M_TREE_ULCORNER : M_TREE_LLCORNER;
   int depth = 0, start_depth = 0, max_depth = 0, max_width = 0;
   int nextdisp = 0, visible;
-  HEADER *tree = ctx->tree;
+  THREAD *tree = ctx->tree;
   HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
-
-  /* A NULL tree should never be passed here, but may occur if there is
-   * a cycle.  
-   */
-  if (!tree)
-    return;
+  HEADER *hdr;
 
   FOREVER
   {
-    if ((visible = (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited)))))
-      tree->display_subject = need_display_subject (ctx, tree);
+    hdr = tree->message;
+
+    if (hdr)
+    {
+      if ((visible = VISIBLE (hdr, ctx)) !=  0)
+       hdr->display_subject = need_display_subject (ctx, hdr);
+
+      safe_free ((void **) &hdr->tree);
+    }
+    else
+      visible = 0;
 
     if (depth >= max_depth)
       safe_realloc ((void **) &pfx,
@@ -184,8 +155,6 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
       safe_realloc ((void **) &arrow,
                    (max_width += 16) * 2 * sizeof (char));
 
-    safe_free ((void **) &tree->tree);
-
     if (depth)
     {
       myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * 2;
@@ -194,8 +163,8 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
       if (depth && start_depth == depth)
        myarrow[0] = nextdisp ? M_TREE_LTEE : corner;
       else
-       myarrow[0] = M_TREE_HIDDEN;
-      myarrow[1] = tree->fake_thread ? M_TREE_STAR : M_TREE_HLINE;
+       myarrow[0] = tree->parent->message ? M_TREE_HIDDEN : M_TREE_MISSING;
+      myarrow[1] = (tree->fake_thread) ? M_TREE_STAR : M_TREE_HLINE;
       if (visible)
       {
        myarrow[2] = M_TREE_RARROW;
@@ -204,21 +173,21 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
 
       if (visible)
       {
-       tree->tree = safe_malloc ((2 + depth * 2) * sizeof (char));
+       hdr->tree = safe_malloc ((2 + depth * 2) * sizeof (char));
        if (start_depth > 1)
        {
-         strncpy (tree->tree, pfx, (start_depth - 1) * 2);
-         strfcpy (tree->tree + (start_depth - 1) * 2,
+         strncpy (hdr->tree, pfx, (start_depth - 1) * 2);
+         strfcpy (hdr->tree + (start_depth - 1) * 2,
                   arrow, (2 + depth - start_depth) * 2);
        }
        else
-         strfcpy (tree->tree, arrow, 2 + depth * 2);
+         strfcpy (hdr->tree, arrow, 2 + depth * 2);
       }
     }
 
-    if (linearize)
+    if (linearize && hdr)
     {
-      *array = tree;
+      *array = hdr;
       array += Sort & SORT_REVERSE ? -1 : 1;
     }
 
@@ -230,25 +199,33 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
        mypfx[0] = nextdisp ? M_TREE_VLINE : M_TREE_SPACE;
        mypfx[1] = M_TREE_SPACE;
       }
-      depth++;
+      if (depth || !option (OPTHIDEMISSING)
+         || tree->message || tree->child->next)
+       depth++;
       if (visible)
         start_depth = depth;
       tree = tree->child;
+      hdr = tree->message;
     }
     else
     {
       while (!tree->next && tree->parent)
       {
-       if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited)))
+       if (hdr && VISIBLE (hdr, ctx))
          start_depth = depth;
        tree = tree->parent;
-       if (start_depth == depth)
-         start_depth--;
-       depth--;
+       hdr = tree->message;
+       if (depth)
+       {
+         if (start_depth == depth)
+           start_depth--;
+         depth--;
+       }
       }
-      if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited)))
+      if (hdr && VISIBLE (hdr, ctx))
        start_depth = depth;
-      if ((tree = tree->next) == NULL)
+      tree = tree->next;
+      if (!tree)
        break;
     }
   }
@@ -261,9 +238,12 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize)
  * assumes that `tree' is the first element in the list, and not some
  * element in the middle of the list.
  */
-static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc)
+
+static void insert_message (THREAD **tree, THREAD *msg)
 {
-  HEADER *tmp;
+#if 0 /* XXX */
+  THREAD *tmp;
+#endif
 
   /* NOTE: we do NOT clear the `msg->child' link here because when we do
    * the pseudo-threading, we want to preserve any sub-threads.  So we clear
@@ -279,13 +259,16 @@ static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc)
   }
 
   /* check to see if this message belongs at the beginning of the list */
+#if 0 /* XXX */
   if (!sortFunc || sortFunc ((void *) &msg, (void *) tree) < 0)
   {
+#endif
     (*tree)->prev = msg;
     msg->next = *tree;
     msg->prev = NULL;
     *tree = msg;
     return;
+#if 0 /* XXX */
   }
   
   /* search for the correct spot in the list to insert */
@@ -303,47 +286,114 @@ static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc)
   tmp->next = msg;
   msg->prev = tmp;
   msg->next = NULL;
+#endif
 }
 
+static LIST *make_subject_list (THREAD *cur, time_t *dateptr)
+{
+  THREAD *start = cur;
+  ENVELOPE *env;
+  time_t thisdate;
+  LIST *curlist, *oldlist, *newlist, *subjects = NULL;
+  int rc = 0;
+  
+  FOREVER
+  {
+    while (!cur->message)
+      cur = cur->child;
+
+    if (dateptr)
+    {
+      thisdate = option (OPTTHREADRECEIVED)
+       ? cur->message->received : cur->message->date_sent;
+      if (!*dateptr || thisdate < *dateptr)
+       *dateptr = thisdate;
+    }
+
+    env = cur->message->env;
+    if (env->real_subj &&
+       ((env->real_subj != env->subject) || (!option (OPTSORTRE))))
+    {
+      for (curlist = subjects, oldlist = NULL;
+          curlist; oldlist = curlist, curlist = curlist->next)
+      {
+       rc = mutt_strcmp (env->real_subj, curlist->data);
+       if (rc >= 0)
+         break;
+      }
+      if (!curlist || rc > 0)
+      {
+       newlist = safe_calloc (1, sizeof (LIST));
+       newlist->data = env->real_subj;
+       if (oldlist)
+       {
+         newlist->next = oldlist->next;
+         oldlist->next = newlist;
+       }
+       else
+       {
+         newlist->next = subjects;
+         subjects = newlist;
+       }
+      }
+    }
+
+    while (!cur->next && cur != start)
+    {
+      cur = cur->parent;
+    }
+    if (cur == start)
+      break;
+    cur = cur->next;
+  }
+
+  return (subjects);
+}
 
 /* find the best possible match for a parent mesage based upon subject.
  * if there are multiple matches, the one which was sent the latest, but
  * before the current message, is used. 
  */
-
-static HEADER *find_subject (CONTEXT *ctx, HEADER *cur)
+static THREAD *find_subject (CONTEXT *ctx, THREAD *cur)
 {
   struct hash_elem *ptr;
-  HEADER *tmp, *last = NULL;
-  ENVELOPE *env = cur->env;
+  THREAD *tmp, *last = NULL;
   int hash;
+  LIST *subjects = NULL, *oldlist;
+  time_t date = 0;  
 
-  if (env->real_subj &&
-      ((env->real_subj != env->subject) || (!option (OPTSORTRE))))
+  subjects = make_subject_list (cur, &date);
+
+  while (subjects)
   {
-    hash = hash_string ((unsigned char *) env->real_subj, ctx->subj_hash->nelem);
+    hash = hash_string ((unsigned char *) subjects->data, ctx->subj_hash->nelem);
     for (ptr = ctx->subj_hash->table[hash]; ptr; ptr = ptr->next)
     {
-      tmp = ptr->data;
+      tmp = ((HEADER *) ptr->data)->thread;
       if (tmp != cur &&                        /* don't match the same message */
          !tmp->fake_thread &&          /* don't match pseudo threads */
-         tmp->subject_changed &&       /* only match interesting replies */
+         tmp->message->subject_changed &&      /* only match interesting replies */
          !is_descendant (tmp, cur) &&  /* don't match in the same thread */
-         ( ( (SORT_MASK & SortAux) == SORT_RECEIVED) ?
-               cur->received >= tmp->received :
-               cur->date_sent >= tmp->date_sent) &&
-         (!last || (last->date_sent <= tmp->date_sent)) &&
-         tmp->env->real_subj &&
-         mutt_strcmp (env->real_subj, tmp->env->real_subj) == 0)
-      {
+         (date >= (option (OPTTHREADRECEIVED) ?
+                   tmp->message->received :
+                   tmp->message->date_sent)) &&
+         (!last ||
+          (option (OPTTHREADRECEIVED) ?
+           (last->message->received < tmp->message->received) :
+           (last->message->date_sent < tmp->message->date_sent))) &&
+         tmp->message->env->real_subj &&
+         mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
        last = tmp; /* best match so far */
-      }
     }
+
+    oldlist = subjects;
+    subjects = subjects->next;
+    safe_free ((void **) &oldlist);
   }
-  return last;
+  return (last);
 }
 
-static void unlink_message (HEADER **top, HEADER *cur)
+static void unlink_message (THREAD **top, THREAD *cur)
 {
   if (cur->prev)
   {
@@ -360,225 +410,208 @@ static void unlink_message (HEADER **top, HEADER *cur)
   }
 }
 
-static void pseudo_threads (CONTEXT *ctx, sort_t *sortFunc)
+static void pseudo_threads (CONTEXT *ctx)
 {
-  HEADER *tree = ctx->tree;
-  HEADER *top = tree, *cur, *tmp, *curchild, *nextchild;
+  THREAD *tree = ctx->tree, *top = tree;
+  THREAD *tmp, *cur, *parent, *curchild, *nextchild;
+
+  if (!ctx->subj_hash)
+    ctx->subj_hash = mutt_make_subj_hash (ctx);
 
   while (tree)
   {
     cur = tree;
     tree = tree->next;
-    if ((tmp = find_subject (ctx, cur)) != NULL)
+    if ((parent = find_subject (ctx, cur)) != NULL)
     {
-      /* detach this message from it's current location */
+      /* detach this message from its current location */
       unlink_message (&top, cur);
 
-      cur->subject_changed = 0;
       cur->fake_thread = 1;
-      cur->parent = tmp;
-      insert_message (&tmp->child, cur, sortFunc);
+      cur->parent = parent;
+      insert_message (&parent->child, cur);
       
-      /* if the message we're attaching has pseudo-children, they
-        need to be attached to its parent, so move them up a level.  */
-      for (curchild = cur->child; curchild; )
-      {
-       nextchild = curchild->next;
-       if (curchild->fake_thread)
-       {
-         /* detach this message from its current location */
-         unlink_message (&cur->child, curchild);
-         curchild->parent = tmp;
-         insert_message (&tmp->child, curchild, sortFunc);
-       }
-       curchild = nextchild;
-      }
-    }
-  }
-  ctx->tree = top;
-}
+      tmp = cur;
 
-static HEADER *sort_last (HEADER *top)
-{
-  HEADER *tree;
-  HEADER *tmp;
-  HEADER *first;
-  HEADER *last;
-  HEADER *nextsearch;
-  sort_t *usefunc;
-  
-  usefunc = mutt_get_sort_func (Sort);
-  
-  tree = top;
-  FOREVER
-  {
-    if (tree->child)
-      tree = tree->child;
-    else
-    {
-      while (!tree->next)
+      FOREVER
       {
-       first = last = tree;
-       nextsearch = tree->prev;
-       first->prev = NULL;
-       last->next = NULL;
-       while ((tree = nextsearch) != NULL)
+       while (!tmp->message)
+         tmp = tmp->child;
+
+       if (tmp == cur
+           || !mutt_strcmp (tmp->message->env->real_subj,
+                            parent->message->env->real_subj))
        {
-          tmp = last;
-          nextsearch = nextsearch->prev;
-         while (tmp && (*usefunc) ((void *) &tree->last_sort, 
-                                   (void *) &tmp->last_sort) < 0)
-           tmp = tmp->prev;
-         if (tmp)
-         {
-           if ((tree->next = tmp->next) != NULL)
-             tmp->next->prev = tree;
-           else
-             last = tree;
-           tmp->next = tree;
-           tree->prev = tmp;
-         }
-         else
+         tmp->message->subject_changed = 0;
+
+         /* if the message we're attaching has pseudo-children, they
+          * need to be attached to its parent, so move them up a level.  */
+         for (curchild = tmp->child; curchild; )
          {
-           tree->next = first;
-           first->prev = tree;
-           first = tree;
-           tree->prev = NULL;
+           nextchild = curchild->next;
+           if (curchild->fake_thread)
+           {
+             /* detach this message from its current location */
+             unlink_message (&tmp->child, curchild);
+             curchild->parent = parent;
+             /* we care in this case that insert_message inserts the
+              * message at the beginning of the list! */
+             insert_message (&parent->child, curchild);
+           }
+           curchild = nextchild;
          }
        }
-       if (first->parent)
+
+       while (!tmp->next && tmp != cur)
        {
-         first->parent->child = first;
-         tree = first->parent;
-         if (Sort & SORT_REVERSE)
-         {
-           if ((*usefunc) ((void *) &tree->last_sort,
-                           (void *) &first->last_sort) > 0)
-             tree->last_sort = first->last_sort;
-         }
-         else
-         {
-           if ((*usefunc) ((void *) &tree->last_sort,
-                           (void *) &last->last_sort) < 0)
-             tree->last_sort = last->last_sort;
-         }
+         tmp = tmp->parent;
        }
-       else
-       {
-         top = first;
-         tree = last;
+       if (tmp == cur)
          break;
-       }
+       tmp = tmp->next;
       }
-      if ((tree = tree->next) == NULL)
-       break;
     }
   }
-  return top;
+  ctx->tree = top;
 }
 
-static int matches_in_reply_to (HEADER *t, const char *p)
-{
-  LIST *irt;
-  
-  for (irt = t->env->in_reply_to; irt; irt = irt->next)
-    if (mutt_strcmp (irt->data, p) == 0)
-      return 1;
-  
-  return 0;
-}
 
-static void move_descendants (HEADER **tree, HEADER *cur, sort_t *usefunc)
+void mutt_clear_threads (CONTEXT *ctx)
 {
-  HEADER *ptr, *tmp = *tree;
+  int i;
 
-  while (tmp)
+  for (i = 0; i < ctx->msgcount; i++)
   {
-    /* Look at the entire in-reply-to header, and at the last (!) reference. */
-    if (matches_in_reply_to (tmp, cur->env->message_id) ||
-       (tmp->env->references && mutt_strcmp (tmp->env->references->data, cur->env->message_id) == 0))
-    {
-      /* remove message from current location */
-      unlink_message (tree, tmp);
-
-      tmp->parent = cur;
-      if (cur->env->real_subj && tmp->env->real_subj)
-       tmp->subject_changed = mutt_strcmp (tmp->env->real_subj, cur->env->real_subj) ? 1 : 0;
-      else
-       tmp->subject_changed = (cur->env->real_subj || tmp->env->real_subj) ? 1 : 0;
-      tmp->fake_thread = 0; /* real reference */
-
-      ptr = tmp;
-      tmp = tmp->next;
-      insert_message (&cur->child, ptr, usefunc);
-    }
-    else
-      tmp = tmp->next;
+    ctx->hdrs[i]->thread = NULL;
   }
+  ctx->tree = NULL;
+
+  if (ctx->thread_hash)
+    hash_destroy (&ctx->thread_hash, *free);
 }
 
-void mutt_clear_threads (CONTEXT *ctx)
+int compare_threads (const void *a, const void *b)
 {
-  int i;
+  static sort_t *sort_func = NULL;
 
-  for (i = 0; i < ctx->msgcount; i++)
+  if (a || b)
+    return ((*sort_func) (&(*((THREAD **) a))->sort_key,
+                         &(*((THREAD **) b))->sort_key));
+  /* a hack to let us reset sort_func even though we can't
+   * have extra arguments because of qsort
+   */
+  else
   {
-    ctx->hdrs[i]->parent = NULL;
-    ctx->hdrs[i]->next = NULL;
-    ctx->hdrs[i]->prev = NULL;
-    ctx->hdrs[i]->child = NULL;
-    ctx->hdrs[i]->threaded = 0;
-    ctx->hdrs[i]->fake_thread = 0;
+    sort_func = NULL;
+    sort_func = mutt_get_sort_func (Sort);
+    return (sort_func ? 1 : 0);
   }
-  ctx->tree = NULL;
 }
 
-HEADER *mutt_sort_subthreads (HEADER *hdr, sort_t *func)
+THREAD *mutt_sort_subthreads (THREAD *thread)
 {
-  static HEADER **array = NULL;
-  static unsigned array_size = 0, array_alloc = 0;
-  unsigned array_alloc_base;
-  unsigned items, item;
+  THREAD **array, *sort_key;
+  int i, array_size;
   
-  if (!func || !hdr)
-    return hdr;
+  Sort ^= SORT_REVERSE;
+  if (!thread || !compare_threads (NULL, NULL))
+    return thread;
+
+  /* we put things into the array backwards to save some cycles,
+   * but we want to have to move less stuff around if we're 
+   * resorting, so we sort backwards and then put them back
+   * in reverse order so they're forwards
+   */
 
-  array_alloc_base = array_alloc;
-  for (item = array_alloc_base; hdr; hdr = hdr->next)
+  array = safe_malloc ((array_size = 256) * sizeof (THREAD *));
+  while (1)
   {
-    if (item >= array_size)
+    while (thread->child)
     {
-      array_size = 2 * MAX(array_size, 0x100);
-      safe_realloc ((void **) &array, array_size * sizeof (*array));
+      thread->sort_key = thread->message;
+      thread = thread->child;
     }
-    array[item++] = hdr;
-    if (hdr->child)
+
+    while (thread->next && !thread->child)
     {
-      array_alloc = item;
-      hdr->child = mutt_sort_subthreads (hdr->child, func);
+      thread->sort_key = thread->message;
+      thread = thread->next;
     }
-  }
-  array_alloc = array_alloc_base;
 
-  items = item - array_alloc_base;
-  qsort ((void *) (array + array_alloc_base), items, sizeof (*array), func);
+    if (thread->child)
+      continue;
 
-  array[array_alloc_base          ]->prev = NULL;
-  array[array_alloc_base + items-1]->next = NULL;
+    thread->sort_key = thread->message;
 
-  for (item = array_alloc_base; item < array_alloc_base + items-1; item++)
-  {
-    array[item  ]->next = array[item+1];
-    array[item+1]->prev = array[item  ];
-  }
+    while (!thread->next)
+    {
+      if (thread->prev)
+      {
+       for (i = 0; thread; i++, thread = thread->prev)
+       {
+         if (i >= array_size)
+           safe_realloc ((void **) &array, (array_size *= 2) * sizeof (THREAD *));
+
+         array[i] = thread;
+       }
+
+       qsort ((void *) array, i, sizeof (THREAD *), *compare_threads);
+
+       array[0]->next = NULL;
+
+       thread = array[i - 1];
+       thread->prev = NULL;
 
-  return array[array_alloc_base];
+       if (thread->parent)
+       {
+         thread->parent->child = thread;
+         sort_key = array[(!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? i - 1 : 0];
+       }
+       
+       while (--i)
+       {
+         array[i - 1]->prev = array[i];
+         array[i]->next = array[i - 1];
+       }
+      }
+      else
+       sort_key = thread;
+
+
+      if (thread->parent)
+      {
+       if (Sort & SORT_LAST)
+       {
+         if (!thread->parent->sort_key
+             || ((((Sort & SORT_REVERSE) ? 1 : -1)
+                  * compare_threads ((void *) &thread->parent,
+                                     (void *) &sort_key))
+                 > 0))
+           thread->parent->sort_key = sort_key->sort_key;
+       }
+       else if (!thread->parent->sort_key)
+         thread->parent->sort_key = sort_key->sort_key;
+
+       thread = thread->parent;
+      }
+      else
+      {
+       Sort ^= SORT_REVERSE;
+       return (thread);
+      }
+    }
+
+    thread = thread->next;
+  }
 }
 
 void mutt_sort_threads (CONTEXT *ctx, int init)
 {
-  HEADER *tmp, *CUR;
-  int i, oldsort;
+  HEADER *cur;
+  int i, oldsort, using_refs = 0;
+  HASH *id_hash;
+  THREAD *thread, *new, *tmp;
+  LIST *ref = NULL;
   
   /* set Sort to the secondary method to support the set sort_aux=reverse-*
    * settings.  The sorting functions just look at the value of
@@ -587,83 +620,155 @@ void mutt_sort_threads (CONTEXT *ctx, int init)
   oldsort = Sort;
   Sort = SortAux;
   
+  id_hash = hash_create (ctx->msgcount * 2);
+  ctx->tree = safe_calloc (1, sizeof (THREAD));
   for (i = 0; i < ctx->msgcount; i++)
   {
-    CUR = ctx->hdrs[i];
+    cur = ctx->hdrs[i];
+    thread = safe_calloc (1, sizeof (THREAD));
+    thread->message = cur;
+    cur->thread = thread;
+    hash_insert (id_hash, cur->env->message_id ? cur->env->message_id : "", thread, 1);
+  }
 
-    if (CUR->fake_thread)
-    {
-      /* Move pseudo threads back to the top level thread so that they can
-       * can be moved later if they are descendants of messages that were
-       * just delivered.
-       */
-      CUR->fake_thread = 0;
-      CUR->subject_changed = 1;
-      unlink_message (&CUR->parent->child, CUR);
-      CUR->parent = NULL;
-      insert_message (&ctx->tree, CUR, NULL);
-    }
-    else if (!CUR->threaded)
+  for (i = 0; i < ctx->msgcount; i++)
+  {
+    cur = ctx->hdrs[i];
+    thread = cur->thread;
+    using_refs = 0;
+
+    while (1)
     {
-      if ((tmp = find_reference (CUR, ctx)) != NULL)
+      if (using_refs == 0)
       {
-       CUR->parent = tmp;
-       if (CUR->env->real_subj && tmp->env->real_subj)
-         CUR->subject_changed = mutt_strcmp (tmp->env->real_subj, CUR->env->real_subj) ? 1 : 0;
+       /* look at the beginning of in-reply-to: */
+       if ((ref = cur->env->in_reply_to) != NULL)
+       {
+         using_refs = 1;
+       }
        else
-         CUR->subject_changed = (CUR->env->real_subj || tmp->env->real_subj) ? 1 : 0;
+       {
+         ref = cur->env->references;
+         using_refs = 2;
+       }
+      }
+      else if (using_refs == 1)
+      {
+       /* if there's no references header, use all the in-reply-to:
+        * data that we have.  otherwise, use the first reference
+        * if it's different than the first in-reply-to, otherwise use
+        * the second reference (since at least eudora puts the most
+        * recent reference in in-reply-to and the rest in references
+        */
+       if (!cur->env->references)
+         ref = ref->next;
+       else
+       {
+         if (mutt_strcmp (ref->data, cur->env->references->data))
+           ref = cur->env->references;
+         else
+           ref = cur->env->references->next;
+         
+         using_refs = 2;
+       }
       }
       else
-       CUR->subject_changed = 1;
+       ref = ref->next;
+      
+      if (!ref)
+       break;
 
-      if (!init)
+      if ((new = hash_find (id_hash, ref->data)) == NULL)
       {
-       /* Search the children of `tmp' for decendants of `cur'.  This is only
-        * done when the mailbox has already been threaded since we don't have
-        * to worry about the tree being threaded wrong (because of a missing
-        * parent) during the initial threading.
+       new = safe_calloc (1, sizeof (THREAD));
+       hash_insert (id_hash, ref->data, new, 1);
+      }
+      else if (is_descendant (new, thread)) /* no loops! */
+       break;
+
+      /* make new the parent of thread */
+      if (thread->parent)
+      {
+       /* this can only happen if thread is attached directly to ctx->tree.
+        * detach it.
         */
-       if (CUR->env->message_id)
-         move_descendants (tmp ? &tmp->child : &ctx->tree, CUR, NULL);
+       if (thread->prev)
+         thread->prev->next = thread->next;
+       if (thread->next)
+         thread->next->prev = thread->prev;
+       if (thread->parent->child == thread)
+         thread->parent->child = thread->next;
       }
-      insert_message (tmp ? &tmp->child : &ctx->tree, CUR, NULL);
-      CUR->threaded = 1;
-    }
-  }
 
-  if (!option (OPTSTRICTTHREADS))
-    pseudo_threads (ctx, NULL);
+      thread->parent = new;
+      thread->prev = NULL;
+      if ((thread->next = new->child) != NULL)
+       thread->next->prev = thread;
+      new->child = thread;
+
+      thread = new;
+
+      if (thread->message || (thread->parent && thread->parent != ctx->tree))
+       break;
+    }
 
-  /* if the SORT_LAST bit is not set, sort the whole tree now */
-  if (!(Sort & SORT_LAST))
-    ctx->tree = mutt_sort_subthreads (ctx->tree, mutt_get_sort_func (Sort));
+    if (!thread->parent)
+    {
+      if ((thread->next = ctx->tree->child) != NULL)
+      {
+       thread->next->prev = thread;
+      }
+      ctx->tree->child = thread;
+      thread->parent = ctx->tree;
+    }
+  }
 
-  /* now that the whole tree is put together, we can sort by last-* */
-  if (Sort & SORT_LAST)
+  for (thread = ctx->tree->child; thread; thread = thread->next)
   {
-    for (i = 0; i < ctx->msgcount; i++)
-      ctx->hdrs[i]->last_sort = ctx->hdrs[i];
-    ctx->tree = sort_last (ctx->tree);
+    thread->parent = NULL;
   }
   
+  tmp = ctx->tree;
+  ctx->tree = ctx->tree->child;
+  safe_free ((void **) &tmp);
+
+  for (i = 0; i < ctx->msgcount; i++)
+  {
+    cur = ctx->hdrs[i];
+    tmp = cur->thread->parent;
+    while (tmp && !tmp->message)
+    {
+      tmp = tmp->parent;
+    }
+
+    if (!tmp)
+      cur->subject_changed = 1;
+    else if (cur->env->real_subj && tmp->message->env->real_subj)
+      cur->subject_changed = mutt_strcmp (cur->env->real_subj, tmp->message->env->real_subj) ? 1 : 0;
+    else
+      cur->subject_changed = (cur->env->real_subj || tmp->message->env->real_subj) ? 1 : 0;
+  }
+
+  if (!option (OPTSTRICTTHREADS))
+    pseudo_threads (ctx);
+
+  ctx->tree = mutt_sort_subthreads (ctx->tree);
+
   /* restore the oldsort order. */
   Sort = oldsort;
 
-  /* Put the list into an array.  If we are reverse sorting, give the
-   * offset of the last message, and work backwards (tested for and
-   * done inside the function), so that the threads go backwards. 
-   * This, of course, means the auxillary sort has to go forwards
-   * because we map it backwards here.
-   */
+  /* Put the list into an array. */
   mutt_linearize_tree (ctx, 1);
+
+  ctx->thread_hash = id_hash;
 }
 
-static HEADER *find_virtual (HEADER *cur)
+static HEADER *find_virtual (THREAD *cur)
 {
-  HEADER *top;
+  THREAD *top;
 
-  if (cur->virtual >= 0)
-      return (cur);
+  if (cur->message && cur->message->virtual >= 0)
+    return (cur->message);
 
   top = cur;
   if ((cur = cur->child) == NULL)
@@ -671,8 +776,8 @@ static HEADER *find_virtual (HEADER *cur)
 
   FOREVER
   {
-    if (cur->virtual >= 0)
-      return (cur);
+    if (cur->message && cur->message->virtual >= 0)
+      return (cur->message);
 
     if (cur->child)
       cur = cur->child;
@@ -694,6 +799,7 @@ static HEADER *find_virtual (HEADER *cur)
 
 int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
 {
+  THREAD *cur;
   HEADER *tmp;
 
   if ((Sort & SORT_MASK) != SORT_THREADS)
@@ -702,22 +808,24 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
     return (hdr->virtual);
   }
 
+  cur = hdr->thread;
+
   if (!subthreads)
   {
-    while (hdr->parent)
-      hdr = hdr->parent;
+    while (cur->parent)
+      cur = cur->parent;
   }
   else
   {
     if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
     {
-      while (!hdr->next && hdr->parent)
-       hdr = hdr->parent;
+      while (!cur->next && cur->parent)
+       cur = cur->parent;
     }
     else
     {
-      while (!hdr->prev && hdr->parent)
-       hdr = hdr->parent;
+      while (!cur->prev && cur->parent)
+       cur = cur->parent;
     }
   }
 
@@ -725,20 +833,20 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
   {
     do
     { 
-       hdr = hdr->next;
-       if (!hdr)
-         return (-1);
-       tmp = find_virtual (hdr);
+      cur = cur->next;
+      if (!cur)
+       return (-1);
+      tmp = find_virtual (cur);
     } while (!tmp);
   }
   else
   {
     do
     { 
-       hdr = hdr->prev;
-       if (!hdr)
-         return (-1);
-       tmp = find_virtual (hdr);
+      cur = cur->prev;
+      if (!cur)
+       return (-1);
+      tmp = find_virtual (cur);
     } while (!tmp);
   }
 
@@ -747,54 +855,52 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
 
 int mutt_parent_message (CONTEXT *ctx, HEADER *hdr)
 {
+  THREAD *thread;
+
   if ((Sort & SORT_MASK) != SORT_THREADS)
   {
     mutt_error _("Threading is not enabled.");
     return (hdr->virtual);
   }
-  if (hdr->parent)
-  {
-    while ((hdr = hdr->parent))
-    {
-      if (hdr->virtual >= 0 && hdr->collapsed && (!ctx->pattern || hdr->limited))
-       return (hdr->virtual);
-    }
-    mutt_error _("Parent message is not visible in limited view");
-    return -1;
-  }
-  else
+
+  thread = hdr->thread;
+  while ((thread = thread->parent))
   {
-    mutt_error _("Parent message is not available.");
-    return -1;
+    hdr = thread->message;
+    if (hdr && VISIBLE (hdr, ctx))
+      return (hdr->virtual);
   }
+  
+  mutt_error _("Parent message is not available.");
+  return (-1);
 }
 
 void mutt_set_virtual (CONTEXT *ctx)
 {
   int i;
+  HEADER *cur;
 
   ctx->vcount = 0;
   ctx->vsize = 0;
 
-#define THIS_BODY cur->content
   for (i = 0; i < ctx->msgcount; i++)
   {
-    HEADER *cur = ctx->hdrs[i];
-    if (cur->virtual != -1)
+    cur = ctx->hdrs[i];
+    if (cur->virtual >= 0)
     {
       cur->virtual = ctx->vcount;
       ctx->v2r[ctx->vcount] = i;
       ctx->vcount++;
-      ctx->vsize += THIS_BODY->length + THIS_BODY->offset - THIS_BODY->hdr_offset;
+      ctx->vsize += cur->content->length + cur->content->offset - cur->content->hdr_offset;
       cur->num_hidden = mutt_get_hidden (ctx, cur);
     }
   }
-#undef THIS_BODY
 }
 
 int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
 {
-  HEADER *roothdr = NULL, *top;
+  THREAD *thread, *top;
+  HEADER *roothdr = NULL;
   int final, reverse = (Sort & SORT_REVERSE), minmsgno;
   int num_hidden = 0, new = 0, old = 0;
   int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
@@ -807,11 +913,14 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
   }
 
   final = cur->virtual;
-  while (cur->parent) 
-     cur = cur->parent;
-  top = cur;
+  thread = cur->thread;
+  while (thread->parent)
+    thread = thread->parent;
+  top = thread;
+  while (!thread->message)
+    thread = thread->child;
+  cur = thread->message;
   minmsgno = cur->msgno;
-  
 
   if (!cur->read && CHECK_LIMIT)
   {
@@ -841,7 +950,7 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
     }
   }
 
-  if ((cur = cur->child) == NULL)
+  if ((thread = thread->child) == NULL)
   {
     /* return value depends on action requested */
     if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
@@ -856,63 +965,68 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
   
   FOREVER
   {
-    if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
+    cur = thread->message;
+
+    if (cur)
     {
-      cur->pair = 0; /* force index entry's color to be re-evaluated */
-      cur->collapsed = flag & M_THREAD_COLLAPSE;
-      if (!roothdr && CHECK_LIMIT)
+      if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
       {
-       roothdr = cur;
-       if (flag & M_THREAD_COLLAPSE)
-         final = roothdr->virtual;
-      }
+       cur->pair = 0; /* force index entry's color to be re-evaluated */
+       cur->collapsed = flag & M_THREAD_COLLAPSE;
+       if (!roothdr && CHECK_LIMIT)
+       {
+         roothdr = cur;
+         if (flag & M_THREAD_COLLAPSE)
+           final = roothdr->virtual;
+       }
 
-      if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
-      {
-       minmsgno = cur->msgno;
-       final = cur->virtual;
-      }
+       if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
+       {
+         minmsgno = cur->msgno;
+         final = cur->virtual;
+       }
 
-      if (flag & M_THREAD_COLLAPSE)
-      {
-       if (cur != roothdr)
-         cur->virtual = -1;
-      }
-      else 
-      {
-       if (CHECK_LIMIT)
-         cur->virtual = cur->msgno;
+       if (flag & M_THREAD_COLLAPSE)
+       {
+         if (cur != roothdr)
+           cur->virtual = -1;
+       }
+       else 
+       {
+         if (CHECK_LIMIT)
+           cur->virtual = cur->msgno;
+       }
       }
-    }
 
 
-    if (!cur->read && CHECK_LIMIT)
-    {
-      if (cur->old)
-       old = 2;
-      else
-       new = 1;
-      if (cur->msgno < min_unread_msgno)
+      if (!cur->read && CHECK_LIMIT)
       {
-       min_unread = cur->virtual;
-       min_unread_msgno = cur->msgno;
+       if (cur->old)
+         old = 2;
+       else
+         new = 1;
+       if (cur->msgno < min_unread_msgno)
+       {
+         min_unread = cur->virtual;
+         min_unread_msgno = cur->msgno;
+       }
       }
-    }
 
-    if (cur->virtual == -1 && CHECK_LIMIT)
-      num_hidden++;
+      if (cur->virtual == -1 && CHECK_LIMIT)
+       num_hidden++;
+    }
 
-    if (cur->child)
-      cur = cur->child;
-    else if (cur->next)
-      cur = cur->next;
+    if (thread->child)
+      thread = thread->child;
+    else if (thread->next)
+      thread = thread->next;
     else
     {
       int done = 0;
-      while (!cur->next)
+      while (!thread->next)
       {
-       cur = cur->parent;
-       if (cur == top)
+       thread = thread->parent;
+       if (thread == top)
        {
          done = 1;
          break;
@@ -920,7 +1034,7 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
       }
       if (done)
        break;
-      cur = cur->next;
+      thread = thread->next;
     }
   }
 
@@ -938,89 +1052,71 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
 #undef CHECK_LIMIT
 }
 
-int mutt_messages_in_thread (HEADER *cur)
+
+/* if flag is 0, we want to know how many messages
+ * are in the thread.  if flag is 1, we want to know
+ * our position in the thread. */
+int mutt_messages_in_thread (HEADER *hdr, int flag)
 {
-  HEADER *top;
-  int n = 1;
+  THREAD *threads[2];
+  int i;
+
   if ((Sort & SORT_MASK) != SORT_THREADS)
-  {
-    /* mutt_error _("Threading is not enabled."); */
     return (1);
-  }
 
-  /* find top parent */
-  while (cur->parent)
-    cur = cur->parent;
-  top = cur;
-  /* return if there are no children */
-  if ((cur = cur->child) == NULL)
-    return n;
+  threads[0] = hdr->thread;
+  while (threads[0]->parent)
+    threads[0] = threads[0]->parent;
+  while (threads[0]->prev)
+    threads[0] = threads[0]->prev;
 
-  FOREVER
+  if (flag)
+    threads[1] = hdr->thread;
+  else
+    threads[1] = threads[0]->next;
+
+  for (i = 0; i < flag ? 1 : 2; i++)
   {
-    n++;
-    if (cur->child)
-      cur = cur->child;
-    else if (cur->next)
-      cur = cur->next;
-    else 
-    {
-      while (!cur->next) 
-      {
-        cur = cur->parent;
-        if (cur == top)
-          return n;
-      }
-      cur = cur->next;
-    }
-  }
-  /* not reached */
+    while (!threads[i]->message)
+      threads[i] = threads[i]->child;
+  } 
+
+  return (((Sort & SORT_REVERSE ? -1 : 1)
+          * threads[1]->message->msgno - threads[0]->message->msgno) + (flag ? 1 : 0));
 }
 
-int mutt_msgno_in_thread (HEADER *cur)
+HASH *mutt_make_id_hash (CONTEXT *ctx)
 {
-  HEADER *top;
-  HEADER *target;
-  int n = 0;
+  int i;
+  HEADER *hdr;
+  HASH *hash;
 
-  if ((Sort & SORT_MASK) != SORT_THREADS)
-  { 
-    /* mutt_error _("Threading is not enabled."); */
-    return (0);
-  } 
+  hash = hash_create (ctx->msgcount * 2);
 
-  /* save target */
-  target = cur;
+  for (i = 0; i < ctx->msgcount; i++)
+  {
+    hdr = ctx->hdrs[i];
+    if (hdr->env->message_id)
+      hash_insert (hash, hdr->env->message_id, hdr, 0);
+  }
 
-  /* find top parent */
-  while (cur->parent)
-    cur = cur->parent;
-  top = cur;
+  return hash;
+}
 
-  /* return if single message */
-  if (top == target)
-    return n;
+HASH *mutt_make_subj_hash (CONTEXT *ctx)
+{
+  int i;
+  HEADER *hdr;
+  HASH *hash;
 
-  /* step into first child */
-  cur = cur->child;
+  hash = hash_create (ctx->msgcount * 2);
 
-  FOREVER
+  for (i = 0; i < ctx->msgcount; i++)
   {
-    n++;
-    if (cur == target)
-      return n;
-    if (cur->child)
-      cur = cur->child;
-    else if (cur->next) 
-      cur = cur->next;
-    else 
-    {
-      while (!cur->next) 
-        cur = cur->parent;
-      cur = cur->next;
-    } 
-  } 
-  /* not reached */
+    hdr = ctx->hdrs[i];
+    if (hdr->env->real_subj)
+      hash_insert (hash, hdr->env->real_subj, hdr, 1);
+  }
+
+  return hash;
 }