NeoMutt  2025-12-11-694-ga89709
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
thread.c
Go to the documentation of this file.
1
26
32
33#include "config.h"
34#include <limits.h>
35#include <stdbool.h>
36#include <string.h>
37#include "mutt/lib.h"
38#include "config/lib.h"
39#include "email/lib.h"
40#include "core/lib.h"
41#include "mutt.h"
42#include "thread.h"
43#include "globals.h"
44#include "mview.h"
45#include "mx.h"
46
50static const struct Mapping UseThreadsMethods[] = {
51 // clang-format off
52 { "unset", UT_UNSET },
53 { "flat", UT_FLAT },
54 { "threads", UT_THREADS },
55 { "reverse", UT_REVERSE },
56 // aliases
57 { "no", UT_FLAT },
58 { "yes", UT_THREADS },
59 { NULL, 0 },
60 // clang-format on
61};
62
64const struct EnumDef UseThreadsTypeDef = {
65 "use_threads_type",
66 4,
67 (struct Mapping *) &UseThreadsMethods,
68};
69
80{
81 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
82 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
83 if (c_use_threads > UT_FLAT)
84 return c_use_threads;
85 if ((c_sort & SORT_MASK) != EMAIL_SORT_THREADS)
86 return UT_FLAT;
87 if (c_sort & SORT_REVERSE)
88 return UT_REVERSE;
89 return UT_THREADS;
90}
91
101
105int sort_validator(const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
106{
108 {
109 buf_printf(err, _("Cannot use 'last-' prefix with 'threads' for %s"), cdef->name);
110 return CSR_ERR_INVALID;
111 }
112 return CSR_SUCCESS;
113}
114
120static bool is_visible(struct Email *e)
121{
122 return e->vnum >= 0 || (e->collapsed && e->visible);
123}
124
130static bool need_display_subject(struct Email *e)
131{
132 struct MuttThread *tmp = NULL;
133 struct MuttThread *tree = e->thread;
134
135 if (!tree)
136 {
137 mutt_debug(LL_DEBUG1, "stranded Email with no thread info, stranded Email index=%d\n",
138 e->index);
139 return true;
140 }
141
142 /* if the user disabled subject hiding, display it */
143 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
144 if (!c_hide_thread_subject)
145 return true;
146
147 /* if our subject is different from our parent's, display it */
148 if (e->subject_changed)
149 return true;
150
151 /* if our subject is different from that of our closest previously displayed
152 * sibling, display the subject */
153 for (tmp = tree->prev; tmp; tmp = tmp->prev)
154 {
155 e = tmp->message;
156 if (e && is_visible(e))
157 {
158 if (e->subject_changed)
159 return true;
160 break;
161 }
162 }
163
164 /* if there is a parent-to-child subject change anywhere between us and our
165 * closest displayed ancestor, display the subject */
166 for (tmp = tree->parent; tmp; tmp = tmp->parent)
167 {
168 e = tmp->message;
169 if (e)
170 {
171 if (is_visible(e))
172 return false;
173 if (e->subject_changed)
174 return true;
175 }
176 }
177
178 /* if we have no visible parent or previous sibling, display the subject */
179 return true;
180}
181
186static void linearize_tree(struct ThreadsContext *tctx)
187{
188 if (!tctx || !tctx->mailbox_view)
189 return;
190
191 struct Mailbox *m = tctx->mailbox_view->mailbox;
192
193 const bool reverse = (mutt_thread_style() == UT_REVERSE);
194 struct MuttThread *tree = tctx->tree;
195 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
196
197 while (tree)
198 {
199 while (!tree->message)
200 tree = tree->child;
201
202 *array = tree->message;
203 array += reverse ? -1 : 1;
204
205 if (tree->child)
206 {
207 tree = tree->child;
208 }
209 else
210 {
211 while (tree)
212 {
213 if (tree->next)
214 {
215 tree = tree->next;
216 break;
217 }
218 else
219 {
220 tree = tree->parent;
221 }
222 }
223 }
224 }
225}
226
239static void calculate_visibility(struct MuttThread *tree, int *max_depth)
240{
241 if (!tree)
242 return;
243
244 struct MuttThread *tmp = NULL;
245 struct MuttThread *orig_tree = tree;
246 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
247 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
248 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
249 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
250 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
251 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
252 int depth = 0;
253
254 /* Walk each level backwards (right to left) to make it easier to
255 * compute next_subtree_visible for each node */
256 while (tree->next)
257 tree = tree->next;
258 *max_depth = 0;
259
260 while (true)
261 {
262 if (depth > *max_depth)
263 *max_depth = depth;
264
265 tree->subtree_visible = 0;
266 if (tree->message)
267 {
268 FREE(&tree->message->tree);
269 if (tree->message->thread != tree)
270 {
271 mutt_debug(LL_DEBUG1, "thread<->message mismatch: Email index=%d, thread=%p, message->thread=%p\n",
272 tree->message->index, (void *) tree, (void *) tree->message->thread);
273 }
274 /* Visible messages propagate subtree_visible up to all ancestors */
275 if (is_visible(tree->message))
276 {
277 tree->deep = true;
278 tree->visible = true;
280 for (tmp = tree; tmp; tmp = tmp->parent)
281 {
282 if (tmp->subtree_visible)
283 {
284 tmp->deep = true;
285 tmp->subtree_visible = 2;
286 break;
287 }
288 else
289 {
290 tmp->subtree_visible = 1;
291 }
292 }
293 }
294 else
295 {
296 tree->visible = false;
297 tree->deep = !c_hide_limited;
298 }
299 }
300 else
301 {
302 tree->visible = false;
303 tree->deep = !c_hide_missing;
304 }
305 /* Compute next_subtree_visible from the next sibling, then
306 * navigate: descend to children, retreat to previous sibling,
307 * or ascend to parent when at the leftmost node */
308 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
309 tree->next->subtree_visible);
310 if (tree->child)
311 {
312 depth++;
313 tree = tree->child;
314 while (tree->next)
315 tree = tree->next;
316 }
317 else if (tree->prev)
318 {
319 tree = tree->prev;
320 }
321 else
322 {
323 while (tree && !tree->prev)
324 {
325 depth--;
326 tree = tree->parent;
327 }
328 if (!tree)
329 break;
330 tree = tree->prev;
331 }
332 }
333
334 /* now fix up for the OPTHIDETOP* options if necessary */
335 if (hide_top_limited || hide_top_missing)
336 {
337 tree = orig_tree;
338 while (true)
339 {
340 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
341 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
342 {
343 tree->deep = false;
344 }
345 if (!tree->deep && tree->child && tree->subtree_visible)
346 {
347 tree = tree->child;
348 }
349 else if (tree->next)
350 {
351 tree = tree->next;
352 }
353 else
354 {
355 while (tree && !tree->next)
356 tree = tree->parent;
357 if (!tree)
358 break;
359 tree = tree->next;
360 }
361 }
362 }
363}
364
371{
372 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
373 tctx->mailbox_view = mv;
374 return tctx;
375}
376
382{
383 if (!ptr || !*ptr)
384 {
385 return;
386 }
387
388 struct ThreadsContext *tctx = *ptr;
389
390 mutt_hash_free(&tctx->hash);
391
392 FREE(ptr);
393}
394
409{
410 if (!tree)
411 return 0;
412
413 /* Rewind to the true head of this sibling level */
414 while (tree->prev)
415 tree = tree->prev;
416
417 int repairs = 0;
418
419 while (true)
420 {
421 if (tree->message && (tree->message->thread != tree))
422 {
423 mutt_debug(LL_DEBUG1, "repairing thread<->message: Email index=%d, expected=%p, actual=%p\n",
424 tree->message->index, (void *) tree, (void *) tree->message->thread);
426 repairs++;
427 }
428
429 if (tree->child)
430 {
431 tree = tree->child;
432 /* Rewind to the true head of this sibling level */
433 while (tree->prev)
434 tree = tree->prev;
435 }
436 else if (tree->next)
437 {
438 tree = tree->next;
439 }
440 else
441 {
442 while (tree && !tree->next)
443 tree = tree->parent;
444 if (!tree)
445 break;
446 tree = tree->next;
447 }
448 }
449
450 return repairs;
451}
452
466{
467 if (!tctx || !tctx->tree)
468 return;
469
470 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
471 const bool reverse = (mutt_thread_style() == UT_REVERSE);
472 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
473 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
474 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
475 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
476 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
477
478 struct MuttThread *tree = tctx->tree;
479
480 /* Verify and repair thread<->message back-pointers before traversal */
482
483 /* Do the visibility calculations and free the old thread chars.
484 * From now on we can simply ignore invisible subtrees */
485 calculate_visibility(tree, &max_depth);
486 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
487 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
488 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
489 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
490 while (tree)
491 {
492 if (depth != 0)
493 {
494 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
495 if (start_depth == depth)
496 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
497 else if (parent->message && !c_hide_limited)
498 myarrow[0] = MUTT_TREE_HIDDEN;
499 else if (!parent->message && !c_hide_missing)
500 myarrow[0] = MUTT_TREE_MISSING;
501 else
502 myarrow[0] = vtee;
503 if (width == 2)
504 {
505 myarrow[1] = pseudo ? MUTT_TREE_STAR :
507 }
508 if (tree->visible)
509 {
510 myarrow[width] = MUTT_TREE_RARROW;
511 myarrow[width + 1] = 0;
512 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
513 if (start_depth > 1)
514 {
515 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
516 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
517 (1 + depth - start_depth) * width + 2);
518 }
519 else
520 {
521 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
522 }
523 tree->message->tree = new_tree;
524 }
525 }
526 if (tree->child && (depth != 0))
527 {
528 mypfx = pfx + (depth - 1) * width;
529 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
530 if (width == 2)
531 mypfx[1] = MUTT_TREE_SPACE;
532 }
533 parent = tree;
534 nextdisp = NULL;
535 pseudo = NULL;
536 do
537 {
538 if (tree->child && tree->subtree_visible)
539 {
540 if (tree->deep)
541 depth++;
542 if (tree->visible)
543 start_depth = depth;
544 tree = tree->child;
545
546 /* we do this here because we need to make sure that the first child thread
547 * of the old tree that we deal with is actually displayed if any are,
548 * or we might set the parent variable wrong while going through it. */
549 while (!tree->subtree_visible && tree->next)
550 tree = tree->next;
551 }
552 else
553 {
554 while (!tree->next && tree->parent)
555 {
556 if (tree == pseudo)
557 pseudo = NULL;
558 if (tree == nextdisp)
559 nextdisp = NULL;
560 if (tree->visible)
561 start_depth = depth;
562 tree = tree->parent;
563 if (tree->deep)
564 {
565 if (start_depth == depth)
566 start_depth--;
567 depth--;
568 }
569 }
570 if (tree == pseudo)
571 pseudo = NULL;
572 if (tree == nextdisp)
573 nextdisp = NULL;
574 if (tree->visible)
575 start_depth = depth;
576 tree = tree->next;
577 if (!tree)
578 break;
579 }
580 if (!pseudo && tree->fake_thread)
581 pseudo = tree;
582 if (!nextdisp && tree->next_subtree_visible)
583 nextdisp = tree;
584 } while (!tree->deep);
585 }
586
587 FREE(&pfx);
588 FREE(&arrow);
589}
590
601static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
602{
603 struct MuttThread *start = cur;
604 struct Envelope *env = NULL;
605 time_t thisdate;
606 int rc = 0;
607
608 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
609 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
610 while (true)
611 {
612 while (!cur->message)
613 cur = cur->child;
614
615 if (dateptr)
616 {
617 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
618 if ((*dateptr == 0) || (thisdate < *dateptr))
619 *dateptr = thisdate;
620 }
621
622 env = cur->message->env;
623 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
624 {
625 struct ListNode *np = NULL;
626 STAILQ_FOREACH(np, subjects, entries)
627 {
628 rc = mutt_str_cmp(env->real_subj, np->data);
629 if (rc >= 0)
630 break;
631 }
632 if (!np)
633 mutt_list_insert_head(subjects, env->real_subj);
634 else if (rc > 0)
635 mutt_list_insert_after(subjects, np, env->real_subj);
636 }
637
638 while (!cur->next && (cur != start))
639 {
640 cur = cur->parent;
641 }
642 if (cur == start)
643 break;
644 cur = cur->next;
645 }
646}
647
657static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
658{
659 if (!m)
660 return NULL;
661
662 struct HashElem *he = NULL;
663 struct MuttThread *tmp = NULL, *last = NULL;
664 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
665 time_t date = 0;
666
667 make_subject_list(&subjects, cur, &date);
668
669 struct ListNode *np = NULL;
670 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
671 STAILQ_FOREACH(np, &subjects, entries)
672 {
673 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
674 {
675 tmp = ((struct Email *) he->data)->thread;
676 if ((tmp != cur) && /* don't match the same message */
677 !tmp->fake_thread && /* don't match pseudo threads */
678 tmp->message->subject_changed && /* only match interesting replies */
679 !is_descendant(tmp, cur) && /* don't match in the same thread */
680 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
681 (!last || (c_thread_received ?
682 (last->message->received < tmp->message->received) :
683 (last->message->date_sent < tmp->message->date_sent))) &&
684 tmp->message->env->real_subj &&
686 {
687 last = tmp; /* best match so far */
688 }
689 }
690 }
691
692 mutt_list_clear(&subjects);
693 return last;
694}
695
701static struct HashTable *make_subj_hash(struct Mailbox *m)
702{
703 if (!m)
704 return NULL;
705
707
708 for (int i = 0; i < m->msg_count; i++)
709 {
710 struct Email *e = m->emails[i];
711 if (!e || !e->env)
712 continue;
713 if (e->env->real_subj)
714 mutt_hash_insert(hash, e->env->real_subj, e);
715 }
716
717 return hash;
718}
719
726static void pseudo_threads(struct ThreadsContext *tctx)
727{
728 if (!tctx || !tctx->mailbox_view)
729 return;
730
731 struct Mailbox *m = tctx->mailbox_view->mailbox;
732
733 struct MuttThread *tree = tctx->tree;
734 struct MuttThread *top = tree;
735 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
736 *nextchild = NULL;
737
738 if (!m->subj_hash)
740
741 while (tree)
742 {
743 cur = tree;
744 tree = tree->next;
745 parent = find_subject(m, cur);
746 if (parent)
747 {
748 cur->fake_thread = true;
749 unlink_message(&top, cur);
751 parent->sort_children = true;
752 tmp = cur;
753 while (true)
754 {
755 while (!tmp->message)
756 tmp = tmp->child;
757
758 /* if the message we're attaching has pseudo-children, they
759 * need to be attached to its parent, so move them up a level.
760 * but only do this if they have the same real subject as the
761 * parent, since otherwise they rightly belong to the message
762 * we're attaching. */
763 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
765 {
766 tmp->message->subject_changed = false;
767
768 for (curchild = tmp->child; curchild;)
769 {
770 nextchild = curchild->next;
771 if (curchild->fake_thread)
772 {
773 unlink_message(&tmp->child, curchild);
774 insert_message(&parent->child, parent, curchild);
775 }
776 curchild = nextchild;
777 }
778 }
779
780 while (!tmp->next && (tmp != cur))
781 {
782 tmp = tmp->parent;
783 }
784 if (tmp == cur)
785 break;
786 tmp = tmp->next;
787 }
788 }
789 }
790 tctx->tree = top;
791}
792
798{
799 if (!tctx || !tctx->tree)
800 return;
801
802 struct MailboxView *mv = tctx->mailbox_view;
803 if (!mv)
804 return;
805
806 struct Mailbox *m = mv->mailbox;
807 if (!m || !m->emails)
808 return;
809
810 for (int i = 0; i < m->msg_count; i++)
811 {
812 struct Email *e = m->emails[i];
813 if (!e)
814 break;
815
816 /* mailbox may have been only partially read */
817 e->thread = NULL;
818 e->threaded = false;
819 }
820 tctx->tree = NULL;
821 mutt_hash_free(&tctx->hash);
822}
823
827static int compare_threads(const void *a, const void *b, void *sdata)
828{
829 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
830 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
831 const struct ThreadsContext *tctx = sdata;
832 ASSERT(ta->parent == tb->parent);
833
834 /* If c_sort ties, remember we are building the thread array in
835 * reverse from the index the mails had in the mailbox. */
836 struct Mailbox *m = tctx->mailbox_view->mailbox;
837 const enum MailboxType mtype = mx_type(m);
838 if (ta->parent)
839 {
840 return mutt_compare_emails(ta->sort_aux_key, tb->sort_aux_key, mtype,
842 }
843 else
844 {
847 }
848}
849
855static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
856{
857 struct MuttThread *thread = tctx->tree;
858 if (!thread)
859 return;
860
861 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
862 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
863 struct Email *oldsort_thread_key = NULL;
864 int i, array_size;
865 bool sort_top = false;
866
867 /* we put things into the array backwards to save some cycles,
868 * but we want to have to move less stuff around if we're
869 * resorting, so we sort backwards and then put them back
870 * in reverse order so they're forwards */
871 const bool reverse = (mutt_thread_style() == UT_REVERSE);
872 enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
873 enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
874 if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
875 {
876 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
877 c_sort = c_sort_aux;
878 }
879 c_sort ^= SORT_REVERSE;
880 c_sort_aux ^= SORT_REVERSE;
881 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
882 {
883 tctx->c_sort = c_sort;
884 tctx->c_sort_aux = c_sort_aux;
885 init = true;
886 }
887
888 top = thread;
889
890 array_size = 256;
891 array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
892 while (true)
893 {
894 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
895 {
896 thread->sort_thread_key = NULL;
897 thread->sort_aux_key = NULL;
898
899 if (thread->parent)
900 thread->parent->sort_children = true;
901 else
902 sort_top = true;
903 }
904
905 if (thread->child)
906 {
908 continue;
909 }
910 else
911 {
912 /* if it has no children, it must be real. sort it on its own merits */
915
916 if (thread->next)
917 {
918 thread = thread->next;
919 continue;
920 }
921 }
922
923 struct Mailbox *m = tctx->mailbox_view->mailbox;
924 const enum MailboxType mtype = mx_type(m);
925 while (!thread->next)
926 {
927 /* if it has siblings and needs to be sorted, sort it... */
928 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
929 {
930 /* put them into the array */
931 for (i = 0; thread; i++, thread = thread->prev)
932 {
933 if (i >= array_size)
934 {
935 array_size *= 2;
936 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
937 }
938
939 array[i] = thread;
940 }
941
942 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
943
944 /* attach them back together. make thread the last sibling. */
945 thread = array[0];
946 thread->next = NULL;
947 array[i - 1]->prev = NULL;
948
949 if (thread->parent)
950 thread->parent->child = array[i - 1];
951 else
952 top = array[i - 1];
953
954 while (--i)
955 {
956 array[i - 1]->prev = array[i];
957 array[i]->next = array[i - 1];
958 }
959 }
960
961 if (thread->parent)
962 {
963 tmp = thread;
964 thread = thread->parent;
965
966 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
967 {
968 /* we just sorted its children */
969 thread->sort_children = false;
970
971 oldsort_aux_key = thread->sort_aux_key;
972 oldsort_thread_key = thread->sort_thread_key;
973
974 /* update sort keys. sort_aux_key will be the first or last
975 * sibling, as appropriate... */
976 thread->sort_aux_key = thread->message;
977 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
978 thread->child->sort_aux_key :
979 tmp->sort_aux_key;
980
981 if (c_sort_aux & SORT_LAST)
982 {
983 if (!thread->sort_aux_key ||
984 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
985 c_sort_aux | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
986 {
987 thread->sort_aux_key = sort_aux_key;
988 }
989 }
990 else if (!thread->sort_aux_key)
991 {
992 thread->sort_aux_key = sort_aux_key;
993 }
994
995 /* ...but sort_thread_key may require searching the entire
996 * list of siblings */
997 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
998 {
999 thread->sort_thread_key = thread->sort_aux_key;
1000 }
1001 else
1002 {
1003 if (thread->message)
1004 {
1005 thread->sort_thread_key = thread->message;
1006 }
1007 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
1008 {
1009 thread->sort_thread_key = tmp->sort_thread_key;
1010 }
1011 else
1012 {
1013 thread->sort_thread_key = thread->child->sort_thread_key;
1014 }
1015 if (c_sort & SORT_LAST)
1016 {
1017 for (tmp = thread->child; tmp; tmp = tmp->next)
1018 {
1019 if (tmp->sort_thread_key == thread->sort_thread_key)
1020 continue;
1021 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key, mtype,
1022 c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
1023 {
1024 thread->sort_thread_key = tmp->sort_thread_key;
1025 }
1026 }
1027 }
1028 }
1029
1030 /* if a sort_key has changed, we need to resort it and siblings */
1031 if ((oldsort_aux_key != thread->sort_aux_key) ||
1032 (oldsort_thread_key != thread->sort_thread_key))
1033 {
1034 if (thread->parent)
1035 thread->parent->sort_children = true;
1036 else
1037 sort_top = true;
1038 }
1039 }
1040 }
1041 else
1042 {
1043 FREE(&array);
1044 tctx->tree = top;
1045 return;
1046 }
1047 }
1048
1049 thread = thread->next;
1050 }
1051}
1052
1058static void check_subjects(struct MailboxView *mv, bool init)
1059{
1060 if (!mv)
1061 return;
1062
1063 struct Mailbox *m = mv->mailbox;
1064 for (int i = 0; i < m->msg_count; i++)
1065 {
1066 struct Email *e = m->emails[i];
1067 if (!e || !e->thread)
1068 continue;
1069
1070 if (e->thread->check_subject)
1071 e->thread->check_subject = false;
1072 else if (!init)
1073 continue;
1074
1075 /* figure out which messages have subjects different than their parents' */
1076 struct MuttThread *tmp = e->thread->parent;
1077 while (tmp && !tmp->message)
1078 {
1079 tmp = tmp->parent;
1080 }
1081
1082 if (!tmp)
1083 {
1084 e->subject_changed = true;
1085 }
1086 else if (e->env->real_subj && tmp->message->env->real_subj)
1087 {
1089 }
1090 else
1091 {
1092 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1093 }
1094 }
1095}
1096
1100static void thread_hash_destructor(int type, void *obj, intptr_t data)
1101{
1102 FREE(&obj);
1103}
1104
1110void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
1111{
1112 if (!tctx || !tctx->mailbox_view)
1113 return;
1114
1115 struct MailboxView *mv = tctx->mailbox_view;
1116 struct Mailbox *m = mv->mailbox;
1117
1118 struct Email *e = NULL;
1119 int i, using_refs = 0;
1120 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1121 struct MuttThread top = { 0 };
1122 struct ListNode *ref = NULL;
1123
1124 ASSERT(m->msg_count > 0);
1125 if (!tctx->hash)
1126 init = true;
1127
1128 if (init)
1129 {
1132 }
1133
1134 /* we want a quick way to see if things are actually attached to the top of the
1135 * thread tree or if they're just dangling, so we attach everything to a top
1136 * node temporarily */
1137 top.parent = NULL;
1138 top.next = NULL;
1139 top.prev = NULL;
1140
1141 top.child = tctx->tree;
1142 for (thread = tctx->tree; thread; thread = thread->next)
1143 thread->parent = &top;
1144
1145 /* put each new message together with the matching messageless MuttThread if it
1146 * exists. otherwise, if there is a MuttThread that already has a message, thread
1147 * new message as an identical child. if we didn't attach the message to a
1148 * MuttThread, make a new one for it. */
1149 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1150 for (i = 0; i < m->msg_count; i++)
1151 {
1152 e = m->emails[i];
1153 if (!e)
1154 continue;
1155
1156 if (e->thread)
1157 {
1158 /* unlink pseudo-threads because they might be children of newly
1159 * arrived messages */
1160 thread = e->thread;
1161 for (tnew = thread->child; tnew;)
1162 {
1163 tmp = tnew->next;
1164 if (tnew->fake_thread)
1165 {
1166 unlink_message(&thread->child, tnew);
1167 insert_message(&top.child, &top, tnew);
1168 tnew->fake_thread = false;
1169 }
1170 tnew = tmp;
1171 }
1172 }
1173 else
1174 {
1175 if ((!init || c_duplicate_threads) && e->env->message_id)
1176 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1177 else
1178 thread = NULL;
1179
1180 if (thread && !thread->message)
1181 {
1182 /* this is a message which was missing before */
1183 thread->message = e;
1184 e->thread = thread;
1185 thread->check_subject = true;
1186
1187 /* mark descendants as needing subject_changed checked */
1188 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1189 {
1190 while (!tmp->message)
1191 tmp = tmp->child;
1192 tmp->check_subject = true;
1193 while (!tmp->next && (tmp != thread))
1194 tmp = tmp->parent;
1195 if (tmp != thread)
1196 tmp = tmp->next;
1197 }
1198
1199 if (thread->parent)
1200 {
1201 /* remove threading info above it based on its children, which we'll
1202 * recalculate based on its headers. make sure not to leave
1203 * dangling missing messages. note that we haven't kept track
1204 * of what info came from its children and what from its siblings'
1205 * children, so we just remove the stuff that's definitely from it */
1206 do
1207 {
1208 tmp = thread->parent;
1209 unlink_message(&tmp->child, thread);
1210 thread->parent = NULL;
1211 thread->sort_thread_key = NULL;
1212 thread->sort_aux_key = NULL;
1213 thread->fake_thread = false;
1214 thread = tmp;
1215 } while (thread != &top && !thread->child && !thread->message);
1216 }
1217 }
1218 else
1219 {
1220 tnew = (c_duplicate_threads ? thread : NULL);
1221
1222 thread = MUTT_MEM_CALLOC(1, struct MuttThread);
1223 thread->message = e;
1224 thread->check_subject = true;
1225 e->thread = thread;
1226 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1227
1228 if (tnew)
1229 {
1230 if (tnew->duplicate_thread)
1231 tnew = tnew->parent;
1232
1233 thread = e->thread;
1234
1235 insert_message(&tnew->child, tnew, thread);
1236 thread->duplicate_thread = true;
1237 thread->message->threaded = true;
1238 }
1239 }
1240 }
1241 }
1242
1243 /* thread by references */
1244 for (i = 0; i < m->msg_count; i++)
1245 {
1246 e = m->emails[i];
1247 if (!e)
1248 break;
1249
1250 if (e->threaded)
1251 continue;
1252 e->threaded = true;
1253
1254 thread = e->thread;
1255 if (!thread)
1256 continue;
1257 using_refs = 0;
1258
1259 while (true)
1260 {
1261 if (using_refs == 0)
1262 {
1263 /* look at the beginning of in-reply-to: */
1264 ref = STAILQ_FIRST(&e->env->in_reply_to);
1265 if (ref)
1266 {
1267 using_refs = 1;
1268 }
1269 else
1270 {
1271 ref = STAILQ_FIRST(&e->env->references);
1272 using_refs = 2;
1273 }
1274 }
1275 else if (using_refs == 1)
1276 {
1277 /* if there's no references header, use all the in-reply-to:
1278 * data that we have. otherwise, use the first reference
1279 * if it's different than the first in-reply-to, otherwise use
1280 * the second reference (since at least eudora puts the most
1281 * recent reference in in-reply-to and the rest in references) */
1282 if (STAILQ_EMPTY(&e->env->references))
1283 {
1284 ref = STAILQ_NEXT(ref, entries);
1285 }
1286 else
1287 {
1288 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1289 ref = STAILQ_FIRST(&e->env->references);
1290 else
1291 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1292
1293 using_refs = 2;
1294 }
1295 }
1296 else
1297 {
1298 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1299 }
1300
1301 if (!ref)
1302 break;
1303
1304 tnew = mutt_hash_find(tctx->hash, ref->data);
1305 if (tnew)
1306 {
1307 if (tnew->duplicate_thread)
1308 tnew = tnew->parent;
1309 if (is_descendant(tnew, thread)) /* no loops! */
1310 continue;
1311 }
1312 else
1313 {
1314 tnew = MUTT_MEM_CALLOC(1, struct MuttThread);
1315 mutt_hash_insert(tctx->hash, ref->data, tnew);
1316 }
1317
1318 if (thread->parent)
1319 unlink_message(&top.child, thread);
1320 insert_message(&tnew->child, tnew, thread);
1321 thread = tnew;
1322 if (thread->message || (thread->parent && (thread->parent != &top)))
1323 break;
1324 }
1325
1326 if (!thread->parent)
1327 insert_message(&top.child, &top, thread);
1328 }
1329
1330 /* detach everything from the temporary top node */
1331 for (thread = top.child; thread; thread = thread->next)
1332 {
1333 thread->parent = NULL;
1334 }
1335 tctx->tree = top.child;
1336
1337 check_subjects(mv, init);
1338
1339 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1340 if (!c_strict_threads)
1341 pseudo_threads(tctx);
1342
1343 /* if $sort_aux or similar changed after the mailbox is sorted, then
1344 * all the subthreads need to be resorted */
1345 if (tctx->tree)
1346 {
1348 OptSortSubthreads = false;
1349
1350 /* Put the list into an array. */
1351 linearize_tree(tctx);
1352
1353 /* Draw the thread tree. */
1354 mutt_draw_tree(tctx);
1355 }
1356}
1357
1366int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1367{
1368 if (!e)
1369 return -1;
1370
1371 struct MuttThread *cur = NULL;
1372 struct Email *e_tmp = NULL;
1373
1374 const enum UseThreads threaded = mutt_thread_style();
1375 if (threaded == UT_FLAT)
1376 {
1377 mutt_warning(_("Threading is not enabled"));
1378 return e->vnum;
1379 }
1380
1381 cur = e->thread;
1382
1383 if (subthreads)
1384 {
1385 if (forwards ^ (threaded == UT_REVERSE))
1386 {
1387 while (!cur->next && cur->parent)
1388 cur = cur->parent;
1389 }
1390 else
1391 {
1392 while (!cur->prev && cur->parent)
1393 cur = cur->parent;
1394 }
1395 }
1396 else
1397 {
1398 while (cur->parent)
1399 cur = cur->parent;
1400 }
1401
1402 if (forwards ^ (threaded == UT_REVERSE))
1403 {
1404 do
1405 {
1406 cur = cur->next;
1407 if (!cur)
1408 return -1;
1409 e_tmp = find_virtual(cur, false);
1410 } while (!e_tmp);
1411 }
1412 else
1413 {
1414 do
1415 {
1416 cur = cur->prev;
1417 if (!cur)
1418 return -1;
1419 e_tmp = find_virtual(cur, true);
1420 } while (!e_tmp);
1421 }
1422
1423 return e_tmp->vnum;
1424}
1425
1433int mutt_parent_message(struct Email *e, bool find_root)
1434{
1435 if (!e)
1436 return -1;
1437
1438 struct MuttThread *thread = NULL;
1439 struct Email *e_parent = NULL;
1440
1441 if (!mutt_using_threads())
1442 {
1443 mutt_warning(_("Threading is not enabled"));
1444 return e->vnum;
1445 }
1446
1447 /* Root may be the current message */
1448 if (find_root)
1449 e_parent = e;
1450
1451 for (thread = e->thread->parent; thread; thread = thread->parent)
1452 {
1453 e = thread->message;
1454 if (e)
1455 {
1456 e_parent = e;
1457 if (!find_root)
1458 break;
1459 }
1460 }
1461
1462 if (!e_parent)
1463 {
1464 mutt_error(_("Parent message is not available"));
1465 return -1;
1466 }
1467 if (!is_visible(e_parent))
1468 {
1469 if (find_root)
1470 mutt_error(_("Root message is not visible in this limited view"));
1471 else
1472 mutt_error(_("Parent message is not visible in this limited view"));
1473 return -1;
1474 }
1475 return e_parent->vnum;
1476}
1477
1483off_t mutt_set_vnum(struct Mailbox *m)
1484{
1485 if (!m)
1486 return 0;
1487
1488 off_t vsize = 0;
1489 const int padding = mx_msg_padding_size(m);
1490
1491 m->vcount = 0;
1492
1493 for (int i = 0; i < m->msg_count; i++)
1494 {
1495 struct Email *e = m->emails[i];
1496 if (!e)
1497 break;
1498
1499 if (e->vnum >= 0)
1500 {
1501 e->vnum = m->vcount;
1502 m->v2r[m->vcount] = i;
1503 m->vcount++;
1504 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1505 }
1506 }
1507
1508 return vsize;
1509}
1510
1518{
1519 struct MuttThread *thread = NULL, *top = NULL;
1520 struct Email *e_root = NULL;
1521 const enum UseThreads threaded = mutt_thread_style();
1522 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1523 int num_hidden = 0, new_mail = 0, old_mail = 0;
1524 bool flagged = false;
1525 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1526
1527 if (threaded == UT_FLAT)
1528 {
1529 mutt_warning(_("Threading is not enabled"));
1530 return e_cur->vnum;
1531 }
1532
1533 if (!e_cur->thread)
1534 {
1535 return e_cur->vnum;
1536 }
1537
1538 final = e_cur->vnum;
1539 thread = e_cur->thread;
1540 while (thread->parent)
1541 thread = thread->parent;
1542 top = thread;
1543 while (!thread->message)
1544 thread = thread->child;
1545 e_cur = thread->message;
1546 minmsgno = e_cur->msgno;
1547
1548 if (!e_cur->read && e_cur->visible)
1549 {
1550 if (e_cur->old)
1551 old_mail = 2;
1552 else
1553 new_mail = 1;
1554 if (e_cur->msgno < min_unread_msgno)
1555 {
1556 min_unread = e_cur->vnum;
1557 min_unread_msgno = e_cur->msgno;
1558 }
1559 }
1560
1561 if (e_cur->flagged && e_cur->visible)
1562 flagged = true;
1563
1564 if ((e_cur->vnum == -1) && e_cur->visible)
1565 num_hidden++;
1566
1568 {
1569 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1570 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1571 if (e_cur->vnum != -1)
1572 {
1573 e_root = e_cur;
1574 if (flag & MUTT_THREAD_COLLAPSE)
1575 final = e_root->vnum;
1576 }
1577 }
1578
1579 if ((thread == top) && !(thread = thread->child))
1580 {
1581 /* return value depends on action requested */
1583 {
1584 e_cur->num_hidden = num_hidden;
1585 return final;
1586 }
1587 if (flag & MUTT_THREAD_UNREAD)
1588 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1589 if (flag & MUTT_THREAD_NEXT_UNREAD)
1590 return min_unread;
1591 if (flag & MUTT_THREAD_FLAGGED)
1592 return flagged;
1593 }
1594
1595 while (true)
1596 {
1597 e_cur = thread->message;
1598
1599 if (e_cur)
1600 {
1602 {
1603 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1604 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1605 if (!e_root && e_cur->visible)
1606 {
1607 e_root = e_cur;
1608 if (flag & MUTT_THREAD_COLLAPSE)
1609 final = e_root->vnum;
1610 }
1611
1612 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1613 (e_cur->msgno < minmsgno) && e_cur->visible)
1614 {
1615 minmsgno = e_cur->msgno;
1616 final = e_cur->vnum;
1617 }
1618
1619 if (flag & MUTT_THREAD_COLLAPSE)
1620 {
1621 if (e_cur != e_root)
1622 e_cur->vnum = -1;
1623 }
1624 else
1625 {
1626 if (e_cur->visible)
1627 e_cur->vnum = e_cur->msgno;
1628 }
1629 }
1630
1631 if (!e_cur->read && e_cur->visible)
1632 {
1633 if (e_cur->old)
1634 old_mail = 2;
1635 else
1636 new_mail = 1;
1637 if (e_cur->msgno < min_unread_msgno)
1638 {
1639 min_unread = e_cur->vnum;
1640 min_unread_msgno = e_cur->msgno;
1641 }
1642 }
1643
1644 if (e_cur->flagged && e_cur->visible)
1645 flagged = true;
1646
1647 if ((e_cur->vnum == -1) && e_cur->visible)
1648 num_hidden++;
1649 }
1650
1651 if (thread->child)
1652 {
1653 thread = thread->child;
1654 }
1655 else if (thread->next)
1656 {
1657 thread = thread->next;
1658 }
1659 else
1660 {
1661 bool done = false;
1662 while (!thread->next)
1663 {
1664 thread = thread->parent;
1665 if (thread == top)
1666 {
1667 done = true;
1668 break;
1669 }
1670 }
1671 if (done)
1672 break;
1673 thread = thread->next;
1674 }
1675 }
1676
1677 /* re-traverse the thread and store num_hidden in all headers, with or
1678 * without a virtual index. this will allow ~v to match all collapsed
1679 * messages when switching sort order to non-threaded. */
1680 if (flag & MUTT_THREAD_COLLAPSE)
1681 {
1682 thread = top;
1683 while (true)
1684 {
1685 e_cur = thread->message;
1686 if (e_cur)
1687 e_cur->num_hidden = num_hidden + 1;
1688
1689 if (thread->child)
1690 {
1691 thread = thread->child;
1692 }
1693 else if (thread->next)
1694 {
1695 thread = thread->next;
1696 }
1697 else
1698 {
1699 bool done = false;
1700 while (!thread->next)
1701 {
1702 thread = thread->parent;
1703 if (thread == top)
1704 {
1705 done = true;
1706 break;
1707 }
1708 }
1709 if (done)
1710 break;
1711 thread = thread->next;
1712 }
1713 }
1714 }
1715
1716 /* return value depends on action requested */
1718 return final;
1719 if (flag & MUTT_THREAD_UNREAD)
1720 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1721 if (flag & MUTT_THREAD_NEXT_UNREAD)
1722 return min_unread;
1723 if (flag & MUTT_THREAD_FLAGGED)
1724 return flagged;
1725
1726 return 0;
1727}
1728
1736int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
1737{
1738 if (!m || !e)
1739 return 1;
1740
1741 struct MuttThread *threads[2];
1742 int rc;
1743
1744 const enum UseThreads threaded = mutt_thread_style();
1745 if ((threaded == UT_FLAT) || !e->thread)
1746 return 1;
1747
1748 threads[0] = e->thread;
1749 while (threads[0]->parent)
1750 threads[0] = threads[0]->parent;
1751
1752 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1753
1754 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1755 {
1756 while (!threads[i]->message)
1757 threads[i] = threads[i]->child;
1758 }
1759
1760 if (threaded == UT_REVERSE)
1761 {
1762 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1763 }
1764 else
1765 {
1766 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1767 threads[0]->message->msgno;
1768 }
1769
1770 if (mit == MIT_POSITION)
1771 rc += 1;
1772
1773 return rc;
1774}
1775
1782{
1783 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1784
1785 for (int i = 0; i < m->msg_count; i++)
1786 {
1787 struct Email *e = m->emails[i];
1788 if (!e || !e->env)
1789 continue;
1790
1791 if (e->env->message_id)
1792 mutt_hash_insert(hash, e->env->message_id, e);
1793 }
1794
1795 return hash;
1796}
1797
1805static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1806{
1807 if (child == parent)
1808 return false;
1809
1810 mutt_break_thread(child);
1812 mutt_set_flag(m, child, MUTT_TAG, false, true);
1813
1814 child->changed = true;
1815 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1816 return true;
1817}
1818
1826bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
1827{
1828 if (!parent || !children || !m)
1829 return false;
1830
1831 bool changed = false;
1832
1833 struct Email **ep = NULL;
1834 ARRAY_FOREACH(ep, children)
1835 {
1836 struct Email *e = *ep;
1837 changed |= link_threads(parent, e, m);
1838 }
1839
1840 return changed;
1841}
1842
1848{
1849 struct MuttThread *thread = NULL;
1850 struct MuttThread *top = tctx->tree;
1851 while ((thread = top))
1852 {
1853 while (!thread->message)
1854 thread = thread->child;
1855
1856 struct Email *e = thread->message;
1857 if (e->collapsed)
1859 top = top->next;
1860 }
1861}
1862
1868void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1869{
1870 struct MuttThread *thread = NULL;
1871 struct MuttThread *top = tctx->tree;
1872 while ((thread = top))
1873 {
1874 while (!thread->message)
1875 thread = thread->child;
1876
1877 struct Email *e = thread->message;
1878
1879 if (e->collapsed != collapse)
1880 {
1881 if (e->collapsed)
1883 else if (mutt_thread_can_collapse(e))
1885 }
1886 top = top->next;
1887 }
1888}
1889
1897{
1898 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1899 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1900 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1901 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1902}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition helpers.c:71
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition helpers.c:266
Convenience wrapper for the config headers.
#define CSR_ERR_INVALID
Value hasn't been set.
Definition set.h:36
#define CSR_SUCCESS
Action completed successfully.
Definition set.h:33
#define SORT_MASK
Mask for the sort id.
Definition sort.h:39
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition sort.h:41
#define SORT_REVERSE
Reverse the order of the sort.
Definition sort.h:40
Convenience wrapper for the core headers.
MailboxType
Supported mailbox formats.
Definition mailbox.h:40
Structs that make up an email.
EmailSortType
Methods for sorting Emails.
Definition sort.h:53
@ EMAIL_SORT_THREADS
Sort by email threads.
Definition sort.h:62
@ EMAIL_SORT_UNSORTED
Sort by the order the messages appear in the mailbox.
Definition sort.h:64
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition thread.c:66
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition thread.c:46
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
Insert a message into a thread.
Definition thread.c:104
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition thread.c:229
struct Email * find_virtual(struct MuttThread *cur, bool reverse)
Find an email with a Virtual message number.
Definition thread.c:124
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition envelope.h:34
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition flags.c:54
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition globals.c:57
Global variables.
int sort_validator(const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
Validate the "sort" config variable - Implements ConfigDef::validator() -.
Definition thread.c:105
static void thread_hash_destructor(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition thread.c:1100
#define mutt_warning(...)
Definition logging2.h:92
#define mutt_error(...)
Definition logging2.h:94
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
static int compare_threads(const void *a, const void *b, void *sdata)
Helper to sort email threads - Implements sort_t -.
Definition thread.c:827
int mutt_compare_emails(const struct Email *a, const struct Email *b, enum MailboxType type, short sort, short sort_aux)
Compare two emails using up to two sort methods -.
Definition sort.c:328
static void linearize_tree(struct ThreadsContext *tctx)
Flatten an email thread.
Definition thread.c:186
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition thread.c:239
void mutt_clear_threads(struct ThreadsContext *tctx)
Clear the threading of message in a mailbox.
Definition thread.c:797
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition thread.c:1517
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition thread.c:855
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
Toggle collapse.
Definition thread.c:1868
struct ThreadsContext * mutt_thread_ctx_init(struct MailboxView *mv)
Initialize a threading context.
Definition thread.c:370
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
Re-collapse threads marked as collapsed.
Definition thread.c:1847
static const struct Mapping UseThreadsMethods[]
Choices for '$use_threads' for the index.
Definition thread.c:50
bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
Forcibly link threads together.
Definition thread.c:1826
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition thread.c:465
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
Count the messages in a thread.
Definition thread.c:1736
static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
Create a sorted list of all subjects in a thread.
Definition thread.c:601
void mutt_thread_ctx_free(struct ThreadsContext **ptr)
Finalize a threading context.
Definition thread.c:381
const char * get_use_threads_str(enum UseThreads value)
Convert UseThreads enum to string.
Definition thread.c:97
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition thread.c:79
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition thread.c:726
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition thread.c:701
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition thread.c:1058
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
Sort email threads.
Definition thread.c:1110
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition thread.c:130
const struct EnumDef UseThreadsTypeDef
Data for the $use_threads enumeration.
Definition thread.c:64
off_t mutt_set_vnum(struct Mailbox *m)
Set the virtual index number of all the messages in a mailbox.
Definition thread.c:1483
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition thread.c:1366
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition thread.c:1896
static struct MuttThread * find_subject(struct Mailbox *m, struct MuttThread *cur)
Find the best possible match for a parent based on subject.
Definition thread.c:657
int mutt_parent_message(struct Email *e, bool find_root)
Find the parent of a message.
Definition thread.c:1433
static int thread_check_integrity(struct MuttThread *tree)
Verify and repair thread<->message back-pointers.
Definition thread.c:408
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for Message-IDs.
Definition thread.c:1781
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition thread.c:1805
static bool is_visible(struct Email *e)
Is the message visible?
Definition thread.c:120
Create/manipulate threading in emails.
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition thread.h:79
uint8_t MuttThreadFlags
Flags, e.g. MUTT_THREAD_COLLAPSE.
Definition thread.h:75
#define mutt_thread_contains_flagged(e)
Definition thread.h:109
UseThreads
Which threading style is active, $use_threads.
Definition thread.h:96
@ UT_FLAT
Unthreaded.
Definition thread.h:98
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition thread.h:97
@ UT_THREADS
Normal threading (root above subthreads)
Definition thread.h:99
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition thread.h:100
#define mutt_using_threads()
Definition thread.h:113
#define mutt_uncollapse_thread(e)
Definition thread.h:107
MessageInThread
Flags for mutt_messages_in_thread()
Definition thread.h:87
@ MIT_POSITION
Our position in the thread.
Definition thread.h:89
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition thread.h:78
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition thread.h:80
#define mutt_thread_contains_unread(e)
Definition thread.h:108
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition thread.h:77
#define mutt_collapse_thread(e)
Definition thread.h:106
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition thread.h:81
TreeChar
Tree characters for menus.
Definition thread.h:56
@ MUTT_TREE_LLCORNER
Lower left corner.
Definition thread.h:57
@ MUTT_TREE_RARROW
Right arrow.
Definition thread.h:63
@ MUTT_TREE_ULCORNER
Upper left corner.
Definition thread.h:58
@ MUTT_TREE_EQUALS
Equals (for threads)
Definition thread.h:66
@ MUTT_TREE_HIDDEN
Ampersand character (for threads)
Definition thread.h:65
@ MUTT_TREE_STAR
Star character (for threads)
Definition thread.h:64
@ MUTT_TREE_LTEE
Left T-piece.
Definition thread.h:59
@ MUTT_TREE_VLINE
Vertical line.
Definition thread.h:61
@ MUTT_TREE_MISSING
Question mark.
Definition thread.h:69
@ MUTT_TREE_TTEE
Top T-piece.
Definition thread.h:67
@ MUTT_TREE_HLINE
Horizontal line.
Definition thread.h:60
@ MUTT_TREE_SPACE
Blank space.
Definition thread.h:62
@ MUTT_TREE_BTEE
Bottom T-piece.
Definition thread.h:68
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition hash.c:337
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition hash.c:364
struct HashElem * mutt_hash_find_bucket(const struct HashTable *table, const char *strkey)
Find the HashElem in a Hash Table element using a key.
Definition hash.c:411
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:261
void mutt_hash_set_destructor(struct HashTable *table, hash_hdata_free_t fn, intptr_t fn_data)
Set the destructor for a Hash Table.
Definition hash.c:303
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:459
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition hash.h:111
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition hash.h:114
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition list.c:46
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition list.c:166
struct ListNode * mutt_list_insert_after(struct ListHead *h, struct ListNode *n, char *s)
Insert a string after a given ListNode.
Definition list.c:85
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
const char * mutt_map_get_name(int val, const struct Mapping *map)
Lookup a string for a constant.
Definition mapping.c:42
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition string.c:403
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:665
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition string.c:586
Many unsorted constants and some structs.
@ MUTT_TAG
Tagged messages.
Definition mutt.h:99
View of a Mailbox.
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition mx.c:1507
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition mx.c:1810
API for mailboxes.
void mutt_qsort_r(void *base, size_t nmemb, size_t size, sort_t compar, void *sdata)
Sort an array, where the comparator has access to opaque data rather than requiring global variables.
Definition qsort_r.c:72
#define STAILQ_HEAD_INITIALIZER(head)
Definition queue.h:324
#define STAILQ_FIRST(head)
Definition queue.h:388
#define STAILQ_FOREACH(var, head, field)
Definition queue.h:390
#define STAILQ_EMPTY(head)
Definition queue.h:382
#define STAILQ_NEXT(elm, field)
Definition queue.h:439
#define ASSERT(COND)
Definition signal2.h:59
LOFF_T offset
offset where the actual data begins
Definition body.h:52
LOFF_T length
length (in bytes) of attachment
Definition body.h:53
long hdr_offset
Offset in stream where the headers begin.
Definition body.h:81
String manipulation buffer.
Definition buffer.h:36
const char * name
User-visible name.
Definition set.h:66
The envelope/body of an email.
Definition email.h:39
bool read
Email is read.
Definition email.h:50
bool display_subject
Used for threading.
Definition email.h:101
bool visible
Is this message part of the view?
Definition email.h:121
struct Envelope * env
Envelope information.
Definition email.h:68
bool collapsed
Is this message part of a collapsed thread?
Definition email.h:120
struct Body * body
List of MIME parts.
Definition email.h:69
bool subject_changed
Used for threading.
Definition email.h:106
char * tree
Character string to print thread tree.
Definition email.h:125
bool old
Email is seen, but unread.
Definition email.h:49
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
Definition email.h:123
bool changed
Email has been edited.
Definition email.h:77
bool flagged
Marked important?
Definition email.h:47
bool threaded
Used for threading.
Definition email.h:108
const struct AttrColor * attr_color
Color-pair to use when displaying in the index.
Definition email.h:112
time_t date_sent
Time when the message was sent (UTC)
Definition email.h:60
int vnum
Virtual message number.
Definition email.h:114
int msgno
Number displayed to the user.
Definition email.h:111
int index
The absolute (unsorted) message number.
Definition email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition email.h:61
struct MuttThread * thread
Thread of Emails.
Definition email.h:119
An enumeration.
Definition enum.h:30
The header of an Email.
Definition envelope.h:57
char *const subject
Email's subject.
Definition envelope.h:70
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition envelope.h:90
char * message_id
Message ID.
Definition envelope.h:73
struct ListHead references
message references (in reverse order)
Definition envelope.h:83
struct ListHead in_reply_to
in-reply-to header content
Definition envelope.h:84
char *const real_subj
Offset of the real subject.
Definition envelope.h:71
The item stored in a Hash Table.
Definition hash.h:44
struct HashElem * next
Linked List.
Definition hash.h:48
void * data
User-supplied data.
Definition hash.h:47
A Hash Table.
Definition hash.h:99
A List node for strings.
Definition list.h:37
char * data
String.
Definition list.h:38
View of a Mailbox.
Definition mview.h:40
struct Mailbox * mailbox
Current Mailbox.
Definition mview.h:51
A mailbox.
Definition mailbox.h:78
int vcount
The number of virtual messages.
Definition mailbox.h:98
int * v2r
Mapping from virtual to real msgno.
Definition mailbox.h:97
int msg_count
Total number of messages.
Definition mailbox.h:87
struct HashTable * subj_hash
Hash Table: "Subject" -> Email.
Definition mailbox.h:123
struct Email ** emails
Array of Emails.
Definition mailbox.h:95
Mapping between user-readable string and a constant.
Definition mapping.h:33
int value
Integer value.
Definition mapping.h:35
An Email conversation.
Definition thread.h:34
bool sort_children
Sort the children.
Definition thread.h:40
bool visible
Is this Thread visible?
Definition thread.h:42
struct MuttThread * parent
Parent of this Thread.
Definition thread.h:44
struct Email * sort_aux_key
Email that controls how subthread siblings sort.
Definition thread.h:51
struct MuttThread * prev
Previous sibling Thread.
Definition thread.h:47
bool fake_thread
Emails grouped by Subject.
Definition thread.h:38
struct MuttThread * child
Child of this Thread.
Definition thread.h:45
struct Email * message
Email this Thread refers to.
Definition thread.h:49
bool deep
Is the Thread deeply nested?
Definition thread.h:36
unsigned int subtree_visible
Is this Thread subtree visible?
Definition thread.h:41
bool duplicate_thread
Duplicated Email in Thread.
Definition thread.h:37
bool next_subtree_visible
Is the next Thread subtree visible?
Definition thread.h:39
bool check_subject
Should the Subject be checked?
Definition thread.h:35
struct Email * sort_thread_key
Email that controls how top thread sorts.
Definition thread.h:50
struct MuttThread * next
Next sibling Thread.
Definition thread.h:46
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
The "current" threading state.
Definition thread.h:42
struct MailboxView * mailbox_view
Current mailbox.
Definition thread.h:43
struct MuttThread * tree
Top of thread tree.
Definition thread.h:44
enum EmailSortType c_sort
Last sort method.
Definition thread.h:46
struct HashTable * hash
Hash Table: "Message-ID" -> MuttThread.
Definition thread.h:45
enum EmailSortType c_sort_aux
Last sort_aux method.
Definition thread.h:47