mutt stable branch with some hacks
at master 1440 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 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, 0); 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, 1); 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, 1); 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, 0); 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, 0); 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, 1); 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}