NeoMutt  2020-11-20
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_thread.h"
40 #include "mx.h"
41 #include "protos.h"
42 #include "sort.h"
43 
44 /* These Config Variables are only used in mutt_thread.c */
54 bool C_SortRe;
57 
62 {
63  struct Mailbox *mailbox;
64  struct MuttThread *tree;
65  struct HashTable *hash;
66 };
67 
73 static bool is_visible(struct Email *e)
74 {
75  return e->vnum >= 0 || (e->collapsed && e->visible);
76 }
77 
83 static bool need_display_subject(struct Email *e)
84 {
85  struct MuttThread *tmp = NULL;
86  struct MuttThread *tree = e->thread;
87 
88  /* if the user disabled subject hiding, display it */
90  return true;
91 
92  /* if our subject is different from our parent's, display it */
93  if (e->subject_changed)
94  return true;
95 
96  /* if our subject is different from that of our closest previously displayed
97  * sibling, display the subject */
98  for (tmp = tree->prev; tmp; tmp = tmp->prev)
99  {
100  e = tmp->message;
101  if (e && is_visible(e))
102  {
103  if (e->subject_changed)
104  return true;
105  break;
106  }
107  }
108 
109  /* if there is a parent-to-child subject change anywhere between us and our
110  * closest displayed ancestor, display the subject */
111  for (tmp = tree->parent; tmp; tmp = tmp->parent)
112  {
113  e = tmp->message;
114  if (e)
115  {
116  if (is_visible(e))
117  return false;
118  if (e->subject_changed)
119  return true;
120  }
121  }
122 
123  /* if we have no visible parent or previous sibling, display the subject */
124  return true;
125 }
126 
131 static void linearize_tree(struct ThreadsContext *tctx)
132 {
133  if (!tctx || !tctx->mailbox)
134  return;
135 
136  struct Mailbox *m = tctx->mailbox;
137 
138  struct MuttThread *tree = tctx->tree;
139  struct Email **array = m->emails + ((C_Sort & SORT_REVERSE) ? m->msg_count - 1 : 0);
140 
141  while (tree)
142  {
143  while (!tree->message)
144  tree = tree->child;
145 
146  *array = tree->message;
147  array += (C_Sort & SORT_REVERSE) ? -1 : 1;
148 
149  if (tree->child)
150  tree = tree->child;
151  else
152  {
153  while (tree)
154  {
155  if (tree->next)
156  {
157  tree = tree->next;
158  break;
159  }
160  else
161  tree = tree->parent;
162  }
163  }
164  }
165 }
166 
179 static void calculate_visibility(struct MuttThread *tree, int *max_depth)
180 {
181  if (!tree)
182  return;
183 
184  struct MuttThread *tmp = NULL;
185  struct MuttThread *orig_tree = tree;
186  int hide_top_missing = C_HideTopMissing && !C_HideMissing;
187  int hide_top_limited = C_HideTopLimited && !C_HideLimited;
188  int depth = 0;
189 
190  /* we walk each level backwards to make it easier to compute next_subtree_visible */
191  while (tree->next)
192  tree = tree->next;
193  *max_depth = 0;
194 
195  while (true)
196  {
197  if (depth > *max_depth)
198  *max_depth = depth;
199 
200  tree->subtree_visible = 0;
201  if (tree->message)
202  {
203  FREE(&tree->message->tree);
204  if (is_visible(tree->message))
205  {
206  tree->deep = true;
207  tree->visible = true;
209  for (tmp = tree; tmp; tmp = tmp->parent)
210  {
211  if (tmp->subtree_visible)
212  {
213  tmp->deep = true;
214  tmp->subtree_visible = 2;
215  break;
216  }
217  else
218  tmp->subtree_visible = 1;
219  }
220  }
221  else
222  {
223  tree->visible = false;
224  tree->deep = !C_HideLimited;
225  }
226  }
227  else
228  {
229  tree->visible = false;
230  tree->deep = !C_HideMissing;
231  }
232  tree->next_subtree_visible =
233  tree->next && (tree->next->next_subtree_visible || tree->next->subtree_visible);
234  if (tree->child)
235  {
236  depth++;
237  tree = tree->child;
238  while (tree->next)
239  tree = tree->next;
240  }
241  else if (tree->prev)
242  tree = tree->prev;
243  else
244  {
245  while (tree && !tree->prev)
246  {
247  depth--;
248  tree = tree->parent;
249  }
250  if (!tree)
251  break;
252  tree = tree->prev;
253  }
254  }
255 
256  /* now fix up for the OPTHIDETOP* options if necessary */
257  if (hide_top_limited || hide_top_missing)
258  {
259  tree = orig_tree;
260  while (true)
261  {
262  if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
263  ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
264  {
265  tree->deep = false;
266  }
267  if (!tree->deep && tree->child && tree->subtree_visible)
268  tree = tree->child;
269  else if (tree->next)
270  tree = tree->next;
271  else
272  {
273  while (tree && !tree->next)
274  tree = tree->parent;
275  if (!tree)
276  break;
277  tree = tree->next;
278  }
279  }
280  }
281 }
282 
289 {
290  struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
291  tctx->mailbox = m;
292  tctx->tree = NULL;
293  tctx->hash = NULL;
294  return tctx;
295 }
296 
302 {
303  (*tctx)->mailbox = NULL;
304  mutt_hash_free(&(*tctx)->hash);
305  FREE(tctx);
306 }
307 
320 void mutt_draw_tree(struct ThreadsContext *tctx)
321 {
322  char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
325  int depth = 0, start_depth = 0, max_depth = 0, width = C_NarrowTree ? 1 : 2;
326  struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
327 
328  struct MuttThread *tree = tctx->tree;
329 
330  /* Do the visibility calculations and free the old thread chars.
331  * From now on we can simply ignore invisible subtrees */
332  calculate_visibility(tree, &max_depth);
333  pfx = mutt_mem_malloc((width * max_depth) + 2);
334  arrow = mutt_mem_malloc((width * max_depth) + 2);
335  while (tree)
336  {
337  if (depth != 0)
338  {
339  myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
340  if (start_depth == depth)
341  myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
342  else if (parent->message && !C_HideLimited)
343  myarrow[0] = MUTT_TREE_HIDDEN;
344  else if (!parent->message && !C_HideMissing)
345  myarrow[0] = MUTT_TREE_MISSING;
346  else
347  myarrow[0] = vtee;
348  if (width == 2)
349  {
350  myarrow[1] = pseudo ? MUTT_TREE_STAR :
352  }
353  if (tree->visible)
354  {
355  myarrow[width] = MUTT_TREE_RARROW;
356  myarrow[width + 1] = 0;
357  new_tree = mutt_mem_malloc(((size_t) depth * width) + 2);
358  if (start_depth > 1)
359  {
360  strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
361  mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
362  (1 + depth - start_depth) * width + 2);
363  }
364  else
365  mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
366  tree->message->tree = new_tree;
367  }
368  }
369  if (tree->child && (depth != 0))
370  {
371  mypfx = pfx + (depth - 1) * width;
372  mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
373  if (width == 2)
374  mypfx[1] = MUTT_TREE_SPACE;
375  }
376  parent = tree;
377  nextdisp = NULL;
378  pseudo = NULL;
379  do
380  {
381  if (tree->child && tree->subtree_visible)
382  {
383  if (tree->deep)
384  depth++;
385  if (tree->visible)
386  start_depth = depth;
387  tree = tree->child;
388 
389  /* we do this here because we need to make sure that the first child thread
390  * of the old tree that we deal with is actually displayed if any are,
391  * or we might set the parent variable wrong while going through it. */
392  while (!tree->subtree_visible && tree->next)
393  tree = tree->next;
394  }
395  else
396  {
397  while (!tree->next && tree->parent)
398  {
399  if (tree == pseudo)
400  pseudo = NULL;
401  if (tree == nextdisp)
402  nextdisp = NULL;
403  if (tree->visible)
404  start_depth = depth;
405  tree = tree->parent;
406  if (tree->deep)
407  {
408  if (start_depth == depth)
409  start_depth--;
410  depth--;
411  }
412  }
413  if (tree == pseudo)
414  pseudo = NULL;
415  if (tree == nextdisp)
416  nextdisp = NULL;
417  if (tree->visible)
418  start_depth = depth;
419  tree = tree->next;
420  if (!tree)
421  break;
422  }
423  if (!pseudo && tree->fake_thread)
424  pseudo = tree;
425  if (!nextdisp && tree->next_subtree_visible)
426  nextdisp = tree;
427  } while (!tree->deep);
428  }
429 
430  FREE(&pfx);
431  FREE(&arrow);
432 }
433 
444 static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
445 {
446  struct MuttThread *start = cur;
447  struct Envelope *env = NULL;
448  time_t thisdate;
449  int rc = 0;
450 
451  while (true)
452  {
453  while (!cur->message)
454  cur = cur->child;
455 
456  if (dateptr)
457  {
458  thisdate = C_ThreadReceived ? cur->message->received : cur->message->date_sent;
459  if ((*dateptr == 0) || (thisdate < *dateptr))
460  *dateptr = thisdate;
461  }
462 
463  env = cur->message->env;
464  if (env->real_subj && ((env->real_subj != env->subject) || !C_SortRe))
465  {
466  struct ListNode *np = NULL;
467  STAILQ_FOREACH(np, subjects, entries)
468  {
469  rc = mutt_str_cmp(env->real_subj, np->data);
470  if (rc >= 0)
471  break;
472  }
473  if (!np)
474  mutt_list_insert_head(subjects, env->real_subj);
475  else if (rc > 0)
476  mutt_list_insert_after(subjects, np, env->real_subj);
477  }
478 
479  while (!cur->next && (cur != start))
480  {
481  cur = cur->parent;
482  }
483  if (cur == start)
484  break;
485  cur = cur->next;
486  }
487 }
488 
498 static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
499 {
500  if (!m)
501  return NULL;
502 
503  struct HashElem *ptr = NULL;
504  struct MuttThread *tmp = NULL, *last = NULL;
505  struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
506  time_t date = 0;
507 
508  make_subject_list(&subjects, cur, &date);
509 
510  struct ListNode *np = NULL;
511  STAILQ_FOREACH(np, &subjects, entries)
512  {
513  for (ptr = mutt_hash_find_bucket(m->subj_hash, np->data); ptr; ptr = ptr->next)
514  {
515  tmp = ((struct Email *) ptr->data)->thread;
516  if ((tmp != cur) && /* don't match the same message */
517  !tmp->fake_thread && /* don't match pseudo threads */
518  tmp->message->subject_changed && /* only match interesting replies */
519  !is_descendant(tmp, cur) && /* don't match in the same thread */
520  (date >= (C_ThreadReceived ? tmp->message->received : tmp->message->date_sent)) &&
521  (!last || (C_ThreadReceived ?
522  (last->message->received < tmp->message->received) :
523  (last->message->date_sent < tmp->message->date_sent))) &&
524  tmp->message->env->real_subj &&
525  mutt_str_equal(np->data, tmp->message->env->real_subj))
526  {
527  last = tmp; /* best match so far */
528  }
529  }
530  }
531 
532  mutt_list_clear(&subjects);
533  return last;
534 }
535 
541 static struct HashTable *make_subj_hash(struct Mailbox *m)
542 {
543  if (!m)
544  return NULL;
545 
547 
548  for (int i = 0; i < m->msg_count; i++)
549  {
550  struct Email *e = m->emails[i];
551  if (!e || !e->env)
552  continue;
553  if (e->env->real_subj)
554  mutt_hash_insert(hash, e->env->real_subj, e);
555  }
556 
557  return hash;
558 }
559 
566 static void pseudo_threads(struct ThreadsContext *tctx)
567 {
568  if (!tctx || !tctx->mailbox)
569  return;
570 
571  struct Mailbox *m = tctx->mailbox;
572 
573  struct MuttThread *tree = tctx->tree;
574  struct MuttThread *top = tree;
575  struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
576  *nextchild = NULL;
577 
578  if (!m->subj_hash)
579  m->subj_hash = make_subj_hash(m);
580 
581  while (tree)
582  {
583  cur = tree;
584  tree = tree->next;
585  parent = find_subject(m, cur);
586  if (parent)
587  {
588  cur->fake_thread = true;
589  unlink_message(&top, cur);
590  insert_message(&parent->child, parent, cur);
591  parent->sort_children = true;
592  tmp = cur;
593  while (true)
594  {
595  while (!tmp->message)
596  tmp = tmp->child;
597 
598  /* if the message we're attaching has pseudo-children, they
599  * need to be attached to its parent, so move them up a level.
600  * but only do this if they have the same real subject as the
601  * parent, since otherwise they rightly belong to the message
602  * we're attaching. */
603  if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
604  parent->message->env->real_subj))
605  {
606  tmp->message->subject_changed = false;
607 
608  for (curchild = tmp->child; curchild;)
609  {
610  nextchild = curchild->next;
611  if (curchild->fake_thread)
612  {
613  unlink_message(&tmp->child, curchild);
614  insert_message(&parent->child, parent, curchild);
615  }
616  curchild = nextchild;
617  }
618  }
619 
620  while (!tmp->next && (tmp != cur))
621  {
622  tmp = tmp->parent;
623  }
624  if (tmp == cur)
625  break;
626  tmp = tmp->next;
627  }
628  }
629  }
630  tctx->tree = top;
631 }
632 
638 {
639  if (!tctx || !tctx->mailbox || !tctx->mailbox->emails || !tctx->tree)
640  return;
641 
642  for (int i = 0; i < tctx->mailbox->msg_count; i++)
643  {
644  struct Email *e = tctx->mailbox->emails[i];
645  if (!e)
646  break;
647 
648  /* mailbox may have been only partially read */
649  e->thread = NULL;
650  e->threaded = false;
651  }
652  tctx->tree = NULL;
653  mutt_hash_free(&tctx->hash);
654 }
655 
664 static int compare_threads(const void *a, const void *b)
665 {
666  static sort_t sort_func = NULL;
667 
668  if (a && b)
669  {
670  return (*sort_func)(&(*((struct MuttThread const *const *) a))->sort_key,
671  &(*((struct MuttThread const *const *) b))->sort_key);
672  }
673  /* a hack to let us reset sort_func even though we can't
674  * have extra arguments because of qsort */
675  else
676  {
677  sort_func = mutt_get_sort_func(C_Sort & SORT_MASK);
678  return sort_func ? 1 : 0;
679  }
680 }
681 
687 void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
688 {
689  struct MuttThread *thread = tctx->tree;
690  if (!thread)
691  return;
692 
693  struct MuttThread **array = NULL, *sort_key = NULL, *top = NULL, *tmp = NULL;
694  struct Email *oldsort_key = NULL;
695  int i, array_size, sort_top = 0;
696 
697  /* we put things into the array backwards to save some cycles,
698  * but we want to have to move less stuff around if we're
699  * resorting, so we sort backwards and then put them back
700  * in reverse order so they're forwards */
701  C_Sort ^= SORT_REVERSE;
702  if (compare_threads(NULL, NULL) == 0)
703  return;
704 
705  top = thread;
706 
707  array_size = 256;
708  array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
709  while (true)
710  {
711  if (init || !thread->sort_key)
712  {
713  thread->sort_key = NULL;
714 
715  if (thread->parent)
716  thread->parent->sort_children = true;
717  else
718  sort_top = 1;
719  }
720 
721  if (thread->child)
722  {
723  thread = thread->child;
724  continue;
725  }
726  else
727  {
728  /* if it has no children, it must be real. sort it on its own merits */
729  thread->sort_key = thread->message;
730 
731  if (thread->next)
732  {
733  thread = thread->next;
734  continue;
735  }
736  }
737 
738  while (!thread->next)
739  {
740  /* if it has siblings and needs to be sorted, sort it... */
741  if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
742  {
743  /* put them into the array */
744  for (i = 0; thread; i++, thread = thread->prev)
745  {
746  if (i >= array_size)
747  mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
748 
749  array[i] = thread;
750  }
751 
752  qsort((void *) array, i, sizeof(struct MuttThread *), *compare_threads);
753 
754  /* attach them back together. make thread the last sibling. */
755  thread = array[0];
756  thread->next = NULL;
757  array[i - 1]->prev = NULL;
758 
759  if (thread->parent)
760  thread->parent->child = array[i - 1];
761  else
762  top = array[i - 1];
763 
764  while (--i)
765  {
766  array[i - 1]->prev = array[i];
767  array[i]->next = array[i - 1];
768  }
769  }
770 
771  if (thread->parent)
772  {
773  tmp = thread;
774  thread = thread->parent;
775 
776  if (!thread->sort_key || thread->sort_children)
777  {
778  /* make sort_key the first or last sibling, as appropriate */
779  sort_key = ((!(C_Sort & SORT_LAST)) ^ (!(C_Sort & SORT_REVERSE))) ?
780  thread->child :
781  tmp;
782 
783  /* we just sorted its children */
784  thread->sort_children = false;
785 
786  oldsort_key = thread->sort_key;
787  thread->sort_key = thread->message;
788 
789  if (C_Sort & SORT_LAST)
790  {
791  if (!thread->sort_key ||
792  ((((C_Sort & SORT_REVERSE) ? 1 : -1) *
793  compare_threads((void *) &thread, (void *) &sort_key)) > 0))
794  {
795  thread->sort_key = sort_key->sort_key;
796  }
797  }
798  else if (!thread->sort_key)
799  thread->sort_key = sort_key->sort_key;
800 
801  /* if its sort_key has changed, we need to resort it and siblings */
802  if (oldsort_key != thread->sort_key)
803  {
804  if (thread->parent)
805  thread->parent->sort_children = true;
806  else
807  sort_top = 1;
808  }
809  }
810  }
811  else
812  {
813  C_Sort ^= SORT_REVERSE;
814  FREE(&array);
815  tctx->tree = top;
816  return;
817  }
818  }
819 
820  thread = thread->next;
821  }
822 }
823 
829 static void check_subjects(struct Mailbox *m, bool init)
830 {
831  if (!m)
832  return;
833 
834  for (int i = 0; i < m->msg_count; i++)
835  {
836  struct Email *e = m->emails[i];
837  if (!e || !e->thread)
838  continue;
839 
840  if (e->thread->check_subject)
841  e->thread->check_subject = false;
842  else if (!init)
843  continue;
844 
845  /* figure out which messages have subjects different than their parents' */
846  struct MuttThread *tmp = e->thread->parent;
847  while (tmp && !tmp->message)
848  {
849  tmp = tmp->parent;
850  }
851 
852  if (!tmp)
853  e->subject_changed = true;
854  else if (e->env->real_subj && tmp->message->env->real_subj)
855  {
857  }
858  else
859  {
860  e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
861  }
862  }
863 }
864 
870 void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
871 {
872  if (!tctx || !tctx->mailbox)
873  return;
874 
875  struct Mailbox *m = tctx->mailbox;
876 
877  struct Email *e = NULL;
878  int i, oldsort, using_refs = 0;
879  struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
880  struct MuttThread top = { 0 };
881  struct ListNode *ref = NULL;
882 
883  /* Set `$sort` to the secondary method to support the set sort_aux=reverse-*
884  * settings. The sorting functions just look at the value of SORT_REVERSE */
885  oldsort = C_Sort;
886  C_Sort = C_SortAux;
887 
888  if (!tctx->hash)
889  init = true;
890 
891  if (init)
892  {
895  }
896 
897  /* we want a quick way to see if things are actually attached to the top of the
898  * thread tree or if they're just dangling, so we attach everything to a top
899  * node temporarily */
900  top.parent = NULL;
901  top.next = NULL;
902  top.prev = NULL;
903 
904  top.child = tctx->tree;
905  for (thread = tctx->tree; thread; thread = thread->next)
906  thread->parent = &top;
907 
908  /* put each new message together with the matching messageless MuttThread if it
909  * exists. otherwise, if there is a MuttThread that already has a message, thread
910  * new message as an identical child. if we didn't attach the message to a
911  * MuttThread, make a new one for it. */
912  for (i = 0; i < m->msg_count; i++)
913  {
914  e = m->emails[i];
915  if (!e)
916  continue;
917 
918  if (!e->thread)
919  {
920  if ((!init || C_DuplicateThreads) && e->env->message_id)
921  thread = mutt_hash_find(tctx->hash, e->env->message_id);
922  else
923  thread = NULL;
924 
925  if (thread && !thread->message)
926  {
927  /* this is a message which was missing before */
928  thread->message = e;
929  e->thread = thread;
930  thread->check_subject = true;
931 
932  /* mark descendants as needing subject_changed checked */
933  for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
934  {
935  while (!tmp->message)
936  tmp = tmp->child;
937  tmp->check_subject = true;
938  while (!tmp->next && (tmp != thread))
939  tmp = tmp->parent;
940  if (tmp != thread)
941  tmp = tmp->next;
942  }
943 
944  if (thread->parent)
945  {
946  /* remove threading info above it based on its children, which we'll
947  * recalculate based on its headers. make sure not to leave
948  * dangling missing messages. note that we haven't kept track
949  * of what info came from its children and what from its siblings'
950  * children, so we just remove the stuff that's definitely from it */
951  do
952  {
953  tmp = thread->parent;
954  unlink_message(&tmp->child, thread);
955  thread->parent = NULL;
956  thread->sort_key = NULL;
957  thread->fake_thread = false;
958  thread = tmp;
959  } while (thread != &top && !thread->child && !thread->message);
960  }
961  }
962  else
963  {
964  tnew = (C_DuplicateThreads ? thread : NULL);
965 
966  thread = mutt_mem_calloc(1, sizeof(struct MuttThread));
967  thread->message = e;
968  thread->check_subject = true;
969  e->thread = thread;
970  mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
971 
972  if (tnew)
973  {
974  if (tnew->duplicate_thread)
975  tnew = tnew->parent;
976 
977  thread = e->thread;
978 
979  insert_message(&tnew->child, tnew, thread);
980  thread->duplicate_thread = true;
981  thread->message->threaded = true;
982  }
983  }
984  }
985  else
986  {
987  /* unlink pseudo-threads because they might be children of newly
988  * arrived messages */
989  thread = e->thread;
990  for (tnew = thread->child; tnew;)
991  {
992  tmp = tnew->next;
993  if (tnew->fake_thread)
994  {
995  unlink_message(&thread->child, tnew);
996  insert_message(&top.child, &top, tnew);
997  tnew->fake_thread = false;
998  }
999  tnew = tmp;
1000  }
1001  }
1002  }
1003 
1004  /* thread by references */
1005  for (i = 0; i < m->msg_count; i++)
1006  {
1007  e = m->emails[i];
1008  if (!e)
1009  break;
1010 
1011  if (e->threaded)
1012  continue;
1013  e->threaded = true;
1014 
1015  thread = e->thread;
1016  if (!thread)
1017  continue;
1018  using_refs = 0;
1019 
1020  while (true)
1021  {
1022  if (using_refs == 0)
1023  {
1024  /* look at the beginning of in-reply-to: */
1025  ref = STAILQ_FIRST(&e->env->in_reply_to);
1026  if (ref)
1027  using_refs = 1;
1028  else
1029  {
1030  ref = STAILQ_FIRST(&e->env->references);
1031  using_refs = 2;
1032  }
1033  }
1034  else if (using_refs == 1)
1035  {
1036  /* if there's no references header, use all the in-reply-to:
1037  * data that we have. otherwise, use the first reference
1038  * if it's different than the first in-reply-to, otherwise use
1039  * the second reference (since at least eudora puts the most
1040  * recent reference in in-reply-to and the rest in references) */
1041  if (STAILQ_EMPTY(&e->env->references))
1042  ref = STAILQ_NEXT(ref, entries);
1043  else
1044  {
1045  if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1046  ref = STAILQ_FIRST(&e->env->references);
1047  else
1048  ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1049 
1050  using_refs = 2;
1051  }
1052  }
1053  else
1054  ref = STAILQ_NEXT(ref, entries); /* go on with references */
1055 
1056  if (!ref)
1057  break;
1058 
1059  tnew = mutt_hash_find(tctx->hash, ref->data);
1060  if (tnew)
1061  {
1062  if (tnew->duplicate_thread)
1063  tnew = tnew->parent;
1064  if (is_descendant(tnew, thread)) /* no loops! */
1065  continue;
1066  }
1067  else
1068  {
1069  tnew = mutt_mem_calloc(1, sizeof(struct MuttThread));
1070  mutt_hash_insert(tctx->hash, ref->data, tnew);
1071  }
1072 
1073  if (thread->parent)
1074  unlink_message(&top.child, thread);
1075  insert_message(&tnew->child, tnew, thread);
1076  thread = tnew;
1077  if (thread->message || (thread->parent && (thread->parent != &top)))
1078  break;
1079  }
1080 
1081  if (!thread->parent)
1082  insert_message(&top.child, &top, thread);
1083  }
1084 
1085  /* detach everything from the temporary top node */
1086  for (thread = top.child; thread; thread = thread->next)
1087  {
1088  thread->parent = NULL;
1089  }
1090  tctx->tree = top.child;
1091 
1092  check_subjects(tctx->mailbox, init);
1093 
1094  if (!C_StrictThreads)
1095  pseudo_threads(tctx);
1096 
1097  if (tctx->tree)
1098  {
1099  mutt_sort_subthreads(tctx, init);
1100 
1101  /* restore the oldsort order. */
1102  C_Sort = oldsort;
1103 
1104  /* Put the list into an array. */
1105  linearize_tree(tctx);
1106 
1107  /* Draw the thread tree. */
1108  mutt_draw_tree(tctx);
1109  }
1110 }
1111 
1119 int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1120 {
1121  struct MuttThread *cur = NULL;
1122  struct Email *e_tmp = NULL;
1123 
1124  if ((C_Sort & SORT_MASK) != SORT_THREADS)
1125  {
1126  mutt_error(_("Threading is not enabled"));
1127  return e->vnum;
1128  }
1129 
1130  cur = e->thread;
1131 
1132  if (subthreads)
1133  {
1134  if (forwards ^ ((C_Sort & SORT_REVERSE) != 0))
1135  {
1136  while (!cur->next && cur->parent)
1137  cur = cur->parent;
1138  }
1139  else
1140  {
1141  while (!cur->prev && cur->parent)
1142  cur = cur->parent;
1143  }
1144  }
1145  else
1146  {
1147  while (cur->parent)
1148  cur = cur->parent;
1149  }
1150 
1151  if (forwards ^ ((C_Sort & SORT_REVERSE) != 0))
1152  {
1153  do
1154  {
1155  cur = cur->next;
1156  if (!cur)
1157  return -1;
1158  e_tmp = find_virtual(cur, 0);
1159  } while (!e_tmp);
1160  }
1161  else
1162  {
1163  do
1164  {
1165  cur = cur->prev;
1166  if (!cur)
1167  return -1;
1168  e_tmp = find_virtual(cur, 1);
1169  } while (!e_tmp);
1170  }
1171 
1172  return e_tmp->vnum;
1173 }
1174 
1182 int mutt_parent_message(struct Email *e, bool find_root)
1183 {
1184  if (!e)
1185  return -1;
1186 
1187  struct MuttThread *thread = NULL;
1188  struct Email *e_parent = NULL;
1189 
1190  if ((C_Sort & SORT_MASK) != SORT_THREADS)
1191  {
1192  mutt_error(_("Threading is not enabled"));
1193  return e->vnum;
1194  }
1195 
1196  /* Root may be the current message */
1197  if (find_root)
1198  e_parent = e;
1199 
1200  for (thread = e->thread->parent; thread; thread = thread->parent)
1201  {
1202  e = thread->message;
1203  if (e)
1204  {
1205  e_parent = e;
1206  if (!find_root)
1207  break;
1208  }
1209  }
1210 
1211  if (!e_parent)
1212  {
1213  mutt_error(_("Parent message is not available"));
1214  return -1;
1215  }
1216  if (!is_visible(e_parent))
1217  {
1218  if (find_root)
1219  mutt_error(_("Root message is not visible in this limited view"));
1220  else
1221  mutt_error(_("Parent message is not visible in this limited view"));
1222  return -1;
1223  }
1224  return e_parent->vnum;
1225 }
1226 
1232 off_t mutt_set_vnum(struct Mailbox *m)
1233 {
1234  if (!m)
1235  return 0;
1236 
1237  off_t vsize = 0;
1238  const int padding = mx_msg_padding_size(m);
1239 
1240  m->vcount = 0;
1241 
1242  for (int i = 0; i < m->msg_count; i++)
1243  {
1244  struct Email *e = m->emails[i];
1245  if (!e)
1246  break;
1247 
1248  if (e->vnum >= 0)
1249  {
1250  e->vnum = m->vcount;
1251  m->v2r[m->vcount] = i;
1252  m->vcount++;
1253  vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1254  }
1255  }
1256 
1257  return vsize;
1258 }
1259 
1267 {
1268  struct MuttThread *thread = NULL, *top = NULL;
1269  struct Email *e_root = NULL;
1270  int final, reverse = (C_Sort & SORT_REVERSE), minmsgno;
1271  int num_hidden = 0, new_mail = 0, old_mail = 0;
1272  bool flagged = false;
1273  int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1274 
1275  if ((C_Sort & SORT_MASK) != SORT_THREADS)
1276  {
1277  mutt_error(_("Threading is not enabled"));
1278  return e_cur->vnum;
1279  }
1280 
1281  if (!e_cur->thread)
1282  {
1283  return e_cur->vnum;
1284  }
1285 
1286  final = e_cur->vnum;
1287  thread = e_cur->thread;
1288  while (thread->parent)
1289  thread = thread->parent;
1290  top = thread;
1291  while (!thread->message)
1292  thread = thread->child;
1293  e_cur = thread->message;
1294  minmsgno = e_cur->msgno;
1295 
1296  if (!e_cur->read && e_cur->visible)
1297  {
1298  if (e_cur->old)
1299  old_mail = 2;
1300  else
1301  new_mail = 1;
1302  if (e_cur->msgno < min_unread_msgno)
1303  {
1304  min_unread = e_cur->vnum;
1305  min_unread_msgno = e_cur->msgno;
1306  }
1307  }
1308 
1309  if (e_cur->flagged && e_cur->visible)
1310  flagged = true;
1311 
1312  if ((e_cur->vnum == -1) && e_cur->visible)
1313  num_hidden++;
1314 
1316  {
1317  e_cur->pair = 0; /* force index entry's color to be re-evaluated */
1318  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1319  if (e_cur->vnum != -1)
1320  {
1321  e_root = e_cur;
1322  if (flag & MUTT_THREAD_COLLAPSE)
1323  final = e_root->vnum;
1324  }
1325  }
1326 
1327  if ((thread == top) && !(thread = thread->child))
1328  {
1329  /* return value depends on action requested */
1331  {
1332  e_cur->num_hidden = num_hidden;
1333  return final;
1334  }
1335  if (flag & MUTT_THREAD_UNREAD)
1336  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1337  if (flag & MUTT_THREAD_NEXT_UNREAD)
1338  return min_unread;
1339  if (flag & MUTT_THREAD_FLAGGED)
1340  return flagged;
1341  }
1342 
1343  while (true)
1344  {
1345  e_cur = thread->message;
1346 
1347  if (e_cur)
1348  {
1350  {
1351  e_cur->pair = 0; /* force index entry's color to be re-evaluated */
1352  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1353  if (!e_root && e_cur->visible)
1354  {
1355  e_root = e_cur;
1356  if (flag & MUTT_THREAD_COLLAPSE)
1357  final = e_root->vnum;
1358  }
1359 
1360  if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1361  (e_cur->msgno < minmsgno) && e_cur->visible)
1362  {
1363  minmsgno = e_cur->msgno;
1364  final = e_cur->vnum;
1365  }
1366 
1367  if (flag & MUTT_THREAD_COLLAPSE)
1368  {
1369  if (e_cur != e_root)
1370  e_cur->vnum = -1;
1371  }
1372  else
1373  {
1374  if (e_cur->visible)
1375  e_cur->vnum = e_cur->msgno;
1376  }
1377  }
1378 
1379  if (!e_cur->read && e_cur->visible)
1380  {
1381  if (e_cur->old)
1382  old_mail = 2;
1383  else
1384  new_mail = 1;
1385  if (e_cur->msgno < min_unread_msgno)
1386  {
1387  min_unread = e_cur->vnum;
1388  min_unread_msgno = e_cur->msgno;
1389  }
1390  }
1391 
1392  if (e_cur->flagged && e_cur->visible)
1393  flagged = true;
1394 
1395  if ((e_cur->vnum == -1) && e_cur->visible)
1396  num_hidden++;
1397  }
1398 
1399  if (thread->child)
1400  thread = thread->child;
1401  else if (thread->next)
1402  thread = thread->next;
1403  else
1404  {
1405  bool done = false;
1406  while (!thread->next)
1407  {
1408  thread = thread->parent;
1409  if (thread == top)
1410  {
1411  done = true;
1412  break;
1413  }
1414  }
1415  if (done)
1416  break;
1417  thread = thread->next;
1418  }
1419  }
1420 
1421  /* re-traverse the thread and store num_hidden in all headers, with or
1422  * without a virtual index. this will allow ~v to match all collapsed
1423  * messages when switching sort order to non-threaded. */
1424  if (flag & MUTT_THREAD_COLLAPSE)
1425  {
1426  thread = top;
1427  while (true)
1428  {
1429  e_cur = thread->message;
1430  if (e_cur)
1431  e_cur->num_hidden = num_hidden + 1;
1432 
1433  if (thread->child)
1434  thread = thread->child;
1435  else if (thread->next)
1436  thread = thread->next;
1437  else
1438  {
1439  bool done = false;
1440  while (!thread->next)
1441  {
1442  thread = thread->parent;
1443  if (thread == top)
1444  {
1445  done = true;
1446  break;
1447  }
1448  }
1449  if (done)
1450  break;
1451  thread = thread->next;
1452  }
1453  }
1454  }
1455 
1456  /* return value depends on action requested */
1457  if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1458  return final;
1459  if (flag & MUTT_THREAD_UNREAD)
1460  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1461  if (flag & MUTT_THREAD_NEXT_UNREAD)
1462  return min_unread;
1463  if (flag & MUTT_THREAD_FLAGGED)
1464  return flagged;
1465 
1466  return 0;
1467 }
1468 
1479 int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, int flag)
1480 {
1481  if (!m || !e)
1482  return 1;
1483 
1484  struct MuttThread *threads[2];
1485  int rc;
1486 
1487  if (((C_Sort & SORT_MASK) != SORT_THREADS) || !e->thread)
1488  return 1;
1489 
1490  threads[0] = e->thread;
1491  while (threads[0]->parent)
1492  threads[0] = threads[0]->parent;
1493 
1494  threads[1] = flag ? e->thread : threads[0]->next;
1495 
1496  for (int i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
1497  {
1498  while (!threads[i]->message)
1499  threads[i] = threads[i]->child;
1500  }
1501 
1502  if (C_Sort & SORT_REVERSE)
1503  rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1504  else
1505  {
1506  rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1507  threads[0]->message->msgno;
1508  }
1509 
1510  if (flag)
1511  rc += 1;
1512 
1513  return rc;
1514 }
1515 
1522 {
1524 
1525  for (int i = 0; i < m->msg_count; i++)
1526  {
1527  struct Email *e = m->emails[i];
1528  if (!e || !e->env)
1529  continue;
1530 
1531  if (e->env->message_id)
1532  mutt_hash_insert(hash, e->env->message_id, e);
1533  }
1534 
1535  return hash;
1536 }
1537 
1545 static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1546 {
1547  if (child == parent)
1548  return false;
1549 
1550  mutt_break_thread(child);
1552  mutt_set_flag(m, child, MUTT_TAG, false);
1553 
1554  child->changed = true;
1555  child->env->changed |= MUTT_ENV_CHANGED_IRT;
1556  return true;
1557 }
1558 
1566 bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
1567 {
1568  if (!parent || !children || !m)
1569  return false;
1570 
1571  bool changed = false;
1572 
1573  struct EmailNode *en = NULL;
1574  STAILQ_FOREACH(en, children, entries)
1575  {
1576  changed |= link_threads(parent, en->email, m);
1577  }
1578 
1579  return changed;
1580 }
1581 
1587 {
1588  struct MuttThread *thread = NULL;
1589  struct MuttThread *top = tctx->tree;
1590  while ((thread = top))
1591  {
1592  while (!thread->message)
1593  thread = thread->child;
1594 
1595  struct Email *e = thread->message;
1596  if (e->collapsed)
1598  top = top->next;
1599  }
1600 }
1601 
1607 void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1608 {
1609  struct MuttThread *thread = NULL;
1610  struct MuttThread *top = tctx->tree;
1611  while ((thread = top))
1612  {
1613  while (!thread->message)
1614  thread = thread->child;
1615 
1616  struct Email *e = thread->message;
1617 
1618  if (e->collapsed != collapse)
1619  {
1620  if (e->collapsed)
1622  else if (mutt_thread_can_collapse(e))
1624  }
1625  top = top->next;
1626  }
1627 }
1628 
1636 {
1639 }
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:354
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:47
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:327
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:567
Star character (for threads)
Definition: mutt_thread.h:63
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
Recurse through an email thread, matching messages.
Definition: mutt_thread.c:1266
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
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
toggle collapse
Definition: mutt_thread.c:1607
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:67
Ampersand character (for threads)
Definition: mutt_thread.h:64
void thread_hash_destructor(int type, void *obj, intptr_t data)
Hash Destructor callback - Implements hash_hdata_free_t.
Definition: thread.c:111
The envelope/body of an email.
Definition: email.h:37
A Hash Table.
Definition: hash.h:84
bool C_HideMissing
Config: Don&#39;t indicate missing messages, in the thread tree.
Definition: mutt_thread.c:49
struct Body * body
List of MIME parts.
Definition: email.h:91
struct Email * find_virtual(struct MuttThread *cur, int reverse)
Find an email with a Virtual message number.
Definition: thread.c:122
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:541
Structs that make up an email.
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:84
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:77
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:83
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:447
struct Mailbox * mailbox
Current mailbox.
Definition: mutt_thread.c:63
The "current" threading state.
Definition: mutt_thread.c:61
struct MuttThread * thread
Thread of Emails.
Definition: email.h:95
struct MuttThread * tree
Top of thread tree.
Definition: mutt_thread.c:64
static bool is_visible(struct Email *e)
Is the message visible?
Definition: mutt_thread.c:73
bool deep
Is the Thread deeply nested?
Definition: thread.h:41
struct HashTable * mutt_make_id_hash(struct Mailbox *m)
Create a Hash Table for message-ids.
Definition: mutt_thread.c:1521
bool C_HideThreadSubject
Config: Hide subjects that are similar to that of the parent message.
Definition: mutt_thread.c:50
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
re-collapse threads marked as collapsed
Definition: mutt_thread.c:1586
bool mutt_link_threads(struct Email *parent, struct EmailList *children, struct Mailbox *m)
Forcibly link threads together.
Definition: mutt_thread.c:1566
bool display_subject
Used for threading.
Definition: email.h:57
bool threaded
Used for threading.
Definition: email.h:56
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
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
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
Sort email threads.
Definition: mutt_thread.c:870
static void linearize_tree(struct ThreadsContext *tctx)
Flatten an email thread.
Definition: mutt_thread.c:131
bool C_DuplicateThreads
Config: Highlight messages with duplicated message IDs.
Definition: mutt_thread.c:47
bool C_SortRe
Config: Sort method for the sidebar.
Definition: mutt_thread.c:54
int vcount
The number of virtual messages.
Definition: mailbox.h:102
Lower left corner.
Definition: mutt_thread.h:56
Convenience wrapper for the config headers.
struct HashElem * next
Linked List.
Definition: hash.h:48
bool C_HideLimited
Config: Don&#39;t indicate hidden messages, in the thread tree.
Definition: mutt_thread.c:48
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:88
Assorted sorting methods.
bool read
Email is read.
Definition: email.h:51
static bool need_display_subject(struct Email *e)
Determines whether to display a message&#39;s subject.
Definition: mutt_thread.c:83
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:82
char * message_id
Message ID.
Definition: envelope.h:69
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
bool C_CollapseUnread
Config: Prevent the collapse of threads with unread emails.
Definition: mutt_thread.c:46
API for mailboxes.
Top T-piece.
Definition: mutt_thread.h:66
bool old
Email is seen, but unread.
Definition: email.h:50
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:498
TreeChar
Tree characters for menus.
Definition: mutt_thread.h:54
struct Envelope * env
Envelope information.
Definition: email.h:90
Convenience wrapper for the core headers.
bool C_HideTopLimited
Config: Don&#39;t indicate hidden top message, in the thread tree.
Definition: mutt_thread.c:51
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:85
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition: thread.c:225
WHERE short C_Sort
Config: Sort method for the index.
Definition: sort.h:60
bool C_StrictThreads
Config: Thread messages using &#39;In-Reply-To&#39; and &#39;References&#39; headers.
Definition: mutt_thread.c:55
void mutt_clear_threads(struct ThreadsContext *tctx)
Clear the threading of message in a mailbox.
Definition: mutt_thread.c:637
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:566
static int compare_threads(const void *a, const void *b)
Sorting function for email threads.
Definition: mutt_thread.c:664
struct HashTable * subj_hash
Hash Table by subject.
Definition: mailbox.h:128
Vertical line.
Definition: mutt_thread.h:60
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:76
int(* sort_t)(const void *a, const void *b)
Prototype for a function to compare two emails.
Definition: sort.h:50
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:82
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:80
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:293
struct MuttThread * child
Child of this Thread.
Definition: thread.h:46
bool duplicate_thread
Duplicated Email in Thread.
Definition: thread.h:37
Prototypes for many functions.
bool visible
Is this message part of the view?
Definition: email.h:74
WHERE short C_SortAux
Config: Secondary sort method for the index.
Definition: sort.h:61
Sort by email threads.
Definition: sort2.h:51
uint8_t MuttThreadFlags
Flags, e.g. MUTT_THREAD_COLLAPSE.
Definition: mutt_thread.h:74
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition: hash.h:100
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:52
Right arrow.
Definition: mutt_thread.h:62
struct ThreadsContext * mutt_thread_ctx_init(struct Mailbox *m)
Initialize a threading context.
Definition: mutt_thread.c:288
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:1545
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
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:84
Tagged messages.
Definition: mutt.h:103
int mutt_parent_message(struct Email *e, bool find_root)
Find the parent of a message.
Definition: mutt_thread.c:1182
Bottom T-piece.
Definition: mutt_thread.h:67
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:73
Left T-piece.
Definition: mutt_thread.h:58
int vnum
Virtual message number.
Definition: email.h:88
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:79
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:45
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, int flag)
Count the messages in a thread.
Definition: mutt_thread.c:1479
Upper left corner.
Definition: mutt_thread.h:57
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
char * tree
Character string to print thread tree.
Definition: email.h:94
#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:53
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:86
struct Email * message
Email this Thread refers to.
Definition: thread.h:49
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition: thread.c:64
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
Find the next/previous (sub)thread.
Definition: mutt_thread.c:1119
An Email conversation.
Definition: thread.h:34
void * data
User-supplied data.
Definition: hash.h:47
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:101
Horizontal line.
Definition: mutt_thread.h:59
bool sort_children
Sort the children.
Definition: thread.h:38
char * data
String.
Definition: list.h:36
char * subject
Email&#39;s subject.
Definition: envelope.h:66
bool flagged
Marked important?
Definition: email.h:43
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:716
struct Email * email
Email in the list.
Definition: email.h:127
#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
void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:687
#define STAILQ_EMPTY(head)
Definition: queue.h:345
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:401
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:321
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:320
bool C_CollapseFlagged
Config: Prevent the collapse of threads with flagged emails.
Definition: mutt_thread.c:45
The item stored in a Hash Table.
Definition: hash.h:43
List of Emails.
Definition: email.h:125
bool visible
Is this Thread visible?
Definition: thread.h:40
void mutt_thread_ctx_free(struct ThreadsContext **tctx)
Finalize a threading context.
Definition: mutt_thread.c:301
bool check_subject
Should the Subject be checked?
Definition: thread.h:39
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1554
struct HashTable * hash
Hash table for threads.
Definition: mutt_thread.c:65
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:829
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:444
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
off_t mutt_set_vnum(struct Mailbox *m)
Set the virtual index number of all the messages in a mailbox.
Definition: mutt_thread.c:1232
Blank space.
Definition: mutt_thread.h:61
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:167
Convenience wrapper for the library headers.
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1635
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:81
struct ListHead references
message references (in reverse order)
Definition: envelope.h:81
A List node for strings.
Definition: list.h:34
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:179
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:82
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:251
#define STAILQ_FIRST(head)
Definition: queue.h:347
Question mark.
Definition: mutt_thread.h:68
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:97
int pair
Color-pair to use when displaying in the index.
Definition: email.h:80
bool C_ThreadReceived
Config: Sort threaded messages by their received date.
Definition: mutt_thread.c:56
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:80
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:83
The header of an Email.
Definition: envelope.h:54
int msgno
Number displayed to the user.
Definition: email.h:87
Equals (for threads)
Definition: mutt_thread.h:65
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:78