PageRenderTime 72ms CodeModel.GetById 24ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/thread.c

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