NeoMutt  2024-12-12-14-g7b49f7
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 <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 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 81 of file mutt_thread.c.

82{
83 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
84 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
85 if (c_use_threads > UT_FLAT)
86 return c_use_threads;
87 if ((c_sort & SORT_MASK) != EMAIL_SORT_THREADS)
88 return UT_FLAT;
89 if (c_sort & SORT_REVERSE)
90 return UT_REVERSE;
91 return UT_THREADS;
92}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:71
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:266
#define SORT_MASK
Mask for the sort id.
Definition: sort.h:38
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort.h:39
EmailSortType
Methods for sorting Emails.
Definition: sort.h:53
@ EMAIL_SORT_THREADS
Sort by email threads.
Definition: sort.h:62
@ 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
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
+ 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 99 of file mutt_thread.c.

100{
102}
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:52
+ 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 123 of file mutt_thread.c.

124{
125 return e->vnum >= 0 || (e->collapsed && e->visible);
126}
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 133 of file mutt_thread.c.

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

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

236{
237 if (!tree)
238 return;
239
240 struct MuttThread *tmp = NULL;
241 struct MuttThread *orig_tree = tree;
242 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
243 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
244 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
245 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
246 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
247 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
248 int depth = 0;
249
250 /* we walk each level backwards to make it easier to compute next_subtree_visible */
251 while (tree->next)
252 tree = tree->next;
253 *max_depth = 0;
254
255 while (true)
256 {
257 if (depth > *max_depth)
258 *max_depth = depth;
259
260 tree->subtree_visible = 0;
261 if (tree->message)
262 {
263 FREE(&tree->message->tree);
264 if (is_visible(tree->message))
265 {
266 tree->deep = true;
267 tree->visible = true;
269 for (tmp = tree; tmp; tmp = tmp->parent)
270 {
271 if (tmp->subtree_visible)
272 {
273 tmp->deep = true;
274 tmp->subtree_visible = 2;
275 break;
276 }
277 else
278 {
279 tmp->subtree_visible = 1;
280 }
281 }
282 }
283 else
284 {
285 tree->visible = false;
286 tree->deep = !c_hide_limited;
287 }
288 }
289 else
290 {
291 tree->visible = false;
292 tree->deep = !c_hide_missing;
293 }
294 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
295 tree->next->subtree_visible);
296 if (tree->child)
297 {
298 depth++;
299 tree = tree->child;
300 while (tree->next)
301 tree = tree->next;
302 }
303 else if (tree->prev)
304 {
305 tree = tree->prev;
306 }
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 {
333 tree = tree->child;
334 }
335 else if (tree->next)
336 {
337 tree = tree->next;
338 }
339 else
340 {
341 while (tree && !tree->next)
342 tree = tree->parent;
343 if (!tree)
344 break;
345 tree = tree->next;
346 }
347 }
348 }
349}
#define FREE(x)
Definition: memory.h:55
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition: mutt_thread.c:133
bool display_subject
Used for threading.
Definition: email.h:101
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 356 of file mutt_thread.c.

357{
358 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
359 tctx->mailbox_view = mv;
360 return tctx;
361}
#define MUTT_MEM_CALLOC(n, type)
Definition: memory.h:40
The "current" threading state.
Definition: mutt_thread.h:43
+ 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 367 of file mutt_thread.c.

368{
369 if (!ptr || !*ptr)
370 {
371 return;
372 }
373
374 struct ThreadsContext *tctx = *ptr;
375
376 mutt_hash_free(&tctx->hash);
377
378 FREE(ptr);
379}
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 393 of file mutt_thread.c.

394{
395 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
396 const bool reverse = (mutt_thread_style() == UT_REVERSE);
397 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
398 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
399 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
400 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
401 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
402
403 struct MuttThread *tree = tctx->tree;
404
405 /* Do the visibility calculations and free the old thread chars.
406 * From now on we can simply ignore invisible subtrees */
407 calculate_visibility(tree, &max_depth);
408 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
409 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
410 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
411 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
412 while (tree)
413 {
414 if (depth != 0)
415 {
416 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
417 if (start_depth == depth)
418 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
419 else if (parent->message && !c_hide_limited)
420 myarrow[0] = MUTT_TREE_HIDDEN;
421 else if (!parent->message && !c_hide_missing)
422 myarrow[0] = MUTT_TREE_MISSING;
423 else
424 myarrow[0] = vtee;
425 if (width == 2)
426 {
427 myarrow[1] = pseudo ? MUTT_TREE_STAR :
429 }
430 if (tree->visible)
431 {
432 myarrow[width] = MUTT_TREE_RARROW;
433 myarrow[width + 1] = 0;
434 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
435 if (start_depth > 1)
436 {
437 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
438 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
439 (1 + depth - start_depth) * width + 2);
440 }
441 else
442 {
443 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
444 }
445 tree->message->tree = new_tree;
446 }
447 }
448 if (tree->child && (depth != 0))
449 {
450 mypfx = pfx + (depth - 1) * width;
451 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
452 if (width == 2)
453 mypfx[1] = MUTT_TREE_SPACE;
454 }
455 parent = tree;
456 nextdisp = NULL;
457 pseudo = NULL;
458 do
459 {
460 if (tree->child && tree->subtree_visible)
461 {
462 if (tree->deep)
463 depth++;
464 if (tree->visible)
465 start_depth = depth;
466 tree = tree->child;
467
468 /* we do this here because we need to make sure that the first child thread
469 * of the old tree that we deal with is actually displayed if any are,
470 * or we might set the parent variable wrong while going through it. */
471 while (!tree->subtree_visible && tree->next)
472 tree = tree->next;
473 }
474 else
475 {
476 while (!tree->next && tree->parent)
477 {
478 if (tree == pseudo)
479 pseudo = NULL;
480 if (tree == nextdisp)
481 nextdisp = NULL;
482 if (tree->visible)
483 start_depth = depth;
484 tree = tree->parent;
485 if (tree->deep)
486 {
487 if (start_depth == depth)
488 start_depth--;
489 depth--;
490 }
491 }
492 if (tree == pseudo)
493 pseudo = NULL;
494 if (tree == nextdisp)
495 nextdisp = NULL;
496 if (tree->visible)
497 start_depth = depth;
498 tree = tree->next;
499 if (!tree)
500 break;
501 }
502 if (!pseudo && tree->fake_thread)
503 pseudo = tree;
504 if (!nextdisp && tree->next_subtree_visible)
505 nextdisp = tree;
506 } while (!tree->deep);
507 }
508
509 FREE(&pfx);
510 FREE(&arrow);
511}
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:41
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:581
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition: mutt_thread.c:235
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 523 of file mutt_thread.c.

524{
525 struct MuttThread *start = cur;
526 struct Envelope *env = NULL;
527 time_t thisdate;
528 int rc = 0;
529
530 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
531 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
532 while (true)
533 {
534 while (!cur->message)
535 cur = cur->child;
536
537 if (dateptr)
538 {
539 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
540 if ((*dateptr == 0) || (thisdate < *dateptr))
541 *dateptr = thisdate;
542 }
543
544 env = cur->message->env;
545 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
546 {
547 struct ListNode *np = NULL;
548 STAILQ_FOREACH(np, subjects, entries)
549 {
550 rc = mutt_str_cmp(env->real_subj, np->data);
551 if (rc >= 0)
552 break;
553 }
554 if (!np)
555 mutt_list_insert_head(subjects, env->real_subj);
556 else if (rc > 0)
557 mutt_list_insert_after(subjects, np, env->real_subj);
558 }
559
560 while (!cur->next && (cur != start))
561 {
562 cur = cur->parent;
563 }
564 if (cur == start)
565 break;
566 cur = cur->next;
567 }
568}
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:46
struct ListNode * mutt_list_insert_after(struct ListHead *h, struct ListNode *n, char *s)
Insert a string after a given ListNode.
Definition: list.c:85
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:399
#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:37
char * data
String.
Definition: list.h:38
+ 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 579 of file mutt_thread.c.

580{
581 if (!m)
582 return NULL;
583
584 struct HashElem *he = NULL;
585 struct MuttThread *tmp = NULL, *last = NULL;
586 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
587 time_t date = 0;
588
589 make_subject_list(&subjects, cur, &date);
590
591 struct ListNode *np = NULL;
592 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
593 STAILQ_FOREACH(np, &subjects, entries)
594 {
595 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
596 {
597 tmp = ((struct Email *) he->data)->thread;
598 if ((tmp != cur) && /* don't match the same message */
599 !tmp->fake_thread && /* don't match pseudo threads */
600 tmp->message->subject_changed && /* only match interesting replies */
601 !is_descendant(tmp, cur) && /* don't match in the same thread */
602 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
603 (!last || (c_thread_received ?
604 (last->message->received < tmp->message->received) :
605 (last->message->date_sent < tmp->message->date_sent))) &&
606 tmp->message->env->real_subj &&
608 {
609 last = tmp; /* best match so far */
610 }
611 }
612 }
613
614 mutt_list_clear(&subjects);
615 return last;
616}
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:166
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
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:523
#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 623 of file mutt_thread.c.

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

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

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

778{
779 struct MuttThread *thread = tctx->tree;
780 if (!thread)
781 return;
782
783 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
784 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
785 struct Email *oldsort_thread_key = NULL;
786 int i, array_size;
787 bool sort_top = false;
788
789 /* we put things into the array backwards to save some cycles,
790 * but we want to have to move less stuff around if we're
791 * resorting, so we sort backwards and then put them back
792 * in reverse order so they're forwards */
793 const bool reverse = (mutt_thread_style() == UT_REVERSE);
794 enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
795 enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
796 if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
797 {
798 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
799 c_sort = c_sort_aux;
800 }
801 c_sort ^= SORT_REVERSE;
802 c_sort_aux ^= SORT_REVERSE;
803 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
804 {
805 tctx->c_sort = c_sort;
806 tctx->c_sort_aux = c_sort_aux;
807 init = true;
808 }
809
810 top = thread;
811
812 array_size = 256;
813 array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
814 while (true)
815 {
816 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
817 {
818 thread->sort_thread_key = NULL;
819 thread->sort_aux_key = NULL;
820
821 if (thread->parent)
822 thread->parent->sort_children = true;
823 else
824 sort_top = true;
825 }
826
827 if (thread->child)
828 {
830 continue;
831 }
832 else
833 {
834 /* if it has no children, it must be real. sort it on its own merits */
837
838 if (thread->next)
839 {
840 thread = thread->next;
841 continue;
842 }
843 }
844
845 struct Mailbox *m = tctx->mailbox_view->mailbox;
846 const enum MailboxType mtype = mx_type(m);
847 while (!thread->next)
848 {
849 /* if it has siblings and needs to be sorted, sort it... */
850 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
851 {
852 /* put them into the array */
853 for (i = 0; thread; i++, thread = thread->prev)
854 {
855 if (i >= array_size)
856 {
857 array_size *= 2;
858 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
859 }
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, EMAIL_SORT_UNSORTED) > 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, mtype,
944 c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 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}
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort.h:40
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
@ EMAIL_SORT_UNSORTED
Sort by the order the messages appear in the mailbox.
Definition: sort.h:64
static int compare_threads(const void *a, const void *b, void *sdata)
Helper to sort email threads - Implements sort_t -.
Definition: mutt_thread.c:749
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:329
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1796
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 ASSERT(COND)
Definition: signal2.h:58
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 EmailSortType c_sort
Last sort method.
Definition: mutt_thread.h:47
enum EmailSortType c_sort_aux
Last sort_aux method.
Definition: mutt_thread.h:48
+ 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, 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, 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:72
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:182
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:777
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:393
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:648
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:1505
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:81
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:123
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:112
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 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 },
}
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:98

Choices for '$use_threads' for the index.

Definition at line 52 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 66 of file mutt_thread.c.