Coverage Report

Created: 2025-04-22 06:17

/src/neomutt/mutt_thread.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Create/manipulate threading in emails
4
 *
5
 * @authors
6
 * Copyright (C) 2017 Peter Lewis <pete@muddygoat.org>
7
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2017-2023 Pietro Cerutti <gahr@gahr.ch>
9
 * Copyright (C) 2019 Federico Kircheis <federico.kircheis@gmail.com>
10
 * Copyright (C) 2021 Eric Blake <eblake@redhat.com>
11
 *
12
 * @copyright
13
 * This program is free software: you can redistribute it and/or modify it under
14
 * the terms of the GNU General Public License as published by the Free Software
15
 * Foundation, either version 2 of the License, or (at your option) any later
16
 * version.
17
 *
18
 * This program is distributed in the hope that it will be useful, but WITHOUT
19
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21
 * details.
22
 *
23
 * You should have received a copy of the GNU General Public License along with
24
 * this program.  If not, see <http://www.gnu.org/licenses/>.
25
 */
26
27
/**
28
 * @page neo_mutt_thread Create/manipulate threading in emails
29
 *
30
 * Create/manipulate threading in emails
31
 */
32
33
#include "config.h"
34
#include <limits.h>
35
#include <stdbool.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include "mutt/lib.h"
39
#include "config/lib.h"
40
#include "email/lib.h"
41
#include "core/lib.h"
42
#include "mutt.h"
43
#include "mutt_thread.h"
44
#include "globals.h"
45
#include "mview.h"
46
#include "mx.h"
47
#include "protos.h"
48
49
/**
50
 * UseThreadsMethods - Choices for '$use_threads' for the index
51
 */
52
static const struct Mapping UseThreadsMethods[] = {
53
  // clang-format off
54
  { "unset",         UT_UNSET },
55
  { "flat",          UT_FLAT },
56
  { "threads",       UT_THREADS },
57
  { "reverse",       UT_REVERSE },
58
  // aliases
59
  { "no",            UT_FLAT },
60
  { "yes",           UT_THREADS },
61
  { NULL, 0 },
62
  // clang-format on
63
};
64
65
/// Data for the $use_threads enumeration
66
const struct EnumDef UseThreadsTypeDef = {
67
  "use_threads_type",
68
  4,
69
  (struct Mapping *) &UseThreadsMethods,
70
};
71
72
/**
73
 * mutt_thread_style - Which threading style is active?
74
 * @retval #UT_FLAT    No threading in use
75
 * @retval #UT_THREADS Normal threads (root above subthread)
76
 * @retval #UT_REVERSE Reverse threads (subthread above root)
77
 *
78
 * @note UT_UNSET is never returned; rather, this function considers the
79
 *       interaction between $use_threads and $sort.
80
 */
81
enum UseThreads mutt_thread_style(void)
82
0
{
83
0
  const unsigned char c_use_threads = cs_subset_enum(NeoMutt->sub, "use_threads");
84
0
  const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
85
0
  if (c_use_threads > UT_FLAT)
86
0
    return c_use_threads;
87
0
  if ((c_sort & SORT_MASK) != EMAIL_SORT_THREADS)
88
0
    return UT_FLAT;
89
0
  if (c_sort & SORT_REVERSE)
90
0
    return UT_REVERSE;
91
0
  return UT_THREADS;
92
0
}
93
94
/**
95
 * get_use_threads_str - Convert UseThreads enum to string
96
 * @param value Value to convert
97
 * @retval ptr String form of value
98
 */
99
const char *get_use_threads_str(enum UseThreads value)
100
0
{
101
0
  return mutt_map_get_name(value, UseThreadsMethods);
102
0
}
103
104
/**
105
 * sort_validator - Validate the "sort" config variable - Implements ConfigDef::validator() - @ingroup cfg_def_validator
106
 */
107
int sort_validator(const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
108
0
{
109
0
  if (((value & SORT_MASK) == EMAIL_SORT_THREADS) && (value & SORT_LAST))
110
0
  {
111
0
    buf_printf(err, _("Cannot use 'last-' prefix with 'threads' for %s"), cdef->name);
112
0
    return CSR_ERR_INVALID;
113
0
  }
114
0
  return CSR_SUCCESS;
115
0
}
116
117
/**
118
 * is_visible - Is the message visible?
119
 * @param e   Email
120
 * @retval true The message is not hidden in some way
121
 */
122
static bool is_visible(struct Email *e)
123
0
{
124
0
  return e->vnum >= 0 || (e->collapsed && e->visible);
125
0
}
126
127
/**
128
 * need_display_subject - Determines whether to display a message's subject
129
 * @param e Email
130
 * @retval true The subject should be displayed
131
 */
132
static bool need_display_subject(struct Email *e)
133
0
{
134
0
  struct MuttThread *tmp = NULL;
135
0
  struct MuttThread *tree = e->thread;
136
137
  /* if the user disabled subject hiding, display it */
138
0
  const bool c_hide_thread_subject = cs_subset_bool(NeoMutt->sub, "hide_thread_subject");
139
0
  if (!c_hide_thread_subject)
140
0
    return true;
141
142
  /* if our subject is different from our parent's, display it */
143
0
  if (e->subject_changed)
144
0
    return true;
145
146
  /* if our subject is different from that of our closest previously displayed
147
   * sibling, display the subject */
148
0
  for (tmp = tree->prev; tmp; tmp = tmp->prev)
149
0
  {
150
0
    e = tmp->message;
151
0
    if (e && is_visible(e))
152
0
    {
153
0
      if (e->subject_changed)
154
0
        return true;
155
0
      break;
156
0
    }
157
0
  }
158
159
  /* if there is a parent-to-child subject change anywhere between us and our
160
   * closest displayed ancestor, display the subject */
161
0
  for (tmp = tree->parent; tmp; tmp = tmp->parent)
162
0
  {
163
0
    e = tmp->message;
164
0
    if (e)
165
0
    {
166
0
      if (is_visible(e))
167
0
        return false;
168
0
      if (e->subject_changed)
169
0
        return true;
170
0
    }
171
0
  }
172
173
  /* if we have no visible parent or previous sibling, display the subject */
174
0
  return true;
175
0
}
176
177
/**
178
 * linearize_tree - Flatten an email thread
179
 * @param tctx Threading context
180
 */
181
static void linearize_tree(struct ThreadsContext *tctx)
182
0
{
183
0
  if (!tctx || !tctx->mailbox_view)
184
0
    return;
185
186
0
  struct Mailbox *m = tctx->mailbox_view->mailbox;
187
188
0
  const bool reverse = (mutt_thread_style() == UT_REVERSE);
189
0
  struct MuttThread *tree = tctx->tree;
190
0
  struct Email **array = m->emails + (reverse ? m->msg_count - 1 : 0);
191
192
0
  while (tree)
193
0
  {
194
0
    while (!tree->message)
195
0
      tree = tree->child;
196
197
0
    *array = tree->message;
198
0
    array += reverse ? -1 : 1;
199
200
0
    if (tree->child)
201
0
    {
202
0
      tree = tree->child;
203
0
    }
204
0
    else
205
0
    {
206
0
      while (tree)
207
0
      {
208
0
        if (tree->next)
209
0
        {
210
0
          tree = tree->next;
211
0
          break;
212
0
        }
213
0
        else
214
0
        {
215
0
          tree = tree->parent;
216
0
        }
217
0
      }
218
0
    }
219
0
  }
220
0
}
221
222
/**
223
 * calculate_visibility - Are tree nodes visible
224
 * @param tree      Threads tree
225
 * @param max_depth Maximum depth to check
226
 *
227
 * this calculates whether a node is the root of a subtree that has visible
228
 * nodes, whether a node itself is visible, whether, if invisible, it has
229
 * depth anyway, and whether any of its later siblings are roots of visible
230
 * subtrees.  while it's at it, it frees the old thread display, so we can
231
 * skip parts of the tree in mutt_draw_tree() if we've decided here that we
232
 * don't care about them any more.
233
 */
234
static void calculate_visibility(struct MuttThread *tree, int *max_depth)
235
0
{
236
0
  if (!tree)
237
0
    return;
238
239
0
  struct MuttThread *tmp = NULL;
240
0
  struct MuttThread *orig_tree = tree;
241
0
  const bool c_hide_top_missing = cs_subset_bool(NeoMutt->sub, "hide_top_missing");
242
0
  const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
243
0
  int hide_top_missing = c_hide_top_missing && !c_hide_missing;
244
0
  const bool c_hide_top_limited = cs_subset_bool(NeoMutt->sub, "hide_top_limited");
245
0
  const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
246
0
  int hide_top_limited = c_hide_top_limited && !c_hide_limited;
247
0
  int depth = 0;
248
249
  /* we walk each level backwards to make it easier to compute next_subtree_visible */
250
0
  while (tree->next)
251
0
    tree = tree->next;
252
0
  *max_depth = 0;
253
254
0
  while (true)
255
0
  {
256
0
    if (depth > *max_depth)
257
0
      *max_depth = depth;
258
259
0
    tree->subtree_visible = 0;
260
0
    if (tree->message)
261
0
    {
262
0
      FREE(&tree->message->tree);
263
0
      if (is_visible(tree->message))
264
0
      {
265
0
        tree->deep = true;
266
0
        tree->visible = true;
267
0
        tree->message->display_subject = need_display_subject(tree->message);
268
0
        for (tmp = tree; tmp; tmp = tmp->parent)
269
0
        {
270
0
          if (tmp->subtree_visible)
271
0
          {
272
0
            tmp->deep = true;
273
0
            tmp->subtree_visible = 2;
274
0
            break;
275
0
          }
276
0
          else
277
0
          {
278
0
            tmp->subtree_visible = 1;
279
0
          }
280
0
        }
281
0
      }
282
0
      else
283
0
      {
284
0
        tree->visible = false;
285
0
        tree->deep = !c_hide_limited;
286
0
      }
287
0
    }
288
0
    else
289
0
    {
290
0
      tree->visible = false;
291
0
      tree->deep = !c_hide_missing;
292
0
    }
293
0
    tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible ||
294
0
                                                tree->next->subtree_visible);
295
0
    if (tree->child)
296
0
    {
297
0
      depth++;
298
0
      tree = tree->child;
299
0
      while (tree->next)
300
0
        tree = tree->next;
301
0
    }
302
0
    else if (tree->prev)
303
0
    {
304
0
      tree = tree->prev;
305
0
    }
306
0
    else
307
0
    {
308
0
      while (tree && !tree->prev)
309
0
      {
310
0
        depth--;
311
0
        tree = tree->parent;
312
0
      }
313
0
      if (!tree)
314
0
        break;
315
0
      tree = tree->prev;
316
0
    }
317
0
  }
318
319
  /* now fix up for the OPTHIDETOP* options if necessary */
320
0
  if (hide_top_limited || hide_top_missing)
321
0
  {
322
0
    tree = orig_tree;
323
0
    while (true)
324
0
    {
325
0
      if (!tree->visible && tree->deep && (tree->subtree_visible < 2) &&
326
0
          ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
327
0
      {
328
0
        tree->deep = false;
329
0
      }
330
0
      if (!tree->deep && tree->child && tree->subtree_visible)
331
0
      {
332
0
        tree = tree->child;
333
0
      }
334
0
      else if (tree->next)
335
0
      {
336
0
        tree = tree->next;
337
0
      }
338
0
      else
339
0
      {
340
0
        while (tree && !tree->next)
341
0
          tree = tree->parent;
342
0
        if (!tree)
343
0
          break;
344
0
        tree = tree->next;
345
0
      }
346
0
    }
347
0
  }
348
0
}
349
350
/**
351
 * mutt_thread_ctx_init - Initialize a threading context
352
 * @param mv Mailbox view
353
 * @retval ptr Threading context
354
 */
355
struct ThreadsContext *mutt_thread_ctx_init(struct MailboxView *mv)
356
0
{
357
0
  struct ThreadsContext *tctx = MUTT_MEM_CALLOC(1, struct ThreadsContext);
358
0
  tctx->mailbox_view = mv;
359
0
  return tctx;
360
0
}
361
362
/**
363
 * mutt_thread_ctx_free - Finalize a threading context
364
 * @param ptr Threading context to free
365
 */
366
void mutt_thread_ctx_free(struct ThreadsContext **ptr)
367
0
{
368
0
  if (!ptr || !*ptr)
369
0
  {
370
0
    return;
371
0
  }
372
373
0
  struct ThreadsContext *tctx = *ptr;
374
375
0
  mutt_hash_free(&tctx->hash);
376
377
0
  FREE(ptr);
378
0
}
379
380
/**
381
 * mutt_draw_tree - Draw a tree of threaded emails
382
 * @param tctx Threading context
383
 *
384
 * Since the graphics characters have a value >255, I have to resort to using
385
 * escape sequences to pass the information to print_enriched_string().  These
386
 * are the macros MUTT_TREE_* defined in mutt.h.
387
 *
388
 * ncurses should automatically use the default ASCII characters instead of
389
 * graphics chars on terminals which don't support them (see the man page for
390
 * curs_addch).
391
 */
392
void mutt_draw_tree(struct ThreadsContext *tctx)
393
0
{
394
0
  char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL;
395
0
  const bool reverse = (mutt_thread_style() == UT_REVERSE);
396
0
  enum TreeChar corner = reverse ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
397
0
  enum TreeChar vtee = reverse ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
398
0
  const bool c_narrow_tree = cs_subset_bool(NeoMutt->sub, "narrow_tree");
399
0
  int depth = 0, start_depth = 0, max_depth = 0, width = c_narrow_tree ? 1 : 2;
400
0
  struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL;
401
402
0
  struct MuttThread *tree = tctx->tree;
403
404
  /* Do the visibility calculations and free the old thread chars.
405
   * From now on we can simply ignore invisible subtrees */
406
0
  calculate_visibility(tree, &max_depth);
407
0
  pfx = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
408
0
  arrow = MUTT_MEM_MALLOC((width * max_depth) + 2, char);
409
0
  const bool c_hide_limited = cs_subset_bool(NeoMutt->sub, "hide_limited");
410
0
  const bool c_hide_missing = cs_subset_bool(NeoMutt->sub, "hide_missing");
411
0
  while (tree)
412
0
  {
413
0
    if (depth != 0)
414
0
    {
415
0
      myarrow = arrow + (depth - start_depth - ((start_depth != 0) ? 0 : 1)) * width;
416
0
      if (start_depth == depth)
417
0
        myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
418
0
      else if (parent->message && !c_hide_limited)
419
0
        myarrow[0] = MUTT_TREE_HIDDEN;
420
0
      else if (!parent->message && !c_hide_missing)
421
0
        myarrow[0] = MUTT_TREE_MISSING;
422
0
      else
423
0
        myarrow[0] = vtee;
424
0
      if (width == 2)
425
0
      {
426
0
        myarrow[1] = pseudo ? MUTT_TREE_STAR :
427
0
                              (tree->duplicate_thread ? MUTT_TREE_EQUALS : MUTT_TREE_HLINE);
428
0
      }
429
0
      if (tree->visible)
430
0
      {
431
0
        myarrow[width] = MUTT_TREE_RARROW;
432
0
        myarrow[width + 1] = 0;
433
0
        new_tree = MUTT_MEM_MALLOC(((size_t) depth * width) + 2, char);
434
0
        if (start_depth > 1)
435
0
        {
436
0
          strncpy(new_tree, pfx, (size_t) width * (start_depth - 1));
437
0
          mutt_str_copy(new_tree + (start_depth - 1) * width, arrow,
438
0
                        (1 + depth - start_depth) * width + 2);
439
0
        }
440
0
        else
441
0
        {
442
0
          mutt_str_copy(new_tree, arrow, ((size_t) depth * width) + 2);
443
0
        }
444
0
        tree->message->tree = new_tree;
445
0
      }
446
0
    }
447
0
    if (tree->child && (depth != 0))
448
0
    {
449
0
      mypfx = pfx + (depth - 1) * width;
450
0
      mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
451
0
      if (width == 2)
452
0
        mypfx[1] = MUTT_TREE_SPACE;
453
0
    }
454
0
    parent = tree;
455
0
    nextdisp = NULL;
456
0
    pseudo = NULL;
457
0
    do
458
0
    {
459
0
      if (tree->child && tree->subtree_visible)
460
0
      {
461
0
        if (tree->deep)
462
0
          depth++;
463
0
        if (tree->visible)
464
0
          start_depth = depth;
465
0
        tree = tree->child;
466
467
        /* we do this here because we need to make sure that the first child thread
468
         * of the old tree that we deal with is actually displayed if any are,
469
         * or we might set the parent variable wrong while going through it. */
470
0
        while (!tree->subtree_visible && tree->next)
471
0
          tree = tree->next;
472
0
      }
473
0
      else
474
0
      {
475
0
        while (!tree->next && tree->parent)
476
0
        {
477
0
          if (tree == pseudo)
478
0
            pseudo = NULL;
479
0
          if (tree == nextdisp)
480
0
            nextdisp = NULL;
481
0
          if (tree->visible)
482
0
            start_depth = depth;
483
0
          tree = tree->parent;
484
0
          if (tree->deep)
485
0
          {
486
0
            if (start_depth == depth)
487
0
              start_depth--;
488
0
            depth--;
489
0
          }
490
0
        }
491
0
        if (tree == pseudo)
492
0
          pseudo = NULL;
493
0
        if (tree == nextdisp)
494
0
          nextdisp = NULL;
495
0
        if (tree->visible)
496
0
          start_depth = depth;
497
0
        tree = tree->next;
498
0
        if (!tree)
499
0
          break;
500
0
      }
501
0
      if (!pseudo && tree->fake_thread)
502
0
        pseudo = tree;
503
0
      if (!nextdisp && tree->next_subtree_visible)
504
0
        nextdisp = tree;
505
0
    } while (!tree->deep);
506
0
  }
507
508
0
  FREE(&pfx);
509
0
  FREE(&arrow);
510
0
}
511
512
/**
513
 * make_subject_list - Create a sorted list of all subjects in a thread
514
 * @param[out] subjects String List of subjects
515
 * @param[in]  cur      Email Thread
516
 * @param[out] dateptr  Earliest date found in thread
517
 *
518
 * Since we may be trying to attach as a pseudo-thread a MuttThread that has no
519
 * message, we have to make a list of all the subjects of its most immediate
520
 * existing descendants.
521
 */
522
static void make_subject_list(struct ListHead *subjects, struct MuttThread *cur, time_t *dateptr)
523
0
{
524
0
  struct MuttThread *start = cur;
525
0
  struct Envelope *env = NULL;
526
0
  time_t thisdate;
527
0
  int rc = 0;
528
529
0
  const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
530
0
  const bool c_sort_re = cs_subset_bool(NeoMutt->sub, "sort_re");
531
0
  while (true)
532
0
  {
533
0
    while (!cur->message)
534
0
      cur = cur->child;
535
536
0
    if (dateptr)
537
0
    {
538
0
      thisdate = c_thread_received ? cur->message->received : cur->message->date_sent;
539
0
      if ((*dateptr == 0) || (thisdate < *dateptr))
540
0
        *dateptr = thisdate;
541
0
    }
542
543
0
    env = cur->message->env;
544
0
    if (env->real_subj && ((env->real_subj != env->subject) || !c_sort_re))
545
0
    {
546
0
      struct ListNode *np = NULL;
547
0
      STAILQ_FOREACH(np, subjects, entries)
548
0
      {
549
0
        rc = mutt_str_cmp(env->real_subj, np->data);
550
0
        if (rc >= 0)
551
0
          break;
552
0
      }
553
0
      if (!np)
554
0
        mutt_list_insert_head(subjects, env->real_subj);
555
0
      else if (rc > 0)
556
0
        mutt_list_insert_after(subjects, np, env->real_subj);
557
0
    }
558
559
0
    while (!cur->next && (cur != start))
560
0
    {
561
0
      cur = cur->parent;
562
0
    }
563
0
    if (cur == start)
564
0
      break;
565
0
    cur = cur->next;
566
0
  }
567
0
}
568
569
/**
570
 * find_subject - Find the best possible match for a parent based on subject
571
 * @param m   Mailbox
572
 * @param cur Email to match
573
 * @retval ptr Best match for a parent
574
 *
575
 * If there are multiple matches, the one which was sent the latest, but before
576
 * the current message, is used.
577
 */
578
static struct MuttThread *find_subject(struct Mailbox *m, struct MuttThread *cur)
579
0
{
580
0
  if (!m)
581
0
    return NULL;
582
583
0
  struct HashElem *he = NULL;
584
0
  struct MuttThread *tmp = NULL, *last = NULL;
585
0
  struct ListHead subjects = STAILQ_HEAD_INITIALIZER(subjects);
586
0
  time_t date = 0;
587
588
0
  make_subject_list(&subjects, cur, &date);
589
590
0
  struct ListNode *np = NULL;
591
0
  const bool c_thread_received = cs_subset_bool(NeoMutt->sub, "thread_received");
592
0
  STAILQ_FOREACH(np, &subjects, entries)
593
0
  {
594
0
    for (he = mutt_hash_find_bucket(m->subj_hash, np->data); he; he = he->next)
595
0
    {
596
0
      tmp = ((struct Email *) he->data)->thread;
597
0
      if ((tmp != cur) &&                  /* don't match the same message */
598
0
          !tmp->fake_thread &&             /* don't match pseudo threads */
599
0
          tmp->message->subject_changed && /* only match interesting replies */
600
0
          !is_descendant(tmp, cur) &&      /* don't match in the same thread */
601
0
          (date >= (c_thread_received ? tmp->message->received : tmp->message->date_sent)) &&
602
0
          (!last || (c_thread_received ?
603
0
                         (last->message->received < tmp->message->received) :
604
0
                         (last->message->date_sent < tmp->message->date_sent))) &&
605
0
          tmp->message->env->real_subj &&
606
0
          mutt_str_equal(np->data, tmp->message->env->real_subj))
607
0
      {
608
0
        last = tmp; /* best match so far */
609
0
      }
610
0
    }
611
0
  }
612
613
0
  mutt_list_clear(&subjects);
614
0
  return last;
615
0
}
616
617
/**
618
 * make_subj_hash - Create a Hash Table for the email subjects
619
 * @param m Mailbox
620
 * @retval ptr Newly allocated Hash Table
621
 */
622
static struct HashTable *make_subj_hash(struct Mailbox *m)
623
0
{
624
0
  if (!m)
625
0
    return NULL;
626
627
0
  struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_ALLOW_DUPS);
628
629
0
  for (int i = 0; i < m->msg_count; i++)
630
0
  {
631
0
    struct Email *e = m->emails[i];
632
0
    if (!e || !e->env)
633
0
      continue;
634
0
    if (e->env->real_subj)
635
0
      mutt_hash_insert(hash, e->env->real_subj, e);
636
0
  }
637
638
0
  return hash;
639
0
}
640
641
/**
642
 * pseudo_threads - Thread messages by subject
643
 * @param tctx Threading context
644
 *
645
 * Thread by subject things that didn't get threaded by message-id
646
 */
647
static void pseudo_threads(struct ThreadsContext *tctx)
648
0
{
649
0
  if (!tctx || !tctx->mailbox_view)
650
0
    return;
651
652
0
  struct Mailbox *m = tctx->mailbox_view->mailbox;
653
654
0
  struct MuttThread *tree = tctx->tree;
655
0
  struct MuttThread *top = tree;
656
0
  struct MuttThread *tmp = NULL, *cur = NULL, *parent = NULL, *curchild = NULL,
657
0
                    *nextchild = NULL;
658
659
0
  if (!m->subj_hash)
660
0
    m->subj_hash = make_subj_hash(m);
661
662
0
  while (tree)
663
0
  {
664
0
    cur = tree;
665
0
    tree = tree->next;
666
0
    parent = find_subject(m, cur);
667
0
    if (parent)
668
0
    {
669
0
      cur->fake_thread = true;
670
0
      unlink_message(&top, cur);
671
0
      insert_message(&parent->child, parent, cur);
672
0
      parent->sort_children = true;
673
0
      tmp = cur;
674
0
      while (true)
675
0
      {
676
0
        while (!tmp->message)
677
0
          tmp = tmp->child;
678
679
        /* if the message we're attaching has pseudo-children, they
680
         * need to be attached to its parent, so move them up a level.
681
         * but only do this if they have the same real subject as the
682
         * parent, since otherwise they rightly belong to the message
683
         * we're attaching. */
684
0
        if ((tmp == cur) || mutt_str_equal(tmp->message->env->real_subj,
685
0
                                           parent->message->env->real_subj))
686
0
        {
687
0
          tmp->message->subject_changed = false;
688
689
0
          for (curchild = tmp->child; curchild;)
690
0
          {
691
0
            nextchild = curchild->next;
692
0
            if (curchild->fake_thread)
693
0
            {
694
0
              unlink_message(&tmp->child, curchild);
695
0
              insert_message(&parent->child, parent, curchild);
696
0
            }
697
0
            curchild = nextchild;
698
0
          }
699
0
        }
700
701
0
        while (!tmp->next && (tmp != cur))
702
0
        {
703
0
          tmp = tmp->parent;
704
0
        }
705
0
        if (tmp == cur)
706
0
          break;
707
0
        tmp = tmp->next;
708
0
      }
709
0
    }
710
0
  }
711
0
  tctx->tree = top;
712
0
}
713
714
/**
715
 * mutt_clear_threads - Clear the threading of message in a mailbox
716
 * @param tctx Threading context
717
 */
718
void mutt_clear_threads(struct ThreadsContext *tctx)
719
0
{
720
0
  if (!tctx || !tctx->tree)
721
0
    return;
722
723
0
  struct MailboxView *mv = tctx->mailbox_view;
724
0
  if (!mv)
725
0
    return;
726
727
0
  struct Mailbox *m = mv->mailbox;
728
0
  if (!m || !m->emails)
729
0
    return;
730
731
0
  for (int i = 0; i < m->msg_count; i++)
732
0
  {
733
0
    struct Email *e = m->emails[i];
734
0
    if (!e)
735
0
      break;
736
737
    /* mailbox may have been only partially read */
738
0
    e->thread = NULL;
739
0
    e->threaded = false;
740
0
  }
741
0
  tctx->tree = NULL;
742
0
  mutt_hash_free(&tctx->hash);
743
0
}
744
745
/**
746
 * compare_threads - Helper to sort email threads - Implements ::sort_t - @ingroup sort_api
747
 */
748
static int compare_threads(const void *a, const void *b, void *sdata)
749
0
{
750
0
  const struct MuttThread *ta = *(struct MuttThread const *const *) a;
751
0
  const struct MuttThread *tb = *(struct MuttThread const *const *) b;
752
0
  const struct ThreadsContext *tctx = sdata;
753
0
  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
0
  struct Mailbox *m = tctx->mailbox_view->mailbox;
758
0
  const enum MailboxType mtype = mx_type(m);
759
0
  if (ta->parent)
760
0
  {
761
0
    return mutt_compare_emails(ta->sort_aux_key, tb->sort_aux_key, mtype,
762
0
                               tctx->c_sort_aux, SORT_REVERSE | EMAIL_SORT_UNSORTED);
763
0
  }
764
0
  else
765
0
  {
766
0
    return mutt_compare_emails(ta->sort_thread_key, tb->sort_thread_key, mtype,
767
0
                               tctx->c_sort, SORT_REVERSE | EMAIL_SORT_UNSORTED);
768
0
  }
769
0
}
770
771
/**
772
 * mutt_sort_subthreads - Sort the children of a thread
773
 * @param tctx Threading context
774
 * @param init If true, rebuild the thread
775
 */
776
static void mutt_sort_subthreads(struct ThreadsContext *tctx, bool init)
777
0
{
778
0
  struct MuttThread *thread = tctx->tree;
779
0
  if (!thread)
780
0
    return;
781
782
0
  struct MuttThread **array = NULL, *top = NULL, *tmp = NULL;
783
0
  struct Email *sort_aux_key = NULL, *oldsort_aux_key = NULL;
784
0
  struct Email *oldsort_thread_key = NULL;
785
0
  int i, array_size;
786
0
  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
0
  const bool reverse = (mutt_thread_style() == UT_REVERSE);
793
0
  enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
794
0
  enum EmailSortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
795
0
  if ((c_sort & SORT_MASK) == EMAIL_SORT_THREADS)
796
0
  {
797
0
    ASSERT(!(c_sort & SORT_REVERSE) != reverse);
798
0
    c_sort = c_sort_aux;
799
0
  }
800
0
  c_sort ^= SORT_REVERSE;
801
0
  c_sort_aux ^= SORT_REVERSE;
802
0
  if (init || (tctx->c_sort != c_sort) || (tctx->c_sort_aux != c_sort_aux))
803
0
  {
804
0
    tctx->c_sort = c_sort;
805
0
    tctx->c_sort_aux = c_sort_aux;
806
0
    init = true;
807
0
  }
808
809
0
  top = thread;
810
811
0
  array_size = 256;
812
0
  array = MUTT_MEM_CALLOC(array_size, struct MuttThread *);
813
0
  while (true)
814
0
  {
815
0
    if (init || !thread->sort_thread_key || !thread->sort_aux_key)
816
0
    {
817
0
      thread->sort_thread_key = NULL;
818
0
      thread->sort_aux_key = NULL;
819
820
0
      if (thread->parent)
821
0
        thread->parent->sort_children = true;
822
0
      else
823
0
        sort_top = true;
824
0
    }
825
826
0
    if (thread->child)
827
0
    {
828
0
      thread = thread->child;
829
0
      continue;
830
0
    }
831
0
    else
832
0
    {
833
      /* if it has no children, it must be real. sort it on its own merits */
834
0
      thread->sort_thread_key = thread->message;
835
0
      thread->sort_aux_key = thread->message;
836
837
0
      if (thread->next)
838
0
      {
839
0
        thread = thread->next;
840
0
        continue;
841
0
      }
842
0
    }
843
844
0
    struct Mailbox *m = tctx->mailbox_view->mailbox;
845
0
    const enum MailboxType mtype = mx_type(m);
846
0
    while (!thread->next)
847
0
    {
848
      /* if it has siblings and needs to be sorted, sort it... */
849
0
      if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
850
0
      {
851
        /* put them into the array */
852
0
        for (i = 0; thread; i++, thread = thread->prev)
853
0
        {
854
0
          if (i >= array_size)
855
0
          {
856
0
            array_size *= 2;
857
0
            MUTT_MEM_REALLOC(&array, array_size, struct MuttThread *);
858
0
          }
859
860
0
          array[i] = thread;
861
0
        }
862
863
0
        mutt_qsort_r((void *) array, i, sizeof(struct MuttThread *), compare_threads, tctx);
864
865
        /* attach them back together.  make thread the last sibling. */
866
0
        thread = array[0];
867
0
        thread->next = NULL;
868
0
        array[i - 1]->prev = NULL;
869
870
0
        if (thread->parent)
871
0
          thread->parent->child = array[i - 1];
872
0
        else
873
0
          top = array[i - 1];
874
875
0
        while (--i)
876
0
        {
877
0
          array[i - 1]->prev = array[i];
878
0
          array[i]->next = array[i - 1];
879
0
        }
880
0
      }
881
882
0
      if (thread->parent)
883
0
      {
884
0
        tmp = thread;
885
0
        thread = thread->parent;
886
887
0
        if (!thread->sort_thread_key || !thread->sort_aux_key || thread->sort_children)
888
0
        {
889
          /* we just sorted its children */
890
0
          thread->sort_children = false;
891
892
0
          oldsort_aux_key = thread->sort_aux_key;
893
0
          oldsort_thread_key = thread->sort_thread_key;
894
895
          /* update sort keys. sort_aux_key will be the first or last
896
           * sibling, as appropriate... */
897
0
          thread->sort_aux_key = thread->message;
898
0
          sort_aux_key = ((!(c_sort_aux & SORT_LAST)) ^ (!(c_sort_aux & SORT_REVERSE))) ?
899
0
                             thread->child->sort_aux_key :
900
0
                             tmp->sort_aux_key;
901
902
0
          if (c_sort_aux & SORT_LAST)
903
0
          {
904
0
            if (!thread->sort_aux_key ||
905
0
                (mutt_compare_emails(thread->sort_aux_key, sort_aux_key, mtype,
906
0
                                     c_sort_aux | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
907
0
            {
908
0
              thread->sort_aux_key = sort_aux_key;
909
0
            }
910
0
          }
911
0
          else if (!thread->sort_aux_key)
912
0
          {
913
0
            thread->sort_aux_key = sort_aux_key;
914
0
          }
915
916
          /* ...but sort_thread_key may require searching the entire
917
           * list of siblings */
918
0
          if ((c_sort_aux & ~SORT_REVERSE) == (c_sort & ~SORT_REVERSE))
919
0
          {
920
0
            thread->sort_thread_key = thread->sort_aux_key;
921
0
          }
922
0
          else
923
0
          {
924
0
            if (thread->message)
925
0
            {
926
0
              thread->sort_thread_key = thread->message;
927
0
            }
928
0
            else if (reverse != (!(c_sort_aux & SORT_REVERSE)))
929
0
            {
930
0
              thread->sort_thread_key = tmp->sort_thread_key;
931
0
            }
932
0
            else
933
0
            {
934
0
              thread->sort_thread_key = thread->child->sort_thread_key;
935
0
            }
936
0
            if (c_sort & SORT_LAST)
937
0
            {
938
0
              for (tmp = thread->child; tmp; tmp = tmp->next)
939
0
              {
940
0
                if (tmp->sort_thread_key == thread->sort_thread_key)
941
0
                  continue;
942
0
                if ((mutt_compare_emails(thread->sort_thread_key, tmp->sort_thread_key, mtype,
943
0
                                         c_sort | SORT_REVERSE, EMAIL_SORT_UNSORTED) > 0))
944
0
                {
945
0
                  thread->sort_thread_key = tmp->sort_thread_key;
946
0
                }
947
0
              }
948
0
            }
949
0
          }
950
951
          /* if a sort_key has changed, we need to resort it and siblings */
952
0
          if ((oldsort_aux_key != thread->sort_aux_key) ||
953
0
              (oldsort_thread_key != thread->sort_thread_key))
954
0
          {
955
0
            if (thread->parent)
956
0
              thread->parent->sort_children = true;
957
0
            else
958
0
              sort_top = true;
959
0
          }
960
0
        }
961
0
      }
962
0
      else
963
0
      {
964
0
        FREE(&array);
965
0
        tctx->tree = top;
966
0
        return;
967
0
      }
968
0
    }
969
970
0
    thread = thread->next;
971
0
  }
972
0
}
973
974
/**
975
 * check_subjects - Find out which emails' subjects differ from their parent's
976
 * @param mv   Mailbox View
977
 * @param init If true, rebuild the thread
978
 */
979
static void check_subjects(struct MailboxView *mv, bool init)
980
0
{
981
0
  if (!mv)
982
0
    return;
983
984
0
  struct Mailbox *m = mv->mailbox;
985
0
  for (int i = 0; i < m->msg_count; i++)
986
0
  {
987
0
    struct Email *e = m->emails[i];
988
0
    if (!e || !e->thread)
989
0
      continue;
990
991
0
    if (e->thread->check_subject)
992
0
      e->thread->check_subject = false;
993
0
    else if (!init)
994
0
      continue;
995
996
    /* figure out which messages have subjects different than their parents' */
997
0
    struct MuttThread *tmp = e->thread->parent;
998
0
    while (tmp && !tmp->message)
999
0
    {
1000
0
      tmp = tmp->parent;
1001
0
    }
1002
1003
0
    if (!tmp)
1004
0
    {
1005
0
      e->subject_changed = true;
1006
0
    }
1007
0
    else if (e->env->real_subj && tmp->message->env->real_subj)
1008
0
    {
1009
0
      e->subject_changed = !mutt_str_equal(e->env->real_subj, tmp->message->env->real_subj);
1010
0
    }
1011
0
    else
1012
0
    {
1013
0
      e->subject_changed = (e->env->real_subj || tmp->message->env->real_subj);
1014
0
    }
1015
0
  }
1016
0
}
1017
1018
/**
1019
 * thread_hash_destructor - Free our hash table data - Implements ::hash_hdata_free_t - @ingroup hash_hdata_free_api
1020
 */
1021
static void thread_hash_destructor(int type, void *obj, intptr_t data)
1022
0
{
1023
0
  FREE(&obj);
1024
0
}
1025
1026
/**
1027
 * mutt_sort_threads - Sort email threads
1028
 * @param tctx Threading context
1029
 * @param init If true, rebuild the thread
1030
 */
1031
void mutt_sort_threads(struct ThreadsContext *tctx, bool init)
1032
0
{
1033
0
  if (!tctx || !tctx->mailbox_view)
1034
0
    return;
1035
1036
0
  struct MailboxView *mv = tctx->mailbox_view;
1037
0
  struct Mailbox *m = mv->mailbox;
1038
1039
0
  struct Email *e = NULL;
1040
0
  int i, using_refs = 0;
1041
0
  struct MuttThread *thread = NULL, *tnew = NULL, *tmp = NULL;
1042
0
  struct MuttThread top = { 0 };
1043
0
  struct ListNode *ref = NULL;
1044
1045
0
  ASSERT(m->msg_count > 0);
1046
0
  if (!tctx->hash)
1047
0
    init = true;
1048
1049
0
  if (init)
1050
0
  {
1051
0
    tctx->hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_ALLOW_DUPS);
1052
0
    mutt_hash_set_destructor(tctx->hash, thread_hash_destructor, 0);
1053
0
  }
1054
1055
  /* we want a quick way to see if things are actually attached to the top of the
1056
   * thread tree or if they're just dangling, so we attach everything to a top
1057
   * node temporarily */
1058
0
  top.parent = NULL;
1059
0
  top.next = NULL;
1060
0
  top.prev = NULL;
1061
1062
0
  top.child = tctx->tree;
1063
0
  for (thread = tctx->tree; thread; thread = thread->next)
1064
0
    thread->parent = &top;
1065
1066
  /* put each new message together with the matching messageless MuttThread if it
1067
   * exists.  otherwise, if there is a MuttThread that already has a message, thread
1068
   * new message as an identical child.  if we didn't attach the message to a
1069
   * MuttThread, make a new one for it. */
1070
0
  const bool c_duplicate_threads = cs_subset_bool(NeoMutt->sub, "duplicate_threads");
1071
0
  for (i = 0; i < m->msg_count; i++)
1072
0
  {
1073
0
    e = m->emails[i];
1074
0
    if (!e)
1075
0
      continue;
1076
1077
0
    if (e->thread)
1078
0
    {
1079
      /* unlink pseudo-threads because they might be children of newly
1080
       * arrived messages */
1081
0
      thread = e->thread;
1082
0
      for (tnew = thread->child; tnew;)
1083
0
      {
1084
0
        tmp = tnew->next;
1085
0
        if (tnew->fake_thread)
1086
0
        {
1087
0
          unlink_message(&thread->child, tnew);
1088
0
          insert_message(&top.child, &top, tnew);
1089
0
          tnew->fake_thread = false;
1090
0
        }
1091
0
        tnew = tmp;
1092
0
      }
1093
0
    }
1094
0
    else
1095
0
    {
1096
0
      if ((!init || c_duplicate_threads) && e->env->message_id)
1097
0
        thread = mutt_hash_find(tctx->hash, e->env->message_id);
1098
0
      else
1099
0
        thread = NULL;
1100
1101
0
      if (thread && !thread->message)
1102
0
      {
1103
        /* this is a message which was missing before */
1104
0
        thread->message = e;
1105
0
        e->thread = thread;
1106
0
        thread->check_subject = true;
1107
1108
        /* mark descendants as needing subject_changed checked */
1109
0
        for (tmp = (thread->child ? thread->child : thread); tmp != thread;)
1110
0
        {
1111
0
          while (!tmp->message)
1112
0
            tmp = tmp->child;
1113
0
          tmp->check_subject = true;
1114
0
          while (!tmp->next && (tmp != thread))
1115
0
            tmp = tmp->parent;
1116
0
          if (tmp != thread)
1117
0
            tmp = tmp->next;
1118
0
        }
1119
1120
0
        if (thread->parent)
1121
0
        {
1122
          /* remove threading info above it based on its children, which we'll
1123
           * recalculate based on its headers.  make sure not to leave
1124
           * dangling missing messages.  note that we haven't kept track
1125
           * of what info came from its children and what from its siblings'
1126
           * children, so we just remove the stuff that's definitely from it */
1127
0
          do
1128
0
          {
1129
0
            tmp = thread->parent;
1130
0
            unlink_message(&tmp->child, thread);
1131
0
            thread->parent = NULL;
1132
0
            thread->sort_thread_key = NULL;
1133
0
            thread->sort_aux_key = NULL;
1134
0
            thread->fake_thread = false;
1135
0
            thread = tmp;
1136
0
          } while (thread != &top && !thread->child && !thread->message);
1137
0
        }
1138
0
      }
1139
0
      else
1140
0
      {
1141
0
        tnew = (c_duplicate_threads ? thread : NULL);
1142
1143
0
        thread = MUTT_MEM_CALLOC(1, struct MuttThread);
1144
0
        thread->message = e;
1145
0
        thread->check_subject = true;
1146
0
        e->thread = thread;
1147
0
        mutt_hash_insert(tctx->hash, e->env->message_id ? e->env->message_id : "", thread);
1148
1149
0
        if (tnew)
1150
0
        {
1151
0
          if (tnew->duplicate_thread)
1152
0
            tnew = tnew->parent;
1153
1154
0
          thread = e->thread;
1155
1156
0
          insert_message(&tnew->child, tnew, thread);
1157
0
          thread->duplicate_thread = true;
1158
0
          thread->message->threaded = true;
1159
0
        }
1160
0
      }
1161
0
    }
1162
0
  }
1163
1164
  /* thread by references */
1165
0
  for (i = 0; i < m->msg_count; i++)
1166
0
  {
1167
0
    e = m->emails[i];
1168
0
    if (!e)
1169
0
      break;
1170
1171
0
    if (e->threaded)
1172
0
      continue;
1173
0
    e->threaded = true;
1174
1175
0
    thread = e->thread;
1176
0
    if (!thread)
1177
0
      continue;
1178
0
    using_refs = 0;
1179
1180
0
    while (true)
1181
0
    {
1182
0
      if (using_refs == 0)
1183
0
      {
1184
        /* look at the beginning of in-reply-to: */
1185
0
        ref = STAILQ_FIRST(&e->env->in_reply_to);
1186
0
        if (ref)
1187
0
        {
1188
0
          using_refs = 1;
1189
0
        }
1190
0
        else
1191
0
        {
1192
0
          ref = STAILQ_FIRST(&e->env->references);
1193
0
          using_refs = 2;
1194
0
        }
1195
0
      }
1196
0
      else if (using_refs == 1)
1197
0
      {
1198
        /* if there's no references header, use all the in-reply-to:
1199
         * data that we have.  otherwise, use the first reference
1200
         * if it's different than the first in-reply-to, otherwise use
1201
         * the second reference (since at least eudora puts the most
1202
         * recent reference in in-reply-to and the rest in references) */
1203
0
        if (STAILQ_EMPTY(&e->env->references))
1204
0
        {
1205
0
          ref = STAILQ_NEXT(ref, entries);
1206
0
        }
1207
0
        else
1208
0
        {
1209
0
          if (!mutt_str_equal(ref->data, STAILQ_FIRST(&e->env->references)->data))
1210
0
            ref = STAILQ_FIRST(&e->env->references);
1211
0
          else
1212
0
            ref = STAILQ_NEXT(STAILQ_FIRST(&e->env->references), entries);
1213
1214
0
          using_refs = 2;
1215
0
        }
1216
0
      }
1217
0
      else
1218
0
      {
1219
0
        ref = STAILQ_NEXT(ref, entries); /* go on with references */
1220
0
      }
1221
1222
0
      if (!ref)
1223
0
        break;
1224
1225
0
      tnew = mutt_hash_find(tctx->hash, ref->data);
1226
0
      if (tnew)
1227
0
      {
1228
0
        if (tnew->duplicate_thread)
1229
0
          tnew = tnew->parent;
1230
0
        if (is_descendant(tnew, thread)) /* no loops! */
1231
0
          continue;
1232
0
      }
1233
0
      else
1234
0
      {
1235
0
        tnew = MUTT_MEM_CALLOC(1, struct MuttThread);
1236
0
        mutt_hash_insert(tctx->hash, ref->data, tnew);
1237
0
      }
1238
1239
0
      if (thread->parent)
1240
0
        unlink_message(&top.child, thread);
1241
0
      insert_message(&tnew->child, tnew, thread);
1242
0
      thread = tnew;
1243
0
      if (thread->message || (thread->parent && (thread->parent != &top)))
1244
0
        break;
1245
0
    }
1246
1247
0
    if (!thread->parent)
1248
0
      insert_message(&top.child, &top, thread);
1249
0
  }
1250
1251
  /* detach everything from the temporary top node */
1252
0
  for (thread = top.child; thread; thread = thread->next)
1253
0
  {
1254
0
    thread->parent = NULL;
1255
0
  }
1256
0
  tctx->tree = top.child;
1257
1258
0
  check_subjects(mv, init);
1259
1260
0
  const bool c_strict_threads = cs_subset_bool(NeoMutt->sub, "strict_threads");
1261
0
  if (!c_strict_threads)
1262
0
    pseudo_threads(tctx);
1263
1264
  /* if $sort_aux or similar changed after the mailbox is sorted, then
1265
   * all the subthreads need to be resorted */
1266
0
  if (tctx->tree)
1267
0
  {
1268
0
    mutt_sort_subthreads(tctx, init || OptSortSubthreads);
1269
0
    OptSortSubthreads = false;
1270
1271
    /* Put the list into an array. */
1272
0
    linearize_tree(tctx);
1273
1274
    /* Draw the thread tree. */
1275
0
    mutt_draw_tree(tctx);
1276
0
  }
1277
0
}
1278
1279
/**
1280
 * mutt_aside_thread - Find the next/previous (sub)thread
1281
 * @param e          Search from this Email
1282
 * @param forwards   Direction to search: 'true' forwards, 'false' backwards
1283
 * @param subthreads Search subthreads: 'true' subthread, 'false' not
1284
 * @retval num Index into the virtual email table
1285
 * @retval  -1 Error
1286
 */
1287
int mutt_aside_thread(struct Email *e, bool forwards, bool subthreads)
1288
0
{
1289
0
  if (!e)
1290
0
    return -1;
1291
1292
0
  struct MuttThread *cur = NULL;
1293
0
  struct Email *e_tmp = NULL;
1294
1295
0
  const enum UseThreads threaded = mutt_thread_style();
1296
0
  if (threaded == UT_FLAT)
1297
0
  {
1298
0
    mutt_error(_("Threading is not enabled"));
1299
0
    return e->vnum;
1300
0
  }
1301
1302
0
  cur = e->thread;
1303
1304
0
  if (subthreads)
1305
0
  {
1306
0
    if (forwards ^ (threaded == UT_REVERSE))
1307
0
    {
1308
0
      while (!cur->next && cur->parent)
1309
0
        cur = cur->parent;
1310
0
    }
1311
0
    else
1312
0
    {
1313
0
      while (!cur->prev && cur->parent)
1314
0
        cur = cur->parent;
1315
0
    }
1316
0
  }
1317
0
  else
1318
0
  {
1319
0
    while (cur->parent)
1320
0
      cur = cur->parent;
1321
0
  }
1322
1323
0
  if (forwards ^ (threaded == UT_REVERSE))
1324
0
  {
1325
0
    do
1326
0
    {
1327
0
      cur = cur->next;
1328
0
      if (!cur)
1329
0
        return -1;
1330
0
      e_tmp = find_virtual(cur, false);
1331
0
    } while (!e_tmp);
1332
0
  }
1333
0
  else
1334
0
  {
1335
0
    do
1336
0
    {
1337
0
      cur = cur->prev;
1338
0
      if (!cur)
1339
0
        return -1;
1340
0
      e_tmp = find_virtual(cur, true);
1341
0
    } while (!e_tmp);
1342
0
  }
1343
1344
0
  return e_tmp->vnum;
1345
0
}
1346
1347
/**
1348
 * mutt_parent_message - Find the parent of a message
1349
 * @param e         Current Email
1350
 * @param find_root If true, find the root message
1351
 * @retval >=0 Virtual index number of parent/root message
1352
 * @retval -1 Error
1353
 */
1354
int mutt_parent_message(struct Email *e, bool find_root)
1355
0
{
1356
0
  if (!e)
1357
0
    return -1;
1358
1359
0
  struct MuttThread *thread = NULL;
1360
0
  struct Email *e_parent = NULL;
1361
1362
0
  if (!mutt_using_threads())
1363
0
  {
1364
0
    mutt_error(_("Threading is not enabled"));
1365
0
    return e->vnum;
1366
0
  }
1367
1368
  /* Root may be the current message */
1369
0
  if (find_root)
1370
0
    e_parent = e;
1371
1372
0
  for (thread = e->thread->parent; thread; thread = thread->parent)
1373
0
  {
1374
0
    e = thread->message;
1375
0
    if (e)
1376
0
    {
1377
0
      e_parent = e;
1378
0
      if (!find_root)
1379
0
        break;
1380
0
    }
1381
0
  }
1382
1383
0
  if (!e_parent)
1384
0
  {
1385
0
    mutt_error(_("Parent message is not available"));
1386
0
    return -1;
1387
0
  }
1388
0
  if (!is_visible(e_parent))
1389
0
  {
1390
0
    if (find_root)
1391
0
      mutt_error(_("Root message is not visible in this limited view"));
1392
0
    else
1393
0
      mutt_error(_("Parent message is not visible in this limited view"));
1394
0
    return -1;
1395
0
  }
1396
0
  return e_parent->vnum;
1397
0
}
1398
1399
/**
1400
 * mutt_set_vnum - Set the virtual index number of all the messages in a mailbox
1401
 * @param m       Mailbox
1402
 * @retval num Size in bytes of all messages shown
1403
 */
1404
off_t mutt_set_vnum(struct Mailbox *m)
1405
0
{
1406
0
  if (!m)
1407
0
    return 0;
1408
1409
0
  off_t vsize = 0;
1410
0
  const int padding = mx_msg_padding_size(m);
1411
1412
0
  m->vcount = 0;
1413
1414
0
  for (int i = 0; i < m->msg_count; i++)
1415
0
  {
1416
0
    struct Email *e = m->emails[i];
1417
0
    if (!e)
1418
0
      break;
1419
1420
0
    if (e->vnum >= 0)
1421
0
    {
1422
0
      e->vnum = m->vcount;
1423
0
      m->v2r[m->vcount] = i;
1424
0
      m->vcount++;
1425
0
      vsize += e->body->length + e->body->offset - e->body->hdr_offset + padding;
1426
0
    }
1427
0
  }
1428
1429
0
  return vsize;
1430
0
}
1431
1432
/**
1433
 * mutt_traverse_thread - Recurse through an email thread, matching messages
1434
 * @param e_cur Current Email
1435
 * @param flag  Flag to set, see #MuttThreadFlags
1436
 * @retval num Number of matches
1437
 */
1438
int mutt_traverse_thread(struct Email *e_cur, MuttThreadFlags flag)
1439
0
{
1440
0
  struct MuttThread *thread = NULL, *top = NULL;
1441
0
  struct Email *e_root = NULL;
1442
0
  const enum UseThreads threaded = mutt_thread_style();
1443
0
  int final, reverse = (threaded == UT_REVERSE), minmsgno;
1444
0
  int num_hidden = 0, new_mail = 0, old_mail = 0;
1445
0
  bool flagged = false;
1446
0
  int min_unread_msgno = INT_MAX, min_unread = e_cur->vnum;
1447
1448
0
  if (threaded == UT_FLAT)
1449
0
  {
1450
0
    mutt_error(_("Threading is not enabled"));
1451
0
    return e_cur->vnum;
1452
0
  }
1453
1454
0
  if (!e_cur->thread)
1455
0
  {
1456
0
    return e_cur->vnum;
1457
0
  }
1458
1459
0
  final = e_cur->vnum;
1460
0
  thread = e_cur->thread;
1461
0
  while (thread->parent)
1462
0
    thread = thread->parent;
1463
0
  top = thread;
1464
0
  while (!thread->message)
1465
0
    thread = thread->child;
1466
0
  e_cur = thread->message;
1467
0
  minmsgno = e_cur->msgno;
1468
1469
0
  if (!e_cur->read && e_cur->visible)
1470
0
  {
1471
0
    if (e_cur->old)
1472
0
      old_mail = 2;
1473
0
    else
1474
0
      new_mail = 1;
1475
0
    if (e_cur->msgno < min_unread_msgno)
1476
0
    {
1477
0
      min_unread = e_cur->vnum;
1478
0
      min_unread_msgno = e_cur->msgno;
1479
0
    }
1480
0
  }
1481
1482
0
  if (e_cur->flagged && e_cur->visible)
1483
0
    flagged = true;
1484
1485
0
  if ((e_cur->vnum == -1) && e_cur->visible)
1486
0
    num_hidden++;
1487
1488
0
  if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1489
0
  {
1490
0
    e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1491
0
    e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1492
0
    if (e_cur->vnum != -1)
1493
0
    {
1494
0
      e_root = e_cur;
1495
0
      if (flag & MUTT_THREAD_COLLAPSE)
1496
0
        final = e_root->vnum;
1497
0
    }
1498
0
  }
1499
1500
0
  if ((thread == top) && !(thread = thread->child))
1501
0
  {
1502
    /* return value depends on action requested */
1503
0
    if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1504
0
    {
1505
0
      e_cur->num_hidden = num_hidden;
1506
0
      return final;
1507
0
    }
1508
0
    if (flag & MUTT_THREAD_UNREAD)
1509
0
      return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1510
0
    if (flag & MUTT_THREAD_NEXT_UNREAD)
1511
0
      return min_unread;
1512
0
    if (flag & MUTT_THREAD_FLAGGED)
1513
0
      return flagged;
1514
0
  }
1515
1516
0
  while (true)
1517
0
  {
1518
0
    e_cur = thread->message;
1519
1520
0
    if (e_cur)
1521
0
    {
1522
0
      if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1523
0
      {
1524
0
        e_cur->attr_color = NULL; /* force index entry's color to be re-evaluated */
1525
0
        e_cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
1526
0
        if (!e_root && e_cur->visible)
1527
0
        {
1528
0
          e_root = e_cur;
1529
0
          if (flag & MUTT_THREAD_COLLAPSE)
1530
0
            final = e_root->vnum;
1531
0
        }
1532
1533
0
        if (reverse && (flag & MUTT_THREAD_COLLAPSE) &&
1534
0
            (e_cur->msgno < minmsgno) && e_cur->visible)
1535
0
        {
1536
0
          minmsgno = e_cur->msgno;
1537
0
          final = e_cur->vnum;
1538
0
        }
1539
1540
0
        if (flag & MUTT_THREAD_COLLAPSE)
1541
0
        {
1542
0
          if (e_cur != e_root)
1543
0
            e_cur->vnum = -1;
1544
0
        }
1545
0
        else
1546
0
        {
1547
0
          if (e_cur->visible)
1548
0
            e_cur->vnum = e_cur->msgno;
1549
0
        }
1550
0
      }
1551
1552
0
      if (!e_cur->read && e_cur->visible)
1553
0
      {
1554
0
        if (e_cur->old)
1555
0
          old_mail = 2;
1556
0
        else
1557
0
          new_mail = 1;
1558
0
        if (e_cur->msgno < min_unread_msgno)
1559
0
        {
1560
0
          min_unread = e_cur->vnum;
1561
0
          min_unread_msgno = e_cur->msgno;
1562
0
        }
1563
0
      }
1564
1565
0
      if (e_cur->flagged && e_cur->visible)
1566
0
        flagged = true;
1567
1568
0
      if ((e_cur->vnum == -1) && e_cur->visible)
1569
0
        num_hidden++;
1570
0
    }
1571
1572
0
    if (thread->child)
1573
0
    {
1574
0
      thread = thread->child;
1575
0
    }
1576
0
    else if (thread->next)
1577
0
    {
1578
0
      thread = thread->next;
1579
0
    }
1580
0
    else
1581
0
    {
1582
0
      bool done = false;
1583
0
      while (!thread->next)
1584
0
      {
1585
0
        thread = thread->parent;
1586
0
        if (thread == top)
1587
0
        {
1588
0
          done = true;
1589
0
          break;
1590
0
        }
1591
0
      }
1592
0
      if (done)
1593
0
        break;
1594
0
      thread = thread->next;
1595
0
    }
1596
0
  }
1597
1598
  /* re-traverse the thread and store num_hidden in all headers, with or
1599
   * without a virtual index.  this will allow ~v to match all collapsed
1600
   * messages when switching sort order to non-threaded.  */
1601
0
  if (flag & MUTT_THREAD_COLLAPSE)
1602
0
  {
1603
0
    thread = top;
1604
0
    while (true)
1605
0
    {
1606
0
      e_cur = thread->message;
1607
0
      if (e_cur)
1608
0
        e_cur->num_hidden = num_hidden + 1;
1609
1610
0
      if (thread->child)
1611
0
      {
1612
0
        thread = thread->child;
1613
0
      }
1614
0
      else if (thread->next)
1615
0
      {
1616
0
        thread = thread->next;
1617
0
      }
1618
0
      else
1619
0
      {
1620
0
        bool done = false;
1621
0
        while (!thread->next)
1622
0
        {
1623
0
          thread = thread->parent;
1624
0
          if (thread == top)
1625
0
          {
1626
0
            done = true;
1627
0
            break;
1628
0
          }
1629
0
        }
1630
0
        if (done)
1631
0
          break;
1632
0
        thread = thread->next;
1633
0
      }
1634
0
    }
1635
0
  }
1636
1637
  /* return value depends on action requested */
1638
0
  if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
1639
0
    return final;
1640
0
  if (flag & MUTT_THREAD_UNREAD)
1641
0
    return (old_mail && new_mail) ? new_mail : (old_mail ? old_mail : new_mail);
1642
0
  if (flag & MUTT_THREAD_NEXT_UNREAD)
1643
0
    return min_unread;
1644
0
  if (flag & MUTT_THREAD_FLAGGED)
1645
0
    return flagged;
1646
1647
0
  return 0;
1648
0
}
1649
1650
/**
1651
 * mutt_messages_in_thread - Count the messages in a thread
1652
 * @param m    Mailbox
1653
 * @param e    Email
1654
 * @param mit  Flag, e.g. #MIT_NUM_MESSAGES
1655
 * @retval num Number of message / Our position
1656
 */
1657
int mutt_messages_in_thread(struct Mailbox *m, struct Email *e, enum MessageInThread mit)
1658
0
{
1659
0
  if (!m || !e)
1660
0
    return 1;
1661
1662
0
  struct MuttThread *threads[2];
1663
0
  int rc;
1664
1665
0
  const enum UseThreads threaded = mutt_thread_style();
1666
0
  if ((threaded == UT_FLAT) || !e->thread)
1667
0
    return 1;
1668
1669
0
  threads[0] = e->thread;
1670
0
  while (threads[0]->parent)
1671
0
    threads[0] = threads[0]->parent;
1672
1673
0
  threads[1] = (mit == MIT_POSITION) ? e->thread : threads[0]->next;
1674
1675
0
  for (int i = 0; i < (((mit == MIT_POSITION) || !threads[1]) ? 1 : 2); i++)
1676
0
  {
1677
0
    while (!threads[i]->message)
1678
0
      threads[i] = threads[i]->child;
1679
0
  }
1680
1681
0
  if (threaded == UT_REVERSE)
1682
0
  {
1683
0
    rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
1684
0
  }
1685
0
  else
1686
0
  {
1687
0
    rc = (threads[1] ? threads[1]->message->msgno : m->msg_count) -
1688
0
         threads[0]->message->msgno;
1689
0
  }
1690
1691
0
  if (mit == MIT_POSITION)
1692
0
    rc += 1;
1693
1694
0
  return rc;
1695
0
}
1696
1697
/**
1698
 * mutt_make_id_hash - Create a Hash Table for message-ids
1699
 * @param m Mailbox
1700
 * @retval ptr Newly allocated Hash Table
1701
 */
1702
struct HashTable *mutt_make_id_hash(struct Mailbox *m)
1703
0
{
1704
0
  struct HashTable *hash = mutt_hash_new(m->msg_count * 2, MUTT_HASH_NO_FLAGS);
1705
1706
0
  for (int i = 0; i < m->msg_count; i++)
1707
0
  {
1708
0
    struct Email *e = m->emails[i];
1709
0
    if (!e || !e->env)
1710
0
      continue;
1711
1712
0
    if (e->env->message_id)
1713
0
      mutt_hash_insert(hash, e->env->message_id, e);
1714
0
  }
1715
1716
0
  return hash;
1717
0
}
1718
1719
/**
1720
 * link_threads - Forcibly link messages together
1721
 * @param parent Parent Email
1722
 * @param child  Child Email
1723
 * @param m      Mailbox
1724
 * @retval true On success
1725
 */
1726
static bool link_threads(struct Email *parent, struct Email *child, struct Mailbox *m)
1727
0
{
1728
0
  if (child == parent)
1729
0
    return false;
1730
1731
0
  mutt_break_thread(child);
1732
0
  mutt_list_insert_head(&child->env->in_reply_to, mutt_str_dup(parent->env->message_id));
1733
0
  mutt_set_flag(m, child, MUTT_TAG, false, true);
1734
1735
0
  child->changed = true;
1736
0
  child->env->changed |= MUTT_ENV_CHANGED_IRT;
1737
0
  return true;
1738
0
}
1739
1740
/**
1741
 * mutt_link_threads - Forcibly link threads together
1742
 * @param parent   Parent Email
1743
 * @param children Array of children Emails
1744
 * @param m        Mailbox
1745
 * @retval true On success
1746
 */
1747
bool mutt_link_threads(struct Email *parent, struct EmailArray *children, struct Mailbox *m)
1748
0
{
1749
0
  if (!parent || !children || !m)
1750
0
    return false;
1751
1752
0
  bool changed = false;
1753
1754
0
  struct Email **ep = NULL;
1755
0
  ARRAY_FOREACH(ep, children)
1756
0
  {
1757
0
    struct Email *e = *ep;
1758
0
    changed |= link_threads(parent, e, m);
1759
0
  }
1760
1761
0
  return changed;
1762
0
}
1763
1764
/**
1765
 * mutt_thread_collapse_collapsed - Re-collapse threads marked as collapsed
1766
 * @param tctx Threading context
1767
 */
1768
void mutt_thread_collapse_collapsed(struct ThreadsContext *tctx)
1769
0
{
1770
0
  struct MuttThread *thread = NULL;
1771
0
  struct MuttThread *top = tctx->tree;
1772
0
  while ((thread = top))
1773
0
  {
1774
0
    while (!thread->message)
1775
0
      thread = thread->child;
1776
1777
0
    struct Email *e = thread->message;
1778
0
    if (e->collapsed)
1779
0
      mutt_collapse_thread(e);
1780
0
    top = top->next;
1781
0
  }
1782
0
}
1783
1784
/**
1785
 * mutt_thread_collapse - Toggle collapse
1786
 * @param tctx Threading context
1787
 * @param collapse Collapse / uncollapse
1788
 */
1789
void mutt_thread_collapse(struct ThreadsContext *tctx, bool collapse)
1790
0
{
1791
0
  struct MuttThread *thread = NULL;
1792
0
  struct MuttThread *top = tctx->tree;
1793
0
  while ((thread = top))
1794
0
  {
1795
0
    while (!thread->message)
1796
0
      thread = thread->child;
1797
1798
0
    struct Email *e = thread->message;
1799
1800
0
    if (e->collapsed != collapse)
1801
0
    {
1802
0
      if (e->collapsed)
1803
0
        mutt_uncollapse_thread(e);
1804
0
      else if (mutt_thread_can_collapse(e))
1805
0
        mutt_collapse_thread(e);
1806
0
    }
1807
0
    top = top->next;
1808
0
  }
1809
0
}
1810
1811
/**
1812
 * mutt_thread_can_collapse - Check whether a thread can be collapsed
1813
 * @param e Head of the thread
1814
 * @retval true Can be collapsed
1815
 * @retval false Cannot be collapsed
1816
 */
1817
bool mutt_thread_can_collapse(struct Email *e)
1818
0
{
1819
0
  const bool c_collapse_flagged = cs_subset_bool(NeoMutt->sub, "collapse_flagged");
1820
0
  const bool c_collapse_unread = cs_subset_bool(NeoMutt->sub, "collapse_unread");
1821
0
  return (c_collapse_unread || !mutt_thread_contains_unread(e)) &&
1822
0
         (c_collapse_flagged || !mutt_thread_contains_flagged(e));
1823
0
}