mutt stable branch with some hacks
at jcs 1449 lines 34 kB view raw
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 if (roothdr) 1198 roothdr->num_hidden = num_hidden; 1199 return (final); 1200 } 1201 else if (flag & MUTT_THREAD_UNREAD) 1202 return ((old && new) ? new : (old ? old : new)); 1203 else if (flag & MUTT_THREAD_NEXT_UNREAD) 1204 return (min_unread); 1205 } 1206 1207 FOREVER 1208 { 1209 cur = thread->message; 1210 1211 if (cur) 1212 { 1213 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE)) 1214 { 1215 cur->pair = 0; /* force index entry's color to be re-evaluated */ 1216 cur->collapsed = flag & MUTT_THREAD_COLLAPSE; 1217 if (!roothdr && CHECK_LIMIT) 1218 { 1219 roothdr = cur; 1220 if (flag & MUTT_THREAD_COLLAPSE) 1221 final = roothdr->virtual; 1222 } 1223 1224 if (reverse && (flag & MUTT_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT) 1225 { 1226 minmsgno = cur->msgno; 1227 final = cur->virtual; 1228 } 1229 1230 if (flag & MUTT_THREAD_COLLAPSE) 1231 { 1232 if (cur != roothdr) 1233 cur->virtual = -1; 1234 } 1235 else 1236 { 1237 if (CHECK_LIMIT) 1238 cur->virtual = cur->msgno; 1239 } 1240 } 1241 1242 1243 if (!cur->read && CHECK_LIMIT) 1244 { 1245 if (cur->old) 1246 old = 2; 1247 else 1248 new = 1; 1249 if (cur->msgno < min_unread_msgno) 1250 { 1251 min_unread = cur->virtual; 1252 min_unread_msgno = cur->msgno; 1253 } 1254 } 1255 1256 if (cur->virtual == -1 && CHECK_LIMIT) 1257 num_hidden++; 1258 } 1259 1260 if (thread->child) 1261 thread = thread->child; 1262 else if (thread->next) 1263 thread = thread->next; 1264 else 1265 { 1266 int done = 0; 1267 while (!thread->next) 1268 { 1269 thread = thread->parent; 1270 if (thread == top) 1271 { 1272 done = 1; 1273 break; 1274 } 1275 } 1276 if (done) 1277 break; 1278 thread = thread->next; 1279 } 1280 } 1281 1282 /* return value depends on action requested */ 1283 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE)) 1284 { 1285 if (roothdr) 1286 roothdr->num_hidden = num_hidden + 1; 1287 return (final); 1288 } 1289 else if (flag & MUTT_THREAD_UNREAD) 1290 return ((old && new) ? new : (old ? old : new)); 1291 else if (flag & MUTT_THREAD_NEXT_UNREAD) 1292 return (min_unread); 1293 1294 return (0); 1295#undef CHECK_LIMIT 1296} 1297 1298 1299/* if flag is 0, we want to know how many messages 1300 * are in the thread. if flag is 1, we want to know 1301 * our position in the thread. */ 1302int mutt_messages_in_thread (CONTEXT *ctx, HEADER *hdr, int flag) 1303{ 1304 THREAD *threads[2]; 1305 int i, rc; 1306 1307 if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread) 1308 return (1); 1309 1310 threads[0] = hdr->thread; 1311 while (threads[0]->parent) 1312 threads[0] = threads[0]->parent; 1313 1314 threads[1] = flag ? hdr->thread : threads[0]->next; 1315 1316 for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++) 1317 { 1318 while (!threads[i]->message) 1319 threads[i] = threads[i]->child; 1320 } 1321 1322 if (Sort & SORT_REVERSE) 1323 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1); 1324 else 1325 rc = (threads[1] ? threads[1]->message->msgno : ctx->msgcount) - threads[0]->message->msgno; 1326 1327 if (flag) 1328 rc += 1; 1329 1330 return (rc); 1331} 1332 1333 1334HASH *mutt_make_id_hash (CONTEXT *ctx) 1335{ 1336 int i; 1337 HEADER *hdr; 1338 HASH *hash; 1339 1340 hash = hash_create (ctx->msgcount * 2, 0); 1341 1342 for (i = 0; i < ctx->msgcount; i++) 1343 { 1344 hdr = ctx->hdrs[i]; 1345 if (hdr->env->message_id) 1346 hash_insert (hash, hdr->env->message_id, hdr); 1347 } 1348 1349 return hash; 1350} 1351 1352HASH *mutt_make_subj_hash (CONTEXT *ctx) 1353{ 1354 int i; 1355 HEADER *hdr; 1356 HASH *hash; 1357 1358 hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS); 1359 1360 for (i = 0; i < ctx->msgcount; i++) 1361 { 1362 hdr = ctx->hdrs[i]; 1363 if (hdr->env->real_subj) 1364 hash_insert (hash, hdr->env->real_subj, hdr); 1365 } 1366 1367 return hash; 1368} 1369 1370static void clean_references (THREAD *brk, THREAD *cur) 1371{ 1372 THREAD *p; 1373 LIST *ref = NULL; 1374 int done = 0; 1375 1376 for (; cur; cur = cur->next, done = 0) 1377 { 1378 /* parse subthread recursively */ 1379 clean_references (brk, cur->child); 1380 1381 if (!cur->message) 1382 break; /* skip pseudo-message */ 1383 1384 /* Looking for the first bad reference according to the new threading. 1385 * Optimal since Mutt stores the references in reverse order, and the 1386 * first loop should match immediately for mails respecting RFC2822. */ 1387 for (p = brk; !done && p; p = p->parent) 1388 for (ref = cur->message->env->references; p->message && ref; ref = ref->next) 1389 if (!mutt_strcasecmp (ref->data, p->message->env->message_id)) 1390 { 1391 done = 1; 1392 break; 1393 } 1394 1395 if (done) 1396 { 1397 HEADER *h = cur->message; 1398 1399 /* clearing the References: header from obsolete Message-ID(s) */ 1400 mutt_free_list (&ref->next); 1401 1402 h->changed = 1; 1403 h->env->changed |= MUTT_ENV_CHANGED_REFS; 1404 } 1405 } 1406} 1407 1408void mutt_break_thread (HEADER *hdr) 1409{ 1410 mutt_free_list (&hdr->env->in_reply_to); 1411 mutt_free_list (&hdr->env->references); 1412 hdr->changed = 1; 1413 hdr->env->changed |= (MUTT_ENV_CHANGED_IRT | MUTT_ENV_CHANGED_REFS); 1414 1415 clean_references (hdr->thread, hdr->thread->child); 1416} 1417 1418static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx) 1419{ 1420 if (child == parent) 1421 return 0; 1422 1423 mutt_break_thread (child); 1424 1425 child->env->in_reply_to = mutt_new_list (); 1426 child->env->in_reply_to->data = safe_strdup (parent->env->message_id); 1427 1428 mutt_set_flag (ctx, child, MUTT_TAG, 0); 1429 1430 child->changed = 1; 1431 child->env->changed |= MUTT_ENV_CHANGED_IRT; 1432 return 1; 1433} 1434 1435int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx) 1436{ 1437 int i, changed = 0; 1438 1439 if (!last) 1440 { 1441 for (i = 0; i < ctx->vcount; i++) 1442 if (ctx->hdrs[Context->v2r[i]]->tagged) 1443 changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx); 1444 } 1445 else 1446 changed = link_threads (cur, last, ctx); 1447 1448 return changed; 1449}