NeoMutt  2023-05-17-33-gce4425
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 "globals.h"
#include "mview.h"
#include "mx.h"
#include "protos.h"
#include "sort.h"
+ Include dependency graph for mutt_thread.c:

Go to the source code of this file.

Functions

enum UseThreads mutt_thread_style (void)
 Which threading style is active? 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 MailboxView *mv)
 Initialize a threading context. More...
 
void mutt_thread_ctx_free (struct ThreadsContext **ptr)
 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 MailboxView *mv, bool init)
 Find out which emails' subjects differ from their parent's. More...
 
static void thread_hash_destructor (int type, void *obj, intptr_t data)
 Hash Destructor callback - Implements hash_hdata_free_t -. 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 EmailArray *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...
 
const struct EnumDef UseThreadsTypeDef
 Data for the $use_threads enumeration. More...
 

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

80{
81 const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
82 const enum SortType 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) != 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: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:97
@ UT_THREADS
Normal threading (root above subthreads)
Definition: mutt_thread.h:98
@ UT_REVERSE
Reverse threading (subthreads above root)
Definition: mutt_thread.h:99
#define SORT_MASK
Mask for the sort id.
Definition: sort2.h:74
SortType
Methods for sorting.
Definition: sort2.h:38
@ SORT_THREADS
Sort by email threads.
Definition: sort2.h:45
#define SORT_REVERSE
Reverse the order of the sort.
Definition: sort2.h:75
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 97 of file mutt_thread.c.

98{
100}
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:50
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ is_visible()

static bool is_visible ( struct Email e)
static

Is the message visible?

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

Definition at line 121 of file mutt_thread.c.

122{
123 return e->vnum >= 0 || (e->collapsed && e->visible);
124}
bool visible
Is this message part of the view?
Definition: email.h:120
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:119
int vnum
Virtual message number.
Definition: email.h:113
+ Here is the caller graph for this function:

◆ need_display_subject()

static bool need_display_subject ( struct Email e)
static

Determines whether to display a message's subject.

Parameters
eEmail
Return values
trueThe subject should be displayed

Definition at line 131 of file mutt_thread.c.

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

◆ linearize_tree()

static void linearize_tree ( struct ThreadsContext tctx)
static

Flatten an email thread.

Parameters
tctxThreading context

Definition at line 180 of file mutt_thread.c.

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

◆ calculate_visibility()

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

Are tree nodes visible.

Parameters
treeThreads tree
max_depthMaximum depth to check

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

Definition at line 233 of file mutt_thread.c.

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

◆ mutt_thread_ctx_init()

struct ThreadsContext * mutt_thread_ctx_init ( struct MailboxView mv)

Initialize a threading context.

Parameters
mvMailbox view
Return values
ptrThreading context

Definition at line 354 of file mutt_thread.c.

355{
356 struct ThreadsContext *tctx = mutt_mem_calloc(1, sizeof(struct ThreadsContext));
357 tctx->mailbox_view = mv;
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.h:41
+ 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 **  ptr)

Finalize a threading context.

Parameters
ptrThreading context to free

Definition at line 365 of file mutt_thread.c.

366{
367 struct ThreadsContext *tctx = *ptr;
368
369 mutt_hash_free(&tctx->hash);
370
371 FREE(ptr);
372}
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:457
struct HashTable * hash
Hash Table: "message-id" -> MuttThread.
Definition: mutt_thread.h:44
+ 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 386 of file mutt_thread.c.

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

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

573{
574 if (!m)
575 return NULL;
576
577 struct HashElem *he = NULL;
578 struct MuttThread *tmp = NULL, *last = NULL;
579 struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
580 time_t date = 0;
581
582 make_subject_list(&subjects, cur, &date);
583
584 struct ListNode *np = NULL;
585 const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
586 STAILQ_FOREACH(np, &subjects, entries)
587 {
588 for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
589 {
590 tmp = ((struct Email *) he->data)->thread;
591 if ((tmp != cur) && /* don't match the same message */
592 !tmp->fake_thread && /* don't match pseudo threads */
593 tmp->message->subject_changed && /* only match interesting replies */
594 !is_descendant(tmp, cur) && /* don't match in the same thread */
595 (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
596 (!last || (c_thread_received ?
597 (last->message->received < tmp->message->received) :
598 (last->message->date_sent < tmp->message->date_sent))) &&
599 tmp->message->env->real_subj &&
601 {
602 last = tmp; /* best match so far */
603 }
604 }
605 }
606
607 mutt_list_clear(&subjects);
608 return last;
609}
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:798
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:516
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
The item stored in a Hash Table.
Definition: hash.h:44
struct HashElem * next
Linked List.
Definition: hash.h:48
void * data
User-supplied data.
Definition: hash.h:47
struct HashTable * subj_hash
Hash Table: "subject" -> Email.
Definition: mailbox.h:124
bool is_descendant(const struct MuttThread *a, const struct MuttThread *b)
Is one thread a descendant of another.
Definition: thread.c:43
+ 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 616 of file mutt_thread.c.

617{
618 if (!m)
619 return NULL;
620
622
623 for (int i = 0; i < m->msg_count; i++)
624 {
625 struct Email *e = m->emails[i];
626 if (!e || !e->env)
627 continue;
628 if (e->env->real_subj)
629 mutt_hash_insert(hash, e->env->real_subj, e);
630 }
631
632 return hash;
633}
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:113
A Hash Table.
Definition: hash.h:98
+ 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 641 of file mutt_thread.c.

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

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

749{
750 const struct MuttThread *ta = *(struct MuttThread const *const *) a;
751 const struct MuttThread *tb = *(struct MuttThread const *const *) b;
752 const struct ThreadsContext *tctx = arg;
753 assert(ta->parent == tb->parent);
754
755 /* If c_sort ties, remember we are building the thread array in
756 * reverse from the index the mails had in the mailbox. */
757 struct Mailbox *m = tctx->mailbox_view->mailbox;
758 const enum MailboxType mtype = mx_type(m);
759 if (ta->parent)
760 {
761 return mutt_compare_emails(ta->sort_aux_key, tb->sort_aux_key, mtype,
763 }
764 else
765 {
768 }
769}
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
enum MailboxType mx_type(struct Mailbox *m)
Return the type of the Mailbox.
Definition: mx.c:1854
@ SORT_ORDER
Sort by the order the messages appear in the mailbox.
Definition: sort2.h:44
int mutt_compare_emails(const struct Email *a, const struct Email *b, enum MailboxType type, short sort, short sort_aux)
Compare two emails using up to two sort methods.
Definition: sort.c:332
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 SortType c_sort_aux
Last sort_aux method.
Definition: mutt_thread.h:46
enum SortType c_sort
Last sort method.
Definition: mutt_thread.h:45
+ 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 776 of file mutt_thread.c.

777{
778 struct MuttThread *thread = tctx->tree;
779 if (!thread)
780 return;
781
782 struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
783 struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
784 struct Email *oldsort_thread_key = NULL;
785 int i, array_size;
786 bool sort_top = false;
787
788 /* we put things into the array backwards to save some cycles,
789 * but we want to have to move less stuff around if we're
790 * resorting, so we sort backwards and then put them back
791 * in reverse order so they're forwards */
792 const bool reverse = (mutt_thread_style() == UT_REVERSE);
793 enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
794 enum SortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
795 if ((c_sort & SORT_MASK) == SORT_THREADS)
796 {
797 assert(!(c_sort & SORT_REVERSE) != reverse);
798 assert(cs_subset_enum(NeoMutt->sub, "use_threads") == UT_UNSET);
799 c_sort = c_sort_aux;
800 }
801 c_sort ^= SORT_REVERSE;
802 c_sort_aux ^= SORT_REVERSE;
803 if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
804 {
805 tctx->c_sort = c_sort;
806 tctx->c_sort_aux = c_sort_aux;
807 init = true;
808 }
809
810 top = thread;
811
812 array_size = 256;
813 array = mutt_mem_calloc(array_size, sizeof(struct MuttThread *));
814 while (true)
815 {
816 if (init || !thread->sort_thread_key || !thread->sort_aux_key)
817 {
818 thread->sort_thread_key = NULL;
819 thread->sort_aux_key = NULL;
820
821 if (thread->parent)
822 thread->parent->sort_children = true;
823 else
824 sort_top = true;
825 }
826
827 if (thread->child)
828 {
830 continue;
831 }
832 else
833 {
834 /* if it has no children, it must be real. sort it on its own merits */
837
838 if (thread->next)
839 {
840 thread = thread->next;
841 continue;
842 }
843 }
844
845 struct Mailbox *m = tctx->mailbox_view->mailbox;
846 const enum MailboxType mtype = mx_type(m);
847 while (!thread->next)
848 {
849 /* if it has siblings and needs to be sorted, sort it... */
850 if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
851 {
852 /* put them into the array */
853 for (i = 0; thread; i++, thread = thread->prev)
854 {
855 if (i >= array_size)
856 mutt_mem_realloc(&array, (array_size *= 2) * sizeof(struct MuttThread *));
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, SORT_ORDER) > 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,
941 mtype, c_sort | SORT_REVERSE, SORT_ORDER) > 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}
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:748
@ UT_UNSET
Not yet set by user, stick to legacy semantics.
Definition: mutt_thread.h:96
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:76
+ 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 mutt_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 mutt_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, sizeof(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, sizeof(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:88
static void thread_hash_destructor(int type, void *obj, intptr_t data)
Hash Destructor callback - Implements hash_hdata_free_t -.
Definition: mutt_thread.c:1019
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:180
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
Sort the children of a thread.
Definition: mutt_thread.c:776
void mutt_draw_tree(struct ThreadsContext *tctx)
Draw a tree of threaded emails.
Definition: mutt_thread.c:386
static void pseudo_threads(struct ThreadsContext *tctx)
Thread messages by subject.
Definition: mutt_thread.c:641
static void check_subjects(struct MailboxView *mv, bool init)
Find out which emails' subjects differ from their parent's.
Definition: mutt_thread.c:977
#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 1284 of file mutt_thread.c.

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

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

1399{
1400 if (!m)
1401 return 0;
1402
1403 off_t vsize = 0;
1404 const int padding = mx_msg_padding_size(m);
1405
1406 m->vcount = 0;
1407
1408 for (int i = 0; i < m->msg_count; i++)
1409 {
1410 struct Email *e = m->emails[i];
1411 if (!e)
1412 break;
1413
1414 if (e->vnum >= 0)
1415 {
1416 e->vnum = m->vcount;
1417 m->v2r[m->vcount] = i;
1418 m->vcount++;
1419 vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1420 }
1421 }
1422
1423 return vsize;
1424}
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1566
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 1432 of file mutt_thread.c.

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

1652{
1653 if (!m || !e)
1654 return 1;
1655
1656 struct MuttThread *threads[2];
1657 int rc;
1658
1659 const enum UseThreads threaded = mutt_thread_style();
1660 if ((threaded == UT_FLAT) || !e->thread)
1661 return 1;
1662
1663 threads[0] = e->thread;
1664 while (threads[0]->parent)
1665 threads[0] = threads[0]->parent;
1666
1667 threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1668
1669 for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1670 {
1671 while (!threads[i]->message)
1672 threads[i] = threads[i]->child;
1673 }
1674
1675 if (threaded == UT_REVERSE)
1676 {
1677 rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1678 }
1679 else
1680 {
1681 rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1682 threads[0]->message->msgno;
1683 }
1684
1685 if (mit == MIT_POSITION)
1686 rc += 1;
1687
1688 return rc;
1689}
@ MIT_POSITION
Our position in the thread.
Definition: mutt_thread.h:88
+ 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 1696 of file mutt_thread.c.

1697{
1698 struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1699
1700 for (int i = 0; i < m->msg_count; i++)
1701 {
1702 struct Email *e = m->emails[i];
1703 if (!e || !e->env)
1704 continue;
1705
1706 if (e->env->message_id)
1707 mutt_hash_insert(hash, e->env->message_id, e);
1708 }
1709
1710 return hash;
1711}
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:110
+ 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 1720 of file mutt_thread.c.

1721{
1722 if (child == parent)
1723 return false;
1724
1725 mutt_break_thread(child);
1727 mutt_set_flag(m, child, MUTT_TAG, false, true);
1728
1729 child->changed = true;
1730 child->env->changed |= MUTT_ENV_CHANGED_IRT;
1731 return true;
1732}
#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:52
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:88
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:226
+ 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 1741 of file mutt_thread.c.

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

1763{
1764 struct MuttThread *thread = NULL;
1765 struct MuttThread *top = tctx->tree;
1766 while ((thread = top))
1767 {
1768 while (!thread->message)
1769 thread = thread->child;
1770
1771 struct Email *e = thread->message;
1772 if (e->collapsed)
1774 top = top->next;
1775 }
1776}
#define mutt_collapse_thread(e)
Definition: mutt_thread.h:105
+ 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 1783 of file mutt_thread.c.

1784{
1785 struct MuttThread *thread = NULL;
1786 struct MuttThread *top = tctx->tree;
1787 while ((thread = top))
1788 {
1789 while (!thread->message)
1790 thread = thread->child;
1791
1792 struct Email *e = thread->message;
1793
1794 if (e->collapsed != collapse)
1795 {
1796 if (e->collapsed)
1798 else if (mutt_thread_can_collapse(e))
1800 }
1801 top = top->next;
1802 }
1803}
bool mutt_thread_can_collapse(struct Email *e)
Check whether a thread can be collapsed.
Definition: mutt_thread.c:1811
#define mutt_uncollapse_thread(e)
Definition: mutt_thread.h:106
+ 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 1811 of file mutt_thread.c.

1812{
1813 const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1814 const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1815 return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1816 (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1817}
#define mutt_thread_contains_flagged(e)
Definition: mutt_thread.h:108
#define mutt_thread_contains_unread(e)
Definition: mutt_thread.h:107
+ 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 50 of file mutt_thread.c.

◆ UseThreadsTypeDef

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

Data for the $use_threads enumeration.

Definition at line 64 of file mutt_thread.c.