NeoMutt  2023-03-22-27-g3cb248
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 "globals.h" // IWYU pragma: keep
42#include "mx.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 {
211 tree = tree->child;
212 }
213 else
214 {
215 while (tree)
216 {
217 if (tree->next)
218 {
219 tree = tree->next;
220 break;
221 }
222 else
223 {
224 tree = tree->parent;
225 }
226 }
227 }
228 }
229}
230
243static void calculate_visibility(struct MuttThread *tree, int *max_depth)
244{
245 if (!tree)
246 return;
247
248 struct MuttThread *tmp = NULL;
249 struct MuttThread *orig_tree = tree;
250 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
251 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
252 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
253 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
254 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
255 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
256 int depth = 0;
257
258 /* we walk each level backwards to make it easier to compute next_subtree_visible */
259 while (tree->next)
260 tree = tree->next;
261 *max_depth = 0;
262
263 while (true)
264 {
265 if (depth > *max_depth)
266 *max_depth = depth;
267
268 tree->subtree_visible = 0;
269 if (tree->message)
270 {
271 FREE(&tree->message->tree);
272 if (is_visible(tree->message))
273 {
274 tree->deep = true;
275 tree->visible = true;
277 for (tmp = tree; tmp; tmp = tmp->parent)
278 {
279 if (tmp->subtree_visible)
280 {
281 tmp->deep = true;
282 tmp->subtree_visible = 2;
283 break;
284 }
285 else
286 {
287 tmp->subtree_visible = 1;
288 }
289 }
290 }
291 else
292 {
293 tree->visible = false;
294 tree->deep = !c_hide_limited;
295 }
296 }
297 else
298 {
299 tree->visible = false;
300 tree->deep = !c_hide_missing;
301 }
302 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
303 tree->next->subtree_visible);
304 if (tree->child)
305 {
306 depth++;
307 tree = tree->child;
308 while (tree->next)
309 tree = tree->next;
310 }
311 else if (tree->prev)
312 tree = tree->prev;
313 else
314 {
315 while (tree && !tree->prev)
316 {
317 depth--;
318 tree = tree->parent;
319 }
320 if (!tree)
321 break;
322 tree = tree->prev;
323 }
324 }
325
326 /* now fix up for the OPTHIDETOP* options if necessary */
327 if (hide_top_limited || hide_top_missing)
328 {
329 tree = orig_tree;
330 while (true)
331 {
332 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
333 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
334 {
335 tree->deep = false;
336 }
337 if (!tree->deep && tree->child && tree->subtree_visible)
338 tree = tree->child;
339 else if (tree->next)
340 tree = tree->next;
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 = m;
362 tctx->tree = NULL;
363 tctx->hash = NULL;
364 return tctx;
365}
366
372{
373 (*tctx)->mailbox = NULL;
374 mutt_hash_free(&(*tctx)->hash);
375 FREE(tctx);
376}
377
391{
392 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
393 const bool reverse = (mutt_thread_style() == UT_REVERSE);
394 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
395 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
396 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
397 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
398 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
399
400 struct MuttThread *tree = tctx->tree;
401
402 /* Do the visibility calculations and free the old thread chars.
403 * From now on we can simply ignore invisible subtrees */
404 calculate_visibility(tree, &max_depth);
405 pfx = mutt_mem_malloc((width * max_depth) + 2);
406 arrow = mutt_mem_malloc((width * max_depth) + 2);
407 while (tree)
408 {
409 if (depth != 0)
410 {
411 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
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 if (start_depth == depth)
415 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
416 else if (parent->message && !c_hide_limited)
417 myarrow[0] = MUTT_TREE_HIDDEN;
418 else if (!parent->message && !c_hide_missing)
419 myarrow[0] = MUTT_TREE_MISSING;
420 else
421 myarrow[0] = vtee;
422 if (width == 2)
423 {
424 myarrow[1] = pseudo ? MUTT_TREE_STAR :
426 }
427 if (tree->visible)
428 {
429 myarrow[width] = MUTT_TREE_RARROW;
430 myarrow[width + 1] = 0;
431 new_tree = mutt_mem_malloc(((size_t) depth * width) + 2);
432 if (start_depth > 1)
433 {
434 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
435 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
436 (1 + depth - start_depth) * width + 2);
437 }
438 else
439 {
440 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
441 }
442 tree->message->tree = new_tree;
443 }
444 }
445 if (tree->child && (depth != 0))
446 {
447 mypfx = pfx + (depth - 1) * width;
448 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
449 if (width == 2)
450 mypfx[1] = MUTT_TREE_SPACE;
451 }
452 parent = tree;
453 nextdisp = NULL;
454 pseudo = NULL;
455 do
456 {
457 if (tree->child && tree->subtree_visible)
458 {
459 if (tree->deep)
460 depth++;
461 if (tree->visible)
462 start_depth = depth;
463 tree = tree->child;
464
465 /* we do this here because we need to make sure that the first child thread
466 * of the old tree that we deal with is actually displayed if any are,
467 * or we might set the parent variable wrong while going through it. */
468 while (!tree->subtree_visible && tree->next)
469 tree = tree->next;
470 }
471 else
472 {
473 while (!tree->next && tree->parent)
474 {
475 if (tree == pseudo)
476 pseudo = NULL;
477 if (tree == nextdisp)
478 nextdisp = NULL;
479 if (tree->visible)
480 start_depth = depth;
481 tree = tree->parent;
482 if (tree->deep)
483 {
484 if (start_depth == depth)
485 start_depth--;
486 depth--;
487 }
488 }
489 if (tree == pseudo)
490 pseudo = NULL;
491 if (tree == nextdisp)
492 nextdisp = NULL;
493 if (tree->visible)
494 start_depth = depth;
495 tree = tree->next;
496 if (!tree)
497 break;
498 }
499 if (!pseudo && tree->fake_thread)
500 pseudo = tree;
501 if (!nextdisp && tree->next_subtree_visible)
502 nextdisp = tree;
503 } while (!tree->deep);
504 }
505
506 FREE(&pfx);
507 FREE(&arrow);
508}
509
520static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
521{
522 struct MuttThread *start = cur;
523 struct Envelope *env = NULL;
524 time_t thisdate;
525 int rc = 0;
526
527 while (true)
528 {
529 while (!cur->message)
530 cur = cur->child;
531
532 if (dateptr)
533 {
534 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
535 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
536 if ((*dateptr == 0) || (thisdate < *dateptr))
537 *dateptr = thisdate;
538 }
539
540 env = cur->message->env;
541 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
542 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
543 {
544 struct ListNode *np = NULL;
545 STAILQ_FOREACH(np, subjects, entries)
546 {
547 rc = mutt_str_cmp(env->real_subj, np->data);
548 if (rc >= 0)
549 break;
550 }
551 if (!np)
552 mutt_list_insert_head(subjects, env->real_subj);
553 else if (rc > 0)
554 mutt_list_insert_after(subjects, np, env->real_subj);
555 }
556
557 while (!cur->next && (cur != start))
558 {
559 cur = cur->parent;
560 }
561 if (cur == start)
562 break;
563 cur = cur->next;
564 }
565}
566
576static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
577{
578 if (!m)
579 return NULL;
580
581 struct HashElem *he = NULL;
582 struct MuttThread *tmp = NULL, *last = NULL;
583 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
584 time_t date = 0;
585
586 make_subject_list(&subjects, cur, &date);
587
588 struct ListNode *np = NULL;
589 STAILQ_FOREACH(np, &subjects, entries)
590 {
591 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
592 {
593 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
594 tmp = ((struct Email *) he->data)->thread;
595 if ((tmp != cur) && /* don't match the same message */
596 !tmp->fake_thread && /* don't match pseudo threads */
597 tmp->message->subject_changed && /* only match interesting replies */
598 !is_descendant(tmp, cur) && /* don't match in the same thread */
599 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
600 (!last || (c_thread_received ?
601 (last->message->received < tmp->message->received) :
602 (last->message->date_sent < tmp->message->date_sent))) &&
603 tmp->message->env->real_subj &&
605 {
606 last = tmp; /* best match so far */
607 }
608 }
609 }
610
611 mutt_list_clear(&subjects);
612 return last;
613}
614
620static struct HashTable *make_subj_hash(struct Mailbox *m)
621{
622 if (!m)
623 return NULL;
624
626
627 for (int i = 0; i < m->msg_count; i++)
628 {
629 struct Email *e = m->emails[i];
630 if (!e || !e->env)
631 continue;
632 if (e->env->real_subj)
633 mutt_hash_insert(hash, e->env->real_subj, e);
634 }
635
636 return hash;
637}
638
645static void pseudo_threads(struct ThreadsContext *tctx)
646{
647 if (!tctx || !tctx->mailbox)
648 return;
649
650 struct Mailbox *m = tctx->mailbox;
651
652 struct MuttThread *tree = tctx->tree;
653 struct MuttThread *top = tree;
654 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
655 *nextchild = NULL;
656
657 if (!m->subj_hash)
659
660 while (tree)
661 {
662 cur = tree;
663 tree = tree->next;
664 parent = find_subject(m, cur);
665 if (parent)
666 {
667 cur->fake_thread = true;
668 unlink_message(&top, cur);
670 parent->sort_children = true;
671 tmp = cur;
672 while (true)
673 {
674 while (!tmp->message)
675 tmp = tmp->child;
676
677 /* if the message we're attaching has pseudo-children, they
678 * need to be attached to its parent, so move them up a level.
679 * but only do this if they have the same real subject as the
680 * parent, since otherwise they rightly belong to the message
681 * we're attaching. */
682 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
684 {
685 tmp->message->subject_changed = false;
686
687 for (curchild = tmp->child; curchild;)
688 {
689 nextchild = curchild->next;
690 if (curchild->fake_thread)
691 {
692 unlink_message(&tmp->child, curchild);
693 insert_message(&parent->child, parent, curchild);
694 }
695 curchild = nextchild;
696 }
697 }
698
699 while (!tmp->next && (tmp != cur))
700 {
701 tmp = tmp->parent;
702 }
703 if (tmp == cur)
704 break;
705 tmp = tmp->next;
706 }
707 }
708 }
709 tctx->tree = top;
710}
711
717{
718 if (!tctx || !tctx->mailbox || !tctx->mailbox->emails || !tctx->tree)
719 return;
720
721 for (int i = 0; i < tctx->mailbox->msg_count; i++)
722 {
723 struct Email *e = tctx->mailbox->emails[i];
724 if (!e)
725 break;
726
727 /* mailbox may have been only partially read */
728 e->thread = NULL;
729 e->threaded = false;
730 }
731 tctx->tree = NULL;
732 mutt_hash_free(&tctx->hash);
733}
734
744static int compare_threads(const void *a, const void *b, void *arg)
745{
746 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
747 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
748 const struct ThreadsContext *tctx = arg;
749 assert(ta->parent == tb->parent);
750 /* If c_sort ties, remember we are building the thread array in
751 * reverse from the index the mails had in the mailbox. */
752 if (ta->parent)
753 {
756 }
757 else
758 {
760 mx_type(tctx->mailbox), tctx->c_sort,
762 }
763}
764
770static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
771{
772 struct MuttThread *thread = tctx->tree;
773 if (!thread)
774 return;
775
776 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
777 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
778 struct Email *oldsort_thread_key = NULL;
779 int i, array_size;
780 bool sort_top = false;
781
782 /* we put things into the array backwards to save some cycles,
783 * but we want to have to move less stuff around if we're
784 * resorting, so we sort backwards and then put them back
785 * in reverse order so they're forwards */
786 const bool reverse = (mutt_thread_style() == UT_REVERSE);
787 short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
788 short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
789 if ((c_sort & SORT_MASK) == SORT_THREADS)
790 {
791 assert(!(c_sort & SORT_REVERSE) != reverse);
792 assert(cs_subset_enum(NeoMutt->sub, "use_threads") == UT_UNSET);
793 c_sort = c_sort_aux;
794 }
795 c_sort ^= SORT_REVERSE;
796 c_sort_aux ^= SORT_REVERSE;
797 if (init || tctx->c_sort != c_sort || tctx->c_sort_aux != c_sort_aux)
798 {
799 tctx->c_sort = c_sort;
800 tctx->c_sort_aux = c_sort_aux;
801 init = true;
802 }
803
804 top = thread;
805
806 array_size = 256;
807 array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
808 while (true)
809 {
810 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
811 {
812 thread->sort_thread_key = NULL;
813 thread->sort_aux_key = NULL;
814
815 if (thread->parent)
816 thread->parent->sort_children = true;
817 else
818 sort_top = true;
819 }
820
821 if (thread->child)
822 {
824 continue;
825 }
826 else
827 {
828 /* if it has no children, it must be real. sort it on its own merits */
831
832 if (thread->next)
833 {
834 thread = thread->next;
835 continue;
836 }
837 }
838
839 while (!thread->next)
840 {
841 /* if it has siblings and needs to be sorted, sort it... */
842 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
843 {
844 /* put them into the array */
845 for (i = 0; thread; i++, thread = thread->prev)
846 {
847 if (i >= array_size)
848 mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
849
850 array[i] = thread;
851 }
852
853 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
854
855 /* attach them back together. make thread the last sibling. */
856 thread = array[0];
857 thread->next = NULL;
858 array[i - 1]->prev = NULL;
859
860 if (thread->parent)
861 thread->parent->child = array[i - 1];
862 else
863 top = array[i - 1];
864
865 while (--i)
866 {
867 array[i - 1]->prev = array[i];
868 array[i]->next = array[i - 1];
869 }
870 }
871
872 if (thread->parent)
873 {
874 tmp = thread;
876
878 {
879 /* we just sorted its children */
880 thread->sort_children = false;
881
882 oldsort_aux_key = thread->sort_aux_key;
883 oldsort_thread_key = thread->sort_thread_key;
884
885 /* update sort keys. sort_aux_key will be the first or last
886 * sibling, as appropriate... */
888 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
890 tmp->sort_aux_key;
891
892 if (c_sort_aux & SORT_LAST)
893 {
894 if (!thread->sort_aux_key ||
895 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mx_type(tctx->mailbox),
896 c_sort_aux | SORT_REVERSE, SORT_ORDER) > 0))
897 {
898 thread->sort_aux_key = sort_aux_key;
899 }
900 }
901 else if (!thread->sort_aux_key)
902 thread->sort_aux_key = sort_aux_key;
903
904 /* ...but sort_thread_key may require searching the entire
905 * list of siblings */
906 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
907 {
909 }
910 else
911 {
912 if (thread->message)
913 {
915 }
916 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
917 {
918 thread->sort_thread_key = tmp->sort_thread_key;
919 }
920 else
921 {
923 }
924 if (c_sort & SORT_LAST)
925 {
926 for (tmp = thread->child; tmp; tmp = tmp->next)
927 {
928 if (tmp->sort_thread_key == thread->sort_thread_key)
929 continue;
930 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key,
931 mx_type(tctx->mailbox),
932 c_sort | SORT_REVERSE, SORT_ORDER) > 0))
933 {
934 thread->sort_thread_key = tmp->sort_thread_key;
935 }
936 }
937 }
938 }
939
940 /* if a sort_key has changed, we need to resort it and siblings */
941 if ((oldsort_aux_key != thread->sort_aux_key) ||
942 (oldsort_thread_key != thread->sort_thread_key))
943 {
944 if (thread->parent)
945 thread->parent->sort_children = true;
946 else
947 sort_top = true;
948 }
949 }
950 }
951 else
952 {
953 FREE(&array);
954 tctx->tree = top;
955 return;
956 }
957 }
958
959 thread = thread->next;
960 }
961}
962
968static void check_subjects(struct Mailbox *m, bool init)
969{
970 if (!m)
971 return;
972
973 for (int i = 0; i < m->msg_count; i++)
974 {
975 struct Email *e = m->emails[i];
976 if (!e || !e->thread)
977 continue;
978
979 if (e->thread->check_subject)
980 e->thread->check_subject = false;
981 else if (!init)
982 continue;
983
984 /* figure out which messages have subjects different than their parents' */
985 struct MuttThread *tmp = e->thread->parent;
986 while (tmp && !tmp->message)
987 {
988 tmp = tmp->parent;
989 }
990
991 if (!tmp)
992 {
993 e->subject_changed = true;
994 }
995 else if (e->env->real_subj && tmp->message->env->real_subj)
996 {
998 }
999 else
1000 {
1001 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1002 }
1003 }
1004}
1005
1011void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
1012{
1013 if (!tctx || !tctx->mailbox)
1014 return;
1015
1016 struct Mailbox *m = tctx->mailbox;
1017
1018 struct Email *e = NULL;
1019 int i, using_refs = 0;
1020 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1021 struct MuttThread top = { 0 };
1022 struct ListNode *ref = NULL;
1023
1024 assert(m->msg_count > 0);
1025 if (!tctx->hash)
1026 init = true;
1027
1028 if (init)
1029 {
1032 }
1033
1034 /* we want a quick way to see if things are actually attached to the top of the
1035 * thread tree or if they're just dangling, so we attach everything to a top
1036 * node temporarily */
1037 top.parent = NULL;
1038 top.next = NULL;
1039 top.prev = NULL;
1040
1041 top.child = tctx->tree;
1042 for (thread = tctx->tree; thread; thread = thread->next)
1043 thread->parent = &top;
1044
1045 /* put each new message together with the matching messageless MuttThread if it
1046 * exists. otherwise, if there is a MuttThread that already has a message, thread
1047 * new message as an identical child. if we didn't attach the message to a
1048 * MuttThread, make a new one for it. */
1049 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1050 for (i = 0; i < m->msg_count; i++)
1051 {
1052 e = m->emails[i];
1053 if (!e)
1054 continue;
1055
1056 if (!e->thread)
1057 {
1058 if ((!init || c_duplicate_threads) && e->env->message_id)
1059 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1060 else
1061 thread = NULL;
1062
1063 if (thread && !thread->message)
1064 {
1065 /* this is a message which was missing before */
1066 thread->message = e;
1067 e->thread = thread;
1068 thread->check_subject = true;
1069
1070 /* mark descendants as needing subject_changed checked */
1071 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1072 {
1073 while (!tmp->message)
1074 tmp = tmp->child;
1075 tmp->check_subject = true;
1076 while (!tmp->next && (tmp != thread))
1077 tmp = tmp->parent;
1078 if (tmp != thread)
1079 tmp = tmp->next;
1080 }
1081
1082 if (thread->parent)
1083 {
1084 /* remove threading info above it based on its children, which we'll
1085 * recalculate based on its headers. make sure not to leave
1086 * dangling missing messages. note that we haven't kept track
1087 * of what info came from its children and what from its siblings'
1088 * children, so we just remove the stuff that's definitely from it */
1089 do
1090 {
1091 tmp = thread->parent;
1092 unlink_message(&tmp->child, thread);
1093 thread->parent = NULL;
1094 thread->sort_thread_key = NULL;
1095 thread->sort_aux_key = NULL;
1096 thread->fake_thread = false;
1097 thread = tmp;
1098 } while (thread != &top && !thread->child && !thread->message);
1099 }
1100 }
1101 else
1102 {
1103 tnew = (c_duplicate_threads ? thread : NULL);
1104
1105 thread = mutt_mem_calloc(1, sizeof(struct MuttThread));
1106 thread->message = e;
1107 thread->check_subject = true;
1108 e->thread = thread;
1109 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1110
1111 if (tnew)
1112 {
1113 if (tnew->duplicate_thread)
1114 tnew = tnew->parent;
1115
1116 thread = e->thread;
1117
1118 insert_message(&tnew->child, tnew, thread);
1119 thread->duplicate_thread = true;
1120 thread->message->threaded = true;
1121 }
1122 }
1123 }
1124 else
1125 {
1126 /* unlink pseudo-threads because they might be children of newly
1127 * arrived messages */
1128 thread = e->thread;
1129 for (tnew = thread->child; tnew;)
1130 {
1131 tmp = tnew->next;
1132 if (tnew->fake_thread)
1133 {
1134 unlink_message(&thread->child, tnew);
1135 insert_message(&top.child, &top, tnew);
1136 tnew->fake_thread = false;
1137 }
1138 tnew = tmp;
1139 }
1140 }
1141 }
1142
1143 /* thread by references */
1144 for (i = 0; i < m->msg_count; i++)
1145 {
1146 e = m->emails[i];
1147 if (!e)
1148 break;
1149
1150 if (e->threaded)
1151 continue;
1152 e->threaded = true;
1153
1154 thread = e->thread;
1155 if (!thread)
1156 continue;
1157 using_refs = 0;
1158
1159 while (true)
1160 {
1161 if (using_refs == 0)
1162 {
1163 /* look at the beginning of in-reply-to: */
1164 ref = STAILQ_FIRST(&e->env->in_reply_to);
1165 if (ref)
1166 {
1167 using_refs = 1;
1168 }
1169 else
1170 {
1171 ref = STAILQ_FIRST(&e->env->references);
1172 using_refs = 2;
1173 }
1174 }
1175 else if (using_refs == 1)
1176 {
1177 /* if there's no references header, use all the in-reply-to:
1178 * data that we have. otherwise, use the first reference
1179 * if it's different than the first in-reply-to, otherwise use
1180 * the second reference (since at least eudora puts the most
1181 * recent reference in in-reply-to and the rest in references) */
1182 if (STAILQ_EMPTY(&e->env->references))
1183 {
1184 ref = STAILQ_NEXT(ref, entries);
1185 }
1186 else
1187 {
1188 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1189 ref = STAILQ_FIRST(&e->env->references);
1190 else
1191 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1192
1193 using_refs = 2;
1194 }
1195 }
1196 else
1197 {
1198 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1199 }
1200
1201 if (!ref)
1202 break;
1203
1204 tnew = mutt_hash_find(tctx->hash, ref->data);
1205 if (tnew)
1206 {
1207 if (tnew->duplicate_thread)
1208 tnew = tnew->parent;
1209 if (is_descendant(tnew, thread)) /* no loops! */
1210 continue;
1211 }
1212 else
1213 {
1214 tnew = mutt_mem_calloc(1, sizeof(struct MuttThread));
1215 mutt_hash_insert(tctx->hash, ref->data, tnew);
1216 }
1217
1218 if (thread->parent)
1219 unlink_message(&top.child, thread);
1220 insert_message(&tnew->child, tnew, thread);
1221 thread = tnew;
1222 if (thread->message || (thread->parent && (thread->parent != &top)))
1223 break;
1224 }
1225
1226 if (!thread->parent)
1227 insert_message(&top.child, &top, thread);
1228 }
1229
1230 /* detach everything from the temporary top node */
1231 for (thread = top.child; thread; thread = thread->next)
1232 {
1233 thread->parent = NULL;
1234 }
1235 tctx->tree = top.child;
1236
1237 check_subjects(tctx->mailbox, init);
1238
1239 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1240 if (!c_strict_threads)
1241 pseudo_threads(tctx);
1242
1243 /* if $sort_aux or similar changed after the mailbox is sorted, then
1244 * all the subthreads need to be resorted */
1245 if (tctx->tree)
1246 {
1248 OptSortSubthreads = false;
1249
1250 /* Put the list into an array. */
1251 linearize_tree(tctx);
1252
1253 /* Draw the thread tree. */
1254 mutt_draw_tree(tctx);
1255 }
1256}
1257
1265int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1266{
1267 struct MuttThread *cur = NULL;
1268 struct Email *e_tmp = NULL;
1269
1270 const enum UseThreads threaded = mutt_thread_style();
1271 if (threaded == UT_FLAT)
1272 {
1273 mutt_error(_("Threading is not enabled"));
1274 return e->vnum;
1275 }
1276
1277 cur = e->thread;
1278
1279 if (subthreads)
1280 {
1281 if (forwards ^ (threaded == UT_REVERSE))
1282 {
1283 while (!cur->next && cur->parent)
1284 cur = cur->parent;
1285 }
1286 else
1287 {
1288 while (!cur->prev && cur->parent)
1289 cur = cur->parent;
1290 }
1291 }
1292 else
1293 {
1294 while (cur->parent)
1295 cur = cur->parent;
1296 }
1297
1298 if (forwards ^ (threaded == UT_REVERSE))
1299 {
1300 do
1301 {
1302 cur = cur->next;
1303 if (!cur)
1304 return -1;
1305 e_tmp = find_virtual(cur, false);
1306 } while (!e_tmp);
1307 }
1308 else
1309 {
1310 do
1311 {
1312 cur = cur->prev;
1313 if (!cur)
1314 return -1;
1315 e_tmp = find_virtual(cur, true);
1316 } while (!e_tmp);
1317 }
1318
1319 return e_tmp->vnum;
1320}
1321
1329int mutt_parent_message(struct Email *e, bool find_root)
1330{
1331 if (!e)
1332 return -1;
1333
1334 struct MuttThread *thread = NULL;
1335 struct Email *e_parent = NULL;
1336
1337 if (!mutt_using_threads())
1338 {
1339 mutt_error(_("Threading is not enabled"));
1340 return e->vnum;
1341 }
1342
1343 /* Root may be the current message */
1344 if (find_root)
1345 e_parent = e;
1346
1347 for (thread = e->thread->parent; thread; thread = thread->parent)
1348 {
1349 e = thread->message;
1350 if (e)
1351 {
1352 e_parent = e;
1353 if (!find_root)
1354 break;
1355 }
1356 }
1357
1358 if (!e_parent)
1359 {
1360 mutt_error(_("Parent message is not available"));
1361 return -1;
1362 }
1363 if (!is_visible(e_parent))
1364 {
1365 if (find_root)
1366 mutt_error(_("Root message is not visible in this limited view"));
1367 else
1368 mutt_error(_("Parent message is not visible in this limited view"));
1369 return -1;
1370 }
1371 return e_parent->vnum;
1372}
1373
1379off_t mutt_set_vnum(struct Mailbox *m)
1380{
1381 if (!m)
1382 return 0;
1383
1384 off_t vsize = 0;
1385 const int padding = mx_msg_padding_size(m);
1386
1387 m->vcount = 0;
1388
1389 for (int i = 0; i < m->msg_count; i++)
1390 {
1391 struct Email *e = m->emails[i];
1392 if (!e)
1393 break;
1394
1395 if (e->vnum >= 0)
1396 {
1397 e->vnum = m->vcount;
1398 m->v2r[m->vcount] = i;
1399 m->vcount++;
1400 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1401 }
1402 }
1403
1404 return vsize;
1405}
1406
1414{
1415 struct MuttThread *thread = NULL, *top = NULL;
1416 struct Email *e_root = NULL;
1417 const enum UseThreads threaded = mutt_thread_style();
1418 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1419 int num_hidden = 0, new_mail = 0, old_mail = 0;
1420 bool flagged = false;
1421 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1422
1423 if (threaded == UT_FLAT)
1424 {
1425 mutt_error(_("Threading is not enabled"));
1426 return e_cur->vnum;
1427 }
1428
1429 if (!e_cur->thread)
1430 {
1431 return e_cur->vnum;
1432 }
1433
1434 final = e_cur->vnum;
1435 thread = e_cur->thread;
1436 while (thread->parent)
1437 thread = thread->parent;
1438 top = thread;
1439 while (!thread->message)
1440 thread = thread->child;
1441 e_cur = thread->message;
1442 minmsgno = e_cur->msgno;
1443
1444 if (!e_cur->read && e_cur->visible)
1445 {
1446 if (e_cur->old)
1447 old_mail = 2;
1448 else
1449 new_mail = 1;
1450 if (e_cur->msgno < min_unread_msgno)
1451 {
1452 min_unread = e_cur->vnum;
1453 min_unread_msgno = e_cur->msgno;
1454 }
1455 }
1456
1457 if (e_cur->flagged && e_cur->visible)
1458 flagged = true;
1459
1460 if ((e_cur->vnum == -1) && e_cur->visible)
1461 num_hidden++;
1462
1464 {
1465 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1466 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1467 if (e_cur->vnum != -1)
1468 {
1469 e_root = e_cur;
1470 if (flag & MUTT_THREAD_COLLAPSE)
1471 final = e_root->vnum;
1472 }
1473 }
1474
1475 if ((thread == top) && !(thread = thread->child))
1476 {
1477 /* return value depends on action requested */
1479 {
1480 e_cur->num_hidden = num_hidden;
1481 return final;
1482 }
1483 if (flag & MUTT_THREAD_UNREAD)
1484 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1485 if (flag & MUTT_THREAD_NEXT_UNREAD)
1486 return min_unread;
1487 if (flag & MUTT_THREAD_FLAGGED)
1488 return flagged;
1489 }
1490
1491 while (true)
1492 {
1493 e_cur = thread->message;
1494
1495 if (e_cur)
1496 {
1498 {
1499 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1500 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1501 if (!e_root && e_cur->visible)
1502 {
1503 e_root = e_cur;
1504 if (flag & MUTT_THREAD_COLLAPSE)
1505 final = e_root->vnum;
1506 }
1507
1508 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1509 (e_cur->msgno < minmsgno) && e_cur->visible)
1510 {
1511 minmsgno = e_cur->msgno;
1512 final = e_cur->vnum;
1513 }
1514
1515 if (flag & MUTT_THREAD_COLLAPSE)
1516 {
1517 if (e_cur != e_root)
1518 e_cur->vnum = -1;
1519 }
1520 else
1521 {
1522 if (e_cur->visible)
1523 e_cur->vnum = e_cur->msgno;
1524 }
1525 }
1526
1527 if (!e_cur->read && e_cur->visible)
1528 {
1529 if (e_cur->old)
1530 old_mail = 2;
1531 else
1532 new_mail = 1;
1533 if (e_cur->msgno < min_unread_msgno)
1534 {
1535 min_unread = e_cur->vnum;
1536 min_unread_msgno = e_cur->msgno;
1537 }
1538 }
1539
1540 if (e_cur->flagged && e_cur->visible)
1541 flagged = true;
1542
1543 if ((e_cur->vnum == -1) && e_cur->visible)
1544 num_hidden++;
1545 }
1546
1547 if (thread->child)
1548 thread = thread->child;
1549 else if (thread->next)
1550 thread = thread->next;
1551 else
1552 {
1553 bool done = false;
1554 while (!thread->next)
1555 {
1556 thread = thread->parent;
1557 if (thread == top)
1558 {
1559 done = true;
1560 break;
1561 }
1562 }
1563 if (done)
1564 break;
1565 thread = thread->next;
1566 }
1567 }
1568
1569 /* re-traverse the thread and store num_hidden in all headers, with or
1570 * without a virtual index. this will allow ~v to match all collapsed
1571 * messages when switching sort order to non-threaded. */
1572 if (flag & MUTT_THREAD_COLLAPSE)
1573 {
1574 thread = top;
1575 while (true)
1576 {
1577 e_cur = thread->message;
1578 if (e_cur)
1579 e_cur->num_hidden = num_hidden + 1;
1580
1581 if (thread->child)
1582 thread = thread->child;
1583 else if (thread->next)
1584 thread = thread->next;
1585 else
1586 {
1587 bool done = false;
1588 while (!thread->next)
1589 {
1590 thread = thread->parent;
1591 if (thread == top)
1592 {
1593 done = true;
1594 break;
1595 }
1596 }
1597 if (done)
1598 break;
1599 thread = thread->next;
1600 }
1601 }
1602 }
1603
1604 /* return value depends on action requested */
1606 return final;
1607 if (flag & MUTT_THREAD_UNREAD)
1608 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1609 if (flag & MUTT_THREAD_NEXT_UNREAD)
1610 return min_unread;
1611 if (flag & MUTT_THREAD_FLAGGED)
1612 return flagged;
1613
1614 return 0;
1615}
1616
1624int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
1625{
1626 if (!m || !e)
1627 return 1;
1628
1629 struct MuttThread *threads[2];
1630 int rc;
1631
1632 const enum UseThreads threaded = mutt_thread_style();
1633 if ((threaded == UT_FLAT) || !e->thread)
1634 return 1;
1635
1636 threads[0] = e->thread;
1637 while (threads[0]->parent)
1638 threads[0] = threads[0]->parent;
1639
1640 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1641
1642 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1643 {
1644 while (!threads[i]->message)
1645 threads[i] = threads[i]->child;
1646 }
1647
1648 if (threaded == UT_REVERSE)
1649 {
1650 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1651 }
1652 else
1653 {
1654 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1655 threads[0]->message->msgno;
1656 }
1657
1658 if (mit == MIT_POSITION)
1659 rc += 1;
1660
1661 return rc;
1662}
1663
1670{
1671 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1672
1673 for (int i = 0; i < m->msg_count; i++)
1674 {
1675 struct Email *e = m->emails[i];
1676 if (!e || !e->env)
1677 continue;
1678
1679 if (e->env->message_id)
1680 mutt_hash_insert(hash, e->env->message_id, e);
1681 }
1682
1683 return hash;
1684}
1685
1693static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1694{
1695 if (child == parent)
1696 return false;
1697
1698 mutt_break_thread(child);
1700 mutt_set_flag(m, child, MUTT_TAG, false);
1701
1702 child->changed = true;
1703 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1704 return true;
1705}
1706
1714bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
1715{
1716 if (!parent || !children || !m)
1717 return false;
1718
1719 bool changed = false;
1720
1721 struct EmailNode *en = NULL;
1722 STAILQ_FOREACH(en, children, entries)
1723 {
1724 changed |= link_threads(parent, en->email, m);
1725 }
1726
1727 return changed;
1728}
1729
1735{
1736 struct MuttThread *thread = NULL;
1737 struct MuttThread *top = tctx->tree;
1738 while ((thread = top))
1739 {
1740 while (!thread->message)
1741 thread = thread->child;
1742
1743 struct Email *e = thread->message;
1744 if (e->collapsed)
1746 top = top->next;
1747 }
1748}
1749
1755void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1756{
1757 struct MuttThread *thread = NULL;
1758 struct MuttThread *top = tctx->tree;
1759 while ((thread = top))
1760 {
1761 while (!thread->message)
1762 thread = thread->child;
1763
1764 struct Email *e = thread->message;
1765
1766 if (e->collapsed != collapse)
1767 {
1768 if (e->collapsed)
1770 else if (mutt_thread_can_collapse(e))
1772 }
1773 top = top->next;
1774 }
1775}
1776
1784{
1785 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1786 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1787 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1788 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1789}
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:168
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.
#define CSR_ERR_INVALID
Value hasn't been set.
Definition: set.h:38
#define CSR_SUCCESS
Action completed successfully.
Definition: set.h:35
Convenience wrapper for the core headers.
Structs that make up an email.
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition: envelope.h:34
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition: globals.c:87
Global variables.
int sort_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
Validate values of "sort" - Implements ConfigDef::validator() -.
Definition: mutt_thread.c: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:108
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition: hash.h:111
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:470
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:807
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:652
Many unsorted constants and some structs.
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:88
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:243
void mutt_clear_threads(struct ThreadsContext *tctx)
Clear the threading of message in a mailbox.
Definition: mutt_thread.c:716
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition: mutt_thread.c:1413
void mutt_thread_ctx_free(struct ThreadsContext **tctx)
Finalize a threading context.
Definition: mutt_thread.c:371
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:770
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
Toggle collapse.
Definition: mutt_thread.c:1755
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
Re-collapse threads marked as collapsed.
Definition: mutt_thread.c:1734
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:390
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
Count the messages in a thread.
Definition: mutt_thread.c:1624
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:520
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:744
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:645
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:620
bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
Forcibly link threads together.
Definition: mutt_thread.c:1714
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
Sort email threads.
Definition: mutt_thread.c:1011
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:1379
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition: mutt_thread.c:1265
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1783
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:576
int mutt_parent_message(struct Email *e, bool find_root)
Find the parent of a message.
Definition: mutt_thread.c:1329
static void check_subjects(struct Mailbox *m, bool init)
Find out which emails' subjects differ from their parent's.
Definition: mutt_thread.c:968
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1669
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1693
struct ThreadsContext * mutt_thread_ctx_init(struct Mailbox *m)
Initialize a threading context.
Definition: mutt_thread.c:358
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:1556
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1844
API for mailboxes.
Prototypes for many functions.
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:63
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 SORT_MASK
Mask for the sort id.
Definition: sort2.h:74
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:76
@ SORT_ORDER
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:44
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:45
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:75
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:252
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:100
bool visible
Is this message part of the view?
Definition: email.h:120
struct Envelope * env
Envelope information.
Definition: email.h:66
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:119
struct Body * body
List of MIME parts.
Definition: email.h:67
bool subject_changed
Used for threading.
Definition: email.h:105
char * tree
Character string to print thread tree.
Definition: email.h:124
bool old
Email is seen, but unread.
Definition: email.h:47
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
Definition: email.h:122
struct AttrColor * attr_color
Color-pair to use when displaying in the index.
Definition: email.h:111
bool changed
Email has been edited.
Definition: email.h:75
bool flagged
Marked important?
Definition: email.h:45
bool threaded
Used for threading.
Definition: email.h:107
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
int vnum
Virtual message number.
Definition: email.h:113
int msgno
Number displayed to the user.
Definition: email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
struct MuttThread * thread
Thread of Emails.
Definition: email.h:118
An enumeration.
Definition: enum.h:30
The header of an Email.
Definition: envelope.h:57
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:92
char * message_id
Message ID.
Definition: envelope.h:73
struct ListHead references
message references (in reverse order)
Definition: envelope.h:85
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:86
char * subject
Email's subject.
Definition: envelope.h:70
char * real_subj
Offset of the real subject.
Definition: envelope.h:71
The item stored in a Hash Table.
Definition: hash.h:44
struct HashElem * next
Linked List.
Definition: hash.h:48
void * data
User-supplied data.
Definition: hash.h:47
A Hash Table.
Definition: hash.h:96
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:124
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