Thanks to visit codestin.com
Credit goes to code.neomutt.org

NeoMutt  2025-12-11-189-gceedb6
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.
 
void mutt_draw_tree (struct ThreadsContext *tctx)
 Draw a tree of threaded emails.
 
static void make_subject_list (struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
 Create a sorted list of all subjects in a thread.
 
static struct MuttThreadfind_subject (struct Mailbox *m, struct MuttThread *cur)
 Find the best possible match for a parent based on subject.
 
static struct HashTablemake_subj_hash (struct Mailbox *m)
 Create a Hash Table for the email subjects.
 
static void pseudo_threads (struct ThreadsContext *tctx)
 Thread messages by subject.
 
void mutt_clear_threads (struct ThreadsContext *tctx)
 Clear the threading of message in a mailbox.
 
static int compare_threads (const void *a, const void *b, void *sdata)
 Helper to sort email threads - Implements sort_t -.
 
static void mutt_sort_subthreads (struct ThreadsContext *tctx, bool init)
 Sort the children of a thread.
 
static void check_subjects (struct MailboxView *mv, bool init)
 Find out which emails' subjects differ from their parent's.
 
static void thread_hash_destructor (int type, void *obj, intptr_t data)
 Free our hash table data - Implements hash_hdata_free_t -.
 
void mutt_sort_threads (struct ThreadsContext *tctx, bool init)
 Sort email threads.
 
int mutt_aside_thread (struct Email *e, bool forwards, bool subthreads)
 Find the next/previous (sub)thread.
 
int mutt_parent_message (struct Email *e, bool find_root)
 Find the parent of a message.
 
off_t mutt_set_vnum (struct Mailbox *m)
 Set the virtual index number of all the messages in a mailbox.
 
int mutt_traverse_thread (struct Email *e_cur, MuttThreadFlags flag)
 Recurse through an email thread, matching messages.
 
int mutt_messages_in_thread (struct Mailbox *m, struct Email *e, enum MessageInThread mit)
 Count the messages in a thread.
 
struct HashTablemutt_make_id_hash (struct Mailbox *m)
 Create a Hash Table for message-ids.
 
static bool link_threads (struct Email *parent, struct Email *child, struct Mailbox *m)
 Forcibly link messages together.
 
bool mutt_link_threads (struct Email *parent, struct EmailArray *children, struct Mailbox *m)
 Forcibly link threads together.
 
void mutt_thread_collapse_collapsed (struct ThreadsContext *tctx)
 Re-collapse threads marked as collapsed.
 
void mutt_thread_collapse (struct ThreadsContext *tctx, bool collapse)
 Toggle collapse.
 
bool mutt_thread_can_collapse (struct Email *e)
 Check whether a thread can be collapsed.
 

Variables

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

Detailed Description

Create/manipulate threading in emails.

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

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

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

Definition in file thread.c.

Function Documentation

◆ mutt_thread_style()

enum UseThreads mutt_thread_style ( void )

Which threading style is active?

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

Definition at line 79 of file thread.c.

80{
81 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
82 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
83 if (c_use_threads > UT_FLAT)
84 return c_use_threads;
85 if ((c_sort & SORT_MASK) != EMAIL_SORT_THREADS)
86 return UT_FLAT;
87 if (c_sort & SORT_REVERSE)
88 return UT_REVERSE;
89 return UT_THREADS;
90}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition helpers.c:71
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition helpers.c:266
#define SORT_MASK
Mask for the sort id.
Definition sort.h: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 thread.h:98
@ UT_THREADS
Normal threading (root above subthreads)
Definition thread.h:99
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition thread.h:100
Container for Accounts, Notifications.
Definition neomutt.h:128
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:134
+ 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 the user disabled subject hiding, display it */
136 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
137 if (!c_hide_thread_subject)
138 return true;
139
140 /* if our subject is different from our parent's, display it */
141 if (e->subject_changed)
142 return true;
143
144 /* if our subject is different from that of our closest previously displayed
145 * sibling, display the subject */
146 for (tmp = tree->prev; tmp; tmp = tmp->prev)
147 {
148 e = tmp->message;
149 if (e && is_visible(e))
150 {
151 if (e->subject_changed)
152 return true;
153 break;
154 }
155 }
156
157 /* if there is a parent-to-child subject change anywhere between us and our
158 * closest displayed ancestor, display the subject */
159 for (tmp = tree->parent; tmp; tmp = tmp->parent)
160 {
161 e = tmp->message;
162 if (e)
163 {
164 if (is_visible(e))
165 return false;
166 if (e->subject_changed)
167 return true;
168 }
169 }
170
171 /* if we have no visible parent or previous sibling, display the subject */
172 return true;
173}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
static bool is_visible(struct Email *e)
Is the message visible?
Definition thread.c:120
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 179 of file thread.c.

180{
181 if (!tctx || !tctx->mailbox_view)
182 return;
183
184 struct Mailbox *m = tctx->mailbox_view->mailbox;
185
186 const bool reverse = (mutt_thread_style() == UT_REVERSE);
187 struct MuttThread *tree = tctx->tree;
188 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
189
190 while (tree)
191 {
192 while (!tree->message)
193 tree = tree->child;
194
195 *array = tree->message;
196 array += reverse ? -1 : 1;
197
198 if (tree->child)
199 {
200 tree = tree->child;
201 }
202 else
203 {
204 while (tree)
205 {
206 if (tree->next)
207 {
208 tree = tree->next;
209 break;
210 }
211 else
212 {
213 tree = tree->parent;
214 }
215 }
216 }
217 }
218}
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: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 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 232 of file thread.c.

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

354{
355 struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
356 tctx->mailbox_view = mv;
357 return tctx;
358}
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:48
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 364 of file thread.c.

365{
366 if (!ptr || !*ptr)
367 {
368 return;
369 }
370
371 struct ThreadsContext *tctx = *ptr;
372
373 mutt_hash_free(&tctx->hash);
374
375 FREE(ptr);
376}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:458
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:

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

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

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

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

621{
622 if (!m)
623 return NULL;
624
626
627 for (int i = 0; i < m->msg_count; i++)
628 {
629 struct Email *e = m->emails[i];
630 if (!e || !e->env)
631 continue;
632 if (e->env->real_subj)
633 mutt_hash_insert(hash, e->env->real_subj, e);
634 }
635
636 return hash;
637}
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:336
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:260
#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 645 of file thread.c.

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

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

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

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

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

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

◆ mutt_parent_message()

int mutt_parent_message ( struct Email * e,
bool find_root )

Find the parent of a message.

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

Definition at line 1352 of file thread.c.

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

◆ mutt_set_vnum()

off_t mutt_set_vnum ( struct Mailbox * m)

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

Parameters
mMailbox
Return values
numSize in bytes of all messages shown

Definition at line 1402 of file thread.c.

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

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

◆ mutt_messages_in_thread()

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

Count the messages in a thread.

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

Definition at line 1655 of file thread.c.

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

◆ mutt_make_id_hash()

struct HashTable * mutt_make_id_hash ( struct Mailbox * m)

Create a Hash Table for message-ids.

Parameters
mMailbox
Return values
ptrNewly allocated Hash Table

Definition at line 1700 of file thread.c.

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

1725{
1726 if (child == parent)
1727 return false;
1728
1729 mutt_break_thread(child);
1731 mutt_set_flag(m, child, MUTT_TAG, false, true);
1732
1733 child->changed = true;
1734 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1735 return true;
1736}
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 1745 of file thread.c.

1746{
1747 if (!parent || !children || !m)
1748 return false;
1749
1750 bool changed = false;
1751
1752 struct Email **ep = NULL;
1753 ARRAY_FOREACH(ep, children)
1754 {
1755 struct Email *e = *ep;
1756 changed |= link_threads(parent, e, m);
1757 }
1758
1759 return changed;
1760}
#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:1724
+ 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 1766 of file thread.c.

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

◆ mutt_thread_collapse()

void mutt_thread_collapse ( struct ThreadsContext * tctx,
bool collapse )

Toggle collapse.

Parameters
tctxThreading context
collapseCollapse / uncollapse

Definition at line 1787 of file thread.c.

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

◆ mutt_thread_can_collapse()

bool mutt_thread_can_collapse ( struct Email * e)

Check whether a thread can be collapsed.

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

Definition at line 1815 of file thread.c.

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

Variable Documentation

◆ UseThreadsMethods

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

Choices for '$use_threads' for the index.

Definition at line 50 of file thread.c.

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

◆ UseThreadsTypeDef

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

Data for the $use_threads enumeration.

Definition at line 64 of file thread.c.

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