NeoMutt  2020-04-24
Teaching an old dog new tricks
DOXYGEN
mutt_thread.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <limits.h>
31 #include <stdbool.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.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 "context.h"
42 #include "mutt_menu.h"
43 #include "mx.h"
44 #include "protos.h"
45 #include "sort.h"
46 
47 /* These Config Variables are only used in mutt_thread.c */
55 bool C_SortRe;
58 
65 static bool is_visible(struct Email *e, struct Context *ctx)
66 {
67  return e->vnum >= 0 || (e->collapsed && (!ctx->pattern || e->limited));
68 }
69 
76 static bool need_display_subject(struct Context *ctx, struct Email *e)
77 {
78  struct MuttThread *tmp = NULL;
79  struct MuttThread *tree = e->thread;
80 
81  /* if the user disabled subject hiding, display it */
83  return true;
84 
85  /* if our subject is different from our parent's, display it */
86  if (e->subject_changed)
87  return true;
88 
89  /* if our subject is different from that of our closest previously displayed
90  * sibling, display the subject */
91  for (tmp = tree->prev; tmp; tmp = tmp->prev)
92  {
93  e = tmp->message;
94  if (e && is_visible(e, ctx))
95  {
96  if (e->subject_changed)
97  return true;
98  break;
99  }
100  }
101 
102  /* if there is a parent-to-child subject change anywhere between us and our
103  * closest displayed ancestor, display the subject */
104  for (tmp = tree->parent; tmp; tmp = tmp->parent)
105  {
106  e = tmp->message;
107  if (e)
108  {
109  if (is_visible(e, ctx))
110  return false;
111  if (e->subject_changed)
112  return true;
113  }
114  }
115 
116  /* if we have no visible parent or previous sibling, display the subject */
117  return true;
118 }
119 
124 static void linearize_tree(struct Context *ctx)
125 {
126  if (!ctx || !ctx->mailbox)
127  return;
128 
129  struct Mailbox *m = ctx->mailbox;
130 
131  struct MuttThread *tree = ctx->tree;
132  struct Email **array = m->emails + ((C_Sort & SORT_REVERSE) ? m->msg_count - 1 : 0);
133 
134  while (tree)
135  {
136  while (!tree->message)
137  tree = tree->child;
138 
139  *array = tree->message;
140  array += (C_Sort & SORT_REVERSE) ? -1 : 1;
141 
142  if (tree->child)
143  tree = tree->child;
144  else
145  {
146  while (tree)
147  {
148  if (tree->next)
149  {
150  tree = tree->next;
151  break;
152  }
153  else
154  tree = tree->parent;
155  }
156  }
157  }
158 }
159 
172 static void calculate_visibility(struct Context *ctx, int *max_depth)
173 {
174  struct MuttThread *tmp = NULL;
175  struct MuttThread *tree = ctx->tree;
176  int hide_top_missing = C_HideTopMissing && !C_HideMissing;
177  int hide_top_limited = C_HideTopLimited && !C_HideLimited;
178  int depth = 0;
179 
180  /* we walk each level backwards to make it easier to compute next_subtree_visible */
181  while (tree->next)
182  tree = tree->next;
183  *max_depth = 0;
184 
185  while (true)
186  {
187  if (depth > *max_depth)
188  *max_depth = depth;
189 
190  tree->subtree_visible = 0;
191  if (tree->message)
192  {
193  FREE(&tree->message->tree);
194  if (is_visible(tree->message, ctx))
195  {
196  tree->deep = true;
197  tree->visible = true;
199  for (tmp = tree; tmp; tmp = tmp->parent)
200  {
201  if (tmp->subtree_visible)
202  {
203  tmp->deep = true;
204  tmp->subtree_visible = 2;
205  break;
206  }
207  else
208  tmp->subtree_visible = 1;
209  }
210  }
211  else
212  {
213  tree->visible = false;
214  tree->deep = !C_HideLimited;
215  }
216  }
217  else
218  {
219  tree->visible = false;
220  tree->deep = !C_HideMissing;
221  }
222  tree->next_subtree_visible =
223  tree->next && (tree->next->next_subtree_visible || tree->next->subtree_visible);
224  if (tree->child)
225  {
226  depth++;
227  tree = tree->child;
228  while (tree->next)
229  tree = tree->next;
230  }
231  else if (tree->prev)
232  tree = tree->prev;
233  else
234  {
235  while (tree && !tree->prev)
236  {
237  depth--;
238  tree = tree->parent;
239  }
240  if (!tree)
241  break;
242  tree = tree->prev;
243  }
244  }
245 
246  /* now fix up for the OPTHIDETOP* options if necessary */
247  if (hide_top_limited || hide_top_missing)
248  {
249  tree = ctx->tree;
250  while (true)
251  {
252  if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
253  ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
254  {
255  tree->deep = false;
256  }
257  if (!tree->deep && tree->child && tree->subtree_visible)
258  tree = tree->child;
259  else if (tree->next)
260  tree = tree->next;
261  else
262  {
263  while (tree && !tree->next)
264  tree = tree->parent;
265  if (!tree)
266  break;
267  tree = tree->next;
268  }
269  }
270  }
271 }
272 
285 void mutt_draw_tree(struct Context *ctx)
286 {
287  char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
290  int depth = 0, start_depth = 0, max_depth = 0, width = C_NarrowTree ? 1 : 2;
291  struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
292  struct MuttThread *tree = ctx->tree;
293 
294  /* Do the visibility calculations and free the old thread chars.
295  * From now on we can simply ignore invisible subtrees */
296  calculate_visibility(ctx, &max_depth);
297  pfx = mutt_mem_malloc((width * max_depth) + 2);
298  arrow = mutt_mem_malloc((width * max_depth) + 2);
299  while (tree)
300  {
301  if (depth != 0)
302  {
303  myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
304  if (start_depth == depth)
305  myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
306  else if (parent->message && !C_HideLimited)
307  myarrow[0] = MUTT_TREE_HIDDEN;
308  else if (!parent->message && !C_HideMissing)
309  myarrow[0] = MUTT_TREE_MISSING;
310  else
311  myarrow[0] = vtee;
312  if (width == 2)
313  {
314  myarrow[1] = pseudo ? MUTT_TREE_STAR :
316  }
317  if (tree->visible)
318  {
319  myarrow[width] = MUTT_TREE_RARROW;
320  myarrow[width + 1] = 0;
321  new_tree = mutt_mem_malloc(((size_t) depth * width) + 2);
322  if (start_depth > 1)
323  {
324  strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
325  mutt_str_strfcpy(new_tree + (start_depth - 1) * width, arrow,
326  (1 + depth - start_depth) * width + 2);
327  }
328  else
329  mutt_str_strfcpy(new_tree, arrow, ((size_t) depth * width) + 2);
330  tree->message->tree = new_tree;
331  }
332  }
333  if (tree->child && (depth != 0))
334  {
335  mypfx = pfx + (depth - 1) * width;
336  mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
337  if (width == 2)
338  mypfx[1] = MUTT_TREE_SPACE;
339  }
340  parent = tree;
341  nextdisp = NULL;
342  pseudo = NULL;
343  do
344  {
345  if (tree->child && tree->subtree_visible)
346  {
347  if (tree->deep)
348  depth++;
349  if (tree->visible)
350  start_depth = depth;
351  tree = tree->child;
352 
353  /* we do this here because we need to make sure that the first child thread
354  * of the old tree that we deal with is actually displayed if any are,
355  * or we might set the parent variable wrong while going through it. */
356  while (!tree->subtree_visible && tree->next)
357  tree = tree->next;
358  }
359  else
360  {
361  while (!tree->next && tree->parent)
362  {
363  if (tree == pseudo)
364  pseudo = NULL;
365  if (tree == nextdisp)
366  nextdisp = NULL;
367  if (tree->visible)
368  start_depth = depth;
369  tree = tree->parent;
370  if (tree->deep)
371  {
372  if (start_depth == depth)
373  start_depth--;
374  depth--;
375  }
376  }
377  if (tree == pseudo)
378  pseudo = NULL;
379  if (tree == nextdisp)
380  nextdisp = NULL;
381  if (tree->visible)
382  start_depth = depth;
383  tree = tree->next;
384  if (!tree)
385  break;
386  }
387  if (!pseudo && tree->fake_thread)
388  pseudo = tree;
389  if (!nextdisp && tree->next_subtree_visible)
390  nextdisp = tree;
391  } while (!tree->deep);
392  }
393 
394  FREE(&pfx);
395  FREE(&arrow);
396 }
397 
408 static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
409 {
410  struct MuttThread *start = cur;
411  struct Envelope *env = NULL;
412  time_t thisdate;
413  int rc = 0;
414 
415  while (true)
416  {
417  while (!cur->message)
418  cur = cur->child;
419 
420  if (dateptr)
421  {
422  thisdate = C_ThreadReceived ? cur->message->received : cur->message->date_sent;
423  if (!*dateptr || (thisdate < *dateptr))
424  *dateptr = thisdate;
425  }
426 
427  env = cur->message->env;
428  if (env->real_subj && ((env->real_subj != env->subject) || (!C_SortRe)))
429  {
430  struct ListNode *np = NULL;
431  STAILQ_FOREACH(np, subjects, entries)
432  {
433  rc = mutt_str_strcmp(env->real_subj, np->data);
434  if (rc >= 0)
435  break;
436  }
437  if (!np)
438  mutt_list_insert_head(subjects, env->real_subj);
439  else if (rc > 0)
440  mutt_list_insert_after(subjects, np, env->real_subj);
441  }
442 
443  while (!cur->next && (cur != start))
444  {
445  cur = cur->parent;
446  }
447  if (cur == start)
448  break;
449  cur = cur->next;
450  }
451 }
452 
462 static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
463 {
464  if (!m)
465  return NULL;
466 
467  struct HashElem *ptr = NULL;
468  struct MuttThread *tmp = NULL, *last = NULL;
469  struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
470  time_t date = 0;
471 
472  make_subject_list(&subjects, cur, &date);
473 
474  struct ListNode *np = NULL;
475  STAILQ_FOREACH(np, &subjects, entries)
476  {
477  for (ptr = mutt_hash_find_bucket(m->subj_hash, np->data); ptr; ptr = ptr->next)
478  {
479  tmp = ((struct Email *) ptr->data)->thread;
480  if ((tmp != cur) && /* don't match the same message */
481  !tmp->fake_thread && /* don't match pseudo threads */
482  tmp->message->subject_changed && /* only match interesting replies */
483  !is_descendant(tmp, cur) && /* don't match in the same thread */
484  (date >= (C_ThreadReceived ? tmp->message->received : tmp->message->date_sent)) &&
485  (!last || (C_ThreadReceived ?
486  (last->message->received < tmp->message->received) :
487  (last->message->date_sent < tmp->message->date_sent))) &&
488  tmp->message->env->real_subj &&
489  (mutt_str_strcmp(np->data, tmp->message->env->real_subj) == 0))
490  {
491  last = tmp; /* best match so far */
492  }
493  }
494  }
495 
496  mutt_list_clear(&subjects);
497  return last;
498 }
499 
505 static struct Hash *make_subj_hash(struct Mailbox *m)
506 {
507  if (!m)
508  return NULL;
509 
510  struct Hash *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_ALLOW_DUPS);
511 
512  for (int i = 0; i < m->msg_count; i++)
513  {
514  struct Email *e = m->emails[i];
515  if (!e || !e->env)
516  continue;
517  if (e->env->real_subj)
518  mutt_hash_insert(hash, e->env->real_subj, e);
519  }
520 
521  return hash;
522 }
523 
530 static void pseudo_threads(struct Context *ctx)
531 {
532  if (!ctx || !ctx->mailbox)
533  return;
534 
535  struct Mailbox *m = ctx->mailbox;
536 
537  struct MuttThread *tree = ctx->tree;
538  struct MuttThread *top = tree;
539  struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
540  *nextchild = NULL;
541 
542  if (!m->subj_hash)
543  m->subj_hash = make_subj_hash(ctx->mailbox);
544 
545  while (tree)
546  {
547  cur = tree;
548  tree = tree->next;
549  parent = find_subject(ctx->mailbox, cur);
550  if (parent)
551  {
552  cur->fake_thread = true;
553  unlink_message(&top, cur);
554  insert_message(&parent->child, parent, cur);
555  parent->sort_children = true;
556  tmp = cur;
557  while (true)
558  {
559  while (!tmp->message)
560  tmp = tmp->child;
561 
562  /* if the message we're attaching has pseudo-children, they
563  * need to be attached to its parent, so move them up a level.
564  * but only do this if they have the same real subject as the
565  * parent, since otherwise they rightly belong to the message
566  * we're attaching. */
567  if ((tmp == cur) || (mutt_str_strcmp(tmp->message->env->real_subj,
568  parent->message->env->real_subj) == 0))
569  {
570  tmp->message->subject_changed = false;
571 
572  for (curchild = tmp->child; curchild;)
573  {
574  nextchild = curchild->next;
575  if (curchild->fake_thread)
576  {
577  unlink_message(&tmp->child, curchild);
578  insert_message(&parent->child, parent, curchild);
579  }
580  curchild = nextchild;
581  }
582  }
583 
584  while (!tmp->next && (tmp != cur))
585  {
586  tmp = tmp->parent;
587  }
588  if (tmp == cur)
589  break;
590  tmp = tmp->next;
591  }
592  }
593  }
594  ctx->tree = top;
595 }
596 
601 void mutt_clear_threads(struct Context *ctx)
602 {
603  if (!ctx || !ctx->mailbox || !ctx->mailbox->emails)
604  return;
605 
606  struct Mailbox *m = ctx->mailbox;
607 
608  for (int i = 0; i < m->msg_count; i++)
609  {
610  struct Email *e = m->emails[i];
611  if (!e)
612  break;
613 
614  /* mailbox may have been only partially read */
615  e->thread = NULL;
616  e->threaded = false;
617  }
618  ctx->tree = NULL;
619 
621 }
622 
631 static int compare_threads(const void *a, const void *b)
632 {
633  static sort_t sort_func = NULL;
634 
635  if (a && b)
636  {
637  return (*sort_func)(&(*((struct MuttThread const *const *) a))->sort_key,
638  &(*((struct MuttThread const *const *) b))->sort_key);
639  }
640  /* a hack to let us reset sort_func even though we can't
641  * have extra arguments because of qsort */
642  else
643  {
644  sort_func = mutt_get_sort_func(C_Sort & SORT_MASK);
645  return sort_func ? 1 : 0;
646  }
647 }
648 
655 struct MuttThread *mutt_sort_subthreads(struct MuttThread *thread, bool init)
656 {
657  struct MuttThread **array = NULL, *sort_key = NULL, *top = NULL, *tmp = NULL;
658  struct Email *oldsort_key = NULL;
659  int i, array_size, sort_top = 0;
660 
661  /* we put things into the array backwards to save some cycles,
662  * but we want to have to move less stuff around if we're
663  * resorting, so we sort backwards and then put them back
664  * in reverse order so they're forwards */
665  C_Sort ^= SORT_REVERSE;
666  if (compare_threads(NULL, NULL) == 0)
667  return thread;
668 
669  top = thread;
670 
671  array_size = 256;
672  array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
673  while (true)
674  {
675  if (init || !thread->sort_key)
676  {
677  thread->sort_key = NULL;
678 
679  if (thread->parent)
680  thread->parent->sort_children = true;
681  else
682  sort_top = 1;
683  }
684 
685  if (thread->child)
686  {
687  thread = thread->child;
688  continue;
689  }
690  else
691  {
692  /* if it has no children, it must be real. sort it on its own merits */
693  thread->sort_key = thread->message;
694 
695  if (thread->next)
696  {
697  thread = thread->next;
698  continue;
699  }
700  }
701 
702  while (!thread->next)
703  {
704  /* if it has siblings and needs to be sorted, sort it... */
705  if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
706  {
707  /* put them into the array */
708  for (i = 0; thread; i++, thread = thread->prev)
709  {
710  if (i >= array_size)
711  mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
712 
713  array[i] = thread;
714  }
715 
716  qsort((void *) array, i, sizeof(struct MuttThread *), *compare_threads);
717 
718  /* attach them back together. make thread the last sibling. */
719  thread = array[0];
720  thread->next = NULL;
721  array[i - 1]->prev = NULL;
722 
723  if (thread->parent)
724  thread->parent->child = array[i - 1];
725  else
726  top = array[i - 1];
727 
728  while (--i)
729  {
730  array[i - 1]->prev = array[i];
731  array[i]->next = array[i - 1];
732  }
733  }
734 
735  if (thread->parent)
736  {
737  tmp = thread;
738  thread = thread->parent;
739 
740  if (!thread->sort_key || thread->sort_children)
741  {
742  /* make sort_key the first or last sibling, as appropriate */
743  sort_key = ((!(C_Sort & SORT_LAST)) ^ (!(C_Sort & SORT_REVERSE))) ?
744  thread->child :
745  tmp;
746 
747  /* we just sorted its children */
748  thread->sort_children = false;
749 
750  oldsort_key = thread->sort_key;
751  thread->sort_key = thread->message;
752 
753  if (C_Sort & SORT_LAST)
754  {
755  if (!thread->sort_key ||
756  ((((C_Sort & SORT_REVERSE) ? 1 : -1) *
757  compare_threads((void *) &thread, (void *) &sort_key)) > 0))
758  {
759  thread->sort_key = sort_key->sort_key;
760  }
761  }
762  else if (!thread->sort_key)
763  thread->sort_key = sort_key->sort_key;
764 
765  /* if its sort_key has changed, we need to resort it and siblings */
766  if (oldsort_key != thread->sort_key)
767  {
768  if (thread->parent)
769  thread->parent->sort_children = true;
770  else
771  sort_top = 1;
772  }
773  }
774  }
775  else
776  {
777  C_Sort ^= SORT_REVERSE;
778  FREE(&array);
779  return top;
780  }
781  }
782 
783  thread = thread->next;
784  }
785 }
786 
792 static void check_subjects(struct Mailbox *m, bool init)
793 {
794  if (!m)
795  return;
796 
797  for (int i = 0; i < m->msg_count; i++)
798  {
799  struct Email *e = m->emails[i];
800  if (!e || !e->thread)
801  continue;
802 
803  if (e->thread->check_subject)
804  e->thread->check_subject = false;
805  else if (!init)
806  continue;
807 
808  /* figure out which messages have subjects different than their parents' */
809  struct MuttThread *tmp = e->thread->parent;
810  while (tmp && !tmp->message)
811  {
812  tmp = tmp->parent;
813  }
814 
815  if (!tmp)
816  e->subject_changed = true;
817  else if (e->env->real_subj && tmp->message->env->real_subj)
818  {
819  e->subject_changed =
820  (mutt_str_strcmp(e->env->real_subj, tmp->message->env->real_subj) != 0);
821  }
822  else
823  {
824  e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
825  }
826  }
827 }
828 
834 void mutt_sort_threads(struct Context *ctx, bool init)
835 {
836  if (!ctx || !ctx->mailbox)
837  return;
838 
839  struct Mailbox *m = ctx->mailbox;
840 
841  struct Email *e = NULL;
842  int i, oldsort, using_refs = 0;
843  struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
844  struct MuttThread top = { 0 };
845  struct ListNode *ref = NULL;
846 
847  /* Set C_Sort to the secondary method to support the set sort_aux=reverse-*
848  * settings. The sorting functions just look at the value of SORT_REVERSE */
849  oldsort = C_Sort;
850  C_Sort = C_SortAux;
851 
852  if (!ctx->thread_hash)
853  init = true;
854 
855  if (init)
856  {
859  }
860 
861  /* we want a quick way to see if things are actually attached to the top of the
862  * thread tree or if they're just dangling, so we attach everything to a top
863  * node temporarily */
864  top.parent = NULL;
865  top.next = NULL;
866  top.prev = NULL;
867 
868  top.child = ctx->tree;
869  for (thread = ctx->tree; thread; thread = thread->next)
870  thread->parent = &top;
871 
872  /* put each new message together with the matching messageless MuttThread if it
873  * exists. otherwise, if there is a MuttThread that already has a message, thread
874  * new message as an identical child. if we didn't attach the message to a
875  * MuttThread, make a new one for it. */
876  for (i = 0; i < m->msg_count; i++)
877  {
878  e = m->emails[i];
879  if (!e)
880  continue;
881 
882  if (!e->thread)
883  {
884  if ((!init || C_DuplicateThreads) && e->env->message_id)
885  thread = mutt_hash_find(ctx->thread_hash, e->env->message_id);
886  else
887  thread = NULL;
888 
889  if (thread && !thread->message)
890  {
891  /* this is a message which was missing before */
892  thread->message = e;
893  e->thread = thread;
894  thread->check_subject = true;
895 
896  /* mark descendants as needing subject_changed checked */
897  for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
898  {
899  while (!tmp->message)
900  tmp = tmp->child;
901  tmp->check_subject = true;
902  while (!tmp->next && (tmp != thread))
903  tmp = tmp->parent;
904  if (tmp != thread)
905  tmp = tmp->next;
906  }
907 
908  if (thread->parent)
909  {
910  /* remove threading info above it based on its children, which we'll
911  * recalculate based on its headers. make sure not to leave
912  * dangling missing messages. note that we haven't kept track
913  * of what info came from its children and what from its siblings'
914  * children, so we just remove the stuff that's definitely from it */
915  do
916  {
917  tmp = thread->parent;
918  unlink_message(&tmp->child, thread);
919  thread->parent = NULL;
920  thread->sort_key = NULL;
921  thread->fake_thread = false;
922  thread = tmp;
923  } while (thread != &top && !thread->child && !thread->message);
924  }
925  }
926  else
927  {
928  tnew = (C_DuplicateThreads ? thread : NULL);
929 
930  thread = mutt_mem_calloc(1, sizeof(struct MuttThread));
931  thread->message = e;
932  thread->check_subject = true;
933  e->thread = thread;
935  e->env->message_id ? e->env->message_id : "", thread);
936 
937  if (tnew)
938  {
939  if (tnew->duplicate_thread)
940  tnew = tnew->parent;
941 
942  thread = e->thread;
943 
944  insert_message(&tnew->child, tnew, thread);
945  thread->duplicate_thread = true;
946  thread->message->threaded = true;
947  }
948  }
949  }
950  else
951  {
952  /* unlink pseudo-threads because they might be children of newly
953  * arrived messages */
954  thread = e->thread;
955  for (tnew = thread->child; tnew;)
956  {
957  tmp = tnew->next;
958  if (tnew->fake_thread)
959  {
960  unlink_message(&thread->child, tnew);
961  insert_message(&top.child, &top, tnew);
962  tnew->fake_thread = false;
963  }
964  tnew = tmp;
965  }
966  }
967  }
968 
969  /* thread by references */
970  for (i = 0; i < m->msg_count; i++)
971  {
972  e = m->emails[i];
973  if (!e)
974  break;
975 
976  if (e->threaded)
977  continue;
978  e->threaded = true;
979 
980  thread = e->thread;
981  if (!thread)
982  continue;
983  using_refs = 0;
984 
985  while (true)
986  {
987  if (using_refs == 0)
988  {
989  /* look at the beginning of in-reply-to: */
990  ref = STAILQ_FIRST(&e->env->in_reply_to);
991  if (ref)
992  using_refs = 1;
993  else
994  {
995  ref = STAILQ_FIRST(&e->env->references);
996  using_refs = 2;
997  }
998  }
999  else if (using_refs == 1)
1000  {
1001  /* if there's no references header, use all the in-reply-to:
1002  * data that we have. otherwise, use the first reference
1003  * if it's different than the first in-reply-to, otherwise use
1004  * the second reference (since at least eudora puts the most
1005  * recent reference in in-reply-to and the rest in references) */
1006  if (STAILQ_EMPTY(&e->env->references))
1007  ref = STAILQ_NEXT(ref, entries);
1008  else
1009  {
1010  if (mutt_str_strcmp(ref->data, STAILQ_FIRST(&e->env->references)->data) != 0)
1011  ref = STAILQ_FIRST(&e->env->references);
1012  else
1013  ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1014 
1015  using_refs = 2;
1016  }
1017  }
1018  else
1019  ref = STAILQ_NEXT(ref, entries); /* go on with references */
1020 
1021  if (!ref)
1022  break;
1023 
1024  tnew = mutt_hash_find(ctx->thread_hash, ref->data);
1025  if (tnew)
1026  {
1027  if (tnew->duplicate_thread)
1028  tnew = tnew->parent;
1029  if (is_descendant(tnew, thread)) /* no loops! */
1030  continue;
1031  }
1032  else
1033  {
1034  tnew = mutt_mem_calloc(1, sizeof(struct MuttThread));
1035  mutt_hash_insert(ctx->thread_hash, ref->data, tnew);
1036  }
1037 
1038  if (thread->parent)
1039  unlink_message(&top.child, thread);
1040  insert_message(&tnew->child, tnew, thread);
1041  thread = tnew;
1042  if (thread->message || (thread->parent && (thread->parent != &top)))
1043  break;
1044  }
1045 
1046  if (!thread->parent)
1047  insert_message(&top.child, &top, thread);
1048  }
1049 
1050  /* detach everything from the temporary top node */
1051  for (thread = top.child; thread; thread = thread->next)
1052  {
1053  thread->parent = NULL;
1054  }
1055  ctx->tree = top.child;
1056 
1057  check_subjects(ctx->mailbox, init);
1058 
1059  if (!C_StrictThreads)
1060  pseudo_threads(ctx);
1061 
1062  if (ctx->tree)
1063  {
1064  ctx->tree = mutt_sort_subthreads(ctx->tree, init);
1065 
1066  /* restore the oldsort order. */
1067  C_Sort = oldsort;
1068 
1069  /* Put the list into an array. */
1070  linearize_tree(ctx);
1071 
1072  /* Draw the thread tree. */
1073  mutt_draw_tree(ctx);
1074  }
1075 }
1076 
1084 int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1085 {
1086  struct MuttThread *cur = NULL;
1087  struct Email *e_tmp = NULL;
1088 
1089  if ((C_Sort & SORT_MASK) != SORT_THREADS)
1090  {
1091  mutt_error(_("Threading is not enabled"));
1092  return e->vnum;
1093  }
1094 
1095  cur = e->thread;
1096 
1097  if (subthreads)
1098  {
1099  if (forwards ^ ((C_Sort & SORT_REVERSE) != 0))
1100  {
1101  while (!cur->next && cur->parent)
1102  cur = cur->parent;
1103  }
1104  else
1105  {
1106  while (!cur->prev && cur->parent)
1107  cur = cur->parent;
1108  }
1109  }
1110  else
1111  {
1112  while (cur->parent)
1113  cur = cur->parent;
1114  }
1115 
1116  if (forwards ^ ((C_Sort & SORT_REVERSE) != 0))
1117  {
1118  do
1119  {
1120  cur = cur->next;
1121  if (!cur)
1122  return -1;
1123  e_tmp = find_virtual(cur, 0);
1124  } while (!e_tmp);
1125  }
1126  else
1127  {
1128  do
1129  {
1130  cur = cur->prev;
1131  if (!cur)
1132  return -1;
1133  e_tmp = find_virtual(cur, 1);
1134  } while (!e_tmp);
1135  }
1136 
1137  return e_tmp->vnum;
1138 }
1139 
1148 int mutt_parent_message(struct Context *ctx, struct Email *e, bool find_root)
1149 {
1150  if (!ctx || !e)
1151  return -1;
1152 
1153  struct MuttThread *thread = NULL;
1154  struct Email *e_parent = NULL;
1155 
1156  if ((C_Sort & SORT_MASK) != SORT_THREADS)
1157  {
1158  mutt_error(_("Threading is not enabled"));
1159  return e->vnum;
1160  }
1161 
1162  /* Root may be the current message */
1163  if (find_root)
1164  e_parent = e;
1165 
1166  for (thread = e->thread->parent; thread; thread = thread->parent)
1167  {
1168  e = thread->message;
1169  if (e)
1170  {
1171  e_parent = e;
1172  if (!find_root)
1173  break;
1174  }
1175  }
1176 
1177  if (!e_parent)
1178  {
1179  mutt_error(_("Parent message is not available"));
1180  return -1;
1181  }
1182  if (!is_visible(e_parent, ctx))
1183  {
1184  if (find_root)
1185  mutt_error(_("Root message is not visible in this limited view"));
1186  else
1187  mutt_error(_("Parent message is not visible in this limited view"));
1188  return -1;
1189  }
1190  return e_parent->vnum;
1191 }
1192 
1197 void mutt_set_vnum(struct Context *ctx)
1198 {
1199  if (!ctx || !ctx->mailbox)
1200  return;
1201 
1202  struct Mailbox *m = ctx->mailbox;
1203 
1204  struct Email *e = NULL;
1205 
1206  m->vcount = 0;
1207  ctx->vsize = 0;
1208  int padding = mx_msg_padding_size(m);
1209 
1210  for (int i = 0; i < m->msg_count; i++)
1211  {
1212  e = m->emails[i];
1213  if (!e)
1214  break;
1215 
1216  if (e->vnum >= 0)
1217  {
1218  e->vnum = m->vcount;
1219  m->v2r[m->vcount] = i;
1220  m->vcount++;
1221  ctx->vsize += e->content->length + e->content->offset - e->content->hdr_offset + padding;
1222  e->num_hidden = mutt_get_hidden(ctx, e);
1223  }
1224  }
1225 }
1226 
1234 int mutt_traverse_thread(struct Context *ctx, struct Email *e_cur, MuttThreadFlags flag)
1235 {
1236  struct MuttThread *thread = NULL, *top = NULL;
1237  struct Email *e_root = NULL;
1238  int final, reverse = (C_Sort & SORT_REVERSE), minmsgno;
1239  int num_hidden = 0, new_mail = 0, old_mail = 0;
1240  bool flagged = false;
1241  int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1242 #define CHECK_LIMIT (!ctx->pattern || e_cur->limited)
1243 
1244  if (((C_Sort & SORT_MASK) != SORT_THREADS) && !(flag & MUTT_THREAD_GET_HIDDEN))
1245  {
1246  mutt_error(_("Threading is not enabled"));
1247  return e_cur->vnum;
1248  }
1249 
1250  if (!e_cur->thread)
1251  {
1252  return e_cur->vnum;
1253  }
1254 
1255  final = e_cur->vnum;
1256  thread = e_cur->thread;
1257  while (thread->parent)
1258  thread = thread->parent;
1259  top = thread;
1260  while (!thread->message)
1261  thread = thread->child;
1262  e_cur = thread->message;
1263  minmsgno = e_cur->msgno;
1264 
1265  if (!e_cur->read && CHECK_LIMIT)
1266  {
1267  if (e_cur->old)
1268  old_mail = 2;
1269  else
1270  new_mail = 1;
1271  if (e_cur->msgno < min_unread_msgno)
1272  {
1273  min_unread = e_cur->vnum;
1274  min_unread_msgno = e_cur->msgno;
1275  }
1276  }
1277 
1278  if (e_cur->flagged && CHECK_LIMIT)
1279  flagged = true;
1280 
1281  if ((e_cur->vnum == -1) && CHECK_LIMIT)
1282  num_hidden++;
1283 
1285  {
1286  e_cur->pair = 0; /* force index entry's color to be re-evaluated */
1287  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1288  if (e_cur->vnum != -1)
1289  {
1290  e_root = e_cur;
1291  if (flag & MUTT_THREAD_COLLAPSE)
1292  final = e_root->vnum;
1293  }
1294  }
1295 
1296  if ((thread == top) && !(thread = thread->child))
1297  {
1298  /* return value depends on action requested */
1300  return final;
1301  if (flag & MUTT_THREAD_UNREAD)
1302  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1303  if (flag & MUTT_THREAD_GET_HIDDEN)
1304  return num_hidden;
1305  if (flag & MUTT_THREAD_NEXT_UNREAD)
1306  return min_unread;
1307  if (flag & MUTT_THREAD_FLAGGED)
1308  return flagged;
1309  }
1310 
1311  while (true)
1312  {
1313  e_cur = thread->message;
1314 
1315  if (e_cur)
1316  {
1318  {
1319  e_cur->pair = 0; /* force index entry's color to be re-evaluated */
1320  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1321  if (!e_root && CHECK_LIMIT)
1322  {
1323  e_root = e_cur;
1324  if (flag & MUTT_THREAD_COLLAPSE)
1325  final = e_root->vnum;
1326  }
1327 
1328  if (reverse && (flag & MUTT_THREAD_COLLAPSE) && (e_cur->msgno < minmsgno) && CHECK_LIMIT)
1329  {
1330  minmsgno = e_cur->msgno;
1331  final = e_cur->vnum;
1332  }
1333 
1334  if (flag & MUTT_THREAD_COLLAPSE)
1335  {
1336  if (e_cur != e_root)
1337  e_cur->vnum = -1;
1338  }
1339  else
1340  {
1341  if (CHECK_LIMIT)
1342  e_cur->vnum = e_cur->msgno;
1343  }
1344  }
1345 
1346  if (!e_cur->read && CHECK_LIMIT)
1347  {
1348  if (e_cur->old)
1349  old_mail = 2;
1350  else
1351  new_mail = 1;
1352  if (e_cur->msgno < min_unread_msgno)
1353  {
1354  min_unread = e_cur->vnum;
1355  min_unread_msgno = e_cur->msgno;
1356  }
1357  }
1358 
1359  if (e_cur->flagged && CHECK_LIMIT)
1360  flagged = true;
1361 
1362  if ((e_cur->vnum == -1) && CHECK_LIMIT)
1363  num_hidden++;
1364  }
1365 
1366  if (thread->child)
1367  thread = thread->child;
1368  else if (thread->next)
1369  thread = thread->next;
1370  else
1371  {
1372  bool done = false;
1373  while (!thread->next)
1374  {
1375  thread = thread->parent;
1376  if (thread == top)
1377  {
1378  done = true;
1379  break;
1380  }
1381  }
1382  if (done)
1383  break;
1384  thread = thread->next;
1385  }
1386  }
1387 
1388  /* return value depends on action requested */
1390  return final;
1391  if (flag & MUTT_THREAD_UNREAD)
1392  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1393  if (flag & MUTT_THREAD_GET_HIDDEN)
1394  return num_hidden + 1;
1395  if (flag & MUTT_THREAD_NEXT_UNREAD)
1396  return min_unread;
1397  if (flag & MUTT_THREAD_FLAGGED)
1398  return flagged;
1399 
1400  return 0;
1401 #undef CHECK_LIMIT
1402 }
1403 
1414 int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, int flag)
1415 {
1416  if (!m || !e)
1417  return 1;
1418 
1419  struct MuttThread *threads[2];
1420  int rc;
1421 
1422  if (((C_Sort & SORT_MASK) != SORT_THREADS) || !e->thread)
1423  return 1;
1424 
1425  threads[0] = e->thread;
1426  while (threads[0]->parent)
1427  threads[0] = threads[0]->parent;
1428 
1429  threads[1] = flag ? e->thread : threads[0]->next;
1430 
1431  for (int i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
1432  {
1433  while (!threads[i]->message)
1434  threads[i] = threads[i]->child;
1435  }
1436 
1437  if (C_Sort & SORT_REVERSE)
1438  rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1439  else
1440  {
1441  rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1442  threads[0]->message->msgno;
1443  }
1444 
1445  if (flag)
1446  rc += 1;
1447 
1448  return rc;
1449 }
1450 
1456 struct Hash *mutt_make_id_hash(struct Mailbox *m)
1457 {
1458  struct Hash *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1459 
1460  for (int i = 0; i < m->msg_count; i++)
1461  {
1462  struct Email *e = m->emails[i];
1463  if (!e || !e->env)
1464  continue;
1465 
1466  if (e->env->message_id)
1467  mutt_hash_insert(hash, e->env->message_id, e);
1468  }
1469 
1470  return hash;
1471 }
1472 
1480 static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1481 {
1482  if (child == parent)
1483  return false;
1484 
1485  mutt_break_thread(child);
1487  mutt_set_flag(m, child, MUTT_TAG, false);
1488 
1489  child->changed = true;
1490  child->env->changed |= MUTT_ENV_CHANGED_IRT;
1491  return true;
1492 }
1493 
1501 bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
1502 {
1503  if (!parent || !children || !m)
1504  return false;
1505 
1506  bool changed = false;
1507 
1508  struct EmailNode *en = NULL;
1509  STAILQ_FOREACH(en, children, entries)
1510  {
1511  changed |= link_threads(parent, en->email, m);
1512  }
1513 
1514  return changed;
1515 }
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:47
The "current" mailbox.
Definition: context.h:37
struct HashElem * mutt_hash_find_bucket(const struct Hash *table, const char *strkey)
Find the HashElem in a Hash table element using a key.
Definition: hash.c:425
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
int msg_count
Total number of messages.
Definition: mailbox.h:91
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:70
Lower left corner.
Definition: mutt_menu.h:61
void thread_hash_destructor(int type, void *obj, intptr_t data)
Hash Destructor callback - Implements hashelem_free_t.
Definition: thread.c:111
The envelope/body of an email.
Definition: email.h:37
if(!test_colorize_)
Definition: acutest.h:499
void mutt_set_vnum(struct Context *ctx)
Set the virtual index number of all the messages in a mailbox.
Definition: mutt_thread.c:1197
bool C_HideMissing
Config: Don&#39;t indicate missing messages, in the thread tree.
Definition: mutt_thread.c:50
static bool is_visible(struct Email *e, struct Context *ctx)
Is the message visible?
Definition: mutt_thread.c:65
struct Email * find_virtual(struct MuttThread *cur, int reverse)
Find an email with a Virtual message number.
Definition: thread.c:122
Structs that make up an email.
struct MuttThread * tree
Top of thread tree.
Definition: context.h:43
The "currently-open" mailbox.
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:50
A Hash Table.
Definition: hash.h:61
struct MuttThread * thread
Thread of Emails.
Definition: email.h:94
static void linearize_tree(struct Context *ctx)
Flatten an email thread.
Definition: mutt_thread.c:124
bool deep
Is the Thread deeply nested?
Definition: thread.h:41
struct Body * content
List of MIME parts.
Definition: email.h:90
bool C_HideThreadSubject
Config: Hide subjects that are similar to that of the parent message.
Definition: mutt_thread.c:51
struct MuttThread * mutt_sort_subthreads(struct MuttThread *thread, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:655
void * mutt_hash_find(const struct Hash *table, const char *strkey)
Find the HashElem data in a Hash table element using a key.
Definition: hash.c:378
bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
Forcibly link threads together.
Definition: mutt_thread.c:1501
bool display_subject
Used for threading.
Definition: email.h:57
static void calculate_visibility(struct Context *ctx, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:172
bool threaded
Used for threading.
Definition: email.h:56
#define mutt_get_hidden(ctx, e)
Definition: mutt_thread.h:59
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
#define _(a)
Definition: message.h:28
char * real_subj
Offset of the real subject.
Definition: envelope.h:67
bool changed
Email has been edited.
Definition: email.h:48
struct MuttThread * parent
Parent of this Thread.
Definition: thread.h:45
bool next_subtree_visible
Is the next Thread subtree visible?
Definition: thread.h:43
bool C_DuplicateThreads
Config: Highlight messages with duplicated message IDs.
Definition: mutt_thread.c:48
bool C_SortRe
Config: Sort method for the sidebar.
Definition: mutt_thread.c:55
int vcount
The number of virtual messages.
Definition: mailbox.h:102
Convenience wrapper for the config headers.
struct HashElem * next
Definition: hash.h:47
Right arrow.
Definition: mutt_menu.h:67
bool C_HideLimited
Config: Don&#39;t indicate hidden messages, in the thread tree.
Definition: mutt_thread.c:49
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:88
Assorted sorting methods.
Bottom T-piece.
Definition: mutt_menu.h:72
Blank space.
Definition: mutt_menu.h:66
bool read
Email is read.
Definition: email.h:51
Vertical line.
Definition: mutt_menu.h:65
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:82
char * message_id
Message ID.
Definition: envelope.h:69
void mutt_sort_threads(struct Context *ctx, bool init)
Sort email threads.
Definition: mutt_thread.c:834
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
struct Mailbox * mailbox
Definition: context.h:51
Many unsorted constants and some structs.
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:50
Top T-piece.
Definition: mutt_menu.h:71
static void pseudo_threads(struct Context *ctx)
Thread messages by subject.
Definition: mutt_thread.c:530
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:462
int mutt_parent_message(struct Context *ctx, struct Email *e, bool find_root)
Find the parent of a message.
Definition: mutt_thread.c:1148
struct Envelope * env
Envelope information.
Definition: email.h:89
Convenience wrapper for the core headers.
int mutt_traverse_thread(struct Context *ctx, struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition: mutt_thread.c:1234
Star character (for threads)
Definition: mutt_menu.h:68
Equals (for threads)
Definition: mutt_menu.h:70
bool C_HideTopLimited
Config: Don&#39;t indicate hidden top message, in the thread tree.
Definition: mutt_thread.c:52
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition: thread.c:225
bool limited
Is this message in a limited view?
Definition: email.h:74
WHERE short C_Sort
Config: Sort method for the index.
Definition: sort.h:58
bool C_StrictThreads
Config: Thread messages using &#39;In-Reply-To&#39; and &#39;References&#39; headers.
Definition: mutt_thread.c:56
static int compare_threads(const void *a, const void *b)
Sorting function for email threads.
Definition: mutt_thread.c:631
Ampersand character (for threads)
Definition: mutt_menu.h:69
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:49
int(* sort_t)(const void *a, const void *b)
Prototype for a function to compare two emails.
Definition: sort.h:48
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:81
off_t vsize
Definition: context.h:39
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:54
struct MuttThread * child
Child of this Thread.
Definition: thread.h:46
struct Hash * thread_hash
Hash table for threading.
Definition: context.h:44
bool duplicate_thread
Duplicated Email in Thread.
Definition: thread.h:37
Prototypes for many functions.
WHERE short C_SortAux
Config: Secondary sort method for the index.
Definition: sort.h:59
Sort by email threads.
Definition: sort2.h:56
uint8_t MuttThreadFlags
Flags, e.g. MUTT_THREAD_COLLAPSE.
Definition: mutt_thread.h:47
Question mark.
Definition: mutt_menu.h:73
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition: hash.h:77
Create/manipulate threading in emails.
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
struct Email * sort_key
Email that this Thread is sorted against.
Definition: thread.h:50
bool fake_thread
Emails grouped by Subject.
Definition: thread.h:36
A mailbox.
Definition: mailbox.h:81
bool C_HideTopMissing
Config: Don&#39;t indicate missing top message, in the thread tree.
Definition: mutt_thread.c:53
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1480
size_t num_hidden
Number of hidden messages in this view.
Definition: email.h:75
unsigned int subtree_visible
Is this Thread subtree visible?
Definition: thread.h:42
struct ListNode * mutt_list_insert_after(struct ListHead *h, struct ListNode *n, char *s)
Insert a string after a given ListNode.
Definition: list.c:85
Tagged messages.
Definition: mutt.h:101
size_t mutt_str_strfcpy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:776
struct Hash * mutt_hash_new(size_t nelem, HashFlags flags)
Create a new Hash table (with string keys)
Definition: hash.c:275
GUI present the user with a selectable list.
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:73
TreeChar
Tree characters for menus.
Definition: mutt_menu.h:59
int vnum
Virtual message number.
Definition: email.h:87
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:53
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:46
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, int flag)
Count the messages in a thread.
Definition: mutt_thread.c:1414
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
char * tree
Character string to print thread tree.
Definition: email.h:93
#define STAILQ_NEXT(elm, field)
Definition: queue.h:397
bool C_NarrowTree
Config: Draw a narrower thread tree in the index.
Definition: mutt_thread.c:54
struct Email * message
Email this Thread refers to.
Definition: thread.h:49
struct Hash * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1456
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition: thread.c:64
void mutt_clear_threads(struct Context *ctx)
Clear the threading of message in a mailbox.
Definition: mutt_thread.c:601
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition: mutt_thread.c:1084
An Email conversation.
Definition: thread.h:34
void * data
Definition: hash.h:46
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:101
bool sort_children
Sort the children.
Definition: thread.h:38
char * data
String.
Definition: list.h:35
char * subject
Email&#39;s subject.
Definition: envelope.h:66
bool flagged
Marked important?
Definition: email.h:43
static bool need_display_subject(struct Context *ctx, struct Email *e)
Determines whether to display a message&#39;s subject.
Definition: mutt_thread.c:76
char * mutt_str_strdup(const char *str)
Copy a string, safely.
Definition: string.c:380
struct Hash * subj_hash
Hash table by subject.
Definition: mailbox.h:128
struct Email * email
Email in the list.
Definition: email.h:116
#define mutt_error(...)
Definition: logging.h:84
#define FREE(x)
Definition: memory.h:40
bool subject_changed
Used for threading.
Definition: email.h:55
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
Insert a message into a thread.
Definition: thread.c:94
#define CHECK_LIMIT
#define STAILQ_EMPTY(head)
Definition: queue.h:345
#define MUTT_THREAD_GET_HIDDEN
Count non-visible emails in a thread.
Definition: mutt_thread.h:51
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:321
The item stored in a Hash Table.
Definition: hash.h:42
List of Emails.
Definition: email.h:114
bool visible
Is this Thread visible?
Definition: thread.h:40
bool check_subject
Should the Subject be checked?
Definition: thread.h:39
void mutt_hash_set_destructor(struct Hash *table, hashelem_free_t fn, intptr_t fn_data)
Set the destructor for a Hash Table.
Definition: hash.c:317
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1528
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:42
static void check_subjects(struct Mailbox *m, bool init)
Find out which emails&#39; subjects differ from their parent&#39;s.
Definition: mutt_thread.c:792
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:408
bool is_descendant(struct MuttThread *a, struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:44
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition: envelope.h:32
sort_t mutt_get_sort_func(enum SortType method)
Get the sort function for a given sort id.
Definition: sort.c:324
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition: list.c:168
Convenience wrapper for the library headers.
void mutt_hash_free(struct Hash **ptr)
Free a hash table.
Definition: hash.c:471
struct HashElem * mutt_hash_insert(struct Hash *table, const char *strkey, void *data)
Add a new element to the Hash table (with string keys)
Definition: hash.c:351
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:86
struct ListHead references
message references (in reverse order)
Definition: envelope.h:81
A List node for strings.
Definition: list.h:33
char * pattern
Limit pattern string.
Definition: context.h:40
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:87
#define STAILQ_FIRST(head)
Definition: queue.h:347
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:74
Left T-piece.
Definition: mutt_menu.h:63
int mutt_str_strcmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:641
int pair
Color-pair to use when displaying in the index.
Definition: email.h:79
bool C_ThreadReceived
Config: Sort threaded messages by their received date.
Definition: mutt_thread.c:57
static struct Hash * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:505
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:85
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:82
The header of an Email.
Definition: envelope.h:54
int msgno
Number displayed to the user.
Definition: email.h:86
void mutt_draw_tree(struct Context *ctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:285
Upper left corner.
Definition: mutt_menu.h:62
Horizontal line.
Definition: mutt_menu.h:64
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:52