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#include "mailbox.h"
26
27#include <string.h>
28#include <ctype.h>
29
30#define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited)))
31
32/* determine whether a is a descendant of b */
33static int is_descendant (THREAD *a, THREAD *b)
34{
35 while (a)
36 {
37 if (a == b)
38 return (1);
39 a = a->parent;
40 }
41 return (0);
42}
43
44/* Determines whether to display a message's subject. */
45static int need_display_subject (CONTEXT *ctx, HEADER *hdr)
46{
47 THREAD *tmp, *tree = hdr->thread;
48
49 /* if the user disabled subject hiding, display it */
50 if (!option (OPTHIDETHREADSUBJECT))
51 return (1);
52
53 /* if our subject is different from our parent's, display it */
54 if (hdr->subject_changed)
55 return (1);
56
57 /* if our subject is different from that of our closest previously displayed
58 * sibling, display the subject */
59 for (tmp = tree->prev; tmp; tmp = tmp->prev)
60 {
61 hdr = tmp->message;
62 if (hdr && VISIBLE (hdr, ctx))
63 {
64 if (hdr->subject_changed)
65 return (1);
66 else
67 break;
68 }
69 }
70
71 /* if there is a parent-to-child subject change anywhere between us and our
72 * closest displayed ancestor, display the subject */
73 for (tmp = tree->parent; tmp; tmp = tmp->parent)
74 {
75 hdr = tmp->message;
76 if (hdr)
77 {
78 if (VISIBLE (hdr, ctx))
79 return (0);
80 else if (hdr->subject_changed)
81 return (1);
82 }
83 }
84
85 /* if we have no visible parent or previous sibling, display the subject */
86 return (1);
87}
88
89static void linearize_tree (CONTEXT *ctx)
90{
91 THREAD *tree = ctx->tree;
92 HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
93
94 while (tree)
95 {
96 while (!tree->message)
97 tree = tree->child;
98
99 *array = tree->message;
100 array += Sort & SORT_REVERSE ? -1 : 1;
101
102 if (tree->child)
103 tree = tree->child;
104 else
105 {
106 while (tree)
107 {
108 if (tree->next)
109 {
110 tree = tree->next;
111 break;
112 }
113 else
114 tree = tree->parent;
115 }
116 }
117 }
118}
119
120/* this calculates whether a node is the root of a subtree that has visible
121 * nodes, whether a node itself is visible, whether, if invisible, it has
122 * depth anyway, and whether any of its later siblings are roots of visible
123 * subtrees. while it's at it, it frees the old thread display, so we can
124 * skip parts of the tree in mutt_draw_tree() if we've decided here that we
125 * don't care about them any more.
126 */
127static void calculate_visibility (CONTEXT *ctx, int *max_depth)
128{
129 THREAD *tmp, *tree = ctx->tree;
130 int hide_top_missing = option (OPTHIDETOPMISSING) && !option (OPTHIDEMISSING);
131 int hide_top_limited = option (OPTHIDETOPLIMITED) && !option (OPTHIDELIMITED);
132 int depth = 0;
133
134 /* we walk each level backwards to make it easier to compute next_subtree_visible */
135 while (tree->next)
136 tree = tree->next;
137 *max_depth = 0;
138
139 FOREVER
140 {
141 if (depth > *max_depth)
142 *max_depth = depth;
143
144 tree->subtree_visible = 0;
145 if (tree->message)
146 {
147 FREE (&tree->message->tree);
148 if (VISIBLE (tree->message, ctx))
149 {
150 tree->deep = 1;
151 tree->visible = 1;
152 tree->message->display_subject = need_display_subject (ctx, tree->message);
153 for (tmp = tree; tmp; tmp = tmp->parent)
154 {
155 if (tmp->subtree_visible)
156 {
157 tmp->deep = 1;
158 tmp->subtree_visible = 2;
159 break;
160 }
161 else
162 tmp->subtree_visible = 1;
163 }
164 }
165 else
166 {
167 tree->visible = 0;
168 tree->deep = !option (OPTHIDELIMITED);
169 }
170 }
171 else
172 {
173 tree->visible = 0;
174 tree->deep = !option (OPTHIDEMISSING);
175 }
176 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible
177 || tree->next->subtree_visible);
178 if (tree->child)
179 {
180 depth++;
181 tree = tree->child;
182 while (tree->next)
183 tree = tree->next;
184 }
185 else if (tree->prev)
186 tree = tree->prev;
187 else
188 {
189 while (tree && !tree->prev)
190 {
191 depth--;
192 tree = tree->parent;
193 }
194 if (!tree)
195 break;
196 else
197 tree = tree->prev;
198 }
199 }
200
201 /* now fix up for the OPTHIDETOP* options if necessary */
202 if (hide_top_limited || hide_top_missing)
203 {
204 tree = ctx->tree;
205 FOREVER
206 {
207 if (!tree->visible && tree->deep && tree->subtree_visible < 2
208 && ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
209 tree->deep = 0;
210 if (!tree->deep && tree->child && tree->subtree_visible)
211 tree = tree->child;
212 else if (tree->next)
213 tree = tree->next;
214 else
215 {
216 while (tree && !tree->next)
217 tree = tree->parent;
218 if (!tree)
219 break;
220 else
221 tree = tree->next;
222 }
223 }
224 }
225}
226
227/* Since the graphics characters have a value >255, I have to resort to
228 * using escape sequences to pass the information to print_enriched_string().
229 * These are the macros MUTT_TREE_* defined in mutt.h.
230 *
231 * ncurses should automatically use the default ASCII characters instead of
232 * graphics chars on terminals which don't support them (see the man page
233 * for curs_addch).
234 */
235void mutt_draw_tree (CONTEXT *ctx)
236{
237 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree;
238 char corner = (Sort & SORT_REVERSE) ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
239 char vtee = (Sort & SORT_REVERSE) ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
240 int depth = 0, start_depth = 0, max_depth = 0, width = option (OPTNARROWTREE) ? 1 : 2;
241 THREAD *nextdisp = NULL, *pseudo = NULL, *parent = NULL, *tree = ctx->tree;
242
243 /* Do the visibility calculations and free the old thread chars.
244 * From now on we can simply ignore invisible subtrees
245 */
246 calculate_visibility (ctx, &max_depth);
247 pfx = safe_malloc (width * max_depth + 2);
248 arrow = safe_malloc (width * max_depth + 2);
249 while (tree)
250 {
251 if (depth)
252 {
253 myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width;
254 if (depth && start_depth == depth)
255 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
256 else if (parent->message && !option (OPTHIDELIMITED))
257 myarrow[0] = MUTT_TREE_HIDDEN;
258 else if (!parent->message && !option (OPTHIDEMISSING))
259 myarrow[0] = MUTT_TREE_MISSING;
260 else
261 myarrow[0] = vtee;
262 if (width == 2)
263 myarrow[1] = pseudo ? MUTT_TREE_STAR
264 : (tree->duplicate_thread ? MUTT_TREE_EQUALS : MUTT_TREE_HLINE);
265 if (tree->visible)
266 {
267 myarrow[width] = MUTT_TREE_RARROW;
268 myarrow[width + 1] = 0;
269 new_tree = safe_malloc ((2 + depth * width));
270 if (start_depth > 1)
271 {
272 strncpy (new_tree, pfx, (start_depth - 1) * width);
273 strfcpy (new_tree + (start_depth - 1) * width,
274 arrow, (1 + depth - start_depth) * width + 2);
275 }
276 else
277 strfcpy (new_tree, arrow, 2 + depth * width);
278 tree->message->tree = new_tree;
279 }
280 }
281 if (tree->child && depth)
282 {
283 mypfx = pfx + (depth - 1) * width;
284 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
285 if (width == 2)
286 mypfx[1] = MUTT_TREE_SPACE;
287 }
288 parent = tree;
289 nextdisp = NULL;
290 pseudo = NULL;
291 do
292 {
293 if (tree->child && tree->subtree_visible)
294 {
295 if (tree->deep)
296 depth++;
297 if (tree->visible)
298 start_depth = depth;
299 tree = tree->child;
300
301 /* we do this here because we need to make sure that the first child thread
302 * of the old tree that we deal with is actually displayed if any are,
303 * or we might set the parent variable wrong while going through it. */
304 while (!tree->subtree_visible && tree->next)
305 tree = tree->next;
306 }
307 else
308 {
309 while (!tree->next && tree->parent)
310 {
311 if (tree == pseudo)
312 pseudo = NULL;
313 if (tree == nextdisp)
314 nextdisp = NULL;
315 if (tree->visible)
316 start_depth = depth;
317 tree = tree->parent;
318 if (tree->deep)
319 {
320 if (start_depth == depth)
321 start_depth--;
322 depth--;
323 }
324 }
325 if (tree == pseudo)
326 pseudo = NULL;
327 if (tree == nextdisp)
328 nextdisp = NULL;
329 if (tree->visible)
330 start_depth = depth;
331 tree = tree->next;
332 if (!tree)
333 break;
334 }
335 if (!pseudo && tree->fake_thread)
336 pseudo = tree;
337 if (!nextdisp && tree->next_subtree_visible)
338 nextdisp = tree;
339 }
340 while (!tree->deep);
341 }
342
343 FREE (&pfx);
344 FREE (&arrow);
345}
346
347/* since we may be trying to attach as a pseudo-thread a THREAD that
348 * has no message, we have to make a list of all the subjects of its
349 * most immediate existing descendants. we also note the earliest
350 * date on any of the parents and put it in *dateptr. */
351static LIST *make_subject_list (THREAD *cur, time_t *dateptr)
352{
353 THREAD *start = cur;
354 ENVELOPE *env;
355 time_t thisdate;
356 LIST *curlist, *oldlist, *newlist, *subjects = NULL;
357 int rc = 0;
358
359 FOREVER
360 {
361 while (!cur->message)
362 cur = cur->child;
363
364 if (dateptr)
365 {
366 thisdate = option (OPTTHREADRECEIVED)
367 ? cur->message->received : cur->message->date_sent;
368 if (!*dateptr || thisdate < *dateptr)
369 *dateptr = thisdate;
370 }
371
372 env = cur->message->env;
373 if (env->real_subj &&
374 ((env->real_subj != env->subject) || (!option (OPTSORTRE))))
375 {
376 for (curlist = subjects, oldlist = NULL;
377 curlist; oldlist = curlist, curlist = curlist->next)
378 {
379 rc = mutt_strcmp (env->real_subj, curlist->data);
380 if (rc >= 0)
381 break;
382 }
383 if (!curlist || rc > 0)
384 {
385 newlist = safe_calloc (1, sizeof (LIST));
386 newlist->data = env->real_subj;
387 if (oldlist)
388 {
389 newlist->next = oldlist->next;
390 oldlist->next = newlist;
391 }
392 else
393 {
394 newlist->next = subjects;
395 subjects = newlist;
396 }
397 }
398 }
399
400 while (!cur->next && cur != start)
401 {
402 cur = cur->parent;
403 }
404 if (cur == start)
405 break;
406 cur = cur->next;
407 }
408
409 return (subjects);
410}
411
412/* find the best possible match for a parent message based upon subject.
413 * if there are multiple matches, the one which was sent the latest, but
414 * before the current message, is used.
415 */
416static THREAD *find_subject (CONTEXT *ctx, THREAD *cur)
417{
418 struct hash_elem *ptr;
419 THREAD *tmp, *last = NULL;
420 LIST *subjects = NULL, *oldlist;
421 time_t date = 0;
422
423 subjects = make_subject_list (cur, &date);
424
425 while (subjects)
426 {
427 for (ptr = hash_find_bucket (ctx->subj_hash, subjects->data); ptr; ptr = ptr->next)
428 {
429 tmp = ((HEADER *) ptr->data)->thread;
430 if (tmp != cur && /* don't match the same message */
431 !tmp->fake_thread && /* don't match pseudo threads */
432 tmp->message->subject_changed && /* only match interesting replies */
433 !is_descendant (tmp, cur) && /* don't match in the same thread */
434 (date >= (option (OPTTHREADRECEIVED) ?
435 tmp->message->received :
436 tmp->message->date_sent)) &&
437 (!last ||
438 (option (OPTTHREADRECEIVED) ?
439 (last->message->received < tmp->message->received) :
440 (last->message->date_sent < tmp->message->date_sent))) &&
441 tmp->message->env->real_subj &&
442 mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
443 last = tmp; /* best match so far */
444 }
445
446 oldlist = subjects;
447 subjects = subjects->next;
448 FREE (&oldlist);
449 }
450 return (last);
451}
452
453/* remove cur and its descendants from their current location.
454 * also make sure ancestors of cur no longer are sorted by the
455 * fact that cur is their descendant. */
456static void unlink_message (THREAD **old, THREAD *cur)
457{
458 THREAD *tmp;
459
460 if (cur->prev)
461 cur->prev->next = cur->next;
462 else
463 *old = cur->next;
464
465 if (cur->next)
466 cur->next->prev = cur->prev;
467
468 if (cur->sort_key)
469 {
470 for (tmp = cur->parent; tmp && tmp->sort_key == cur->sort_key;
471 tmp = tmp->parent)
472 tmp->sort_key = NULL;
473 }
474}
475
476/* add cur as a prior sibling of *new, with parent newparent */
477static void insert_message (THREAD **new, THREAD *newparent, THREAD *cur)
478{
479 if (*new)
480 (*new)->prev = cur;
481
482 cur->parent = newparent;
483 cur->next = *new;
484 cur->prev = NULL;
485 *new = cur;
486}
487
488/* thread by subject things that didn't get threaded by message-id */
489static void pseudo_threads (CONTEXT *ctx)
490{
491 THREAD *tree = ctx->tree, *top = tree;
492 THREAD *tmp, *cur, *parent, *curchild, *nextchild;
493
494 if (!ctx->subj_hash)
495 ctx->subj_hash = mutt_make_subj_hash (ctx);
496
497 while (tree)
498 {
499 cur = tree;
500 tree = tree->next;
501 if ((parent = find_subject (ctx, cur)) != NULL)
502 {
503 cur->fake_thread = 1;
504 unlink_message (&top, cur);
505 insert_message (&parent->child, parent, cur);
506 parent->sort_children = 1;
507 tmp = cur;
508 FOREVER
509 {
510 while (!tmp->message)
511 tmp = tmp->child;
512
513 /* if the message we're attaching has pseudo-children, they
514 * need to be attached to its parent, so move them up a level.
515 * but only do this if they have the same real subject as the
516 * parent, since otherwise they rightly belong to the message
517 * we're attaching. */
518 if (tmp == cur
519 || !mutt_strcmp (tmp->message->env->real_subj,
520 parent->message->env->real_subj))
521 {
522 tmp->message->subject_changed = 0;
523
524 for (curchild = tmp->child; curchild; )
525 {
526 nextchild = curchild->next;
527 if (curchild->fake_thread)
528 {
529 unlink_message (&tmp->child, curchild);
530 insert_message (&parent->child, parent, curchild);
531 }
532 curchild = nextchild;
533 }
534 }
535
536 while (!tmp->next && tmp != cur)
537 {
538 tmp = tmp->parent;
539 }
540 if (tmp == cur)
541 break;
542 tmp = tmp->next;
543 }
544 }
545 }
546 ctx->tree = top;
547}
548
549
550void mutt_clear_threads (CONTEXT *ctx)
551{
552 int i;
553
554 for (i = 0; i < ctx->msgcount; i++)
555 {
556 /* mailbox may have been only partially read */
557 if (ctx->hdrs[i])
558 {
559 ctx->hdrs[i]->thread = NULL;
560 ctx->hdrs[i]->threaded = 0;
561 }
562 }
563 ctx->tree = NULL;
564
565 if (ctx->thread_hash)
566 hash_destroy (&ctx->thread_hash, *free);
567}
568
569static int compare_threads (const void *a, const void *b)
570{
571 static sort_t *sort_func = NULL;
572
573 if (a && b)
574 return ((*sort_func) (&(*((THREAD **) a))->sort_key,
575 &(*((THREAD **) b))->sort_key));
576 /* a hack to let us reset sort_func even though we can't
577 * have extra arguments because of qsort
578 */
579 else
580 {
581 sort_func = mutt_get_sort_func (Sort);
582 return (sort_func ? 1 : 0);
583 }
584}
585
586THREAD *mutt_sort_subthreads (THREAD *thread, int init)
587{
588 THREAD **array, *sort_key, *top, *tmp;
589 HEADER *oldsort_key;
590 int i, array_size, sort_top = 0;
591
592 /* we put things into the array backwards to save some cycles,
593 * but we want to have to move less stuff around if we're
594 * resorting, so we sort backwards and then put them back
595 * in reverse order so they're forwards
596 */
597 Sort ^= SORT_REVERSE;
598 if (!compare_threads (NULL, NULL))
599 return (thread);
600
601 top = thread;
602
603 array = safe_calloc ((array_size = 256), sizeof (THREAD *));
604 while (1)
605 {
606 if (init || !thread->sort_key)
607 {
608 thread->sort_key = NULL;
609
610 if (thread->parent)
611 thread->parent->sort_children = 1;
612 else
613 sort_top = 1;
614 }
615
616 if (thread->child)
617 {
618 thread = thread->child;
619 continue;
620 }
621 else
622 {
623 /* if it has no children, it must be real. sort it on its own merits */
624 thread->sort_key = thread->message;
625
626 if (thread->next)
627 {
628 thread = thread->next;
629 continue;
630 }
631 }
632
633 while (!thread->next)
634 {
635 /* if it has siblings and needs to be sorted, sort it... */
636 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
637 {
638 /* put them into the array */
639 for (i = 0; thread; i++, thread = thread->prev)
640 {
641 if (i >= array_size)
642 safe_realloc (&array, (array_size *= 2) * sizeof (THREAD *));
643
644 array[i] = thread;
645 }
646
647 qsort ((void *) array, i, sizeof (THREAD *), *compare_threads);
648
649 /* attach them back together. make thread the last sibling. */
650 thread = array[0];
651 thread->next = NULL;
652 array[i - 1]->prev = NULL;
653
654 if (thread->parent)
655 thread->parent->child = array[i - 1];
656 else
657 top = array[i - 1];
658
659 while (--i)
660 {
661 array[i - 1]->prev = array[i];
662 array[i]->next = array[i - 1];
663 }
664 }
665
666 if (thread->parent)
667 {
668 tmp = thread;
669 thread = thread->parent;
670
671 if (!thread->sort_key || thread->sort_children)
672 {
673 /* make sort_key the first or last sibling, as appropriate */
674 sort_key = (!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? thread->child : tmp;
675
676 /* we just sorted its children */
677 thread->sort_children = 0;
678
679 oldsort_key = thread->sort_key;
680 thread->sort_key = thread->message;
681
682 if (Sort & SORT_LAST)
683 {
684 if (!thread->sort_key
685 || ((((Sort & SORT_REVERSE) ? 1 : -1)
686 * compare_threads ((void *) &thread,
687 (void *) &sort_key))
688 > 0))
689 thread->sort_key = sort_key->sort_key;
690 }
691 else if (!thread->sort_key)
692 thread->sort_key = sort_key->sort_key;
693
694 /* if its sort_key has changed, we need to resort it and siblings */
695 if (oldsort_key != thread->sort_key)
696 {
697 if (thread->parent)
698 thread->parent->sort_children = 1;
699 else
700 sort_top = 1;
701 }
702 }
703 }
704 else
705 {
706 Sort ^= SORT_REVERSE;
707 FREE (&array);
708 return (top);
709 }
710 }
711
712 thread = thread->next;
713 }
714}
715
716static void check_subjects (CONTEXT *ctx, int init)
717{
718 HEADER *cur;
719 THREAD *tmp;
720 int i;
721
722 for (i = 0; i < ctx->msgcount; i++)
723 {
724 cur = ctx->hdrs[i];
725 if (cur->thread->check_subject)
726 cur->thread->check_subject = 0;
727 else if (!init)
728 continue;
729
730 /* figure out which messages have subjects different than their parents' */
731 tmp = cur->thread->parent;
732 while (tmp && !tmp->message)
733 {
734 tmp = tmp->parent;
735 }
736
737 if (!tmp)
738 cur->subject_changed = 1;
739 else if (cur->env->real_subj && tmp->message->env->real_subj)
740 cur->subject_changed = mutt_strcmp (cur->env->real_subj,
741 tmp->message->env->real_subj) ? 1 : 0;
742 else
743 cur->subject_changed = (cur->env->real_subj
744 || tmp->message->env->real_subj) ? 1 : 0;
745 }
746}
747
748void mutt_sort_threads (CONTEXT *ctx, int init)
749{
750 HEADER *cur;
751 int i, oldsort, using_refs = 0;
752 THREAD *thread, *new, *tmp, top;
753 LIST *ref = NULL;
754
755 /* set Sort to the secondary method to support the set sort_aux=reverse-*
756 * settings. The sorting functions just look at the value of
757 * SORT_REVERSE
758 */
759 oldsort = Sort;
760 Sort = SortAux;
761
762 if (!ctx->thread_hash)
763 init = 1;
764
765 if (init)
766 ctx->thread_hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
767
768 /* we want a quick way to see if things are actually attached to the top of the
769 * thread tree or if they're just dangling, so we attach everything to a top
770 * node temporarily */
771 top.parent = top.next = top.prev = NULL;
772 top.child = ctx->tree;
773 for (thread = ctx->tree; thread; thread = thread->next)
774 thread->parent = ⊤
775
776 /* put each new message together with the matching messageless THREAD if it
777 * exists. otherwise, if there is a THREAD that already has a message, thread
778 * new message as an identical child. if we didn't attach the message to a
779 * THREAD, make a new one for it. */
780 for (i = 0; i < ctx->msgcount; i++)
781 {
782 cur = ctx->hdrs[i];
783
784 if (!cur->thread)
785 {
786 if ((!init || option (OPTDUPTHREADS)) && cur->env->message_id)
787 thread = hash_find (ctx->thread_hash, cur->env->message_id);
788 else
789 thread = NULL;
790
791 if (thread && !thread->message)
792 {
793 /* this is a message which was missing before */
794 thread->message = cur;
795 cur->thread = thread;
796 thread->check_subject = 1;
797
798 /* mark descendants as needing subject_changed checked */
799 for (tmp = (thread->child ? thread->child : thread); tmp != thread; )
800 {
801 while (!tmp->message)
802 tmp = tmp->child;
803 tmp->check_subject = 1;
804 while (!tmp->next && tmp != thread)
805 tmp = tmp->parent;
806 if (tmp != thread)
807 tmp = tmp->next;
808 }
809
810 if (thread->parent)
811 {
812 /* remove threading info above it based on its children, which we'll
813 * recalculate based on its headers. make sure not to leave
814 * dangling missing messages. note that we haven't kept track
815 * of what info came from its children and what from its siblings'
816 * children, so we just remove the stuff that's definitely from it */
817 do
818 {
819 tmp = thread->parent;
820 unlink_message (&tmp->child, thread);
821 thread->parent = NULL;
822 thread->sort_key = NULL;
823 thread->fake_thread = 0;
824 thread = tmp;
825 } while (thread != &top && !thread->child && !thread->message);
826 }
827 }
828 else
829 {
830 new = (option (OPTDUPTHREADS) ? thread : NULL);
831
832 thread = safe_calloc (1, sizeof (THREAD));
833 thread->message = cur;
834 thread->check_subject = 1;
835 cur->thread = thread;
836 hash_insert (ctx->thread_hash,
837 cur->env->message_id ? cur->env->message_id : "",
838 thread);
839
840 if (new)
841 {
842 if (new->duplicate_thread)
843 new = new->parent;
844
845 thread = cur->thread;
846
847 insert_message (&new->child, new, thread);
848 thread->duplicate_thread = 1;
849 thread->message->threaded = 1;
850 }
851 }
852 }
853 else
854 {
855 /* unlink pseudo-threads because they might be children of newly
856 * arrived messages */
857 thread = cur->thread;
858 for (new = thread->child; new; )
859 {
860 tmp = new->next;
861 if (new->fake_thread)
862 {
863 unlink_message (&thread->child, new);
864 insert_message (&top.child, &top, new);
865 new->fake_thread = 0;
866 }
867 new = tmp;
868 }
869 }
870 }
871
872 /* thread by references */
873 for (i = 0; i < ctx->msgcount; i++)
874 {
875 cur = ctx->hdrs[i];
876 if (cur->threaded)
877 continue;
878 cur->threaded = 1;
879
880 thread = cur->thread;
881 using_refs = 0;
882
883 while (1)
884 {
885 if (using_refs == 0)
886 {
887 /* look at the beginning of in-reply-to: */
888 if ((ref = cur->env->in_reply_to) != NULL)
889 using_refs = 1;
890 else
891 {
892 ref = cur->env->references;
893 using_refs = 2;
894 }
895 }
896 else if (using_refs == 1)
897 {
898 /* if there's no references header, use all the in-reply-to:
899 * data that we have. otherwise, use the first reference
900 * if it's different than the first in-reply-to, otherwise use
901 * the second reference (since at least eudora puts the most
902 * recent reference in in-reply-to and the rest in references)
903 */
904 if (!cur->env->references)
905 ref = ref->next;
906 else
907 {
908 if (mutt_strcmp (ref->data, cur->env->references->data))
909 ref = cur->env->references;
910 else
911 ref = cur->env->references->next;
912
913 using_refs = 2;
914 }
915 }
916 else
917 ref = ref->next; /* go on with references */
918
919 if (!ref)
920 break;
921
922 if ((new = hash_find (ctx->thread_hash, ref->data)) == NULL)
923 {
924 new = safe_calloc (1, sizeof (THREAD));
925 hash_insert (ctx->thread_hash, ref->data, new);
926 }
927 else
928 {
929 if (new->duplicate_thread)
930 new = new->parent;
931 if (is_descendant (new, thread)) /* no loops! */
932 continue;
933 }
934
935 if (thread->parent)
936 unlink_message (&top.child, thread);
937 insert_message (&new->child, new, thread);
938 thread = new;
939 if (thread->message || (thread->parent && thread->parent != &top))
940 break;
941 }
942
943 if (!thread->parent)
944 insert_message (&top.child, &top, thread);
945 }
946
947 /* detach everything from the temporary top node */
948 for (thread = top.child; thread; thread = thread->next)
949 {
950 thread->parent = NULL;
951 }
952 ctx->tree = top.child;
953
954 check_subjects (ctx, init);
955
956 if (!option (OPTSTRICTTHREADS))
957 pseudo_threads (ctx);
958
959 if (ctx->tree)
960 {
961 ctx->tree = mutt_sort_subthreads (ctx->tree, init);
962
963 /* restore the oldsort order. */
964 Sort = oldsort;
965
966 /* Put the list into an array. */
967 linearize_tree (ctx);
968
969 /* Draw the thread tree. */
970 mutt_draw_tree (ctx);
971 }
972}
973
974static HEADER *find_virtual (THREAD *cur, int reverse)
975{
976 THREAD *top;
977
978 if (cur->message && cur->message->virtual >= 0)
979 return (cur->message);
980
981 top = cur;
982 if ((cur = cur->child) == NULL)
983 return (NULL);
984
985 while (reverse && cur->next)
986 cur = cur->next;
987
988 FOREVER
989 {
990 if (cur->message && cur->message->virtual >= 0)
991 return (cur->message);
992
993 if (cur->child)
994 {
995 cur = cur->child;
996
997 while (reverse && cur->next)
998 cur = cur->next;
999 }
1000 else if (reverse ? cur->prev : cur->next)
1001 cur = reverse ? cur->prev : cur->next;
1002 else
1003 {
1004 while (!(reverse ? cur->prev : cur->next))
1005 {
1006 cur = cur->parent;
1007 if (cur == top)
1008 return (NULL);
1009 }
1010 cur = reverse ? cur->prev : cur->next;
1011 }
1012 /* not reached */
1013 }
1014}
1015
1016/* dir => true when moving forward, false when moving in reverse
1017 * subthreads => false when moving to next thread, true when moving to next subthread
1018 */
1019int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
1020{
1021 THREAD *cur;
1022 HEADER *tmp;
1023
1024 if ((Sort & SORT_MASK) != SORT_THREADS)
1025 {
1026 mutt_error _("Threading is not enabled.");
1027 return (hdr->virtual);
1028 }
1029
1030 cur = hdr->thread;
1031
1032 if (!subthreads)
1033 {
1034 while (cur->parent)
1035 cur = cur->parent;
1036 }
1037 else
1038 {
1039 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1040 {
1041 while (!cur->next && cur->parent)
1042 cur = cur->parent;
1043 }
1044 else
1045 {
1046 while (!cur->prev && cur->parent)
1047 cur = cur->parent;
1048 }
1049 }
1050
1051 if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
1052 {
1053 do
1054 {
1055 cur = cur->next;
1056 if (!cur)
1057 return (-1);
1058 tmp = find_virtual (cur, 0);
1059 } while (!tmp);
1060 }
1061 else
1062 {
1063 do
1064 {
1065 cur = cur->prev;
1066 if (!cur)
1067 return (-1);
1068 tmp = find_virtual (cur, 1);
1069 } while (!tmp);
1070 }
1071
1072 return (tmp->virtual);
1073}
1074
1075int mutt_parent_message (CONTEXT *ctx, HEADER *hdr, int find_root)
1076{
1077 THREAD *thread;
1078 HEADER *parent = NULL;
1079
1080 if ((Sort & SORT_MASK) != SORT_THREADS)
1081 {
1082 mutt_error _("Threading is not enabled.");
1083 return (hdr->virtual);
1084 }
1085
1086 /* Root may be the current message */
1087 if (find_root)
1088 parent = hdr;
1089
1090 for (thread = hdr->thread->parent; thread; thread = thread->parent)
1091 {
1092 if ((hdr = thread->message) != NULL)
1093 {
1094 parent = hdr;
1095 if (!find_root)
1096 break;
1097 }
1098 }
1099
1100 if (!parent)
1101 {
1102 mutt_error _("Parent message is not available.");
1103 return (-1);
1104 }
1105 if (!VISIBLE (parent, ctx))
1106 {
1107 if (find_root)
1108 mutt_error _("Root message is not visible in this limited view.");
1109 else
1110 mutt_error _("Parent message is not visible in this limited view.");
1111 return (-1);
1112 }
1113 return (parent->virtual);
1114}
1115
1116void mutt_set_virtual (CONTEXT *ctx)
1117{
1118 int i, padding;
1119 HEADER *cur;
1120
1121 ctx->vcount = 0;
1122 ctx->vsize = 0;
1123 padding = mx_msg_padding_size (ctx);
1124
1125 for (i = 0; i < ctx->msgcount; i++)
1126 {
1127 cur = ctx->hdrs[i];
1128 if (cur->virtual >= 0)
1129 {
1130 cur->virtual = ctx->vcount;
1131 ctx->v2r[ctx->vcount] = i;
1132 ctx->vcount++;
1133 ctx->vsize += cur->content->length + cur->content->offset -
1134 cur->content->hdr_offset + padding;
1135 }
1136 }
1137}
1138
1139int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
1140{
1141 THREAD *thread, *top;
1142 HEADER *roothdr = NULL;
1143 int final, reverse = (Sort & SORT_REVERSE), minmsgno;
1144 int num_hidden = 0, new = 0, old = 0;
1145 int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
1146#define CHECK_LIMIT (!ctx->pattern || cur->limited)
1147
1148 if ((Sort & SORT_MASK) != SORT_THREADS)
1149 {
1150 mutt_error (_("Threading is not enabled."));
1151 return (cur->virtual);
1152 }
1153
1154 final = cur->virtual;
1155 thread = cur->thread;
1156 while (thread->parent)
1157 thread = thread->parent;
1158 top = thread;
1159 while (!thread->message)
1160 thread = thread->child;
1161 cur = thread->message;
1162 minmsgno = cur->msgno;
1163
1164 if (!cur->read && CHECK_LIMIT)
1165 {
1166 if (cur->old)
1167 old = 2;
1168 else
1169 new = 1;
1170 if (cur->msgno < min_unread_msgno)
1171 {
1172 min_unread = cur->virtual;
1173 min_unread_msgno = cur->msgno;
1174 }
1175 }
1176
1177 if (cur->virtual == -1 && CHECK_LIMIT)
1178 num_hidden++;
1179
1180 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1181 {
1182 cur->pair = 0; /* force index entry's color to be re-evaluated */
1183 cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1184 if (cur->virtual != -1)
1185 {
1186 roothdr = cur;
1187 if (flag & MUTT_THREAD_COLLAPSE)
1188 final = roothdr->virtual;
1189 }
1190 }
1191
1192 if (thread == top && (thread = thread->child) == NULL)
1193 {
1194 /* return value depends on action requested */
1195 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1196 {
1197 if (roothdr)
1198 roothdr->num_hidden = num_hidden;
1199 return (final);
1200 }
1201 else if (flag & MUTT_THREAD_UNREAD)
1202 return ((old && new) ? new : (old ? old : new));
1203 else if (flag & MUTT_THREAD_NEXT_UNREAD)
1204 return (min_unread);
1205 }
1206
1207 FOREVER
1208 {
1209 cur = thread->message;
1210
1211 if (cur)
1212 {
1213 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1214 {
1215 cur->pair = 0; /* force index entry's color to be re-evaluated */
1216 cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1217 if (!roothdr && CHECK_LIMIT)
1218 {
1219 roothdr = cur;
1220 if (flag & MUTT_THREAD_COLLAPSE)
1221 final = roothdr->virtual;
1222 }
1223
1224 if (reverse && (flag & MUTT_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
1225 {
1226 minmsgno = cur->msgno;
1227 final = cur->virtual;
1228 }
1229
1230 if (flag & MUTT_THREAD_COLLAPSE)
1231 {
1232 if (cur != roothdr)
1233 cur->virtual = -1;
1234 }
1235 else
1236 {
1237 if (CHECK_LIMIT)
1238 cur->virtual = cur->msgno;
1239 }
1240 }
1241
1242
1243 if (!cur->read && CHECK_LIMIT)
1244 {
1245 if (cur->old)
1246 old = 2;
1247 else
1248 new = 1;
1249 if (cur->msgno < min_unread_msgno)
1250 {
1251 min_unread = cur->virtual;
1252 min_unread_msgno = cur->msgno;
1253 }
1254 }
1255
1256 if (cur->virtual == -1 && CHECK_LIMIT)
1257 num_hidden++;
1258 }
1259
1260 if (thread->child)
1261 thread = thread->child;
1262 else if (thread->next)
1263 thread = thread->next;
1264 else
1265 {
1266 int done = 0;
1267 while (!thread->next)
1268 {
1269 thread = thread->parent;
1270 if (thread == top)
1271 {
1272 done = 1;
1273 break;
1274 }
1275 }
1276 if (done)
1277 break;
1278 thread = thread->next;
1279 }
1280 }
1281
1282 /* return value depends on action requested */
1283 if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1284 {
1285 if (roothdr)
1286 roothdr->num_hidden = num_hidden + 1;
1287 return (final);
1288 }
1289 else if (flag & MUTT_THREAD_UNREAD)
1290 return ((old && new) ? new : (old ? old : new));
1291 else if (flag & MUTT_THREAD_NEXT_UNREAD)
1292 return (min_unread);
1293
1294 return (0);
1295#undef CHECK_LIMIT
1296}
1297
1298
1299/* if flag is 0, we want to know how many messages
1300 * are in the thread. if flag is 1, we want to know
1301 * our position in the thread. */
1302int mutt_messages_in_thread (CONTEXT *ctx, HEADER *hdr, int flag)
1303{
1304 THREAD *threads[2];
1305 int i, rc;
1306
1307 if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread)
1308 return (1);
1309
1310 threads[0] = hdr->thread;
1311 while (threads[0]->parent)
1312 threads[0] = threads[0]->parent;
1313
1314 threads[1] = flag ? hdr->thread : threads[0]->next;
1315
1316 for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
1317 {
1318 while (!threads[i]->message)
1319 threads[i] = threads[i]->child;
1320 }
1321
1322 if (Sort & SORT_REVERSE)
1323 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1324 else
1325 rc = (threads[1] ? threads[1]->message->msgno : ctx->msgcount) - threads[0]->message->msgno;
1326
1327 if (flag)
1328 rc += 1;
1329
1330 return (rc);
1331}
1332
1333
1334HASH *mutt_make_id_hash (CONTEXT *ctx)
1335{
1336 int i;
1337 HEADER *hdr;
1338 HASH *hash;
1339
1340 hash = hash_create (ctx->msgcount * 2, 0);
1341
1342 for (i = 0; i < ctx->msgcount; i++)
1343 {
1344 hdr = ctx->hdrs[i];
1345 if (hdr->env->message_id)
1346 hash_insert (hash, hdr->env->message_id, hdr);
1347 }
1348
1349 return hash;
1350}
1351
1352HASH *mutt_make_subj_hash (CONTEXT *ctx)
1353{
1354 int i;
1355 HEADER *hdr;
1356 HASH *hash;
1357
1358 hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
1359
1360 for (i = 0; i < ctx->msgcount; i++)
1361 {
1362 hdr = ctx->hdrs[i];
1363 if (hdr->env->real_subj)
1364 hash_insert (hash, hdr->env->real_subj, hdr);
1365 }
1366
1367 return hash;
1368}
1369
1370static void clean_references (THREAD *brk, THREAD *cur)
1371{
1372 THREAD *p;
1373 LIST *ref = NULL;
1374 int done = 0;
1375
1376 for (; cur; cur = cur->next, done = 0)
1377 {
1378 /* parse subthread recursively */
1379 clean_references (brk, cur->child);
1380
1381 if (!cur->message)
1382 break; /* skip pseudo-message */
1383
1384 /* Looking for the first bad reference according to the new threading.
1385 * Optimal since Mutt stores the references in reverse order, and the
1386 * first loop should match immediately for mails respecting RFC2822. */
1387 for (p = brk; !done && p; p = p->parent)
1388 for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
1389 if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
1390 {
1391 done = 1;
1392 break;
1393 }
1394
1395 if (done)
1396 {
1397 HEADER *h = cur->message;
1398
1399 /* clearing the References: header from obsolete Message-ID(s) */
1400 mutt_free_list (&ref->next);
1401
1402 h->changed = 1;
1403 h->env->changed |= MUTT_ENV_CHANGED_REFS;
1404 }
1405 }
1406}
1407
1408void mutt_break_thread (HEADER *hdr)
1409{
1410 mutt_free_list (&hdr->env->in_reply_to);
1411 mutt_free_list (&hdr->env->references);
1412 hdr->changed = 1;
1413 hdr->env->changed |= (MUTT_ENV_CHANGED_IRT | MUTT_ENV_CHANGED_REFS);
1414
1415 clean_references (hdr->thread, hdr->thread->child);
1416}
1417
1418static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
1419{
1420 if (child == parent)
1421 return 0;
1422
1423 mutt_break_thread (child);
1424
1425 child->env->in_reply_to = mutt_new_list ();
1426 child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
1427
1428 mutt_set_flag (ctx, child, MUTT_TAG, 0);
1429
1430 child->changed = 1;
1431 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1432 return 1;
1433}
1434
1435int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
1436{
1437 int i, changed = 0;
1438
1439 if (!last)
1440 {
1441 for (i = 0; i < ctx->vcount; i++)
1442 if (ctx->hdrs[Context->v2r[i]]->tagged)
1443 changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
1444 }
1445 else
1446 changed = link_threads (cur, last, ctx);
1447
1448 return changed;
1449}