NeoMutt  2024-02-01-35-geee02f
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
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 "globals.h"
#include "mview.h"
#include "mx.h"
#include "protos.h"
#include "sort.h"
+ Include dependency graph for mutt_thread.c:

Go to the source code of this file.

Functions

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

Variables

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

Detailed Description

Create/manipulate threading in emails.

Authors
  • Peter Lewis
  • Richard Russon
  • Pietro Cerutti
  • Federico Kircheis
  • Eric Blake

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 83 of file mutt_thread.c.

84{
85 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
86 const enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
87 if (c_use_threads > UT_FLAT)
88 return c_use_threads;
89 if ((c_sort & SORT_MASK) != SORT_THREADS)
90 return UT_FLAT;
91 if (c_sort & SORT_REVERSE)
92 return UT_REVERSE;
93 return UT_THREADS;
94}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:72
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:267
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:99
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:100
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:101
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:70
SortType
Methods for sorting.
Definition: sort2.h:34
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:41
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:71
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
+ Here is the call graph for this function:
+ 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 101 of file mutt_thread.c.

102{
104}
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:54
+ 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 125 of file mutt_thread.c.

126{
127 return e->vnum >= 0 || (e->collapsed && e->visible);
128}
bool visible
Is this message part of the view?
Definition: email.h:124
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:123
int vnum
Virtual message number.
Definition: email.h:117
+ 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 135 of file mutt_thread.c.

136{
137 struct MuttThread *tmp = NULL;
138 struct MuttThread *tree = e->thread;
139
140 /* if the user disabled subject hiding, display it */
141 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
142 if (!c_hide_thread_subject)
143 return true;
144
145 /* if our subject is different from our parent's, display it */
146 if (e->subject_changed)
147 return true;
148
149 /* if our subject is different from that of our closest previously displayed
150 * sibling, display the subject */
151 for (tmp = tree->prev; tmp; tmp = tmp->prev)
152 {
153 e = tmp->message;
154 if (e && is_visible(e))
155 {
156 if (e->subject_changed)
157 return true;
158 break;
159 }
160 }
161
162 /* if there is a parent-to-child subject change anywhere between us and our
163 * closest displayed ancestor, display the subject */
164 for (tmp = tree->parent; tmp; tmp = tmp->parent)
165 {
166 e = tmp->message;
167 if (e)
168 {
169 if (is_visible(e))
170 return false;
171 if (e->subject_changed)
172 return true;
173 }
174 }
175
176 /* if we have no visible parent or previous sibling, display the subject */
177 return true;
178}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
static bool is_visible(struct Email *e)
Is the message visible?
Definition: mutt_thread.c:125
bool subject_changed
Used for threading.
Definition: email.h:109
struct MuttThread * thread
Thread of Emails.
Definition: email.h:122
An Email conversation.
Definition: thread.h:34
struct MuttThread * parent
Parent of this Thread.
Definition: thread.h:44
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:47
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 184 of file mutt_thread.c.

185{
186 if (!tctx || !tctx->mailbox_view)
187 return;
188
189 struct Mailbox *m = tctx->mailbox_view->mailbox;
190
191 const bool reverse = (mutt_thread_style() == UT_REVERSE);
192 struct MuttThread *tree = tctx->tree;
193 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
194
195 while (tree)
196 {
197 while (!tree->message)
198 tree = tree->child;
199
200 *array = tree->message;
201 array += reverse ? -1 : 1;
202
203 if (tree->child)
204 {
205 tree = tree->child;
206 }
207 else
208 {
209 while (tree)
210 {
211 if (tree->next)
212 {
213 tree = tree->next;
214 break;
215 }
216 else
217 {
218 tree = tree->parent;
219 }
220 }
221 }
222 }
223}
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition: mutt_thread.c:83
The envelope/body of an email.
Definition: email.h:39
char * tree
Character string to print thread tree.
Definition: email.h:128
struct Mailbox * mailbox
Current Mailbox.
Definition: mview.h:51
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 MailboxView * mailbox_view
Current mailbox.
Definition: mutt_thread.h:44
struct MuttThread * tree
Top of thread tree.
Definition: mutt_thread.h:45
+ 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 237 of file mutt_thread.c.

238{
239 if (!tree)
240 return;
241
242 struct MuttThread *tmp = NULL;
243 struct MuttThread *orig_tree = tree;
244 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
245 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
246 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
247 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
248 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
249 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
250 int depth = 0;
251
252 /* we walk each level backwards to make it easier to compute next_subtree_visible */
253 while (tree->next)
254 tree = tree->next;
255 *max_depth = 0;
256
257 while (true)
258 {
259 if (depth > *max_depth)
260 *max_depth = depth;
261
262 tree->subtree_visible = 0;
263 if (tree->message)
264 {
265 FREE(&tree->message->tree);
266 if (is_visible(tree->message))
267 {
268 tree->deep = true;
269 tree->visible = true;
271 for (tmp = tree; tmp; tmp = tmp->parent)
272 {
273 if (tmp->subtree_visible)
274 {
275 tmp->deep = true;
276 tmp->subtree_visible = 2;
277 break;
278 }
279 else
280 {
281 tmp->subtree_visible = 1;
282 }
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 {
307 tree = tree->prev;
308 }
309 else
310 {
311 while (tree && !tree->prev)
312 {
313 depth--;
314 tree = tree->parent;
315 }
316 if (!tree)
317 break;
318 tree = tree->prev;
319 }
320 }
321
322 /* now fix up for the OPTHIDETOP* options if necessary */
323 if (hide_top_limited || hide_top_missing)
324 {
325 tree = orig_tree;
326 while (true)
327 {
328 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
329 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
330 {
331 tree->deep = false;
332 }
333 if (!tree->deep && tree->child && tree->subtree_visible)
334 {
335 tree = tree->child;
336 }
337 else if (tree->next)
338 {
339 tree = tree->next;
340 }
341 else
342 {
343 while (tree && !tree->next)
344 tree = tree->parent;
345 if (!tree)
346 break;
347 tree = tree->next;
348 }
349 }
350 }
351}
#define FREE(x)
Definition: memory.h:45
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition: mutt_thread.c:135
bool display_subject
Used for threading.
Definition: email.h:104
bool visible
Is this Thread visible?
Definition: thread.h:42
struct MuttThread * child
Child of this Thread.
Definition: thread.h:45
bool deep
Is the Thread deeply nested?
Definition: thread.h:36
unsigned int subtree_visible
Is this Thread subtree visible?
Definition: thread.h:41
bool next_subtree_visible
Is the next Thread subtree visible?
Definition: thread.h:39
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:46
+ 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 MailboxView mv)

Initialize a threading context.

Parameters
mvMailbox view
Return values
ptrThreading context

Definition at line 358 of file mutt_thread.c.

359{
360 struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
361 tctx->mailbox_view = mv;
362 return tctx;
363}
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.h:43
+ 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 **  ptr)

Finalize a threading context.

Parameters
ptrThreading context to free

Definition at line 369 of file mutt_thread.c.

370{
371 if (!ptr || !*ptr)
372 {
373 return;
374 }
375
376 struct ThreadsContext *tctx = *ptr;
377
378 mutt_hash_free(&tctx->hash);
379
380 FREE(ptr);
381}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:457
struct HashTable * hash
Hash Table: "message-id" -> MuttThread.
Definition: mutt_thread.h:46
+ 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 395 of file mutt_thread.c.

396{
397 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
398 const bool reverse = (mutt_thread_style() == UT_REVERSE);
399 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
400 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
401 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
402 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
403 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
404
405 struct MuttThread *tree = tctx->tree;
406
407 /* Do the visibility calculations and free the old thread chars.
408 * From now on we can simply ignore invisible subtrees */
409 calculate_visibility(tree, &max_depth);
410 pfx = mutt_mem_malloc((width * max_depth) + 2);
411 arrow = mutt_mem_malloc((width * max_depth) + 2);
412 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
413 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
414 while (tree)
415 {
416 if (depth != 0)
417 {
418 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
419 if (start_depth == depth)
420 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
421 else if (parent->message && !c_hide_limited)
422 myarrow[0] = MUTT_TREE_HIDDEN;
423 else if (!parent->message && !c_hide_missing)
424 myarrow[0] = MUTT_TREE_MISSING;
425 else
426 myarrow[0] = vtee;
427 if (width == 2)
428 {
429 myarrow[1] = pseudo ? MUTT_TREE_STAR :
431 }
432 if (tree->visible)
433 {
434 myarrow[width] = MUTT_TREE_RARROW;
435 myarrow[width + 1] = 0;
436 new_tree = mutt_mem_malloc(((size_t) depth * width) + 2);
437 if (start_depth > 1)
438 {
439 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
440 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
441 (1 + depth - start_depth) * width + 2);
442 }
443 else
444 {
445 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
446 }
447 tree->message->tree = new_tree;
448 }
449 }
450 if (tree->child && (depth != 0))
451 {
452 mypfx = pfx + (depth - 1) * width;
453 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
454 if (width == 2)
455 mypfx[1] = MUTT_TREE_SPACE;
456 }
457 parent = tree;
458 nextdisp = NULL;
459 pseudo = NULL;
460 do
461 {
462 if (tree->child && tree->subtree_visible)
463 {
464 if (tree->deep)
465 depth++;
466 if (tree->visible)
467 start_depth = depth;
468 tree = tree->child;
469
470 /* we do this here because we need to make sure that the first child thread
471 * of the old tree that we deal with is actually displayed if any are,
472 * or we might set the parent variable wrong while going through it. */
473 while (!tree->subtree_visible && tree->next)
474 tree = tree->next;
475 }
476 else
477 {
478 while (!tree->next && tree->parent)
479 {
480 if (tree == pseudo)
481 pseudo = NULL;
482 if (tree == nextdisp)
483 nextdisp = NULL;
484 if (tree->visible)
485 start_depth = depth;
486 tree = tree->parent;
487 if (tree->deep)
488 {
489 if (start_depth == depth)
490 start_depth--;
491 depth--;
492 }
493 }
494 if (tree == pseudo)
495 pseudo = NULL;
496 if (tree == nextdisp)
497 nextdisp = NULL;
498 if (tree->visible)
499 start_depth = depth;
500 tree = tree->next;
501 if (!tree)
502 break;
503 }
504 if (!pseudo && tree->fake_thread)
505 pseudo = tree;
506 if (!nextdisp && tree->next_subtree_visible)
507 nextdisp = tree;
508 } while (!tree->deep);
509 }
510
511 FREE(&pfx);
512 FREE(&arrow);
513}
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:630
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:237
TreeChar
Tree characters for menus.
Definition: mutt_thread.h:57
@ MUTT_TREE_LLCORNER
Lower left corner.
Definition: mutt_thread.h:58
@ MUTT_TREE_RARROW
Right arrow.
Definition: mutt_thread.h:64
@ MUTT_TREE_ULCORNER
Upper left corner.
Definition: mutt_thread.h:59
@ MUTT_TREE_EQUALS
Equals (for threads)
Definition: mutt_thread.h:67
@ MUTT_TREE_HIDDEN
Ampersand character (for threads)
Definition: mutt_thread.h:66
@ MUTT_TREE_STAR
Star character (for threads)
Definition: mutt_thread.h:65
@ MUTT_TREE_LTEE
Left T-piece.
Definition: mutt_thread.h:60
@ MUTT_TREE_VLINE
Vertical line.
Definition: mutt_thread.h:62
@ MUTT_TREE_MISSING
Question mark.
Definition: mutt_thread.h:70
@ MUTT_TREE_TTEE
Top T-piece.
Definition: mutt_thread.h:68
@ MUTT_TREE_HLINE
Horizontal line.
Definition: mutt_thread.h:61
@ MUTT_TREE_SPACE
Blank space.
Definition: mutt_thread.h:63
@ MUTT_TREE_BTEE
Bottom T-piece.
Definition: mutt_thread.h:69
bool fake_thread
Emails grouped by Subject.
Definition: thread.h:38
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 525 of file mutt_thread.c.

526{
527 struct MuttThread *start = cur;
528 struct Envelope *env = NULL;
529 time_t thisdate;
530 int rc = 0;
531
532 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
533 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
534 while (true)
535 {
536 while (!cur->message)
537 cur = cur->child;
538
539 if (dateptr)
540 {
541 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
542 if ((*dateptr == 0) || (thisdate < *dateptr))
543 *dateptr = thisdate;
544 }
545
546 env = cur->message->env;
547 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
548 {
549 struct ListNode *np = NULL;
550 STAILQ_FOREACH(np, subjects, entries)
551 {
552 rc = mutt_str_cmp(env->real_subj, np->data);
553 if (rc >= 0)
554 break;
555 }
556 if (!np)
557 mutt_list_insert_head(subjects, env->real_subj);
558 else if (rc > 0)
559 mutt_list_insert_after(subjects, np, env->real_subj);
560 }
561
562 while (!cur->next && (cur != start))
563 {
564 cur = cur->parent;
565 }
566 if (cur == start)
567 break;
568 cur = cur->next;
569 }
570}
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:448
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
struct Envelope * env
Envelope information.
Definition: email.h:68
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:60
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:61
The header of an Email.
Definition: envelope.h:57
char *const subject
Email's subject.
Definition: envelope.h:70
char *const 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 581 of file mutt_thread.c.

582{
583 if (!m)
584 return NULL;
585
586 struct HashElem *he = NULL;
587 struct MuttThread *tmp = NULL, *last = NULL;
588 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
589 time_t date = 0;
590
591 make_subject_list(&subjects, cur, &date);
592
593 struct ListNode *np = NULL;
594 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
595 STAILQ_FOREACH(np, &subjects, entries)
596 {
597 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
598 {
599 tmp = ((struct Email *) he->data)->thread;
600 if ((tmp != cur) && /* don't match the same message */
601 !tmp->fake_thread && /* don't match pseudo threads */
602 tmp->message->subject_changed && /* only match interesting replies */
603 !is_descendant(tmp, cur) && /* don't match in the same thread */
604 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
605 (!last || (c_thread_received ?
606 (last->message->received < tmp->message->received) :
607 (last->message->date_sent < tmp->message->date_sent))) &&
608 tmp->message->env->real_subj &&
610 {
611 last = tmp; /* best match so far */
612 }
613 }
614 }
615
616 mutt_list_clear(&subjects);
617 return last;
618}
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:709
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:525
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
The item stored in a Hash Table.
Definition: hash.h:43
struct HashElem * next
Linked List.
Definition: hash.h:47
void * data
User-supplied data.
Definition: hash.h:46
struct HashTable * subj_hash
Hash Table: "subject" -> Email.
Definition: mailbox.h:124
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:46
+ 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 625 of file mutt_thread.c.

626{
627 if (!m)
628 return NULL;
629
631
632 for (int i = 0; i < m->msg_count; i++)
633 {
634 struct Email *e = m->emails[i];
635 if (!e || !e->env)
636 continue;
637 if (e->env->real_subj)
638 mutt_hash_insert(hash, e->env->real_subj, e);
639 }
640
641 return hash;
642}
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 650 of file mutt_thread.c.

651{
652 if (!tctx || !tctx->mailbox_view)
653 return;
654
655 struct Mailbox *m = tctx->mailbox_view->mailbox;
656
657 struct MuttThread *tree = tctx->tree;
658 struct MuttThread *top = tree;
659 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
660 *nextchild = NULL;
661
662 if (!m->subj_hash)
664
665 while (tree)
666 {
667 cur = tree;
668 tree = tree->next;
669 parent = find_subject(m, cur);
670 if (parent)
671 {
672 cur->fake_thread = true;
673 unlink_message(&top, cur);
675 parent->sort_children = true;
676 tmp = cur;
677 while (true)
678 {
679 while (!tmp->message)
680 tmp = tmp->child;
681
682 /* if the message we're attaching has pseudo-children, they
683 * need to be attached to its parent, so move them up a level.
684 * but only do this if they have the same real subject as the
685 * parent, since otherwise they rightly belong to the message
686 * we're attaching. */
687 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
689 {
690 tmp->message->subject_changed = false;
691
692 for (curchild = tmp->child; curchild;)
693 {
694 nextchild = curchild->next;
695 if (curchild->fake_thread)
696 {
697 unlink_message(&tmp->child, curchild);
698 insert_message(&parent->child, parent, curchild);
699 }
700 curchild = nextchild;
701 }
702 }
703
704 while (!tmp->next && (tmp != cur))
705 {
706 tmp = tmp->parent;
707 }
708 if (tmp == cur)
709 break;
710 tmp = tmp->next;
711 }
712 }
713 }
714 tctx->tree = top;
715}
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:625
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:581
bool sort_children
Sort the children.
Definition: thread.h:40
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition: thread.c:66
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
Insert a message into a thread.
Definition: thread.c:104
+ 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 721 of file mutt_thread.c.

722{
723 if (!tctx || !tctx->tree)
724 return;
725
726 struct MailboxView *mv = tctx->mailbox_view;
727 if (!mv)
728 return;
729
730 struct Mailbox *m = mv->mailbox;
731 if (!m || !m->emails)
732 return;
733
734 for (int i = 0; i < m->msg_count; i++)
735 {
736 struct Email *e = m->emails[i];
737 if (!e)
738 break;
739
740 /* mailbox may have been only partially read */
741 e->thread = NULL;
742 e->threaded = false;
743 }
744 tctx->tree = NULL;
745 mutt_hash_free(&tctx->hash);
746}
bool threaded
Used for threading.
Definition: email.h:111
View of a Mailbox.
Definition: mview.h:40
+ 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 779 of file mutt_thread.c.

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

◆ check_subjects()

static void check_subjects ( struct MailboxView mv,
bool  init 
)
static

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

Parameters
mvMailbox View
initIf true, rebuild the thread

Definition at line 980 of file mutt_thread.c.

981{
982 if (!mv)
983 return;
984
985 struct Mailbox *m = mv->mailbox;
986 for (int i = 0; i < m->msg_count; i++)
987 {
988 struct Email *e = m->emails[i];
989 if (!e || !e->thread)
990 continue;
991
992 if (e->thread->check_subject)
993 e->thread->check_subject = false;
994 else if (!init)
995 continue;
996
997 /* figure out which messages have subjects different than their parents' */
998 struct MuttThread *tmp = e->thread->parent;
999 while (tmp && !tmp->message)
1000 {
1001 tmp = tmp->parent;
1002 }
1003
1004 if (!tmp)
1005 {
1006 e->subject_changed = true;
1007 }
1008 else if (e->env->real_subj && tmp->message->env->real_subj)
1009 {
1011 }
1012 else
1013 {
1014 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1015 }
1016 }
1017}
bool check_subject
Should the Subject be checked?
Definition: thread.h:35
+ 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 1032 of file mutt_thread.c.

1033{
1034 if (!tctx || !tctx->mailbox_view)
1035 return;
1036
1037 struct MailboxView *mv = tctx->mailbox_view;
1038 struct Mailbox *m = mv->mailbox;
1039
1040 struct Email *e = NULL;
1041 int i, using_refs = 0;
1042 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1043 struct MuttThread top = { 0 };
1044 struct ListNode *ref = NULL;
1045
1046 assert(m->msg_count > 0);
1047 if (!tctx->hash)
1048 init = true;
1049
1050 if (init)
1051 {
1054 }
1055
1056 /* we want a quick way to see if things are actually attached to the top of the
1057 * thread tree or if they're just dangling, so we attach everything to a top
1058 * node temporarily */
1059 top.parent = NULL;
1060 top.next = NULL;
1061 top.prev = NULL;
1062
1063 top.child = tctx->tree;
1064 for (thread = tctx->tree; thread; thread = thread->next)
1065 thread->parent = &top;
1066
1067 /* put each new message together with the matching messageless MuttThread if it
1068 * exists. otherwise, if there is a MuttThread that already has a message, thread
1069 * new message as an identical child. if we didn't attach the message to a
1070 * MuttThread, make a new one for it. */
1071 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1072 for (i = 0; i < m->msg_count; i++)
1073 {
1074 e = m->emails[i];
1075 if (!e)
1076 continue;
1077
1078 if (e->thread)
1079 {
1080 /* unlink pseudo-threads because they might be children of newly
1081 * arrived messages */
1082 thread = e->thread;
1083 for (tnew = thread->child; tnew;)
1084 {
1085 tmp = tnew->next;
1086 if (tnew->fake_thread)
1087 {
1088 unlink_message(&thread->child, tnew);
1089 insert_message(&top.child, &top, tnew);
1090 tnew->fake_thread = false;
1091 }
1092 tnew = tmp;
1093 }
1094 }
1095 else
1096 {
1097 if ((!init || c_duplicate_threads) && e->env->message_id)
1098 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1099 else
1100 thread = NULL;
1101
1102 if (thread && !thread->message)
1103 {
1104 /* this is a message which was missing before */
1105 thread->message = e;
1106 e->thread = thread;
1107 thread->check_subject = true;
1108
1109 /* mark descendants as needing subject_changed checked */
1110 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1111 {
1112 while (!tmp->message)
1113 tmp = tmp->child;
1114 tmp->check_subject = true;
1115 while (!tmp->next && (tmp != thread))
1116 tmp = tmp->parent;
1117 if (tmp != thread)
1118 tmp = tmp->next;
1119 }
1120
1121 if (thread->parent)
1122 {
1123 /* remove threading info above it based on its children, which we'll
1124 * recalculate based on its headers. make sure not to leave
1125 * dangling missing messages. note that we haven't kept track
1126 * of what info came from its children and what from its siblings'
1127 * children, so we just remove the stuff that's definitely from it */
1128 do
1129 {
1130 tmp = thread->parent;
1131 unlink_message(&tmp->child, thread);
1132 thread->parent = NULL;
1133 thread->sort_thread_key = NULL;
1134 thread->sort_aux_key = NULL;
1135 thread->fake_thread = false;
1136 thread = tmp;
1137 } while (thread != &top && !thread->child && !thread->message);
1138 }
1139 }
1140 else
1141 {
1142 tnew = (c_duplicate_threads ? thread : NULL);
1143
1144 thread = mutt_mem_calloc(1, sizeof(struct MuttThread));
1145 thread->message = e;
1146 thread->check_subject = true;
1147 e->thread = thread;
1148 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1149
1150 if (tnew)
1151 {
1152 if (tnew->duplicate_thread)
1153 tnew = tnew->parent;
1154
1155 thread = e->thread;
1156
1157 insert_message(&tnew->child, tnew, thread);
1158 thread->duplicate_thread = true;
1159 thread->message->threaded = true;
1160 }
1161 }
1162 }
1163 }
1164
1165 /* thread by references */
1166 for (i = 0; i < m->msg_count; i++)
1167 {
1168 e = m->emails[i];
1169 if (!e)
1170 break;
1171
1172 if (e->threaded)
1173 continue;
1174 e->threaded = true;
1175
1176 thread = e->thread;
1177 if (!thread)
1178 continue;
1179 using_refs = 0;
1180
1181 while (true)
1182 {
1183 if (using_refs == 0)
1184 {
1185 /* look at the beginning of in-reply-to: */
1186 ref = STAILQ_FIRST(&e->env->in_reply_to);
1187 if (ref)
1188 {
1189 using_refs = 1;
1190 }
1191 else
1192 {
1193 ref = STAILQ_FIRST(&e->env->references);
1194 using_refs = 2;
1195 }
1196 }
1197 else if (using_refs == 1)
1198 {
1199 /* if there's no references header, use all the in-reply-to:
1200 * data that we have. otherwise, use the first reference
1201 * if it's different than the first in-reply-to, otherwise use
1202 * the second reference (since at least eudora puts the most
1203 * recent reference in in-reply-to and the rest in references) */
1204 if (STAILQ_EMPTY(&e->env->references))
1205 {
1206 ref = STAILQ_NEXT(ref, entries);
1207 }
1208 else
1209 {
1210 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1211 ref = STAILQ_FIRST(&e->env->references);
1212 else
1213 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1214
1215 using_refs = 2;
1216 }
1217 }
1218 else
1219 {
1220 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1221 }
1222
1223 if (!ref)
1224 break;
1225
1226 tnew = mutt_hash_find(tctx->hash, ref->data);
1227 if (tnew)
1228 {
1229 if (tnew->duplicate_thread)
1230 tnew = tnew->parent;
1231 if (is_descendant(tnew, thread)) /* no loops! */
1232 continue;
1233 }
1234 else
1235 {
1236 tnew = mutt_mem_calloc(1, sizeof(struct MuttThread));
1237 mutt_hash_insert(tctx->hash, ref->data, tnew);
1238 }
1239
1240 if (thread->parent)
1241 unlink_message(&top.child, thread);
1242 insert_message(&tnew->child, tnew, thread);
1243 thread = tnew;
1244 if (thread->message || (thread->parent && (thread->parent != &top)))
1245 break;
1246 }
1247
1248 if (!thread->parent)
1249 insert_message(&top.child, &top, thread);
1250 }
1251
1252 /* detach everything from the temporary top node */
1253 for (thread = top.child; thread; thread = thread->next)
1254 {
1255 thread->parent = NULL;
1256 }
1257 tctx->tree = top.child;
1258
1259 check_subjects(mv, init);
1260
1261 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1262 if (!c_strict_threads)
1263 pseudo_threads(tctx);
1264
1265 /* if $sort_aux or similar changed after the mailbox is sorted, then
1266 * all the subthreads need to be resorted */
1267 if (tctx->tree)
1268 {
1270 OptSortSubthreads = false;
1271
1272 /* Put the list into an array. */
1273 linearize_tree(tctx);
1274
1275 /* Draw the thread tree. */
1276 mutt_draw_tree(tctx);
1277 }
1278}
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition: globals.c:75
static void thread_hash_destructor(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition: mutt_thread.c:1022
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:184
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:779
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:395
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:650
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition: mutt_thread.c:980
#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:83
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:84
+ Here is the call graph for this function:
+ Here is the caller 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
-1Error

Definition at line 1288 of file mutt_thread.c.

1289{
1290 if (!e)
1291 return -1;
1292
1293 struct MuttThread *cur = NULL;
1294 struct Email *e_tmp = NULL;
1295
1296 const enum UseThreads threaded = mutt_thread_style();
1297 if (threaded == UT_FLAT)
1298 {
1299 mutt_error(_("Threading is not enabled"));
1300 return e->vnum;
1301 }
1302
1303 cur = e->thread;
1304
1305 if (subthreads)
1306 {
1307 if (forwards ^ (threaded == UT_REVERSE))
1308 {
1309 while (!cur->next && cur->parent)
1310 cur = cur->parent;
1311 }
1312 else
1313 {
1314 while (!cur->prev && cur->parent)
1315 cur = cur->parent;
1316 }
1317 }
1318 else
1319 {
1320 while (cur->parent)
1321 cur = cur->parent;
1322 }
1323
1324 if (forwards ^ (threaded == UT_REVERSE))
1325 {
1326 do
1327 {
1328 cur = cur->next;
1329 if (!cur)
1330 return -1;
1331 e_tmp = find_virtual(cur, false);
1332 } while (!e_tmp);
1333 }
1334 else
1335 {
1336 do
1337 {
1338 cur = cur->prev;
1339 if (!cur)
1340 return -1;
1341 e_tmp = find_virtual(cur, true);
1342 } while (!e_tmp);
1343 }
1344
1345 return e_tmp->vnum;
1346}
#define mutt_error(...)
Definition: logging2.h:92
#define _(a)
Definition: message.h:28
UseThreads
Which threading style is active, $use_threads.
Definition: mutt_thread.h:97
struct Email * find_virtual(struct MuttThread *cur, bool reverse)
Find an email with a Virtual message number.
Definition: thread.c:124
+ 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 1355 of file mutt_thread.c.

1356{
1357 if (!e)
1358 return -1;
1359
1360 struct MuttThread *thread = NULL;
1361 struct Email *e_parent = NULL;
1362
1363 if (!mutt_using_threads())
1364 {
1365 mutt_error(_("Threading is not enabled"));
1366 return e->vnum;
1367 }
1368
1369 /* Root may be the current message */
1370 if (find_root)
1371 e_parent = e;
1372
1373 for (thread = e->thread->parent; thread; thread = thread->parent)
1374 {
1375 e = thread->message;
1376 if (e)
1377 {
1378 e_parent = e;
1379 if (!find_root)
1380 break;
1381 }
1382 }
1383
1384 if (!e_parent)
1385 {
1386 mutt_error(_("Parent message is not available"));
1387 return -1;
1388 }
1389 if (!is_visible(e_parent))
1390 {
1391 if (find_root)
1392 mutt_error(_("Root message is not visible in this limited view"));
1393 else
1394 mutt_error(_("Parent message is not visible in this limited view"));
1395 return -1;
1396 }
1397 return e_parent->vnum;
1398}
#define mutt_using_threads()
Definition: mutt_thread.h:114
+ 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
numSize in bytes of all messages shown

Definition at line 1405 of file mutt_thread.c.

1406{
1407 if (!m)
1408 return 0;
1409
1410 off_t vsize = 0;
1411 const int padding = mx_msg_padding_size(m);
1412
1413 m->vcount = 0;
1414
1415 for (int i = 0; i < m->msg_count; i++)
1416 {
1417 struct Email *e = m->emails[i];
1418 if (!e)
1419 break;
1420
1421 if (e->vnum >= 0)
1422 {
1423 e->vnum = m->vcount;
1424 m->v2r[m->vcount] = i;
1425 m->vcount++;
1426 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1427 }
1428 }
1429
1430 return vsize;
1431}
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1503
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:69
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 1439 of file mutt_thread.c.

1440{
1441 struct MuttThread *thread = NULL, *top = NULL;
1442 struct Email *e_root = NULL;
1443 const enum UseThreads threaded = mutt_thread_style();
1444 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1445 int num_hidden = 0, new_mail = 0, old_mail = 0;
1446 bool flagged = false;
1447 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1448
1449 if (threaded == UT_FLAT)
1450 {
1451 mutt_error(_("Threading is not enabled"));
1452 return e_cur->vnum;
1453 }
1454
1455 if (!e_cur->thread)
1456 {
1457 return e_cur->vnum;
1458 }
1459
1460 final = e_cur->vnum;
1461 thread = e_cur->thread;
1462 while (thread->parent)
1463 thread = thread->parent;
1464 top = thread;
1465 while (!thread->message)
1466 thread = thread->child;
1467 e_cur = thread->message;
1468 minmsgno = e_cur->msgno;
1469
1470 if (!e_cur->read && e_cur->visible)
1471 {
1472 if (e_cur->old)
1473 old_mail = 2;
1474 else
1475 new_mail = 1;
1476 if (e_cur->msgno < min_unread_msgno)
1477 {
1478 min_unread = e_cur->vnum;
1479 min_unread_msgno = e_cur->msgno;
1480 }
1481 }
1482
1483 if (e_cur->flagged && e_cur->visible)
1484 flagged = true;
1485
1486 if ((e_cur->vnum == -1) && e_cur->visible)
1487 num_hidden++;
1488
1490 {
1491 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1492 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1493 if (e_cur->vnum != -1)
1494 {
1495 e_root = e_cur;
1496 if (flag & MUTT_THREAD_COLLAPSE)
1497 final = e_root->vnum;
1498 }
1499 }
1500
1501 if ((thread == top) && !(thread = thread->child))
1502 {
1503 /* return value depends on action requested */
1505 {
1506 e_cur->num_hidden = num_hidden;
1507 return final;
1508 }
1509 if (flag & MUTT_THREAD_UNREAD)
1510 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1511 if (flag & MUTT_THREAD_NEXT_UNREAD)
1512 return min_unread;
1513 if (flag & MUTT_THREAD_FLAGGED)
1514 return flagged;
1515 }
1516
1517 while (true)
1518 {
1519 e_cur = thread->message;
1520
1521 if (e_cur)
1522 {
1524 {
1525 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1526 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1527 if (!e_root && e_cur->visible)
1528 {
1529 e_root = e_cur;
1530 if (flag & MUTT_THREAD_COLLAPSE)
1531 final = e_root->vnum;
1532 }
1533
1534 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1535 (e_cur->msgno < minmsgno) && e_cur->visible)
1536 {
1537 minmsgno = e_cur->msgno;
1538 final = e_cur->vnum;
1539 }
1540
1541 if (flag & MUTT_THREAD_COLLAPSE)
1542 {
1543 if (e_cur != e_root)
1544 e_cur->vnum = -1;
1545 }
1546 else
1547 {
1548 if (e_cur->visible)
1549 e_cur->vnum = e_cur->msgno;
1550 }
1551 }
1552
1553 if (!e_cur->read && e_cur->visible)
1554 {
1555 if (e_cur->old)
1556 old_mail = 2;
1557 else
1558 new_mail = 1;
1559 if (e_cur->msgno < min_unread_msgno)
1560 {
1561 min_unread = e_cur->vnum;
1562 min_unread_msgno = e_cur->msgno;
1563 }
1564 }
1565
1566 if (e_cur->flagged && e_cur->visible)
1567 flagged = true;
1568
1569 if ((e_cur->vnum == -1) && e_cur->visible)
1570 num_hidden++;
1571 }
1572
1573 if (thread->child)
1574 {
1575 thread = thread->child;
1576 }
1577 else if (thread->next)
1578 {
1579 thread = thread->next;
1580 }
1581 else
1582 {
1583 bool done = false;
1584 while (!thread->next)
1585 {
1586 thread = thread->parent;
1587 if (thread == top)
1588 {
1589 done = true;
1590 break;
1591 }
1592 }
1593 if (done)
1594 break;
1595 thread = thread->next;
1596 }
1597 }
1598
1599 /* re-traverse the thread and store num_hidden in all headers, with or
1600 * without a virtual index. this will allow ~v to match all collapsed
1601 * messages when switching sort order to non-threaded. */
1602 if (flag & MUTT_THREAD_COLLAPSE)
1603 {
1604 thread = top;
1605 while (true)
1606 {
1607 e_cur = thread->message;
1608 if (e_cur)
1609 e_cur->num_hidden = num_hidden + 1;
1610
1611 if (thread->child)
1612 {
1613 thread = thread->child;
1614 }
1615 else if (thread->next)
1616 {
1617 thread = thread->next;
1618 }
1619 else
1620 {
1621 bool done = false;
1622 while (!thread->next)
1623 {
1624 thread = thread->parent;
1625 if (thread == top)
1626 {
1627 done = true;
1628 break;
1629 }
1630 }
1631 if (done)
1632 break;
1633 thread = thread->next;
1634 }
1635 }
1636 }
1637
1638 /* return value depends on action requested */
1640 return final;
1641 if (flag & MUTT_THREAD_UNREAD)
1642 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1643 if (flag & MUTT_THREAD_NEXT_UNREAD)
1644 return min_unread;
1645 if (flag & MUTT_THREAD_FLAGGED)
1646 return flagged;
1647
1648 return 0;
1649}
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:80
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:79
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:81
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:78
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:82
bool read
Email is read.
Definition: email.h:50
bool old
Email is seen, but unread.
Definition: email.h:49
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
Definition: email.h:126
bool flagged
Marked important?
Definition: email.h:47
const struct AttrColor * attr_color
Color-pair to use when displaying in the index.
Definition: email.h:115
int msgno
Number displayed to the user.
Definition: email.h:114
+ 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 1658 of file mutt_thread.c.

1659{
1660 if (!m || !e)
1661 return 1;
1662
1663 struct MuttThread *threads[2];
1664 int rc;
1665
1666 const enum UseThreads threaded = mutt_thread_style();
1667 if ((threaded == UT_FLAT) || !e->thread)
1668 return 1;
1669
1670 threads[0] = e->thread;
1671 while (threads[0]->parent)
1672 threads[0] = threads[0]->parent;
1673
1674 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1675
1676 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1677 {
1678 while (!threads[i]->message)
1679 threads[i] = threads[i]->child;
1680 }
1681
1682 if (threaded == UT_REVERSE)
1683 {
1684 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1685 }
1686 else
1687 {
1688 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1689 threads[0]->message->msgno;
1690 }
1691
1692 if (mit == MIT_POSITION)
1693 rc += 1;
1694
1695 return rc;
1696}
@ MIT_POSITION
Our position in the thread.
Definition: mutt_thread.h:90
+ 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 1703 of file mutt_thread.c.

1704{
1705 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1706
1707 for (int i = 0; i < m->msg_count; i++)
1708 {
1709 struct Email *e = m->emails[i];
1710 if (!e || !e->env)
1711 continue;
1712
1713 if (e->env->message_id)
1714 mutt_hash_insert(hash, e->env->message_id, e);
1715 }
1716
1717 return hash;
1718}
#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 1727 of file mutt_thread.c.

1728{
1729 if (child == parent)
1730 return false;
1731
1732 mutt_break_thread(child);
1734 mutt_set_flag(m, child, MUTT_TAG, false, true);
1735
1736 child->changed = true;
1737 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1738 return true;
1739}
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition: envelope.h:34
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition: flags.c:57
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:80
bool changed
Email has been edited.
Definition: email.h:77
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:90
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition: thread.c:229
+ 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 EmailArray *  children,
struct Mailbox m 
)

Forcibly link threads together.

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

Definition at line 1748 of file mutt_thread.c.

1749{
1750 if (!parent || !children || !m)
1751 return false;
1752
1753 bool changed = false;
1754
1755 struct Email **ep = NULL;
1756 ARRAY_FOREACH(ep, children)
1757 {
1758 struct Email *e = *ep;
1759 changed |= link_threads(parent, e, m);
1760 }
1761
1762 return changed;
1763}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:212
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1727
+ 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 1769 of file mutt_thread.c.

1770{
1771 struct MuttThread *thread = NULL;
1772 struct MuttThread *top = tctx->tree;
1773 while ((thread = top))
1774 {
1775 while (!thread->message)
1776 thread = thread->child;
1777
1778 struct Email *e = thread->message;
1779 if (e->collapsed)
1781 top = top->next;
1782 }
1783}
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:107
+ Here is the caller graph for this function:

◆ mutt_thread_collapse()

void mutt_thread_collapse ( struct ThreadsContext tctx,
bool  collapse 
)

Toggle collapse.

Parameters
tctxThreading context
collapseCollapse / uncollapse

Definition at line 1790 of file mutt_thread.c.

1791{
1792 struct MuttThread *thread = NULL;
1793 struct MuttThread *top = tctx->tree;
1794 while ((thread = top))
1795 {
1796 while (!thread->message)
1797 thread = thread->child;
1798
1799 struct Email *e = thread->message;
1800
1801 if (e->collapsed != collapse)
1802 {
1803 if (e->collapsed)
1805 else if (mutt_thread_can_collapse(e))
1807 }
1808 top = top->next;
1809 }
1810}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1818
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:108
+ 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 1818 of file mutt_thread.c.

1819{
1820 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1821 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1822 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1823 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1824}
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:110
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:109
+ 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 54 of file mutt_thread.c.

◆ UseThreadsTypeDef

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

Data for the $use_threads enumeration.

Definition at line 68 of file mutt_thread.c.