NeoMutt  2023-11-03-107-g582dc1
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mutt_thread.c File Reference

Create/manipulate threading in emails. More...

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

Go to the source code of this file.

Functions

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

Variables

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

Detailed Description

Create/manipulate threading in emails.

Authors
  • Michael R. Elkins

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

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

Definition in file mutt_thread.c.

Function Documentation

◆ mutt_thread_style()

enum UseThreads mutt_thread_style ( void  )

Which threading style is active?

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

Definition at line 79 of file mutt_thread.c.

80{
81 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
82 const enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
83 if (c_use_threads > UT_FLAT)
84 return c_use_threads;
85 if ((c_sort & SORT_MASK) != SORT_THREADS)
86 return UT_FLAT;
87 if (c_sort & SORT_REVERSE)
88 return UT_REVERSE;
89 return UT_THREADS;
90}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:72
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:267
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:97
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:98
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:99
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:74
SortType
Methods for sorting.
Definition: sort2.h:38
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:45
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:75
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_use_threads_str()

const char * get_use_threads_str ( enum UseThreads  value)

Convert UseThreads enum to string.

Parameters
valueValue to convert
Return values
ptrString form of value

Definition at line 97 of file mutt_thread.c.

98{
100}
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:50
+ 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 121 of file mutt_thread.c.

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

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

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

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

355{
356 struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
357 tctx->mailbox_view = mv;
358 return tctx;
359}
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
The "current" threading state.
Definition: mutt_thread.h:41
+ 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 365 of file mutt_thread.c.

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

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

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

◆ find_subject()

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

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

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

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

Definition at line 577 of file mutt_thread.c.

578{
579 if (!m)
580 return NULL;
581
582 struct HashElem *he = NULL;
583 struct MuttThread *tmp = NULL, *last = NULL;
584 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
585 time_t date = 0;
586
587 make_subject_list(&subjects, cur, &date);
588
589 struct ListNode *np = NULL;
590 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
591 STAILQ_FOREACH(np, &subjects, entries)
592 {
593 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
594 {
595 tmp = ((struct Email *) he->data)->thread;
596 if ((tmp != cur) && /* don't match the same message */
597 !tmp->fake_thread && /* don't match pseudo threads */
598 tmp->message->subject_changed && /* only match interesting replies */
599 !is_descendant(tmp, cur) && /* don't match in the same thread */
600 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
601 (!last || (c_thread_received ?
602 (last->message->received < tmp->message->received) :
603 (last->message->date_sent < tmp->message->date_sent))) &&
604 tmp->message->env->real_subj &&
606 {
607 last = tmp; /* best match so far */
608 }
609 }
610 }
611
612 mutt_list_clear(&subjects);
613 return last;
614}
struct HashElem * mutt_hash_find_bucket(const struct HashTable *table, const char *strkey)
Find the HashElem in a Hash Table element using a key.
Definition: hash.c:409
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition: list.c:167
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:763
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:521
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
The item stored in a Hash Table.
Definition: hash.h:44
struct HashElem * next
Linked List.
Definition: hash.h:48
void * data
User-supplied data.
Definition: hash.h:47
struct HashTable * subj_hash
Hash Table: "subject" -> Email.
Definition: mailbox.h:123
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:43
+ 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 621 of file mutt_thread.c.

622{
623 if (!m)
624 return NULL;
625
627
628 for (int i = 0; i < m->msg_count; i++)
629 {
630 struct Email *e = m->emails[i];
631 if (!e || !e->env)
632 continue;
633 if (e->env->real_subj)
634 mutt_hash_insert(hash, e->env->real_subj, e);
635 }
636
637 return hash;
638}
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:113
A Hash Table.
Definition: hash.h:98
+ 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 646 of file mutt_thread.c.

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

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

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

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

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

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

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

1402{
1403 if (!m)
1404 return 0;
1405
1406 off_t vsize = 0;
1407 const int padding = mx_msg_padding_size(m);
1408
1409 m->vcount = 0;
1410
1411 for (int i = 0; i < m->msg_count; i++)
1412 {
1413 struct Email *e = m->emails[i];
1414 if (!e)
1415 break;
1416
1417 if (e->vnum >= 0)
1418 {
1419 e->vnum = m->vcount;
1420 m->v2r[m->vcount] = i;
1421 m->vcount++;
1422 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1423 }
1424 }
1425
1426 return vsize;
1427}
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1510
LOFF_T offset
offset where the actual data begins
Definition: body.h:52
LOFF_T length
length (in bytes) of attachment
Definition: body.h:53
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:80
struct Body * body
List of MIME parts.
Definition: email.h:67
int vcount
The number of virtual messages.
Definition: mailbox.h:99
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:98
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_traverse_thread()

int mutt_traverse_thread ( struct Email e_cur,
MuttThreadFlags  flag 
)

Recurse through an email thread, matching messages.

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

Definition at line 1435 of file mutt_thread.c.

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

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

1700{
1701 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1702
1703 for (int i = 0; i < m->msg_count; i++)
1704 {
1705 struct Email *e = m->emails[i];
1706 if (!e || !e->env)
1707 continue;
1708
1709 if (e->env->message_id)
1710 mutt_hash_insert(hash, e->env->message_id, e);
1711 }
1712
1713 return hash;
1714}
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:110
+ 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 1723 of file mutt_thread.c.

1724{
1725 if (child == parent)
1726 return false;
1727
1728 mutt_break_thread(child);
1730 mutt_set_flag(m, child, MUTT_TAG, false, true);
1731
1732 child->changed = true;
1733 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1734 return true;
1735}
#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:53
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:79
bool changed
Email has been edited.
Definition: email.h:75
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:226
+ 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 1744 of file mutt_thread.c.

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

1766{
1767 struct MuttThread *thread = NULL;
1768 struct MuttThread *top = tctx->tree;
1769 while ((thread = top))
1770 {
1771 while (!thread->message)
1772 thread = thread->child;
1773
1774 struct Email *e = thread->message;
1775 if (e->collapsed)
1777 top = top->next;
1778 }
1779}
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:105
+ 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 1786 of file mutt_thread.c.

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

1815{
1816 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1817 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1818 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1819 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1820}
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:108
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:107
+ 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 50 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:32

Data for the $use_threads enumeration.

Definition at line 64 of file mutt_thread.c.