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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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)
43 /* Determines whether to display a message's subject. */
44 static int need_display_subject (CONTEXT *ctx, HEADER *hdr)
46 THREAD *tmp, *tree = hdr->thread;
48 /* if the user disabled subject hiding, display it */
49 if (!option (OPTHIDETHREADSUBJECT))
52 /* if our subject is different from our parent's, display it */
53 if (hdr->subject_changed)
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)
61 if (hdr && VISIBLE (hdr, ctx))
63 if (hdr->subject_changed)
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)
77 if (VISIBLE (hdr, ctx))
79 else if (hdr->subject_changed)
84 /* if we have no visible parent or previous sibling, display the subject */
88 static void linearize_tree (CONTEXT *ctx)
90 THREAD *tree = ctx->tree;
91 HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
95 while (!tree->message)
98 *array = tree->message;
99 array += Sort & SORT_REVERSE ? -1 : 1;
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.
126 static void calculate_visibility (CONTEXT *ctx, int *max_depth)
128 THREAD *tmp, *tree = ctx->tree;
129 int hide_top_missing = option (OPTHIDETOPMISSING) && !option (OPTHIDEMISSING);
130 int hide_top_limited = option (OPTHIDETOPLIMITED) && !option (OPTHIDELIMITED);
133 /* we walk each level backwards to make it easier to compute next_subtree_visible */
140 if (depth > *max_depth)
143 tree->subtree_visible = 0;
146 FREE (&tree->message->tree);
147 if (VISIBLE (tree->message, ctx))
151 tree->message->display_subject = need_display_subject (ctx, tree->message);
152 for (tmp = tree; tmp; tmp = tmp->parent)
154 if (tmp->subtree_visible)
157 tmp->subtree_visible = 2;
161 tmp->subtree_visible = 1;
167 tree->deep = !option (OPTHIDELIMITED);
173 tree->deep = !option (OPTHIDEMISSING);
175 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible
176 || tree->next->subtree_visible);
188 while (tree && !tree->prev)
200 /* now fix up for the OPTHIDETOP* options if necessary */
201 if (hide_top_limited || hide_top_missing)
206 if (!tree->visible && tree->deep && tree->subtree_visible < 2
207 && ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
209 if (!tree->deep && tree->child && tree->subtree_visible)
215 while (tree && !tree->next)
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 M_TREE_* defined in mutt.h.
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
234 void mutt_draw_tree (CONTEXT *ctx)
236 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree;
237 char corner = (Sort & SORT_REVERSE) ? M_TREE_ULCORNER : M_TREE_LLCORNER;
238 char vtee = (Sort & SORT_REVERSE) ? M_TREE_BTEE : M_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;
242 /* Do the visibility calculations and free the old thread chars.
243 * From now on we can simply ignore invisible subtrees
245 calculate_visibility (ctx, &max_depth);
246 pfx = safe_malloc (width * max_depth + 2);
247 arrow = safe_malloc (width * max_depth + 2);
252 myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width;
253 if (depth && start_depth == depth)
254 myarrow[0] = nextdisp ? M_TREE_LTEE : corner;
255 else if (parent->message && !option (OPTHIDELIMITED))
256 myarrow[0] = M_TREE_HIDDEN;
257 else if (!parent->message && !option (OPTHIDEMISSING))
258 myarrow[0] = M_TREE_MISSING;
262 myarrow[1] = pseudo ? M_TREE_STAR
263 : (tree->duplicate_thread ? M_TREE_EQUALS : M_TREE_HLINE);
266 myarrow[width] = M_TREE_RARROW;
267 myarrow[width + 1] = 0;
268 new_tree = safe_malloc ((2 + depth * width));
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);
276 strfcpy (new_tree, arrow, 2 + depth * width);
277 tree->message->tree = new_tree;
280 if (tree->child && depth)
282 mypfx = pfx + (depth - 1) * width;
283 mypfx[0] = nextdisp ? M_TREE_VLINE : M_TREE_SPACE;
285 mypfx[1] = M_TREE_SPACE;
292 if (tree->child && tree->subtree_visible)
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)
308 while (!tree->next && tree->parent)
312 if (tree == nextdisp)
319 if (start_depth == depth)
326 if (tree == nextdisp)
334 if (!pseudo && tree->fake_thread)
336 if (!nextdisp && tree->next_subtree_visible)
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. */
350 static LIST *make_subject_list (THREAD *cur, time_t *dateptr)
355 LIST *curlist, *oldlist, *newlist, *subjects = NULL;
360 while (!cur->message)
365 thisdate = option (OPTTHREADRECEIVED)
366 ? cur->message->received : cur->message->date_sent;
367 if (!*dateptr || thisdate < *dateptr)
371 env = cur->message->env;
372 if (env->real_subj &&
373 ((env->real_subj != env->subject) || (!option (OPTSORTRE))))
375 for (curlist = subjects, oldlist = NULL;
376 curlist; oldlist = curlist, curlist = curlist->next)
378 rc = mutt_strcmp (env->real_subj, curlist->data);
382 if (!curlist || rc > 0)
384 newlist = safe_calloc (1, sizeof (LIST));
385 newlist->data = env->real_subj;
388 newlist->next = oldlist->next;
389 oldlist->next = newlist;
393 newlist->next = subjects;
399 while (!cur->next && cur != start)
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.
415 static THREAD *find_subject (CONTEXT *ctx, THREAD *cur)
417 struct hash_elem *ptr;
418 THREAD *tmp, *last = NULL;
420 LIST *subjects = NULL, *oldlist;
423 subjects = make_subject_list (cur, &date);
427 hash = ctx->subj_hash->hash_string ((unsigned char *) subjects->data,
428 ctx->subj_hash->nelem);
429 for (ptr = ctx->subj_hash->table[hash]; ptr; ptr = ptr->next)
431 tmp = ((HEADER *) ptr->data)->thread;
432 if (tmp != cur && /* don't match the same message */
433 !tmp->fake_thread && /* don't match pseudo threads */
434 tmp->message->subject_changed && /* only match interesting replies */
435 !is_descendant (tmp, cur) && /* don't match in the same thread */
436 (date >= (option (OPTTHREADRECEIVED) ?
437 tmp->message->received :
438 tmp->message->date_sent)) &&
440 (option (OPTTHREADRECEIVED) ?
441 (last->message->received < tmp->message->received) :
442 (last->message->date_sent < tmp->message->date_sent))) &&
443 tmp->message->env->real_subj &&
444 mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
445 last = tmp; /* best match so far */
449 subjects = subjects->next;
455 /* remove cur and its descendants from their current location.
456 * also make sure ancestors of cur no longer are sorted by the
457 * fact that cur is their descendant. */
458 static void unlink_message (THREAD **old, THREAD *cur)
463 cur->prev->next = cur->next;
468 cur->next->prev = cur->prev;
472 for (tmp = cur->parent; tmp && tmp->sort_key == cur->sort_key;
474 tmp->sort_key = NULL;
478 /* add cur as a prior sibling of *new, with parent newparent */
479 static void insert_message (THREAD **new, THREAD *newparent, THREAD *cur)
484 cur->parent = newparent;
490 /* thread by subject things that didn't get threaded by message-id */
491 static void pseudo_threads (CONTEXT *ctx)
493 THREAD *tree = ctx->tree, *top = tree;
494 THREAD *tmp, *cur, *parent, *curchild, *nextchild;
497 ctx->subj_hash = mutt_make_subj_hash (ctx);
503 if ((parent = find_subject (ctx, cur)) != NULL)
505 cur->fake_thread = 1;
506 unlink_message (&top, cur);
507 insert_message (&parent->child, parent, cur);
508 parent->sort_children = 1;
512 while (!tmp->message)
515 /* if the message we're attaching has pseudo-children, they
516 * need to be attached to its parent, so move them up a level.
517 * but only do this if they have the same real subject as the
518 * parent, since otherwise they rightly belong to the message
519 * we're attaching. */
521 || !mutt_strcmp (tmp->message->env->real_subj,
522 parent->message->env->real_subj))
524 tmp->message->subject_changed = 0;
526 for (curchild = tmp->child; curchild; )
528 nextchild = curchild->next;
529 if (curchild->fake_thread)
531 unlink_message (&tmp->child, curchild);
532 insert_message (&parent->child, parent, curchild);
534 curchild = nextchild;
538 while (!tmp->next && tmp != cur)
552 void mutt_clear_threads (CONTEXT *ctx)
556 for (i = 0; i < ctx->msgcount; i++)
558 /* mailbox may have been only partially read */
561 ctx->hdrs[i]->thread = NULL;
562 ctx->hdrs[i]->threaded = 0;
567 if (ctx->thread_hash)
568 hash_destroy (&ctx->thread_hash, *free);
571 static int compare_threads (const void *a, const void *b)
573 static sort_t *sort_func = NULL;
576 return ((*sort_func) (&(*((THREAD **) a))->sort_key,
577 &(*((THREAD **) b))->sort_key));
578 /* a hack to let us reset sort_func even though we can't
579 * have extra arguments because of qsort
584 sort_func = mutt_get_sort_func (Sort);
585 return (sort_func ? 1 : 0);
589 THREAD *mutt_sort_subthreads (THREAD *thread, int init)
591 THREAD **array, *sort_key, *top, *tmp;
593 int i, array_size, sort_top = 0;
595 /* we put things into the array backwards to save some cycles,
596 * but we want to have to move less stuff around if we're
597 * resorting, so we sort backwards and then put them back
598 * in reverse order so they're forwards
600 Sort ^= SORT_REVERSE;
601 if (!compare_threads (NULL, NULL))
606 array = safe_calloc ((array_size = 256), sizeof (THREAD *));
609 if (init || !thread->sort_key)
611 thread->sort_key = NULL;
614 thread->parent->sort_children = 1;
621 thread = thread->child;
626 /* if it has no children, it must be real. sort it on its own merits */
627 thread->sort_key = thread->message;
631 thread = thread->next;
636 while (!thread->next)
638 /* if it has siblings and needs to be sorted, sort it... */
639 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
641 /* put them into the array */
642 for (i = 0; thread; i++, thread = thread->prev)
645 safe_realloc (&array, (array_size *= 2) * sizeof (THREAD *));
650 qsort ((void *) array, i, sizeof (THREAD *), *compare_threads);
652 /* attach them back together. make thread the last sibling. */
655 array[i - 1]->prev = NULL;
658 thread->parent->child = array[i - 1];
664 array[i - 1]->prev = array[i];
665 array[i]->next = array[i - 1];
672 thread = thread->parent;
674 if (!thread->sort_key || thread->sort_children)
676 /* make sort_key the first or last sibling, as appropriate */
677 sort_key = (!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? thread->child : tmp;
679 /* we just sorted its children */
680 thread->sort_children = 0;
682 oldsort_key = thread->sort_key;
683 thread->sort_key = thread->message;
685 if (Sort & SORT_LAST)
687 if (!thread->sort_key
688 || ((((Sort & SORT_REVERSE) ? 1 : -1)
689 * compare_threads ((void *) &thread,
692 thread->sort_key = sort_key->sort_key;
694 else if (!thread->sort_key)
695 thread->sort_key = sort_key->sort_key;
697 /* if its sort_key has changed, we need to resort it and siblings */
698 if (oldsort_key != thread->sort_key)
701 thread->parent->sort_children = 1;
709 Sort ^= SORT_REVERSE;
715 thread = thread->next;
719 static void check_subjects (CONTEXT *ctx, int init)
725 for (i = 0; i < ctx->msgcount; i++)
728 if (cur->thread->check_subject)
729 cur->thread->check_subject = 0;
733 /* figure out which messages have subjects different than their parents' */
734 tmp = cur->thread->parent;
735 while (tmp && !tmp->message)
741 cur->subject_changed = 1;
742 else if (cur->env->real_subj && tmp->message->env->real_subj)
743 cur->subject_changed = mutt_strcmp (cur->env->real_subj,
744 tmp->message->env->real_subj) ? 1 : 0;
746 cur->subject_changed = (cur->env->real_subj
747 || tmp->message->env->real_subj) ? 1 : 0;
751 void mutt_sort_threads (CONTEXT *ctx, int init)
754 int i, oldsort, using_refs = 0;
755 THREAD *thread, *new, *tmp, top;
758 /* set Sort to the secondary method to support the set sort_aux=reverse-*
759 * settings. The sorting functions just look at the value of
765 if (!ctx->thread_hash)
769 ctx->thread_hash = hash_create (ctx->msgcount * 2, 0);
771 /* we want a quick way to see if things are actually attached to the top of the
772 * thread tree or if they're just dangling, so we attach everything to a top
773 * node temporarily */
774 top.parent = top.next = top.prev = NULL;
775 top.child = ctx->tree;
776 for (thread = ctx->tree; thread; thread = thread->next)
777 thread->parent = ⊤
779 /* put each new message together with the matching messageless THREAD if it
780 * exists. otherwise, if there is a THREAD that already has a message, thread
781 * new message as an identical child. if we didn't attach the message to a
782 * THREAD, make a new one for it. */
783 for (i = 0; i < ctx->msgcount; i++)
789 if ((!init || option (OPTDUPTHREADS)) && cur->env->message_id)
790 thread = hash_find (ctx->thread_hash, cur->env->message_id);
794 if (thread && !thread->message)
796 /* this is a message which was missing before */
797 thread->message = cur;
798 cur->thread = thread;
799 thread->check_subject = 1;
801 /* mark descendants as needing subject_changed checked */
802 for (tmp = (thread->child ? thread->child : thread); tmp != thread; )
804 while (!tmp->message)
806 tmp->check_subject = 1;
807 while (!tmp->next && tmp != thread)
815 /* remove threading info above it based on its children, which we'll
816 * recalculate based on its headers. make sure not to leave
817 * dangling missing messages. note that we haven't kept track
818 * of what info came from its children and what from its siblings'
819 * children, so we just remove the stuff that's definitely from it */
822 tmp = thread->parent;
823 unlink_message (&tmp->child, thread);
824 thread->parent = NULL;
825 thread->sort_key = NULL;
826 thread->fake_thread = 0;
828 } while (thread != &top && !thread->child && !thread->message);
833 new = (option (OPTDUPTHREADS) ? thread : NULL);
835 thread = safe_calloc (1, sizeof (THREAD));
836 thread->message = cur;
837 thread->check_subject = 1;
838 cur->thread = thread;
839 hash_insert (ctx->thread_hash,
840 cur->env->message_id ? cur->env->message_id : "",
845 if (new->duplicate_thread)
848 thread = cur->thread;
850 insert_message (&new->child, new, thread);
851 thread->duplicate_thread = 1;
852 thread->message->threaded = 1;
858 /* unlink pseudo-threads because they might be children of newly
859 * arrived messages */
860 thread = cur->thread;
861 for (new = thread->child; new; )
864 if (new->fake_thread)
866 unlink_message (&thread->child, new);
867 insert_message (&top.child, &top, new);
868 new->fake_thread = 0;
875 /* thread by references */
876 for (i = 0; i < ctx->msgcount; i++)
883 thread = cur->thread;
890 /* look at the beginning of in-reply-to: */
891 if ((ref = cur->env->in_reply_to) != NULL)
895 ref = cur->env->references;
899 else if (using_refs == 1)
901 /* if there's no references header, use all the in-reply-to:
902 * data that we have. otherwise, use the first reference
903 * if it's different than the first in-reply-to, otherwise use
904 * the second reference (since at least eudora puts the most
905 * recent reference in in-reply-to and the rest in references)
907 if (!cur->env->references)
911 if (mutt_strcmp (ref->data, cur->env->references->data))
912 ref = cur->env->references;
914 ref = cur->env->references->next;
920 ref = ref->next; /* go on with references */
925 if ((new = hash_find (ctx->thread_hash, ref->data)) == NULL)
927 new = safe_calloc (1, sizeof (THREAD));
928 hash_insert (ctx->thread_hash, ref->data, new, 1);
932 if (new->duplicate_thread)
934 if (is_descendant (new, thread)) /* no loops! */
939 unlink_message (&top.child, thread);
940 insert_message (&new->child, new, thread);
942 if (thread->message || (thread->parent && thread->parent != &top))
947 insert_message (&top.child, &top, thread);
950 /* detach everything from the temporary top node */
951 for (thread = top.child; thread; thread = thread->next)
953 thread->parent = NULL;
955 ctx->tree = top.child;
957 check_subjects (ctx, init);
959 if (!option (OPTSTRICTTHREADS))
960 pseudo_threads (ctx);
964 ctx->tree = mutt_sort_subthreads (ctx->tree, init);
966 /* restore the oldsort order. */
969 /* Put the list into an array. */
970 linearize_tree (ctx);
972 /* Draw the thread tree. */
973 mutt_draw_tree (ctx);
977 static HEADER *find_virtual (THREAD *cur, int reverse)
981 if (cur->message && cur->message->virtual >= 0)
982 return (cur->message);
985 if ((cur = cur->child) == NULL)
988 while (reverse && cur->next)
993 if (cur->message && cur->message->virtual >= 0)
994 return (cur->message);
1000 while (reverse && cur->next)
1003 else if (reverse ? cur->prev : cur->next)
1004 cur = reverse ? cur->prev : cur->next;
1007 while (!(reverse ? cur->prev : cur->next))
1013 cur = reverse ? cur->prev : cur->next;
1019 /* dir => true when moving forward, false when moving in reverse
1020 * subthreads => false when moving to next thread, true when moving to next subthread
1022 int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
1027 if ((Sort & SORT_MASK) != SORT_THREADS)
1029 mutt_error _("Threading is not enabled.");
1030 return (hdr->virtual);
1042 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1044 while (!cur->next && cur->parent)
1049 while (!cur->prev && cur->parent)
1054 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1061 tmp = find_virtual (cur, 0);
1071 tmp = find_virtual (cur, 1);
1075 return (tmp->virtual);
1078 int mutt_parent_message (CONTEXT *ctx, HEADER *hdr)
1082 if ((Sort & SORT_MASK) != SORT_THREADS)
1084 mutt_error _("Threading is not enabled.");
1085 return (hdr->virtual);
1088 for (thread = hdr->thread->parent; thread; thread = thread->parent)
1090 if ((hdr = thread->message) != NULL)
1092 if (VISIBLE (hdr, ctx))
1093 return (hdr->virtual);
1096 mutt_error _("Parent message is not visible in this limited view.");
1102 mutt_error _("Parent message is not available.");
1106 void mutt_set_virtual (CONTEXT *ctx)
1114 for (i = 0; i < ctx->msgcount; i++)
1117 if (cur->virtual >= 0)
1119 cur->virtual = ctx->vcount;
1120 ctx->v2r[ctx->vcount] = i;
1122 ctx->vsize += cur->content->length + cur->content->offset - cur->content->hdr_offset;
1123 cur->num_hidden = mutt_get_hidden (ctx, cur);
1128 int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
1130 THREAD *thread, *top;
1131 HEADER *roothdr = NULL;
1132 int final, reverse = (Sort & SORT_REVERSE), minmsgno;
1133 int num_hidden = 0, new = 0, old = 0;
1134 int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
1135 #define CHECK_LIMIT (!ctx->pattern || cur->limited)
1137 if ((Sort & SORT_MASK) != SORT_THREADS && !(flag & M_THREAD_GET_HIDDEN))
1139 mutt_error (_("Threading is not enabled."));
1140 return (cur->virtual);
1143 final = cur->virtual;
1144 thread = cur->thread;
1145 while (thread->parent)
1146 thread = thread->parent;
1148 while (!thread->message)
1149 thread = thread->child;
1150 cur = thread->message;
1151 minmsgno = cur->msgno;
1153 if (!cur->read && CHECK_LIMIT)
1159 if (cur->msgno < min_unread_msgno)
1161 min_unread = cur->virtual;
1162 min_unread_msgno = cur->msgno;
1166 if (cur->virtual == -1 && CHECK_LIMIT)
1169 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1171 cur->pair = 0; /* force index entry's color to be re-evaluated */
1172 cur->collapsed = flag & M_THREAD_COLLAPSE;
1173 if (cur->virtual != -1)
1176 if (flag & M_THREAD_COLLAPSE)
1177 final = roothdr->virtual;
1181 if (thread == top && (thread = thread->child) == NULL)
1183 /* return value depends on action requested */
1184 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1186 else if (flag & M_THREAD_UNREAD)
1187 return ((old && new) ? new : (old ? old : new));
1188 else if (flag & M_THREAD_GET_HIDDEN)
1189 return (num_hidden);
1190 else if (flag & M_THREAD_NEXT_UNREAD)
1191 return (min_unread);
1196 cur = thread->message;
1200 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1202 cur->pair = 0; /* force index entry's color to be re-evaluated */
1203 cur->collapsed = flag & M_THREAD_COLLAPSE;
1204 if (!roothdr && CHECK_LIMIT)
1207 if (flag & M_THREAD_COLLAPSE)
1208 final = roothdr->virtual;
1211 if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
1213 minmsgno = cur->msgno;
1214 final = cur->virtual;
1217 if (flag & M_THREAD_COLLAPSE)
1225 cur->virtual = cur->msgno;
1230 if (!cur->read && CHECK_LIMIT)
1236 if (cur->msgno < min_unread_msgno)
1238 min_unread = cur->virtual;
1239 min_unread_msgno = cur->msgno;
1243 if (cur->virtual == -1 && CHECK_LIMIT)
1248 thread = thread->child;
1249 else if (thread->next)
1250 thread = thread->next;
1254 while (!thread->next)
1256 thread = thread->parent;
1265 thread = thread->next;
1269 /* return value depends on action requested */
1270 if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE))
1272 else if (flag & M_THREAD_UNREAD)
1273 return ((old && new) ? new : (old ? old : new));
1274 else if (flag & M_THREAD_GET_HIDDEN)
1275 return (num_hidden+1);
1276 else if (flag & M_THREAD_NEXT_UNREAD)
1277 return (min_unread);
1284 /* if flag is 0, we want to know how many messages
1285 * are in the thread. if flag is 1, we want to know
1286 * our position in the thread. */
1287 int mutt_messages_in_thread (CONTEXT *ctx, HEADER *hdr, int flag)
1292 if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread)
1295 threads[0] = hdr->thread;
1296 while (threads[0]->parent)
1297 threads[0] = threads[0]->parent;
1299 threads[1] = flag ? hdr->thread : threads[0]->next;
1301 for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
1303 while (!threads[i]->message)
1304 threads[i] = threads[i]->child;
1307 if (Sort & SORT_REVERSE)
1308 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1310 rc = (threads[1] ? threads[1]->message->msgno : ctx->msgcount) - threads[0]->message->msgno;
1319 HASH *mutt_make_id_hash (CONTEXT *ctx)
1325 hash = hash_create (ctx->msgcount * 2, 0);
1327 for (i = 0; i < ctx->msgcount; i++)
1330 if (hdr->env->message_id)
1331 hash_insert (hash, hdr->env->message_id, hdr, 0);
1337 HASH *mutt_make_subj_hash (CONTEXT *ctx)
1343 hash = hash_create (ctx->msgcount * 2, 0);
1345 for (i = 0; i < ctx->msgcount; i++)
1348 if (hdr->env->real_subj)
1349 hash_insert (hash, hdr->env->real_subj, hdr, 1);
1355 static void clean_references (THREAD *brk, THREAD *cur)
1361 for (; cur; cur = cur->next, done = 0)
1363 /* parse subthread recursively */
1364 clean_references (brk, cur->child);
1367 break; /* skip pseudo-message */
1369 /* Looking for the first bad reference according to the new threading.
1370 * Optimal since Mutt stores the references in reverse order, and the
1371 * first loop should match immediatly for mails respecting RFC2822. */
1372 for (p = brk; !done && p; p = p->parent)
1373 for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
1374 if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
1382 HEADER *h = cur->message;
1384 /* clearing the References: header from obsolete Message-ID(s) */
1385 mutt_free_list (&ref->next);
1387 h->env->refs_changed = h->changed = 1;
1392 void mutt_break_thread (HEADER *hdr)
1394 mutt_free_list (&hdr->env->in_reply_to);
1395 mutt_free_list (&hdr->env->references);
1396 hdr->env->irt_changed = hdr->env->refs_changed = hdr->changed = 1;
1398 clean_references (hdr->thread, hdr->thread->child);
1401 static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
1403 if (child == parent)
1406 mutt_break_thread (child);
1408 child->env->in_reply_to = mutt_new_list ();
1409 child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
1411 mutt_set_flag (ctx, child, M_TAG, 0);
1413 child->env->irt_changed = child->changed = 1;
1417 int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
1423 for (i = 0; i < ctx->vcount; i++)
1424 if (ctx->hdrs[Context->v2r[i]]->tagged)
1425 changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
1428 changed = link_threads (cur, last, ctx);