PageRenderTime 78ms CodeModel.GetById 26ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/thread.c

https://bitbucket.org/mutt/mutt
C | 1440 lines | 1131 code | 178 blank | 131 comment | 378 complexity | 7baed38601e4280cf435e9090c5677fa MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, LGPL-2.0, LGPL-2.1
   1/*
   2 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
   3 *
   4 *     This program is free software; you can redistribute it and/or modify
   5 *     it under the terms of the GNU General Public License as published by
   6 *     the Free Software Foundation; either version 2 of the License, or
   7 *     (at your option) any later version.
   8 * 
   9 *     This program is distributed in the hope that it will be useful,
  10 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12 *     GNU General Public License for more details.
  13 * 
  14 *     You should have received a copy of the GNU General Public License
  15 *     along with this program; if not, write to the Free Software
  16 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  17 */ 
  18
  19#if HAVE_CONFIG_H
  20# include "config.h"
  21#endif
  22
  23#include "mutt.h"
  24#include "sort.h"
  25
  26#include <string.h>
  27#include <ctype.h>
  28
  29#define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited)))
  30
  31/* determine whether a is a descendant of b */
  32static int is_descendant (THREAD *a, THREAD *b)
  33{
  34  while (a)
  35  {
  36    if (a == b)
  37      return (1);
  38    a = a->parent;
  39  }
  40  return (0);
  41}
  42
  43/* Determines whether to display a message's subject. */
  44static int need_display_subject (CONTEXT *ctx, HEADER *hdr)
  45{
  46  THREAD *tmp, *tree = hdr->thread;
  47
  48  /* if the user disabled subject hiding, display it */
  49  if (!option (OPTHIDETHREADSUBJECT))
  50    return (1);
  51
  52  /* if our subject is different from our parent's, display it */
  53  if (hdr->subject_changed)
  54    return (1);
  55
  56  /* if our subject is different from that of our closest previously displayed
  57   * sibling, display the subject */
  58  for (tmp = tree->prev; tmp; tmp = tmp->prev)
  59  {
  60    hdr = tmp->message;
  61    if (hdr && VISIBLE (hdr, ctx))
  62    {
  63      if (hdr->subject_changed)
  64	return (1);
  65      else
  66	break;
  67    }
  68  }
  69  
  70  /* if there is a parent-to-child subject change anywhere between us and our
  71   * closest displayed ancestor, display the subject */
  72  for (tmp = tree->parent; tmp; tmp = tmp->parent)
  73  {
  74    hdr = tmp->message;
  75    if (hdr)
  76    {
  77      if (VISIBLE (hdr, ctx))
  78	return (0);
  79      else if (hdr->subject_changed)
  80	return (1);
  81    }
  82  }
  83
  84  /* if we have no visible parent or previous sibling, display the subject */
  85  return (1);
  86}
  87
  88static void linearize_tree (CONTEXT *ctx)
  89{
  90  THREAD *tree = ctx->tree;
  91  HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
  92
  93  while (tree)
  94  {
  95    while (!tree->message)
  96      tree = tree->child;
  97
  98    *array = tree->message;
  99    array += Sort & SORT_REVERSE ? -1 : 1;
 100
 101    if (tree->child)
 102      tree = tree->child;
 103    else
 104    {
 105      while (tree)
 106      {
 107	if (tree->next)
 108	{
 109	  tree = tree->next;
 110	  break;
 111	}
 112	else
 113	  tree = tree->parent;
 114      }
 115    }
 116  }
 117}
 118
 119/* this calculates whether a node is the root of a subtree that has visible
 120 * nodes, whether a node itself is visible, whether, if invisible, it has
 121 * depth anyway, and whether any of its later siblings are roots of visible
 122 * subtrees.  while it's at it, it frees the old thread display, so we can
 123 * skip parts of the tree in mutt_draw_tree() if we've decided here that we
 124 * don't care about them any more.
 125 */
 126static void calculate_visibility (CONTEXT *ctx, int *max_depth)
 127{
 128  THREAD *tmp, *tree = ctx->tree;
 129  int hide_top_missing = option (OPTHIDETOPMISSING) && !option (OPTHIDEMISSING);
 130  int hide_top_limited = option (OPTHIDETOPLIMITED) && !option (OPTHIDELIMITED);
 131  int depth = 0;
 132
 133  /* we walk each level backwards to make it easier to compute next_subtree_visible */
 134  while (tree->next)
 135    tree = tree->next;
 136  *max_depth = 0;
 137
 138  FOREVER
 139  {
 140    if (depth > *max_depth)
 141      *max_depth = depth;
 142
 143    tree->subtree_visible = 0;
 144    if (tree->message)
 145    {
 146      FREE (&tree->message->tree);
 147      if (VISIBLE (tree->message, ctx))
 148      {
 149	tree->deep = 1;
 150	tree->visible = 1;
 151	tree->message->display_subject = need_display_subject (ctx, tree->message);
 152	for (tmp = tree; tmp; tmp = tmp->parent)
 153	{
 154	  if (tmp->subtree_visible)
 155	  {
 156	    tmp->deep = 1;
 157	    tmp->subtree_visible = 2;
 158	    break;
 159	  }
 160	  else
 161	    tmp->subtree_visible = 1;
 162	}
 163      }
 164      else
 165      {
 166	tree->visible = 0;
 167	tree->deep = !option (OPTHIDELIMITED);
 168      }
 169    }
 170    else
 171    {
 172      tree->visible = 0;
 173      tree->deep = !option (OPTHIDEMISSING);
 174    }
 175    tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible
 176						|| tree->next->subtree_visible);
 177    if (tree->child)
 178    {
 179      depth++;
 180      tree = tree->child;
 181      while (tree->next)
 182	tree = tree->next;
 183    }
 184    else if (tree->prev)
 185      tree = tree->prev;
 186    else
 187    {
 188      while (tree && !tree->prev)
 189      {
 190	depth--;
 191	tree = tree->parent;
 192      }
 193      if (!tree)
 194	break;
 195      else
 196	tree = tree->prev;
 197    }
 198  }
 199  
 200  /* now fix up for the OPTHIDETOP* options if necessary */
 201  if (hide_top_limited || hide_top_missing)
 202  {
 203    tree = ctx->tree;
 204    FOREVER
 205    {
 206      if (!tree->visible && tree->deep && tree->subtree_visible < 2 
 207	  && ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
 208	tree->deep = 0;
 209      if (!tree->deep && tree->child && tree->subtree_visible)
 210	tree = tree->child;
 211      else if (tree->next)
 212	tree = tree->next;
 213      else
 214      {
 215	while (tree && !tree->next)
 216	  tree = tree->parent;
 217	if (!tree)
 218	  break;
 219	else
 220	  tree = tree->next;
 221      }
 222    }
 223  }
 224}
 225
 226/* Since the graphics characters have a value >255, I have to resort to
 227 * using escape sequences to pass the information to print_enriched_string().
 228 * These are the macros MUTT_TREE_* defined in mutt.h.
 229 *
 230 * ncurses should automatically use the default ASCII characters instead of
 231 * graphics chars on terminals which don't support them (see the man page
 232 * for curs_addch).
 233 */
 234void mutt_draw_tree (CONTEXT *ctx)
 235{
 236  char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree;
 237  char corner = (Sort & SORT_REVERSE) ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
 238  char vtee = (Sort & SORT_REVERSE) ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
 239  int depth = 0, start_depth = 0, max_depth = 0, width = option (OPTNARROWTREE) ? 1 : 2;
 240  THREAD *nextdisp = NULL, *pseudo = NULL, *parent = NULL, *tree = ctx->tree;
 241
 242  /* Do the visibility calculations and free the old thread chars.
 243   * From now on we can simply ignore invisible subtrees
 244   */
 245  calculate_visibility (ctx, &max_depth);
 246  pfx = safe_malloc (width * max_depth + 2);
 247  arrow = safe_malloc (width * max_depth + 2);
 248  while (tree)
 249  {
 250    if (depth)
 251    {
 252      myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width;
 253      if (depth && start_depth == depth)
 254	myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
 255      else if (parent->message && !option (OPTHIDELIMITED))
 256	myarrow[0] = MUTT_TREE_HIDDEN;
 257      else if (!parent->message && !option (OPTHIDEMISSING))
 258	myarrow[0] = MUTT_TREE_MISSING;
 259      else
 260	myarrow[0] = vtee;
 261      if (width == 2)
 262	myarrow[1] = pseudo ?  MUTT_TREE_STAR
 263	                     : (tree->duplicate_thread ? MUTT_TREE_EQUALS : MUTT_TREE_HLINE);
 264      if (tree->visible)
 265      {
 266	myarrow[width] = MUTT_TREE_RARROW;
 267	myarrow[width + 1] = 0;
 268	new_tree = safe_malloc ((2 + depth * width));
 269	if (start_depth > 1)
 270	{
 271	  strncpy (new_tree, pfx, (start_depth - 1) * width);
 272	  strfcpy (new_tree + (start_depth - 1) * width,
 273		   arrow, (1 + depth - start_depth) * width + 2);
 274	}
 275	else
 276	  strfcpy (new_tree, arrow, 2 + depth * width);
 277	tree->message->tree = new_tree;
 278      }
 279    }
 280    if (tree->child && depth)
 281    {
 282      mypfx = pfx + (depth - 1) * width;
 283      mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
 284      if (width == 2)
 285	mypfx[1] = MUTT_TREE_SPACE;
 286    }
 287    parent = tree;
 288    nextdisp = NULL;
 289    pseudo = NULL;
 290    do
 291    {
 292      if (tree->child && tree->subtree_visible)
 293      {
 294	if (tree->deep)
 295	  depth++;
 296	if (tree->visible)
 297	  start_depth = depth;
 298	tree = tree->child;
 299
 300	/* we do this here because we need to make sure that the first child thread
 301	 * of the old tree that we deal with is actually displayed if any are,
 302	 * or we might set the parent variable wrong while going through it. */
 303	while (!tree->subtree_visible && tree->next)
 304	  tree = tree->next;
 305      }
 306      else
 307      {
 308	while (!tree->next && tree->parent)
 309	{
 310	  if (tree == pseudo)
 311	    pseudo = NULL;
 312	  if (tree == nextdisp)
 313	    nextdisp = NULL;
 314	  if (tree->visible)
 315	    start_depth = depth;
 316	  tree = tree->parent;
 317	  if (tree->deep)
 318	  {
 319	    if (start_depth == depth)
 320	      start_depth--;
 321	    depth--;
 322	  }
 323	}
 324	if (tree == pseudo)
 325	  pseudo = NULL;
 326	if (tree == nextdisp)
 327	  nextdisp = NULL;
 328	if (tree->visible)
 329	  start_depth = depth;
 330	tree = tree->next;
 331	if (!tree)
 332	  break;
 333      }
 334      if (!pseudo && tree->fake_thread)
 335	pseudo = tree;
 336      if (!nextdisp && tree->next_subtree_visible)
 337	nextdisp = tree;
 338    }
 339    while (!tree->deep);
 340  }
 341
 342  FREE (&pfx);
 343  FREE (&arrow);
 344}
 345
 346/* since we may be trying to attach as a pseudo-thread a THREAD that
 347 * has no message, we have to make a list of all the subjects of its
 348 * most immediate existing descendants.  we also note the earliest
 349 * date on any of the parents and put it in *dateptr. */
 350static LIST *make_subject_list (THREAD *cur, time_t *dateptr)
 351{
 352  THREAD *start = cur;
 353  ENVELOPE *env;
 354  time_t thisdate;
 355  LIST *curlist, *oldlist, *newlist, *subjects = NULL;
 356  int rc = 0;
 357  
 358  FOREVER
 359  {
 360    while (!cur->message)
 361      cur = cur->child;
 362
 363    if (dateptr)
 364    {
 365      thisdate = option (OPTTHREADRECEIVED)
 366	? cur->message->received : cur->message->date_sent;
 367      if (!*dateptr || thisdate < *dateptr)
 368	*dateptr = thisdate;
 369    }
 370
 371    env = cur->message->env;
 372    if (env->real_subj &&
 373	((env->real_subj != env->subject) || (!option (OPTSORTRE))))
 374    {
 375      for (curlist = subjects, oldlist = NULL;
 376	   curlist; oldlist = curlist, curlist = curlist->next)
 377      {
 378	rc = mutt_strcmp (env->real_subj, curlist->data);
 379	if (rc >= 0)
 380	  break;
 381      }
 382      if (!curlist || rc > 0)
 383      {
 384	newlist = safe_calloc (1, sizeof (LIST));
 385	newlist->data = env->real_subj;
 386	if (oldlist)
 387	{
 388	  newlist->next = oldlist->next;
 389	  oldlist->next = newlist;
 390	}
 391	else
 392	{
 393	  newlist->next = subjects;
 394	  subjects = newlist;
 395	}
 396      }
 397    }
 398
 399    while (!cur->next && cur != start)
 400    {
 401      cur = cur->parent;
 402    }
 403    if (cur == start)
 404      break;
 405    cur = cur->next;
 406  }
 407
 408  return (subjects);
 409}
 410
 411/* find the best possible match for a parent mesage based upon subject.
 412 * if there are multiple matches, the one which was sent the latest, but
 413 * before the current message, is used. 
 414 */
 415static THREAD *find_subject (CONTEXT *ctx, THREAD *cur)
 416{
 417  struct hash_elem *ptr;
 418  THREAD *tmp, *last = NULL;
 419  LIST *subjects = NULL, *oldlist;
 420  time_t date = 0;  
 421
 422  subjects = make_subject_list (cur, &date);
 423
 424  while (subjects)
 425  {
 426    for (ptr = hash_find_bucket (ctx->subj_hash, subjects->data); ptr; ptr = ptr->next)
 427    {
 428      tmp = ((HEADER *) ptr->data)->thread;
 429      if (tmp != cur &&			   /* don't match the same message */
 430	  !tmp->fake_thread &&		   /* don't match pseudo threads */
 431	  tmp->message->subject_changed && /* only match interesting replies */
 432	  !is_descendant (tmp, cur) &&	   /* don't match in the same thread */
 433	  (date >= (option (OPTTHREADRECEIVED) ?
 434		    tmp->message->received :
 435		    tmp->message->date_sent)) &&
 436	  (!last ||
 437	   (option (OPTTHREADRECEIVED) ?
 438	    (last->message->received < tmp->message->received) :
 439	    (last->message->date_sent < tmp->message->date_sent))) &&
 440	  tmp->message->env->real_subj &&
 441	  mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
 442	last = tmp; /* best match so far */
 443    }
 444
 445    oldlist = subjects;
 446    subjects = subjects->next;
 447    FREE (&oldlist);
 448  }
 449  return (last);
 450}
 451
 452/* remove cur and its descendants from their current location.
 453 * also make sure ancestors of cur no longer are sorted by the
 454 * fact that cur is their descendant. */
 455static void unlink_message (THREAD **old, THREAD *cur)
 456{
 457  THREAD *tmp;
 458
 459  if (cur->prev)
 460    cur->prev->next = cur->next;
 461  else
 462    *old = cur->next;
 463
 464  if (cur->next)
 465    cur->next->prev = cur->prev;
 466
 467  if (cur->sort_key)
 468  {
 469    for (tmp = cur->parent; tmp && tmp->sort_key == cur->sort_key;
 470	 tmp = tmp->parent)
 471      tmp->sort_key = NULL;
 472  }
 473}
 474
 475/* add cur as a prior sibling of *new, with parent newparent */
 476static void insert_message (THREAD **new, THREAD *newparent, THREAD *cur)
 477{
 478  if (*new)
 479    (*new)->prev = cur;
 480
 481  cur->parent = newparent;
 482  cur->next = *new;
 483  cur->prev = NULL;
 484  *new = cur;
 485}
 486
 487/* thread by subject things that didn't get threaded by message-id */
 488static void pseudo_threads (CONTEXT *ctx)
 489{
 490  THREAD *tree = ctx->tree, *top = tree;
 491  THREAD *tmp, *cur, *parent, *curchild, *nextchild;
 492
 493  if (!ctx->subj_hash)
 494    ctx->subj_hash = mutt_make_subj_hash (ctx);
 495
 496  while (tree)
 497  {
 498    cur = tree;
 499    tree = tree->next;
 500    if ((parent = find_subject (ctx, cur)) != NULL)
 501    {
 502      cur->fake_thread = 1;
 503      unlink_message (&top, cur);
 504      insert_message (&parent->child, parent, cur);
 505      parent->sort_children = 1;
 506      tmp = cur;
 507      FOREVER
 508      {
 509	while (!tmp->message)
 510	  tmp = tmp->child;
 511
 512	/* if the message we're attaching has pseudo-children, they
 513	 * need to be attached to its parent, so move them up a level.
 514	 * but only do this if they have the same real subject as the
 515	 * parent, since otherwise they rightly belong to the message
 516	 * we're attaching. */
 517	if (tmp == cur
 518	    || !mutt_strcmp (tmp->message->env->real_subj,
 519			     parent->message->env->real_subj))
 520	{
 521	  tmp->message->subject_changed = 0;
 522
 523	  for (curchild = tmp->child; curchild; )
 524	  {
 525	    nextchild = curchild->next;
 526	    if (curchild->fake_thread)
 527	    {
 528	      unlink_message (&tmp->child, curchild);
 529	      insert_message (&parent->child, parent, curchild);
 530	    }
 531	    curchild = nextchild;
 532	  }
 533	}
 534
 535	while (!tmp->next && tmp != cur)
 536	{
 537	  tmp = tmp->parent;
 538	}
 539	if (tmp == cur)
 540	  break;
 541	tmp = tmp->next;
 542      }
 543    }
 544  }
 545  ctx->tree = top;
 546}
 547
 548
 549void mutt_clear_threads (CONTEXT *ctx)
 550{
 551  int i;
 552
 553  for (i = 0; i < ctx->msgcount; i++)
 554  {
 555    /* mailbox may have been only partially read */
 556    if (ctx->hdrs[i])
 557    {
 558      ctx->hdrs[i]->thread = NULL;
 559      ctx->hdrs[i]->threaded = 0;
 560    }
 561  }
 562  ctx->tree = NULL;
 563
 564  if (ctx->thread_hash)
 565    hash_destroy (&ctx->thread_hash, *free);
 566}
 567
 568static int compare_threads (const void *a, const void *b)
 569{
 570  static sort_t *sort_func = NULL;
 571
 572  if (a && b)
 573    return ((*sort_func) (&(*((THREAD **) a))->sort_key,
 574			  &(*((THREAD **) b))->sort_key));
 575  /* a hack to let us reset sort_func even though we can't
 576   * have extra arguments because of qsort
 577   */
 578  else
 579  {
 580    sort_func = mutt_get_sort_func (Sort);
 581    return (sort_func ? 1 : 0);
 582  }
 583}
 584
 585THREAD *mutt_sort_subthreads (THREAD *thread, int init)
 586{
 587  THREAD **array, *sort_key, *top, *tmp;
 588  HEADER *oldsort_key;
 589  int i, array_size, sort_top = 0;
 590  
 591  /* we put things into the array backwards to save some cycles,
 592   * but we want to have to move less stuff around if we're 
 593   * resorting, so we sort backwards and then put them back
 594   * in reverse order so they're forwards
 595   */
 596  Sort ^= SORT_REVERSE;
 597  if (!compare_threads (NULL, NULL))
 598    return (thread);
 599
 600  top = thread;
 601
 602  array = safe_calloc ((array_size = 256), sizeof (THREAD *));
 603  while (1)
 604  {
 605    if (init || !thread->sort_key)
 606    {
 607      thread->sort_key = NULL;
 608
 609      if (thread->parent)
 610        thread->parent->sort_children = 1;
 611      else
 612	sort_top = 1;
 613    }
 614
 615    if (thread->child)
 616    {
 617      thread = thread->child;
 618      continue;
 619    }
 620    else
 621    {
 622      /* if it has no children, it must be real. sort it on its own merits */
 623      thread->sort_key = thread->message;
 624
 625      if (thread->next)
 626      {
 627	thread = thread->next;
 628	continue;
 629      }
 630    }
 631
 632    while (!thread->next)
 633    {
 634      /* if it has siblings and needs to be sorted, sort it... */
 635      if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
 636      {
 637	/* put them into the array */
 638	for (i = 0; thread; i++, thread = thread->prev)
 639	{
 640	  if (i >= array_size)
 641	    safe_realloc (&array, (array_size *= 2) * sizeof (THREAD *));
 642
 643	  array[i] = thread;
 644	}
 645
 646	qsort ((void *) array, i, sizeof (THREAD *), *compare_threads);
 647
 648	/* attach them back together.  make thread the last sibling. */
 649	thread = array[0];
 650	thread->next = NULL;
 651	array[i - 1]->prev = NULL;
 652
 653	if (thread->parent)
 654	  thread->parent->child = array[i - 1];
 655	else
 656	  top = array[i - 1];
 657
 658	while (--i)
 659	{
 660	  array[i - 1]->prev = array[i];
 661	  array[i]->next = array[i - 1];
 662	}
 663      }
 664
 665      if (thread->parent)
 666      {
 667	tmp = thread;
 668	thread = thread->parent;
 669
 670	if (!thread->sort_key || thread->sort_children)
 671	{
 672	  /* make sort_key the first or last sibling, as appropriate */
 673	  sort_key = (!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? thread->child : tmp;
 674
 675	  /* we just sorted its children */
 676	  thread->sort_children = 0;
 677
 678	  oldsort_key = thread->sort_key;
 679	  thread->sort_key = thread->message;
 680
 681	  if (Sort & SORT_LAST)
 682	  {
 683	    if (!thread->sort_key
 684		|| ((((Sort & SORT_REVERSE) ? 1 : -1)
 685		     * compare_threads ((void *) &thread,
 686					(void *) &sort_key))
 687		    > 0))
 688	      thread->sort_key = sort_key->sort_key;
 689	  }
 690	  else if (!thread->sort_key)
 691	    thread->sort_key = sort_key->sort_key;
 692
 693	  /* if its sort_key has changed, we need to resort it and siblings */
 694	  if (oldsort_key != thread->sort_key)
 695	  {
 696	    if (thread->parent)
 697	      thread->parent->sort_children = 1;
 698	    else
 699	      sort_top = 1;
 700	  }
 701	}
 702      }
 703      else
 704      {
 705	Sort ^= SORT_REVERSE;
 706	FREE (&array);
 707	return (top);
 708      }
 709    }
 710
 711    thread = thread->next;
 712  }
 713}
 714
 715static void check_subjects (CONTEXT *ctx, int init)
 716{
 717  HEADER *cur;
 718  THREAD *tmp;
 719  int i;
 720
 721  for (i = 0; i < ctx->msgcount; i++)
 722  {
 723    cur = ctx->hdrs[i];
 724    if (cur->thread->check_subject)
 725      cur->thread->check_subject = 0;
 726    else if (!init)
 727      continue;
 728
 729    /* figure out which messages have subjects different than their parents' */
 730    tmp = cur->thread->parent;
 731    while (tmp && !tmp->message)
 732    {
 733      tmp = tmp->parent;
 734    }
 735
 736    if (!tmp)
 737      cur->subject_changed = 1;
 738    else if (cur->env->real_subj && tmp->message->env->real_subj)
 739      cur->subject_changed = mutt_strcmp (cur->env->real_subj,
 740					  tmp->message->env->real_subj) ? 1 : 0;
 741    else
 742      cur->subject_changed = (cur->env->real_subj
 743			      || tmp->message->env->real_subj) ? 1 : 0;
 744  }
 745}
 746
 747void mutt_sort_threads (CONTEXT *ctx, int init)
 748{
 749  HEADER *cur;
 750  int i, oldsort, using_refs = 0;
 751  THREAD *thread, *new, *tmp, top;
 752  LIST *ref = NULL;
 753  
 754  /* set Sort to the secondary method to support the set sort_aux=reverse-*
 755   * settings.  The sorting functions just look at the value of
 756   * SORT_REVERSE
 757   */
 758  oldsort = Sort;
 759  Sort = SortAux;
 760  
 761  if (!ctx->thread_hash)
 762    init = 1;
 763
 764  if (init)
 765    ctx->thread_hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
 766
 767  /* we want a quick way to see if things are actually attached to the top of the
 768   * thread tree or if they're just dangling, so we attach everything to a top
 769   * node temporarily */
 770  top.parent = top.next = top.prev = NULL;
 771  top.child = ctx->tree;
 772  for (thread = ctx->tree; thread; thread = thread->next)
 773    thread->parent = &top;
 774
 775  /* put each new message together with the matching messageless THREAD if it
 776   * exists.  otherwise, if there is a THREAD that already has a message, thread
 777   * new message as an identical child.  if we didn't attach the message to a
 778   * THREAD, make a new one for it. */
 779  for (i = 0; i < ctx->msgcount; i++)
 780  {
 781    cur = ctx->hdrs[i];
 782
 783    if (!cur->thread)
 784    {
 785      if ((!init || option (OPTDUPTHREADS)) && cur->env->message_id)
 786	thread = hash_find (ctx->thread_hash, cur->env->message_id);
 787      else
 788	thread = NULL;
 789
 790      if (thread && !thread->message)
 791      {
 792	/* this is a message which was missing before */
 793	thread->message = cur;
 794	cur->thread = thread;
 795	thread->check_subject = 1;
 796
 797	/* mark descendants as needing subject_changed checked */
 798	for (tmp = (thread->child ? thread->child : thread); tmp != thread; )
 799	{
 800	  while (!tmp->message)
 801	    tmp = tmp->child;
 802	  tmp->check_subject = 1;
 803	  while (!tmp->next && tmp != thread)
 804	    tmp = tmp->parent;
 805	  if (tmp != thread)
 806	    tmp = tmp->next;
 807	}
 808
 809	if (thread->parent)
 810	{
 811	  /* remove threading info above it based on its children, which we'll
 812	   * recalculate based on its headers.  make sure not to leave
 813	   * dangling missing messages.  note that we haven't kept track
 814	   * of what info came from its children and what from its siblings'
 815	   * children, so we just remove the stuff that's definitely from it */
 816	  do
 817	  {
 818	    tmp = thread->parent;
 819	    unlink_message (&tmp->child, thread);
 820	    thread->parent = NULL;
 821	    thread->sort_key = NULL;
 822	    thread->fake_thread = 0;
 823	    thread = tmp;
 824	  } while (thread != &top && !thread->child && !thread->message);
 825	}
 826      }
 827      else
 828      {
 829	new = (option (OPTDUPTHREADS) ? thread : NULL);
 830
 831	thread = safe_calloc (1, sizeof (THREAD));
 832	thread->message = cur;
 833	thread->check_subject = 1;
 834	cur->thread = thread;
 835	hash_insert (ctx->thread_hash,
 836		     cur->env->message_id ? cur->env->message_id : "",
 837		     thread);
 838
 839	if (new)
 840	{
 841	  if (new->duplicate_thread)
 842	    new = new->parent;
 843
 844	  thread = cur->thread;
 845
 846	  insert_message (&new->child, new, thread);
 847	  thread->duplicate_thread = 1;
 848	  thread->message->threaded = 1;
 849	}
 850      }
 851    }
 852    else
 853    {
 854      /* unlink pseudo-threads because they might be children of newly
 855       * arrived messages */
 856      thread = cur->thread;
 857      for (new = thread->child; new; )
 858      {
 859	tmp = new->next;
 860	if (new->fake_thread)
 861	{
 862	  unlink_message (&thread->child, new);
 863	  insert_message (&top.child, &top, new);
 864	  new->fake_thread = 0;
 865	}
 866	new = tmp;
 867      }
 868    }
 869  }
 870
 871  /* thread by references */
 872  for (i = 0; i < ctx->msgcount; i++)
 873  {
 874    cur = ctx->hdrs[i];
 875    if (cur->threaded)
 876      continue;
 877    cur->threaded = 1;
 878
 879    thread = cur->thread;
 880    using_refs = 0;
 881
 882    while (1)
 883    {
 884      if (using_refs == 0)
 885      {
 886	/* look at the beginning of in-reply-to: */
 887	if ((ref = cur->env->in_reply_to) != NULL)
 888	  using_refs = 1;
 889	else
 890	{
 891	  ref = cur->env->references;
 892	  using_refs = 2;
 893	}
 894      }
 895      else if (using_refs == 1)
 896      {
 897	/* if there's no references header, use all the in-reply-to:
 898	 * data that we have.  otherwise, use the first reference
 899	 * if it's different than the first in-reply-to, otherwise use
 900	 * the second reference (since at least eudora puts the most
 901	 * recent reference in in-reply-to and the rest in references)
 902	 */
 903	if (!cur->env->references)
 904	  ref = ref->next;
 905	else
 906	{
 907	  if (mutt_strcmp (ref->data, cur->env->references->data))
 908	    ref = cur->env->references;
 909	  else
 910	    ref = cur->env->references->next;
 911	  
 912	  using_refs = 2;
 913	}
 914      }
 915      else
 916	ref = ref->next; /* go on with references */
 917      
 918      if (!ref)
 919	break;
 920
 921      if ((new = hash_find (ctx->thread_hash, ref->data)) == NULL)
 922      {
 923	new = safe_calloc (1, sizeof (THREAD));
 924	hash_insert (ctx->thread_hash, ref->data, new);
 925      }
 926      else
 927      {
 928	if (new->duplicate_thread)
 929	  new = new->parent;
 930	if (is_descendant (new, thread)) /* no loops! */
 931	  continue;
 932      }
 933
 934      if (thread->parent)
 935	unlink_message (&top.child, thread);
 936      insert_message (&new->child, new, thread);
 937      thread = new;
 938      if (thread->message || (thread->parent && thread->parent != &top))
 939	break;
 940    }
 941
 942    if (!thread->parent)
 943      insert_message (&top.child, &top, thread);
 944  }
 945
 946  /* detach everything from the temporary top node */
 947  for (thread = top.child; thread; thread = thread->next)
 948  {
 949    thread->parent = NULL;
 950  }
 951  ctx->tree = top.child;
 952
 953  check_subjects (ctx, init);
 954
 955  if (!option (OPTSTRICTTHREADS))
 956    pseudo_threads (ctx);
 957
 958  if (ctx->tree)
 959  {
 960    ctx->tree = mutt_sort_subthreads (ctx->tree, init);
 961
 962    /* restore the oldsort order. */
 963    Sort = oldsort;
 964    
 965    /* Put the list into an array. */
 966    linearize_tree (ctx);
 967
 968    /* Draw the thread tree. */
 969    mutt_draw_tree (ctx);
 970  }
 971}
 972
 973static HEADER *find_virtual (THREAD *cur, int reverse)
 974{
 975  THREAD *top;
 976
 977  if (cur->message && cur->message->virtual >= 0)
 978    return (cur->message);
 979
 980  top = cur;
 981  if ((cur = cur->child) == NULL)
 982    return (NULL);
 983
 984  while (reverse && cur->next)
 985    cur = cur->next;
 986
 987  FOREVER
 988  {
 989    if (cur->message && cur->message->virtual >= 0)
 990      return (cur->message);
 991
 992    if (cur->child)
 993    {
 994      cur = cur->child;
 995
 996      while (reverse && cur->next)
 997	cur = cur->next;
 998    }
 999    else if (reverse ? cur->prev : cur->next)
1000      cur = reverse ? cur->prev : cur->next;
1001    else
1002    {
1003      while (!(reverse ? cur->prev : cur->next))
1004      {
1005	cur = cur->parent;
1006	if (cur == top)
1007	  return (NULL);
1008      }
1009      cur = reverse ? cur->prev : cur->next;
1010    }
1011    /* not reached */
1012  }
1013}
1014
1015/* dir => true when moving forward, false when moving in reverse
1016 * subthreads => false when moving to next thread, true when moving to next subthread
1017 */
1018int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
1019{
1020  THREAD *cur;
1021  HEADER *tmp;
1022
1023  if ((Sort & SORT_MASK) != SORT_THREADS)
1024  {
1025    mutt_error _("Threading is not enabled.");
1026    return (hdr->virtual);
1027  }
1028
1029  cur = hdr->thread;
1030
1031  if (!subthreads)
1032  {
1033    while (cur->parent)
1034      cur = cur->parent;
1035  }
1036  else
1037  {
1038    if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1039    {
1040      while (!cur->next && cur->parent)
1041	cur = cur->parent;
1042    }
1043    else
1044    {
1045      while (!cur->prev && cur->parent)
1046	cur = cur->parent;
1047    }
1048  }
1049
1050  if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1051  {
1052    do
1053    { 
1054      cur = cur->next;
1055      if (!cur)
1056	return (-1);
1057      tmp = find_virtual (cur, 0);
1058    } while (!tmp);
1059  }
1060  else
1061  {
1062    do
1063    { 
1064      cur = cur->prev;
1065      if (!cur)
1066	return (-1);
1067      tmp = find_virtual (cur, 1);
1068    } while (!tmp);
1069  }
1070
1071  return (tmp->virtual);
1072}
1073
1074int mutt_parent_message (CONTEXT *ctx, HEADER *hdr, int find_root)
1075{
1076  THREAD *thread;
1077  HEADER *parent = NULL;
1078
1079  if ((Sort & SORT_MASK) != SORT_THREADS)
1080  {
1081    mutt_error _("Threading is not enabled.");
1082    return (hdr->virtual);
1083  }
1084
1085  /* Root may be the current message */
1086  if (find_root)
1087    parent = hdr;
1088
1089  for (thread = hdr->thread->parent; thread; thread = thread->parent)
1090  {
1091    if ((hdr = thread->message) != NULL)
1092    {
1093      parent = hdr;
1094      if (!find_root)
1095        break;
1096    }
1097  }
1098
1099  if (!parent)
1100  {
1101    mutt_error _("Parent message is not available.");
1102    return (-1);
1103  }
1104  if (!VISIBLE (parent, ctx))
1105  {
1106    if (find_root)
1107      mutt_error _("Root message is not visible in this limited view.");
1108    else
1109      mutt_error _("Parent message is not visible in this limited view.");
1110    return (-1);
1111  }
1112  return (parent->virtual);
1113}
1114
1115void mutt_set_virtual (CONTEXT *ctx)
1116{
1117  int i;
1118  HEADER *cur;
1119
1120  ctx->vcount = 0;
1121  ctx->vsize = 0;
1122
1123  for (i = 0; i < ctx->msgcount; i++)
1124  {
1125    cur = ctx->hdrs[i];
1126    if (cur->virtual >= 0)
1127    {
1128      cur->virtual = ctx->vcount;
1129      ctx->v2r[ctx->vcount] = i;
1130      ctx->vcount++;
1131      ctx->vsize += cur->content->length + cur->content->offset - cur->content->hdr_offset;
1132      cur->num_hidden = mutt_get_hidden (ctx, cur);
1133    }
1134  }
1135}
1136
1137int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
1138{
1139  THREAD *thread, *top;
1140  HEADER *roothdr = NULL;
1141  int final, reverse = (Sort & SORT_REVERSE), minmsgno;
1142  int num_hidden = 0, new = 0, old = 0;
1143  int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
1144#define CHECK_LIMIT (!ctx->pattern || cur->limited)
1145
1146  if ((Sort & SORT_MASK) != SORT_THREADS && !(flag & MUTT_THREAD_GET_HIDDEN))
1147  {
1148    mutt_error (_("Threading is not enabled."));
1149    return (cur->virtual);
1150  }
1151
1152  final = cur->virtual;
1153  thread = cur->thread;
1154  while (thread->parent)
1155    thread = thread->parent;
1156  top = thread;
1157  while (!thread->message)
1158    thread = thread->child;
1159  cur = thread->message;
1160  minmsgno = cur->msgno;
1161
1162  if (!cur->read && CHECK_LIMIT)
1163  {
1164    if (cur->old)
1165      old = 2;
1166    else
1167      new = 1;
1168    if (cur->msgno < min_unread_msgno)
1169    {
1170      min_unread = cur->virtual;
1171      min_unread_msgno = cur->msgno;
1172    }
1173  }
1174
1175  if (cur->virtual == -1 && CHECK_LIMIT)
1176    num_hidden++;
1177
1178  if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1179  {
1180    cur->pair = 0; /* force index entry's color to be re-evaluated */
1181    cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1182    if (cur->virtual != -1)
1183    {
1184      roothdr = cur;
1185      if (flag & MUTT_THREAD_COLLAPSE)
1186	final = roothdr->virtual;
1187    }
1188  }
1189
1190  if (thread == top && (thread = thread->child) == NULL)
1191  {
1192    /* return value depends on action requested */
1193    if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1194      return (final);
1195    else if (flag & MUTT_THREAD_UNREAD)
1196      return ((old && new) ? new : (old ? old : new));
1197    else if (flag & MUTT_THREAD_GET_HIDDEN)
1198      return (num_hidden);
1199    else if (flag & MUTT_THREAD_NEXT_UNREAD)
1200      return (min_unread);
1201  }
1202  
1203  FOREVER
1204  {
1205    cur = thread->message;
1206
1207    if (cur)
1208    {
1209      if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1210      {
1211	cur->pair = 0; /* force index entry's color to be re-evaluated */
1212	cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1213	if (!roothdr && CHECK_LIMIT)
1214	{
1215	  roothdr = cur;
1216	  if (flag & MUTT_THREAD_COLLAPSE)
1217	    final = roothdr->virtual;
1218	}
1219
1220	if (reverse && (flag & MUTT_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
1221	{
1222	  minmsgno = cur->msgno;
1223	  final = cur->virtual;
1224	}
1225
1226	if (flag & MUTT_THREAD_COLLAPSE)
1227	{
1228	  if (cur != roothdr)
1229	    cur->virtual = -1;
1230	}
1231	else 
1232	{
1233	  if (CHECK_LIMIT)
1234	    cur->virtual = cur->msgno;
1235	}
1236      }
1237
1238
1239      if (!cur->read && CHECK_LIMIT)
1240      {
1241	if (cur->old)
1242	  old = 2;
1243	else
1244	  new = 1;
1245	if (cur->msgno < min_unread_msgno)
1246	{
1247	  min_unread = cur->virtual;
1248	  min_unread_msgno = cur->msgno;
1249	}
1250      }
1251
1252      if (cur->virtual == -1 && CHECK_LIMIT)
1253	num_hidden++;
1254    }
1255
1256    if (thread->child)
1257      thread = thread->child;
1258    else if (thread->next)
1259      thread = thread->next;
1260    else
1261    {
1262      int done = 0;
1263      while (!thread->next)
1264      {
1265	thread = thread->parent;
1266	if (thread == top)
1267	{
1268	  done = 1;
1269	  break;
1270	}
1271      }
1272      if (done)
1273	break;
1274      thread = thread->next;
1275    }
1276  }
1277
1278  /* return value depends on action requested */
1279  if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1280    return (final);
1281  else if (flag & MUTT_THREAD_UNREAD)
1282    return ((old && new) ? new : (old ? old : new));
1283  else if (flag & MUTT_THREAD_GET_HIDDEN)
1284    return (num_hidden+1);
1285  else if (flag & MUTT_THREAD_NEXT_UNREAD)
1286    return (min_unread);
1287
1288  return (0);
1289#undef CHECK_LIMIT
1290}
1291
1292
1293/* if flag is 0, we want to know how many messages
1294 * are in the thread.  if flag is 1, we want to know
1295 * our position in the thread. */
1296int mutt_messages_in_thread (CONTEXT *ctx, HEADER *hdr, int flag)
1297{
1298  THREAD *threads[2];
1299  int i, rc;
1300
1301  if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread)
1302    return (1);
1303
1304  threads[0] = hdr->thread;
1305  while (threads[0]->parent)
1306    threads[0] = threads[0]->parent;
1307
1308  threads[1] = flag ? hdr->thread : threads[0]->next;
1309
1310  for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
1311  {
1312    while (!threads[i]->message)
1313      threads[i] = threads[i]->child;
1314  } 
1315
1316  if (Sort & SORT_REVERSE)
1317    rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1318  else
1319    rc = (threads[1] ? threads[1]->message->msgno : ctx->msgcount) - threads[0]->message->msgno;
1320  
1321  if (flag)
1322    rc += 1;
1323  
1324  return (rc);
1325}
1326
1327
1328HASH *mutt_make_id_hash (CONTEXT *ctx)
1329{
1330  int i;
1331  HEADER *hdr;
1332  HASH *hash;
1333
1334  hash = hash_create (ctx->msgcount * 2, 0);
1335
1336  for (i = 0; i < ctx->msgcount; i++)
1337  {
1338    hdr = ctx->hdrs[i];
1339    if (hdr->env->message_id)
1340      hash_insert (hash, hdr->env->message_id, hdr);
1341  }
1342
1343  return hash;
1344}
1345
1346HASH *mutt_make_subj_hash (CONTEXT *ctx)
1347{
1348  int i;
1349  HEADER *hdr;
1350  HASH *hash;
1351
1352  hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
1353
1354  for (i = 0; i < ctx->msgcount; i++)
1355  {
1356    hdr = ctx->hdrs[i];
1357    if (hdr->env->real_subj)
1358      hash_insert (hash, hdr->env->real_subj, hdr);
1359  }
1360
1361  return hash;
1362}
1363
1364static void clean_references (THREAD *brk, THREAD *cur)
1365{
1366  THREAD *p;
1367  LIST *ref = NULL;
1368  int done = 0;
1369
1370  for (; cur; cur = cur->next, done = 0)
1371  {
1372    /* parse subthread recursively */
1373    clean_references (brk, cur->child);
1374
1375    if (!cur->message)
1376      break; /* skip pseudo-message */
1377
1378    /* Looking for the first bad reference according to the new threading.
1379     * Optimal since Mutt stores the references in reverse order, and the
1380     * first loop should match immediately for mails respecting RFC2822. */
1381    for (p = brk; !done && p; p = p->parent)
1382      for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
1383	if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
1384	{
1385	  done = 1;
1386	  break;
1387	}
1388
1389    if (done)
1390    {
1391      HEADER *h = cur->message;
1392
1393      /* clearing the References: header from obsolete Message-ID(s) */
1394      mutt_free_list (&ref->next);
1395
1396      h->env->refs_changed = h->changed = 1;
1397    }
1398  }
1399}
1400
1401void mutt_break_thread (HEADER *hdr)
1402{
1403  mutt_free_list (&hdr->env->in_reply_to);
1404  mutt_free_list (&hdr->env->references);
1405  hdr->env->irt_changed = hdr->env->refs_changed = hdr->changed = 1;
1406
1407  clean_references (hdr->thread, hdr->thread->child);
1408}
1409
1410static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
1411{
1412  if (child == parent)
1413    return 0;
1414
1415  mutt_break_thread (child);
1416
1417  child->env->in_reply_to = mutt_new_list ();
1418  child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
1419  
1420  mutt_set_flag (ctx, child, MUTT_TAG, 0);
1421  
1422  child->env->irt_changed = child->changed = 1;
1423  return 1;
1424}
1425
1426int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
1427{
1428  int i, changed = 0;
1429
1430  if (!last)
1431  {
1432    for (i = 0; i < ctx->vcount; i++)
1433      if (ctx->hdrs[Context->v2r[i]]->tagged)
1434	changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
1435  }
1436  else
1437    changed = link_threads (cur, last, ctx);
1438
1439  return changed;
1440}