NeoMutt  2025-09-05-55-g97fc89
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mutt_thread.c File Reference

Create/manipulate threading in emails. More...

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

Go to the source code of this file.

Functions

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

Variables

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

Detailed Description

Create/manipulate threading in emails.

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

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

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

Definition in file mutt_thread.c.

Function Documentation

◆ mutt_thread_style()

enum UseThreads mutt_thread_style ( void )

Which threading style is active?

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

Definition at line 80 of file mutt_thread.c.

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

◆ get_use_threads_str()

const char * get_use_threads_str ( enum UseThreads value)

Convert UseThreads enum to string.

Parameters
valueValue to convert
Return values
ptrString form of value

Definition at line 98 of file mutt_thread.c.

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

◆ is_visible()

static bool is_visible ( struct Email * e)
static

Is the message visible?

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

Definition at line 121 of file mutt_thread.c.

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

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

◆ linearize_tree()

static void linearize_tree ( struct ThreadsContext * tctx)
static

Flatten an email thread.

Parameters
tctxThreading context

Definition at line 180 of file mutt_thread.c.

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

◆ calculate_visibility()

static void calculate_visibility ( struct MuttThread * tree,
int * max_depth )
static

Are tree nodes visible.

Parameters
treeThreads tree
max_depthMaximum depth to check

this calculates whether a node is the root of a subtree that has visible nodes, whether a node itself is visible, whether, if invisible, it has depth anyway, and whether any of its later siblings are roots of visible subtrees. while it's at it, it frees the old thread display, so we can skip parts of the tree in mutt_draw_tree() if we've decided here that we don't care about them any more.

Definition at line 233 of file mutt_thread.c.

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

355{
356 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
357 tctx->mailbox_view = mv;
358 return tctx;
359}
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:47
The "current" threading state.
Definition mutt_thread.h:42
+ Here is the caller graph for this function:

◆ mutt_thread_ctx_free()

void mutt_thread_ctx_free ( struct ThreadsContext ** ptr)

Finalize a threading context.

Parameters
ptrThreading context to free

Definition at line 365 of file mutt_thread.c.

366{
367 if (!ptr || !*ptr)
368 {
369 return;
370 }
371
372 struct ThreadsContext *tctx = *ptr;
373
374 mutt_hash_free(&tctx->hash);
375
376 FREE(ptr);
377}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:457
struct HashTable * hash
Hash Table: "message-id" -> MuttThread.
Definition mutt_thread.h:45
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_draw_tree()

void mutt_draw_tree ( struct ThreadsContext * tctx)

Draw a tree of threaded emails.

Parameters
tctxThreading context

Since the graphics characters have a value >255, I have to resort to using escape sequences to pass the information to print_enriched_string(). These are the macros MUTT_TREE_* defined in mutt.h.

ncurses should automatically use the default ASCII characters instead of graphics chars on terminals which don't support them (see the man page for curs_addch).

Definition at line 391 of file mutt_thread.c.

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

◆ make_subject_list()

static void make_subject_list ( struct ListHead * subjects,
struct MuttThread * cur,
time_t * dateptr )
static

Create a sorted list of all subjects in a thread.

Parameters
[out]subjectsString List of subjects
[in]curEmail Thread
[out]dateptrEarliest date found in thread

Since we may be trying to attach as a pseudo-thread a MuttThread that has no message, we have to make a list of all the subjects of its most immediate existing descendants.

Definition at line 521 of file mutt_thread.c.

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

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

◆ make_subj_hash()

static struct HashTable * make_subj_hash ( struct Mailbox * m)
static

Create a Hash Table for the email subjects.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 621 of file mutt_thread.c.

622{
623 if (!m)
624 return NULL;
625
627
628 for (int i = 0; i < m->msg_count; i++)
629 {
630 struct Email *e = m->emails[i];
631 if (!e || !e->env)
632 continue;
633 if (e->env->real_subj)
634 mutt_hash_insert(hash, e->env->real_subj, e);
635 }
636
637 return hash;
638}
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition hash.c:335
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:259
#define MUTT_HASH_ALLOW_DUPS
allow duplicate keys to be inserted
Definition hash.h: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 646 of file mutt_thread.c.

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

◆ mutt_clear_threads()

void mutt_clear_threads ( struct ThreadsContext * tctx)

Clear the threading of message in a mailbox.

Parameters
tctxThreading context

Definition at line 717 of file mutt_thread.c.

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

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

◆ check_subjects()

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

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

Parameters
mvMailbox View
initIf true, rebuild the thread

Definition at line 978 of file mutt_thread.c.

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

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

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

◆ mutt_parent_message()

int mutt_parent_message ( struct Email * e,
bool find_root )

Find the parent of a message.

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

Definition at line 1353 of file mutt_thread.c.

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

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

◆ mutt_traverse_thread()

int mutt_traverse_thread ( struct Email * e_cur,
MuttThreadFlags flag )

Recurse through an email thread, matching messages.

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

Definition at line 1437 of file mutt_thread.c.

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

◆ mutt_messages_in_thread()

int mutt_messages_in_thread ( struct Mailbox * m,
struct Email * e,
enum MessageInThread mit )

Count the messages in a thread.

Parameters
mMailbox
eEmail
mitFlag, e.g. MIT_NUM_MESSAGES
Return values
numNumber of message / Our position

Definition at line 1656 of file mutt_thread.c.

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

◆ mutt_make_id_hash()

struct HashTable * mutt_make_id_hash ( struct Mailbox * m)

Create a Hash Table for message-ids.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 1701 of file mutt_thread.c.

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

1726{
1727 if (child == parent)
1728 return false;
1729
1730 mutt_break_thread(child);
1732 mutt_set_flag(m, child, MUTT_TAG, false, true);
1733
1734 child->changed = true;
1735 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1736 return true;
1737}
#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:56
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:255
@ MUTT_TAG
Tagged messages.
Definition mutt.h:80
bool changed
Email has been edited.
Definition email.h:77
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition envelope.h:90
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition thread.c:229
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_link_threads()

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

Forcibly link threads together.

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

Definition at line 1746 of file mutt_thread.c.

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

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

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

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

Variable Documentation

◆ UseThreadsMethods

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

Choices for '$use_threads' for the index.

Definition at line 51 of file mutt_thread.c.

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

◆ 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 65 of file mutt_thread.c.

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