NeoMutt  2022-04-29-215-gc12b98
Teaching an old dog new tricks
DOXYGEN
mutt_thread.c File Reference

Create/manipulate threading in emails. More...

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

Go to the source code of this file.

Data Structures

struct  ThreadsContext
 The "current" threading state. More...
 

Functions

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

Variables

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

Detailed Description

Create/manipulate threading in emails.

Authors
  • Michael R. Elkins

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

90{
91 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
92 const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
93 if (c_use_threads > UT_FLAT)
94 return c_use_threads;
95 if ((c_sort & SORT_MASK) != SORT_THREADS)
96 return UT_FLAT;
97 if (c_sort & SORT_REVERSE)
98 return UT_REVERSE;
99 return UT_THREADS;
100}
unsigned char cs_subset_enum(const struct ConfigSubset *sub, const char *name)
Get a enumeration config item by name.
Definition: helpers.c:97
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition: helpers.c:292
@ UT_FLAT
Unthreaded.
Definition: mutt_thread.h:85
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:86
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:87
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:78
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:49
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:79
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
+ 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 107 of file mutt_thread.c.

108{
110}
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:61
+ 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 131 of file mutt_thread.c.

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

142{
143 struct MuttThread *tmp = NULL;
144 struct MuttThread *tree = e->thread;
145
146 /* if the user disabled subject hiding, display it */
147 const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
148 if (!c_hide_thread_subject)
149 return true;
150
151 /* if our subject is different from our parent's, display it */
152 if (e->subject_changed)
153 return true;
154
155 /* if our subject is different from that of our closest previously displayed
156 * sibling, display the subject */
157 for (tmp = tree->prev; tmp; tmp = tmp->prev)
158 {
159 e = tmp->message;
160 if (e && is_visible(e))
161 {
162 if (e->subject_changed)
163 return true;
164 break;
165 }
166 }
167
168 /* if there is a parent-to-child subject change anywhere between us and our
169 * closest displayed ancestor, display the subject */
170 for (tmp = tree->parent; tmp; tmp = tmp->parent)
171 {
172 e = tmp->message;
173 if (e)
174 {
175 if (is_visible(e))
176 return false;
177 if (e->subject_changed)
178 return true;
179 }
180 }
181
182 /* if we have no visible parent or previous sibling, display the subject */
183 return true;
184}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
static bool is_visible(struct Email *e)
Is the message visible?
Definition: mutt_thread.c:131
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:35
struct MuttThread * parent
Parent of this Thread.
Definition: thread.h:45
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
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 190 of file mutt_thread.c.

191{
192 if (!tctx || !tctx->mailbox)
193 return;
194
195 struct Mailbox *m = tctx->mailbox;
196
197 const bool reverse = (mutt_thread_style() == UT_REVERSE);
198 struct MuttThread *tree = tctx->tree;
199 struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
200
201 while (tree)
202 {
203 while (!tree->message)
204 tree = tree->child;
205
206 *array = tree->message;
207 array += reverse ? -1 : 1;
208
209 if (tree->child)
210 tree = tree->child;
211 else
212 {
213 while (tree)
214 {
215 if (tree->next)
216 {
217 tree = tree->next;
218 break;
219 }
220 else
221 tree = tree->parent;
222 }
223 }
224 }
225}
enum UseThreads mutt_thread_style(void)
Which threading style is active?
Definition: mutt_thread.c:89
The envelope/body of an email.
Definition: email.h:37
char * tree
Character string to print thread tree.
Definition: email.h:124
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 MuttThread * tree
Top of thread tree.
Definition: mutt_thread.c:52
struct Mailbox * mailbox
Current mailbox.
Definition: mutt_thread.c:51
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ calculate_visibility()

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

Are tree nodes visible.

Parameters
treeThreads tree
max_depthMaximum depth to check

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

Definition at line 239 of file mutt_thread.c.

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

Initialize a threading context.

Parameters
mCurrent mailbox
Return values
ptrThreading context

Definition at line 352 of file mutt_thread.c.

353{
354 struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
355 tctx->mailbox = m;
356 tctx->tree = NULL;
357 tctx->hash = NULL;
358 return tctx;
359}
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
The "current" threading state.
Definition: mutt_thread.c:50
struct HashTable * hash
Hash table for threads.
Definition: mutt_thread.c:53
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_thread_ctx_free()

void mutt_thread_ctx_free ( struct ThreadsContext **  tctx)

Finalize a threading context.

Parameters
tctxThreading context to finalize

Definition at line 365 of file mutt_thread.c.

366{
367 (*tctx)->mailbox = NULL;
368 mutt_hash_free(&(*tctx)->hash);
369 FREE(tctx);
370}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:457
+ 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 384 of file mutt_thread.c.

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

513{
514 struct MuttThread *start = cur;
515 struct Envelope *env = NULL;
516 time_t thisdate;
517 int rc = 0;
518
519 while (true)
520 {
521 while (!cur->message)
522 cur = cur->child;
523
524 if (dateptr)
525 {
526 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
527 thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
528 if ((*dateptr == 0) || (thisdate < *dateptr))
529 *dateptr = thisdate;
530 }
531
532 env = cur->message->env;
533 const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
534 if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
535 {
536 struct ListNode *np = NULL;
537 STAILQ_FOREACH(np, subjects, entries)
538 {
539 rc = mutt_str_cmp(env->real_subj, np->data);
540 if (rc >= 0)
541 break;
542 }
543 if (!np)
544 mutt_list_insert_head(subjects, env->real_subj);
545 else if (rc > 0)
546 mutt_list_insert_after(subjects, np, env->real_subj);
547 }
548
549 while (!cur->next && (cur != start))
550 {
551 cur = cur->parent;
552 }
553 if (cur == start)
554 break;
555 cur = cur->next;
556 }
557}
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:45
struct ListNode * mutt_list_insert_after(struct ListHead *h, struct ListNode *n, char *s)
Insert a string after a given ListNode.
Definition: list.c:84
int mutt_str_cmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:470
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
struct Envelope * env
Envelope information.
Definition: email.h:66
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
The header of an Email.
Definition: envelope.h:57
char * subject
Email's subject.
Definition: envelope.h:70
char * real_subj
Offset of the real subject.
Definition: envelope.h:71
A List node for strings.
Definition: list.h:35
char * data
String.
Definition: list.h:36
+ 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 568 of file mutt_thread.c.

569{
570 if (!m)
571 return NULL;
572
573 struct HashElem *ptr = NULL;
574 struct MuttThread *tmp = NULL, *last = NULL;
575 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
576 time_t date = 0;
577
578 make_subject_list(&subjects, cur, &date);
579
580 struct ListNode *np = NULL;
581 STAILQ_FOREACH(np, &subjects, entries)
582 {
583 for (ptr = mutt_hash_find_bucket(m->subj_hash, np->data); ptr; ptr = ptr->next)
584 {
585 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
586 tmp = ((struct Email *) ptr->data)->thread;
587 if ((tmp != cur) && /* don't match the same message */
588 !tmp->fake_thread && /* don't match pseudo threads */
589 tmp->message->subject_changed && /* only match interesting replies */
590 !is_descendant(tmp, cur) && /* don't match in the same thread */
591 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
592 (!last || (c_thread_received ?
593 (last->message->received < tmp->message->received) :
594 (last->message->date_sent < tmp->message->date_sent))) &&
595 tmp->message->env->real_subj &&
597 {
598 last = tmp; /* best match so far */
599 }
600 }
601 }
602
603 mutt_list_clear(&subjects);
604 return last;
605}
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:167
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
Create a sorted list of all subjects in a thread.
Definition: mutt_thread.c:512
#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 by subject.
Definition: mailbox.h:124
bool is_descendant(struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:44
+ 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 612 of file mutt_thread.c.

613{
614 if (!m)
615 return NULL;
616
618
619 for (int i = 0; i < m->msg_count; i++)
620 {
621 struct Email *e = m->emails[i];
622 if (!e || !e->env)
623 continue;
624 if (e->env->real_subj)
625 mutt_hash_insert(hash, e->env->real_subj, e);
626 }
627
628 return hash;
629}
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:112
A Hash Table.
Definition: hash.h:97
+ 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 637 of file mutt_thread.c.

638{
639 if (!tctx || !tctx->mailbox)
640 return;
641
642 struct Mailbox *m = tctx->mailbox;
643
644 struct MuttThread *tree = tctx->tree;
645 struct MuttThread *top = tree;
646 struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
647 *nextchild = NULL;
648
649 if (!m->subj_hash)
651
652 while (tree)
653 {
654 cur = tree;
655 tree = tree->next;
656 parent = find_subject(m, cur);
657 if (parent)
658 {
659 cur->fake_thread = true;
660 unlink_message(&top, cur);
662 parent->sort_children = true;
663 tmp = cur;
664 while (true)
665 {
666 while (!tmp->message)
667 tmp = tmp->child;
668
669 /* if the message we're attaching has pseudo-children, they
670 * need to be attached to its parent, so move them up a level.
671 * but only do this if they have the same real subject as the
672 * parent, since otherwise they rightly belong to the message
673 * we're attaching. */
674 if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
676 {
677 tmp->message->subject_changed = false;
678
679 for (curchild = tmp->child; curchild;)
680 {
681 nextchild = curchild->next;
682 if (curchild->fake_thread)
683 {
684 unlink_message(&tmp->child, curchild);
685 insert_message(&parent->child, parent, curchild);
686 }
687 curchild = nextchild;
688 }
689 }
690
691 while (!tmp->next && (tmp != cur))
692 {
693 tmp = tmp->parent;
694 }
695 if (tmp == cur)
696 break;
697 tmp = tmp->next;
698 }
699 }
700 }
701 tctx->tree = top;
702}
static struct HashTable * make_subj_hash(struct Mailbox *m)
Create a Hash Table for the email subjects.
Definition: mutt_thread.c:612
static struct MuttThread * find_subject(struct Mailbox *m, struct MuttThread *cur)
Find the best possible match for a parent based on subject.
Definition: mutt_thread.c:568
bool sort_children
Sort the children.
Definition: thread.h:38
void unlink_message(struct MuttThread **old, struct MuttThread *cur)
Break the message out of the thread.
Definition: thread.c:64
void insert_message(struct MuttThread **add, struct MuttThread *parent, struct MuttThread *cur)
Insert a message into a thread.
Definition: thread.c:102
+ 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 708 of file mutt_thread.c.

709{
710 if (!tctx || !tctx->mailbox || !tctx->mailbox->emails || !tctx->tree)
711 return;
712
713 for (int i = 0; i < tctx->mailbox->msg_count; i++)
714 {
715 struct Email *e = tctx->mailbox->emails[i];
716 if (!e)
717 break;
718
719 /* mailbox may have been only partially read */
720 e->thread = NULL;
721 e->threaded = false;
722 }
723 tctx->tree = NULL;
724 mutt_hash_free(&tctx->hash);
725}
bool threaded
Used for threading.
Definition: email.h:108
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ compare_threads()

static int compare_threads ( const void *  a,
const void *  b,
void *  arg 
)
static

qsort_r() function for comparing email threads

Parameters
aFirst thread to compare
bSecond thread to compare
argThreadsContext for how to compare
Return values
<0a precedes b
0a and b are identical
>0b precedes a

Definition at line 736 of file mutt_thread.c.

737{
738 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
739 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
740 const struct ThreadsContext *tctx = arg;
741 assert(ta->parent == tb->parent);
742 /* If c_sort ties, remember we are building the thread array in
743 * reverse from the index the mails had in the mailbox. */
744 if (ta->parent)
745 {
748 }
749 else
750 {
752 mx_type(tctx->mailbox), tctx->c_sort,
754 }
755}
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1837
@ SORT_ORDER
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:48
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
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
short c_sort_aux
Last sort_aux method.
Definition: mutt_thread.c:55
short c_sort
Last sort method.
Definition: mutt_thread.c:54
+ 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 762 of file mutt_thread.c.

763{
764 struct MuttThread *thread = tctx->tree;
765 if (!thread)
766 return;
767
768 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
769 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
770 struct Email *oldsort_thread_key = NULL;
771 int i, array_size;
772 bool sort_top = false;
773
774 /* we put things into the array backwards to save some cycles,
775 * but we want to have to move less stuff around if we're
776 * resorting, so we sort backwards and then put them back
777 * in reverse order so they're forwards */
778 const bool reverse = (mutt_thread_style() == UT_REVERSE);
779 short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
780 short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
781 if ((c_sort & SORT_MASK) == SORT_THREADS)
782 {
783 assert(!(c_sort & SORT_REVERSE) != reverse);
784 assert(cs_subset_enum(NeoMutt->sub, "use_threads") == UT_UNSET);
785 c_sort = c_sort_aux;
786 }
787 c_sort ^= SORT_REVERSE;
788 c_sort_aux ^= SORT_REVERSE;
789 if (init || tctx->c_sort != c_sort || tctx->c_sort_aux != c_sort_aux)
790 {
791 tctx->c_sort = c_sort;
792 tctx->c_sort_aux = c_sort_aux;
793 init = true;
794 }
795
796 top = thread;
797
798 array_size = 256;
799 array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
800 while (true)
801 {
802 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
803 {
804 thread->sort_thread_key = NULL;
805 thread->sort_aux_key = NULL;
806
807 if (thread->parent)
808 thread->parent->sort_children = true;
809 else
810 sort_top = true;
811 }
812
813 if (thread->child)
814 {
816 continue;
817 }
818 else
819 {
820 /* if it has no children, it must be real. sort it on its own merits */
823
824 if (thread->next)
825 {
826 thread = thread->next;
827 continue;
828 }
829 }
830
831 while (!thread->next)
832 {
833 /* if it has siblings and needs to be sorted, sort it... */
834 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
835 {
836 /* put them into the array */
837 for (i = 0; thread; i++, thread = thread->prev)
838 {
839 if (i >= array_size)
840 mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
841
842 array[i] = thread;
843 }
844
845 mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
846
847 /* attach them back together. make thread the last sibling. */
848 thread = array[0];
849 thread->next = NULL;
850 array[i - 1]->prev = NULL;
851
852 if (thread->parent)
853 thread->parent->child = array[i - 1];
854 else
855 top = array[i - 1];
856
857 while (--i)
858 {
859 array[i - 1]->prev = array[i];
860 array[i]->next = array[i - 1];
861 }
862 }
863
864 if (thread->parent)
865 {
866 tmp = thread;
868
870 {
871 /* we just sorted its children */
872 thread->sort_children = false;
873
874 oldsort_aux_key = thread->sort_aux_key;
875 oldsort_thread_key = thread->sort_thread_key;
876
877 /* update sort keys. sort_aux_key will be the first or last
878 * sibling, as appropriate... */
880 sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
882 tmp->sort_aux_key;
883
884 if (c_sort_aux & SORT_LAST)
885 {
886 if (!thread->sort_aux_key ||
887 (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mx_type(tctx->mailbox),
888 c_sort_aux | SORT_REVERSE, SORT_ORDER) > 0))
889 {
890 thread->sort_aux_key = sort_aux_key;
891 }
892 }
893 else if (!thread->sort_aux_key)
894 thread->sort_aux_key = sort_aux_key;
895
896 /* ...but sort_thread_key may require searching the entire
897 * list of siblings */
898 if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
899 {
901 }
902 else
903 {
904 if (thread->message)
905 {
907 }
908 else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
909 {
910 thread->sort_thread_key = tmp->sort_thread_key;
911 }
912 else
913 {
915 }
916 if (c_sort & SORT_LAST)
917 {
918 for (tmp = thread->child; tmp; tmp = tmp->next)
919 {
920 if (tmp->sort_thread_key == thread->sort_thread_key)
921 continue;
922 if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key,
923 mx_type(tctx->mailbox),
924 c_sort | SORT_REVERSE, SORT_ORDER) > 0))
925 {
926 thread->sort_thread_key = tmp->sort_thread_key;
927 }
928 }
929 }
930 }
931
932 /* if a sort_key has changed, we need to resort it and siblings */
933 if ((oldsort_aux_key != thread->sort_aux_key) ||
934 (oldsort_thread_key != thread->sort_thread_key))
935 {
936 if (thread->parent)
937 thread->parent->sort_children = true;
938 else
939 sort_top = true;
940 }
941 }
942 }
943 else
944 {
945 FREE(&array);
946 tctx->tree = top;
947 return;
948 }
949 }
950
951 thread = thread->next;
952 }
953}
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
static int compare_threads(const void *a, const void *b, void *arg)
qsort_r() function for comparing email threads
Definition: mutt_thread.c:736
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:84
void mutt_qsort_r(void *base, size_t nmemb, size_t size, qsort_r_compar_t compar, void *arg)
Sort an array, where the comparator has access to opaque data rather than requiring global variables.
Definition: qsort_r.c:76
#define SORT_LAST
Sort thread by last-X, e.g. received date.
Definition: sort2.h:80
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ check_subjects()

static void check_subjects ( struct Mailbox m,
bool  init 
)
static

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

Parameters
mMailbox
initIf true, rebuild the thread

Definition at line 960 of file mutt_thread.c.

961{
962 if (!m)
963 return;
964
965 for (int i = 0; i < m->msg_count; i++)
966 {
967 struct Email *e = m->emails[i];
968 if (!e || !e->thread)
969 continue;
970
971 if (e->thread->check_subject)
972 e->thread->check_subject = false;
973 else if (!init)
974 continue;
975
976 /* figure out which messages have subjects different than their parents' */
977 struct MuttThread *tmp = e->thread->parent;
978 while (tmp && !tmp->message)
979 {
980 tmp = tmp->parent;
981 }
982
983 if (!tmp)
984 e->subject_changed = true;
985 else if (e->env->real_subj && tmp->message->env->real_subj)
986 {
988 }
989 else
990 {
991 e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
992 }
993 }
994}
bool check_subject
Should the Subject be checked?
Definition: thread.h:39
+ 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 1001 of file mutt_thread.c.

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

Definition at line 1249 of file mutt_thread.c.

1250{
1251 struct MuttThread *cur = NULL;
1252 struct Email *e_tmp = NULL;
1253
1254 const enum UseThreads threaded = mutt_thread_style();
1255 if (threaded == UT_FLAT)
1256 {
1257 mutt_error(_("Threading is not enabled"));
1258 return e->vnum;
1259 }
1260
1261 cur = e->thread;
1262
1263 if (subthreads)
1264 {
1265 if (forwards ^ (threaded == UT_REVERSE))
1266 {
1267 while (!cur->next && cur->parent)
1268 cur = cur->parent;
1269 }
1270 else
1271 {
1272 while (!cur->prev && cur->parent)
1273 cur = cur->parent;
1274 }
1275 }
1276 else
1277 {
1278 while (cur->parent)
1279 cur = cur->parent;
1280 }
1281
1282 if (forwards ^ (threaded == UT_REVERSE))
1283 {
1284 do
1285 {
1286 cur = cur->next;
1287 if (!cur)
1288 return -1;
1289 e_tmp = find_virtual(cur, false);
1290 } while (!e_tmp);
1291 }
1292 else
1293 {
1294 do
1295 {
1296 cur = cur->prev;
1297 if (!cur)
1298 return -1;
1299 e_tmp = find_virtual(cur, true);
1300 } while (!e_tmp);
1301 }
1302
1303 return e_tmp->vnum;
1304}
#define mutt_error(...)
Definition: logging.h:87
#define _(a)
Definition: message.h:28
UseThreads
Which threading style is active, $use_threads.
Definition: mutt_thread.h:83
struct Email * find_virtual(struct MuttThread *cur, bool reverse)
Find an email with a Virtual message number.
Definition: thread.c:130
+ 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 1313 of file mutt_thread.c.

1314{
1315 if (!e)
1316 return -1;
1317
1318 struct MuttThread *thread = NULL;
1319 struct Email *e_parent = NULL;
1320
1321 if (!mutt_using_threads())
1322 {
1323 mutt_error(_("Threading is not enabled"));
1324 return e->vnum;
1325 }
1326
1327 /* Root may be the current message */
1328 if (find_root)
1329 e_parent = e;
1330
1331 for (thread = e->thread->parent; thread; thread = thread->parent)
1332 {
1333 e = thread->message;
1334 if (e)
1335 {
1336 e_parent = e;
1337 if (!find_root)
1338 break;
1339 }
1340 }
1341
1342 if (!e_parent)
1343 {
1344 mutt_error(_("Parent message is not available"));
1345 return -1;
1346 }
1347 if (!is_visible(e_parent))
1348 {
1349 if (find_root)
1350 mutt_error(_("Root message is not visible in this limited view"));
1351 else
1352 mutt_error(_("Parent message is not visible in this limited view"));
1353 return -1;
1354 }
1355 return e_parent->vnum;
1356}
#define mutt_using_threads()
Definition: mutt_thread.h:100
+ 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
mumSize in bytes of all messages shown

Definition at line 1363 of file mutt_thread.c.

1364{
1365 if (!m)
1366 return 0;
1367
1368 off_t vsize = 0;
1369 const int padding = mx_msg_padding_size(m);
1370
1371 m->vcount = 0;
1372
1373 for (int i = 0; i < m->msg_count; i++)
1374 {
1375 struct Email *e = m->emails[i];
1376 if (!e)
1377 break;
1378
1379 if (e->vnum >= 0)
1380 {
1381 e->vnum = m->vcount;
1382 m->v2r[m->vcount] = i;
1383 m->vcount++;
1384 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1385 }
1386 }
1387
1388 return vsize;
1389}
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1549
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:80
struct Body * body
List of MIME parts.
Definition: email.h:67
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 1397 of file mutt_thread.c.

1398{
1399 struct MuttThread *thread = NULL, *top = NULL;
1400 struct Email *e_root = NULL;
1401 const enum UseThreads threaded = mutt_thread_style();
1402 int final, reverse = (threaded == UT_REVERSE), minmsgno;
1403 int num_hidden = 0, new_mail = 0, old_mail = 0;
1404 bool flagged = false;
1405 int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1406
1407 if (threaded == UT_FLAT)
1408 {
1409 mutt_error(_("Threading is not enabled"));
1410 return e_cur->vnum;
1411 }
1412
1413 if (!e_cur->thread)
1414 {
1415 return e_cur->vnum;
1416 }
1417
1418 final = e_cur->vnum;
1419 thread = e_cur->thread;
1420 while (thread->parent)
1421 thread = thread->parent;
1422 top = thread;
1423 while (!thread->message)
1424 thread = thread->child;
1425 e_cur = thread->message;
1426 minmsgno = e_cur->msgno;
1427
1428 if (!e_cur->read && e_cur->visible)
1429 {
1430 if (e_cur->old)
1431 old_mail = 2;
1432 else
1433 new_mail = 1;
1434 if (e_cur->msgno < min_unread_msgno)
1435 {
1436 min_unread = e_cur->vnum;
1437 min_unread_msgno = e_cur->msgno;
1438 }
1439 }
1440
1441 if (e_cur->flagged && e_cur->visible)
1442 flagged = true;
1443
1444 if ((e_cur->vnum == -1) && e_cur->visible)
1445 num_hidden++;
1446
1448 {
1449 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1450 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1451 if (e_cur->vnum != -1)
1452 {
1453 e_root = e_cur;
1454 if (flag & MUTT_THREAD_COLLAPSE)
1455 final = e_root->vnum;
1456 }
1457 }
1458
1459 if ((thread == top) && !(thread = thread->child))
1460 {
1461 /* return value depends on action requested */
1463 {
1464 e_cur->num_hidden = num_hidden;
1465 return final;
1466 }
1467 if (flag & MUTT_THREAD_UNREAD)
1468 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1469 if (flag & MUTT_THREAD_NEXT_UNREAD)
1470 return min_unread;
1471 if (flag & MUTT_THREAD_FLAGGED)
1472 return flagged;
1473 }
1474
1475 while (true)
1476 {
1477 e_cur = thread->message;
1478
1479 if (e_cur)
1480 {
1482 {
1483 e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1484 e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1485 if (!e_root && e_cur->visible)
1486 {
1487 e_root = e_cur;
1488 if (flag & MUTT_THREAD_COLLAPSE)
1489 final = e_root->vnum;
1490 }
1491
1492 if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1493 (e_cur->msgno < minmsgno) && e_cur->visible)
1494 {
1495 minmsgno = e_cur->msgno;
1496 final = e_cur->vnum;
1497 }
1498
1499 if (flag & MUTT_THREAD_COLLAPSE)
1500 {
1501 if (e_cur != e_root)
1502 e_cur->vnum = -1;
1503 }
1504 else
1505 {
1506 if (e_cur->visible)
1507 e_cur->vnum = e_cur->msgno;
1508 }
1509 }
1510
1511 if (!e_cur->read && e_cur->visible)
1512 {
1513 if (e_cur->old)
1514 old_mail = 2;
1515 else
1516 new_mail = 1;
1517 if (e_cur->msgno < min_unread_msgno)
1518 {
1519 min_unread = e_cur->vnum;
1520 min_unread_msgno = e_cur->msgno;
1521 }
1522 }
1523
1524 if (e_cur->flagged && e_cur->visible)
1525 flagged = true;
1526
1527 if ((e_cur->vnum == -1) && e_cur->visible)
1528 num_hidden++;
1529 }
1530
1531 if (thread->child)
1532 thread = thread->child;
1533 else if (thread->next)
1534 thread = thread->next;
1535 else
1536 {
1537 bool done = false;
1538 while (!thread->next)
1539 {
1540 thread = thread->parent;
1541 if (thread == top)
1542 {
1543 done = true;
1544 break;
1545 }
1546 }
1547 if (done)
1548 break;
1549 thread = thread->next;
1550 }
1551 }
1552
1553 /* re-traverse the thread and store num_hidden in all headers, with or
1554 * without a virtual index. this will allow ~v to match all collapsed
1555 * messages when switching sort order to non-threaded. */
1556 if (flag & MUTT_THREAD_COLLAPSE)
1557 {
1558 thread = top;
1559 while (true)
1560 {
1561 e_cur = thread->message;
1562 if (e_cur)
1563 e_cur->num_hidden = num_hidden + 1;
1564
1565 if (thread->child)
1566 thread = thread->child;
1567 else if (thread->next)
1568 thread = thread->next;
1569 else
1570 {
1571 bool done = false;
1572 while (!thread->next)
1573 {
1574 thread = thread->parent;
1575 if (thread == top)
1576 {
1577 done = true;
1578 break;
1579 }
1580 }
1581 if (done)
1582 break;
1583 thread = thread->next;
1584 }
1585 }
1586 }
1587
1588 /* return value depends on action requested */
1590 return final;
1591 if (flag & MUTT_THREAD_UNREAD)
1592 return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1593 if (flag & MUTT_THREAD_NEXT_UNREAD)
1594 return min_unread;
1595 if (flag & MUTT_THREAD_FLAGGED)
1596 return flagged;
1597
1598 return 0;
1599}
#define MUTT_THREAD_UNREAD
Count unread emails in a thread.
Definition: mutt_thread.h:66
#define MUTT_THREAD_UNCOLLAPSE
Uncollapse an email thread.
Definition: mutt_thread.h:65
#define MUTT_THREAD_NEXT_UNREAD
Find the next unread email.
Definition: mutt_thread.h:67
#define MUTT_THREAD_COLLAPSE
Collapse an email thread.
Definition: mutt_thread.h:64
#define MUTT_THREAD_FLAGGED
Count flagged emails in a thread.
Definition: mutt_thread.h:68
bool read
Email is read.
Definition: email.h:48
bool old
Email is seen, but unread.
Definition: email.h:47
size_t num_hidden
Number of hidden messages in this view (only valid when collapsed is set)
Definition: email.h:122
struct AttrColor * attr_color
Color-pair to use when displaying in the index.
Definition: email.h:112
bool flagged
Marked important?
Definition: email.h:45
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 1608 of file mutt_thread.c.

1609{
1610 if (!m || !e)
1611 return 1;
1612
1613 struct MuttThread *threads[2];
1614 int rc;
1615
1616 const enum UseThreads threaded = mutt_thread_style();
1617 if ((threaded == UT_FLAT) || !e->thread)
1618 return 1;
1619
1620 threads[0] = e->thread;
1621 while (threads[0]->parent)
1622 threads[0] = threads[0]->parent;
1623
1624 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1625
1626 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1627 {
1628 while (!threads[i]->message)
1629 threads[i] = threads[i]->child;
1630 }
1631
1632 if (threaded == UT_REVERSE)
1633 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1634 else
1635 {
1636 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1637 threads[0]->message->msgno;
1638 }
1639
1640 if (mit == MIT_POSITION)
1641 rc += 1;
1642
1643 return rc;
1644}
@ MIT_POSITION
Our position in the thread.
Definition: mutt_thread.h:76
+ 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 1651 of file mutt_thread.c.

1652{
1653 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1654
1655 for (int i = 0; i < m->msg_count; i++)
1656 {
1657 struct Email *e = m->emails[i];
1658 if (!e || !e->env)
1659 continue;
1660
1661 if (e->env->message_id)
1662 mutt_hash_insert(hash, e->env->message_id, e);
1663 }
1664
1665 return hash;
1666}
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:109
+ 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 1675 of file mutt_thread.c.

1676{
1677 if (child == parent)
1678 return false;
1679
1680 mutt_break_thread(child);
1682 mutt_set_flag(m, child, MUTT_TAG, false);
1683
1684 child->changed = true;
1685 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1686 return true;
1687}
#define MUTT_ENV_CHANGED_IRT
In-Reply-To changed to link/break threads.
Definition: envelope.h:34
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:100
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:63
bool changed
Email has been edited.
Definition: email.h:75
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition: envelope.h:92
void mutt_break_thread(struct Email *e)
Break the email Thread.
Definition: thread.c:233
+ 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 EmailList *  children,
struct Mailbox m 
)

Forcibly link threads together.

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

Definition at line 1696 of file mutt_thread.c.

1697{
1698 if (!parent || !children || !m)
1699 return false;
1700
1701 bool changed = false;
1702
1703 struct EmailNode *en = NULL;
1704 STAILQ_FOREACH(en, children, entries)
1705 {
1706 changed |= link_threads(parent, en->email, m);
1707 }
1708
1709 return changed;
1710}
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
Forcibly link messages together.
Definition: mutt_thread.c:1675
List of Emails.
Definition: email.h:131
struct Email * email
Email in the list.
Definition: email.h:132
+ 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 1716 of file mutt_thread.c.

1717{
1718 struct MuttThread *thread = NULL;
1719 struct MuttThread *top = tctx->tree;
1720 while ((thread = top))
1721 {
1722 while (!thread->message)
1723 thread = thread->child;
1724
1725 struct Email *e = thread->message;
1726 if (e->collapsed)
1728 top = top->next;
1729 }
1730}
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:93
+ 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 1737 of file mutt_thread.c.

1738{
1739 struct MuttThread *thread = NULL;
1740 struct MuttThread *top = tctx->tree;
1741 while ((thread = top))
1742 {
1743 while (!thread->message)
1744 thread = thread->child;
1745
1746 struct Email *e = thread->message;
1747
1748 if (e->collapsed != collapse)
1749 {
1750 if (e->collapsed)
1752 else if (mutt_thread_can_collapse(e))
1754 }
1755 top = top->next;
1756 }
1757}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1765
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:94
+ 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 1765 of file mutt_thread.c.

1766{
1767 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1768 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1769 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1770 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1771}
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:96
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:95
+ 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 },
}

Choices for '$use_threads' for the index.

Definition at line 61 of file mutt_thread.c.

◆ UseThreadsTypeDef

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

Definition at line 74 of file mutt_thread.c.