NeoMutt  2022-04-29-81-g9c5a59
Teaching an old dog new tricks
DOXYGEN
mutt_thread.c File Reference

Create/manipulate threading in emails. More...

#include "config.h"
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "mutt/lib.h"
#include "config/lib.h"
#include "email/lib.h"
#include "core/lib.h"
#include "mutt.h"
#include "mutt_thread.h"
#include "mx.h"
#include "options.h"
#include "protos.h"
#include "sort.h"
+ Include dependency graph for mutt_thread.c:

Go to the source code of this file.

Data Structures

struct  ThreadsContext
 The "current" threading state. More...
 

Functions

enum UseThreads mutt_thread_style (void)
 Which threading style is active? More...
 
const char * get_use_threads_str (enum UseThreads value)
 Convert UseThreads enum to string. More...
 
int sort_validator (const struct ConfigSet *cs, const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
 Validate values of "sort" - Implements ConfigDef::validator() -. More...
 
static bool is_visible (struct Email *e)
 Is the message visible? More...
 
static bool need_display_subject (struct Email *e)
 Determines whether to display a message's subject. More...
 
static void linearize_tree (struct ThreadsContext *tctx)
 Flatten an email thread. More...
 
static void calculate_visibility (struct MuttThread *tree, int *max_depth)
 Are tree nodes visible. More...
 
struct ThreadsContextmutt_thread_ctx_init (struct Mailbox *m)
 Initialize a threading context. More...
 
void mutt_thread_ctx_free (struct ThreadsContext **tctx)
 Finalize a threading context. More...
 
void mutt_draw_tree (struct ThreadsContext *tctx)
 Draw a tree of threaded emails. More...
 
static void make_subject_list (struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
 Create a sorted list of all subjects in a thread. More...
 
static struct MuttThreadfind_subject (struct Mailbox *m, struct MuttThread *cur)
 Find the best possible match for a parent based on subject. More...
 
static struct HashTablemake_subj_hash (struct Mailbox *m)
 Create a Hash Table for the email subjects. More...
 
static void pseudo_threads (struct ThreadsContext *tctx)
 Thread messages by subject. More...
 
void mutt_clear_threads (struct ThreadsContext *tctx)
 Clear the threading of message in a mailbox. More...
 
static int compare_threads (const void *a, const void *b, void *arg)
 qsort_r() function for comparing email threads More...
 
static void mutt_sort_subthreads (struct ThreadsContext *tctx, bool init)
 Sort the children of a thread. More...
 
static void check_subjects (struct Mailbox *m, bool init)
 Find out which emails' subjects differ from their parent's. More...
 
void mutt_sort_threads (struct ThreadsContext *tctx, bool init)
 Sort email threads. More...
 
int mutt_aside_thread (struct Email *e, bool forwards, bool subthreads)
 Find the next/previous (sub)thread. More...
 
int mutt_parent_message (struct Email *e, bool find_root)
 Find the parent of a message. More...
 
off_t mutt_set_vnum (struct Mailbox *m)
 Set the virtual index number of all the messages in a mailbox. More...
 
int mutt_traverse_thread (struct Email *e_cur, MuttThreadFlags flag)
 Recurse through an email thread, matching messages. More...
 
int mutt_messages_in_thread (struct Mailbox *m, struct Email *e, enum MessageInThread mit)
 Count the messages in a thread. More...
 
struct HashTablemutt_make_id_hash (struct Mailbox *m)
 Create a Hash Table for message-ids. More...
 
static bool link_threads (struct Email *parent, struct Email *child, struct Mailbox *m)
 Forcibly link messages together. More...
 
bool mutt_link_threads (struct Email *parent, struct EmailList *children, struct Mailbox *m)
 Forcibly link threads together. More...
 
void mutt_thread_collapse_collapsed (struct ThreadsContext *tctx)
 Re-collapse threads marked as collapsed. More...
 
void mutt_thread_collapse (struct ThreadsContext *tctx, bool collapse)
 Toggle collapse. More...
 
bool mutt_thread_can_collapse (struct Email *e)
 Check whether a thread can be collapsed. More...
 

Variables

static const struct Mapping UseThreadsMethods []
 Choices for '$use_threads' for the index. More...
 
struct EnumDef UseThreadsTypeDef
 

Detailed Description

Create/manipulate threading in emails.

Authors
  • Michael R. Elkins

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file mutt_thread.c.

Function Documentation

◆ mutt_thread_style()

enum UseThreads mutt_thread_style ( void  )

Which threading style is active?

Return values
UT_FLATNo threading in use
UT_THREADSNormal threads (root above subthread)
UT_REVERSEReverse threads (subthread above root)
Note
UT_UNSET is never returned; rather, this function considers the interaction between $use_threads and $sort.

Definition at line 1 of file mutt_thread.c.

90 {
91  const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
92  const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
93  if (c_use_threads > UT_FLAT)
94  return c_use_threads;
95  if ((c_sort & SORT_MASK) != SORT_THREADS)
96  return UT_FLAT;
97  if (c_sort & SORT_REVERSE)
98  return UT_REVERSE;
99  return UT_THREADS;
100 }
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:97
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:292
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:85
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:86
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:87
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:78
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:49
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:79
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
+ Here is the caller graph for this function:

◆ get_use_threads_str()

const char* get_use_threads_str ( enum UseThreads  value)

Convert UseThreads enum to string.

Parameters
valueValue to convert
Return values
ptrString form of value

Definition at line 107 of file mutt_thread.c.

108 {
109  return mutt_map_get_name(value, UseThreadsMethods);
110 }
const char * mutt_map_get_name(int val, const struct Mapping *map)
Lookup a string for a constant.
Definition: mapping.c:42
static const struct Mapping UseThreadsMethods[]
Choices for '$use_threads' for the index.
Definition: mutt_thread.c:61
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ is_visible()

static bool is_visible ( struct Email e)
static

Is the message visible?

Parameters
eEmail
Return values
trueThe message is not hidden in some way

Definition at line 131 of file mutt_thread.c.

132 {
133  return e->vnum >= 0 || (e->collapsed && e->visible);
134 }
bool visible
Is this message part of the view?
Definition: email.h:121
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:120
int vnum
Virtual message number.
Definition: email.h:114
+ Here is the caller graph for this function:

◆ need_display_subject()

static bool need_display_subject ( struct Email e)
static

Determines whether to display a message's subject.

Parameters
eEmail
Return values
trueThe subject should be displayed

Definition at line 141 of file mutt_thread.c.

142 {
143  struct MuttThread *tmp = NULL;
144  struct MuttThread *tree = e->thread;
145 
146  /* if the user disabled subject hiding, display it */
147  const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
148  if (!c_hide_thread_subject)
149  return true;
150 
151  /* if our subject is different from our parent's, display it */
152  if (e->subject_changed)
153  return true;
154 
155  /* if our subject is different from that of our closest previously displayed
156  * sibling, display the subject */
157  for (tmp = tree->prev; tmp; tmp = tmp->prev)
158  {
159  e = tmp->message;
160  if (e && is_visible(e))
161  {
162  if (e->subject_changed)
163  return true;
164  break;
165  }
166  }
167 
168  /* if there is a parent-to-child subject change anywhere between us and our
169  * closest displayed ancestor, display the subject */
170  for (tmp = tree->parent; tmp; tmp = tmp->parent)
171  {
172  e = tmp->message;
173  if (e)
174  {
175  if (is_visible(e))
176  return false;
177  if (e->subject_changed)
178  return true;
179  }
180  }
181 
182  /* if we have no visible parent or previous sibling, display the subject */
183  return true;
184 }
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
static bool is_visible(struct Email *e)
Is the message visible?
Definition: mutt_thread.c:131
bool subject_changed
Used for threading.
Definition: email.h:106
struct MuttThread * thread
Thread of Emails.
Definition: email.h:119
An Email conversation.
Definition: thread.h:35
struct MuttThread * parent
Parent of this Thread.
Definition: thread.h:45
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
struct Email * message
Email this Thread refers to.
Definition: thread.h:49
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ linearize_tree()

static void linearize_tree ( struct ThreadsContext tctx)
static

Flatten an email thread.

Parameters
tctxThreading context

Definition at line 190 of file mutt_thread.c.

191 {
192  if (!tctx || !tctx->mailbox)
193  return;
194 
195  struct Mailbox *m = tctx->mailbox;
196 
197  const bool reverse = (mutt_thread_style() == UT_REVERSE);
198  struct MuttThread *tree = tctx->tree;
199  struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
200 
201  while (tree)
202  {
203  while (!tree->message)
204  tree = tree->child;
205 
206  *array = tree->message;
207  array += reverse ? -1 : 1;
208 
209  if (tree->child)
210  tree = tree->child;
211  else
212  {
213  while (tree)
214  {
215  if (tree->next)
216  {
217  tree = tree->next;
218  break;
219  }
220  else
221  tree = tree->parent;
222  }
223  }
224  }
225 }
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition: mutt_thread.c:89
The envelope/body of an email.
Definition: email.h:37
char * tree
Character string to print thread tree.
Definition: email.h:124
A mailbox.
Definition: mailbox.h:79
int msg_count
Total number of messages.
Definition: mailbox.h:88
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
struct MuttThread * tree
Top of thread tree.
Definition: mutt_thread.c:52
struct Mailbox * mailbox
Current mailbox.
Definition: mutt_thread.c:51
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ calculate_visibility()

static void calculate_visibility ( struct MuttThread tree,
int *  max_depth 
)
static

Are tree nodes visible.

Parameters
treeThreads tree
max_depthMaximum depth to check

this calculates whether a node is the root of a subtree that has visible nodes, whether a node itself is visible, whether, if invisible, it has depth anyway, and whether any of its later siblings are roots of visible subtrees. while it's at it, it frees the old thread display, so we can skip parts of the tree in mutt_draw_tree() if we've decided here that we don't care about them any more.

Definition at line 239 of file mutt_thread.c.

240 {
241  if (!tree)
242  return;
243 
244  struct MuttThread *tmp = NULL;
245  struct MuttThread *orig_tree = tree;
246  const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
247  const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
248  int hide_top_missing = c_hide_top_missing && !c_hide_missing;
249  const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
250  const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
251  int hide_top_limited = c_hide_top_limited && !c_hide_limited;
252  int depth = 0;
253 
254  /* we walk each level backwards to make it easier to compute next_subtree_visible */
255  while (tree->next)
256  tree = tree->next;
257  *max_depth = 0;
258 
259  while (true)
260  {
261  if (depth > *max_depth)
262  *max_depth = depth;
263 
264  tree->subtree_visible = 0;
265  if (tree->message)
266  {
267  FREE(&tree->message->tree);
268  if (is_visible(tree->message))
269  {
270  tree->deep = true;
271  tree->visible = true;
273  for (tmp = tree; tmp; tmp = tmp->parent)
274  {
275  if (tmp->subtree_visible)
276  {
277  tmp->deep = true;
278  tmp->subtree_visible = 2;
279  break;
280  }
281  else
282  tmp->subtree_visible = 1;
283  }
284  }
285  else
286  {
287  tree->visible = false;
288  tree->deep = !c_hide_limited;
289  }
290  }
291  else
292  {
293  tree->visible = false;
294  tree->deep = !c_hide_missing;
295  }
296  tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
297  tree->next->subtree_visible);
298  if (tree->child)
299  {
300  depth++;
301  tree = tree->child;
302  while (tree->next)
303  tree = tree->next;
304  }
305  else if (tree->prev)
306  tree = tree->prev;
307  else
308  {
309  while (tree && !tree->prev)
310  {
311  depth--;
312  tree = tree->parent;
313  }
314  if (!tree)
315  break;
316  tree = tree->prev;
317  }
318  }
319 
320  /* now fix up for the OPTHIDETOP* options if necessary */
321  if (hide_top_limited || hide_top_missing)
322  {
323  tree = orig_tree;
324  while (true)
325  {
326  if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
327  ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
328  {
329  tree->deep = false;
330  }
331  if (!tree->deep && tree->child && tree->subtree_visible)
332  tree = tree->child;
333  else if (tree->next)
334  tree = tree->next;
335  else
336  {
337  while (tree && !tree->next)
338  tree = tree->parent;
339  if (!tree)
340  break;
341  tree = tree->next;
342  }
343  }
344  }
345 }
#define FREE(x)
Definition: memory.h:40
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition: mutt_thread.c:141
bool display_subject
Used for threading.
Definition: email.h:101
bool visible
Is this Thread visible?
Definition: thread.h:40
struct MuttThread * child
Child of this Thread.
Definition: thread.h:46
bool deep
Is the Thread deeply nested?
Definition: thread.h:41
unsigned int subtree_visible
Is this Thread subtree visible?
Definition: thread.h:42
bool next_subtree_visible
Is the next Thread subtree visible?
Definition: thread.h:43
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:47
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_ctx_init()

struct ThreadsContext* mutt_thread_ctx_init ( struct Mailbox m)

Initialize a threading context.

Parameters
mCurrent mailbox
Return values
ptrThreading context

Definition at line 352 of file mutt_thread.c.

353 {
354  struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
355  tctx->mailbox = m;
356  tctx->tree = NULL;
357  tctx->hash = NULL;
358  return tctx;
359 }
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
The "current" threading state.
Definition: mutt_thread.c:50
struct HashTable * hash
Hash table for threads.
Definition: mutt_thread.c:53
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_ctx_free()

void mutt_thread_ctx_free ( struct ThreadsContext **  tctx)

Finalize a threading context.

Parameters
tctxThreading context to finalize

Definition at line 365 of file mutt_thread.c.

366 {
367  (*tctx)->mailbox = NULL;
368  mutt_hash_free(&(*tctx)->hash);
369  FREE(tctx);
370 }
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:457
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_draw_tree()

void mutt_draw_tree ( struct ThreadsContext tctx)

Draw a tree of threaded emails.

Parameters
tctxThreading context

Since the graphics characters have a value >255, I have to resort to using escape sequences to pass the information to print_enriched_string(). These are the macros MUTT_TREE_* defined in mutt.h.

ncurses should automatically use the default ASCII characters instead of graphics chars on terminals which don't support them (see the man page for curs_addch).

Definition at line 384 of file mutt_thread.c.

385 {
386  char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
387  const bool reverse = (mutt_thread_style() == UT_REVERSE);
388  enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
389  enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
390  const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
391  int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
392  struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
393 
394  struct MuttThread *tree = tctx->tree;
395 
396  /* Do the visibility calculations and free the old thread chars.
397  * From now on we can simply ignore invisible subtrees */
398  calculate_visibility(tree, &max_depth);
399  pfx = mutt_mem_malloc((width * max_depth) + 2);
400  arrow = mutt_mem_malloc((width * max_depth) + 2);
401  while (tree)
402  {
403  if (depth != 0)
404  {
405  myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
406  const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
407  const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
408  if (start_depth == depth)
409  myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
410  else if (parent->message && !c_hide_limited)
411  myarrow[0] = MUTT_TREE_HIDDEN;
412  else if (!parent->message && !c_hide_missing)
413  myarrow[0] = MUTT_TREE_MISSING;
414  else
415  myarrow[0] = vtee;
416  if (width == 2)
417  {
418  myarrow[1] = pseudo ? MUTT_TREE_STAR :
420  }
421  if (tree->visible)
422  {
423  myarrow[width] = MUTT_TREE_RARROW;
424  myarrow[width + 1] = 0;
425  new_tree = mutt_mem_malloc(((size_t) depth * width) + 2);
426  if (start_depth > 1)
427  {
428  strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
429  mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
430  (1 + depth - start_depth) * width + 2);
431  }
432  else
433  mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
434  tree->message->tree = new_tree;
435  }
436  }
437  if (tree->child && (depth != 0))
438  {
439  mypfx = pfx + (depth - 1) * width;
440  mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
441  if (width == 2)
442  mypfx[1] = MUTT_TREE_SPACE;
443  }
444  parent = tree;
445  nextdisp = NULL;
446  pseudo = NULL;
447  do
448  {
449  if (tree->child && tree->subtree_visible)
450  {
451  if (tree->deep)
452  depth++;
453  if (tree->visible)
454  start_depth = depth;
455  tree = tree->child;
456 
457  /* we do this here because we need to make sure that the first child thread
458  * of the old tree that we deal with is actually displayed if any are,
459  * or we might set the parent variable wrong while going through it. */
460  while (!tree->subtree_visible && tree->next)
461  tree = tree->next;
462  }
463  else
464  {
465  while (!tree->next && tree->parent)
466  {
467  if (tree == pseudo)
468  pseudo = NULL;
469  if (tree == nextdisp)
470  nextdisp = NULL;
471  if (tree->visible)
472  start_depth = depth;
473  tree = tree->parent;
474  if (tree->deep)
475  {
476  if (start_depth == depth)
477  start_depth--;
478  depth--;
479  }
480  }
481  if (tree == pseudo)
482  pseudo = NULL;
483  if (tree == nextdisp)
484  nextdisp = NULL;
485  if (tree->visible)
486  start_depth = depth;
487  tree = tree->next;
488  if (!tree)
489  break;
490  }
491  if (!pseudo && tree->fake_thread)
492  pseudo = tree;
493  if (!nextdisp && tree->next_subtree_visible)
494  nextdisp = tree;
495  } while (!tree->deep);
496  }
497 
498  FREE(&pfx);
499  FREE(&arrow);
500 }
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:629
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:239
TreeChar
Tree characters for menus.
Definition: mutt_thread.h:43
@ MUTT_TREE_LLCORNER
Lower left corner.
Definition: mutt_thread.h:44
@ MUTT_TREE_RARROW
Right arrow.
Definition: mutt_thread.h:50
@ MUTT_TREE_ULCORNER
Upper left corner.
Definition: mutt_thread.h:45
@ MUTT_TREE_EQUALS
Equals (for threads)
Definition: mutt_thread.h:53
@ MUTT_TREE_HIDDEN
Ampersand character (for threads)
Definition: mutt_thread.h:52
@ MUTT_TREE_STAR
Star character (for threads)
Definition: mutt_thread.h:51
@ MUTT_TREE_LTEE
Left T-piece.
Definition: mutt_thread.h:46
@ MUTT_TREE_VLINE
Vertical line.
Definition: mutt_thread.h:48
@ MUTT_TREE_MISSING
Question mark.
Definition: mutt_thread.h:56
@ MUTT_TREE_TTEE
Top T-piece.
Definition: mutt_thread.h:54
@ MUTT_TREE_HLINE
Horizontal line.
Definition: mutt_thread.h:47
@ MUTT_TREE_SPACE
Blank space.
Definition: mutt_thread.h:49
@ MUTT_TREE_BTEE
Bottom T-piece.
Definition: mutt_thread.h:55
bool fake_thread
Emails grouped by Subject.
Definition: thread.h:36
bool duplicate_thread
Duplicated Email in Thread.
Definition: thread.h:37
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ make_subject_list()

static void make_subject_list ( struct ListHead *  subjects,
struct MuttThread cur,
time_t *  dateptr 
)
static

Create a sorted list of all subjects in a thread.

Parameters
[out]subjectsString List of subjects
[in]curEmail Thread
[out]dateptrEarliest date found in thread

Since we may be trying to attach as a pseudo-thread a MuttThread that has no message, we have to make a list of all the subjects of its most immediate existing descendants.

Definition at line 512 of file mutt_thread.c.

513 {
514  struct MuttThread *start = cur;
515  struct Envelope *env = NULL;
516  time_t thisdate;
517  int rc = 0;
518 
519  while (true)
520  {
521  while (!cur->message)
522  cur = cur->child;
523 
524  if (dateptr)
525  {
526  const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
527  thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
528  if ((*dateptr == 0) || (thisdate < *dateptr))
529  *dateptr = thisdate;
530  }
531 
532  env = cur->message->env;
533  const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
534  if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
535  {
536  struct ListNode *np = NULL;
537  STAILQ_FOREACH(np, subjects, entries)
538  {
539  rc = mutt_str_cmp(env->real_subj, np->data);
540  if (rc >= 0)
541  break;
542  }
543  if (!np)
544  mutt_list_insert_head(subjects, env->real_subj);
545  else if (rc > 0)
546  mutt_list_insert_after(subjects, np, env->real_subj);
547  }
548 
549  while (!cur->next && (cur != start))
550  {
551  cur = cur->parent;
552  }
553  if (cur == start)
554  break;
555  cur = cur->next;
556  }
557 }
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:45
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
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:447
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
struct Envelope * env
Envelope information.
Definition: email.h:66
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
The header of an Email.
Definition: envelope.h:57
char * subject
Email's subject.
Definition: envelope.h:70
char * real_subj
Offset of the real subject.
Definition: envelope.h:71
A List node for strings.
Definition: list.h:35
char * data
String.
Definition: list.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ find_subject()

static struct MuttThread* find_subject ( struct Mailbox m,
struct MuttThread cur 
)
static

Find the best possible match for a parent based on subject.

Parameters
mMailbox
curEmail to match
Return values
ptrBest match for a parent

If there are multiple matches, the one which was sent the latest, but before the current message, is used.

Definition at line 568 of file mutt_thread.c.

569 {
570  if (!m)
571  return NULL;
572 
573  struct HashElem *ptr = NULL;
574  struct MuttThread *tmp = NULL, *last = NULL;
575  struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
576  time_t date = 0;
577 
578  make_subject_list(&subjects, cur, &date);
579 
580  struct ListNode *np = NULL;
581  STAILQ_FOREACH(np, &subjects, entries)
582  {
583  for (ptr = mutt_hash_find_bucket(m->subj_hash, np->data); ptr; ptr = ptr->next)
584  {
585  const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
586  tmp = ((struct Email *) ptr->data)->thread;
587  if ((tmp != cur) && /* don't match the same message */
588  !tmp->fake_thread && /* don't match pseudo threads */
589  tmp->message->subject_changed && /* only match interesting replies */
590  !is_descendant(tmp, cur) && /* don't match in the same thread */
591  (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
592  (!last || (c_thread_received ?
593  (last->message->received < tmp->message->received) :
594  (last->message->date_sent < tmp->message->date_sent))) &&
595  tmp->message->env->real_subj &&
596  mutt_str_equal(np->data, tmp->message->env->real_subj))
597  {
598  last = tmp; /* best match so far */
599  }
600  }
601  }
602 
603  mutt_list_clear(&subjects);
604  return last;
605 }
struct HashElem * mutt_hash_find_bucket(const struct HashTable *table, const char *strkey)
Find the HashElem in a Hash Table element using a key.
Definition: hash.c:409
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition: list.c:167
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:784
static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
Create a sorted list of all subjects in a thread.
Definition: mutt_thread.c:512
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
The item stored in a Hash Table.
Definition: hash.h:44
struct HashElem * next
Linked List.
Definition: hash.h:48
void * data
User-supplied data.
Definition: hash.h:47
struct HashTable * subj_hash
Hash Table by subject.
Definition: mailbox.h:125
bool is_descendant(struct MuttThread *a, struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:44
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ make_subj_hash()

static struct HashTable* make_subj_hash ( struct Mailbox m)
static

Create a Hash Table for the email subjects.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 612 of file mutt_thread.c.

613 {
614  if (!m)
615  return NULL;
616 
617  struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_ALLOW_DUPS);
618 
619  for (int i = 0; i < m->msg_count; i++)
620  {
621  struct Email *e = m->emails[i];
622  if (!e || !e->env)
623  continue;
624  if (e->env->real_subj)
625  mutt_hash_insert(hash, e->env->real_subj, e);
626  }
627 
628  return hash;
629 }
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition: hash.c:335
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:259
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition: hash.h:112
A Hash Table.
Definition: hash.h:97
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ pseudo_threads()

static void pseudo_threads ( struct ThreadsContext tctx)
static

Thread messages by subject.

Parameters
tctxThreading context

Thread by subject things that didn't get threaded by message-id

Definition at line 637 of file mutt_thread.c.

638 {
639  if (!tctx || !tctx->mailbox)
640  return;
641 
642  struct Mailbox *m = tctx->mailbox;
643 
644  struct MuttThread *tree = tctx->tree;
645  struct MuttThread *top = tree;
646  struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
647  *nextchild = NULL;
648 
649  if (!m->subj_hash)
650  m->subj_hash = make_subj_hash(m);
651 
652  while (tree)
653  {
654  cur = tree;
655  tree = tree->next;
656  parent = find_subject(m, cur);
657  if (parent)
658  {
659  cur->fake_thread = true;
660  unlink_message(&top, cur);
662  parent->sort_children = true;
663  tmp = cur;
664  while (true)
665  {
666  while (!tmp->message)
667  tmp = tmp->child;
668 
669  /* if the message we're attaching has pseudo-children, they
670  * need to be attached to its parent, so move them up a level.
671  * but only do this if they have the same real subject as the
672  * parent, since otherwise they rightly belong to the message
673  * we're attaching. */
674  if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
676  {
677  tmp->message->subject_changed = false;
678 
679  for (curchild = tmp->child; curchild;)
680  {
681  nextchild = curchild->next;
682  if (curchild->fake_thread)
683  {
684  unlink_message(&tmp->child, curchild);
685  insert_message(&parent->child, parent, curchild);
686  }
687  curchild = nextchild;
688  }
689  }
690 
691  while (!tmp->next && (tmp != cur))
692  {
693  tmp = tmp->parent;
694  }
695  if (tmp == cur)
696  break;
697  tmp = tmp->next;
698  }
699  }
700  }
701  tctx->tree = top;
702 }
static struct MuttThread * find_subject(struct Mailbox *m, struct MuttThread *cur)
Find the best possible match for a parent based on subject.
Definition: mutt_thread.c:568
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:612
bool sort_children
Sort the children.
Definition: thread.h:38
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition: thread.c:64
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
Insert a message into a thread.
Definition: thread.c:102
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_clear_threads()

void mutt_clear_threads ( struct ThreadsContext tctx)

Clear the threading of message in a mailbox.

Parameters
tctxThreading context

Definition at line 708 of file mutt_thread.c.

709 {
710  if (!tctx || !tctx->mailbox || !tctx->mailbox->emails || !tctx->tree)
711  return;
712 
713  for (int i = 0; i < tctx->mailbox->msg_count; i++)
714  {
715  struct Email *e = tctx->mailbox->emails[i];
716  if (!e)
717  break;
718 
719  /* mailbox may have been only partially read */
720  e->thread = NULL;
721  e->threaded = false;
722  }
723  tctx->tree = NULL;
724  mutt_hash_free(&tctx->hash);
725 }
bool threaded
Used for threading.
Definition: email.h:108
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ compare_threads()

static int compare_threads ( const void *  a,
const void *  b,
void *  arg 
)
static

qsort_r() function for comparing email threads

Parameters
aFirst thread to compare
bSecond thread to compare
argThreadsContext for how to compare
Return values
<0a precedes b
0a and b are identical
>0b precedes a

Definition at line 736 of file mutt_thread.c.

737 {
738  const struct MuttThread *ta = *(struct MuttThread const *const *) a;
739  const struct MuttThread *tb = *(struct MuttThread const *const *) b;
740  const struct ThreadsContext *tctx = arg;
741  assert(ta->parent == tb->parent);
742  /* If c_sort ties, remember we are building the thread array in
743  * reverse from the index the mails had in the mailbox. */
744  if (ta->parent)
745  {
748  }
749  else
750  {
752  mx_type(tctx->mailbox), tctx->c_sort,
754  }
755 }
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1837
@ SORT_ORDER
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:48
int mutt_compare_emails(const struct Email *a, const struct Email *b, enum MailboxType type, short sort, short sort_aux)
Compare two emails using up to two sort methods.
Definition: sort.c:328
struct Email * sort_aux_key
Email that controls how subthread siblings sort.
Definition: thread.h:51
struct Email * sort_thread_key
Email that controls how top thread sorts.
Definition: thread.h:50
short c_sort_aux
Last sort_aux method.
Definition: mutt_thread.c:55
short c_sort
Last sort method.
Definition: mutt_thread.c:54
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_sort_subthreads()

static void mutt_sort_subthreads ( struct ThreadsContext tctx,
bool  init 
)
static

Sort the children of a thread.

Parameters
tctxThreading context
initIf true, rebuild the thread

Definition at line 762 of file mutt_thread.c.

763 {
764  struct MuttThread *thread = tctx->tree;
765  if (!thread)
766  return;
767 
768  struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
769  struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
770  struct Email *oldsort_thread_key = NULL;
771  int i, array_size;
772  bool sort_top = false;
773 
774  /* we put things into the array backwards to save some cycles,
775  * but we want to have to move less stuff around if we're
776  * resorting, so we sort backwards and then put them back
777  * in reverse order so they're forwards */
778  const bool reverse = (mutt_thread_style() == UT_REVERSE);
779  short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
780  short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
781  if ((c_sort & SORT_MASK) == SORT_THREADS)
782  {
783  assert(!(c_sort & SORT_REVERSE) != reverse);
784  assert(cs_subset_enum(NeoMutt->sub, "use_threads") == UT_UNSET);
785  c_sort = c_sort_aux;
786  }
787  c_sort ^= SORT_REVERSE;
788  c_sort_aux ^= SORT_REVERSE;
789  if (init || tctx->c_sort != c_sort || tctx->c_sort_aux != c_sort_aux)
790  {
791  tctx->c_sort = c_sort;
792  tctx->c_sort_aux = c_sort_aux;
793  init = true;
794  }
795 
796  top = thread;
797 
798  array_size = 256;
799  array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
800  while (true)
801  {
802  if (init || !thread->sort_thread_key || !thread->sort_aux_key)
803  {
804  thread->sort_thread_key = NULL;
805  thread->sort_aux_key = NULL;
806 
807  if (thread->parent)
808  thread->parent->sort_children = true;
809  else
810  sort_top = true;
811  }
812 
813  if (thread->child)
814  {
815  thread = thread->child;
816  continue;
817  }
818  else
819  {
820  /* if it has no children, it must be real. sort it on its own merits */
823 
824  if (thread->next)
825  {
826  thread = thread->next;
827  continue;
828  }
829  }
830 
831  while (!thread->next)
832  {
833  /* if it has siblings and needs to be sorted, sort it... */
834  if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
835  {
836  /* put them into the array */
837  for (i = 0; thread; i++, thread = thread->prev)
838  {
839  if (i >= array_size)
840  mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
841 
842  array[i] = thread;
843  }
844 
845  mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
846 
847  /* attach them back together. make thread the last sibling. */
848  thread = array[0];
849  thread->next = NULL;
850  array[i - 1]->prev = NULL;
851 
852  if (thread->parent)
853  thread->parent->child = array[i - 1];
854  else
855  top = array[i - 1];
856 
857  while (--i)
858  {
859  array[i - 1]->prev = array[i];
860  array[i]->next = array[i - 1];
861  }
862  }
863 
864  if (thread->parent)
865  {
866  tmp = thread;
867  thread = thread->parent;
868 
870  {
871  /* we just sorted its children */
872  thread->sort_children = false;
873 
874  oldsort_aux_key = thread->sort_aux_key;
875  oldsort_thread_key = thread->sort_thread_key;
876 
877  /* update sort keys. sort_aux_key will be the first or last
878  * sibling, as appropriate... */
880  sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
882  tmp->sort_aux_key;
883 
884  if (c_sort_aux & SORT_LAST)
885  {
886  if (!thread->sort_aux_key ||
887  (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mx_type(tctx->mailbox),
888  c_sort_aux | SORT_REVERSE, SORT_ORDER) > 0))
889  {
890  thread->sort_aux_key = sort_aux_key;
891  }
892  }
893  else if (!thread->sort_aux_key)
894  thread->sort_aux_key = sort_aux_key;
895 
896  /* ...but sort_thread_key may require searching the entire
897  * list of siblings */
898  if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
899  {
901  }
902  else
903  {
904  if (thread->message)
905  {
907  }
908  else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
909  {
910  thread->sort_thread_key = tmp->sort_thread_key;
911  }
912  else
913  {
915  }
916  if (c_sort & SORT_LAST)
917  {
918  for (tmp = thread->child; tmp; tmp = tmp->next)
919  {
920  if (tmp->sort_thread_key == thread->sort_thread_key)
921  continue;
922  if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key,
923  mx_type(tctx->mailbox),
924  c_sort | SORT_REVERSE, SORT_ORDER) > 0))
925  {
926  thread->sort_thread_key = tmp->sort_thread_key;
927  }
928  }
929  }
930  }
931 
932  /* if a sort_key has changed, we need to resort it and siblings */
933  if ((oldsort_aux_key != thread->sort_aux_key) ||
934  (oldsort_thread_key != thread->sort_thread_key))
935  {
936  if (thread->parent)
937  thread->parent->sort_children = true;
938  else
939  sort_top = true;
940  }
941  }
942  }
943  else
944  {
945  FREE(&array);
946  tctx->tree = top;
947  return;
948  }
949  }
950 
951  thread = thread->next;
952  }
953 }
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
static int compare_threads(const void *a, const void *b, void *arg)
qsort_r() function for comparing email threads
Definition: mutt_thread.c:736
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:84
void mutt_qsort_r(void *base, size_t nmemb, size_t size, qsort_r_compar_t compar, void *arg)
Sort an array, where the comparator has access to opaque data rather than requiring global variables.
Definition: qsort_r.c:76
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:80
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ check_subjects()

static void check_subjects ( struct Mailbox m,
bool  init 
)
static

Find out which emails' subjects differ from their parent's.

Parameters
mMailbox
initIf true, rebuild the thread

Definition at line 960 of file mutt_thread.c.

961 {
962  if (!m)
963  return;
964 
965  for (int i = 0; i < m->msg_count; i++)
966  {
967  struct Email *e = m->emails[i];
968  if (!e || !e->thread)
969  continue;
970 
971  if (e->thread->check_subject)
972  e->thread->check_subject = false;
973  else if (!init)
974  continue;
975 
976  /* figure out which messages have subjects different than their parents' */
977  struct MuttThread *tmp = e->thread->parent;
978  while (tmp && !tmp->message)
979  {
980  tmp = tmp->parent;
981  }
982 
983  if (!tmp)
984  e->subject_changed = true;
985  else if (e->env->real_subj && tmp->message->env->real_subj)
986  {
988  }
989  else
990  {
991  e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
992  }
993  }
994 }
bool check_subject
Should the Subject be checked?
Definition: thread.h:39
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_sort_threads()

void mutt_sort_threads ( struct ThreadsContext tctx,
bool  init 
)

Sort email threads.

Parameters
tctxThreading context
initIf true, rebuild the thread

Definition at line 1001 of file mutt_thread.c.

1002 {
1003  if (!tctx || !tctx->mailbox)
1004  return;
1005 
1006  struct Mailbox *m = tctx->mailbox;
1007 
1008  struct Email *e = NULL;
1009  int i, using_refs = 0;
1010  struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1011  struct MuttThread top = { 0 };
1012  struct ListNode *ref = NULL;
1013 
1014  assert(m->msg_count > 0);
1015  if (!tctx->hash)
1016  init = true;
1017 
1018  if (init)
1019  {
1022  }
1023 
1024  /* we want a quick way to see if things are actually attached to the top of the
1025  * thread tree or if they're just dangling, so we attach everything to a top
1026  * node temporarily */
1027  top.parent = NULL;
1028  top.next = NULL;
1029  top.prev = NULL;
1030 
1031  top.child = tctx->tree;
1032  for (thread = tctx->tree; thread; thread = thread->next)
1033  thread->parent = &top;
1034 
1035  /* put each new message together with the matching messageless MuttThread if it
1036  * exists. otherwise, if there is a MuttThread that already has a message, thread
1037  * new message as an identical child. if we didn't attach the message to a
1038  * MuttThread, make a new one for it. */
1039  const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1040  for (i = 0; i < m->msg_count; i++)
1041  {
1042  e = m->emails[i];
1043  if (!e)
1044  continue;
1045 
1046  if (!e->thread)
1047  {
1048  if ((!init || c_duplicate_threads) && e->env->message_id)
1049  thread = mutt_hash_find(tctx->hash, e->env->message_id);
1050  else
1051  thread = NULL;
1052 
1053  if (thread && !thread->message)
1054  {
1055  /* this is a message which was missing before */
1056  thread->message = e;
1057  e->thread = thread;
1058  thread->check_subject = true;
1059 
1060  /* mark descendants as needing subject_changed checked */
1061  for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1062  {
1063  while (!tmp->message)
1064  tmp = tmp->child;
1065  tmp->check_subject = true;
1066  while (!tmp->next && (tmp != thread))
1067  tmp = tmp->parent;
1068  if (tmp != thread)
1069  tmp = tmp->next;
1070  }
1071 
1072  if (thread->parent)
1073  {
1074  /* remove threading info above it based on its children, which we'll
1075  * recalculate based on its headers. make sure not to leave
1076  * dangling missing messages. note that we haven't kept track
1077  * of what info came from its children and what from its siblings'
1078  * children, so we just remove the stuff that's definitely from it */
1079  do
1080  {
1081  tmp = thread->parent;
1082  unlink_message(&tmp->child, thread);
1083  thread->parent = NULL;
1084  thread->sort_thread_key = NULL;
1085  thread->sort_aux_key = NULL;
1086  thread->fake_thread = false;
1087  thread = tmp;
1088  } while (thread != &top && !thread->child && !thread->message);
1089  }
1090  }
1091  else
1092  {
1093  tnew = (c_duplicate_threads ? thread : NULL);
1094 
1095  thread = mutt_mem_calloc(1, sizeof(struct MuttThread));
1096  thread->message = e;
1097  thread->check_subject = true;
1098  e->thread = thread;
1099  mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1100 
1101  if (tnew)
1102  {
1103  if (tnew->duplicate_thread)
1104  tnew = tnew->parent;
1105 
1106  thread = e->thread;
1107 
1108  insert_message(&tnew->child, tnew, thread);
1109  thread->duplicate_thread = true;
1110  thread->message->threaded = true;
1111  }
1112  }
1113  }
1114  else
1115  {
1116  /* unlink pseudo-threads because they might be children of newly
1117  * arrived messages */
1118  thread = e->thread;
1119  for (tnew = thread->child; tnew;)
1120  {
1121  tmp = tnew->next;
1122  if (tnew->fake_thread)
1123  {
1124  unlink_message(&thread->child, tnew);
1125  insert_message(&top.child, &top, tnew);
1126  tnew->fake_thread = false;
1127  }
1128  tnew = tmp;
1129  }
1130  }
1131  }
1132 
1133  /* thread by references */
1134  for (i = 0; i < m->msg_count; i++)
1135  {
1136  e = m->emails[i];
1137  if (!e)
1138  break;
1139 
1140  if (e->threaded)
1141  continue;
1142  e->threaded = true;
1143 
1144  thread = e->thread;
1145  if (!thread)
1146  continue;
1147  using_refs = 0;
1148 
1149  while (true)
1150  {
1151  if (using_refs == 0)
1152  {
1153  /* look at the beginning of in-reply-to: */
1154  ref = STAILQ_FIRST(&e->env->in_reply_to);
1155  if (ref)
1156  using_refs = 1;
1157  else
1158  {
1159  ref = STAILQ_FIRST(&e->env->references);
1160  using_refs = 2;
1161  }
1162  }
1163  else if (using_refs == 1)
1164  {
1165  /* if there's no references header, use all the in-reply-to:
1166  * data that we have. otherwise, use the first reference
1167  * if it's different than the first in-reply-to, otherwise use
1168  * the second reference (since at least eudora puts the most
1169  * recent reference in in-reply-to and the rest in references) */
1170  if (STAILQ_EMPTY(&e->env->references))
1171  ref = STAILQ_NEXT(ref, entries);
1172  else
1173  {
1174  if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1175  ref = STAILQ_FIRST(&e->env->references);
1176  else
1177  ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1178 
1179  using_refs = 2;
1180  }
1181  }
1182  else
1183  ref = STAILQ_NEXT(ref, entries); /* go on with references */
1184 
1185  if (!ref)
1186  break;
1187 
1188  tnew = mutt_hash_find(tctx->hash, ref->data);
1189  if (tnew)
1190  {
1191  if (tnew->duplicate_thread)
1192  tnew = tnew->parent;
1193  if (is_descendant(tnew, thread)) /* no loops! */
1194  continue;
1195  }
1196  else
1197  {
1198  tnew = mutt_mem_calloc(1, sizeof(struct MuttThread));
1199  mutt_hash_insert(tctx->hash, ref->data, tnew);
1200  }
1201 
1202  if (thread->parent)
1203  unlink_message(&top.child, thread);
1204  insert_message(&tnew->child, tnew, thread);
1205  thread = tnew;
1206  if (thread->message || (thread->parent && (thread->parent != &top)))
1207  break;
1208  }
1209 
1210  if (!thread->parent)
1211  insert_message(&top.child, &top, thread);
1212  }
1213 
1214  /* detach everything from the temporary top node */
1215  for (thread = top.child; thread; thread = thread->next)
1216  {
1217  thread->parent = NULL;
1218  }
1219  tctx->tree = top.child;
1220 
1221  check_subjects(tctx->mailbox, init);
1222 
1223  const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1224  if (!c_strict_threads)
1225  pseudo_threads(tctx);
1226 
1227  /* if $sort_aux or similar changed after the mailbox is sorted, then
1228  * all the subthreads need to be resorted */
1229  if (tctx->tree)
1230  {
1231  mutt_sort_subthreads(tctx, init || OptSortSubthreads);
1232  OptSortSubthreads = false;
1233 
1234  /* Put the list into an array. */
1235  linearize_tree(tctx);
1236 
1237  /* Draw the thread tree. */
1238  mutt_draw_tree(tctx);
1239  }
1240 }
void thread_hash_destructor(int type, void *obj, intptr_t data)
Hash Destructor callback - Implements hash_hdata_free_t -.
Definition: thread.c:119
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:362
void mutt_hash_set_destructor(struct HashTable *table, hash_hdata_free_t fn, intptr_t fn_data)
Set the destructor for a Hash Table.
Definition: hash.c:301
static void linearize_tree(struct ThreadsContext *tctx)
Flatten an email thread.
Definition: mutt_thread.c:190
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:762
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:384
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:637
static void check_subjects(struct Mailbox *m, bool init)
Find out which emails' subjects differ from their parent's.
Definition: mutt_thread.c:960
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition: options.h:59
#define STAILQ_FIRST(head)
Definition: queue.h:350
#define STAILQ_EMPTY(head)
Definition: queue.h:348
#define STAILQ_NEXT(elm, field)
Definition: queue.h:400
char * message_id
Message ID.
Definition: envelope.h:73
struct ListHead references
message references (in reverse order)
Definition: envelope.h:85
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:86
+ Here is the call graph for this function:

◆ mutt_aside_thread()

int mutt_aside_thread ( struct Email e,
bool  forwards,
bool  subthreads 
)

Find the next/previous (sub)thread.

Parameters
eSearch from this Email
forwardsDirection to search: 'true' forwards, 'false' backwards
subthreadsSearch subthreads: 'true' subthread, 'false' not
Return values
numIndex into the virtual email table

Definition at line 1249 of file mutt_thread.c.

1250 {
1251  struct MuttThread *cur = NULL;
1252  struct Email *e_tmp = NULL;
1253 
1254  const enum UseThreads threaded = mutt_thread_style();
1255  if (threaded == UT_FLAT)
1256  {
1257  mutt_error(_("Threading is not enabled"));
1258  return e->vnum;
1259  }
1260 
1261  cur = e->thread;
1262 
1263  if (subthreads)
1264  {
1265  if (forwards ^ (threaded == UT_REVERSE))
1266  {
1267  while (!cur->next && cur->parent)
1268  cur = cur->parent;
1269  }
1270  else
1271  {
1272  while (!cur->prev && cur->parent)
1273  cur = cur->parent;
1274  }
1275  }
1276  else
1277  {
1278  while (cur->parent)
1279  cur = cur->parent;
1280  }
1281 
1282  if (forwards ^ (threaded == UT_REVERSE))
1283  {
1284  do
1285  {
1286  cur = cur->next;
1287  if (!cur)
1288  return -1;
1289  e_tmp = find_virtual(cur, false);
1290  } while (!e_tmp);
1291  }
1292  else
1293  {
1294  do
1295  {
1296  cur = cur->prev;
1297  if (!cur)
1298  return -1;
1299  e_tmp = find_virtual(cur, true);
1300  } while (!e_tmp);
1301  }
1302 
1303  return e_tmp->vnum;
1304 }
#define mutt_error(...)
Definition: logging.h:87
#define _(a)
Definition: message.h:28
UseThreads
Which threading style is active, $use_threads.
Definition: mutt_thread.h:83
struct Email * find_virtual(struct MuttThread *cur, bool reverse)
Find an email with a Virtual message number.
Definition: thread.c:130
+ Here is the call graph for this function:

◆ mutt_parent_message()

int mutt_parent_message ( struct Email e,
bool  find_root 
)

Find the parent of a message.

Parameters
eCurrent Email
find_rootIf true, find the root message
Return values
>=0Virtual index number of parent/root message
-1Error

Definition at line 1313 of file mutt_thread.c.

1314 {
1315  if (!e)
1316  return -1;
1317 
1318  struct MuttThread *thread = NULL;
1319  struct Email *e_parent = NULL;
1320 
1321  if (!mutt_using_threads())
1322  {
1323  mutt_error(_("Threading is not enabled"));
1324  return e->vnum;
1325  }
1326 
1327  /* Root may be the current message */
1328  if (find_root)
1329  e_parent = e;
1330 
1331  for (thread = e->thread->parent; thread; thread = thread->parent)
1332  {
1333  e = thread->message;
1334  if (e)
1335  {
1336  e_parent = e;
1337  if (!find_root)
1338  break;
1339  }
1340  }
1341 
1342  if (!e_parent)
1343  {
1344  mutt_error(_("Parent message is not available"));
1345  return -1;
1346  }
1347  if (!is_visible(e_parent))
1348  {
1349  if (find_root)
1350  mutt_error(_("Root message is not visible in this limited view"));
1351  else
1352  mutt_error(_("Parent message is not visible in this limited view"));
1353  return -1;
1354  }
1355  return e_parent->vnum;
1356 }
#define mutt_using_threads()
Definition: mutt_thread.h:100
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_set_vnum()

off_t mutt_set_vnum ( struct Mailbox m)

Set the virtual index number of all the messages in a mailbox.

Parameters
mMailbox
Return values
mumSize in bytes of all messages shown

Definition at line 1363 of file mutt_thread.c.

1364 {
1365  if (!m)
1366  return 0;
1367 
1368  off_t vsize = 0;
1369  const int padding = mx_msg_padding_size(m);
1370 
1371  m->vcount = 0;
1372 
1373  for (int i = 0; i < m->msg_count; i++)
1374  {
1375  struct Email *e = m->emails[i];
1376  if (!e)
1377  break;
1378 
1379  if (e->vnum >= 0)
1380  {
1381  e->vnum = m->vcount;
1382  m->v2r[m->vcount] = i;
1383  m->vcount++;
1384  vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1385  }
1386  }
1387 
1388  return vsize;
1389 }
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1549
LOFF_T offset
offset where the actual data begins
Definition: body.h:52
LOFF_T length
length (in bytes) of attachment
Definition: body.h:53
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:80
struct Body * body
List of MIME parts.
Definition: email.h:67
int vcount
The number of virtual messages.
Definition: mailbox.h:99
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:98
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_traverse_thread()

int mutt_traverse_thread ( struct Email e_cur,
MuttThreadFlags  flag 
)

Recurse through an email thread, matching messages.

Parameters
e_curCurrent Email
flagFlag to set, see MuttThreadFlags
Return values
numNumber of matches

Definition at line 1397 of file mutt_thread.c.

1398 {
1399  struct MuttThread *thread = NULL, *top = NULL;
1400  struct Email *e_root = NULL;
1401  const enum UseThreads threaded = mutt_thread_style();
1402  int final, reverse = (threaded == UT_REVERSE), minmsgno;
1403  int num_hidden = 0, new_mail = 0, old_mail = 0;
1404  bool flagged = false;
1405  int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1406 
1407  if (threaded == UT_FLAT)
1408  {
1409  mutt_error(_("Threading is not enabled"));
1410  return e_cur->vnum;
1411  }
1412 
1413  if (!e_cur->thread)
1414  {
1415  return e_cur->vnum;
1416  }
1417 
1418  final = e_cur->vnum;
1419  thread = e_cur->thread;
1420  while (thread->parent)
1421  thread = thread->parent;
1422  top = thread;
1423  while (!thread->message)
1424  thread = thread->child;
1425  e_cur = thread->message;
1426  minmsgno = e_cur->msgno;
1427 
1428  if (!e_cur->read && e_cur->visible)
1429  {
1430  if (e_cur->old)
1431  old_mail = 2;
1432  else
1433  new_mail = 1;
1434  if (e_cur->msgno < min_unread_msgno)
1435  {
1436  min_unread = e_cur->vnum;
1437  min_unread_msgno = e_cur->msgno;
1438  }
1439  }
1440 
1441  if (e_cur->flagged && e_cur->visible)
1442  flagged = true;
1443 
1444  if ((e_cur->vnum == -1) && e_cur->visible)
1445  num_hidden++;
1446 
1448  {
1449  e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1450  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1451  if (e_cur->vnum != -1)
1452  {
1453  e_root = e_cur;
1454  if (flag & MUTT_THREAD_COLLAPSE)
1455  final = e_root->vnum;
1456  }
1457  }
1458 
1459  if ((thread == top) && !(thread = thread->child))
1460  {
1461  /* return value depends on action requested */
1463  {
1464  e_cur->num_hidden = num_hidden;
1465  return final;
1466  }
1467  if (flag & MUTT_THREAD_UNREAD)
1468  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1469  if (flag & MUTT_THREAD_NEXT_UNREAD)
1470  return min_unread;
1471  if (flag & MUTT_THREAD_FLAGGED)
1472  return flagged;
1473  }
1474 
1475  while (true)
1476  {
1477  e_cur = thread->message;
1478 
1479  if (e_cur)
1480  {
1482  {
1483  e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1484  e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1485  if (!e_root && e_cur->visible)
1486  {
1487  e_root = e_cur;
1488  if (flag & MUTT_THREAD_COLLAPSE)
1489  final = e_root->vnum;
1490  }
1491 
1492  if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1493  (e_cur->msgno < minmsgno) && e_cur->visible)
1494  {
1495  minmsgno = e_cur->msgno;
1496  final = e_cur->vnum;
1497  }
1498 
1499  if (flag & MUTT_THREAD_COLLAPSE)
1500  {
1501  if (e_cur != e_root)
1502  e_cur->vnum = -1;
1503  }
1504  else
1505  {
1506  if (e_cur->visible)
1507  e_cur->vnum = e_cur->msgno;
1508  }
1509  }
1510 
1511  if (!e_cur->read && e_cur->visible)
1512  {
1513  if (e_cur->old)
1514  old_mail = 2;
1515  else
1516  new_mail = 1;
1517  if (e_cur->msgno < min_unread_msgno)
1518  {
1519  min_unread = e_cur->vnum;
1520  min_unread_msgno = e_cur->msgno;
1521  }
1522  }
1523 
1524  if (e_cur->flagged && e_cur->visible)
1525  flagged = true;
1526 
1527  if ((e_cur->vnum == -1) && e_cur->visible)
1528  num_hidden++;
1529  }
1530 
1531  if (thread->child)
1532  thread = thread->child;
1533  else if (thread->next)
1534  thread = thread->next;
1535  else
1536  {
1537  bool done = false;
1538  while (!thread->next)
1539  {
1540  thread = thread->parent;
1541  if (thread == top)
1542  {
1543  done = true;
1544  break;
1545  }
1546  }
1547  if (done)
1548  break;
1549  thread = thread->next;
1550  }
1551  }
1552 
1553  /* re-traverse the thread and store num_hidden in all headers, with or
1554  * without a virtual index. this will allow ~v to match all collapsed
1555  * messages when switching sort order to non-threaded. */
1556  if (flag & MUTT_THREAD_COLLAPSE)
1557  {
1558  thread = top;
1559  while (true)
1560  {
1561  e_cur = thread->message;
1562  if (e_cur)
1563  e_cur->num_hidden = num_hidden + 1;
1564 
1565  if (thread->child)
1566  thread = thread->child;
1567  else if (thread->next)
1568  thread = thread->next;
1569  else
1570  {
1571  bool done = false;
1572  while (!thread->next)
1573  {
1574  thread = thread->parent;
1575  if (thread == top)
1576  {
1577  done = true;
1578  break;
1579  }
1580  }
1581  if (done)
1582  break;
1583  thread = thread->next;
1584  }
1585  }
1586  }
1587 
1588  /* return value depends on action requested */
1590  return final;
1591  if (flag & MUTT_THREAD_UNREAD)
1592  return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1593  if (flag & MUTT_THREAD_NEXT_UNREAD)
1594  return min_unread;
1595  if (flag & MUTT_THREAD_FLAGGED)
1596  return flagged;
1597 
1598  return 0;
1599 }
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:66
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:65
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:67
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:64
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:68
bool read
Email is read.
Definition: email.h:48
bool old
Email is seen, but unread.
Definition: email.h:47
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
Definition: email.h:122
struct AttrColor * attr_color
Color-pair to use when displaying in the index.
Definition: email.h:112
bool flagged
Marked important?
Definition: email.h:45
int msgno
Number displayed to the user.
Definition: email.h:111
+ Here is the call graph for this function:

◆ mutt_messages_in_thread()

int mutt_messages_in_thread ( struct Mailbox m,
struct Email e,
enum MessageInThread  mit 
)

Count the messages in a thread.

Parameters
mMailbox
eEmail
mitFlag, e.g. MIT_NUM_MESSAGES
Return values
numNumber of message / Our position

Definition at line 1608 of file mutt_thread.c.

1609 {
1610  if (!m || !e)
1611  return 1;
1612 
1613  struct MuttThread *threads[2];
1614  int rc;
1615 
1616  const enum UseThreads threaded = mutt_thread_style();
1617  if ((threaded == UT_FLAT) || !e->thread)
1618  return 1;
1619 
1620  threads[0] = e->thread;
1621  while (threads[0]->parent)
1622  threads[0] = threads[0]->parent;
1623 
1624  threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1625 
1626  for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1627  {
1628  while (!threads[i]->message)
1629  threads[i] = threads[i]->child;
1630  }
1631 
1632  if (threaded == UT_REVERSE)
1633  rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1634  else
1635  {
1636  rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1637  threads[0]->message->msgno;
1638  }
1639 
1640  if (mit == MIT_POSITION)
1641  rc += 1;
1642 
1643  return rc;
1644 }
@ MIT_POSITION
Our position in the thread.
Definition: mutt_thread.h:76
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_make_id_hash()

struct HashTable* mutt_make_id_hash ( struct Mailbox m)

Create a Hash Table for message-ids.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 1651 of file mutt_thread.c.

1652 {
1653  struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1654 
1655  for (int i = 0; i < m->msg_count; i++)
1656  {
1657  struct Email *e = m->emails[i];
1658  if (!e || !e->env)
1659  continue;
1660 
1661  if (e->env->message_id)
1662  mutt_hash_insert(hash, e->env->message_id, e);
1663  }
1664 
1665  return hash;
1666 }
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:109
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ link_threads()

static bool link_threads ( struct Email parent,
struct Email child,
struct Mailbox m 
)
static

Forcibly link messages together.

Parameters
parentParent Email
childChild Email
mMailbox
Return values
trueOn success

Definition at line 1675 of file mutt_thread.c.

1676 {
1677  if (child == parent)
1678  return false;
1679 
1680  mutt_break_thread(child);
1682  mutt_set_flag(m, child, MUTT_TAG, false);
1683 
1684  child->changed = true;
1685  child->env->changed |= MUTT_ENV_CHANGED_IRT;
1686  return true;
1687 }
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition: envelope.h:34
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:99
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:64
bool changed
Email has been edited.
Definition: email.h:75
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:92
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition: thread.c:233
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_link_threads()

bool mutt_link_threads ( struct Email parent,
struct EmailList *  children,
struct Mailbox m 
)

Forcibly link threads together.

Parameters
parentParent Email
childrenList of children Emails
mMailbox
Return values
trueOn success

Definition at line 1696 of file mutt_thread.c.

1697 {
1698  if (!parent || !children || !m)
1699  return false;
1700 
1701  bool changed = false;
1702 
1703  struct EmailNode *en = NULL;
1704  STAILQ_FOREACH(en, children, entries)
1705  {
1706  changed |= link_threads(parent, en->email, m);
1707  }
1708 
1709  return changed;
1710 }
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1675
List of Emails.
Definition: email.h:131
struct Email * email
Email in the list.
Definition: email.h:132
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_collapse_collapsed()

void mutt_thread_collapse_collapsed ( struct ThreadsContext tctx)

Re-collapse threads marked as collapsed.

Parameters
tctxThreading context

Definition at line 1716 of file mutt_thread.c.

1717 {
1718  struct MuttThread *thread = NULL;
1719  struct MuttThread *top = tctx->tree;
1720  while ((thread = top))
1721  {
1722  while (!thread->message)
1723  thread = thread->child;
1724 
1725  struct Email *e = thread->message;
1726  if (e->collapsed)
1728  top = top->next;
1729  }
1730 }
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:93

◆ mutt_thread_collapse()

void mutt_thread_collapse ( struct ThreadsContext tctx,
bool  collapse 
)

Toggle collapse.

Parameters
tctxThreading context
collapseCollapse / uncollapse

Definition at line 1737 of file mutt_thread.c.

1738 {
1739  struct MuttThread *thread = NULL;
1740  struct MuttThread *top = tctx->tree;
1741  while ((thread = top))
1742  {
1743  while (!thread->message)
1744  thread = thread->child;
1745 
1746  struct Email *e = thread->message;
1747 
1748  if (e->collapsed != collapse)
1749  {
1750  if (e->collapsed)
1752  else if (mutt_thread_can_collapse(e))
1754  }
1755  top = top->next;
1756  }
1757 }
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1765
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:94
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_can_collapse()

bool mutt_thread_can_collapse ( struct Email e)

Check whether a thread can be collapsed.

Parameters
eHead of the thread
Return values
trueCan be collapsed
falseCannot be collapsed

Definition at line 1765 of file mutt_thread.c.

1766 {
1767  const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1768  const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1769  return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1770  (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1771 }
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:96
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:95
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ UseThreadsMethods

const struct Mapping UseThreadsMethods[]
static
Initial value:
= {
{ "unset", UT_UNSET },
{ "flat", UT_FLAT },
{ "threads", UT_THREADS },
{ "reverse", UT_REVERSE },
{ "no", UT_FLAT },
{ "yes", UT_THREADS },
{ NULL, 0 },
}

Choices for '$use_threads' for the index.

Definition at line 1 of file mutt_thread.c.

◆ UseThreadsTypeDef

struct EnumDef UseThreadsTypeDef
Initial value:
= {
"use_threads_type",
4,
}
Mapping between user-readable string and a constant.
Definition: mapping.h:32

Definition at line 1 of file mutt_thread.c.