NeoMutt  2024-11-14-34-g5aaf0d
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mutt_thread.c
Go to the documentation of this file.
1
33#include "config.h"
34#include <limits.h>
35#include <stdbool.h>
36#include <stdlib.h>
37#include <string.h>
38#include "mutt/lib.h"
39#include "config/lib.h"
40#include "email/lib.h"
41#include "core/lib.h"
42#include "mutt.h"
43#include "mutt_thread.h"
44#include "globals.h"
45#include "mview.h"
46#include "mx.h"
47#include "protos.h"
48#include "sort.h"
49
53static const struct Mapping UseThreadsMethods[] = {
54 // clang-format off
55 { "unset", UT_UNSET },
56 { "flat", UT_FLAT },
57 { "threads", UT_THREADS },
58 { "reverse", UT_REVERSE },
59 // aliases
60 { "no", UT_FLAT },
61 { "yes", UT_THREADS },
62 { NULL, 0 },
63 // clang-format on
64};
65
67const struct EnumDef UseThreadsTypeDef = {
68 "use_threads_type",
69 4,
70 (struct Mapping *) &UseThreadsMethods,
71};
72
83{
84 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
85 const enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
86 if (c_use_threads > UT_FLAT)
87 return c_use_threads;
88 if ((c_sort & SORT_MASK) != SORT_THREADS)
89 return UT_FLAT;
90 if (c_sort & SORT_REVERSE)
91 return UT_REVERSE;
92 return UT_THREADS;
93}
94
101{
103}
104
108int sort_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
109 intptr_t value, struct Buffer *err)
110{
111 if (((value & SORT_MASK) == SORT_THREADS) && (value & SORT_LAST))
112 {
113 buf_printf(err, _("Cannot use 'last-' prefix with 'threads' for %s"), cdef->name);
114 return CSR_ERR_INVALID;
115 }
116 return CSR_SUCCESS;
117}
118
124static bool is_visible(struct Email *e)
125{
126 return e->vnum >= 0 || (e->collapsed && e->visible);
127}
128
134static bool need_display_subject(struct Email *e)
135{
136 struct MuttThread *tmp = NULL;
137 struct MuttThread *tree = e->thread;
138
139 /* if the user disabled subject hiding, display it */
140 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
141 if (!c_hide_thread_subject)
142 return true;
143
144 /* if our subject is different from our parent's, display it */
145 if (e->subject_changed)
146 return true;
147
148 /* if our subject is different from that of our closest previously displayed
149 * sibling, display the subject */
150 for (tmp = tree->prev; tmp; tmp = tmp->prev)
151 {
152 e = tmp->message;
153 if (e && is_visible(e))
154 {
155 if (e->subject_changed)
156 return true;
157 break;
158 }
159 }
160
161 /* if there is a parent-to-child subject change anywhere between us and our
162 * closest displayed ancestor, display the subject */
163 for (tmp = tree->parent; tmp; tmp = tmp->parent)
164 {
165 e = tmp->message;
166 if (e)
167 {
168 if (is_visible(e))
169 return false;
170 if (e->subject_changed)
171 return true;
172 }
173 }
174
175 /* if we have no visible parent or previous sibling, display the subject */
176 return true;
177}
178
183static void linearize_tree(struct ThreadsContext *tctx)
184{
185 if (!tctx || !tctx->mailbox_view)
186 return;
187
188 struct Mailbox *m = tctx->mailbox_view->mailbox;
189
190 const bool reverse = (mutt_thread_style() == UT_REVERSE);
191 struct MuttThread *tree = tctx->tree;
192 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
193
194 while (tree)
195 {
196 while (!tree->message)
197 tree = tree->child;
198
199 *array = tree->message;
200 array += reverse ? -1 : 1;
201
202 if (tree->child)
203 {
204 tree = tree->child;
205 }
206 else
207 {
208 while (tree)
209 {
210 if (tree->next)
211 {
212 tree = tree->next;
213 break;
214 }
215 else
216 {
217 tree = tree->parent;
218 }
219 }
220 }
221 }
222}
223
236static void calculate_visibility(struct MuttThread *tree, int *max_depth)
237{
238 if (!tree)
239 return;
240
241 struct MuttThread *tmp = NULL;
242 struct MuttThread *orig_tree = tree;
243 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
244 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
245 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
246 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
247 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
248 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
249 int depth = 0;
250
251 /* we walk each level backwards to make it easier to compute next_subtree_visible */
252 while (tree->next)
253 tree = tree->next;
254 *max_depth = 0;
255
256 while (true)
257 {
258 if (depth > *max_depth)
259 *max_depth = depth;
260
261 tree->subtree_visible = 0;
262 if (tree->message)
263 {
264 FREE(&tree->message->tree);
265 if (is_visible(tree->message))
266 {
267 tree->deep = true;
268 tree->visible = true;
270 for (tmp = tree; tmp; tmp = tmp->parent)
271 {
272 if (tmp->subtree_visible)
273 {
274 tmp->deep = true;
275 tmp->subtree_visible = 2;
276 break;
277 }
278 else
279 {
280 tmp->subtree_visible = 1;
281 }
282 }
283 }
284 else
285 {
286 tree->visible = false;
287 tree->deep = !c_hide_limited;
288 }
289 }
290 else
291 {
292 tree->visible = false;
293 tree->deep = !c_hide_missing;
294 }
295 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
296 tree->next->subtree_visible);
297 if (tree->child)
298 {
299 depth++;
300 tree = tree->child;
301 while (tree->next)
302 tree = tree->next;
303 }
304 else if (tree->prev)
305 {
306 tree = tree->prev;
307 }
308 else
309 {
310 while (tree && !tree->prev)
311 {
312 depth--;
313 tree = tree->parent;
314 }
315 if (!tree)
316 break;
317 tree = tree->prev;
318 }
319 }
320
321 /* now fix up for the OPTHIDETOP* options if necessary */
322 if (hide_top_limited || hide_top_missing)
323 {
324 tree = orig_tree;
325 while (true)
326 {
327 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
328 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
329 {
330 tree->deep = false;
331 }
332 if (!tree->deep && tree->child && tree->subtree_visible)
333 {
334 tree = tree->child;
335 }
336 else if (tree->next)
337 {
338 tree = tree->next;
339 }
340 else
341 {
342 while (tree && !tree->next)
343 tree = tree->parent;
344 if (!tree)
345 break;
346 tree = tree->next;
347 }
348 }
349 }
350}
351
358{
359 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
360 tctx->mailbox_view = mv;
361 return tctx;
362}
363
369{
370 if (!ptr || !*ptr)
371 {
372 return;
373 }
374
375 struct ThreadsContext *tctx = *ptr;
376
377 mutt_hash_free(&tctx->hash);
378
379 FREE(ptr);
380}
381
395{
396 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
397 const bool reverse = (mutt_thread_style() == UT_REVERSE);
398 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
399 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
400 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
401 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
402 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
403
404 struct MuttThread *tree = tctx->tree;
405
406 /* Do the visibility calculations and free the old thread chars.
407 * From now on we can simply ignore invisible subtrees */
408 calculate_visibility(tree, &max_depth);
409 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
410 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
411 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
412 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
413 while (tree)
414 {
415 if (depth != 0)
416 {
417 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
418 if (start_depth == depth)
419 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
420 else if (parent->message && !c_hide_limited)
421 myarrow[0] = MUTT_TREE_HIDDEN;
422 else if (!parent->message && !c_hide_missing)
423 myarrow[0] = MUTT_TREE_MISSING;
424 else
425 myarrow[0] = vtee;
426 if (width == 2)
427 {
428 myarrow[1] = pseudo ? MUTT_TREE_STAR :
430 }
431 if (tree->visible)
432 {
433 myarrow[width] = MUTT_TREE_RARROW;
434 myarrow[width + 1] = 0;
435 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
436 if (start_depth > 1)
437 {
438 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
439 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
440 (1 + depth - start_depth) * width + 2);
441 }
442 else
443 {
444 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
445 }
446 tree->message->tree = new_tree;
447 }
448 }
449 if (tree->child && (depth != 0))
450 {
451 mypfx = pfx + (depth - 1) * width;
452 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
453 if (width == 2)
454 mypfx[1] = MUTT_TREE_SPACE;
455 }
456 parent = tree;
457 nextdisp = NULL;
458 pseudo = NULL;
459 do
460 {
461 if (tree->child && tree->subtree_visible)
462 {
463 if (tree->deep)
464 depth++;
465 if (tree->visible)
466 start_depth = depth;
467 tree = tree->child;
468
469 /* we do this here because we need to make sure that the first child thread
470 * of the old tree that we deal with is actually displayed if any are,
471 * or we might set the parent variable wrong while going through it. */
472 while (!tree->subtree_visible && tree->next)
473 tree = tree->next;
474 }
475 else
476 {
477 while (!tree->next && tree->parent)
478 {
479 if (tree == pseudo)
480 pseudo = NULL;
481 if (tree == nextdisp)
482 nextdisp = NULL;
483 if (tree->visible)
484 start_depth = depth;
485 tree = tree->parent;
486 if (tree->deep)
487 {
488 if (start_depth == depth)
489 start_depth--;
490 depth--;
491 }
492 }
493 if (tree == pseudo)
494 pseudo = NULL;
495 if (tree == nextdisp)
496 nextdisp = NULL;
497 if (tree->visible)
498 start_depth = depth;
499 tree = tree->next;
500 if (!tree)
501 break;
502 }
503 if (!pseudo && tree->fake_thread)
504 pseudo = tree;
505 if (!nextdisp && tree->next_subtree_visible)
506 nextdisp = tree;
507 } while (!tree->deep);
508 }
509
510 FREE(&pfx);
511 FREE(&arrow);
512}
513
524static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
525{
526 struct MuttThread *start = cur;
527 struct Envelope *env = NULL;
528 time_t thisdate;
529 int rc = 0;
530
531 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
532 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
533 while (true)
534 {
535 while (!cur->message)
536 cur = cur->child;
537
538 if (dateptr)
539 {
540 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
541 if ((*dateptr == 0) || (thisdate < *dateptr))
542 *dateptr = thisdate;
543 }
544
545 env = cur->message->env;
546 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
547 {
548 struct ListNode *np = NULL;
549 STAILQ_FOREACH(np, subjects, entries)
550 {
551 rc = mutt_str_cmp(env->real_subj, np->data);
552 if (rc >= 0)
553 break;
554 }
555 if (!np)
556 mutt_list_insert_head(subjects, env->real_subj);
557 else if (rc > 0)
558 mutt_list_insert_after(subjects, np, env->real_subj);
559 }
560
561 while (!cur->next && (cur != start))
562 {
563 cur = cur->parent;
564 }
565 if (cur == start)
566 break;
567 cur = cur->next;
568 }
569}
570
580static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
581{
582 if (!m)
583 return NULL;
584
585 struct HashElem *he = NULL;
586 struct MuttThread *tmp = NULL, *last = NULL;
587 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
588 time_t date = 0;
589
590 make_subject_list(&subjects, cur, &date);
591
592 struct ListNode *np = NULL;
593 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
594 STAILQ_FOREACH(np, &subjects, entries)
595 {
596 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
597 {
598 tmp = ((struct Email *) he->data)->thread;
599 if ((tmp != cur) && /* don't match the same message */
600 !tmp->fake_thread && /* don't match pseudo threads */
601 tmp->message->subject_changed && /* only match interesting replies */
602 !is_descendant(tmp, cur) && /* don't match in the same thread */
603 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
604 (!last || (c_thread_received ?
605 (last->message->received < tmp->message->received) :
606 (last->message->date_sent < tmp->message->date_sent))) &&
607 tmp->message->env->real_subj &&
609 {
610 last = tmp; /* best match so far */
611 }
612 }
613 }
614
615 mutt_list_clear(&subjects);
616 return last;
617}
618
624static struct HashTable *make_subj_hash(struct Mailbox *m)
625{
626 if (!m)
627 return NULL;
628
630
631 for (int i = 0; i < m->msg_count; i++)
632 {
633 struct Email *e = m->emails[i];
634 if (!e || !e->env)
635 continue;
636 if (e->env->real_subj)
637 mutt_hash_insert(hash, e->env->real_subj, e);
638 }
639
640 return hash;
641}
642
649static void pseudo_threads(struct ThreadsContext *tctx)
650{
651 if (!tctx || !tctx->mailbox_view)
652 return;
653
654 struct Mailbox *m = tctx->mailbox_view->mailbox;
655
656 struct MuttThread *tree = tctx->tree;
657 struct MuttThread *top = tree;
658 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
659 *nextchild = NULL;
660
661 if (!m->subj_hash)
663
664 while (tree)
665 {
666 cur = tree;
667 tree = tree->next;
668 parent = find_subject(m, cur);
669 if (parent)
670 {
671 cur->fake_thread = true;
672 unlink_message(&top, cur);
674 parent->sort_children = true;
675 tmp = cur;
676 while (true)
677 {
678 while (!tmp->message)
679 tmp = tmp->child;
680
681 /* if the message we're attaching has pseudo-children, they
682 * need to be attached to its parent, so move them up a level.
683 * but only do this if they have the same real subject as the
684 * parent, since otherwise they rightly belong to the message
685 * we're attaching. */
686 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
688 {
689 tmp->message->subject_changed = false;
690
691 for (curchild = tmp->child; curchild;)
692 {
693 nextchild = curchild->next;
694 if (curchild->fake_thread)
695 {
696 unlink_message(&tmp->child, curchild);
697 insert_message(&parent->child, parent, curchild);
698 }
699 curchild = nextchild;
700 }
701 }
702
703 while (!tmp->next && (tmp != cur))
704 {
705 tmp = tmp->parent;
706 }
707 if (tmp == cur)
708 break;
709 tmp = tmp->next;
710 }
711 }
712 }
713 tctx->tree = top;
714}
715
721{
722 if (!tctx || !tctx->tree)
723 return;
724
725 struct MailboxView *mv = tctx->mailbox_view;
726 if (!mv)
727 return;
728
729 struct Mailbox *m = mv->mailbox;
730 if (!m || !m->emails)
731 return;
732
733 for (int i = 0; i < m->msg_count; i++)
734 {
735 struct Email *e = m->emails[i];
736 if (!e)
737 break;
738
739 /* mailbox may have been only partially read */
740 e->thread = NULL;
741 e->threaded = false;
742 }
743 tctx->tree = NULL;
744 mutt_hash_free(&tctx->hash);
745}
746
750static int compare_threads(const void *a, const void *b, void *sdata)
751{
752 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
753 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
754 const struct ThreadsContext *tctx = sdata;
755 ASSERT(ta->parent == tb->parent);
756
757 /* If c_sort ties, remember we are building the thread array in
758 * reverse from the index the mails had in the mailbox. */
759 struct Mailbox *m = tctx->mailbox_view->mailbox;
760 const enum MailboxType mtype = mx_type(m);
761 if (ta->parent)
762 {
763 return mutt_compare_emails(ta->sort_aux_key, tb->sort_aux_key, mtype,
765 }
766 else
767 {
770 }
771}
772
778static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
779{
780 struct MuttThread *thread = tctx->tree;
781 if (!thread)
782 return;
783
784 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
785 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
786 struct Email *oldsort_thread_key = NULL;
787 int i, array_size;
788 bool sort_top = false;
789
790 /* we put things into the array backwards to save some cycles,
791 * but we want to have to move less stuff around if we're
792 * resorting, so we sort backwards and then put them back
793 * in reverse order so they're forwards */
794 const bool reverse = (mutt_thread_style() == UT_REVERSE);
795 enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
796 enum SortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
797 if ((c_sort & SORT_MASK) == SORT_THREADS)
798 {
799 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
800 ASSERT(cs_subset_enum(NeoMutt->sub, "use_threads") == UT_UNSET);
801 c_sort = c_sort_aux;
802 }
803 c_sort ^= SORT_REVERSE;
804 c_sort_aux ^= SORT_REVERSE;
805 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
806 {
807 tctx->c_sort = c_sort;
808 tctx->c_sort_aux = c_sort_aux;
809 init = true;
810 }
811
812 top = thread;
813
814 array_size = 256;
815 array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
816 while (true)
817 {
818 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
819 {
820 thread->sort_thread_key = NULL;
821 thread->sort_aux_key = NULL;
822
823 if (thread->parent)
824 thread->parent->sort_children = true;
825 else
826 sort_top = true;
827 }
828
829 if (thread->child)
830 {
832 continue;
833 }
834 else
835 {
836 /* if it has no children, it must be real. sort it on its own merits */
839
840 if (thread->next)
841 {
842 thread = thread->next;
843 continue;
844 }
845 }
846
847 struct Mailbox *m = tctx->mailbox_view->mailbox;
848 const enum MailboxType mtype = mx_type(m);
849 while (!thread->next)
850 {
851 /* if it has siblings and needs to be sorted, sort it... */
852 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
853 {
854 /* put them into the array */
855 for (i = 0; thread; i++, thread = thread->prev)
856 {
857 if (i >= array_size)
858 {
859 array_size *= 2;
860 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
861 }
862
863 array[i] = thread;
864 }
865
866 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
867
868 /* attach them back together. make thread the last sibling. */
869 thread = array[0];
870 thread->next = NULL;
871 array[i - 1]->prev = NULL;
872
873 if (thread->parent)
874 thread->parent->child = array[i - 1];
875 else
876 top = array[i - 1];
877
878 while (--i)
879 {
880 array[i - 1]->prev = array[i];
881 array[i]->next = array[i - 1];
882 }
883 }
884
885 if (thread->parent)
886 {
887 tmp = thread;
888 thread = thread->parent;
889
890 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
891 {
892 /* we just sorted its children */
893 thread->sort_children = false;
894
895 oldsort_aux_key = thread->sort_aux_key;
896 oldsort_thread_key = thread->sort_thread_key;
897
898 /* update sort keys. sort_aux_key will be the first or last
899 * sibling, as appropriate... */
900 thread->sort_aux_key = thread->message;
901 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
902 thread->child->sort_aux_key :
903 tmp->sort_aux_key;
904
905 if (c_sort_aux & SORT_LAST)
906 {
907 if (!thread->sort_aux_key ||
908 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
909 c_sort_aux | SORT_REVERSE, SORT_ORDER) > 0))
910 {
911 thread->sort_aux_key = sort_aux_key;
912 }
913 }
914 else if (!thread->sort_aux_key)
915 {
916 thread->sort_aux_key = sort_aux_key;
917 }
918
919 /* ...but sort_thread_key may require searching the entire
920 * list of siblings */
921 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
922 {
923 thread->sort_thread_key = thread->sort_aux_key;
924 }
925 else
926 {
927 if (thread->message)
928 {
929 thread->sort_thread_key = thread->message;
930 }
931 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
932 {
933 thread->sort_thread_key = tmp->sort_thread_key;
934 }
935 else
936 {
937 thread->sort_thread_key = thread->child->sort_thread_key;
938 }
939 if (c_sort & SORT_LAST)
940 {
941 for (tmp = thread->child; tmp; tmp = tmp->next)
942 {
943 if (tmp->sort_thread_key == thread->sort_thread_key)
944 continue;
945 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key,
946 mtype, c_sort | SORT_REVERSE, SORT_ORDER) > 0))
947 {
948 thread->sort_thread_key = tmp->sort_thread_key;
949 }
950 }
951 }
952 }
953
954 /* if a sort_key has changed, we need to resort it and siblings */
955 if ((oldsort_aux_key != thread->sort_aux_key) ||
956 (oldsort_thread_key != thread->sort_thread_key))
957 {
958 if (thread->parent)
959 thread->parent->sort_children = true;
960 else
961 sort_top = true;
962 }
963 }
964 }
965 else
966 {
967 FREE(&array);
968 tctx->tree = top;
969 return;
970 }
971 }
972
973 thread = thread->next;
974 }
975}
976
982static void check_subjects(struct MailboxView *mv, bool init)
983{
984 if (!mv)
985 return;
986
987 struct Mailbox *m = mv->mailbox;
988 for (int i = 0; i < m->msg_count; i++)
989 {
990 struct Email *e = m->emails[i];
991 if (!e || !e->thread)
992 continue;
993
994 if (e->thread->check_subject)
995 e->thread->check_subject = false;
996 else if (!init)
997 continue;
998
999 /* figure out which messages have subjects different than their parents' */
1000 struct MuttThread *tmp = e->thread->parent;
1001 while (tmp && !tmp->message)
1002 {
1003 tmp = tmp->parent;
1004 }
1005
1006 if (!tmp)
1007 {
1008 e->subject_changed = true;
1009 }
1010 else if (e->env->real_subj && tmp->message->env->real_subj)
1011 {
1013 }
1014 else
1015 {
1016 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1017 }
1018 }
1019}
1020
1024static void thread_hash_destructor(int type, void *obj, intptr_t data)
1025{
1026 FREE(&obj);
1027}
1028
1034void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
1035{
1036 if (!tctx || !tctx->mailbox_view)
1037 return;
1038
1039 struct MailboxView *mv = tctx->mailbox_view;
1040 struct Mailbox *m = mv->mailbox;
1041
1042 struct Email *e = NULL;
1043 int i, using_refs = 0;
1044 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1045 struct MuttThread top = { 0 };
1046 struct ListNode *ref = NULL;
1047
1048 ASSERT(m->msg_count > 0);
1049 if (!tctx->hash)
1050 init = true;
1051
1052 if (init)
1053 {
1056 }
1057
1058 /* we want a quick way to see if things are actually attached to the top of the
1059 * thread tree or if they're just dangling, so we attach everything to a top
1060 * node temporarily */
1061 top.parent = NULL;
1062 top.next = NULL;
1063 top.prev = NULL;
1064
1065 top.child = tctx->tree;
1066 for (thread = tctx->tree; thread; thread = thread->next)
1067 thread->parent = &top;
1068
1069 /* put each new message together with the matching messageless MuttThread if it
1070 * exists. otherwise, if there is a MuttThread that already has a message, thread
1071 * new message as an identical child. if we didn't attach the message to a
1072 * MuttThread, make a new one for it. */
1073 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1074 for (i = 0; i < m->msg_count; i++)
1075 {
1076 e = m->emails[i];
1077 if (!e)
1078 continue;
1079
1080 if (e->thread)
1081 {
1082 /* unlink pseudo-threads because they might be children of newly
1083 * arrived messages */
1084 thread = e->thread;
1085 for (tnew = thread->child; tnew;)
1086 {
1087 tmp = tnew->next;
1088 if (tnew->fake_thread)
1089 {
1090 unlink_message(&thread->child, tnew);
1091 insert_message(&top.child, &top, tnew);
1092 tnew->fake_thread = false;
1093 }
1094 tnew = tmp;
1095 }
1096 }
1097 else
1098 {
1099 if ((!init || c_duplicate_threads) && e->env->message_id)
1100 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1101 else
1102 thread = NULL;
1103
1104 if (thread && !thread->message)
1105 {
1106 /* this is a message which was missing before */
1107 thread->message = e;
1108 e->thread = thread;
1109 thread->check_subject = true;
1110
1111 /* mark descendants as needing subject_changed checked */
1112 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1113 {
1114 while (!tmp->message)
1115 tmp = tmp->child;
1116 tmp->check_subject = true;
1117 while (!tmp->next && (tmp != thread))
1118 tmp = tmp->parent;
1119 if (tmp != thread)
1120 tmp = tmp->next;
1121 }
1122
1123 if (thread->parent)
1124 {
1125 /* remove threading info above it based on its children, which we'll
1126 * recalculate based on its headers. make sure not to leave
1127 * dangling missing messages. note that we haven't kept track
1128 * of what info came from its children and what from its siblings'
1129 * children, so we just remove the stuff that's definitely from it */
1130 do
1131 {
1132 tmp = thread->parent;
1133 unlink_message(&tmp->child, thread);
1134 thread->parent = NULL;
1135 thread->sort_thread_key = NULL;
1136 thread->sort_aux_key = NULL;
1137 thread->fake_thread = false;
1138 thread = tmp;
1139 } while (thread != &top && !thread->child && !thread->message);
1140 }
1141 }
1142 else
1143 {
1144 tnew = (c_duplicate_threads ? thread : NULL);
1145
1146 thread = MUTT_MEM_CALLOC(1, struct MuttThread);
1147 thread->message = e;
1148 thread->check_subject = true;
1149 e->thread = thread;
1150 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1151
1152 if (tnew)
1153 {
1154 if (tnew->duplicate_thread)
1155 tnew = tnew->parent;
1156
1157 thread = e->thread;
1158
1159 insert_message(&tnew->child, tnew, thread);
1160 thread->duplicate_thread = true;
1161 thread->message->threaded = true;
1162 }
1163 }
1164 }
1165 }
1166
1167 /* thread by references */
1168 for (i = 0; i < m->msg_count; i++)
1169 {
1170 e = m->emails[i];
1171 if (!e)
1172 break;
1173
1174 if (e->threaded)
1175 continue;
1176 e->threaded = true;
1177
1178 thread = e->thread;
1179 if (!thread)
1180 continue;
1181 using_refs = 0;
1182
1183 while (true)
1184 {
1185 if (using_refs == 0)
1186 {
1187 /* look at the beginning of in-reply-to: */
1188 ref = STAILQ_FIRST(&e->env->in_reply_to);
1189 if (ref)
1190 {
1191 using_refs = 1;
1192 }
1193 else
1194 {
1195 ref = STAILQ_FIRST(&e->env->references);
1196 using_refs = 2;
1197 }
1198 }
1199 else if (using_refs == 1)
1200 {
1201 /* if there's no references header, use all the in-reply-to:
1202 * data that we have. otherwise, use the first reference
1203 * if it's different than the first in-reply-to, otherwise use
1204 * the second reference (since at least eudora puts the most
1205 * recent reference in in-reply-to and the rest in references) */
1206 if (STAILQ_EMPTY(&e->env->references))
1207 {
1208 ref = STAILQ_NEXT(ref, entries);
1209 }
1210 else
1211 {
1212 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1213 ref = STAILQ_FIRST(&e->env->references);
1214 else
1215 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1216
1217 using_refs = 2;
1218 }
1219 }
1220 else
1221 {
1222 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1223 }
1224
1225 if (!ref)
1226 break;
1227
1228 tnew = mutt_hash_find(tctx->hash, ref->data);
1229 if (tnew)
1230 {
1231 if (tnew->duplicate_thread)
1232 tnew = tnew->parent;
1233 if (is_descendant(tnew, thread)) /* no loops! */
1234 continue;
1235 }
1236 else
1237 {
1238 tnew = MUTT_MEM_CALLOC(1, struct MuttThread);
1239 mutt_hash_insert(tctx->hash, ref->data, tnew);
1240 }
1241
1242 if (thread->parent)
1243 unlink_message(&top.child, thread);
1244 insert_message(&tnew->child, tnew, thread);
1245 thread = tnew;
1246 if (thread->message || (thread->parent && (thread->parent != &top)))
1247 break;
1248 }
1249
1250 if (!thread->parent)
1251 insert_message(&top.child, &top, thread);
1252 }
1253
1254 /* detach everything from the temporary top node */
1255 for (thread = top.child; thread; thread = thread->next)
1256 {
1257 thread->parent = NULL;
1258 }
1259 tctx->tree = top.child;
1260
1261 check_subjects(mv, init);
1262
1263 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1264 if (!c_strict_threads)
1265 pseudo_threads(tctx);
1266
1267 /* if $sort_aux or similar changed after the mailbox is sorted, then
1268 * all the subthreads need to be resorted */
1269 if (tctx->tree)
1270 {
1272 OptSortSubthreads = false;
1273
1274 /* Put the list into an array. */
1275 linearize_tree(tctx);
1276
1277 /* Draw the thread tree. */
1278 mutt_draw_tree(tctx);
1279 }
1280}
1281
1290int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1291{
1292 if (!e)
1293 return -1;
1294
1295 struct MuttThread *cur = NULL;
1296 struct Email *e_tmp = NULL;
1297
1298 const enum UseThreads threaded = mutt_thread_style();
1299 if (threaded == UT_FLAT)
1300 {
1301 mutt_error(_("Threading is not enabled"));
1302 return e->vnum;
1303 }
1304
1305 cur = e->thread;
1306
1307 if (subthreads)
1308 {
1309 if (forwards ^ (threaded == UT_REVERSE))
1310 {
1311 while (!cur->next && cur->parent)
1312 cur = cur->parent;
1313 }
1314 else
1315 {
1316 while (!cur->prev && cur->parent)
1317 cur = cur->parent;
1318 }
1319 }
1320 else
1321 {
1322 while (cur->parent)
1323 cur = cur->parent;
1324 }
1325
1326 if (forwards ^ (threaded == UT_REVERSE))
1327 {
1328 do
1329 {
1330 cur = cur->next;
1331 if (!cur)
1332 return -1;
1333 e_tmp = find_virtual(cur, false);
1334 } while (!e_tmp);
1335 }
1336 else
1337 {
1338 do
1339 {
1340 cur = cur->prev;
1341 if (!cur)
1342 return -1;
1343 e_tmp = find_virtual(cur, true);
1344 } while (!e_tmp);
1345 }
1346
1347 return e_tmp->vnum;
1348}
1349
1357int mutt_parent_message(struct Email *e, bool find_root)
1358{
1359 if (!e)
1360 return -1;
1361
1362 struct MuttThread *thread = NULL;
1363 struct Email *e_parent = NULL;
1364
1365 if (!mutt_using_threads())
1366 {
1367 mutt_error(_("Threading is not enabled"));
1368 return e->vnum;
1369 }
1370
1371 /* Root may be the current message */
1372 if (find_root)
1373 e_parent = e;
1374
1375 for (thread = e->thread->parent; thread; thread = thread->parent)
1376 {
1377 e = thread->message;
1378 if (e)
1379 {
1380 e_parent = e;
1381 if (!find_root)
1382 break;
1383 }
1384 }
1385
1386 if (!e_parent)
1387 {
1388 mutt_error(_("Parent message is not available"));
1389 return -1;
1390 }
1391 if (!is_visible(e_parent))
1392 {
1393 if (find_root)
1394 mutt_error(_("Root message is not visible in this limited view"));
1395 else
1396 mutt_error(_("Parent message is not visible in this limited view"));
1397 return -1;
1398 }
1399 return e_parent->vnum;
1400}
1401
1407off_t mutt_set_vnum(struct Mailbox *m)
1408{
1409 if (!m)
1410 return 0;
1411
1412 off_t vsize = 0;
1413 const int padding = mx_msg_padding_size(m);
1414
1415 m->vcount = 0;
1416
1417 for (int i = 0; i < m->msg_count; i++)
1418 {
1419 struct Email *e = m->emails[i];
1420 if (!e)
1421 break;
1422
1423 if (e->vnum >= 0)
1424 {
1425 e->vnum = m->vcount;
1426 m->v2r[m->vcount] = i;
1427 m->vcount++;
1428 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1429 }
1430 }
1431
1432 return vsize;
1433}
1434
1442{
1443 struct MuttThread *thread = NULL, *top = NULL;
1444 struct Email *e_root = NULL;
1445 const enum UseThreads threaded = mutt_thread_style();
1446 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1447 int num_hidden = 0, new_mail = 0, old_mail = 0;
1448 bool flagged = false;
1449 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1450
1451 if (threaded == UT_FLAT)
1452 {
1453 mutt_error(_("Threading is not enabled"));
1454 return e_cur->vnum;
1455 }
1456
1457 if (!e_cur->thread)
1458 {
1459 return e_cur->vnum;
1460 }
1461
1462 final = e_cur->vnum;
1463 thread = e_cur->thread;
1464 while (thread->parent)
1465 thread = thread->parent;
1466 top = thread;
1467 while (!thread->message)
1468 thread = thread->child;
1469 e_cur = thread->message;
1470 minmsgno = e_cur->msgno;
1471
1472 if (!e_cur->read && e_cur->visible)
1473 {
1474 if (e_cur->old)
1475 old_mail = 2;
1476 else
1477 new_mail = 1;
1478 if (e_cur->msgno < min_unread_msgno)
1479 {
1480 min_unread = e_cur->vnum;
1481 min_unread_msgno = e_cur->msgno;
1482 }
1483 }
1484
1485 if (e_cur->flagged && e_cur->visible)
1486 flagged = true;
1487
1488 if ((e_cur->vnum == -1) && e_cur->visible)
1489 num_hidden++;
1490
1492 {
1493 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1494 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1495 if (e_cur->vnum != -1)
1496 {
1497 e_root = e_cur;
1498 if (flag & MUTT_THREAD_COLLAPSE)
1499 final = e_root->vnum;
1500 }
1501 }
1502
1503 if ((thread == top) && !(thread = thread->child))
1504 {
1505 /* return value depends on action requested */
1507 {
1508 e_cur->num_hidden = num_hidden;
1509 return final;
1510 }
1511 if (flag & MUTT_THREAD_UNREAD)
1512 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1513 if (flag & MUTT_THREAD_NEXT_UNREAD)
1514 return min_unread;
1515 if (flag & MUTT_THREAD_FLAGGED)
1516 return flagged;
1517 }
1518
1519 while (true)
1520 {
1521 e_cur = thread->message;
1522
1523 if (e_cur)
1524 {
1526 {
1527 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1528 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1529 if (!e_root && e_cur->visible)
1530 {
1531 e_root = e_cur;
1532 if (flag & MUTT_THREAD_COLLAPSE)
1533 final = e_root->vnum;
1534 }
1535
1536 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1537 (e_cur->msgno < minmsgno) && e_cur->visible)
1538 {
1539 minmsgno = e_cur->msgno;
1540 final = e_cur->vnum;
1541 }
1542
1543 if (flag & MUTT_THREAD_COLLAPSE)
1544 {
1545 if (e_cur != e_root)
1546 e_cur->vnum = -1;
1547 }
1548 else
1549 {
1550 if (e_cur->visible)
1551 e_cur->vnum = e_cur->msgno;
1552 }
1553 }
1554
1555 if (!e_cur->read && e_cur->visible)
1556 {
1557 if (e_cur->old)
1558 old_mail = 2;
1559 else
1560 new_mail = 1;
1561 if (e_cur->msgno < min_unread_msgno)
1562 {
1563 min_unread = e_cur->vnum;
1564 min_unread_msgno = e_cur->msgno;
1565 }
1566 }
1567
1568 if (e_cur->flagged && e_cur->visible)
1569 flagged = true;
1570
1571 if ((e_cur->vnum == -1) && e_cur->visible)
1572 num_hidden++;
1573 }
1574
1575 if (thread->child)
1576 {
1577 thread = thread->child;
1578 }
1579 else if (thread->next)
1580 {
1581 thread = thread->next;
1582 }
1583 else
1584 {
1585 bool done = false;
1586 while (!thread->next)
1587 {
1588 thread = thread->parent;
1589 if (thread == top)
1590 {
1591 done = true;
1592 break;
1593 }
1594 }
1595 if (done)
1596 break;
1597 thread = thread->next;
1598 }
1599 }
1600
1601 /* re-traverse the thread and store num_hidden in all headers, with or
1602 * without a virtual index. this will allow ~v to match all collapsed
1603 * messages when switching sort order to non-threaded. */
1604 if (flag & MUTT_THREAD_COLLAPSE)
1605 {
1606 thread = top;
1607 while (true)
1608 {
1609 e_cur = thread->message;
1610 if (e_cur)
1611 e_cur->num_hidden = num_hidden + 1;
1612
1613 if (thread->child)
1614 {
1615 thread = thread->child;
1616 }
1617 else if (thread->next)
1618 {
1619 thread = thread->next;
1620 }
1621 else
1622 {
1623 bool done = false;
1624 while (!thread->next)
1625 {
1626 thread = thread->parent;
1627 if (thread == top)
1628 {
1629 done = true;
1630 break;
1631 }
1632 }
1633 if (done)
1634 break;
1635 thread = thread->next;
1636 }
1637 }
1638 }
1639
1640 /* return value depends on action requested */
1642 return final;
1643 if (flag & MUTT_THREAD_UNREAD)
1644 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1645 if (flag & MUTT_THREAD_NEXT_UNREAD)
1646 return min_unread;
1647 if (flag & MUTT_THREAD_FLAGGED)
1648 return flagged;
1649
1650 return 0;
1651}
1652
1660int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
1661{
1662 if (!m || !e)
1663 return 1;
1664
1665 struct MuttThread *threads[2];
1666 int rc;
1667
1668 const enum UseThreads threaded = mutt_thread_style();
1669 if ((threaded == UT_FLAT) || !e->thread)
1670 return 1;
1671
1672 threads[0] = e->thread;
1673 while (threads[0]->parent)
1674 threads[0] = threads[0]->parent;
1675
1676 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1677
1678 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1679 {
1680 while (!threads[i]->message)
1681 threads[i] = threads[i]->child;
1682 }
1683
1684 if (threaded == UT_REVERSE)
1685 {
1686 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1687 }
1688 else
1689 {
1690 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1691 threads[0]->message->msgno;
1692 }
1693
1694 if (mit == MIT_POSITION)
1695 rc += 1;
1696
1697 return rc;
1698}
1699
1706{
1707 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1708
1709 for (int i = 0; i < m->msg_count; i++)
1710 {
1711 struct Email *e = m->emails[i];
1712 if (!e || !e->env)
1713 continue;
1714
1715 if (e->env->message_id)
1716 mutt_hash_insert(hash, e->env->message_id, e);
1717 }
1718
1719 return hash;
1720}
1721
1729static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1730{
1731 if (child == parent)
1732 return false;
1733
1734 mutt_break_thread(child);
1736 mutt_set_flag(m, child, MUTT_TAG, false, true);
1737
1738 child->changed = true;
1739 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1740 return true;
1741}
1742
1750bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
1751{
1752 if (!parent || !children || !m)
1753 return false;
1754
1755 bool changed = false;
1756
1757 struct Email **ep = NULL;
1758 ARRAY_FOREACH(ep, children)
1759 {
1760 struct Email *e = *ep;
1761 changed |= link_threads(parent, e, m);
1762 }
1763
1764 return changed;
1765}
1766
1772{
1773 struct MuttThread *thread = NULL;
1774 struct MuttThread *top = tctx->tree;
1775 while ((thread = top))
1776 {
1777 while (!thread->message)
1778 thread = thread->child;
1779
1780 struct Email *e = thread->message;
1781 if (e->collapsed)
1783 top = top->next;
1784 }
1785}
1786
1792void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1793{
1794 struct MuttThread *thread = NULL;
1795 struct MuttThread *top = tctx->tree;
1796 while ((thread = top))
1797 {
1798 while (!thread->message)
1799 thread = thread->child;
1800
1801 struct Email *e = thread->message;
1802
1803 if (e->collapsed != collapse)
1804 {
1805 if (e->collapsed)
1807 else if (mutt_thread_can_collapse(e))
1809 }
1810 top = top->next;
1811 }
1812}
1813
1821{
1822 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1823 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1824 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1825 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1826}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:212
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:38
#define CSR_SUCCESS
Action completed successfully.
Definition: set.h:35
Convenience wrapper for the core headers.
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
Structs that make up an email.
#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:57
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition: globals.c:72
Global variables.
int sort_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
Validate the "sort" config variable - Implements ConfigDef::validator() -.
Definition: mutt_thread.c:108
static void thread_hash_destructor(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition: mutt_thread.c:1024
#define mutt_error(...)
Definition: logging2.h:92
static int compare_threads(const void *a, const void *b, void *sdata)
Helper to sort email threads - Implements sort_t -.
Definition: mutt_thread.c:750
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:324
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:335
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:362
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:409
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:259
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:301
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:457
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:109
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition: hash.h:112
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:55
#define MUTT_MEM_CALLOC(n, type)
Definition: memory.h:40
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:41
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:399
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
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:581
Many unsorted constants and some structs.
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:80
static void linearize_tree(struct ThreadsContext *tctx)
Flatten an email thread.
Definition: mutt_thread.c:183
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:236
void mutt_clear_threads(struct ThreadsContext *tctx)
Clear the threading of message in a mailbox.
Definition: mutt_thread.c:720
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition: mutt_thread.c:1441
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:778
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
Toggle collapse.
Definition: mutt_thread.c:1792
struct ThreadsContext * mutt_thread_ctx_init(struct MailboxView *mv)
Initialize a threading context.
Definition: mutt_thread.c:357
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
Re-collapse threads marked as collapsed.
Definition: mutt_thread.c:1771
static const struct Mapping UseThreadsMethods[]
Choices for '$use_threads' for the index.
Definition: mutt_thread.c:53
bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
Forcibly link threads together.
Definition: mutt_thread.c:1750
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:394
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
Count the messages in a thread.
Definition: mutt_thread.c:1660
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: mutt_thread.c:524
void mutt_thread_ctx_free(struct ThreadsContext **ptr)
Finalize a threading context.
Definition: mutt_thread.c:368
const char * get_use_threads_str(enum UseThreads value)
Convert UseThreads enum to string.
Definition: mutt_thread.c:100
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition: mutt_thread.c:82
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:649
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:624
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition: mutt_thread.c:982
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
Sort email threads.
Definition: mutt_thread.c:1034
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition: mutt_thread.c:134
const struct EnumDef UseThreadsTypeDef
Data for the $use_threads enumeration.
Definition: mutt_thread.c:67
off_t mutt_set_vnum(struct Mailbox *m)
Set the virtual index number of all the messages in a mailbox.
Definition: mutt_thread.c:1407
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition: mutt_thread.c:1290
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1820
static struct MuttThread * find_subject(struct Mailbox *m, struct MuttThread *cur)
Find the best possible match for a parent based on subject.
Definition: mutt_thread.c:580
int mutt_parent_message(struct Email *e, bool find_root)
Find the parent of a message.
Definition: mutt_thread.c:1357
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1705
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1729
static bool is_visible(struct Email *e)
Is the message visible?
Definition: mutt_thread.c:124
Create/manipulate threading in emails.
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:80
uint8_t MuttThreadFlags
Flags, e.g. MUTT_THREAD_COLLAPSE.
Definition: mutt_thread.h:76
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:110
UseThreads
Which threading style is active, $use_threads.
Definition: mutt_thread.h:97
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:99
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:98
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:100
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:101
#define mutt_using_threads()
Definition: mutt_thread.h:114
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:108
MessageInThread
Flags for mutt_messages_in_thread()
Definition: mutt_thread.h:88
@ MIT_POSITION
Our position in the thread.
Definition: mutt_thread.h:90
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:79
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:81
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:109
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:78
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:107
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:82
TreeChar
Tree characters for menus.
Definition: mutt_thread.h:57
@ MUTT_TREE_LLCORNER
Lower left corner.
Definition: mutt_thread.h:58
@ MUTT_TREE_RARROW
Right arrow.
Definition: mutt_thread.h:64
@ MUTT_TREE_ULCORNER
Upper left corner.
Definition: mutt_thread.h:59
@ MUTT_TREE_EQUALS
Equals (for threads)
Definition: mutt_thread.h:67
@ MUTT_TREE_HIDDEN
Ampersand character (for threads)
Definition: mutt_thread.h:66
@ MUTT_TREE_STAR
Star character (for threads)
Definition: mutt_thread.h:65
@ MUTT_TREE_LTEE
Left T-piece.
Definition: mutt_thread.h:60
@ MUTT_TREE_VLINE
Vertical line.
Definition: mutt_thread.h:62
@ MUTT_TREE_MISSING
Question mark.
Definition: mutt_thread.h:70
@ MUTT_TREE_TTEE
Top T-piece.
Definition: mutt_thread.h:68
@ MUTT_TREE_HLINE
Horizontal line.
Definition: mutt_thread.h:61
@ MUTT_TREE_SPACE
Blank space.
Definition: mutt_thread.h:63
@ MUTT_TREE_BTEE
Bottom T-piece.
Definition: mutt_thread.h:69
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:1505
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1796
API for mailboxes.
Prototypes for many functions.
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:350
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
#define STAILQ_EMPTY(head)
Definition: queue.h:348
#define STAILQ_NEXT(elm, field)
Definition: queue.h:400
#define ASSERT(COND)
Definition: signal2.h:58
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:70
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:72
SortType
Methods for sorting.
Definition: sort2.h:34
@ SORT_ORDER
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:40
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:41
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:71
Assorted sorting methods.
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
Definition: set.h:64
const char * name
User-visible name.
Definition: set.h:65
Container for lots of config items.
Definition: set.h:252
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:43
struct HashElem * next
Linked List.
Definition: hash.h:47
void * data
User-supplied data.
Definition: hash.h:46
A Hash Table.
Definition: hash.h:97
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:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
The "current" threading state.
Definition: mutt_thread.h:43
struct MailboxView * mailbox_view
Current mailbox.
Definition: mutt_thread.h:44
struct MuttThread * tree
Top of thread tree.
Definition: mutt_thread.h:45
struct HashTable * hash
Hash Table: "message-id" -> MuttThread.
Definition: mutt_thread.h:46
enum SortType c_sort_aux
Last sort_aux method.
Definition: mutt_thread.h:48
enum SortType c_sort
Last sort method.
Definition: mutt_thread.h:47
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