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