Bump S-nail v14.7.6
[s-mailx.git] / thread.c
blob75a2999b07fcad3f5b8048d3794aa03c622442a5
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message threading.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2004
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 /* Open addressing is used for Message-IDs because the maximum number of
45 * messages in the table is known in advance (== msgCount) */
46 struct mitem {
47 struct message *mi_data;
48 char *mi_id;
50 #define NOT_AN_ID ((struct mitem*)-1)
52 struct msort {
53 union {
54 #ifdef HAVE_SPAM
55 ui32_t ms_ui;
56 #endif
57 long ms_long;
58 char *ms_char;
59 } ms_u;
60 int ms_n;
63 /* Return the hash value for a message id modulo mprime, or mprime if the
64 * passed string does not look like a message-id */
65 static ui32_t _mhash(char const *cp, ui32_t mprime);
67 /* Look up a message id. Returns NOT_AN_ID if the passed string does not look
68 * like a message-id */
69 static struct mitem * _mlook(char *id, struct mitem *mt,
70 struct message *mdata, ui32_t mprime);
72 /* Child is to be adopted by parent. A thread tree is structured as follows:
74 * ------ m_child ------ m_child
75 * | |-------------------->| |------------------------> . . .
76 * | |<--------------------| |<----------------------- . . .
77 * ------ m_parent ------ m_parent
78 * ^^ | ^
79 * | \____ m_younger | |
80 * | \ | |
81 * | ---- | |
82 * | \ | | m_elder
83 * | m_parent ---- | |
84 * \ | |
85 * ---- | |
86 * \ + |
87 * ------ m_child
88 * | |------------------------> . . .
89 * | |<----------------------- . . .
90 * ------ m_parent
91 * | ^
92 * . . .
94 * The base message of a thread does not have a m_parent link. Elements
95 * connected by m_younger/m_elder links are replies to the same message, which
96 * is connected to them by m_parent links. The first reply to a message gets
97 * the m_child link */
98 static void _adopt(struct message *parent, struct message *child,
99 int dist);
101 /* Connect all msgs on the lowest thread level with m_younger/m_elder links */
102 static struct message * _interlink(struct message *m, ui32_t cnt, int nmail);
104 static void _finalize(struct message *mp);
106 /* Several sort comparison PTFs */
107 #ifdef HAVE_SPAM
108 static int _mui32lt(void const *a, void const *b);
109 #endif
110 static int _mlonglt(void const *a, void const *b);
111 static int _mcharlt(void const *a, void const *b);
113 static void _lookup(struct message *m, struct mitem *mi,
114 ui32_t mprime);
115 static void _makethreads(struct message *m, ui32_t cnt, int nmail);
116 static int _colpt(int *msgvec, int cl);
117 static void _colps(struct message *b, int cl);
118 static void _colpm(struct message *m, int cl, int *cc, int *uc);
120 static ui32_t
121 _mhash(char const *cp, ui32_t mprime)
123 ui32_t h = 0, g, at = 0;
124 NYD2_ENTER;
126 for (--cp; *++cp != '\0';) {
127 /* Pay attention not to hash characters which are irrelevant for
128 * Message-ID semantics */
129 if (*cp == '(') {
130 cp = skip_comment(cp + 1) - 1;
131 continue;
133 if (*cp == '"' || *cp == '\\')
134 continue;
135 if (*cp == '@')
136 ++at;
137 /* TODO torek hash */
138 h = ((h << 4) & 0xffffffff) + lowerconv(*cp);
139 if ((g = h & 0xf0000000) != 0) {
140 h = h ^ (g >> 24);
141 h = h ^ g;
144 NYD2_LEAVE;
145 return (at ? h % mprime : mprime);
148 static struct mitem *
149 _mlook(char *id, struct mitem *mt, struct message *mdata, ui32_t mprime)
151 struct mitem *mp = NULL;
152 ui32_t h, c, n = 0;
153 NYD_ENTER;
155 if (id == NULL && (id = hfield1("message-id", mdata)) == NULL)
156 goto jleave;
158 if (mdata != NULL && mdata->m_idhash)
159 h = ~mdata->m_idhash;
160 else {
161 h = _mhash(id, mprime);
162 if (h == mprime) {
163 mp = NOT_AN_ID;
164 goto jleave;
168 mp = mt + (c = h);
169 while (mp->mi_id != NULL) {
170 if (!msgidcmp(mp->mi_id, id))
171 break;
172 c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
173 ++n;
174 while (c >= mprime)
175 c -= mprime;
176 mp = mt + c;
179 if (mdata != NULL && mp->mi_id == NULL) {
180 mp->mi_id = sstrdup(id);
181 mp->mi_data = mdata;
182 mdata->m_idhash = ~h;
184 if (mp->mi_id == NULL)
185 mp = NULL;
186 jleave:
187 NYD_LEAVE;
188 return mp;
191 static void
192 _adopt(struct message *parent, struct message *child, int dist)
194 struct message *mp, *mq;
195 NYD_ENTER;
197 for (mp = parent; mp != NULL; mp = mp->m_parent)
198 if (mp == child)
199 goto jleave;
201 child->m_level = dist; /* temporarily store distance */
202 child->m_parent = parent;
204 if (parent->m_child != NULL) {
205 mq = NULL;
206 for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
207 if (mp->m_date >= child->m_date) {
208 if (mp->m_elder != NULL)
209 mp->m_elder->m_younger = child;
210 child->m_elder = mp->m_elder;
211 mp->m_elder = child;
212 child->m_younger = mp;
213 if (mp == parent->m_child)
214 parent->m_child = child;
215 goto jleave;
217 mq = mp;
219 mq->m_younger = child;
220 child->m_elder = mq;
221 } else
222 parent->m_child = child;
223 jleave:
224 NYD_LEAVE;
227 static struct message *
228 _interlink(struct message *m, ui32_t cnt, int nmail)
230 struct message *root;
231 ui32_t n;
232 struct msort *ms;
233 int i, autocollapse;
234 NYD_ENTER;
236 autocollapse = (!nmail && !(inhook & 2) && ok_blook(autocollapse));
237 ms = smalloc(sizeof *ms * cnt);
239 for (n = 0, i = 0; UICMP(32, i, <, cnt); ++i) {
240 if (m[i].m_parent == NULL) {
241 if (autocollapse)
242 _colps(m + i, 1);
243 ms[n].ms_u.ms_long = m[i].m_date;
244 ms[n].ms_n = i;
245 ++n;
249 if (n > 0) {
250 qsort(ms, n, sizeof *ms, &_mlonglt);
251 root = m + ms[0].ms_n;
252 for (i = 1; UICMP(32, i, <, n); ++i) {
253 m[ms[i-1].ms_n].m_younger = m + ms[i].ms_n;
254 m[ms[i].ms_n].m_elder = m + ms[i - 1].ms_n;
256 } else
257 root = m;
259 free(ms);
260 NYD_LEAVE;
261 return root;
264 static void
265 _finalize(struct message *mp)
267 long n;
268 NYD_ENTER;
270 for (n = 0; mp; mp = next_in_thread(mp)) {
271 mp->m_threadpos = ++n;
272 mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
274 NYD_LEAVE;
277 #ifdef HAVE_SPAM
278 static int
279 _mui32lt(void const *a, void const *b)
281 struct msort const *xa = a, *xb = b;
282 int i;
283 NYD_ENTER;
285 i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
286 if (i == 0)
287 i = xa->ms_n - xb->ms_n;
288 NYD_LEAVE;
289 return i;
291 #endif
293 static int
294 _mlonglt(void const *a, void const *b)
296 struct msort const *xa = a, *xb = b;
297 int i;
298 NYD_ENTER;
300 i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
301 if (i == 0)
302 i = xa->ms_n - xb->ms_n;
303 NYD_LEAVE;
304 return i;
307 static int
308 _mcharlt(void const *a, void const *b)
310 struct msort const *xa = a, *xb = b;
311 int i;
312 NYD_ENTER;
314 i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
315 if (i == 0)
316 i = xa->ms_n - xb->ms_n;
317 NYD_LEAVE;
318 return i;
321 static void
322 _lookup(struct message *m, struct mitem *mi, ui32_t mprime)
324 struct name *np;
325 struct mitem *ip;
326 char *cp;
327 long dist;
328 NYD_ENTER;
330 if (m->m_flag & MHIDDEN)
331 goto jleave;
333 dist = 1;
334 if ((cp = hfield1("in-reply-to", m)) != NULL) {
335 if ((np = extract(cp, GREF)) != NULL)
336 do {
337 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
338 ip != NOT_AN_ID) {
339 _adopt(ip->mi_data, m, 1);
340 goto jleave;
342 } while ((np = np->n_flink) != NULL);
345 if ((cp = hfield1("references", m)) != NULL) {
346 if ((np = extract(cp, GREF)) != NULL) {
347 while (np->n_flink != NULL)
348 np = np->n_flink;
349 do {
350 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
351 if (ip == NOT_AN_ID)
352 continue; /* skip dist++ */
353 _adopt(ip->mi_data, m, dist);
354 goto jleave;
356 ++dist;
357 } while ((np = np->n_blink) != NULL);
360 jleave:
361 NYD_LEAVE;
364 static void
365 _makethreads(struct message *m, ui32_t cnt, int nmail)
367 struct mitem *mt;
368 char *cp;
369 ui32_t i, mprime;
370 NYD_ENTER;
372 if (cnt == 0)
373 goto jleave;
375 mprime = nextprime(cnt);
376 mt = scalloc(mprime, sizeof *mt);
378 srelax_hold();
380 for (i = 0; i < cnt; ++i) {
381 if (!(m[i].m_flag & MHIDDEN)) {
382 _mlook(NULL, mt, m + i, mprime);
383 if (m[i].m_date == 0) {
384 if ((cp = hfield1("date", m + i)) != NULL)
385 m[i].m_date = rfctime(cp);
388 m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
389 m[i].m_level = 0;
390 if (!nmail && !(inhook & 2))
391 m[i].m_collapsed = 0;
392 srelax();
395 /* Most folders contain the eldest messages first. Traversing them in
396 * descending order makes it more likely that younger brothers are found
397 * first, so elder ones can be prepended to the brother list, which is
398 * faster. The worst case is still in O(n^2) and occurs when all but one
399 * messages in a folder are replies to the one message, and are sorted such
400 * that youngest messages occur first */
401 for (i = cnt; i > 0; --i) {
402 _lookup(m + i - 1, mt, mprime);
403 srelax();
406 srelax_rele();
408 threadroot = _interlink(m, cnt, nmail);
409 _finalize(threadroot);
411 for (i = 0; i < mprime; ++i)
412 if (mt[i].mi_id != NULL)
413 free(mt[i].mi_id);
415 free(mt);
416 mb.mb_threaded = 1;
417 jleave:
418 NYD_LEAVE;
421 static int
422 _colpt(int *msgvec, int cl)
424 int *ip, rv;
425 NYD_ENTER;
427 if (mb.mb_threaded != 1) {
428 puts("Not in threaded mode.");
429 rv = 1;
430 } else {
431 for (ip = msgvec; *ip != 0; ++ip)
432 _colps(message + *ip - 1, cl);
433 rv = 0;
435 NYD_LEAVE;
436 return rv;
439 static void
440 _colps(struct message *b, int cl)
442 struct message *m;
443 int cc = 0, uc = 0;
444 NYD_ENTER;
446 if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
447 goto jleave;
449 if (b->m_child != NULL) {
450 m = b->m_child;
451 _colpm(m, cl, &cc, &uc);
452 for (m = m->m_younger; m != NULL; m = m->m_younger)
453 _colpm(m, cl, &cc, &uc);
456 if (cl) {
457 b->m_collapsed = -cc;
458 for (m = b->m_parent; m != NULL; m = m->m_parent)
459 if (m->m_collapsed <= -uc) {
460 m->m_collapsed += uc;
461 break;
463 } else {
464 if (b->m_collapsed > 0) {
465 b->m_collapsed = 0;
466 ++uc;
468 for (m = b; m != NULL; m = m->m_parent)
469 if (m->m_collapsed <= -uc) {
470 m->m_collapsed += uc;
471 break;
474 jleave:
475 NYD_LEAVE;
478 static void
479 _colpm(struct message *m, int cl, int *cc, int *uc)
481 NYD_ENTER;
482 if (cl) {
483 if (m->m_collapsed > 0)
484 ++(*uc);
485 if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
486 m->m_collapsed = 1;
487 if (m->m_collapsed > 0)
488 ++(*cc);
489 } else {
490 if (m->m_collapsed > 0) {
491 m->m_collapsed = 0;
492 ++(*uc);
496 if (m->m_child != NULL) {
497 m = m->m_child;
498 _colpm(m, cl, cc, uc);
499 for (m = m->m_younger; m != NULL; m = m->m_younger)
500 _colpm(m, cl, cc, uc);
502 NYD_LEAVE;
505 FL int
506 c_thread(void *vp)
508 int rv;
509 NYD_ENTER;
511 if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
512 #ifdef HAVE_IMAP
513 if (mb.mb_type == MB_IMAP)
514 imap_getheaders(1, msgCount);
515 #endif
516 _makethreads(message, msgCount, (vp == (void*)-1));
517 if (mb.mb_sorted != NULL)
518 free(mb.mb_sorted);
519 mb.mb_sorted = sstrdup("thread");
522 if (vp != NULL && vp != (void*)-1 && !inhook && ok_blook(header))
523 rv = c_headers(vp);
524 else
525 rv = 0;
526 NYD_LEAVE;
527 return rv;
530 FL int
531 c_unthread(void *vp)
533 struct message *m;
534 int rv;
535 NYD_ENTER;
537 mb.mb_threaded = 0;
538 free(mb.mb_sorted);
539 mb.mb_sorted = NULL;
541 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
542 m->m_collapsed = 0;
544 if (vp && !inhook && ok_blook(header))
545 rv = c_headers(vp);
546 else
547 rv = 0;
548 NYD_LEAVE;
549 return rv;
552 FL struct message *
553 next_in_thread(struct message *mp)
555 struct message *rv;
556 NYD2_ENTER;
558 if ((rv = mp->m_child) != NULL)
559 goto jleave;
560 if ((rv = mp->m_younger) != NULL)
561 goto jleave;
563 while ((rv = mp->m_parent) != NULL) {
564 mp = rv;
565 if ((rv = rv->m_younger) != NULL)
566 goto jleave;
568 jleave:
569 NYD2_LEAVE;
570 return rv;
573 FL struct message *
574 prev_in_thread(struct message *mp)
576 struct message *rv;
577 NYD2_ENTER;
579 if ((rv = mp->m_elder) != NULL) {
580 for (mp = rv; (rv = mp->m_child) != NULL;) {
581 mp = rv;
582 while ((rv = mp->m_younger) != NULL)
583 mp = rv;
585 rv = mp;
586 goto jleave;
588 rv = mp->m_parent;
589 jleave:
590 NYD2_LEAVE;
591 return rv;
594 FL struct message *
595 this_in_thread(struct message *mp, long n)
597 struct message *rv;
598 NYD2_ENTER;
600 if (n == -1) { /* find end of thread */
601 while (mp != NULL) {
602 if ((rv = mp->m_younger) != NULL) {
603 mp = rv;
604 continue;
606 rv = next_in_thread(mp);
607 if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
608 rv = mp;
609 goto jleave;
611 mp = rv;
613 rv = mp;
614 goto jleave;
617 while (mp != NULL && mp->m_threadpos < n) {
618 if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
619 mp = rv;
620 continue;
622 mp = next_in_thread(mp);
624 rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
625 jleave:
626 NYD2_LEAVE;
627 return rv;
630 FL int
631 c_sort(void *vp)
633 enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
634 SORT_TO, SORT_SPAM, SORT_THREAD} method;
635 struct {
636 char const *me_name;
637 enum method me_method;
638 int (*me_func)(void const *, void const *);
639 } const methnames[] = {
640 {"date", SORT_DATE, &_mlonglt},
641 {"from", SORT_FROM, &_mcharlt},
642 {"to", SORT_TO, &_mcharlt},
643 {"subject", SORT_SUBJECT, &_mcharlt},
644 {"size", SORT_SIZE, &_mlonglt},
645 #ifdef HAVE_SPAM
646 {"spam", SORT_SPAM, &_mui32lt},
647 #endif
648 {"status", SORT_STATUS, &_mlonglt},
649 {"thread", SORT_THREAD, NULL}
652 struct str in, out;
653 char **args = (char**)vp, *cp, *_args[2];
654 int (*func)(void const *, void const *);
655 struct msort *ms;
656 int i, n, msgvec[2];
657 struct message *mp;
658 bool_t showname;
659 NYD_ENTER;
661 showname = ok_blook(showname);
662 msgvec[0] = (int)PTR2SIZE(dot - message + 1);
663 msgvec[1] = 0;
665 if (vp == NULL || vp == (void*)-1) {
666 _args[0] = savestr(mb.mb_sorted);
667 _args[1] = NULL;
668 args = _args;
669 } else if (args[0] == NULL) {
670 printf("Current sorting criterion is: %s\n",
671 (mb.mb_sorted ? mb.mb_sorted : "unsorted"));
672 i = 0;
673 goto jleave;
676 for (i = 0; UICMP(z, i, <, NELEM(methnames)); ++i)
677 if (*args[0] != '\0' && is_prefix(args[0], methnames[i].me_name))
678 goto jmethok;
679 fprintf(stderr, "Unknown sorting method \"%s\"\n", args[0]);
680 i = 1;
681 goto jleave;
683 jmethok:
684 method = methnames[i].me_method;
685 func = methnames[i].me_func;
686 free(mb.mb_sorted);
687 mb.mb_sorted = sstrdup(args[0]);
689 if (method == SORT_THREAD) {
690 i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
691 goto jleave;
694 ms = ac_alloc(sizeof *ms * msgCount);
695 #ifdef HAVE_IMAP
696 switch (method) {
697 case SORT_SUBJECT:
698 case SORT_DATE:
699 case SORT_FROM:
700 case SORT_TO:
701 if (mb.mb_type == MB_IMAP)
702 imap_getheaders(1, msgCount);
703 break;
704 default:
705 break;
707 #endif
709 srelax_hold();
710 for (n = 0, i = 0; i < msgCount; ++i) {
711 mp = message + i;
712 if (!(mp->m_flag & MHIDDEN)) {
713 switch (method) {
714 case SORT_DATE:
715 if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
716 mp->m_date = rfctime(cp);
717 ms[n].ms_u.ms_long = mp->m_date;
718 break;
719 case SORT_STATUS:
720 if (mp->m_flag & MDELETED)
721 ms[n].ms_u.ms_long = 1;
722 else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
723 ms[n].ms_u.ms_long = 90;
724 else if (mp->m_flag & MFLAGGED)
725 ms[n].ms_u.ms_long = 85;
726 else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
727 ms[n].ms_u.ms_long = 70;
728 else if (mp->m_flag & MNEW)
729 ms[n].ms_u.ms_long = 80;
730 else if (mp->m_flag & MREAD)
731 ms[n].ms_u.ms_long = 40;
732 else
733 ms[n].ms_u.ms_long = 60;
734 break;
735 case SORT_SIZE:
736 ms[n].ms_u.ms_long = mp->m_xsize;
737 break;
738 #ifdef HAVE_SPAM
739 case SORT_SPAM:
740 ms[n].ms_u.ms_ui = mp->m_spamscore;
741 break;
742 #endif
743 case SORT_FROM:
744 case SORT_TO:
745 if ((cp = hfield1((method == SORT_FROM ? "from" : "to"), mp)) !=
746 NULL) {
747 ms[n].ms_u.ms_char = sstrdup(showname ? realname(cp) : skin(cp));
748 makelow(ms[n].ms_u.ms_char);
749 } else
750 ms[n].ms_u.ms_char = sstrdup("");
751 break;
752 default:
753 case SORT_SUBJECT:
754 if ((cp = hfield1("subject", mp)) != NULL) {
755 in.s = cp;
756 in.l = strlen(in.s);
757 mime_fromhdr(&in, &out, TD_ICONV);
758 ms[n].ms_u.ms_char = sstrdup(subject_re_trim(out.s));
759 free(out.s);
760 makelow(ms[n].ms_u.ms_char);
761 } else
762 ms[n].ms_u.ms_char = sstrdup("");
763 break;
765 ms[n++].ms_n = i;
767 mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
768 mp->m_level = 0;
769 mp->m_collapsed = 0;
770 srelax();
772 srelax_rele();
774 if (n > 0) {
775 qsort(ms, n, sizeof *ms, func);
776 threadroot = message + ms[0].ms_n;
777 for (i = 1; i < n; ++i) {
778 message[ms[i - 1].ms_n].m_younger = message + ms[i].ms_n;
779 message[ms[i].ms_n].m_elder = message + ms[i - 1].ms_n;
781 } else
782 threadroot = message;
783 _finalize(threadroot);
784 mb.mb_threaded = 2;
786 switch (method) {
787 case SORT_FROM:
788 case SORT_TO:
789 case SORT_SUBJECT:
790 for (i = 0; i < n; ++i)
791 free(ms[i].ms_u.ms_char);
792 /* FALLTHRU */
793 default:
794 break;
796 ac_free(ms);
797 i = ((vp != NULL && vp != (void*)-1 && !inhook && ok_blook(header))
798 ? c_headers(msgvec) : 0);
799 jleave:
800 NYD_LEAVE;
801 return i;
804 FL int
805 c_collapse(void *v)
807 int rv;
808 NYD_ENTER;
810 rv = _colpt(v, 1);
811 NYD_LEAVE;
812 return rv;
815 FL int
816 c_uncollapse(void *v)
818 int rv;
819 NYD_ENTER;
821 rv = _colpt(v, 0);
822 NYD_LEAVE;
823 return rv;
826 FL void
827 uncollapse1(struct message *mp, int always)
829 NYD_ENTER;
830 if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
831 _colps(mp, 0);
832 NYD_LEAVE;
835 /* s-it-mode */