makefile:test: pass $(MAKEJOBS)
[s-mailx.git] / thread.c
blob6f030bd282975807e23d8a4cc33b37841ab03d25
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 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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.
39 #undef n_FILE
40 #define n_FILE thread
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 /* Open addressing is used for Message-IDs because the maximum number of
47 * messages in the table is known in advance (== msgCount) */
48 struct mitem {
49 struct message *mi_data;
50 char *mi_id;
52 #define NOT_AN_ID ((struct mitem*)-1)
54 struct msort {
55 union {
56 #ifdef HAVE_SPAM
57 ui32_t ms_ui;
58 #endif
59 long ms_long;
60 char *ms_char;
61 } ms_u;
62 int ms_n;
65 /* Return the hash value for a message id modulo mprime, or mprime if the
66 * passed string does not look like a message-id */
67 static ui32_t _mhash(char const *cp, ui32_t mprime);
69 /* Look up a message id. Returns NOT_AN_ID if the passed string does not look
70 * like a message-id */
71 static struct mitem * _mlook(char *id, struct mitem *mt,
72 struct message *mdata, ui32_t mprime);
74 /* Child is to be adopted by parent. A thread tree is structured as follows:
76 * ------ m_child ------ m_child
77 * | |-------------------->| |------------------------> . . .
78 * | |<--------------------| |<----------------------- . . .
79 * ------ m_parent ------ m_parent
80 * ^^ | ^
81 * | \____ m_younger | |
82 * | \ | |
83 * | ---- | |
84 * | \ | | m_elder
85 * | m_parent ---- | |
86 * \ | |
87 * ---- | |
88 * \ + |
89 * ------ m_child
90 * | |------------------------> . . .
91 * | |<----------------------- . . .
92 * ------ m_parent
93 * | ^
94 * . . .
96 * The base message of a thread does not have a m_parent link. Elements
97 * connected by m_younger/m_elder links are replies to the same message, which
98 * is connected to them by m_parent links. The first reply to a message gets
99 * the m_child link */
100 static void _adopt(struct message *parent, struct message *child,
101 int dist);
103 /* Connect all msgs on the lowest thread level with m_younger/m_elder links */
104 static struct message * _interlink(struct message *m, ui32_t cnt, int nmail);
106 static void _finalize(struct message *mp);
108 /* Several sort comparison PTFs */
109 #ifdef HAVE_SPAM
110 static int _mui32lt(void const *a, void const *b);
111 #endif
112 static int _mlonglt(void const *a, void const *b);
113 static int _mcharlt(void const *a, void const *b);
115 static void _lookup(struct message *m, struct mitem *mi,
116 ui32_t mprime);
117 static void _makethreads(struct message *m, ui32_t cnt, int nmail);
118 static int _colpt(int *msgvec, int cl);
119 static void _colps(struct message *b, int cl);
120 static void _colpm(struct message *m, int cl, int *cc, int *uc);
122 static ui32_t
123 _mhash(char const *cp, ui32_t mprime)
125 ui32_t h = 0, g, at = 0;
126 NYD2_ENTER;
128 for (--cp; *++cp != '\0';) {
129 /* Pay attention not to hash characters which are irrelevant for
130 * Message-ID semantics */
131 if (*cp == '(') {
132 cp = skip_comment(cp + 1) - 1;
133 continue;
135 if (*cp == '"' || *cp == '\\')
136 continue;
137 if (*cp == '@')
138 ++at;
139 /* TODO torek hash */
140 h = ((h << 4) & 0xffffffff) + lowerconv(*cp);
141 if ((g = h & 0xf0000000) != 0) {
142 h = h ^ (g >> 24);
143 h = h ^ g;
146 NYD2_LEAVE;
147 return (at ? h % mprime : mprime);
150 static struct mitem *
151 _mlook(char *id, struct mitem *mt, struct message *mdata, ui32_t mprime)
153 struct mitem *mp = NULL;
154 ui32_t h, c, n = 0;
155 NYD2_ENTER;
157 if (id == NULL) {
158 if ((id = hfield1("message-id", mdata)) == NULL)
159 goto jleave;
160 /* Normalize, what hfield1() doesn't do (TODO should now GREF, too!!) */
161 if (id[0] == '<') {
162 id[strlen(id) -1] = '\0';
163 if (*id != '\0')
164 ++id;
168 if (mdata != NULL && mdata->m_idhash)
169 h = ~mdata->m_idhash;
170 else {
171 h = _mhash(id, mprime);
172 if (h == mprime) {
173 mp = NOT_AN_ID;
174 goto jleave;
178 mp = mt + (c = h);
179 while (mp->mi_id != NULL) {
180 if (!msgidcmp(mp->mi_id, id))
181 break;
182 c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
183 ++n;
184 if ((si32_t)c < 0)
185 c = 0;
186 else while (c >= mprime)
187 c -= mprime;
188 mp = mt + c;
191 if (mdata != NULL && mp->mi_id == NULL) {
192 mp->mi_id = sstrdup(id);
193 mp->mi_data = mdata;
194 mdata->m_idhash = ~h;
196 if (mp->mi_id == NULL)
197 mp = NULL;
198 jleave:
199 NYD2_LEAVE;
200 return mp;
203 static void
204 _adopt(struct message *parent, struct message *child, int dist)
206 struct message *mp, *mq;
207 NYD2_ENTER;
209 for (mp = parent; mp != NULL; mp = mp->m_parent)
210 if (mp == child)
211 goto jleave;
213 child->m_level = dist; /* temporarily store distance */
214 child->m_parent = parent;
216 if (parent->m_child != NULL) {
217 mq = NULL;
218 for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
219 if (mp->m_date >= child->m_date) {
220 if (mp->m_elder != NULL)
221 mp->m_elder->m_younger = child;
222 child->m_elder = mp->m_elder;
223 mp->m_elder = child;
224 child->m_younger = mp;
225 if (mp == parent->m_child)
226 parent->m_child = child;
227 goto jleave;
229 mq = mp;
231 mq->m_younger = child;
232 child->m_elder = mq;
233 } else
234 parent->m_child = child;
235 jleave:
236 NYD2_LEAVE;
239 static struct message *
240 _interlink(struct message *m, ui32_t cnt, int nmail)
242 struct message *root;
243 ui32_t n;
244 struct msort *ms;
245 int i, autocollapse;
246 NYD2_ENTER;
248 autocollapse = (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL) &&
249 ok_blook(autocollapse));
250 ms = smalloc(sizeof *ms * cnt);
252 for (n = 0, i = 0; UICMP(32, i, <, cnt); ++i) {
253 if (m[i].m_parent == NULL) {
254 if (autocollapse)
255 _colps(m + i, 1);
256 ms[n].ms_u.ms_long = m[i].m_date;
257 ms[n].ms_n = i;
258 ++n;
262 if (n > 0) {
263 qsort(ms, n, sizeof *ms, &_mlonglt);
264 root = m + ms[0].ms_n;
265 for (i = 1; UICMP(32, i, <, n); ++i) {
266 m[ms[i-1].ms_n].m_younger = m + ms[i].ms_n;
267 m[ms[i].ms_n].m_elder = m + ms[i - 1].ms_n;
269 } else
270 root = NULL;
272 free(ms);
273 NYD2_LEAVE;
274 return root;
277 static void
278 _finalize(struct message *mp)
280 long n;
281 NYD2_ENTER;
283 for (n = 0; mp; mp = next_in_thread(mp)) {
284 mp->m_threadpos = ++n;
285 mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
287 NYD2_LEAVE;
290 #ifdef HAVE_SPAM
291 static int
292 _mui32lt(void const *a, void const *b)
294 struct msort const *xa = a, *xb = b;
295 int i;
296 NYD2_ENTER;
298 i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
299 if (i == 0)
300 i = xa->ms_n - xb->ms_n;
301 NYD2_LEAVE;
302 return i;
304 #endif
306 static int
307 _mlonglt(void const *a, void const *b)
309 struct msort const *xa = a, *xb = b;
310 int i;
311 NYD2_ENTER;
313 i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
314 if (i == 0)
315 i = xa->ms_n - xb->ms_n;
316 NYD2_LEAVE;
317 return i;
320 static int
321 _mcharlt(void const *a, void const *b)
323 struct msort const *xa = a, *xb = b;
324 int i;
325 NYD2_ENTER;
327 i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
328 if (i == 0)
329 i = xa->ms_n - xb->ms_n;
330 NYD2_LEAVE;
331 return i;
334 static void
335 _lookup(struct message *m, struct mitem *mi, ui32_t mprime)
337 struct name *np;
338 struct mitem *ip;
339 char *cp;
340 long dist;
341 NYD2_ENTER;
343 if (m->m_flag & MHIDDEN)
344 goto jleave;
346 dist = 1;
347 if ((cp = hfield1("in-reply-to", m)) != NULL) {
348 if ((np = extract(cp, GREF)) != NULL)
349 do {
350 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
351 ip != NOT_AN_ID) {
352 _adopt(ip->mi_data, m, 1);
353 goto jleave;
355 } while ((np = np->n_flink) != NULL);
358 if ((cp = hfield1("references", m)) != NULL) {
359 if ((np = extract(cp, GREF)) != NULL) {
360 while (np->n_flink != NULL)
361 np = np->n_flink;
362 do {
363 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
364 if (ip == NOT_AN_ID)
365 continue; /* skip dist++ */
366 _adopt(ip->mi_data, m, dist);
367 goto jleave;
369 ++dist;
370 } while ((np = np->n_blink) != NULL);
373 jleave:
374 NYD2_LEAVE;
377 static void
378 _makethreads(struct message *m, ui32_t cnt, int nmail)
380 struct mitem *mt;
381 char *cp;
382 ui32_t i, mprime;
383 NYD2_ENTER;
385 if (cnt == 0)
386 goto jleave;
388 /* It is performance crucial to space this large enough in order to minimize
389 * bucket sharing */
390 mprime = nextprime((cnt < UI32_MAX >> 3) ? cnt << 2 : cnt);
391 mt = scalloc(mprime, sizeof *mt);
393 srelax_hold();
395 for (i = 0; i < cnt; ++i) {
396 if (!(m[i].m_flag & MHIDDEN)) {
397 _mlook(NULL, mt, m + i, mprime);
398 if (m[i].m_date == 0) {
399 if ((cp = hfield1("date", m + i)) != NULL)
400 m[i].m_date = rfctime(cp);
403 m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
404 m[i].m_level = 0;
405 if (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL))
406 m[i].m_collapsed = 0;
407 srelax();
410 /* Most folders contain the eldest messages first. Traversing them in
411 * descending order makes it more likely that younger brothers are found
412 * first, so elder ones can be prepended to the brother list, which is
413 * faster. The worst case is still in O(n^2) and occurs when all but one
414 * messages in a folder are replies to the one message, and are sorted such
415 * that youngest messages occur first */
416 for (i = cnt; i > 0; --i) {
417 _lookup(m + i - 1, mt, mprime);
418 srelax();
421 srelax_rele();
423 threadroot = _interlink(m, cnt, nmail);
424 _finalize(threadroot);
426 for (i = 0; i < mprime; ++i)
427 if (mt[i].mi_id != NULL)
428 free(mt[i].mi_id);
430 free(mt);
431 mb.mb_threaded = 1;
432 jleave:
433 NYD2_LEAVE;
436 static int
437 _colpt(int *msgvec, int cl)
439 int *ip, rv;
440 NYD2_ENTER;
442 if (mb.mb_threaded != 1) {
443 fputs("Not in threaded mode.\n", n_stdout);
444 rv = 1;
445 } else {
446 for (ip = msgvec; *ip != 0; ++ip)
447 _colps(message + *ip - 1, cl);
448 rv = 0;
450 NYD2_LEAVE;
451 return rv;
454 static void
455 _colps(struct message *b, int cl)
457 struct message *m;
458 int cc = 0, uc = 0;
459 NYD2_ENTER;
461 if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
462 goto jleave;
464 if (b->m_child != NULL) {
465 m = b->m_child;
466 _colpm(m, cl, &cc, &uc);
467 for (m = m->m_younger; m != NULL; m = m->m_younger)
468 _colpm(m, cl, &cc, &uc);
471 if (cl) {
472 b->m_collapsed = -cc;
473 for (m = b->m_parent; m != NULL; m = m->m_parent)
474 if (m->m_collapsed <= -uc) {
475 m->m_collapsed += uc;
476 break;
478 } else {
479 if (b->m_collapsed > 0) {
480 b->m_collapsed = 0;
481 ++uc;
483 for (m = b; m != NULL; m = m->m_parent)
484 if (m->m_collapsed <= -uc) {
485 m->m_collapsed += uc;
486 break;
489 jleave:
490 NYD2_LEAVE;
493 static void
494 _colpm(struct message *m, int cl, int *cc, int *uc)
496 NYD2_ENTER;
497 if (cl) {
498 if (m->m_collapsed > 0)
499 ++(*uc);
500 if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
501 m->m_collapsed = 1;
502 if (m->m_collapsed > 0)
503 ++(*cc);
504 } else {
505 if (m->m_collapsed > 0) {
506 m->m_collapsed = 0;
507 ++(*uc);
511 if (m->m_child != NULL) {
512 m = m->m_child;
513 _colpm(m, cl, cc, uc);
514 for (m = m->m_younger; m != NULL; m = m->m_younger)
515 _colpm(m, cl, cc, uc);
517 NYD2_LEAVE;
520 FL int
521 c_thread(void *vp)
523 int rv;
524 NYD_ENTER;
526 if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
527 _makethreads(message, msgCount, (vp == (void*)-1));
528 if (mb.mb_sorted != NULL)
529 free(mb.mb_sorted);
530 mb.mb_sorted = sstrdup("thread");
533 if (vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
534 ok_blook(header))
535 rv = print_header_group(vp);
536 else
537 rv = 0;
538 NYD_LEAVE;
539 return rv;
542 FL int
543 c_unthread(void *vp)
545 struct message *m;
546 int rv;
547 NYD_ENTER;
549 mb.mb_threaded = 0;
550 if (mb.mb_sorted != NULL)
551 free(mb.mb_sorted);
552 mb.mb_sorted = NULL;
554 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
555 m->m_collapsed = 0;
557 if (vp && !(n_pstate & n_PS_HOOK_MASK) && ok_blook(header))
558 rv = print_header_group(vp);
559 else
560 rv = 0;
561 NYD_LEAVE;
562 return rv;
565 FL struct message *
566 next_in_thread(struct message *mp)
568 struct message *rv;
569 NYD2_ENTER;
571 if ((rv = mp->m_child) != NULL)
572 goto jleave;
573 if ((rv = mp->m_younger) != NULL)
574 goto jleave;
576 while ((rv = mp->m_parent) != NULL) {
577 mp = rv;
578 if ((rv = rv->m_younger) != NULL)
579 goto jleave;
581 jleave:
582 NYD2_LEAVE;
583 return rv;
586 FL struct message *
587 prev_in_thread(struct message *mp)
589 struct message *rv;
590 NYD2_ENTER;
592 if ((rv = mp->m_elder) != NULL) {
593 for (mp = rv; (rv = mp->m_child) != NULL;) {
594 mp = rv;
595 while ((rv = mp->m_younger) != NULL)
596 mp = rv;
598 rv = mp;
599 goto jleave;
601 rv = mp->m_parent;
602 jleave:
603 NYD2_LEAVE;
604 return rv;
607 FL struct message *
608 this_in_thread(struct message *mp, long n)
610 struct message *rv;
611 NYD2_ENTER;
613 if (n == -1) { /* find end of thread */
614 while (mp != NULL) {
615 if ((rv = mp->m_younger) != NULL) {
616 mp = rv;
617 continue;
619 rv = next_in_thread(mp);
620 if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
621 rv = mp;
622 goto jleave;
624 mp = rv;
626 rv = mp;
627 goto jleave;
630 while (mp != NULL && mp->m_threadpos < n) {
631 if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
632 mp = rv;
633 continue;
635 mp = next_in_thread(mp);
637 rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
638 jleave:
639 NYD2_LEAVE;
640 return rv;
643 FL int
644 c_sort(void *vp)
646 enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
647 SORT_TO, SORT_SPAM, SORT_THREAD} method;
648 struct {
649 char const *me_name;
650 enum method me_method;
651 int (*me_func)(void const *, void const *);
652 } const methnames[] = {
653 {"date", SORT_DATE, &_mlonglt},
654 {"from", SORT_FROM, &_mcharlt},
655 {"to", SORT_TO, &_mcharlt},
656 {"subject", SORT_SUBJECT, &_mcharlt},
657 {"size", SORT_SIZE, &_mlonglt},
658 #ifdef HAVE_SPAM
659 {"spam", SORT_SPAM, &_mui32lt},
660 #endif
661 {"status", SORT_STATUS, &_mlonglt},
662 {"thread", SORT_THREAD, NULL}
665 struct str in, out;
666 char *_args[2], *cp, **args = vp;
667 int msgvec[2], i, n;
668 int (*func)(void const *, void const *);
669 struct msort *ms;
670 struct message *mp;
671 bool_t showname;
672 NYD_ENTER;
674 if (vp == NULL || vp == (void*)-1) {
675 _args[0] = savestr((mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
676 _args[1] = NULL;
677 args = _args;
678 } else if (args[0] == NULL) {
679 fprintf(n_stdout, "Current sorting criterion is: %s\n",
680 (mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
681 i = 0;
682 goto jleave;
685 i = 0;
686 for (;;) {
687 if (*args[0] != '\0' && is_prefix(args[0], methnames[i].me_name))
688 break;
689 if (UICMP(z, ++i, >=, n_NELEM(methnames))) {
690 n_err(_("Unknown sorting method: %s\n"), args[0]);
691 i = 1;
692 goto jleave;
696 if (mb.mb_sorted != NULL)
697 free(mb.mb_sorted);
698 mb.mb_sorted = sstrdup(args[0]);
700 method = methnames[i].me_method;
701 func = methnames[i].me_func;
702 msgvec[0] = (int)PTR2SIZE(dot - message + 1);
703 msgvec[1] = 0;
705 if (method == SORT_THREAD) {
706 i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
707 goto jleave;
710 showname = ok_blook(showname);
711 ms = ac_alloc(sizeof *ms * msgCount);
713 srelax_hold();
714 for (n = 0, i = 0; i < msgCount; ++i) {
715 mp = message + i;
716 if (!(mp->m_flag & MHIDDEN)) {
717 switch (method) {
718 case SORT_DATE:
719 if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
720 mp->m_date = rfctime(cp);
721 ms[n].ms_u.ms_long = mp->m_date;
722 break;
723 case SORT_STATUS:
724 if (mp->m_flag & MDELETED)
725 ms[n].ms_u.ms_long = 1;
726 else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
727 ms[n].ms_u.ms_long = 90;
728 else if (mp->m_flag & MFLAGGED)
729 ms[n].ms_u.ms_long = 85;
730 else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
731 ms[n].ms_u.ms_long = 70;
732 else if (mp->m_flag & MNEW)
733 ms[n].ms_u.ms_long = 80;
734 else if (mp->m_flag & MREAD)
735 ms[n].ms_u.ms_long = 40;
736 else
737 ms[n].ms_u.ms_long = 60;
738 break;
739 case SORT_SIZE:
740 ms[n].ms_u.ms_long = mp->m_xsize;
741 break;
742 #ifdef HAVE_SPAM
743 case SORT_SPAM:
744 ms[n].ms_u.ms_ui = mp->m_spamscore;
745 break;
746 #endif
747 case SORT_FROM:
748 case SORT_TO:
749 if ((cp = hfield1((method == SORT_FROM ? "from" : "to"), mp)) !=
750 NULL) {
751 ms[n].ms_u.ms_char = sstrdup(showname ? realname(cp) : skin(cp));
752 makelow(ms[n].ms_u.ms_char);
753 } else
754 ms[n].ms_u.ms_char = sstrdup(n_empty);
755 break;
756 default:
757 case SORT_SUBJECT:
758 if ((cp = hfield1("subject", mp)) != NULL) {
759 in.s = cp;
760 in.l = strlen(in.s);
761 mime_fromhdr(&in, &out, TD_ICONV);
762 ms[n].ms_u.ms_char = sstrdup(subject_re_trim(out.s));
763 free(out.s);
764 makelow(ms[n].ms_u.ms_char);
765 } else
766 ms[n].ms_u.ms_char = sstrdup(n_empty);
767 break;
769 ms[n++].ms_n = i;
771 mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
772 mp->m_level = 0;
773 mp->m_collapsed = 0;
774 srelax();
776 srelax_rele();
778 if (n > 0) {
779 qsort(ms, n, sizeof *ms, func);
780 threadroot = message + ms[0].ms_n;
781 for (i = 1; i < n; ++i) {
782 message[ms[i - 1].ms_n].m_younger = message + ms[i].ms_n;
783 message[ms[i].ms_n].m_elder = message + ms[i - 1].ms_n;
785 } else
786 threadroot = NULL;
788 _finalize(threadroot);
789 mb.mb_threaded = 2;
791 switch (method) {
792 case SORT_FROM:
793 case SORT_TO:
794 case SORT_SUBJECT:
795 for (i = 0; i < n; ++i)
796 free(ms[i].ms_u.ms_char);
797 /* FALLTHRU */
798 default:
799 break;
801 ac_free(ms);
803 i = ((vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
804 ok_blook(header)) ? print_header_group(msgvec) : 0);
805 jleave:
806 NYD_LEAVE;
807 return i;
810 FL int
811 c_collapse(void *v)
813 int rv;
814 NYD_ENTER;
816 rv = _colpt(v, 1);
817 NYD_LEAVE;
818 return rv;
821 FL int
822 c_uncollapse(void *v)
824 int rv;
825 NYD_ENTER;
827 rv = _colpt(v, 0);
828 NYD_LEAVE;
829 return rv;
832 FL void
833 uncollapse1(struct message *mp, int always)
835 NYD_ENTER;
836 if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
837 _colps(mp, 0);
838 NYD_LEAVE;
841 /* s-it-mode */