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