NeoMutt  2024-11-14-34-g5aaf0d
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: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 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: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 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: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 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:55
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: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 357 of file mutt_thread.c.

358{
359 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
360 tctx->mailbox_view = mv;
361 return tctx;
362}
#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 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, char);
410 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
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, char);
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}
#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: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: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 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, 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 {
859 array_size *= 2;
860 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
861 }
862
863 array[i] = thread;
864 }
865
866 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
867
868 /* attach them back together. make thread the last sibling. */
869 thread = array[0];
870 thread->next = NULL;
871 array[i - 1]->prev = NULL;
872
873 if (thread->parent)
874 thread->parent->child = array[i - 1];
875 else
876 top = array[i - 1];
877
878 while (--i)
879 {
880 array[i - 1]->prev = array[i];
881 array[i]->next = array[i - 1];
882 }
883 }
884
885 if (thread->parent)
886 {
887 tmp = thread;
888 thread = thread->parent;
889
890 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
891 {
892 /* we just sorted its children */
893 thread->sort_children = false;
894
895 oldsort_aux_key = thread->sort_aux_key;
896 oldsort_thread_key = thread->sort_thread_key;
897
898 /* update sort keys. sort_aux_key will be the first or last
899 * sibling, as appropriate... */
900 thread->sort_aux_key = thread->message;
901 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
902 thread->child->sort_aux_key :
903 tmp->sort_aux_key;
904
905 if (c_sort_aux & SORT_LAST)
906 {
907 if (!thread->sort_aux_key ||
908 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
909 c_sort_aux | SORT_REVERSE, SORT_ORDER) > 0))
910 {
911 thread->sort_aux_key = sort_aux_key;
912 }
913 }
914 else if (!thread->sort_aux_key)
915 {
916 thread->sort_aux_key = sort_aux_key;
917 }
918
919 /* ...but sort_thread_key may require searching the entire
920 * list of siblings */
921 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
922 {
923 thread->sort_thread_key = thread->sort_aux_key;
924 }
925 else
926 {
927 if (thread->message)
928 {
929 thread->sort_thread_key = thread->message;
930 }
931 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
932 {
933 thread->sort_thread_key = tmp->sort_thread_key;
934 }
935 else
936 {
937 thread->sort_thread_key = thread->child->sort_thread_key;
938 }
939 if (c_sort & SORT_LAST)
940 {
941 for (tmp = thread->child; tmp; tmp = tmp->next)
942 {
943 if (tmp->sort_thread_key == thread->sort_thread_key)
944 continue;
945 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key,
946 mtype, c_sort | SORT_REVERSE, SORT_ORDER) > 0))
947 {
948 thread->sort_thread_key = tmp->sort_thread_key;
949 }
950 }
951 }
952 }
953
954 /* if a sort_key has changed, we need to resort it and siblings */
955 if ((oldsort_aux_key != thread->sort_aux_key) ||
956 (oldsort_thread_key != thread->sort_thread_key))
957 {
958 if (thread->parent)
959 thread->parent->sort_children = true;
960 else
961 sort_top = true;
962 }
963 }
964 }
965 else
966 {
967 FREE(&array);
968 tctx->tree = top;
969 return;
970 }
971 }
972
973 thread = thread->next;
974 }
975}
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
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
@ 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 982 of file mutt_thread.c.

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

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

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

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

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

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

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

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

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

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

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

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

1821{
1822 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1823 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1824 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1825 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1826}
#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.