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