Thanks to visit codestin.com
Credit goes to code.neomutt.org

NeoMutt  2025-12-11-189-gceedb6
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 the user disabled subject hiding, display it */
136 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
137 if (!c_hide_thread_subject)
138 return true;
139
140 /* if our subject is different from our parent's, display it */
141 if (e->subject_changed)
142 return true;
143
144 /* if our subject is different from that of our closest previously displayed
145 * sibling, display the subject */
146 for (tmp = tree->prev; tmp; tmp = tmp->prev)
147 {
148 e = tmp->message;
149 if (e && is_visible(e))
150 {
151 if (e->subject_changed)
152 return true;
153 break;
154 }
155 }
156
157 /* if there is a parent-to-child subject change anywhere between us and our
158 * closest displayed ancestor, display the subject */
159 for (tmp = tree->parent; tmp; tmp = tmp->parent)
160 {
161 e = tmp->message;
162 if (e)
163 {
164 if (is_visible(e))
165 return false;
166 if (e->subject_changed)
167 return true;
168 }
169 }
170
171 /* if we have no visible parent or previous sibling, display the subject */
172 return true;
173}
174
179static void linearize_tree(struct ThreadsContext *tctx)
180{
181 if (!tctx || !tctx->mailbox_view)
182 return;
183
184 struct Mailbox *m = tctx->mailbox_view->mailbox;
185
186 const bool reverse = (mutt_thread_style() == UT_REVERSE);
187 struct MuttThread *tree = tctx->tree;
188 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
189
190 while (tree)
191 {
192 while (!tree->message)
193 tree = tree->child;
194
195 *array = tree->message;
196 array += reverse ? -1 : 1;
197
198 if (tree->child)
199 {
200 tree = tree->child;
201 }
202 else
203 {
204 while (tree)
205 {
206 if (tree->next)
207 {
208 tree = tree->next;
209 break;
210 }
211 else
212 {
213 tree = tree->parent;
214 }
215 }
216 }
217 }
218}
219
232static void calculate_visibility(struct MuttThread *tree, int *max_depth)
233{
234 if (!tree)
235 return;
236
237 struct MuttThread *tmp = NULL;
238 struct MuttThread *orig_tree = tree;
239 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
240 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
241 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
242 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
243 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
244 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
245 int depth = 0;
246
247 /* we walk each level backwards to make it easier to compute next_subtree_visible */
248 while (tree->next)
249 tree = tree->next;
250 *max_depth = 0;
251
252 while (true)
253 {
254 if (depth > *max_depth)
255 *max_depth = depth;
256
257 tree->subtree_visible = 0;
258 if (tree->message)
259 {
260 FREE(&tree->message->tree);
261 if (is_visible(tree->message))
262 {
263 tree->deep = true;
264 tree->visible = true;
266 for (tmp = tree; tmp; tmp = tmp->parent)
267 {
268 if (tmp->subtree_visible)
269 {
270 tmp->deep = true;
271 tmp->subtree_visible = 2;
272 break;
273 }
274 else
275 {
276 tmp->subtree_visible = 1;
277 }
278 }
279 }
280 else
281 {
282 tree->visible = false;
283 tree->deep = !c_hide_limited;
284 }
285 }
286 else
287 {
288 tree->visible = false;
289 tree->deep = !c_hide_missing;
290 }
291 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
292 tree->next->subtree_visible);
293 if (tree->child)
294 {
295 depth++;
296 tree = tree->child;
297 while (tree->next)
298 tree = tree->next;
299 }
300 else if (tree->prev)
301 {
302 tree = tree->prev;
303 }
304 else
305 {
306 while (tree && !tree->prev)
307 {
308 depth--;
309 tree = tree->parent;
310 }
311 if (!tree)
312 break;
313 tree = tree->prev;
314 }
315 }
316
317 /* now fix up for the OPTHIDETOP* options if necessary */
318 if (hide_top_limited || hide_top_missing)
319 {
320 tree = orig_tree;
321 while (true)
322 {
323 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
324 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
325 {
326 tree->deep = false;
327 }
328 if (!tree->deep && tree->child && tree->subtree_visible)
329 {
330 tree = tree->child;
331 }
332 else if (tree->next)
333 {
334 tree = tree->next;
335 }
336 else
337 {
338 while (tree && !tree->next)
339 tree = tree->parent;
340 if (!tree)
341 break;
342 tree = tree->next;
343 }
344 }
345 }
346}
347
354{
355 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
356 tctx->mailbox_view = mv;
357 return tctx;
358}
359
365{
366 if (!ptr || !*ptr)
367 {
368 return;
369 }
370
371 struct ThreadsContext *tctx = *ptr;
372
373 mutt_hash_free(&tctx->hash);
374
375 FREE(ptr);
376}
377
391{
392 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
393 const bool reverse = (mutt_thread_style() == UT_REVERSE);
394 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
395 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
396 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
397 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
398 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
399
400 struct MuttThread *tree = tctx->tree;
401
402 /* Do the visibility calculations and free the old thread chars.
403 * From now on we can simply ignore invisible subtrees */
404 calculate_visibility(tree, &max_depth);
405 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
406 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
407 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
408 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
409 while (tree)
410 {
411 if (depth != 0)
412 {
413 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
414 if (start_depth == depth)
415 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
416 else if (parent->message && !c_hide_limited)
417 myarrow[0] = MUTT_TREE_HIDDEN;
418 else if (!parent->message && !c_hide_missing)
419 myarrow[0] = MUTT_TREE_MISSING;
420 else
421 myarrow[0] = vtee;
422 if (width == 2)
423 {
424 myarrow[1] = pseudo ? MUTT_TREE_STAR :
426 }
427 if (tree->visible)
428 {
429 myarrow[width] = MUTT_TREE_RARROW;
430 myarrow[width + 1] = 0;
431 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
432 if (start_depth > 1)
433 {
434 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
435 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
436 (1 + depth - start_depth) * width + 2);
437 }
438 else
439 {
440 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
441 }
442 tree->message->tree = new_tree;
443 }
444 }
445 if (tree->child && (depth != 0))
446 {
447 mypfx = pfx + (depth - 1) * width;
448 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
449 if (width == 2)
450 mypfx[1] = MUTT_TREE_SPACE;
451 }
452 parent = tree;
453 nextdisp = NULL;
454 pseudo = NULL;
455 do
456 {
457 if (tree->child && tree->subtree_visible)
458 {
459 if (tree->deep)
460 depth++;
461 if (tree->visible)
462 start_depth = depth;
463 tree = tree->child;
464
465 /* we do this here because we need to make sure that the first child thread
466 * of the old tree that we deal with is actually displayed if any are,
467 * or we might set the parent variable wrong while going through it. */
468 while (!tree->subtree_visible && tree->next)
469 tree = tree->next;
470 }
471 else
472 {
473 while (!tree->next && tree->parent)
474 {
475 if (tree == pseudo)
476 pseudo = NULL;
477 if (tree == nextdisp)
478 nextdisp = NULL;
479 if (tree->visible)
480 start_depth = depth;
481 tree = tree->parent;
482 if (tree->deep)
483 {
484 if (start_depth == depth)
485 start_depth--;
486 depth--;
487 }
488 }
489 if (tree == pseudo)
490 pseudo = NULL;
491 if (tree == nextdisp)
492 nextdisp = NULL;
493 if (tree->visible)
494 start_depth = depth;
495 tree = tree->next;
496 if (!tree)
497 break;
498 }
499 if (!pseudo && tree->fake_thread)
500 pseudo = tree;
501 if (!nextdisp && tree->next_subtree_visible)
502 nextdisp = tree;
503 } while (!tree->deep);
504 }
505
506 FREE(&pfx);
507 FREE(&arrow);
508}
509
520static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
521{
522 struct MuttThread *start = cur;
523 struct Envelope *env = NULL;
524 time_t thisdate;
525 int rc = 0;
526
527 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
528 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
529 while (true)
530 {
531 while (!cur->message)
532 cur = cur->child;
533
534 if (dateptr)
535 {
536 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
537 if ((*dateptr == 0) || (thisdate < *dateptr))
538 *dateptr = thisdate;
539 }
540
541 env = cur->message->env;
542 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
543 {
544 struct ListNode *np = NULL;
545 STAILQ_FOREACH(np, subjects, entries)
546 {
547 rc = mutt_str_cmp(env->real_subj, np->data);
548 if (rc >= 0)
549 break;
550 }
551 if (!np)
552 mutt_list_insert_head(subjects, env->real_subj);
553 else if (rc > 0)
554 mutt_list_insert_after(subjects, np, env->real_subj);
555 }
556
557 while (!cur->next && (cur != start))
558 {
559 cur = cur->parent;
560 }
561 if (cur == start)
562 break;
563 cur = cur->next;
564 }
565}
566
576static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
577{
578 if (!m)
579 return NULL;
580
581 struct HashElem *he = NULL;
582 struct MuttThread *tmp = NULL, *last = NULL;
583 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
584 time_t date = 0;
585
586 make_subject_list(&subjects, cur, &date);
587
588 struct ListNode *np = NULL;
589 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
590 STAILQ_FOREACH(np, &subjects, entries)
591 {
592 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
593 {
594 tmp = ((struct Email *) he->data)->thread;
595 if ((tmp != cur) && /* don't match the same message */
596 !tmp->fake_thread && /* don't match pseudo threads */
597 tmp->message->subject_changed && /* only match interesting replies */
598 !is_descendant(tmp, cur) && /* don't match in the same thread */
599 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
600 (!last || (c_thread_received ?
601 (last->message->received < tmp->message->received) :
602 (last->message->date_sent < tmp->message->date_sent))) &&
603 tmp->message->env->real_subj &&
605 {
606 last = tmp; /* best match so far */
607 }
608 }
609 }
610
611 mutt_list_clear(&subjects);
612 return last;
613}
614
620static struct HashTable *make_subj_hash(struct Mailbox *m)
621{
622 if (!m)
623 return NULL;
624
626
627 for (int i = 0; i < m->msg_count; i++)
628 {
629 struct Email *e = m->emails[i];
630 if (!e || !e->env)
631 continue;
632 if (e->env->real_subj)
633 mutt_hash_insert(hash, e->env->real_subj, e);
634 }
635
636 return hash;
637}
638
645static void pseudo_threads(struct ThreadsContext *tctx)
646{
647 if (!tctx || !tctx->mailbox_view)
648 return;
649
650 struct Mailbox *m = tctx->mailbox_view->mailbox;
651
652 struct MuttThread *tree = tctx->tree;
653 struct MuttThread *top = tree;
654 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
655 *nextchild = NULL;
656
657 if (!m->subj_hash)
659
660 while (tree)
661 {
662 cur = tree;
663 tree = tree->next;
664 parent = find_subject(m, cur);
665 if (parent)
666 {
667 cur->fake_thread = true;
668 unlink_message(&top, cur);
670 parent->sort_children = true;
671 tmp = cur;
672 while (true)
673 {
674 while (!tmp->message)
675 tmp = tmp->child;
676
677 /* if the message we're attaching has pseudo-children, they
678 * need to be attached to its parent, so move them up a level.
679 * but only do this if they have the same real subject as the
680 * parent, since otherwise they rightly belong to the message
681 * we're attaching. */
682 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
684 {
685 tmp->message->subject_changed = false;
686
687 for (curchild = tmp->child; curchild;)
688 {
689 nextchild = curchild->next;
690 if (curchild->fake_thread)
691 {
692 unlink_message(&tmp->child, curchild);
693 insert_message(&parent->child, parent, curchild);
694 }
695 curchild = nextchild;
696 }
697 }
698
699 while (!tmp->next && (tmp != cur))
700 {
701 tmp = tmp->parent;
702 }
703 if (tmp == cur)
704 break;
705 tmp = tmp->next;
706 }
707 }
708 }
709 tctx->tree = top;
710}
711
717{
718 if (!tctx || !tctx->tree)
719 return;
720
721 struct MailboxView *mv = tctx->mailbox_view;
722 if (!mv)
723 return;
724
725 struct Mailbox *m = mv->mailbox;
726 if (!m || !m->emails)
727 return;
728
729 for (int i = 0; i < m->msg_count; i++)
730 {
731 struct Email *e = m->emails[i];
732 if (!e)
733 break;
734
735 /* mailbox may have been only partially read */
736 e->thread = NULL;
737 e->threaded = false;
738 }
739 tctx->tree = NULL;
740 mutt_hash_free(&tctx->hash);
741}
742
746static int compare_threads(const void *a, const void *b, void *sdata)
747{
748 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
749 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
750 const struct ThreadsContext *tctx = sdata;
751 ASSERT(ta->parent == tb->parent);
752
753 /* If c_sort ties, remember we are building the thread array in
754 * reverse from the index the mails had in the mailbox. */
755 struct Mailbox *m = tctx->mailbox_view->mailbox;
756 const enum MailboxType mtype = mx_type(m);
757 if (ta->parent)
758 {
759 return mutt_compare_emails(ta->sort_aux_key, tb->sort_aux_key, mtype,
761 }
762 else
763 {
766 }
767}
768
774static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
775{
776 struct MuttThread *thread = tctx->tree;
777 if (!thread)
778 return;
779
780 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
781 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
782 struct Email *oldsort_thread_key = NULL;
783 int i, array_size;
784 bool sort_top = false;
785
786 /* we put things into the array backwards to save some cycles,
787 * but we want to have to move less stuff around if we're
788 * resorting, so we sort backwards and then put them back
789 * in reverse order so they're forwards */
790 const bool reverse = (mutt_thread_style() == UT_REVERSE);
791 enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
792 enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
793 if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
794 {
795 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
796 c_sort = c_sort_aux;
797 }
798 c_sort ^= SORT_REVERSE;
799 c_sort_aux ^= SORT_REVERSE;
800 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
801 {
802 tctx->c_sort = c_sort;
803 tctx->c_sort_aux = c_sort_aux;
804 init = true;
805 }
806
807 top = thread;
808
809 array_size = 256;
810 array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
811 while (true)
812 {
813 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
814 {
815 thread->sort_thread_key = NULL;
816 thread->sort_aux_key = NULL;
817
818 if (thread->parent)
819 thread->parent->sort_children = true;
820 else
821 sort_top = true;
822 }
823
824 if (thread->child)
825 {
827 continue;
828 }
829 else
830 {
831 /* if it has no children, it must be real. sort it on its own merits */
834
835 if (thread->next)
836 {
837 thread = thread->next;
838 continue;
839 }
840 }
841
842 struct Mailbox *m = tctx->mailbox_view->mailbox;
843 const enum MailboxType mtype = mx_type(m);
844 while (!thread->next)
845 {
846 /* if it has siblings and needs to be sorted, sort it... */
847 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
848 {
849 /* put them into the array */
850 for (i = 0; thread; i++, thread = thread->prev)
851 {
852 if (i >= array_size)
853 {
854 array_size *= 2;
855 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
856 }
857
858 array[i] = thread;
859 }
860
861 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
862
863 /* attach them back together. make thread the last sibling. */
864 thread = array[0];
865 thread->next = NULL;
866 array[i - 1]->prev = NULL;
867
868 if (thread->parent)
869 thread->parent->child = array[i - 1];
870 else
871 top = array[i - 1];
872
873 while (--i)
874 {
875 array[i - 1]->prev = array[i];
876 array[i]->next = array[i - 1];
877 }
878 }
879
880 if (thread->parent)
881 {
882 tmp = thread;
883 thread = thread->parent;
884
885 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
886 {
887 /* we just sorted its children */
888 thread->sort_children = false;
889
890 oldsort_aux_key = thread->sort_aux_key;
891 oldsort_thread_key = thread->sort_thread_key;
892
893 /* update sort keys. sort_aux_key will be the first or last
894 * sibling, as appropriate... */
895 thread->sort_aux_key = thread->message;
896 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
897 thread->child->sort_aux_key :
898 tmp->sort_aux_key;
899
900 if (c_sort_aux & SORT_LAST)
901 {
902 if (!thread->sort_aux_key ||
903 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
904 c_sort_aux | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
905 {
906 thread->sort_aux_key = sort_aux_key;
907 }
908 }
909 else if (!thread->sort_aux_key)
910 {
911 thread->sort_aux_key = sort_aux_key;
912 }
913
914 /* ...but sort_thread_key may require searching the entire
915 * list of siblings */
916 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
917 {
918 thread->sort_thread_key = thread->sort_aux_key;
919 }
920 else
921 {
922 if (thread->message)
923 {
924 thread->sort_thread_key = thread->message;
925 }
926 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
927 {
928 thread->sort_thread_key = tmp->sort_thread_key;
929 }
930 else
931 {
932 thread->sort_thread_key = thread->child->sort_thread_key;
933 }
934 if (c_sort & SORT_LAST)
935 {
936 for (tmp = thread->child; tmp; tmp = tmp->next)
937 {
938 if (tmp->sort_thread_key == thread->sort_thread_key)
939 continue;
940 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key, mtype,
941 c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
942 {
943 thread->sort_thread_key = tmp->sort_thread_key;
944 }
945 }
946 }
947 }
948
949 /* if a sort_key has changed, we need to resort it and siblings */
950 if ((oldsort_aux_key != thread->sort_aux_key) ||
951 (oldsort_thread_key != thread->sort_thread_key))
952 {
953 if (thread->parent)
954 thread->parent->sort_children = true;
955 else
956 sort_top = true;
957 }
958 }
959 }
960 else
961 {
962 FREE(&array);
963 tctx->tree = top;
964 return;
965 }
966 }
967
968 thread = thread->next;
969 }
970}
971
977static void check_subjects(struct MailboxView *mv, bool init)
978{
979 if (!mv)
980 return;
981
982 struct Mailbox *m = mv->mailbox;
983 for (int i = 0; i < m->msg_count; i++)
984 {
985 struct Email *e = m->emails[i];
986 if (!e || !e->thread)
987 continue;
988
989 if (e->thread->check_subject)
990 e->thread->check_subject = false;
991 else if (!init)
992 continue;
993
994 /* figure out which messages have subjects different than their parents' */
995 struct MuttThread *tmp = e->thread->parent;
996 while (tmp && !tmp->message)
997 {
998 tmp = tmp->parent;
999 }
1000
1001 if (!tmp)
1002 {
1003 e->subject_changed = true;
1004 }
1005 else if (e->env->real_subj && tmp->message->env->real_subj)
1006 {
1008 }
1009 else
1010 {
1011 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1012 }
1013 }
1014}
1015
1019static void thread_hash_destructor(int type, void *obj, intptr_t data)
1020{
1021 FREE(&obj);
1022}
1023
1029void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
1030{
1031 if (!tctx || !tctx->mailbox_view)
1032 return;
1033
1034 struct MailboxView *mv = tctx->mailbox_view;
1035 struct Mailbox *m = mv->mailbox;
1036
1037 struct Email *e = NULL;
1038 int i, using_refs = 0;
1039 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1040 struct MuttThread top = { 0 };
1041 struct ListNode *ref = NULL;
1042
1043 ASSERT(m->msg_count > 0);
1044 if (!tctx->hash)
1045 init = true;
1046
1047 if (init)
1048 {
1051 }
1052
1053 /* we want a quick way to see if things are actually attached to the top of the
1054 * thread tree or if they're just dangling, so we attach everything to a top
1055 * node temporarily */
1056 top.parent = NULL;
1057 top.next = NULL;
1058 top.prev = NULL;
1059
1060 top.child = tctx->tree;
1061 for (thread = tctx->tree; thread; thread = thread->next)
1062 thread->parent = &top;
1063
1064 /* put each new message together with the matching messageless MuttThread if it
1065 * exists. otherwise, if there is a MuttThread that already has a message, thread
1066 * new message as an identical child. if we didn't attach the message to a
1067 * MuttThread, make a new one for it. */
1068 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1069 for (i = 0; i < m->msg_count; i++)
1070 {
1071 e = m->emails[i];
1072 if (!e)
1073 continue;
1074
1075 if (e->thread)
1076 {
1077 /* unlink pseudo-threads because they might be children of newly
1078 * arrived messages */
1079 thread = e->thread;
1080 for (tnew = thread->child; tnew;)
1081 {
1082 tmp = tnew->next;
1083 if (tnew->fake_thread)
1084 {
1085 unlink_message(&thread->child, tnew);
1086 insert_message(&top.child, &top, tnew);
1087 tnew->fake_thread = false;
1088 }
1089 tnew = tmp;
1090 }
1091 }
1092 else
1093 {
1094 if ((!init || c_duplicate_threads) && e->env->message_id)
1095 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1096 else
1097 thread = NULL;
1098
1099 if (thread && !thread->message)
1100 {
1101 /* this is a message which was missing before */
1102 thread->message = e;
1103 e->thread = thread;
1104 thread->check_subject = true;
1105
1106 /* mark descendants as needing subject_changed checked */
1107 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1108 {
1109 while (!tmp->message)
1110 tmp = tmp->child;
1111 tmp->check_subject = true;
1112 while (!tmp->next && (tmp != thread))
1113 tmp = tmp->parent;
1114 if (tmp != thread)
1115 tmp = tmp->next;
1116 }
1117
1118 if (thread->parent)
1119 {
1120 /* remove threading info above it based on its children, which we'll
1121 * recalculate based on its headers. make sure not to leave
1122 * dangling missing messages. note that we haven't kept track
1123 * of what info came from its children and what from its siblings'
1124 * children, so we just remove the stuff that's definitely from it */
1125 do
1126 {
1127 tmp = thread->parent;
1128 unlink_message(&tmp->child, thread);
1129 thread->parent = NULL;
1130 thread->sort_thread_key = NULL;
1131 thread->sort_aux_key = NULL;
1132 thread->fake_thread = false;
1133 thread = tmp;
1134 } while (thread != &top && !thread->child && !thread->message);
1135 }
1136 }
1137 else
1138 {
1139 tnew = (c_duplicate_threads ? thread : NULL);
1140
1141 thread = MUTT_MEM_CALLOC(1, struct MuttThread);
1142 thread->message = e;
1143 thread->check_subject = true;
1144 e->thread = thread;
1145 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1146
1147 if (tnew)
1148 {
1149 if (tnew->duplicate_thread)
1150 tnew = tnew->parent;
1151
1152 thread = e->thread;
1153
1154 insert_message(&tnew->child, tnew, thread);
1155 thread->duplicate_thread = true;
1156 thread->message->threaded = true;
1157 }
1158 }
1159 }
1160 }
1161
1162 /* thread by references */
1163 for (i = 0; i < m->msg_count; i++)
1164 {
1165 e = m->emails[i];
1166 if (!e)
1167 break;
1168
1169 if (e->threaded)
1170 continue;
1171 e->threaded = true;
1172
1173 thread = e->thread;
1174 if (!thread)
1175 continue;
1176 using_refs = 0;
1177
1178 while (true)
1179 {
1180 if (using_refs == 0)
1181 {
1182 /* look at the beginning of in-reply-to: */
1183 ref = STAILQ_FIRST(&e->env->in_reply_to);
1184 if (ref)
1185 {
1186 using_refs = 1;
1187 }
1188 else
1189 {
1190 ref = STAILQ_FIRST(&e->env->references);
1191 using_refs = 2;
1192 }
1193 }
1194 else if (using_refs == 1)
1195 {
1196 /* if there's no references header, use all the in-reply-to:
1197 * data that we have. otherwise, use the first reference
1198 * if it's different than the first in-reply-to, otherwise use
1199 * the second reference (since at least eudora puts the most
1200 * recent reference in in-reply-to and the rest in references) */
1201 if (STAILQ_EMPTY(&e->env->references))
1202 {
1203 ref = STAILQ_NEXT(ref, entries);
1204 }
1205 else
1206 {
1207 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1208 ref = STAILQ_FIRST(&e->env->references);
1209 else
1210 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1211
1212 using_refs = 2;
1213 }
1214 }
1215 else
1216 {
1217 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1218 }
1219
1220 if (!ref)
1221 break;
1222
1223 tnew = mutt_hash_find(tctx->hash, ref->data);
1224 if (tnew)
1225 {
1226 if (tnew->duplicate_thread)
1227 tnew = tnew->parent;
1228 if (is_descendant(tnew, thread)) /* no loops! */
1229 continue;
1230 }
1231 else
1232 {
1233 tnew = MUTT_MEM_CALLOC(1, struct MuttThread);
1234 mutt_hash_insert(tctx->hash, ref->data, tnew);
1235 }
1236
1237 if (thread->parent)
1238 unlink_message(&top.child, thread);
1239 insert_message(&tnew->child, tnew, thread);
1240 thread = tnew;
1241 if (thread->message || (thread->parent && (thread->parent != &top)))
1242 break;
1243 }
1244
1245 if (!thread->parent)
1246 insert_message(&top.child, &top, thread);
1247 }
1248
1249 /* detach everything from the temporary top node */
1250 for (thread = top.child; thread; thread = thread->next)
1251 {
1252 thread->parent = NULL;
1253 }
1254 tctx->tree = top.child;
1255
1256 check_subjects(mv, init);
1257
1258 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1259 if (!c_strict_threads)
1260 pseudo_threads(tctx);
1261
1262 /* if $sort_aux or similar changed after the mailbox is sorted, then
1263 * all the subthreads need to be resorted */
1264 if (tctx->tree)
1265 {
1267 OptSortSubthreads = false;
1268
1269 /* Put the list into an array. */
1270 linearize_tree(tctx);
1271
1272 /* Draw the thread tree. */
1273 mutt_draw_tree(tctx);
1274 }
1275}
1276
1285int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1286{
1287 if (!e)
1288 return -1;
1289
1290 struct MuttThread *cur = NULL;
1291 struct Email *e_tmp = NULL;
1292
1293 const enum UseThreads threaded = mutt_thread_style();
1294 if (threaded == UT_FLAT)
1295 {
1296 mutt_error(_("Threading is not enabled"));
1297 return e->vnum;
1298 }
1299
1300 cur = e->thread;
1301
1302 if (subthreads)
1303 {
1304 if (forwards ^ (threaded == UT_REVERSE))
1305 {
1306 while (!cur->next && cur->parent)
1307 cur = cur->parent;
1308 }
1309 else
1310 {
1311 while (!cur->prev && cur->parent)
1312 cur = cur->parent;
1313 }
1314 }
1315 else
1316 {
1317 while (cur->parent)
1318 cur = cur->parent;
1319 }
1320
1321 if (forwards ^ (threaded == UT_REVERSE))
1322 {
1323 do
1324 {
1325 cur = cur->next;
1326 if (!cur)
1327 return -1;
1328 e_tmp = find_virtual(cur, false);
1329 } while (!e_tmp);
1330 }
1331 else
1332 {
1333 do
1334 {
1335 cur = cur->prev;
1336 if (!cur)
1337 return -1;
1338 e_tmp = find_virtual(cur, true);
1339 } while (!e_tmp);
1340 }
1341
1342 return e_tmp->vnum;
1343}
1344
1352int mutt_parent_message(struct Email *e, bool find_root)
1353{
1354 if (!e)
1355 return -1;
1356
1357 struct MuttThread *thread = NULL;
1358 struct Email *e_parent = NULL;
1359
1360 if (!mutt_using_threads())
1361 {
1362 mutt_error(_("Threading is not enabled"));
1363 return e->vnum;
1364 }
1365
1366 /* Root may be the current message */
1367 if (find_root)
1368 e_parent = e;
1369
1370 for (thread = e->thread->parent; thread; thread = thread->parent)
1371 {
1372 e = thread->message;
1373 if (e)
1374 {
1375 e_parent = e;
1376 if (!find_root)
1377 break;
1378 }
1379 }
1380
1381 if (!e_parent)
1382 {
1383 mutt_error(_("Parent message is not available"));
1384 return -1;
1385 }
1386 if (!is_visible(e_parent))
1387 {
1388 if (find_root)
1389 mutt_error(_("Root message is not visible in this limited view"));
1390 else
1391 mutt_error(_("Parent message is not visible in this limited view"));
1392 return -1;
1393 }
1394 return e_parent->vnum;
1395}
1396
1402off_t mutt_set_vnum(struct Mailbox *m)
1403{
1404 if (!m)
1405 return 0;
1406
1407 off_t vsize = 0;
1408 const int padding = mx_msg_padding_size(m);
1409
1410 m->vcount = 0;
1411
1412 for (int i = 0; i < m->msg_count; i++)
1413 {
1414 struct Email *e = m->emails[i];
1415 if (!e)
1416 break;
1417
1418 if (e->vnum >= 0)
1419 {
1420 e->vnum = m->vcount;
1421 m->v2r[m->vcount] = i;
1422 m->vcount++;
1423 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1424 }
1425 }
1426
1427 return vsize;
1428}
1429
1437{
1438 struct MuttThread *thread = NULL, *top = NULL;
1439 struct Email *e_root = NULL;
1440 const enum UseThreads threaded = mutt_thread_style();
1441 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1442 int num_hidden = 0, new_mail = 0, old_mail = 0;
1443 bool flagged = false;
1444 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1445
1446 if (threaded == UT_FLAT)
1447 {
1448 mutt_error(_("Threading is not enabled"));
1449 return e_cur->vnum;
1450 }
1451
1452 if (!e_cur->thread)
1453 {
1454 return e_cur->vnum;
1455 }
1456
1457 final = e_cur->vnum;
1458 thread = e_cur->thread;
1459 while (thread->parent)
1460 thread = thread->parent;
1461 top = thread;
1462 while (!thread->message)
1463 thread = thread->child;
1464 e_cur = thread->message;
1465 minmsgno = e_cur->msgno;
1466
1467 if (!e_cur->read && e_cur->visible)
1468 {
1469 if (e_cur->old)
1470 old_mail = 2;
1471 else
1472 new_mail = 1;
1473 if (e_cur->msgno < min_unread_msgno)
1474 {
1475 min_unread = e_cur->vnum;
1476 min_unread_msgno = e_cur->msgno;
1477 }
1478 }
1479
1480 if (e_cur->flagged && e_cur->visible)
1481 flagged = true;
1482
1483 if ((e_cur->vnum == -1) && e_cur->visible)
1484 num_hidden++;
1485
1487 {
1488 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1489 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1490 if (e_cur->vnum != -1)
1491 {
1492 e_root = e_cur;
1493 if (flag & MUTT_THREAD_COLLAPSE)
1494 final = e_root->vnum;
1495 }
1496 }
1497
1498 if ((thread == top) && !(thread = thread->child))
1499 {
1500 /* return value depends on action requested */
1502 {
1503 e_cur->num_hidden = num_hidden;
1504 return final;
1505 }
1506 if (flag & MUTT_THREAD_UNREAD)
1507 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1508 if (flag & MUTT_THREAD_NEXT_UNREAD)
1509 return min_unread;
1510 if (flag & MUTT_THREAD_FLAGGED)
1511 return flagged;
1512 }
1513
1514 while (true)
1515 {
1516 e_cur = thread->message;
1517
1518 if (e_cur)
1519 {
1521 {
1522 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1523 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1524 if (!e_root && e_cur->visible)
1525 {
1526 e_root = e_cur;
1527 if (flag & MUTT_THREAD_COLLAPSE)
1528 final = e_root->vnum;
1529 }
1530
1531 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1532 (e_cur->msgno < minmsgno) && e_cur->visible)
1533 {
1534 minmsgno = e_cur->msgno;
1535 final = e_cur->vnum;
1536 }
1537
1538 if (flag & MUTT_THREAD_COLLAPSE)
1539 {
1540 if (e_cur != e_root)
1541 e_cur->vnum = -1;
1542 }
1543 else
1544 {
1545 if (e_cur->visible)
1546 e_cur->vnum = e_cur->msgno;
1547 }
1548 }
1549
1550 if (!e_cur->read && e_cur->visible)
1551 {
1552 if (e_cur->old)
1553 old_mail = 2;
1554 else
1555 new_mail = 1;
1556 if (e_cur->msgno < min_unread_msgno)
1557 {
1558 min_unread = e_cur->vnum;
1559 min_unread_msgno = e_cur->msgno;
1560 }
1561 }
1562
1563 if (e_cur->flagged && e_cur->visible)
1564 flagged = true;
1565
1566 if ((e_cur->vnum == -1) && e_cur->visible)
1567 num_hidden++;
1568 }
1569
1570 if (thread->child)
1571 {
1572 thread = thread->child;
1573 }
1574 else if (thread->next)
1575 {
1576 thread = thread->next;
1577 }
1578 else
1579 {
1580 bool done = false;
1581 while (!thread->next)
1582 {
1583 thread = thread->parent;
1584 if (thread == top)
1585 {
1586 done = true;
1587 break;
1588 }
1589 }
1590 if (done)
1591 break;
1592 thread = thread->next;
1593 }
1594 }
1595
1596 /* re-traverse the thread and store num_hidden in all headers, with or
1597 * without a virtual index. this will allow ~v to match all collapsed
1598 * messages when switching sort order to non-threaded. */
1599 if (flag & MUTT_THREAD_COLLAPSE)
1600 {
1601 thread = top;
1602 while (true)
1603 {
1604 e_cur = thread->message;
1605 if (e_cur)
1606 e_cur->num_hidden = num_hidden + 1;
1607
1608 if (thread->child)
1609 {
1610 thread = thread->child;
1611 }
1612 else if (thread->next)
1613 {
1614 thread = thread->next;
1615 }
1616 else
1617 {
1618 bool done = false;
1619 while (!thread->next)
1620 {
1621 thread = thread->parent;
1622 if (thread == top)
1623 {
1624 done = true;
1625 break;
1626 }
1627 }
1628 if (done)
1629 break;
1630 thread = thread->next;
1631 }
1632 }
1633 }
1634
1635 /* return value depends on action requested */
1637 return final;
1638 if (flag & MUTT_THREAD_UNREAD)
1639 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1640 if (flag & MUTT_THREAD_NEXT_UNREAD)
1641 return min_unread;
1642 if (flag & MUTT_THREAD_FLAGGED)
1643 return flagged;
1644
1645 return 0;
1646}
1647
1655int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
1656{
1657 if (!m || !e)
1658 return 1;
1659
1660 struct MuttThread *threads[2];
1661 int rc;
1662
1663 const enum UseThreads threaded = mutt_thread_style();
1664 if ((threaded == UT_FLAT) || !e->thread)
1665 return 1;
1666
1667 threads[0] = e->thread;
1668 while (threads[0]->parent)
1669 threads[0] = threads[0]->parent;
1670
1671 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1672
1673 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1674 {
1675 while (!threads[i]->message)
1676 threads[i] = threads[i]->child;
1677 }
1678
1679 if (threaded == UT_REVERSE)
1680 {
1681 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1682 }
1683 else
1684 {
1685 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1686 threads[0]->message->msgno;
1687 }
1688
1689 if (mit == MIT_POSITION)
1690 rc += 1;
1691
1692 return rc;
1693}
1694
1701{
1702 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1703
1704 for (int i = 0; i < m->msg_count; i++)
1705 {
1706 struct Email *e = m->emails[i];
1707 if (!e || !e->env)
1708 continue;
1709
1710 if (e->env->message_id)
1711 mutt_hash_insert(hash, e->env->message_id, e);
1712 }
1713
1714 return hash;
1715}
1716
1724static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1725{
1726 if (child == parent)
1727 return false;
1728
1729 mutt_break_thread(child);
1731 mutt_set_flag(m, child, MUTT_TAG, false, true);
1732
1733 child->changed = true;
1734 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1735 return true;
1736}
1737
1745bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
1746{
1747 if (!parent || !children || !m)
1748 return false;
1749
1750 bool changed = false;
1751
1752 struct Email **ep = NULL;
1753 ARRAY_FOREACH(ep, children)
1754 {
1755 struct Email *e = *ep;
1756 changed |= link_threads(parent, e, m);
1757 }
1758
1759 return changed;
1760}
1761
1767{
1768 struct MuttThread *thread = NULL;
1769 struct MuttThread *top = tctx->tree;
1770 while ((thread = top))
1771 {
1772 while (!thread->message)
1773 thread = thread->child;
1774
1775 struct Email *e = thread->message;
1776 if (e->collapsed)
1778 top = top->next;
1779 }
1780}
1781
1787void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1788{
1789 struct MuttThread *thread = NULL;
1790 struct MuttThread *top = tctx->tree;
1791 while ((thread = top))
1792 {
1793 while (!thread->message)
1794 thread = thread->child;
1795
1796 struct Email *e = thread->message;
1797
1798 if (e->collapsed != collapse)
1799 {
1800 if (e->collapsed)
1802 else if (mutt_thread_can_collapse(e))
1804 }
1805 top = top->next;
1806 }
1807}
1808
1816{
1817 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1818 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1819 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1820 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1821}
#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:38
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition sort.h:40
#define SORT_REVERSE
Reverse the order of the sort.
Definition sort.h:39
Convenience wrapper for the core headers.
MailboxType
Supported mailbox formats.
Definition mailbox.h:41
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:68
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:1019
#define mutt_error(...)
Definition logging2.h:94
static int compare_threads(const void *a, const void *b, void *sdata)
Helper to sort email threads - Implements sort_t -.
Definition thread.c:746
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:179
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition thread.c:232
void mutt_clear_threads(struct ThreadsContext *tctx)
Clear the threading of message in a mailbox.
Definition thread.c:716
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition thread.c:1436
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition thread.c:774
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
Toggle collapse.
Definition thread.c:1787
struct ThreadsContext * mutt_thread_ctx_init(struct MailboxView *mv)
Initialize a threading context.
Definition thread.c:353
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
Re-collapse threads marked as collapsed.
Definition thread.c:1766
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:1745
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition thread.c:390
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
Count the messages in a thread.
Definition thread.c:1655
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:520
void mutt_thread_ctx_free(struct ThreadsContext **ptr)
Finalize a threading context.
Definition thread.c:364
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:645
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition thread.c:620
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition thread.c:977
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
Sort email threads.
Definition thread.c:1029
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:1402
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition thread.c:1285
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition thread.c:1815
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:576
int mutt_parent_message(struct Email *e, bool find_root)
Find the parent of a message.
Definition thread.c:1352
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition thread.c:1700
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition thread.c:1724
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:336
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:363
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:410
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:260
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:302
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:458
#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
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)
Definition memory.h:63
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:48
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:51
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:49
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:662
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:583
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:67
#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:60
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:63
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
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:79
int vcount
The number of virtual messages.
Definition mailbox.h:99
int * v2r
Mapping from virtual to real msgno.
Definition mailbox.h:98
int msg_count
Total number of messages.
Definition mailbox.h:88
struct HashTable * subj_hash
Hash Table: "subject" -> Email.
Definition mailbox.h:124
struct Email ** emails
Array of Emails.
Definition mailbox.h:96
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:128
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:134
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