2 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
29 #define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited)))
31 /* determine whether a is a descendant of b */
32 static int is_descendant (THREAD * a, THREAD * b)
42 /* Determines whether to display a message's subject. */
43 static int need_display_subject (CONTEXT * ctx, HEADER * hdr)
45 THREAD *tmp, *tree = hdr->thread;
47 /* if the user disabled subject hiding, display it */
48 if (!option (OPTHIDETHREADSUBJECT))
51 /* if our subject is different from our parent's, display it */
52 if (hdr->subject_changed)
55 /* if our subject is different from that of our closest previously displayed
56 * sibling, display the subject */
57 for (tmp = tree->prev; tmp; tmp = tmp->prev) {
59 if (hdr && VISIBLE (hdr, ctx)) {
60 if (hdr->subject_changed)
67 /* if there is a parent-to-child subject change anywhere between us and our
68 * closest displayed ancestor, display the subject */
69 for (tmp = tree->parent; tmp; tmp = tmp->parent) {
72 if (VISIBLE (hdr, ctx))
74 else if (hdr->subject_changed)
79 /* if we have no visible parent or previous sibling, display the subject */
83 static void linearize_tree (CONTEXT * ctx)
85 THREAD *tree = ctx->tree;
86 HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
89 while (!tree->message)
92 *array = tree->message;
93 array += Sort & SORT_REVERSE ? -1 : 1;
110 /* this calculates whether a node is the root of a subtree that has visible
111 * nodes, whether a node itself is visible, whether, if invisible, it has
112 * depth anyway, and whether any of its later siblings are roots of visible
113 * subtrees. while it's at it, it frees the old thread display, so we can
114 * skip parts of the tree in mutt_draw_tree() if we've decided here that we
115 * don't care about them any more.
117 static void calculate_visibility (CONTEXT * ctx, int *max_depth)
119 THREAD *tmp, *tree = ctx->tree;
120 int hide_top_missing = option (OPTHIDETOPMISSING)
121 && !option (OPTHIDEMISSING);
122 int hide_top_limited = option (OPTHIDETOPLIMITED)
123 && !option (OPTHIDELIMITED);
126 /* we walk each level backwards to make it easier to compute next_subtree_visible */
132 if (depth > *max_depth)
135 tree->subtree_visible = 0;
137 FREE (&tree->message->tree);
138 if (VISIBLE (tree->message, ctx)) {
141 tree->message->display_subject =
142 need_display_subject (ctx, tree->message);
143 for (tmp = tree; tmp; tmp = tmp->parent) {
144 if (tmp->subtree_visible) {
146 tmp->subtree_visible = 2;
150 tmp->subtree_visible = 1;
155 tree->deep = !option (OPTHIDELIMITED);
160 tree->deep = !option (OPTHIDEMISSING);
162 tree->next_subtree_visible = tree->next
163 && (tree->next->next_subtree_visible || tree->next->subtree_visible);
173 while (tree && !tree->prev) {
184 /* now fix up for the OPTHIDETOP* options if necessary */
185 if (hide_top_limited || hide_top_missing) {
188 if (!tree->visible && tree->deep && tree->subtree_visible < 2
189 && ((tree->message && hide_top_limited)
190 || (!tree->message && hide_top_missing)))
192 if (!tree->deep && tree->child && tree->subtree_visible)
197 while (tree && !tree->next)
208 /* Since the graphics characters have a value >255, I have to resort to
209 * using escape sequences to pass the information to print_enriched_string().
210 * These are the macros M_TREE_* defined in mutt.h.
212 * ncurses should automatically use the default ASCII characters instead of
213 * graphics chars on terminals which don't support them (see the man page
216 void mutt_draw_tree (CONTEXT * ctx)
218 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree;
219 char corner = (Sort & SORT_REVERSE) ? M_TREE_ULCORNER : M_TREE_LLCORNER;
220 char vtee = (Sort & SORT_REVERSE) ? M_TREE_BTEE : M_TREE_TTEE;
221 int depth = 0, start_depth = 0, max_depth = 0, width =
222 option (OPTNARROWTREE) ? 1 : 2;
223 THREAD *nextdisp = NULL, *pseudo = NULL, *parent = NULL, *tree = ctx->tree;
225 /* Do the visibility calculations and free the old thread chars.
226 * From now on we can simply ignore invisible subtrees
228 calculate_visibility (ctx, &max_depth);
229 pfx = safe_malloc (width * max_depth + 2);
230 arrow = safe_malloc (width * max_depth + 2);
233 myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width;
234 if (depth && start_depth == depth)
235 myarrow[0] = nextdisp ? M_TREE_LTEE : corner;
236 else if (parent->message && !option (OPTHIDELIMITED))
237 myarrow[0] = M_TREE_HIDDEN;
238 else if (!parent->message && !option (OPTHIDEMISSING))
239 myarrow[0] = M_TREE_MISSING;
243 myarrow[1] = pseudo ? M_TREE_STAR
244 : (tree->duplicate_thread ? M_TREE_EQUALS : M_TREE_HLINE);
246 myarrow[width] = M_TREE_RARROW;
247 myarrow[width + 1] = 0;
248 new_tree = safe_malloc ((2 + depth * width));
249 if (start_depth > 1) {
250 strncpy (new_tree, pfx, (start_depth - 1) * width);
251 strfcpy (new_tree + (start_depth - 1) * width,
252 arrow, (1 + depth - start_depth) * width + 2);
255 strfcpy (new_tree, arrow, 2 + depth * width);
256 tree->message->tree = new_tree;
259 if (tree->child && depth) {
260 mypfx = pfx + (depth - 1) * width;
261 mypfx[0] = nextdisp ? M_TREE_VLINE : M_TREE_SPACE;
263 mypfx[1] = M_TREE_SPACE;
269 if (tree->child && tree->subtree_visible) {
276 /* we do this here because we need to make sure that the first child thread
277 * of the old tree that we deal with is actually displayed if any are,
278 * or we might set the parent variable wrong while going through it. */
279 while (!tree->subtree_visible && tree->next)
283 while (!tree->next && tree->parent) {
286 if (tree == nextdisp)
292 if (start_depth == depth)
299 if (tree == nextdisp)
307 if (!pseudo && tree->fake_thread)
309 if (!nextdisp && tree->next_subtree_visible)
319 /* since we may be trying to attach as a pseudo-thread a THREAD that
320 * has no message, we have to make a list of all the subjects of its
321 * most immediate existing descendants. we also note the earliest
322 * date on any of the parents and put it in *dateptr. */
323 static LIST *make_subject_list (THREAD * cur, time_t * dateptr)
328 LIST *curlist, *oldlist, *newlist, *subjects = NULL;
332 while (!cur->message)
336 thisdate = option (OPTTHREADRECEIVED)
337 ? cur->message->received : cur->message->date_sent;
338 if (!*dateptr || thisdate < *dateptr)
342 env = cur->message->env;
343 if (env->real_subj &&
344 ((env->real_subj != env->subject) || (!option (OPTSORTRE)))) {
345 for (curlist = subjects, oldlist = NULL;
346 curlist; oldlist = curlist, curlist = curlist->next) {
347 rc = mutt_strcmp (env->real_subj, curlist->data);
351 if (!curlist || rc > 0) {
352 newlist = safe_calloc (1, sizeof (LIST));
353 newlist->data = env->real_subj;
355 newlist->next = oldlist->next;
356 oldlist->next = newlist;
359 newlist->next = subjects;
365 while (!cur->next && cur != start) {
376 /* find the best possible match for a parent mesage based upon subject.
377 * if there are multiple matches, the one which was sent the latest, but
378 * before the current message, is used.
380 static THREAD *find_subject (CONTEXT * ctx, THREAD * cur)
382 struct hash_elem *ptr;
383 THREAD *tmp, *last = NULL;
385 LIST *subjects = NULL, *oldlist;
388 subjects = make_subject_list (cur, &date);
391 hash = hash_string ((unsigned char *) subjects->data,
392 ctx->subj_hash->nelem);
393 for (ptr = ctx->subj_hash->table[hash]; ptr; ptr = ptr->next) {
394 tmp = ((HEADER *) ptr->data)->thread;
395 if (tmp != cur && /* don't match the same message */
396 !tmp->fake_thread && /* don't match pseudo threads */
397 tmp->message->subject_changed && /* only match interesting replies */
398 !is_descendant (tmp, cur) && /* don't match in the same thread */
399 (date >= (option (OPTTHREADRECEIVED) ?
400 tmp->message->received :
401 tmp->message->date_sent)) &&
403 (option (OPTTHREADRECEIVED) ?
404 (last->message->received < tmp->message->received) :
405 (last->message->date_sent < tmp->message->date_sent))) &&
406 tmp->message->env->real_subj &&
407 mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
408 last = tmp; /* best match so far */
412 subjects = subjects->next;
418 /* remove cur and its descendants from their current location.
419 * also make sure ancestors of cur no longer are sorted by the
420 * fact that cur is their descendant. */
421 static void unlink_message (THREAD ** old, THREAD * cur)
426 cur->prev->next = cur->next;
431 cur->next->prev = cur->prev;
434 for (tmp = cur->parent; tmp && tmp->sort_key == cur->sort_key;
436 tmp->sort_key = NULL;
440 /* add cur as a prior sibling of *new, with parent newparent */
441 static void insert_message (THREAD ** new, THREAD * newparent, THREAD * cur)
446 cur->parent = newparent;
452 /* thread by subject things that didn't get threaded by message-id */
453 static void pseudo_threads (CONTEXT * ctx)
455 THREAD *tree = ctx->tree, *top = tree;
456 THREAD *tmp, *cur, *parent, *curchild, *nextchild;
459 ctx->subj_hash = mutt_make_subj_hash (ctx);
464 if ((parent = find_subject (ctx, cur)) != NULL) {
465 cur->fake_thread = 1;
466 unlink_message (&top, cur);
467 insert_message (&parent->child, parent, cur);
468 parent->sort_children = 1;
471 while (!tmp->message)
474 /* if the message we're attaching has pseudo-children, they
475 * need to be attached to its parent, so move them up a level.
476 * but only do this if they have the same real subject as the
477 * parent, since otherwise they rightly belong to the message
478 * we're attaching. */
480 || !mutt_strcmp (tmp->message->env->real_subj,
481 parent->message->env->real_subj)) {
482 tmp->message->subject_changed = 0;
484 for (curchild = tmp->child; curchild;) {
485 nextchild = curchild->next;
486 if (curchild->fake_thread) {
487 unlink_message (&tmp->child, curchild);
488 insert_message (&parent->child, parent, curchild);
490 curchild = nextchild;
494 while (!tmp->next && tmp != cur) {
507 void mutt_clear_threads (CONTEXT * ctx)
511 for (i = 0; i < ctx->msgcount; i++) {
512 ctx->hdrs[i]->thread = NULL;
513 ctx->hdrs[i]->threaded = 0;
517 if (ctx->thread_hash)
518 hash_destroy (&ctx->thread_hash, free);
521 int compare_threads (const void *a, const void *b)
523 static sort_t *sort_func = NULL;
526 return ((*sort_func) (&(*((THREAD **) a))->sort_key,
527 &(*((THREAD **) b))->sort_key));
528 /* a hack to let us reset sort_func even though we can't
529 * have extra arguments because of qsort
533 sort_func = mutt_get_sort_func (Sort);
534 return (sort_func ? 1 : 0);
538 THREAD *mutt_sort_subthreads (THREAD * thread, int init)
540 THREAD **array, *sort_key, *top, *tmp;
542 int i, array_size, sort_top = 0;
544 /* we put things into the array backwards to save some cycles,
545 * but we want to have to move less stuff around if we're
546 * resorting, so we sort backwards and then put them back
547 * in reverse order so they're forwards
549 Sort ^= SORT_REVERSE;
550 if (!compare_threads (NULL, NULL))
555 array = safe_calloc ((array_size = 256), sizeof (THREAD *));
557 if (init || !thread->sort_key) {
558 thread->sort_key = NULL;
561 thread->parent->sort_children = 1;
567 thread = thread->child;
571 /* if it has no children, it must be real. sort it on its own merits */
572 thread->sort_key = thread->message;
575 thread = thread->next;
580 while (!thread->next) {
581 /* if it has siblings and needs to be sorted, sort it... */
583 && (thread->parent ? thread->parent->sort_children : sort_top)) {
584 /* put them into the array */
585 for (i = 0; thread; i++, thread = thread->prev) {
587 safe_realloc (&array, (array_size *= 2) * sizeof (THREAD *));
592 qsort ((void *) array, i, sizeof (THREAD *), compare_threads);
594 /* attach them back together. make thread the last sibling. */
597 array[i - 1]->prev = NULL;
600 thread->parent->child = array[i - 1];
605 array[i - 1]->prev = array[i];
606 array[i]->next = array[i - 1];
610 if (thread->parent) {
612 thread = thread->parent;
614 if (!thread->sort_key || thread->sort_children) {
615 /* make sort_key the first or last sibling, as appropriate */
617 (!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? thread->
620 /* we just sorted its children */
621 thread->sort_children = 0;
623 oldsort_key = thread->sort_key;
624 thread->sort_key = thread->message;
626 if (Sort & SORT_LAST) {
627 if (!thread->sort_key || ((((Sort & SORT_REVERSE) ? 1 : -1)
628 * compare_threads ((void *) &thread,
631 thread->sort_key = sort_key->sort_key;
633 else if (!thread->sort_key)
634 thread->sort_key = sort_key->sort_key;
636 /* if its sort_key has changed, we need to resort it and siblings */
637 if (oldsort_key != thread->sort_key) {
639 thread->parent->sort_children = 1;
646 Sort ^= SORT_REVERSE;
652 thread = thread->next;
656 static void check_subjects (CONTEXT * ctx, int init)
662 for (i = 0; i < ctx->msgcount; i++) {
664 if (cur->thread->check_subject)
665 cur->thread->check_subject = 0;
669 /* figure out which messages have subjects different than their parents' */
670 tmp = cur->thread->parent;
671 while (tmp && !tmp->message) {
676 cur->subject_changed = 1;
677 else if (cur->env->real_subj && tmp->message->env->real_subj)
678 cur->subject_changed = mutt_strcmp (cur->env->real_subj,
682 cur->subject_changed = (cur->env->real_subj
683 || tmp->message->env->real_subj) ? 1 : 0;
687 void mutt_sort_threads (CONTEXT * ctx, int init)
690 int i, oldsort, using_refs = 0;
691 THREAD *thread, *new, *tmp, top;
694 /* set Sort to the secondary method to support the set sort_aux=reverse-*
695 * settings. The sorting functions just look at the value of
701 if (!ctx->thread_hash)
705 ctx->thread_hash = hash_create (ctx->msgcount * 2);
707 /* we want a quick way to see if things are actually attached to the top of the
708 * thread tree or if they're just dangling, so we attach everything to a top
709 * node temporarily */
710 top.parent = top.next = top.prev = NULL;
711 top.child = ctx->tree;
712 for (thread = ctx->tree; thread; thread = thread->next)
713 thread->parent = ⊤
715 /* put each new message together with the matching messageless THREAD if it
716 * exists. otherwise, if there is a THREAD that already has a message, thread
717 * new message as an identical child. if we didn't attach the message to a
718 * THREAD, make a new one for it. */
719 for (i = 0; i < ctx->msgcount; i++) {
723 if ((!init || option (OPTDUPTHREADS)) && cur->env->message_id)
724 thread = hash_find (ctx->thread_hash, cur->env->message_id);
728 if (thread && !thread->message) {
729 /* this is a message which was missing before */
730 thread->message = cur;
731 cur->thread = thread;
732 thread->check_subject = 1;
734 /* mark descendants as needing subject_changed checked */
735 for (tmp = (thread->child ? thread->child : thread); tmp != thread;) {
736 while (!tmp->message)
738 tmp->check_subject = 1;
739 while (!tmp->next && tmp != thread)
745 if (thread->parent) {
746 /* remove threading info above it based on its children, which we'll
747 * recalculate based on its headers. make sure not to leave
748 * dangling missing messages. note that we haven't kept track
749 * of what info came from its children and what from its siblings'
750 * children, so we just remove the stuff that's definitely from it */
752 tmp = thread->parent;
753 unlink_message (&tmp->child, thread);
754 thread->parent = NULL;
755 thread->sort_key = NULL;
756 thread->fake_thread = 0;
758 } while (thread != &top && !thread->child && !thread->message);
762 new = (option (OPTDUPTHREADS) ? thread : NULL);
764 thread = safe_calloc (1, sizeof (THREAD));
765 thread->message = cur;
766 thread->check_subject = 1;
767 cur->thread = thread;
768 hash_insert (ctx->thread_hash,
769 cur->env->message_id ? cur->env->message_id : "",
773 if (new->duplicate_thread)
776 thread = cur->thread;
778 insert_message (&new->child, new, thread);
779 thread->duplicate_thread = 1;
780 thread->message->threaded = 1;
785 /* unlink pseudo-threads because they might be children of newly
786 * arrived messages */
787 thread = cur->thread;
788 for (new = thread->child; new;) {
790 if (new->fake_thread) {
791 unlink_message (&thread->child, new);
792 insert_message (&top.child, &top, new);
793 new->fake_thread = 0;
800 /* thread by references */
801 for (i = 0; i < ctx->msgcount; i++) {
807 thread = cur->thread;
811 if (using_refs == 0) {
812 /* look at the beginning of in-reply-to: */
813 if ((ref = cur->env->in_reply_to) != NULL)
816 ref = cur->env->references;
820 else if (using_refs == 1) {
821 /* if there's no references header, use all the in-reply-to:
822 * data that we have. otherwise, use the first reference
823 * if it's different than the first in-reply-to, otherwise use
824 * the second reference (since at least eudora puts the most
825 * recent reference in in-reply-to and the rest in references)
827 if (!cur->env->references)
830 if (mutt_strcmp (ref->data, cur->env->references->data))
831 ref = cur->env->references;
833 ref = cur->env->references->next;
839 ref = ref->next; /* go on with references */
844 if ((new = hash_find (ctx->thread_hash, ref->data)) == NULL) {
845 new = safe_calloc (1, sizeof (THREAD));
846 hash_insert (ctx->thread_hash, ref->data, new, 1);
849 if (new->duplicate_thread)
851 if (is_descendant (new, thread)) /* no loops! */
856 unlink_message (&top.child, thread);
857 insert_message (&new->child, new, thread);
859 if (thread->message || (thread->parent && thread->parent != &top))
864 insert_message (&top.child, &top, thread);
867 /* detach everything from the temporary top node */
868 for (thread = top.child; thread; thread = thread->next) {
869 thread->parent = NULL;
871 ctx->tree = top.child;
873 check_subjects (ctx, init);
875 if (!option (OPTSTRICTTHREADS))
876 pseudo_threads (ctx);
879 ctx->tree = mutt_sort_subthreads (ctx->tree, init);
881 /* restore the oldsort order. */
884 /* Put the list into an array. */
885 linearize_tree (ctx);
887 /* Draw the thread tree. */
888 mutt_draw_tree (ctx);
892 static HEADER *find_virtual (THREAD * cur, int reverse)
896 if (cur->message && cur->message->virtual >= 0)
897 return (cur->message);
900 if ((cur = cur->child) == NULL)
903 while (reverse && cur->next)
907 if (cur->message && cur->message->virtual >= 0)
908 return (cur->message);
913 while (reverse && cur->next)
916 else if (reverse ? cur->prev : cur->next)
917 cur = reverse ? cur->prev : cur->next;
919 while (!(reverse ? cur->prev : cur->next)) {
924 cur = reverse ? cur->prev : cur->next;
930 int _mutt_aside_thread (HEADER * hdr, short dir, short subthreads)
935 if ((Sort & SORT_MASK) != SORT_THREADS) {
936 mutt_error _("Threading is not enabled.");
938 return (hdr->virtual);
948 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0)) {
949 while (!cur->next && cur->parent)
953 while (!cur->prev && cur->parent)
958 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0)) {
963 tmp = find_virtual (cur, 0);
971 tmp = find_virtual (cur, 1);
975 return (tmp->virtual);
978 int mutt_parent_message (CONTEXT * ctx, HEADER * hdr)
982 if ((Sort & SORT_MASK) != SORT_THREADS) {
983 mutt_error _("Threading is not enabled.");
985 return (hdr->virtual);
988 for (thread = hdr->thread->parent; thread; thread = thread->parent) {
989 if ((hdr = thread->message) != NULL) {
990 if (VISIBLE (hdr, ctx))
991 return (hdr->virtual);
993 mutt_error _("Parent message is not visible in this limited view.");
1000 mutt_error _("Parent message is not available.");
1005 void mutt_set_virtual (CONTEXT * ctx)
1013 for (i = 0; i < ctx->msgcount; i++) {
1015 if (cur->virtual >= 0) {
1016 cur->virtual = ctx->vcount;
1017 ctx->v2r[ctx->vcount] = i;
1020 cur->content->length + cur->content->offset -
1021 cur->content->hdr_offset;
1022 cur->num_hidden = mutt_get_hidden (ctx, cur);
1027 int _mutt_traverse_thread (CONTEXT * ctx, HEADER * cur, int flag)
1029 THREAD *thread, *top;
1030 HEADER *roothdr = NULL;
1031 int final, reverse = (Sort & SORT_REVERSE), minmsgno;
1032 int num_hidden = 0, new = 0, old = 0;
1033 int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
1035 #define CHECK_LIMIT (!ctx->pattern || cur->limited)
1037 if ((Sort & SORT_MASK) != SORT_THREADS && !(flag & M_THREAD_GET_HIDDEN)) {
1038 mutt_error (_("Threading is not enabled."));
1039 return (cur->virtual);
1042 final = cur->virtual;
1043 thread = cur->thread;
1044 while (thread->parent)
1045 thread = thread->parent;
1047 while (!thread->message)
1048 thread = thread->child;
1049 cur = thread->message;
1050 minmsgno = cur->msgno;
1052 if (!cur->read && CHECK_LIMIT) {
1057 if (cur->msgno < min_unread_msgno) {
1058 min_unread = cur->virtual;
1059 min_unread_msgno = cur->msgno;
1063 if (cur->virtual == -1 && CHECK_LIMIT)
1066 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE)) {
1067 cur->pair = 0; /* force index entry's color to be re-evaluated */
1068 cur->collapsed = flag & M_THREAD_COLLAPSE;
1069 if (cur->virtual != -1) {
1071 if (flag & M_THREAD_COLLAPSE)
1072 final = roothdr->virtual;
1076 if (thread == top && (thread = thread->child) == NULL) {
1077 /* return value depends on action requested */
1078 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1080 else if (flag & M_THREAD_UNREAD)
1081 return ((old && new) ? new : (old ? old : new));
1082 else if (flag & M_THREAD_GET_HIDDEN)
1083 return (num_hidden);
1084 else if (flag & M_THREAD_NEXT_UNREAD)
1085 return (min_unread);
1089 cur = thread->message;
1092 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE)) {
1093 cur->pair = 0; /* force index entry's color to be re-evaluated */
1094 cur->collapsed = flag & M_THREAD_COLLAPSE;
1095 if (!roothdr && CHECK_LIMIT) {
1097 if (flag & M_THREAD_COLLAPSE)
1098 final = roothdr->virtual;
1101 if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno)
1103 minmsgno = cur->msgno;
1104 final = cur->virtual;
1107 if (flag & M_THREAD_COLLAPSE) {
1113 cur->virtual = cur->msgno;
1118 if (!cur->read && CHECK_LIMIT) {
1123 if (cur->msgno < min_unread_msgno) {
1124 min_unread = cur->virtual;
1125 min_unread_msgno = cur->msgno;
1129 if (cur->virtual == -1 && CHECK_LIMIT)
1134 thread = thread->child;
1135 else if (thread->next)
1136 thread = thread->next;
1140 while (!thread->next) {
1141 thread = thread->parent;
1142 if (thread == top) {
1149 thread = thread->next;
1153 /* return value depends on action requested */
1154 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1156 else if (flag & M_THREAD_UNREAD)
1157 return ((old && new) ? new : (old ? old : new));
1158 else if (flag & M_THREAD_GET_HIDDEN)
1159 return (num_hidden + 1);
1160 else if (flag & M_THREAD_NEXT_UNREAD)
1161 return (min_unread);
1168 /* if flag is 0, we want to know how many messages
1169 * are in the thread. if flag is 1, we want to know
1170 * our position in the thread. */
1171 int mutt_messages_in_thread (CONTEXT * ctx, HEADER * hdr, int flag)
1176 if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread)
1179 threads[0] = hdr->thread;
1180 while (threads[0]->parent)
1181 threads[0] = threads[0]->parent;
1183 threads[1] = flag ? hdr->thread : threads[0]->next;
1185 for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++) {
1186 while (!threads[i]->message)
1187 threads[i] = threads[i]->child;
1190 if (Sort & SORT_REVERSE)
1192 threads[0]->message->msgno -
1193 (threads[1] ? threads[1]->message->msgno : -1);
1196 (threads[1] ? threads[1]->message->msgno : ctx->msgcount) -
1197 threads[0]->message->msgno;
1206 HASH *mutt_make_id_hash (CONTEXT * ctx)
1212 hash = hash_create (ctx->msgcount * 2);
1214 for (i = 0; i < ctx->msgcount; i++) {
1216 if (hdr->env->message_id)
1217 hash_insert (hash, hdr->env->message_id, hdr, 0);
1223 HASH *mutt_make_subj_hash (CONTEXT * ctx)
1229 hash = hash_create (ctx->msgcount * 2);
1231 for (i = 0; i < ctx->msgcount; i++) {
1233 if (hdr->env->real_subj)
1234 hash_insert (hash, hdr->env->real_subj, hdr, 1);
1240 static void clean_references (THREAD * brk, THREAD * cur)
1246 for (; cur; cur = cur->next, done = 0) {
1247 /* parse subthread recursively */
1248 clean_references (brk, cur->child);
1251 break; /* skip pseudo-message */
1253 /* Looking for the first bad reference according to the new threading.
1254 * Optimal since Mutt stores the references in reverse order, and the
1255 * first loop should match immediatly for mails respecting RFC2822. */
1256 for (p = brk; !done && p; p = p->parent)
1257 for (ref = cur->message->env->references; p->message && ref;
1259 if (!mutt_strcasecmp (ref->data, p->message->env->message_id)) {
1265 HEADER *h = cur->message;
1267 /* clearing the References: header from obsolete Message-Id(s) */
1268 mutt_free_list (&ref->next);
1271 mutt_free_list (&h->new_env->references);
1273 h->new_env = mutt_new_envelope ();
1275 h->new_env->references = mutt_copy_list (h->env->references);
1277 h->refs_changed = h->changed = 1;
1282 void mutt_break_thread (HEADER * hdr)
1284 mutt_free_list (&hdr->env->in_reply_to);
1285 mutt_free_list (&hdr->env->references);
1286 hdr->irt_changed = hdr->refs_changed = hdr->changed = 1;
1289 mutt_free_list (&hdr->new_env->in_reply_to);
1290 mutt_free_list (&hdr->new_env->references);
1293 hdr->new_env = mutt_new_envelope ();
1295 clean_references (hdr->thread, hdr->thread->child);
1298 static int link_threads (HEADER * parent, HEADER * child, CONTEXT * ctx)
1300 if (child == parent)
1303 mutt_break_thread (child);
1305 child->env->in_reply_to = mutt_new_list ();
1306 child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
1308 child->new_env->in_reply_to = mutt_new_list ();
1309 child->new_env->in_reply_to->data = safe_strdup (parent->env->message_id);
1311 mutt_set_flag (ctx, child, M_TAG, 0);
1313 child->irt_changed = child->changed = 1;
1317 int mutt_link_threads (HEADER * cur, HEADER * last, CONTEXT * ctx)
1322 for (i = 0; i < ctx->vcount; i++)
1323 if (ctx->hdrs[Context->v2r[i]]->tagged)
1324 changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
1327 changed = link_threads (cur, last, ctx);