NeoMutt  2025-01-09-117-gace867
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mutt_thread.c File Reference

Create/manipulate threading in emails. More...

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

Go to the source code of this file.

Functions

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

Variables

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

Detailed Description

Create/manipulate threading in emails.

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

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

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

Definition in file mutt_thread.c.

Function Documentation

◆ mutt_thread_style()

enum UseThreads mutt_thread_style ( void  )

Which threading style is active?

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

Definition at line 81 of file mutt_thread.c.

82{
83 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
84 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
85 if (c_use_threads > UT_FLAT)
86 return c_use_threads;
87 if ((c_sort & SORT_MASK) != EMAIL_SORT_THREADS)
88 return UT_FLAT;
89 if (c_sort & SORT_REVERSE)
90 return UT_REVERSE;
91 return UT_THREADS;
92}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:71
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:266
#define SORT_MASK
Mask for the sort id.
Definition: sort.h:38
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort.h:39
EmailSortType
Methods for sorting Emails.
Definition: sort.h:53
@ EMAIL_SORT_THREADS
Sort by email threads.
Definition: sort.h:62
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:98
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:99
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:100
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_use_threads_str()

const char * get_use_threads_str ( enum UseThreads  value)

Convert UseThreads enum to string.

Parameters
valueValue to convert
Return values
ptrString form of value

Definition at line 99 of file mutt_thread.c.

100{
102}
const char * mutt_map_get_name(int val, const struct Mapping *map)
Lookup a string for a constant.
Definition: mapping.c:42
static const struct Mapping UseThreadsMethods[]
Choices for '$use_threads' for the index.
Definition: mutt_thread.c:52
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ is_visible()

static bool is_visible ( struct Email e)
static

Is the message visible?

Parameters
eEmail
Return values
trueThe message is not hidden in some way

Definition at line 122 of file mutt_thread.c.

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

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

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

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

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

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

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

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

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

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

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

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

777{
778 struct MuttThread *thread = tctx->tree;
779 if (!thread)
780 return;
781
782 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
783 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
784 struct Email *oldsort_thread_key = NULL;
785 int i, array_size;
786 bool sort_top = false;
787
788 /* we put things into the array backwards to save some cycles,
789 * but we want to have to move less stuff around if we're
790 * resorting, so we sort backwards and then put them back
791 * in reverse order so they're forwards */
792 const bool reverse = (mutt_thread_style() == UT_REVERSE);
793 enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
794 enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
795 if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
796 {
797 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
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, 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 {
856 array_size *= 2;
857 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
858 }
859
860 array[i] = thread;
861 }
862
863 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
864
865 /* attach them back together. make thread the last sibling. */
866 thread = array[0];
867 thread->next = NULL;
868 array[i - 1]->prev = NULL;
869
870 if (thread->parent)
871 thread->parent->child = array[i - 1];
872 else
873 top = array[i - 1];
874
875 while (--i)
876 {
877 array[i - 1]->prev = array[i];
878 array[i]->next = array[i - 1];
879 }
880 }
881
882 if (thread->parent)
883 {
884 tmp = thread;
885 thread = thread->parent;
886
887 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
888 {
889 /* we just sorted its children */
890 thread->sort_children = false;
891
892 oldsort_aux_key = thread->sort_aux_key;
893 oldsort_thread_key = thread->sort_thread_key;
894
895 /* update sort keys. sort_aux_key will be the first or last
896 * sibling, as appropriate... */
897 thread->sort_aux_key = thread->message;
898 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
899 thread->child->sort_aux_key :
900 tmp->sort_aux_key;
901
902 if (c_sort_aux & SORT_LAST)
903 {
904 if (!thread->sort_aux_key ||
905 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
906 c_sort_aux | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
907 {
908 thread->sort_aux_key = sort_aux_key;
909 }
910 }
911 else if (!thread->sort_aux_key)
912 {
913 thread->sort_aux_key = sort_aux_key;
914 }
915
916 /* ...but sort_thread_key may require searching the entire
917 * list of siblings */
918 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
919 {
920 thread->sort_thread_key = thread->sort_aux_key;
921 }
922 else
923 {
924 if (thread->message)
925 {
926 thread->sort_thread_key = thread->message;
927 }
928 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
929 {
930 thread->sort_thread_key = tmp->sort_thread_key;
931 }
932 else
933 {
934 thread->sort_thread_key = thread->child->sort_thread_key;
935 }
936 if (c_sort & SORT_LAST)
937 {
938 for (tmp = thread->child; tmp; tmp = tmp->next)
939 {
940 if (tmp->sort_thread_key == thread->sort_thread_key)
941 continue;
942 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key, mtype,
943 c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
944 {
945 thread->sort_thread_key = tmp->sort_thread_key;
946 }
947 }
948 }
949 }
950
951 /* if a sort_key has changed, we need to resort it and siblings */
952 if ((oldsort_aux_key != thread->sort_aux_key) ||
953 (oldsort_thread_key != thread->sort_thread_key))
954 {
955 if (thread->parent)
956 thread->parent->sort_children = true;
957 else
958 sort_top = true;
959 }
960 }
961 }
962 else
963 {
964 FREE(&array);
965 tctx->tree = top;
966 return;
967 }
968 }
969
970 thread = thread->next;
971 }
972}
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort.h:40
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
@ EMAIL_SORT_UNSORTED
Sort by the order the messages appear in the mailbox.
Definition: sort.h:64
static int compare_threads(const void *a, const void *b, void *sdata)
Helper to sort email threads - Implements sort_t -.
Definition: mutt_thread.c:748
int mutt_compare_emails(const struct Email *a, const struct Email *b, enum MailboxType type, short sort, short sort_aux)
Compare two emails using up to two sort methods -.
Definition: sort.c:329
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c: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:67
#define ASSERT(COND)
Definition: signal2.h:60
struct Email * sort_aux_key
Email that controls how subthread siblings sort.
Definition: thread.h:51
struct Email * sort_thread_key
Email that controls how top thread sorts.
Definition: thread.h:50
enum EmailSortType c_sort
Last sort method.
Definition: mutt_thread.h:46
enum EmailSortType c_sort_aux
Last sort_aux method.
Definition: mutt_thread.h:47
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ check_subjects()

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

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

Parameters
mvMailbox View
initIf true, rebuild the thread

Definition at line 979 of file mutt_thread.c.

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

◆ mutt_sort_threads()

void mutt_sort_threads ( struct ThreadsContext tctx,
bool  init 
)

Sort email threads.

Parameters
tctxThreading context
initIf true, rebuild the thread

Definition at line 1031 of file mutt_thread.c.

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

◆ mutt_aside_thread()

int mutt_aside_thread ( struct Email e,
bool  forwards,
bool  subthreads 
)

Find the next/previous (sub)thread.

Parameters
eSearch from this Email
forwardsDirection to search: 'true' forwards, 'false' backwards
subthreadsSearch subthreads: 'true' subthread, 'false' not
Return values
numIndex into the virtual email table
-1Error

Definition at line 1287 of file mutt_thread.c.

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

◆ mutt_parent_message()

int mutt_parent_message ( struct Email e,
bool  find_root 
)

Find the parent of a message.

Parameters
eCurrent Email
find_rootIf true, find the root message
Return values
>=0Virtual index number of parent/root message
-1Error

Definition at line 1354 of file mutt_thread.c.

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

◆ mutt_set_vnum()

off_t mutt_set_vnum ( struct Mailbox m)

Set the virtual index number of all the messages in a mailbox.

Parameters
mMailbox
Return values
numSize in bytes of all messages shown

Definition at line 1404 of file mutt_thread.c.

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

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

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

◆ mutt_make_id_hash()

struct HashTable * mutt_make_id_hash ( struct Mailbox m)

Create a Hash Table for message-ids.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 1702 of file mutt_thread.c.

1703{
1704 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1705
1706 for (int i = 0; i < m->msg_count; i++)
1707 {
1708 struct Email *e = m->emails[i];
1709 if (!e || !e->env)
1710 continue;
1711
1712 if (e->env->message_id)
1713 mutt_hash_insert(hash, e->env->message_id, e);
1714 }
1715
1716 return hash;
1717}
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:111
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ link_threads()

static bool link_threads ( struct Email parent,
struct Email child,
struct Mailbox m 
)
static

Forcibly link messages together.

Parameters
parentParent Email
childChild Email
mMailbox
Return values
trueOn success

Definition at line 1726 of file mutt_thread.c.

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

◆ mutt_link_threads()

bool mutt_link_threads ( struct Email parent,
struct EmailArray *  children,
struct Mailbox m 
)

Forcibly link threads together.

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

Definition at line 1747 of file mutt_thread.c.

1748{
1749 if (!parent || !children || !m)
1750 return false;
1751
1752 bool changed = false;
1753
1754 struct Email **ep = NULL;
1755 ARRAY_FOREACH(ep, children)
1756 {
1757 struct Email *e = *ep;
1758 changed |= link_threads(parent, e, m);
1759 }
1760
1761 return changed;
1762}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:214
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1726
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_collapse_collapsed()

void mutt_thread_collapse_collapsed ( struct ThreadsContext tctx)

Re-collapse threads marked as collapsed.

Parameters
tctxThreading context

Definition at line 1768 of file mutt_thread.c.

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

◆ mutt_thread_collapse()

void mutt_thread_collapse ( struct ThreadsContext tctx,
bool  collapse 
)

Toggle collapse.

Parameters
tctxThreading context
collapseCollapse / uncollapse

Definition at line 1789 of file mutt_thread.c.

1790{
1791 struct MuttThread *thread = NULL;
1792 struct MuttThread *top = tctx->tree;
1793 while ((thread = top))
1794 {
1795 while (!thread->message)
1796 thread = thread->child;
1797
1798 struct Email *e = thread->message;
1799
1800 if (e->collapsed != collapse)
1801 {
1802 if (e->collapsed)
1804 else if (mutt_thread_can_collapse(e))
1806 }
1807 top = top->next;
1808 }
1809}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1817
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:107
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_can_collapse()

bool mutt_thread_can_collapse ( struct Email e)

Check whether a thread can be collapsed.

Parameters
eHead of the thread
Return values
trueCan be collapsed
falseCannot be collapsed

Definition at line 1817 of file mutt_thread.c.

1818{
1819 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1820 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1821 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1822 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1823}
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:109
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:108
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ UseThreadsMethods

const struct Mapping UseThreadsMethods[]
static
Initial value:
= {
{ "unset", UT_UNSET },
{ "flat", UT_FLAT },
{ "threads", UT_THREADS },
{ "reverse", UT_REVERSE },
{ "no", UT_FLAT },
{ "yes", UT_THREADS },
{ NULL, 0 },
}
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:97

Choices for '$use_threads' for the index.

Definition at line 52 of file mutt_thread.c.

◆ UseThreadsTypeDef

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

Data for the $use_threads enumeration.

Definition at line 66 of file mutt_thread.c.