mutt stable branch with some hacks
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 = ⊤
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}