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