NeoMutt  2025-12-11-980-ge38c27
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, int count)
 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:104
@ UT_THREADS
Normal threading (root above subthreads)
Definition thread.h:105
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition thread.h:106
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:81
int msg_count
Total number of messages.
Definition mailbox.h:90
struct Email ** emails
Array of Emails.
Definition mailbox.h:98
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:460
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;
471 char *mypfx = NULL;
472 char *arrow = NULL;
473 char *myarrow = NULL;
474 char *new_tree = NULL;
475 const bool reverse = (mutt_thread_style() == UT_REVERSE);
476 enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
477 enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
478 const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
479 int depth = 0;
480 int start_depth = 0;
481 int max_depth = 0;
482 int width = c_narrow_tree ? 1 : 2;
483 struct MuttThread *nextdisp = NULL;
484 struct MuttThread *pseudo = NULL;
485 struct MuttThread *parent = NULL;
486
487 struct MuttThread *tree = tctx->tree;
488
489 /* Verify and repair thread<->message back-pointers before traversal */
491
492 /* Do the visibility calculations and free the old thread chars.
493 * From now on we can simply ignore invisible subtrees */
494 calculate_visibility(tree, &max_depth);
495 pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
496 arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
497 const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
498 const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
499 while (tree)
500 {
501 if (depth != 0)
502 {
503 myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
504 if (start_depth == depth)
505 myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
506 else if (parent->message && !c_hide_limited)
507 myarrow[0] = MUTT_TREE_HIDDEN;
508 else if (!parent->message && !c_hide_missing)
509 myarrow[0] = MUTT_TREE_MISSING;
510 else
511 myarrow[0] = vtee;
512 if (width == 2)
513 {
514 myarrow[1] = pseudo ? MUTT_TREE_STAR :
516 }
517 if (tree->visible)
518 {
519 myarrow[width] = MUTT_TREE_RARROW;
520 myarrow[width + 1] = 0;
521 new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
522 if (start_depth > 1)
523 {
524 strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
525 mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
526 (1 + depth - start_depth) * width + 2);
527 }
528 else
529 {
530 mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
531 }
532 tree->message->tree = new_tree;
533 }
534 }
535 if (tree->child && (depth != 0))
536 {
537 mypfx = pfx + (depth - 1) * width;
538 mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
539 if (width == 2)
540 mypfx[1] = MUTT_TREE_SPACE;
541 }
542 parent = tree;
543 nextdisp = NULL;
544 pseudo = NULL;
545 do
546 {
547 if (tree->child && tree->subtree_visible)
548 {
549 if (tree->deep)
550 depth++;
551 if (tree->visible)
552 start_depth = depth;
553 tree = tree->child;
554
555 /* we do this here because we need to make sure that the first child thread
556 * of the old tree that we deal with is actually displayed if any are,
557 * or we might set the parent variable wrong while going through it. */
558 while (!tree->subtree_visible && tree->next)
559 tree = tree->next;
560 }
561 else
562 {
563 while (!tree->next && tree->parent)
564 {
565 if (tree == pseudo)
566 pseudo = NULL;
567 if (tree == nextdisp)
568 nextdisp = NULL;
569 if (tree->visible)
570 start_depth = depth;
571 tree = tree->parent;
572 if (tree->deep)
573 {
574 if (start_depth == depth)
575 start_depth--;
576 depth--;
577 }
578 }
579 if (tree == pseudo)
580 pseudo = NULL;
581 if (tree == nextdisp)
582 nextdisp = NULL;
583 if (tree->visible)
584 start_depth = depth;
585 tree = tree->next;
586 if (!tree)
587 break;
588 }
589 if (!pseudo && tree->fake_thread)
590 pseudo = tree;
591 if (!nextdisp && tree->next_subtree_visible)
592 nextdisp = tree;
593 } while (!tree->deep);
594 }
595
596 FREE(&pfx);
597 FREE(&arrow);
598}
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:587
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 610 of file thread.c.

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

667{
668 if (!m)
669 return NULL;
670
671 struct HashElem *he = NULL;
672 struct MuttThread *tmp = NULL;
673 struct MuttThread *last = NULL;
674 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
675 time_t date = 0;
676
677 make_subject_list(&subjects, cur, &date);
678
679 struct ListNode *np = NULL;
680 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
681 STAILQ_FOREACH(np, &subjects, entries)
682 {
683 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
684 {
685 tmp = ((struct Email *) he->data)->thread;
686 if ((tmp != cur) && /* don't match the same message */
687 !tmp->fake_thread && /* don't match pseudo threads */
688 tmp->message->subject_changed && /* only match interesting replies */
689 !is_descendant(tmp, cur) && /* don't match in the same thread */
690 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
691 (!last || (c_thread_received ?
692 (last->message->received < tmp->message->received) :
693 (last->message->date_sent < tmp->message->date_sent))) &&
694 tmp->message->env->real_subj &&
696 {
697 last = tmp; /* best match so far */
698 }
699 }
700 }
701
702 mutt_list_clear(&subjects);
703 return last;
704}
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:610
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:412
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition list.c:168
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
#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:126
+ 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 711 of file thread.c.

712{
713 if (!m)
714 return NULL;
715
717
718 for (int i = 0; i < m->msg_count; i++)
719 {
720 struct Email *e = m->emails[i];
721 if (!e || !e->env)
722 continue;
723 if (e->env->real_subj)
724 mutt_hash_insert(hash, e->env->real_subj, e);
725 }
726
727 return hash;
728}
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:338
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:262
@ MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition hash.h:118
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 736 of file thread.c.

737{
738 if (!tctx || !tctx->mailbox_view)
739 return;
740
741 struct Mailbox *m = tctx->mailbox_view->mailbox;
742
743 struct MuttThread *tree = tctx->tree;
744 struct MuttThread *top = tree;
745 struct MuttThread *tmp = NULL;
746 struct MuttThread *cur = NULL;
747 struct MuttThread *parent = NULL;
748 struct MuttThread *curchild = NULL;
749 struct MuttThread *nextchild = NULL;
750
751 if (!m->subj_hash)
753
754 while (tree)
755 {
756 cur = tree;
757 tree = tree->next;
758 parent = find_subject(m, cur);
759 if (parent)
760 {
761 cur->fake_thread = true;
762 unlink_message(&top, cur);
764 parent->sort_children = true;
765 tmp = cur;
766 while (true)
767 {
768 while (!tmp->message)
769 tmp = tmp->child;
770
771 /* if the message we're attaching has pseudo-children, they
772 * need to be attached to its parent, so move them up a level.
773 * but only do this if they have the same real subject as the
774 * parent, since otherwise they rightly belong to the message
775 * we're attaching. */
776 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
778 {
779 tmp->message->subject_changed = false;
780
781 for (curchild = tmp->child; curchild;)
782 {
783 nextchild = curchild->next;
784 if (curchild->fake_thread)
785 {
786 unlink_message(&tmp->child, curchild);
787 insert_message(&parent->child, parent, curchild);
788 }
789 curchild = nextchild;
790 }
791 }
792
793 while (!tmp->next && (tmp != cur))
794 {
795 tmp = tmp->parent;
796 }
797 if (tmp == cur)
798 break;
799 tmp = tmp->next;
800 }
801 }
802 }
803 tctx->tree = top;
804}
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:711
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:666
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 810 of file thread.c.

811{
812 if (!tctx || !tctx->tree)
813 return;
814
815 struct MailboxView *mv = tctx->mailbox_view;
816 if (!mv)
817 return;
818
819 struct Mailbox *m = mv->mailbox;
820 if (!m || !m->emails)
821 return;
822
823 for (int i = 0; i < m->msg_count; i++)
824 {
825 struct Email *e = m->emails[i];
826 if (!e)
827 {
828 // Keep processing, in case there are other holes in the Email array
829 continue;
830 }
831
832 /* mailbox may have been only partially read */
833 e->thread = NULL;
834 e->threaded = false;
835 }
836 tctx->tree = NULL;
837 mutt_hash_free(&tctx->hash);
838}
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 871 of file thread.c.

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

1079{
1080 if (!mv)
1081 return;
1082
1083 struct Mailbox *m = mv->mailbox;
1084 for (int i = 0; i < m->msg_count; i++)
1085 {
1086 struct Email *e = m->emails[i];
1087 if (!e || !e->thread)
1088 continue;
1089
1090 if (e->thread->check_subject)
1091 e->thread->check_subject = false;
1092 else if (!init)
1093 continue;
1094
1095 /* figure out which messages have subjects different than their parents' */
1096 struct MuttThread *tmp = e->thread->parent;
1097 while (tmp && !tmp->message)
1098 {
1099 tmp = tmp->parent;
1100 }
1101
1102 if (!tmp)
1103 {
1104 e->subject_changed = true;
1105 }
1106 else if (e->env->real_subj && tmp->message->env->real_subj)
1107 {
1109 }
1110 else
1111 {
1112 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1113 }
1114 }
1115}
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 1130 of file thread.c.

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

1390{
1391 if (!e)
1392 return -1;
1393
1394 struct MuttThread *cur = NULL;
1395 struct Email *e_tmp = NULL;
1396
1397 const enum UseThreads threaded = mutt_thread_style();
1398 if (threaded == UT_FLAT)
1399 {
1400 mutt_warning(_("Threading is not enabled"));
1401 return e->vnum;
1402 }
1403
1404 cur = e->thread;
1405
1406 if (subthreads)
1407 {
1408 if (forwards ^ (threaded == UT_REVERSE))
1409 {
1410 while (!cur->next && cur->parent)
1411 cur = cur->parent;
1412 }
1413 else
1414 {
1415 while (!cur->prev && cur->parent)
1416 cur = cur->parent;
1417 }
1418 }
1419 else
1420 {
1421 while (cur->parent)
1422 cur = cur->parent;
1423 }
1424
1425 if (forwards ^ (threaded == UT_REVERSE))
1426 {
1427 do
1428 {
1429 cur = cur->next;
1430 if (!cur)
1431 return -1;
1432 e_tmp = find_virtual(cur, false);
1433 } while (!e_tmp);
1434 }
1435 else
1436 {
1437 do
1438 {
1439 cur = cur->prev;
1440 if (!cur)
1441 return -1;
1442 e_tmp = find_virtual(cur, true);
1443 } while (!e_tmp);
1444 }
1445
1446 return e_tmp->vnum;
1447}
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:102
#define _(a)
Definition message.h:28
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_parent_message()

int mutt_parent_message ( struct Email * e,
bool find_root,
int count )

Find the parent of a message.

Parameters
eCurrent Email
find_rootIf true, find the root message
countNumber of generations to go up (1 for direct parent, 0 = count as 1)
Return values
>=0Virtual index number of parent/root message
-1Error

Definition at line 1457 of file thread.c.

1458{
1459 if (!e)
1460 return -1;
1461
1462 struct MuttThread *thread = NULL;
1463 struct Email *e_parent = NULL;
1464
1465 if (!mutt_using_threads())
1466 {
1467 mutt_warning(_("Threading is not enabled"));
1468 return e->vnum;
1469 }
1470
1471 /* Normalize count to at least 1 */
1472 if (count <= 0)
1473 count = 1;
1474
1475 /* Root may be the current message */
1476 if (find_root)
1477 e_parent = e;
1478
1479 for (thread = e->thread->parent; thread; thread = thread->parent)
1480 {
1481 e = thread->message;
1482 if (e)
1483 {
1484 e_parent = e;
1485 if (!find_root)
1486 {
1487 count--;
1488 if (count == 0)
1489 break;
1490 }
1491 }
1492 }
1493
1494 if (!e_parent)
1495 {
1496 mutt_error(_("Parent message is not available"));
1497 return -1;
1498 }
1499 if (!is_visible(e_parent))
1500 {
1501 if (find_root)
1502 mutt_error(_("Root message is not visible in this limited view"));
1503 else
1504 mutt_error(_("Parent message is not visible in this limited view"));
1505 return -1;
1506 }
1507 return e_parent->vnum;
1508}
#define mutt_error(...)
Definition logging2.h:94
#define mutt_using_threads()
Definition thread.h:119
+ 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 1515 of file thread.c.

1516{
1517 if (!m)
1518 return 0;
1519
1520 off_t vsize = 0;
1521 const int padding = mx_msg_padding_size(m);
1522
1523 m->vcount = 0;
1524
1525 for (int i = 0; i < m->msg_count; i++)
1526 {
1527 struct Email *e = m->emails[i];
1528 if (!e)
1529 break;
1530
1531 if (e->vnum >= 0)
1532 {
1533 e->vnum = m->vcount;
1534 m->v2r[m->vcount] = i;
1535 m->vcount++;
1536 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1537 }
1538 }
1539
1540 return vsize;
1541}
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:101
int * v2r
Mapping from virtual to real msgno.
Definition mailbox.h:100
+ 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 1549 of file thread.c.

1550{
1551 struct MuttThread *thread = NULL;
1552 struct MuttThread *top = NULL;
1553 struct Email *e_root = NULL;
1554 const enum UseThreads threaded = mutt_thread_style();
1555 int final;
1556 int reverse = (threaded == UT_REVERSE);
1557 int minmsgno;
1558 int num_hidden = 0;
1559 int new_mail = 0;
1560 int old_mail = 0;
1561 bool flagged = false;
1562 int min_unread_msgno = INT_MAX;
1563 int min_unread = e_cur->vnum;
1564
1565 if (threaded == UT_FLAT)
1566 {
1567 mutt_warning(_("Threading is not enabled"));
1568 return e_cur->vnum;
1569 }
1570
1571 if (!e_cur->thread)
1572 {
1573 return e_cur->vnum;
1574 }
1575
1576 final = e_cur->vnum;
1577 thread = e_cur->thread;
1578 while (thread->parent)
1579 thread = thread->parent;
1580 top = thread;
1581 while (!thread->message)
1582 thread = thread->child;
1583 e_cur = thread->message;
1584 minmsgno = e_cur->msgno;
1585
1586 if (!e_cur->read && e_cur->visible)
1587 {
1588 if (e_cur->old)
1589 old_mail = 2;
1590 else
1591 new_mail = 1;
1592 if (e_cur->msgno < min_unread_msgno)
1593 {
1594 min_unread = e_cur->vnum;
1595 min_unread_msgno = e_cur->msgno;
1596 }
1597 }
1598
1599 if (e_cur->flagged && e_cur->visible)
1600 flagged = true;
1601
1602 if ((e_cur->vnum == -1) && e_cur->visible)
1603 num_hidden++;
1604
1606 {
1607 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1608 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1609 if (e_cur->vnum != -1)
1610 {
1611 e_root = e_cur;
1612 if (flag & MUTT_THREAD_COLLAPSE)
1613 final = e_root->vnum;
1614 }
1615 }
1616
1617 if ((thread == top) && !(thread = thread->child))
1618 {
1619 /* return value depends on action requested */
1621 {
1622 e_cur->num_hidden = num_hidden;
1623 return final;
1624 }
1625 if (flag & MUTT_THREAD_UNREAD)
1626 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1627 if (flag & MUTT_THREAD_NEXT_UNREAD)
1628 return min_unread;
1629 if (flag & MUTT_THREAD_FLAGGED)
1630 return flagged;
1631 }
1632
1633 while (true)
1634 {
1635 e_cur = thread->message;
1636
1637 if (e_cur)
1638 {
1640 {
1641 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1642 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1643 if (!e_root && e_cur->visible)
1644 {
1645 e_root = e_cur;
1646 if (flag & MUTT_THREAD_COLLAPSE)
1647 final = e_root->vnum;
1648 }
1649
1650 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1651 (e_cur->msgno < minmsgno) && e_cur->visible)
1652 {
1653 minmsgno = e_cur->msgno;
1654 final = e_cur->vnum;
1655 }
1656
1657 if (flag & MUTT_THREAD_COLLAPSE)
1658 {
1659 if (e_cur != e_root)
1660 e_cur->vnum = -1;
1661 }
1662 else
1663 {
1664 if (e_cur->visible)
1665 e_cur->vnum = e_cur->msgno;
1666 }
1667 }
1668
1669 if (!e_cur->read && e_cur->visible)
1670 {
1671 if (e_cur->old)
1672 old_mail = 2;
1673 else
1674 new_mail = 1;
1675 if (e_cur->msgno < min_unread_msgno)
1676 {
1677 min_unread = e_cur->vnum;
1678 min_unread_msgno = e_cur->msgno;
1679 }
1680 }
1681
1682 if (e_cur->flagged && e_cur->visible)
1683 flagged = true;
1684
1685 if ((e_cur->vnum == -1) && e_cur->visible)
1686 num_hidden++;
1687 }
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 /* re-traverse the thread and store num_hidden in all headers, with or
1716 * without a virtual index. this will allow ~v to match all collapsed
1717 * messages when switching sort order to non-threaded. */
1718 if (flag & MUTT_THREAD_COLLAPSE)
1719 {
1720 thread = top;
1721 while (true)
1722 {
1723 e_cur = thread->message;
1724 if (e_cur)
1725 e_cur->num_hidden = num_hidden + 1;
1726
1727 if (thread->child)
1728 {
1729 thread = thread->child;
1730 }
1731 else if (thread->next)
1732 {
1733 thread = thread->next;
1734 }
1735 else
1736 {
1737 bool done = false;
1738 while (!thread->next)
1739 {
1740 thread = thread->parent;
1741 if (thread == top)
1742 {
1743 done = true;
1744 break;
1745 }
1746 }
1747 if (done)
1748 break;
1749 thread = thread->next;
1750 }
1751 }
1752 }
1753
1754 /* return value depends on action requested */
1756 return final;
1757 if (flag & MUTT_THREAD_UNREAD)
1758 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1759 if (flag & MUTT_THREAD_NEXT_UNREAD)
1760 return min_unread;
1761 if (flag & MUTT_THREAD_FLAGGED)
1762 return flagged;
1763
1764 return 0;
1765}
@ MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition thread.h:82
@ MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition thread.h:83
@ MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition thread.h:85
@ MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition thread.h:84
@ MUTT_THREAD_COLLAPSE
Collapse an email 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 1774 of file thread.c.

1775{
1776 if (!m || !e)
1777 return 1;
1778
1779 struct MuttThread *threads[2] = { 0 };
1780 int rc;
1781
1782 const enum UseThreads threaded = mutt_thread_style();
1783 if ((threaded == UT_FLAT) || !e->thread)
1784 return 1;
1785
1786 threads[0] = e->thread;
1787 while (threads[0]->parent)
1788 threads[0] = threads[0]->parent;
1789
1790 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1791
1792 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1793 {
1794 /* Descend to the first node that references a real Email.
1795 * Guard against a malformed or partially-built tree:
1796 * a node with no message and no child would otherwise cause a NULL dereference. */
1797 while (threads[i] && !threads[i]->message)
1798 threads[i] = threads[i]->child;
1799
1800 if (!threads[i] || !threads[i]->message)
1801 return 1;
1802 }
1803
1804 if (threaded == UT_REVERSE)
1805 {
1806 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1807 }
1808 else
1809 {
1810 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1811 threads[0]->message->msgno;
1812 }
1813
1814 if (mit == MIT_POSITION)
1815 rc += 1;
1816
1817 return rc;
1818}
@ MIT_POSITION
Our position in the thread.
Definition thread.h:95
+ 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 1825 of file thread.c.

1826{
1827 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NONE);
1828
1829 for (int i = 0; i < m->msg_count; i++)
1830 {
1831 struct Email *e = m->emails[i];
1832 if (!e || !e->env)
1833 continue;
1834
1835 if (e->env->message_id)
1836 mutt_hash_insert(hash, e->env->message_id, e);
1837 }
1838
1839 return hash;
1840}
@ MUTT_HASH_NONE
No flags are set.
Definition hash.h:115
+ 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 1849 of file thread.c.

1850{
1851 if (child == parent)
1852 return false;
1853
1854 mutt_break_thread(child);
1856 mutt_set_flag(m, child, MUTT_TAG, false, true);
1857
1858 child->changed = true;
1859 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1860 return true;
1861}
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 1870 of file thread.c.

1871{
1872 if (!parent || !children || !m)
1873 return false;
1874
1875 bool changed = false;
1876
1877 struct Email **ep = NULL;
1878 ARRAY_FOREACH(ep, children)
1879 {
1880 struct Email *e = *ep;
1881 changed |= link_threads(parent, e, m);
1882 }
1883
1884 return changed;
1885}
#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:1849
+ 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 1891 of file thread.c.

1892{
1893 struct MuttThread *thread = NULL;
1894 struct MuttThread *top = tctx->tree;
1895 while ((thread = top))
1896 {
1897 while (!thread->message)
1898 thread = thread->child;
1899
1900 struct Email *e = thread->message;
1901 if (e->collapsed)
1903 top = top->next;
1904 }
1905}
#define mutt_collapse_thread(e)
Definition thread.h:112
+ 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 1912 of file thread.c.

1913{
1914 struct MuttThread *thread = NULL;
1915 struct MuttThread *top = tctx->tree;
1916 while ((thread = top))
1917 {
1918 while (!thread->message)
1919 thread = thread->child;
1920
1921 struct Email *e = thread->message;
1922
1923 if (e->collapsed != collapse)
1924 {
1925 if (e->collapsed)
1927 else if (mutt_thread_can_collapse(e))
1929 }
1930 top = top->next;
1931 }
1932}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition thread.c:1940
#define mutt_uncollapse_thread(e)
Definition thread.h:113
+ 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 1940 of file thread.c.

1941{
1942 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1943 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1944 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1945 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1946}
#define mutt_thread_contains_flagged(e)
Definition thread.h:115
#define mutt_thread_contains_unread(e)
Definition thread.h:114
+ 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:103

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};