NeoMutt  2025-12-11-694-ga89709
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
thread.c File Reference

Create/manipulate threading in emails. More...

#include "config.h"
#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include "mutt/lib.h"
#include "config/lib.h"
#include "email/lib.h"
#include "core/lib.h"
#include "mutt.h"
#include "thread.h"
#include "globals.h"
#include "mview.h"
#include "mx.h"
+ Include dependency graph for 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.
 
static int thread_check_integrity (struct MuttThread *tree)
 Verify and repair thread<->message back-pointers.
 
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 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 thread.c.

80{
81 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
82 const enum EmailSortType 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) != EMAIL_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: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:39
#define SORT_REVERSE
Reverse the order of the sort.
Definition sort.h:40
EmailSortType
Methods for sorting Emails.
Definition sort.h:53
@ EMAIL_SORT_THREADS
Sort by email threads.
Definition sort.h:62
@ UT_FLAT
Unthreaded.
Definition thread.h:98
@ UT_THREADS
Normal threading (root above subthreads)
Definition thread.h:99
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition thread.h:100
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
+ 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 thread.c.

98{
100}
static const struct Mapping UseThreadsMethods[]
Choices for '$use_threads' for the index.
Definition thread.c:50
const char * mutt_map_get_name(int val, const struct Mapping *map)
Lookup a string for a constant.
Definition mapping.c:42
+ 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 120 of file thread.c.

121{
122 return e->vnum >= 0 || (e->collapsed && e->visible);
123}
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 130 of file thread.c.

131{
132 struct MuttThread *tmp = NULL;
133 struct MuttThread *tree = e->thread;
134
135 if (!tree)
136 {
137 mutt_debug(LL_DEBUG1, "stranded Email with no thread info, stranded Email index=%d\n",
138 e->index);
139 return true;
140 }
141
142 /* if the user disabled subject hiding, display it */
143 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
144 if (!c_hide_thread_subject)
145 return true;
146
147 /* if our subject is different from our parent's, display it */
148 if (e->subject_changed)
149 return true;
150
151 /* if our subject is different from that of our closest previously displayed
152 * sibling, display the subject */
153 for (tmp = tree->prev; tmp; tmp = tmp->prev)
154 {
155 e = tmp->message;
156 if (e && is_visible(e))
157 {
158 if (e->subject_changed)
159 return true;
160 break;
161 }
162 }
163
164 /* if there is a parent-to-child subject change anywhere between us and our
165 * closest displayed ancestor, display the subject */
166 for (tmp = tree->parent; tmp; tmp = tmp->parent)
167 {
168 e = tmp->message;
169 if (e)
170 {
171 if (is_visible(e))
172 return false;
173 if (e->subject_changed)
174 return true;
175 }
176 }
177
178 /* if we have no visible parent or previous sibling, display the subject */
179 return true;
180}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
static bool is_visible(struct Email *e)
Is the message visible?
Definition thread.c:120
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
bool subject_changed
Used for threading.
Definition email.h:106
int index
The absolute (unsorted) message number.
Definition email.h:110
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 186 of file thread.c.

187{
188 if (!tctx || !tctx->mailbox_view)
189 return;
190
191 struct Mailbox *m = tctx->mailbox_view->mailbox;
192
193 const bool reverse = (mutt_thread_style() == UT_REVERSE);
194 struct MuttThread *tree = tctx->tree;
195 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
196
197 while (tree)
198 {
199 while (!tree->message)
200 tree = tree->child;
201
202 *array = tree->message;
203 array += reverse ? -1 : 1;
204
205 if (tree->child)
206 {
207 tree = tree->child;
208 }
209 else
210 {
211 while (tree)
212 {
213 if (tree->next)
214 {
215 tree = tree->next;
216 break;
217 }
218 else
219 {
220 tree = tree->parent;
221 }
222 }
223 }
224 }
225}
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition thread.c:79
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:78
int msg_count
Total number of messages.
Definition mailbox.h:87
struct Email ** emails
Array of Emails.
Definition mailbox.h:95
struct MailboxView * mailbox_view
Current mailbox.
Definition thread.h:43
struct MuttThread * tree
Top of thread tree.
Definition 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 239 of file thread.c.

240{
241 if (!tree)
242 return;
243
244 struct MuttThread *tmp = NULL;
245 struct MuttThread *orig_tree = tree;
246 const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
247 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
248 int hide_top_missing = c_hide_top_missing && !c_hide_missing;
249 const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
250 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
251 int hide_top_limited = c_hide_top_limited && !c_hide_limited;
252 int depth = 0;
253
254 /* Walk each level backwards (right to left) to make it easier to
255 * compute next_subtree_visible for each node */
256 while (tree->next)
257 tree = tree->next;
258 *max_depth = 0;
259
260 while (true)
261 {
262 if (depth > *max_depth)
263 *max_depth = depth;
264
265 tree->subtree_visible = 0;
266 if (tree->message)
267 {
268 FREE(&tree->message->tree);
269 if (tree->message->thread != tree)
270 {
271 mutt_debug(LL_DEBUG1, "thread<->message mismatch: Email index=%d, thread=%p, message->thread=%p\n",
272 tree->message->index, (void *) tree, (void *) tree->message->thread);
273 }
274 /* Visible messages propagate subtree_visible up to all ancestors */
275 if (is_visible(tree->message))
276 {
277 tree->deep = true;
278 tree->visible = true;
280 for (tmp = tree; tmp; tmp = tmp->parent)
281 {
282 if (tmp->subtree_visible)
283 {
284 tmp->deep = true;
285 tmp->subtree_visible = 2;
286 break;
287 }
288 else
289 {
290 tmp->subtree_visible = 1;
291 }
292 }
293 }
294 else
295 {
296 tree->visible = false;
297 tree->deep = !c_hide_limited;
298 }
299 }
300 else
301 {
302 tree->visible = false;
303 tree->deep = !c_hide_missing;
304 }
305 /* Compute next_subtree_visible from the next sibling, then
306 * navigate: descend to children, retreat to previous sibling,
307 * or ascend to parent when at the leftmost node */
308 tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
309 tree->next->subtree_visible);
310 if (tree->child)
311 {
312 depth++;
313 tree = tree->child;
314 while (tree->next)
315 tree = tree->next;
316 }
317 else if (tree->prev)
318 {
319 tree = tree->prev;
320 }
321 else
322 {
323 while (tree && !tree->prev)
324 {
325 depth--;
326 tree = tree->parent;
327 }
328 if (!tree)
329 break;
330 tree = tree->prev;
331 }
332 }
333
334 /* now fix up for the OPTHIDETOP* options if necessary */
335 if (hide_top_limited || hide_top_missing)
336 {
337 tree = orig_tree;
338 while (true)
339 {
340 if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
341 ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
342 {
343 tree->deep = false;
344 }
345 if (!tree->deep && tree->child && tree->subtree_visible)
346 {
347 tree = tree->child;
348 }
349 else if (tree->next)
350 {
351 tree = tree->next;
352 }
353 else
354 {
355 while (tree && !tree->next)
356 tree = tree->parent;
357 if (!tree)
358 break;
359 tree = tree->next;
360 }
361 }
362 }
363}
static bool need_display_subject(struct Email *e)
Determines whether to display a message's subject.
Definition thread.c:130
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
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 370 of file thread.c.

371{
372 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
373 tctx->mailbox_view = mv;
374 return tctx;
375}
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
The "current" threading state.
Definition 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 381 of file thread.c.

382{
383 if (!ptr || !*ptr)
384 {
385 return;
386 }
387
388 struct ThreadsContext *tctx = *ptr;
389
390 mutt_hash_free(&tctx->hash);
391
392 FREE(ptr);
393}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:459
struct HashTable * hash
Hash Table: "Message-ID" -> MuttThread.
Definition thread.h:45
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ thread_check_integrity()

static int thread_check_integrity ( struct MuttThread * tree)
static

Verify and repair thread<->message back-pointers.

Parameters
treeRoot of the thread tree
Return values
numNumber of repairs made

Walk the entire thread tree and ensure that every MuttThread node with a message has the bidirectional invariant: tree->message->thread == tree. If broken, repair it in-place and log a debug warning.

At each sibling level, rewind via prev to the true head before walking forward via next, so that all nodes are visited even if the parent's child pointer doesn't reference the first sibling.

Definition at line 408 of file thread.c.

409{
410 if (!tree)
411 return 0;
412
413 /* Rewind to the true head of this sibling level */
414 while (tree->prev)
415 tree = tree->prev;
416
417 int repairs = 0;
418
419 while (true)
420 {
421 if (tree->message && (tree->message->thread != tree))
422 {
423 mutt_debug(LL_DEBUG1, "repairing thread<->message: Email index=%d, expected=%p, actual=%p\n",
424 tree->message->index, (void *) tree, (void *) tree->message->thread);
425 tree->message->thread = tree;
426 repairs++;
427 }
428
429 if (tree->child)
430 {
431 tree = tree->child;
432 /* Rewind to the true head of this sibling level */
433 while (tree->prev)
434 tree = tree->prev;
435 }
436 else if (tree->next)
437 {
438 tree = tree->next;
439 }
440 else
441 {
442 while (tree && !tree->next)
443 tree = tree->parent;
444 if (!tree)
445 break;
446 tree = tree->next;
447 }
448 }
449
450 return repairs;
451}
+ 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 465 of file thread.c.

466{
467 if (!tctx || !tctx->tree)
468 return;
469
470 char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
471 const bool reverse = (mutt_thread_style() == UT_REVERSE);
472 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
473 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
474 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
475 int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
476 struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
477
478 struct MuttThread *tree = tctx->tree;
479
480 /* Verify and repair thread<->message back-pointers before traversal */
482
483 /* Do the visibility calculations and free the old thread chars.
484 * From now on we can simply ignore invisible subtrees */
485 calculate_visibility(tree, &max_depth);
486 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
487 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
488 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
489 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
490 while (tree)
491 {
492 if (depth != 0)
493 {
494 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
495 if (start_depth == depth)
496 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
497 else if (parent->message && !c_hide_limited)
498 myarrow[0] = MUTT_TREE_HIDDEN;
499 else if (!parent->message && !c_hide_missing)
500 myarrow[0] = MUTT_TREE_MISSING;
501 else
502 myarrow[0] = vtee;
503 if (width == 2)
504 {
505 myarrow[1] = pseudo ? MUTT_TREE_STAR :
507 }
508 if (tree->visible)
509 {
510 myarrow[width] = MUTT_TREE_RARROW;
511 myarrow[width + 1] = 0;
512 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
513 if (start_depth > 1)
514 {
515 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
516 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
517 (1 + depth - start_depth) * width + 2);
518 }
519 else
520 {
521 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
522 }
523 tree->message->tree = new_tree;
524 }
525 }
526 if (tree->child && (depth != 0))
527 {
528 mypfx = pfx + (depth - 1) * width;
529 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
530 if (width == 2)
531 mypfx[1] = MUTT_TREE_SPACE;
532 }
533 parent = tree;
534 nextdisp = NULL;
535 pseudo = NULL;
536 do
537 {
538 if (tree->child && tree->subtree_visible)
539 {
540 if (tree->deep)
541 depth++;
542 if (tree->visible)
543 start_depth = depth;
544 tree = tree->child;
545
546 /* we do this here because we need to make sure that the first child thread
547 * of the old tree that we deal with is actually displayed if any are,
548 * or we might set the parent variable wrong while going through it. */
549 while (!tree->subtree_visible && tree->next)
550 tree = tree->next;
551 }
552 else
553 {
554 while (!tree->next && tree->parent)
555 {
556 if (tree == pseudo)
557 pseudo = NULL;
558 if (tree == nextdisp)
559 nextdisp = NULL;
560 if (tree->visible)
561 start_depth = depth;
562 tree = tree->parent;
563 if (tree->deep)
564 {
565 if (start_depth == depth)
566 start_depth--;
567 depth--;
568 }
569 }
570 if (tree == pseudo)
571 pseudo = NULL;
572 if (tree == nextdisp)
573 nextdisp = NULL;
574 if (tree->visible)
575 start_depth = depth;
576 tree = tree->next;
577 if (!tree)
578 break;
579 }
580 if (!pseudo && tree->fake_thread)
581 pseudo = tree;
582 if (!nextdisp && tree->next_subtree_visible)
583 nextdisp = tree;
584 } while (!tree->deep);
585 }
586
587 FREE(&pfx);
588 FREE(&arrow);
589}
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
Are tree nodes visible.
Definition thread.c:239
static int thread_check_integrity(struct MuttThread *tree)
Verify and repair thread<->message back-pointers.
Definition thread.c:408
TreeChar
Tree characters for menus.
Definition thread.h:56
@ MUTT_TREE_LLCORNER
Lower left corner.
Definition thread.h:57
@ MUTT_TREE_RARROW
Right arrow.
Definition thread.h:63
@ MUTT_TREE_ULCORNER
Upper left corner.
Definition thread.h:58
@ MUTT_TREE_EQUALS
Equals (for threads)
Definition thread.h:66
@ MUTT_TREE_HIDDEN
Ampersand character (for threads)
Definition thread.h:65
@ MUTT_TREE_STAR
Star character (for threads)
Definition thread.h:64
@ MUTT_TREE_LTEE
Left T-piece.
Definition thread.h:59
@ MUTT_TREE_VLINE
Vertical line.
Definition thread.h:61
@ MUTT_TREE_MISSING
Question mark.
Definition thread.h:69
@ MUTT_TREE_TTEE
Top T-piece.
Definition thread.h:67
@ MUTT_TREE_HLINE
Horizontal line.
Definition thread.h:60
@ MUTT_TREE_SPACE
Blank space.
Definition thread.h:62
@ MUTT_TREE_BTEE
Bottom T-piece.
Definition thread.h:68
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
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:586
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 601 of file thread.c.

602{
603 struct MuttThread *start = cur;
604 struct Envelope *env = NULL;
605 time_t thisdate;
606 int rc = 0;
607
608 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
609 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
610 while (true)
611 {
612 while (!cur->message)
613 cur = cur->child;
614
615 if (dateptr)
616 {
617 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
618 if ((*dateptr == 0) || (thisdate < *dateptr))
619 *dateptr = thisdate;
620 }
621
622 env = cur->message->env;
623 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
624 {
625 struct ListNode *np = NULL;
626 STAILQ_FOREACH(np, subjects, entries)
627 {
628 rc = mutt_str_cmp(env->real_subj, np->data);
629 if (rc >= 0)
630 break;
631 }
632 if (!np)
633 mutt_list_insert_head(subjects, env->real_subj);
634 else if (rc > 0)
635 mutt_list_insert_after(subjects, np, env->real_subj);
636 }
637
638 while (!cur->next && (cur != start))
639 {
640 cur = cur->parent;
641 }
642 if (cur == start)
643 break;
644 cur = cur->next;
645 }
646}
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:403
#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 657 of file thread.c.

658{
659 if (!m)
660 return NULL;
661
662 struct HashElem *he = NULL;
663 struct MuttThread *tmp = NULL, *last = NULL;
664 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
665 time_t date = 0;
666
667 make_subject_list(&subjects, cur, &date);
668
669 struct ListNode *np = NULL;
670 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
671 STAILQ_FOREACH(np, &subjects, entries)
672 {
673 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
674 {
675 tmp = ((struct Email *) he->data)->thread;
676 if ((tmp != cur) && /* don't match the same message */
677 !tmp->fake_thread && /* don't match pseudo threads */
678 tmp->message->subject_changed && /* only match interesting replies */
679 !is_descendant(tmp, cur) && /* don't match in the same thread */
680 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
681 (!last || (c_thread_received ?
682 (last->message->received < tmp->message->received) :
683 (last->message->date_sent < tmp->message->date_sent))) &&
684 tmp->message->env->real_subj &&
686 {
687 last = tmp; /* best match so far */
688 }
689 }
690 }
691
692 mutt_list_clear(&subjects);
693 return last;
694}
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition thread.c:46
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 thread.c:601
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:411
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:665
#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
+ 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 701 of file thread.c.

702{
703 if (!m)
704 return NULL;
705
707
708 for (int i = 0; i < m->msg_count; i++)
709 {
710 struct Email *e = m->emails[i];
711 if (!e || !e->env)
712 continue;
713 if (e->env->real_subj)
714 mutt_hash_insert(hash, e->env->real_subj, e);
715 }
716
717 return hash;
718}
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:337
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:261
#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 726 of file thread.c.

727{
728 if (!tctx || !tctx->mailbox_view)
729 return;
730
731 struct Mailbox *m = tctx->mailbox_view->mailbox;
732
733 struct MuttThread *tree = tctx->tree;
734 struct MuttThread *top = tree;
735 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
736 *nextchild = NULL;
737
738 if (!m->subj_hash)
740
741 while (tree)
742 {
743 cur = tree;
744 tree = tree->next;
745 parent = find_subject(m, cur);
746 if (parent)
747 {
748 cur->fake_thread = true;
749 unlink_message(&top, cur);
751 parent->sort_children = true;
752 tmp = cur;
753 while (true)
754 {
755 while (!tmp->message)
756 tmp = tmp->child;
757
758 /* if the message we're attaching has pseudo-children, they
759 * need to be attached to its parent, so move them up a level.
760 * but only do this if they have the same real subject as the
761 * parent, since otherwise they rightly belong to the message
762 * we're attaching. */
763 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
765 {
766 tmp->message->subject_changed = false;
767
768 for (curchild = tmp->child; curchild;)
769 {
770 nextchild = curchild->next;
771 if (curchild->fake_thread)
772 {
773 unlink_message(&tmp->child, curchild);
774 insert_message(&parent->child, parent, curchild);
775 }
776 curchild = nextchild;
777 }
778 }
779
780 while (!tmp->next && (tmp != cur))
781 {
782 tmp = tmp->parent;
783 }
784 if (tmp == cur)
785 break;
786 tmp = tmp->next;
787 }
788 }
789 }
790 tctx->tree = top;
791}
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
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition thread.c:701
static struct MuttThread * find_subject(struct Mailbox *m, struct MuttThread *cur)
Find the best possible match for a parent based on subject.
Definition thread.c:657
bool sort_children
Sort the children.
Definition thread.h:40
+ 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 797 of file thread.c.

798{
799 if (!tctx || !tctx->tree)
800 return;
801
802 struct MailboxView *mv = tctx->mailbox_view;
803 if (!mv)
804 return;
805
806 struct Mailbox *m = mv->mailbox;
807 if (!m || !m->emails)
808 return;
809
810 for (int i = 0; i < m->msg_count; i++)
811 {
812 struct Email *e = m->emails[i];
813 if (!e)
814 break;
815
816 /* mailbox may have been only partially read */
817 e->thread = NULL;
818 e->threaded = false;
819 }
820 tctx->tree = NULL;
821 mutt_hash_free(&tctx->hash);
822}
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 855 of file thread.c.

856{
857 struct MuttThread *thread = tctx->tree;
858 if (!thread)
859 return;
860
861 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
862 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
863 struct Email *oldsort_thread_key = NULL;
864 int i, array_size;
865 bool sort_top = false;
866
867 /* we put things into the array backwards to save some cycles,
868 * but we want to have to move less stuff around if we're
869 * resorting, so we sort backwards and then put them back
870 * in reverse order so they're forwards */
871 const bool reverse = (mutt_thread_style() == UT_REVERSE);
872 enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
873 enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
874 if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
875 {
876 ASSERT(!(c_sort & SORT_REVERSE) != reverse);
877 c_sort = c_sort_aux;
878 }
879 c_sort ^= SORT_REVERSE;
880 c_sort_aux ^= SORT_REVERSE;
881 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
882 {
883 tctx->c_sort = c_sort;
884 tctx->c_sort_aux = c_sort_aux;
885 init = true;
886 }
887
888 top = thread;
889
890 array_size = 256;
891 array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
892 while (true)
893 {
894 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
895 {
896 thread->sort_thread_key = NULL;
897 thread->sort_aux_key = NULL;
898
899 if (thread->parent)
900 thread->parent->sort_children = true;
901 else
902 sort_top = true;
903 }
904
905 if (thread->child)
906 {
908 continue;
909 }
910 else
911 {
912 /* if it has no children, it must be real. sort it on its own merits */
915
916 if (thread->next)
917 {
918 thread = thread->next;
919 continue;
920 }
921 }
922
923 struct Mailbox *m = tctx->mailbox_view->mailbox;
924 const enum MailboxType mtype = mx_type(m);
925 while (!thread->next)
926 {
927 /* if it has siblings and needs to be sorted, sort it... */
928 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
929 {
930 /* put them into the array */
931 for (i = 0; thread; i++, thread = thread->prev)
932 {
933 if (i >= array_size)
934 {
935 array_size *= 2;
936 MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
937 }
938
939 array[i] = thread;
940 }
941
942 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
943
944 /* attach them back together. make thread the last sibling. */
945 thread = array[0];
946 thread->next = NULL;
947 array[i - 1]->prev = NULL;
948
949 if (thread->parent)
950 thread->parent->child = array[i - 1];
951 else
952 top = array[i - 1];
953
954 while (--i)
955 {
956 array[i - 1]->prev = array[i];
957 array[i]->next = array[i - 1];
958 }
959 }
960
961 if (thread->parent)
962 {
963 tmp = thread;
964 thread = thread->parent;
965
966 if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
967 {
968 /* we just sorted its children */
969 thread->sort_children = false;
970
971 oldsort_aux_key = thread->sort_aux_key;
972 oldsort_thread_key = thread->sort_thread_key;
973
974 /* update sort keys. sort_aux_key will be the first or last
975 * sibling, as appropriate... */
976 thread->sort_aux_key = thread->message;
977 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
978 thread->child->sort_aux_key :
979 tmp->sort_aux_key;
980
981 if (c_sort_aux & SORT_LAST)
982 {
983 if (!thread->sort_aux_key ||
984 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
985 c_sort_aux | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
986 {
987 thread->sort_aux_key = sort_aux_key;
988 }
989 }
990 else if (!thread->sort_aux_key)
991 {
992 thread->sort_aux_key = sort_aux_key;
993 }
994
995 /* ...but sort_thread_key may require searching the entire
996 * list of siblings */
997 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
998 {
999 thread->sort_thread_key = thread->sort_aux_key;
1000 }
1001 else
1002 {
1003 if (thread->message)
1004 {
1005 thread->sort_thread_key = thread->message;
1006 }
1007 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
1008 {
1009 thread->sort_thread_key = tmp->sort_thread_key;
1010 }
1011 else
1012 {
1013 thread->sort_thread_key = thread->child->sort_thread_key;
1014 }
1015 if (c_sort & SORT_LAST)
1016 {
1017 for (tmp = thread->child; tmp; tmp = tmp->next)
1018 {
1019 if (tmp->sort_thread_key == thread->sort_thread_key)
1020 continue;
1021 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key, mtype,
1022 c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
1023 {
1024 thread->sort_thread_key = tmp->sort_thread_key;
1025 }
1026 }
1027 }
1028 }
1029
1030 /* if a sort_key has changed, we need to resort it and siblings */
1031 if ((oldsort_aux_key != thread->sort_aux_key) ||
1032 (oldsort_thread_key != thread->sort_thread_key))
1033 {
1034 if (thread->parent)
1035 thread->parent->sort_children = true;
1036 else
1037 sort_top = true;
1038 }
1039 }
1040 }
1041 else
1042 {
1043 FREE(&array);
1044 tctx->tree = top;
1045 return;
1046 }
1047 }
1048
1049 thread = thread->next;
1050 }
1051}
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition sort.h:41
MailboxType
Supported mailbox formats.
Definition mailbox.h:40
@ 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 thread.c:827
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:328
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition mx.c:1810
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:72
#define ASSERT(COND)
Definition signal2.h:59
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 thread.h:46
enum EmailSortType c_sort_aux
Last sort_aux method.
Definition 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 1058 of file thread.c.

1059{
1060 if (!mv)
1061 return;
1062
1063 struct Mailbox *m = mv->mailbox;
1064 for (int i = 0; i < m->msg_count; i++)
1065 {
1066 struct Email *e = m->emails[i];
1067 if (!e || !e->thread)
1068 continue;
1069
1070 if (e->thread->check_subject)
1071 e->thread->check_subject = false;
1072 else if (!init)
1073 continue;
1074
1075 /* figure out which messages have subjects different than their parents' */
1076 struct MuttThread *tmp = e->thread->parent;
1077 while (tmp && !tmp->message)
1078 {
1079 tmp = tmp->parent;
1080 }
1081
1082 if (!tmp)
1083 {
1084 e->subject_changed = true;
1085 }
1086 else if (e->env->real_subj && tmp->message->env->real_subj)
1087 {
1089 }
1090 else
1091 {
1092 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1093 }
1094 }
1095}
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 1110 of file thread.c.

1111{
1112 if (!tctx || !tctx->mailbox_view)
1113 return;
1114
1115 struct MailboxView *mv = tctx->mailbox_view;
1116 struct Mailbox *m = mv->mailbox;
1117
1118 struct Email *e = NULL;
1119 int i, using_refs = 0;
1120 struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1121 struct MuttThread top = { 0 };
1122 struct ListNode *ref = NULL;
1123
1124 ASSERT(m->msg_count > 0);
1125 if (!tctx->hash)
1126 init = true;
1127
1128 if (init)
1129 {
1132 }
1133
1134 /* we want a quick way to see if things are actually attached to the top of the
1135 * thread tree or if they're just dangling, so we attach everything to a top
1136 * node temporarily */
1137 top.parent = NULL;
1138 top.next = NULL;
1139 top.prev = NULL;
1140
1141 top.child = tctx->tree;
1142 for (thread = tctx->tree; thread; thread = thread->next)
1143 thread->parent = &top;
1144
1145 /* put each new message together with the matching messageless MuttThread if it
1146 * exists. otherwise, if there is a MuttThread that already has a message, thread
1147 * new message as an identical child. if we didn't attach the message to a
1148 * MuttThread, make a new one for it. */
1149 const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1150 for (i = 0; i < m->msg_count; i++)
1151 {
1152 e = m->emails[i];
1153 if (!e)
1154 continue;
1155
1156 if (e->thread)
1157 {
1158 /* unlink pseudo-threads because they might be children of newly
1159 * arrived messages */
1160 thread = e->thread;
1161 for (tnew = thread->child; tnew;)
1162 {
1163 tmp = tnew->next;
1164 if (tnew->fake_thread)
1165 {
1166 unlink_message(&thread->child, tnew);
1167 insert_message(&top.child, &top, tnew);
1168 tnew->fake_thread = false;
1169 }
1170 tnew = tmp;
1171 }
1172 }
1173 else
1174 {
1175 if ((!init || c_duplicate_threads) && e->env->message_id)
1176 thread = mutt_hash_find(tctx->hash, e->env->message_id);
1177 else
1178 thread = NULL;
1179
1180 if (thread && !thread->message)
1181 {
1182 /* this is a message which was missing before */
1183 thread->message = e;
1184 e->thread = thread;
1185 thread->check_subject = true;
1186
1187 /* mark descendants as needing subject_changed checked */
1188 for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1189 {
1190 while (!tmp->message)
1191 tmp = tmp->child;
1192 tmp->check_subject = true;
1193 while (!tmp->next && (tmp != thread))
1194 tmp = tmp->parent;
1195 if (tmp != thread)
1196 tmp = tmp->next;
1197 }
1198
1199 if (thread->parent)
1200 {
1201 /* remove threading info above it based on its children, which we'll
1202 * recalculate based on its headers. make sure not to leave
1203 * dangling missing messages. note that we haven't kept track
1204 * of what info came from its children and what from its siblings'
1205 * children, so we just remove the stuff that's definitely from it */
1206 do
1207 {
1208 tmp = thread->parent;
1209 unlink_message(&tmp->child, thread);
1210 thread->parent = NULL;
1211 thread->sort_thread_key = NULL;
1212 thread->sort_aux_key = NULL;
1213 thread->fake_thread = false;
1214 thread = tmp;
1215 } while (thread != &top && !thread->child && !thread->message);
1216 }
1217 }
1218 else
1219 {
1220 tnew = (c_duplicate_threads ? thread : NULL);
1221
1222 thread = MUTT_MEM_CALLOC(1, struct MuttThread);
1223 thread->message = e;
1224 thread->check_subject = true;
1225 e->thread = thread;
1226 mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1227
1228 if (tnew)
1229 {
1230 if (tnew->duplicate_thread)
1231 tnew = tnew->parent;
1232
1233 thread = e->thread;
1234
1235 insert_message(&tnew->child, tnew, thread);
1236 thread->duplicate_thread = true;
1237 thread->message->threaded = true;
1238 }
1239 }
1240 }
1241 }
1242
1243 /* thread by references */
1244 for (i = 0; i < m->msg_count; i++)
1245 {
1246 e = m->emails[i];
1247 if (!e)
1248 break;
1249
1250 if (e->threaded)
1251 continue;
1252 e->threaded = true;
1253
1254 thread = e->thread;
1255 if (!thread)
1256 continue;
1257 using_refs = 0;
1258
1259 while (true)
1260 {
1261 if (using_refs == 0)
1262 {
1263 /* look at the beginning of in-reply-to: */
1264 ref = STAILQ_FIRST(&e->env->in_reply_to);
1265 if (ref)
1266 {
1267 using_refs = 1;
1268 }
1269 else
1270 {
1271 ref = STAILQ_FIRST(&e->env->references);
1272 using_refs = 2;
1273 }
1274 }
1275 else if (using_refs == 1)
1276 {
1277 /* if there's no references header, use all the in-reply-to:
1278 * data that we have. otherwise, use the first reference
1279 * if it's different than the first in-reply-to, otherwise use
1280 * the second reference (since at least eudora puts the most
1281 * recent reference in in-reply-to and the rest in references) */
1282 if (STAILQ_EMPTY(&e->env->references))
1283 {
1284 ref = STAILQ_NEXT(ref, entries);
1285 }
1286 else
1287 {
1288 if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1289 ref = STAILQ_FIRST(&e->env->references);
1290 else
1291 ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1292
1293 using_refs = 2;
1294 }
1295 }
1296 else
1297 {
1298 ref = STAILQ_NEXT(ref, entries); /* go on with references */
1299 }
1300
1301 if (!ref)
1302 break;
1303
1304 tnew = mutt_hash_find(tctx->hash, ref->data);
1305 if (tnew)
1306 {
1307 if (tnew->duplicate_thread)
1308 tnew = tnew->parent;
1309 if (is_descendant(tnew, thread)) /* no loops! */
1310 continue;
1311 }
1312 else
1313 {
1314 tnew = MUTT_MEM_CALLOC(1, struct MuttThread);
1315 mutt_hash_insert(tctx->hash, ref->data, tnew);
1316 }
1317
1318 if (thread->parent)
1319 unlink_message(&top.child, thread);
1320 insert_message(&tnew->child, tnew, thread);
1321 thread = tnew;
1322 if (thread->message || (thread->parent && (thread->parent != &top)))
1323 break;
1324 }
1325
1326 if (!thread->parent)
1327 insert_message(&top.child, &top, thread);
1328 }
1329
1330 /* detach everything from the temporary top node */
1331 for (thread = top.child; thread; thread = thread->next)
1332 {
1333 thread->parent = NULL;
1334 }
1335 tctx->tree = top.child;
1336
1337 check_subjects(mv, init);
1338
1339 const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1340 if (!c_strict_threads)
1341 pseudo_threads(tctx);
1342
1343 /* if $sort_aux or similar changed after the mailbox is sorted, then
1344 * all the subthreads need to be resorted */
1345 if (tctx->tree)
1346 {
1348 OptSortSubthreads = false;
1349
1350 /* Put the list into an array. */
1351 linearize_tree(tctx);
1352
1353 /* Draw the thread tree. */
1354 mutt_draw_tree(tctx);
1355 }
1356}
bool OptSortSubthreads
(pseudo) used when $sort_aux changes
Definition globals.c:57
static void thread_hash_destructor(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition thread.c:1100
static void linearize_tree(struct ThreadsContext *tctx)
Flatten an email thread.
Definition thread.c:186
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition thread.c:855
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition thread.c:465
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition thread.c:726
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition thread.c:1058
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:364
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:303
#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 1366 of file thread.c.

1367{
1368 if (!e)
1369 return -1;
1370
1371 struct MuttThread *cur = NULL;
1372 struct Email *e_tmp = NULL;
1373
1374 const enum UseThreads threaded = mutt_thread_style();
1375 if (threaded == UT_FLAT)
1376 {
1377 mutt_warning(_("Threading is not enabled"));
1378 return e->vnum;
1379 }
1380
1381 cur = e->thread;
1382
1383 if (subthreads)
1384 {
1385 if (forwards ^ (threaded == UT_REVERSE))
1386 {
1387 while (!cur->next && cur->parent)
1388 cur = cur->parent;
1389 }
1390 else
1391 {
1392 while (!cur->prev && cur->parent)
1393 cur = cur->parent;
1394 }
1395 }
1396 else
1397 {
1398 while (cur->parent)
1399 cur = cur->parent;
1400 }
1401
1402 if (forwards ^ (threaded == UT_REVERSE))
1403 {
1404 do
1405 {
1406 cur = cur->next;
1407 if (!cur)
1408 return -1;
1409 e_tmp = find_virtual(cur, false);
1410 } while (!e_tmp);
1411 }
1412 else
1413 {
1414 do
1415 {
1416 cur = cur->prev;
1417 if (!cur)
1418 return -1;
1419 e_tmp = find_virtual(cur, true);
1420 } while (!e_tmp);
1421 }
1422
1423 return e_tmp->vnum;
1424}
struct Email * find_virtual(struct MuttThread *cur, bool reverse)
Find an email with a Virtual message number.
Definition thread.c:124
#define mutt_warning(...)
Definition logging2.h:92
UseThreads
Which threading style is active, $use_threads.
Definition thread.h:96
#define _(a)
Definition message.h:28
+ 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 1433 of file thread.c.

1434{
1435 if (!e)
1436 return -1;
1437
1438 struct MuttThread *thread = NULL;
1439 struct Email *e_parent = NULL;
1440
1441 if (!mutt_using_threads())
1442 {
1443 mutt_warning(_("Threading is not enabled"));
1444 return e->vnum;
1445 }
1446
1447 /* Root may be the current message */
1448 if (find_root)
1449 e_parent = e;
1450
1451 for (thread = e->thread->parent; thread; thread = thread->parent)
1452 {
1453 e = thread->message;
1454 if (e)
1455 {
1456 e_parent = e;
1457 if (!find_root)
1458 break;
1459 }
1460 }
1461
1462 if (!e_parent)
1463 {
1464 mutt_error(_("Parent message is not available"));
1465 return -1;
1466 }
1467 if (!is_visible(e_parent))
1468 {
1469 if (find_root)
1470 mutt_error(_("Root message is not visible in this limited view"));
1471 else
1472 mutt_error(_("Parent message is not visible in this limited view"));
1473 return -1;
1474 }
1475 return e_parent->vnum;
1476}
#define mutt_error(...)
Definition logging2.h:94
#define mutt_using_threads()
Definition 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 1483 of file thread.c.

1484{
1485 if (!m)
1486 return 0;
1487
1488 off_t vsize = 0;
1489 const int padding = mx_msg_padding_size(m);
1490
1491 m->vcount = 0;
1492
1493 for (int i = 0; i < m->msg_count; i++)
1494 {
1495 struct Email *e = m->emails[i];
1496 if (!e)
1497 break;
1498
1499 if (e->vnum >= 0)
1500 {
1501 e->vnum = m->vcount;
1502 m->v2r[m->vcount] = i;
1503 m->vcount++;
1504 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1505 }
1506 }
1507
1508 return vsize;
1509}
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition mx.c:1507
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:98
int * v2r
Mapping from virtual to real msgno.
Definition mailbox.h:97
+ 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 1517 of file thread.c.

1518{
1519 struct MuttThread *thread = NULL, *top = NULL;
1520 struct Email *e_root = NULL;
1521 const enum UseThreads threaded = mutt_thread_style();
1522 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1523 int num_hidden = 0, new_mail = 0, old_mail = 0;
1524 bool flagged = false;
1525 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1526
1527 if (threaded == UT_FLAT)
1528 {
1529 mutt_warning(_("Threading is not enabled"));
1530 return e_cur->vnum;
1531 }
1532
1533 if (!e_cur->thread)
1534 {
1535 return e_cur->vnum;
1536 }
1537
1538 final = e_cur->vnum;
1539 thread = e_cur->thread;
1540 while (thread->parent)
1541 thread = thread->parent;
1542 top = thread;
1543 while (!thread->message)
1544 thread = thread->child;
1545 e_cur = thread->message;
1546 minmsgno = e_cur->msgno;
1547
1548 if (!e_cur->read && e_cur->visible)
1549 {
1550 if (e_cur->old)
1551 old_mail = 2;
1552 else
1553 new_mail = 1;
1554 if (e_cur->msgno < min_unread_msgno)
1555 {
1556 min_unread = e_cur->vnum;
1557 min_unread_msgno = e_cur->msgno;
1558 }
1559 }
1560
1561 if (e_cur->flagged && e_cur->visible)
1562 flagged = true;
1563
1564 if ((e_cur->vnum == -1) && e_cur->visible)
1565 num_hidden++;
1566
1568 {
1569 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1570 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1571 if (e_cur->vnum != -1)
1572 {
1573 e_root = e_cur;
1574 if (flag & MUTT_THREAD_COLLAPSE)
1575 final = e_root->vnum;
1576 }
1577 }
1578
1579 if ((thread == top) && !(thread = thread->child))
1580 {
1581 /* return value depends on action requested */
1583 {
1584 e_cur->num_hidden = num_hidden;
1585 return final;
1586 }
1587 if (flag & MUTT_THREAD_UNREAD)
1588 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1589 if (flag & MUTT_THREAD_NEXT_UNREAD)
1590 return min_unread;
1591 if (flag & MUTT_THREAD_FLAGGED)
1592 return flagged;
1593 }
1594
1595 while (true)
1596 {
1597 e_cur = thread->message;
1598
1599 if (e_cur)
1600 {
1602 {
1603 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1604 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1605 if (!e_root && e_cur->visible)
1606 {
1607 e_root = e_cur;
1608 if (flag & MUTT_THREAD_COLLAPSE)
1609 final = e_root->vnum;
1610 }
1611
1612 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1613 (e_cur->msgno < minmsgno) && e_cur->visible)
1614 {
1615 minmsgno = e_cur->msgno;
1616 final = e_cur->vnum;
1617 }
1618
1619 if (flag & MUTT_THREAD_COLLAPSE)
1620 {
1621 if (e_cur != e_root)
1622 e_cur->vnum = -1;
1623 }
1624 else
1625 {
1626 if (e_cur->visible)
1627 e_cur->vnum = e_cur->msgno;
1628 }
1629 }
1630
1631 if (!e_cur->read && e_cur->visible)
1632 {
1633 if (e_cur->old)
1634 old_mail = 2;
1635 else
1636 new_mail = 1;
1637 if (e_cur->msgno < min_unread_msgno)
1638 {
1639 min_unread = e_cur->vnum;
1640 min_unread_msgno = e_cur->msgno;
1641 }
1642 }
1643
1644 if (e_cur->flagged && e_cur->visible)
1645 flagged = true;
1646
1647 if ((e_cur->vnum == -1) && e_cur->visible)
1648 num_hidden++;
1649 }
1650
1651 if (thread->child)
1652 {
1653 thread = thread->child;
1654 }
1655 else if (thread->next)
1656 {
1657 thread = thread->next;
1658 }
1659 else
1660 {
1661 bool done = false;
1662 while (!thread->next)
1663 {
1664 thread = thread->parent;
1665 if (thread == top)
1666 {
1667 done = true;
1668 break;
1669 }
1670 }
1671 if (done)
1672 break;
1673 thread = thread->next;
1674 }
1675 }
1676
1677 /* re-traverse the thread and store num_hidden in all headers, with or
1678 * without a virtual index. this will allow ~v to match all collapsed
1679 * messages when switching sort order to non-threaded. */
1680 if (flag & MUTT_THREAD_COLLAPSE)
1681 {
1682 thread = top;
1683 while (true)
1684 {
1685 e_cur = thread->message;
1686 if (e_cur)
1687 e_cur->num_hidden = num_hidden + 1;
1688
1689 if (thread->child)
1690 {
1691 thread = thread->child;
1692 }
1693 else if (thread->next)
1694 {
1695 thread = thread->next;
1696 }
1697 else
1698 {
1699 bool done = false;
1700 while (!thread->next)
1701 {
1702 thread = thread->parent;
1703 if (thread == top)
1704 {
1705 done = true;
1706 break;
1707 }
1708 }
1709 if (done)
1710 break;
1711 thread = thread->next;
1712 }
1713 }
1714 }
1715
1716 /* return value depends on action requested */
1718 return final;
1719 if (flag & MUTT_THREAD_UNREAD)
1720 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1721 if (flag & MUTT_THREAD_NEXT_UNREAD)
1722 return min_unread;
1723 if (flag & MUTT_THREAD_FLAGGED)
1724 return flagged;
1725
1726 return 0;
1727}
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition thread.h:79
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition thread.h:78
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition thread.h:80
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition thread.h:77
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition 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 1736 of file thread.c.

1737{
1738 if (!m || !e)
1739 return 1;
1740
1741 struct MuttThread *threads[2];
1742 int rc;
1743
1744 const enum UseThreads threaded = mutt_thread_style();
1745 if ((threaded == UT_FLAT) || !e->thread)
1746 return 1;
1747
1748 threads[0] = e->thread;
1749 while (threads[0]->parent)
1750 threads[0] = threads[0]->parent;
1751
1752 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1753
1754 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1755 {
1756 while (!threads[i]->message)
1757 threads[i] = threads[i]->child;
1758 }
1759
1760 if (threaded == UT_REVERSE)
1761 {
1762 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1763 }
1764 else
1765 {
1766 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1767 threads[0]->message->msgno;
1768 }
1769
1770 if (mit == MIT_POSITION)
1771 rc += 1;
1772
1773 return rc;
1774}
@ MIT_POSITION
Our position in the thread.
Definition 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 1781 of file thread.c.

1782{
1783 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1784
1785 for (int i = 0; i < m->msg_count; i++)
1786 {
1787 struct Email *e = m->emails[i];
1788 if (!e || !e->env)
1789 continue;
1790
1791 if (e->env->message_id)
1792 mutt_hash_insert(hash, e->env->message_id, e);
1793 }
1794
1795 return hash;
1796}
#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 1805 of file thread.c.

1806{
1807 if (child == parent)
1808 return false;
1809
1810 mutt_break_thread(child);
1812 mutt_set_flag(m, child, MUTT_TAG, false, true);
1813
1814 child->changed = true;
1815 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1816 return true;
1817}
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition thread.c:229
#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:54
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
@ MUTT_TAG
Tagged messages.
Definition mutt.h:99
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
+ 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 1826 of file thread.c.

1827{
1828 if (!parent || !children || !m)
1829 return false;
1830
1831 bool changed = false;
1832
1833 struct Email **ep = NULL;
1834 ARRAY_FOREACH(ep, children)
1835 {
1836 struct Email *e = *ep;
1837 changed |= link_threads(parent, e, m);
1838 }
1839
1840 return changed;
1841}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition thread.c:1805
+ 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 1847 of file thread.c.

1848{
1849 struct MuttThread *thread = NULL;
1850 struct MuttThread *top = tctx->tree;
1851 while ((thread = top))
1852 {
1853 while (!thread->message)
1854 thread = thread->child;
1855
1856 struct Email *e = thread->message;
1857 if (e->collapsed)
1859 top = top->next;
1860 }
1861}
#define mutt_collapse_thread(e)
Definition 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 1868 of file thread.c.

1869{
1870 struct MuttThread *thread = NULL;
1871 struct MuttThread *top = tctx->tree;
1872 while ((thread = top))
1873 {
1874 while (!thread->message)
1875 thread = thread->child;
1876
1877 struct Email *e = thread->message;
1878
1879 if (e->collapsed != collapse)
1880 {
1881 if (e->collapsed)
1883 else if (mutt_thread_can_collapse(e))
1885 }
1886 top = top->next;
1887 }
1888}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition thread.c:1896
#define mutt_uncollapse_thread(e)
Definition 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 1896 of file thread.c.

1897{
1898 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1899 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1900 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1901 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1902}
#define mutt_thread_contains_flagged(e)
Definition thread.h:109
#define mutt_thread_contains_unread(e)
Definition 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 thread.h:97

Choices for '$use_threads' for the index.

Definition at line 50 of file thread.c.

50 {
51 // clang-format off
52 { "unset", UT_UNSET },
53 { "flat", UT_FLAT },
54 { "threads", UT_THREADS },
55 { "reverse", UT_REVERSE },
56 // aliases
57 { "no", UT_FLAT },
58 { "yes", UT_THREADS },
59 { NULL, 0 },
60 // clang-format on
61};

◆ 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 64 of file thread.c.

64 {
65 "use_threads_type",
66 4,
67 (struct Mapping *) &UseThreadsMethods,
68};