PageRenderTime 240ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/thread.c

https://git.sr.ht/~kevin8t8/mutt/
C | 1480 lines | 1165 code | 180 blank | 135 comment | 381 complexity | c9c5d2567bfd2574a776fd53de08fab2 MD5 | raw file
Possible License(s): AGPL-1.0
  1. /*
  2. * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. */
  18. #if HAVE_CONFIG_H
  19. # include "config.h"
  20. #endif
  21. #include "mutt.h"
  22. #include "sort.h"
  23. #include "mailbox.h"
  24. #include <string.h>
  25. #include <ctype.h>
  26. #define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited)))
  27. /* determine whether a is a descendant of b */
  28. static int is_descendant (THREAD *a, THREAD *b)
  29. {
  30. while (a)
  31. {
  32. if (a == b)
  33. return (1);
  34. a = a->parent;
  35. }
  36. return (0);
  37. }
  38. /* Determines whether to display a message's subject. */
  39. static int need_display_subject (CONTEXT *ctx, HEADER *hdr)
  40. {
  41. THREAD *tmp, *tree = hdr->thread;
  42. /* if the user disabled subject hiding, display it */
  43. if (!option (OPTHIDETHREADSUBJECT))
  44. return (1);
  45. /* if our subject is different from our parent's, display it */
  46. if (hdr->subject_changed)
  47. return (1);
  48. /* if our subject is different from that of our closest previously displayed
  49. * sibling, display the subject */
  50. for (tmp = tree->prev; tmp; tmp = tmp->prev)
  51. {
  52. hdr = tmp->message;
  53. if (hdr && VISIBLE (hdr, ctx))
  54. {
  55. if (hdr->subject_changed)
  56. return (1);
  57. else
  58. break;
  59. }
  60. }
  61. /* if there is a parent-to-child subject change anywhere between us and our
  62. * closest displayed ancestor, display the subject */
  63. for (tmp = tree->parent; tmp; tmp = tmp->parent)
  64. {
  65. hdr = tmp->message;
  66. if (hdr)
  67. {
  68. if (VISIBLE (hdr, ctx))
  69. return (0);
  70. else if (hdr->subject_changed)
  71. return (1);
  72. }
  73. }
  74. /* if we have no visible parent or previous sibling, display the subject */
  75. return (1);
  76. }
  77. static void linearize_tree (CONTEXT *ctx)
  78. {
  79. THREAD *tree = ctx->tree;
  80. HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0);
  81. while (tree)
  82. {
  83. while (!tree->message)
  84. tree = tree->child;
  85. *array = tree->message;
  86. array += Sort & SORT_REVERSE ? -1 : 1;
  87. if (tree->child)
  88. tree = tree->child;
  89. else
  90. {
  91. while (tree)
  92. {
  93. if (tree->next)
  94. {
  95. tree = tree->next;
  96. break;
  97. }
  98. else
  99. tree = tree->parent;
  100. }
  101. }
  102. }
  103. }
  104. /* this calculates whether a node is the root of a subtree that has visible
  105. * nodes, whether a node itself is visible, whether, if invisible, it has
  106. * depth anyway, and whether any of its later siblings are roots of visible
  107. * subtrees. while it's at it, it frees the old thread display, so we can
  108. * skip parts of the tree in mutt_draw_tree() if we've decided here that we
  109. * don't care about them any more.
  110. */
  111. static void calculate_visibility (CONTEXT *ctx, int *max_depth)
  112. {
  113. THREAD *tmp, *tree = ctx->tree;
  114. int hide_top_missing = option (OPTHIDETOPMISSING) && !option (OPTHIDEMISSING);
  115. int hide_top_limited = option (OPTHIDETOPLIMITED) && !option (OPTHIDELIMITED);
  116. int depth = 0;
  117. /* we walk each level backwards to make it easier to compute next_subtree_visible */
  118. while (tree->next)
  119. tree = tree->next;
  120. *max_depth = 0;
  121. FOREVER
  122. {
  123. if (depth > *max_depth)
  124. *max_depth = depth;
  125. tree->subtree_visible = 0;
  126. if (tree->message)
  127. {
  128. FREE (&tree->message->tree);
  129. if (VISIBLE (tree->message, ctx))
  130. {
  131. tree->deep = 1;
  132. tree->visible = 1;
  133. tree->message->display_subject = need_display_subject (ctx, tree->message);
  134. for (tmp = tree; tmp; tmp = tmp->parent)
  135. {
  136. if (tmp->subtree_visible)
  137. {
  138. tmp->deep = 1;
  139. tmp->subtree_visible = 2;
  140. break;
  141. }
  142. else
  143. tmp->subtree_visible = 1;
  144. }
  145. }
  146. else
  147. {
  148. tree->visible = 0;
  149. tree->deep = !option (OPTHIDELIMITED);
  150. }
  151. }
  152. else
  153. {
  154. tree->visible = 0;
  155. tree->deep = !option (OPTHIDEMISSING);
  156. }
  157. tree->next_subtree_visible = tree->next && (tree->next->next_subtree_visible
  158. || tree->next->subtree_visible);
  159. if (tree->child)
  160. {
  161. depth++;
  162. tree = tree->child;
  163. while (tree->next)
  164. tree = tree->next;
  165. }
  166. else if (tree->prev)
  167. tree = tree->prev;
  168. else
  169. {
  170. while (tree && !tree->prev)
  171. {
  172. depth--;
  173. tree = tree->parent;
  174. }
  175. if (!tree)
  176. break;
  177. else
  178. tree = tree->prev;
  179. }
  180. }
  181. /* now fix up for the OPTHIDETOP* options if necessary */
  182. if (hide_top_limited || hide_top_missing)
  183. {
  184. tree = ctx->tree;
  185. FOREVER
  186. {
  187. if (!tree->visible && tree->deep && tree->subtree_visible < 2
  188. && ((tree->message && hide_top_limited) || (!tree->message && hide_top_missing)))
  189. tree->deep = 0;
  190. if (!tree->deep && tree->child && tree->subtree_visible)
  191. tree = tree->child;
  192. else if (tree->next)
  193. tree = tree->next;
  194. else
  195. {
  196. while (tree && !tree->next)
  197. tree = tree->parent;
  198. if (!tree)
  199. break;
  200. else
  201. tree = tree->next;
  202. }
  203. }
  204. }
  205. }
  206. /* Since the graphics characters have a value >255, I have to resort to
  207. * using escape sequences to pass the information to print_enriched_string().
  208. * These are the macros MUTT_TREE_* defined in mutt.h.
  209. *
  210. * ncurses should automatically use the default ASCII characters instead of
  211. * graphics chars on terminals which don't support them (see the man page
  212. * for curs_addch).
  213. */
  214. void mutt_draw_tree (CONTEXT *ctx)
  215. {
  216. char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree;
  217. char corner = (Sort & SORT_REVERSE) ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER;
  218. char vtee = (Sort & SORT_REVERSE) ? MUTT_TREE_BTEE : MUTT_TREE_TTEE;
  219. int depth = 0, start_depth = 0, max_depth = 0, width = option (OPTNARROWTREE) ? 1 : 2;
  220. THREAD *nextdisp = NULL, *pseudo = NULL, *parent = NULL, *tree = ctx->tree;
  221. /* Do the visibility calculations and free the old thread chars.
  222. * From now on we can simply ignore invisible subtrees
  223. */
  224. calculate_visibility (ctx, &max_depth);
  225. pfx = safe_malloc (width * max_depth + 2);
  226. arrow = safe_malloc (width * max_depth + 2);
  227. while (tree)
  228. {
  229. if (depth)
  230. {
  231. myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width;
  232. if (depth && start_depth == depth)
  233. myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner;
  234. else if (parent->message && !option (OPTHIDELIMITED))
  235. myarrow[0] = MUTT_TREE_HIDDEN;
  236. else if (!parent->message && !option (OPTHIDEMISSING))
  237. myarrow[0] = MUTT_TREE_MISSING;
  238. else
  239. myarrow[0] = vtee;
  240. if (width == 2)
  241. myarrow[1] = pseudo ? MUTT_TREE_STAR
  242. : (tree->duplicate_thread ? MUTT_TREE_EQUALS : MUTT_TREE_HLINE);
  243. if (tree->visible)
  244. {
  245. myarrow[width] = MUTT_TREE_RARROW;
  246. myarrow[width + 1] = 0;
  247. new_tree = safe_malloc ((2 + depth * width));
  248. if (start_depth > 1)
  249. {
  250. strncpy (new_tree, pfx, (start_depth - 1) * width);
  251. strfcpy (new_tree + (start_depth - 1) * width,
  252. arrow, (1 + depth - start_depth) * width + 2);
  253. }
  254. else
  255. strfcpy (new_tree, arrow, 2 + depth * width);
  256. tree->message->tree = new_tree;
  257. }
  258. }
  259. if (tree->child && depth)
  260. {
  261. mypfx = pfx + (depth - 1) * width;
  262. mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE;
  263. if (width == 2)
  264. mypfx[1] = MUTT_TREE_SPACE;
  265. }
  266. parent = tree;
  267. nextdisp = NULL;
  268. pseudo = NULL;
  269. do
  270. {
  271. if (tree->child && tree->subtree_visible)
  272. {
  273. if (tree->deep)
  274. depth++;
  275. if (tree->visible)
  276. start_depth = depth;
  277. tree = tree->child;
  278. /* we do this here because we need to make sure that the first child thread
  279. * of the old tree that we deal with is actually displayed if any are,
  280. * or we might set the parent variable wrong while going through it. */
  281. while (!tree->subtree_visible && tree->next)
  282. tree = tree->next;
  283. }
  284. else
  285. {
  286. while (!tree->next && tree->parent)
  287. {
  288. if (tree == pseudo)
  289. pseudo = NULL;
  290. if (tree == nextdisp)
  291. nextdisp = NULL;
  292. if (tree->visible)
  293. start_depth = depth;
  294. tree = tree->parent;
  295. if (tree->deep)
  296. {
  297. if (start_depth == depth)
  298. start_depth--;
  299. depth--;
  300. }
  301. }
  302. if (tree == pseudo)
  303. pseudo = NULL;
  304. if (tree == nextdisp)
  305. nextdisp = NULL;
  306. if (tree->visible)
  307. start_depth = depth;
  308. tree = tree->next;
  309. if (!tree)
  310. break;
  311. }
  312. if (!pseudo && tree->fake_thread)
  313. pseudo = tree;
  314. if (!nextdisp && tree->next_subtree_visible)
  315. nextdisp = tree;
  316. }
  317. while (!tree->deep);
  318. }
  319. FREE (&pfx);
  320. FREE (&arrow);
  321. }
  322. /* since we may be trying to attach as a pseudo-thread a THREAD that
  323. * has no message, we have to make a list of all the subjects of its
  324. * most immediate existing descendants. we also note the earliest
  325. * date on any of the parents and put it in *dateptr. */
  326. static LIST *make_subject_list (THREAD *cur, time_t *dateptr)
  327. {
  328. THREAD *start = cur;
  329. ENVELOPE *env;
  330. time_t thisdate;
  331. LIST *curlist, *oldlist, *newlist, *subjects = NULL;
  332. int rc = 0;
  333. FOREVER
  334. {
  335. while (!cur->message)
  336. cur = cur->child;
  337. if (dateptr)
  338. {
  339. thisdate = option (OPTTHREADRECEIVED)
  340. ? cur->message->received : cur->message->date_sent;
  341. if (!*dateptr || thisdate < *dateptr)
  342. *dateptr = thisdate;
  343. }
  344. env = cur->message->env;
  345. if (env->real_subj &&
  346. ((env->real_subj != env->subject) || (!option (OPTSORTRE))))
  347. {
  348. for (curlist = subjects, oldlist = NULL;
  349. curlist; oldlist = curlist, curlist = curlist->next)
  350. {
  351. rc = mutt_strcmp (env->real_subj, curlist->data);
  352. if (rc >= 0)
  353. break;
  354. }
  355. if (!curlist || rc > 0)
  356. {
  357. newlist = safe_calloc (1, sizeof (LIST));
  358. newlist->data = env->real_subj;
  359. if (oldlist)
  360. {
  361. newlist->next = oldlist->next;
  362. oldlist->next = newlist;
  363. }
  364. else
  365. {
  366. newlist->next = subjects;
  367. subjects = newlist;
  368. }
  369. }
  370. }
  371. while (!cur->next && cur != start)
  372. {
  373. cur = cur->parent;
  374. }
  375. if (cur == start)
  376. break;
  377. cur = cur->next;
  378. }
  379. return (subjects);
  380. }
  381. /* find the best possible match for a parent message based upon subject.
  382. * if there are multiple matches, the one which was sent the latest, but
  383. * before the current message, is used.
  384. */
  385. static THREAD *find_subject (CONTEXT *ctx, THREAD *cur)
  386. {
  387. struct hash_elem *ptr;
  388. THREAD *tmp, *last = NULL;
  389. LIST *subjects = NULL, *oldlist;
  390. time_t date = 0;
  391. subjects = make_subject_list (cur, &date);
  392. while (subjects)
  393. {
  394. for (ptr = hash_find_bucket (ctx->subj_hash, subjects->data); ptr; ptr = ptr->next)
  395. {
  396. tmp = ((HEADER *) ptr->data)->thread;
  397. if (tmp != cur && /* don't match the same message */
  398. !tmp->fake_thread && /* don't match pseudo threads */
  399. tmp->message->subject_changed && /* only match interesting replies */
  400. !is_descendant (tmp, cur) && /* don't match in the same thread */
  401. (date >= (option (OPTTHREADRECEIVED) ?
  402. tmp->message->received :
  403. tmp->message->date_sent)) &&
  404. (!last ||
  405. (option (OPTTHREADRECEIVED) ?
  406. (last->message->received < tmp->message->received) :
  407. (last->message->date_sent < tmp->message->date_sent))) &&
  408. tmp->message->env->real_subj &&
  409. mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0)
  410. last = tmp; /* best match so far */
  411. }
  412. oldlist = subjects;
  413. subjects = subjects->next;
  414. FREE (&oldlist);
  415. }
  416. return (last);
  417. }
  418. /* remove cur and its descendants from their current location.
  419. * also make sure ancestors of cur no longer are sorted by the
  420. * fact that cur is their descendant. */
  421. static void unlink_message (THREAD **old, THREAD *cur)
  422. {
  423. THREAD *tmp;
  424. if (cur->prev)
  425. cur->prev->next = cur->next;
  426. else
  427. *old = cur->next;
  428. if (cur->next)
  429. cur->next->prev = cur->prev;
  430. if (cur->sort_key)
  431. {
  432. for (tmp = cur->parent; tmp && tmp->sort_key == cur->sort_key;
  433. tmp = tmp->parent)
  434. tmp->sort_key = NULL;
  435. }
  436. }
  437. /* add cur as a prior sibling of *new, with parent newparent */
  438. static void insert_message (THREAD **new, THREAD *newparent, THREAD *cur)
  439. {
  440. if (*new)
  441. (*new)->prev = cur;
  442. cur->parent = newparent;
  443. cur->next = *new;
  444. cur->prev = NULL;
  445. *new = cur;
  446. }
  447. /* thread by subject things that didn't get threaded by message-id */
  448. static void pseudo_threads (CONTEXT *ctx)
  449. {
  450. THREAD *tree = ctx->tree, *top = tree;
  451. THREAD *tmp, *cur, *parent, *curchild, *nextchild;
  452. if (!ctx->subj_hash)
  453. ctx->subj_hash = mutt_make_subj_hash (ctx);
  454. while (tree)
  455. {
  456. cur = tree;
  457. tree = tree->next;
  458. if ((parent = find_subject (ctx, cur)) != NULL)
  459. {
  460. cur->fake_thread = 1;
  461. unlink_message (&top, cur);
  462. insert_message (&parent->child, parent, cur);
  463. parent->sort_children = 1;
  464. tmp = cur;
  465. FOREVER
  466. {
  467. while (!tmp->message)
  468. tmp = tmp->child;
  469. /* if the message we're attaching has pseudo-children, they
  470. * need to be attached to its parent, so move them up a level.
  471. * but only do this if they have the same real subject as the
  472. * parent, since otherwise they rightly belong to the message
  473. * we're attaching. */
  474. if (tmp == cur
  475. || !mutt_strcmp (tmp->message->env->real_subj,
  476. parent->message->env->real_subj))
  477. {
  478. tmp->message->subject_changed = 0;
  479. for (curchild = tmp->child; curchild; )
  480. {
  481. nextchild = curchild->next;
  482. if (curchild->fake_thread)
  483. {
  484. unlink_message (&tmp->child, curchild);
  485. insert_message (&parent->child, parent, curchild);
  486. }
  487. curchild = nextchild;
  488. }
  489. }
  490. while (!tmp->next && tmp != cur)
  491. {
  492. tmp = tmp->parent;
  493. }
  494. if (tmp == cur)
  495. break;
  496. tmp = tmp->next;
  497. }
  498. }
  499. }
  500. ctx->tree = top;
  501. }
  502. void mutt_clear_threads (CONTEXT *ctx)
  503. {
  504. int i;
  505. for (i = 0; i < ctx->msgcount; i++)
  506. {
  507. /* mailbox may have been only partially read */
  508. if (ctx->hdrs[i])
  509. {
  510. ctx->hdrs[i]->thread = NULL;
  511. ctx->hdrs[i]->threaded = 0;
  512. }
  513. }
  514. ctx->tree = NULL;
  515. if (ctx->thread_hash)
  516. hash_destroy (&ctx->thread_hash, *free);
  517. }
  518. static int compare_threads (const void *a, const void *b)
  519. {
  520. static sort_t *sort_func = NULL;
  521. if (a && b)
  522. return ((*sort_func) (&(*((THREAD **) a))->sort_key,
  523. &(*((THREAD **) b))->sort_key));
  524. /* a hack to let us reset sort_func even though we can't
  525. * have extra arguments because of qsort
  526. */
  527. else
  528. {
  529. sort_func = mutt_get_sort_func (Sort);
  530. return (sort_func ? 1 : 0);
  531. }
  532. }
  533. THREAD *mutt_sort_subthreads (THREAD *thread, int init)
  534. {
  535. THREAD **array, *sort_key, *top, *tmp;
  536. HEADER *oldsort_key;
  537. int i, array_size, sort_top = 0;
  538. /* we put things into the array backwards to save some cycles,
  539. * but we want to have to move less stuff around if we're
  540. * resorting, so we sort backwards and then put them back
  541. * in reverse order so they're forwards
  542. */
  543. Sort ^= SORT_REVERSE;
  544. if (!compare_threads (NULL, NULL))
  545. return (thread);
  546. top = thread;
  547. array = safe_calloc ((array_size = 256), sizeof (THREAD *));
  548. while (1)
  549. {
  550. if (init || !thread->sort_key)
  551. {
  552. thread->sort_key = NULL;
  553. if (thread->parent)
  554. thread->parent->sort_children = 1;
  555. else
  556. sort_top = 1;
  557. }
  558. if (thread->child)
  559. {
  560. thread = thread->child;
  561. continue;
  562. }
  563. else
  564. {
  565. /* if it has no children, it must be real. sort it on its own merits */
  566. thread->sort_key = thread->message;
  567. if (thread->next)
  568. {
  569. thread = thread->next;
  570. continue;
  571. }
  572. }
  573. while (!thread->next)
  574. {
  575. /* if it has siblings and needs to be sorted, sort it... */
  576. if (thread->prev && (thread->parent ? thread->parent->sort_children : sort_top))
  577. {
  578. /* put them into the array */
  579. for (i = 0; thread; i++, thread = thread->prev)
  580. {
  581. if (i >= array_size)
  582. safe_realloc (&array, (array_size *= 2) * sizeof (THREAD *));
  583. array[i] = thread;
  584. }
  585. qsort ((void *) array, i, sizeof (THREAD *), *compare_threads);
  586. /* attach them back together. make thread the last sibling. */
  587. thread = array[0];
  588. thread->next = NULL;
  589. array[i - 1]->prev = NULL;
  590. if (thread->parent)
  591. thread->parent->child = array[i - 1];
  592. else
  593. top = array[i - 1];
  594. while (--i)
  595. {
  596. array[i - 1]->prev = array[i];
  597. array[i]->next = array[i - 1];
  598. }
  599. }
  600. if (thread->parent)
  601. {
  602. tmp = thread;
  603. thread = thread->parent;
  604. if (!thread->sort_key || thread->sort_children)
  605. {
  606. /* make sort_key the first or last sibling, as appropriate */
  607. sort_key = (!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? thread->child : tmp;
  608. /* we just sorted its children */
  609. thread->sort_children = 0;
  610. oldsort_key = thread->sort_key;
  611. thread->sort_key = thread->message;
  612. if (Sort & SORT_LAST)
  613. {
  614. if (!thread->sort_key
  615. || ((((Sort & SORT_REVERSE) ? 1 : -1)
  616. * compare_threads ((void *) &thread,
  617. (void *) &sort_key))
  618. > 0))
  619. thread->sort_key = sort_key->sort_key;
  620. }
  621. else if (!thread->sort_key)
  622. thread->sort_key = sort_key->sort_key;
  623. /* if its sort_key has changed, we need to resort it and siblings */
  624. if (oldsort_key != thread->sort_key)
  625. {
  626. if (thread->parent)
  627. thread->parent->sort_children = 1;
  628. else
  629. sort_top = 1;
  630. }
  631. }
  632. }
  633. else
  634. {
  635. Sort ^= SORT_REVERSE;
  636. FREE (&array);
  637. return (top);
  638. }
  639. }
  640. thread = thread->next;
  641. }
  642. }
  643. static void check_subjects (CONTEXT *ctx, int init)
  644. {
  645. HEADER *cur;
  646. THREAD *tmp;
  647. int i;
  648. for (i = 0; i < ctx->msgcount; i++)
  649. {
  650. cur = ctx->hdrs[i];
  651. if (cur->thread->check_subject)
  652. cur->thread->check_subject = 0;
  653. else if (!init)
  654. continue;
  655. /* figure out which messages have subjects different than their parents' */
  656. tmp = cur->thread->parent;
  657. while (tmp && !tmp->message)
  658. {
  659. tmp = tmp->parent;
  660. }
  661. if (!tmp)
  662. cur->subject_changed = 1;
  663. else if (cur->env->real_subj && tmp->message->env->real_subj)
  664. cur->subject_changed = mutt_strcmp (cur->env->real_subj,
  665. tmp->message->env->real_subj) ? 1 : 0;
  666. else
  667. cur->subject_changed = (cur->env->real_subj
  668. || tmp->message->env->real_subj) ? 1 : 0;
  669. }
  670. }
  671. void mutt_sort_threads (CONTEXT *ctx, int init)
  672. {
  673. HEADER *cur;
  674. int i, oldsort, using_refs = 0;
  675. THREAD *thread, *new, *tmp, top;
  676. LIST *ref = NULL;
  677. /* set Sort to the secondary method to support the set sort_aux=reverse-*
  678. * settings. The sorting functions just look at the value of
  679. * SORT_REVERSE
  680. */
  681. oldsort = Sort;
  682. Sort = SortAux;
  683. if (!ctx->thread_hash)
  684. init = 1;
  685. if (init)
  686. ctx->thread_hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
  687. /* we want a quick way to see if things are actually attached to the top of the
  688. * thread tree or if they're just dangling, so we attach everything to a top
  689. * node temporarily */
  690. top.parent = top.next = top.prev = NULL;
  691. top.child = ctx->tree;
  692. for (thread = ctx->tree; thread; thread = thread->next)
  693. thread->parent = &top;
  694. /* put each new message together with the matching messageless THREAD if it
  695. * exists. otherwise, if there is a THREAD that already has a message, thread
  696. * new message as an identical child. if we didn't attach the message to a
  697. * THREAD, make a new one for it. */
  698. for (i = 0; i < ctx->msgcount; i++)
  699. {
  700. cur = ctx->hdrs[i];
  701. if (!cur->thread)
  702. {
  703. if ((!init || option (OPTDUPTHREADS)) && cur->env->message_id)
  704. thread = hash_find (ctx->thread_hash, cur->env->message_id);
  705. else
  706. thread = NULL;
  707. if (thread && !thread->message)
  708. {
  709. /* this is a message which was missing before */
  710. thread->message = cur;
  711. cur->thread = thread;
  712. thread->check_subject = 1;
  713. /* mark descendants as needing subject_changed checked */
  714. for (tmp = (thread->child ? thread->child : thread); tmp != thread; )
  715. {
  716. while (!tmp->message)
  717. tmp = tmp->child;
  718. tmp->check_subject = 1;
  719. while (!tmp->next && tmp != thread)
  720. tmp = tmp->parent;
  721. if (tmp != thread)
  722. tmp = tmp->next;
  723. }
  724. if (thread->parent)
  725. {
  726. /* remove threading info above it based on its children, which we'll
  727. * recalculate based on its headers. make sure not to leave
  728. * dangling missing messages. note that we haven't kept track
  729. * of what info came from its children and what from its siblings'
  730. * children, so we just remove the stuff that's definitely from it */
  731. do
  732. {
  733. tmp = thread->parent;
  734. unlink_message (&tmp->child, thread);
  735. thread->parent = NULL;
  736. thread->sort_key = NULL;
  737. thread->fake_thread = 0;
  738. thread = tmp;
  739. } while (thread != &top && !thread->child && !thread->message);
  740. }
  741. }
  742. else
  743. {
  744. new = (option (OPTDUPTHREADS) ? thread : NULL);
  745. thread = safe_calloc (1, sizeof (THREAD));
  746. thread->message = cur;
  747. thread->check_subject = 1;
  748. cur->thread = thread;
  749. hash_insert (ctx->thread_hash,
  750. cur->env->message_id ? cur->env->message_id : "",
  751. thread);
  752. if (new)
  753. {
  754. if (new->duplicate_thread)
  755. new = new->parent;
  756. thread = cur->thread;
  757. insert_message (&new->child, new, thread);
  758. thread->duplicate_thread = 1;
  759. thread->message->threaded = 1;
  760. }
  761. }
  762. }
  763. else
  764. {
  765. /* unlink pseudo-threads because they might be children of newly
  766. * arrived messages */
  767. thread = cur->thread;
  768. for (new = thread->child; new; )
  769. {
  770. tmp = new->next;
  771. if (new->fake_thread)
  772. {
  773. unlink_message (&thread->child, new);
  774. insert_message (&top.child, &top, new);
  775. new->fake_thread = 0;
  776. }
  777. new = tmp;
  778. }
  779. }
  780. }
  781. /* thread by references */
  782. for (i = 0; i < ctx->msgcount; i++)
  783. {
  784. cur = ctx->hdrs[i];
  785. if (cur->threaded)
  786. continue;
  787. cur->threaded = 1;
  788. thread = cur->thread;
  789. using_refs = 0;
  790. while (1)
  791. {
  792. if (using_refs == 0)
  793. {
  794. /* look at the beginning of in-reply-to: */
  795. if ((ref = cur->env->in_reply_to) != NULL)
  796. using_refs = 1;
  797. else
  798. {
  799. ref = cur->env->references;
  800. using_refs = 2;
  801. }
  802. }
  803. else if (using_refs == 1)
  804. {
  805. /* if there's no references header, use all the in-reply-to:
  806. * data that we have. otherwise, use the first reference
  807. * if it's different than the first in-reply-to, otherwise use
  808. * the second reference (since at least eudora puts the most
  809. * recent reference in in-reply-to and the rest in references)
  810. */
  811. if (!cur->env->references)
  812. ref = ref->next;
  813. else
  814. {
  815. if (mutt_strcmp (ref->data, cur->env->references->data))
  816. ref = cur->env->references;
  817. else
  818. ref = cur->env->references->next;
  819. using_refs = 2;
  820. }
  821. }
  822. else
  823. ref = ref->next; /* go on with references */
  824. if (!ref)
  825. break;
  826. if ((new = hash_find (ctx->thread_hash, ref->data)) == NULL)
  827. {
  828. new = safe_calloc (1, sizeof (THREAD));
  829. hash_insert (ctx->thread_hash, ref->data, new);
  830. }
  831. else
  832. {
  833. if (new->duplicate_thread)
  834. new = new->parent;
  835. if (is_descendant (new, thread)) /* no loops! */
  836. continue;
  837. }
  838. if (thread->parent)
  839. unlink_message (&top.child, thread);
  840. insert_message (&new->child, new, thread);
  841. thread = new;
  842. if (thread->message || (thread->parent && thread->parent != &top))
  843. break;
  844. }
  845. if (!thread->parent)
  846. insert_message (&top.child, &top, thread);
  847. }
  848. /* detach everything from the temporary top node */
  849. for (thread = top.child; thread; thread = thread->next)
  850. {
  851. thread->parent = NULL;
  852. }
  853. ctx->tree = top.child;
  854. check_subjects (ctx, init);
  855. if (!option (OPTSTRICTTHREADS))
  856. pseudo_threads (ctx);
  857. if (ctx->tree)
  858. {
  859. ctx->tree = mutt_sort_subthreads (ctx->tree, init);
  860. /* restore the oldsort order. */
  861. Sort = oldsort;
  862. /* Put the list into an array. */
  863. linearize_tree (ctx);
  864. /* Draw the thread tree. */
  865. mutt_draw_tree (ctx);
  866. }
  867. }
  868. static HEADER *find_virtual (THREAD *cur, int reverse)
  869. {
  870. THREAD *top;
  871. if (cur->message && cur->message->virtual >= 0)
  872. return (cur->message);
  873. top = cur;
  874. if ((cur = cur->child) == NULL)
  875. return (NULL);
  876. while (reverse && cur->next)
  877. cur = cur->next;
  878. FOREVER
  879. {
  880. if (cur->message && cur->message->virtual >= 0)
  881. return (cur->message);
  882. if (cur->child)
  883. {
  884. cur = cur->child;
  885. while (reverse && cur->next)
  886. cur = cur->next;
  887. }
  888. else if (reverse ? cur->prev : cur->next)
  889. cur = reverse ? cur->prev : cur->next;
  890. else
  891. {
  892. while (!(reverse ? cur->prev : cur->next))
  893. {
  894. cur = cur->parent;
  895. if (cur == top)
  896. return (NULL);
  897. }
  898. cur = reverse ? cur->prev : cur->next;
  899. }
  900. /* not reached */
  901. }
  902. }
  903. /* dir => true when moving forward, false when moving in reverse
  904. * subthreads => false when moving to next thread, true when moving to next subthread
  905. */
  906. int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads)
  907. {
  908. THREAD *cur;
  909. HEADER *tmp;
  910. if ((Sort & SORT_MASK) != SORT_THREADS)
  911. {
  912. mutt_error _("Threading is not enabled.");
  913. return (hdr->virtual);
  914. }
  915. cur = hdr->thread;
  916. if (!subthreads)
  917. {
  918. while (cur->parent)
  919. cur = cur->parent;
  920. }
  921. else
  922. {
  923. if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
  924. {
  925. while (!cur->next && cur->parent)
  926. cur = cur->parent;
  927. }
  928. else
  929. {
  930. while (!cur->prev && cur->parent)
  931. cur = cur->parent;
  932. }
  933. }
  934. if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0))
  935. {
  936. do
  937. {
  938. cur = cur->next;
  939. if (!cur)
  940. return (-1);
  941. tmp = find_virtual (cur, 0);
  942. } while (!tmp);
  943. }
  944. else
  945. {
  946. do
  947. {
  948. cur = cur->prev;
  949. if (!cur)
  950. return (-1);
  951. tmp = find_virtual (cur, 1);
  952. } while (!tmp);
  953. }
  954. return (tmp->virtual);
  955. }
  956. int mutt_parent_message (CONTEXT *ctx, HEADER *hdr, int find_root)
  957. {
  958. THREAD *thread;
  959. HEADER *parent = NULL;
  960. if ((Sort & SORT_MASK) != SORT_THREADS)
  961. {
  962. mutt_error _("Threading is not enabled.");
  963. return (hdr->virtual);
  964. }
  965. /* Root may be the current message */
  966. if (find_root)
  967. parent = hdr;
  968. for (thread = hdr->thread->parent; thread; thread = thread->parent)
  969. {
  970. if ((hdr = thread->message) != NULL)
  971. {
  972. parent = hdr;
  973. if (!find_root)
  974. break;
  975. }
  976. }
  977. if (!parent)
  978. {
  979. mutt_error _("Parent message is not available.");
  980. return (-1);
  981. }
  982. if (!VISIBLE (parent, ctx))
  983. {
  984. if (find_root)
  985. mutt_error _("Root message is not visible in this limited view.");
  986. else
  987. mutt_error _("Parent message is not visible in this limited view.");
  988. return (-1);
  989. }
  990. return (parent->virtual);
  991. }
  992. void mutt_set_virtual (CONTEXT *ctx)
  993. {
  994. int i, padding;
  995. HEADER *cur;
  996. ctx->vcount = 0;
  997. ctx->vsize = 0;
  998. padding = mx_msg_padding_size (ctx);
  999. for (i = 0; i < ctx->msgcount; i++)
  1000. {
  1001. cur = ctx->hdrs[i];
  1002. if (cur->virtual >= 0)
  1003. {
  1004. cur->virtual = ctx->vcount;
  1005. ctx->v2r[ctx->vcount] = i;
  1006. ctx->vcount++;
  1007. ctx->vsize += cur->content->length + cur->content->offset -
  1008. cur->content->hdr_offset + padding;
  1009. }
  1010. }
  1011. }
  1012. int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag)
  1013. {
  1014. THREAD *thread, *top;
  1015. HEADER *roothdr = NULL;
  1016. int final, reverse = (Sort & SORT_REVERSE), minmsgno;
  1017. int num_hidden = 0, new = 0, old = 0;
  1018. int min_unread_msgno = INT_MAX, min_unread = cur->virtual;
  1019. #define CHECK_LIMIT (!ctx->pattern || cur->limited)
  1020. if ((Sort & SORT_MASK) != SORT_THREADS)
  1021. {
  1022. mutt_error (_("Threading is not enabled."));
  1023. return (cur->virtual);
  1024. }
  1025. final = cur->virtual;
  1026. thread = cur->thread;
  1027. while (thread->parent)
  1028. thread = thread->parent;
  1029. top = thread;
  1030. while (!thread->message)
  1031. thread = thread->child;
  1032. cur = thread->message;
  1033. minmsgno = cur->msgno;
  1034. if (!cur->read && CHECK_LIMIT)
  1035. {
  1036. if (cur->old)
  1037. old = 2;
  1038. else
  1039. new = 1;
  1040. if (cur->msgno < min_unread_msgno)
  1041. {
  1042. min_unread = cur->virtual;
  1043. min_unread_msgno = cur->msgno;
  1044. }
  1045. }
  1046. if (cur->virtual == -1 && CHECK_LIMIT)
  1047. num_hidden++;
  1048. if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
  1049. {
  1050. cur->pair = 0; /* force index entry's color to be re-evaluated */
  1051. cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
  1052. if (cur->virtual != -1)
  1053. {
  1054. roothdr = cur;
  1055. if (flag & MUTT_THREAD_COLLAPSE)
  1056. final = roothdr->virtual;
  1057. }
  1058. }
  1059. if (thread == top && (thread = thread->child) == NULL)
  1060. {
  1061. /* return value depends on action requested */
  1062. if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
  1063. {
  1064. cur->num_hidden = num_hidden;
  1065. return (final);
  1066. }
  1067. else if (flag & MUTT_THREAD_UNREAD)
  1068. return ((old && new) ? new : (old ? old : new));
  1069. else if (flag & MUTT_THREAD_NEXT_UNREAD)
  1070. return (min_unread);
  1071. }
  1072. FOREVER
  1073. {
  1074. cur = thread->message;
  1075. if (cur)
  1076. {
  1077. if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
  1078. {
  1079. cur->pair = 0; /* force index entry's color to be re-evaluated */
  1080. cur->collapsed = flag & MUTT_THREAD_COLLAPSE;
  1081. if (!roothdr && CHECK_LIMIT)
  1082. {
  1083. roothdr = cur;
  1084. if (flag & MUTT_THREAD_COLLAPSE)
  1085. final = roothdr->virtual;
  1086. }
  1087. if (reverse && (flag & MUTT_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT)
  1088. {
  1089. minmsgno = cur->msgno;
  1090. final = cur->virtual;
  1091. }
  1092. if (flag & MUTT_THREAD_COLLAPSE)
  1093. {
  1094. if (cur != roothdr)
  1095. cur->virtual = -1;
  1096. }
  1097. else
  1098. {
  1099. if (CHECK_LIMIT)
  1100. cur->virtual = cur->msgno;
  1101. }
  1102. }
  1103. if (!cur->read && CHECK_LIMIT)
  1104. {
  1105. if (cur->old)
  1106. old = 2;
  1107. else
  1108. new = 1;
  1109. if (cur->msgno < min_unread_msgno)
  1110. {
  1111. min_unread = cur->virtual;
  1112. min_unread_msgno = cur->msgno;
  1113. }
  1114. }
  1115. if (cur->virtual == -1 && CHECK_LIMIT)
  1116. num_hidden++;
  1117. }
  1118. if (thread->child)
  1119. thread = thread->child;
  1120. else if (thread->next)
  1121. thread = thread->next;
  1122. else
  1123. {
  1124. int done = 0;
  1125. while (!thread->next)
  1126. {
  1127. thread = thread->parent;
  1128. if (thread == top)
  1129. {
  1130. done = 1;
  1131. break;
  1132. }
  1133. }
  1134. if (done)
  1135. break;
  1136. thread = thread->next;
  1137. }
  1138. }
  1139. /* retraverse the thread and store num_hidden in all headers, with
  1140. * or without a virtual index. this will allow ~v to match all
  1141. * collapsed messages when switching sort order to non-threaded.
  1142. */
  1143. if (flag & MUTT_THREAD_COLLAPSE)
  1144. {
  1145. thread = top;
  1146. FOREVER
  1147. {
  1148. cur = thread->message;
  1149. if (cur)
  1150. cur->num_hidden = num_hidden + 1;
  1151. if (thread->child)
  1152. thread = thread->child;
  1153. else if (thread->next)
  1154. thread = thread->next;
  1155. else
  1156. {
  1157. int done = 0;
  1158. while (!thread->next)
  1159. {
  1160. thread = thread->parent;
  1161. if (thread == top)
  1162. {
  1163. done = 1;
  1164. break;
  1165. }
  1166. }
  1167. if (done)
  1168. break;
  1169. thread = thread->next;
  1170. }
  1171. }
  1172. }
  1173. /* return value depends on action requested */
  1174. if (flag & (MUTT_THREAD_COLLAPSE | MUTT_THREAD_UNCOLLAPSE))
  1175. return (final);
  1176. else if (flag & MUTT_THREAD_UNREAD)
  1177. return ((old && new) ? new : (old ? old : new));
  1178. else if (flag & MUTT_THREAD_NEXT_UNREAD)
  1179. return (min_unread);
  1180. return (0);
  1181. #undef CHECK_LIMIT
  1182. }
  1183. /* if flag is 0, we want to know how many messages
  1184. * are in the thread. if flag is 1, we want to know
  1185. * our position in the thread. */
  1186. int mutt_messages_in_thread (CONTEXT *ctx, HEADER *hdr, int flag)
  1187. {
  1188. THREAD *threads[2];
  1189. int i, rc;
  1190. if ((Sort & SORT_MASK) != SORT_THREADS || !hdr->thread)
  1191. return (1);
  1192. threads[0] = hdr->thread;
  1193. while (threads[0]->parent)
  1194. threads[0] = threads[0]->parent;
  1195. threads[1] = flag ? hdr->thread : threads[0]->next;
  1196. for (i = 0; i < ((flag || !threads[1]) ? 1 : 2); i++)
  1197. {
  1198. while (!threads[i]->message)
  1199. threads[i] = threads[i]->child;
  1200. }
  1201. if (Sort & SORT_REVERSE)
  1202. rc = threads[0]->message->msgno - (threads[1] ? threads[1]->message->msgno : -1);
  1203. else
  1204. rc = (threads[1] ? threads[1]->message->msgno : ctx->msgcount) - threads[0]->message->msgno;
  1205. if (flag)
  1206. rc += 1;
  1207. return (rc);
  1208. }
  1209. HASH *mutt_make_id_hash (CONTEXT *ctx)
  1210. {
  1211. int i;
  1212. HEADER *hdr;
  1213. HASH *hash;
  1214. hash = hash_create (ctx->msgcount * 2, 0);
  1215. for (i = 0; i < ctx->msgcount; i++)
  1216. {
  1217. hdr = ctx->hdrs[i];
  1218. if (hdr->env->message_id)
  1219. hash_insert (hash, hdr->env->message_id, hdr);
  1220. }
  1221. return hash;
  1222. }
  1223. HASH *mutt_make_subj_hash (CONTEXT *ctx)
  1224. {
  1225. int i;
  1226. HEADER *hdr;
  1227. HASH *hash;
  1228. hash = hash_create (ctx->msgcount * 2, MUTT_HASH_ALLOW_DUPS);
  1229. for (i = 0; i < ctx->msgcount; i++)
  1230. {
  1231. hdr = ctx->hdrs[i];
  1232. if (hdr->env->real_subj)
  1233. hash_insert (hash, hdr->env->real_subj, hdr);
  1234. }
  1235. return hash;
  1236. }
  1237. static void clean_references (THREAD *brk, THREAD *cur)
  1238. {
  1239. THREAD *p;
  1240. LIST *ref = NULL;
  1241. int done = 0;
  1242. for (; cur; cur = cur->next, done = 0)
  1243. {
  1244. /* parse subthread recursively */
  1245. clean_references (brk, cur->child);
  1246. if (!cur->message)
  1247. break; /* skip pseudo-message */
  1248. /* Looking for the first bad reference according to the new threading.
  1249. * Optimal since Mutt stores the references in reverse order, and the
  1250. * first loop should match immediately for mails respecting RFC2822. */
  1251. for (p = brk; !done && p; p = p->parent)
  1252. for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
  1253. if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
  1254. {
  1255. done = 1;
  1256. break;
  1257. }
  1258. if (done)
  1259. {
  1260. HEADER *h = cur->message;
  1261. /* clearing the References: header from obsolete Message-ID(s) */
  1262. mutt_free_list (&ref->next);
  1263. h->changed = 1;
  1264. h->env->changed |= MUTT_ENV_CHANGED_REFS;
  1265. }
  1266. }
  1267. }
  1268. void mutt_break_thread (HEADER *hdr)
  1269. {
  1270. mutt_free_list (&hdr->env->in_reply_to);
  1271. mutt_free_list (&hdr->env->references);
  1272. hdr->changed = 1;
  1273. hdr->env->changed |= (MUTT_ENV_CHANGED_IRT | MUTT_ENV_CHANGED_REFS);
  1274. clean_references (hdr->thread, hdr->thread->child);
  1275. }
  1276. static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
  1277. {
  1278. if (child == parent)
  1279. return 0;
  1280. mutt_break_thread (child);
  1281. child->env->in_reply_to = mutt_new_list ();
  1282. child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
  1283. mutt_set_flag (ctx, child, MUTT_TAG, 0);
  1284. child->changed = 1;
  1285. child->env->changed |= MUTT_ENV_CHANGED_IRT;
  1286. return 1;
  1287. }
  1288. int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
  1289. {
  1290. int i, changed = 0;
  1291. if (!last)
  1292. {
  1293. for (i = 0; i < ctx->vcount; i++)
  1294. if (ctx->hdrs[Context->v2r[i]]->tagged)
  1295. changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
  1296. }
  1297. else
  1298. changed = link_threads (cur, last, ctx);
  1299. return changed;
  1300. }