NeoMutt  2024-04-25-89-g194907
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 "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 82 of file mutt_thread.c.

83{
84 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
85 const enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
86 if (c_use_threads > UT_FLAT)
87 return c_use_threads;
88 if ((c_sort & SORT_MASK) != SORT_THREADS)
89 return UT_FLAT;
90 if (c_sort & SORT_REVERSE)
91 return UT_REVERSE;
92 return UT_THREADS;
93}
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
@ 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: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 100 of file mutt_thread.c.

101{
103}
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:53
+ 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 124 of file mutt_thread.c.

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

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

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

237{
238 if (!tree)
239 return;
240
241 struct MuttThread *tmp = NULL;
242 struct MuttThread *orig_tree = tree;
243 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
244 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
245 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
246 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
247 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
248 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
249 int depth = 0;
250
251 /* we walk each level backwards to make it easier to compute next_subtree_visible */
252 while (tree->next)
253 tree = tree->next;
254 *max_depth = 0;
255
256 while (true)
257 {
258 if (depth > *max_depth)
259 *max_depth = depth;
260
261 tree->subtree_visible = 0;
262 if (tree->message)
263 {
264 FREE(&tree->message->tree);
265 if (is_visible(tree->message))
266 {
267 tree->deep = true;
268 tree->visible = true;
270 for (tmp = tree; tmp; tmp = tmp->parent)
271 {
272 if (tmp->subtree_visible)
273 {
274 tmp->deep = true;
275 tmp->subtree_visible = 2;
276 break;
277 }
278 else
279 {
280 tmp->subtree_visible = 1;
281 }
282 }
283 }
284 else
285 {
286 tree->visible = false;
287 tree->deep = !c_hide_limited;
288 }
289 }
290 else
291 {
292 tree->visible = false;
293 tree->deep = !c_hide_missing;
294 }
295 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
296 tree->next->subtree_visible);
297 if (tree->child)
298 {
299 depth++;
300 tree = tree->child;
301 while (tree->next)
302 tree = tree->next;
303 }
304 else if (tree->prev)
305 {
306 tree = tree->prev;
307 }
308 else
309 {
310 while (tree && !tree->prev)
311 {
312 depth--;
313 tree = tree->parent;
314 }
315 if (!tree)
316 break;
317 tree = tree->prev;
318 }
319 }
320
321 /* now fix up for the OPTHIDETOP* options if necessary */
322 if (hide_top_limited || hide_top_missing)
323 {
324 tree = orig_tree;
325 while (true)
326 {
327 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
328 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
329 {
330 tree->deep = false;
331 }
332 if (!tree->deep && tree->child && tree->subtree_visible)
333 {
334 tree = tree->child;
335 }
336 else if (tree->next)
337 {
338 tree = tree->next;
339 }
340 else
341 {
342 while (tree && !tree->next)
343 tree = tree->parent;
344 if (!tree)
345 break;
346 tree = tree->next;
347 }
348 }
349 }
350}
#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:134
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 357 of file mutt_thread.c.

358{
359 struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
360 tctx->mailbox_view = mv;
361 return tctx;
362}
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:51
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 368 of file mutt_thread.c.

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

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

525{
526 struct MuttThread *start = cur;
527 struct Envelope *env = NULL;
528 time_t thisdate;
529 int rc = 0;
530
531 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
532 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
533 while (true)
534 {
535 while (!cur->message)
536 cur = cur->child;
537
538 if (dateptr)
539 {
540 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
541 if ((*dateptr == 0) || (thisdate < *dateptr))
542 *dateptr = thisdate;
543 }
544
545 env = cur->message->env;
546 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
547 {
548 struct ListNode *np = NULL;
549 STAILQ_FOREACH(np, subjects, entries)
550 {
551 rc = mutt_str_cmp(env->real_subj, np->data);
552 if (rc >= 0)
553 break;
554 }
555 if (!np)
556 mutt_list_insert_head(subjects, env->real_subj);
557 else if (rc > 0)
558 mutt_list_insert_after(subjects, np, env->real_subj);
559 }
560
561 while (!cur->next && (cur != start))
562 {
563 cur = cur->parent;
564 }
565 if (cur == start)
566 break;
567 cur = cur->next;
568 }
569}
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 580 of file mutt_thread.c.

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

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

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

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

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

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

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

1288{
1289 if (!e)
1290 return -1;
1291
1292 struct MuttThread *cur = NULL;
1293 struct Email *e_tmp = NULL;
1294
1295 const enum UseThreads threaded = mutt_thread_style();
1296 if (threaded == UT_FLAT)
1297 {
1298 mutt_error(_("Threading is not enabled"));
1299 return e->vnum;
1300 }
1301
1302 cur = e->thread;
1303
1304 if (subthreads)
1305 {
1306 if (forwards ^ (threaded == UT_REVERSE))
1307 {
1308 while (!cur->next && cur->parent)
1309 cur = cur->parent;
1310 }
1311 else
1312 {
1313 while (!cur->prev && cur->parent)
1314 cur = cur->parent;
1315 }
1316 }
1317 else
1318 {
1319 while (cur->parent)
1320 cur = cur->parent;
1321 }
1322
1323 if (forwards ^ (threaded == UT_REVERSE))
1324 {
1325 do
1326 {
1327 cur = cur->next;
1328 if (!cur)
1329 return -1;
1330 e_tmp = find_virtual(cur, false);
1331 } while (!e_tmp);
1332 }
1333 else
1334 {
1335 do
1336 {
1337 cur = cur->prev;
1338 if (!cur)
1339 return -1;
1340 e_tmp = find_virtual(cur, true);
1341 } while (!e_tmp);
1342 }
1343
1344 return e_tmp->vnum;
1345}
#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 1354 of file mutt_thread.c.

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

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

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

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

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

1727{
1728 if (child == parent)
1729 return false;
1730
1731 mutt_break_thread(child);
1733 mutt_set_flag(m, child, MUTT_TAG, false, true);
1734
1735 child->changed = true;
1736 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1737 return true;
1738}
#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 1747 of file mutt_thread.c.

1748{
1749 if (!parent || !children || !m)
1750 return false;
1751
1752 bool changed = false;
1753
1754 struct Email **ep = NULL;
1755 ARRAY_FOREACH(ep, children)
1756 {
1757 struct Email *e = *ep;
1758 changed |= link_threads(parent, e, m);
1759 }
1760
1761 return changed;
1762}
#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:1726
+ 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 1768 of file mutt_thread.c.

1769{
1770 struct MuttThread *thread = NULL;
1771 struct MuttThread *top = tctx->tree;
1772 while ((thread = top))
1773 {
1774 while (!thread->message)
1775 thread = thread->child;
1776
1777 struct Email *e = thread->message;
1778 if (e->collapsed)
1780 top = top->next;
1781 }
1782}
#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 1789 of file mutt_thread.c.

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

1818{
1819 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1820 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1821 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1822 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1823}
#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 53 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 67 of file mutt_thread.c.