cc-test.sh: do a simple \`Resend' test
[s-mailx.git] / thread.c
blob26b889adfedfee605c30b2195a014aa4a568b974
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 - 2015 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.
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 while (c >= mprime)
185 c -= mprime;
186 mp = mt + c;
189 if (mdata != NULL && mp->mi_id == NULL) {
190 mp->mi_id = sstrdup(id);
191 mp->mi_data = mdata;
192 mdata->m_idhash = ~h;
194 if (mp->mi_id == NULL)
195 mp = NULL;
196 jleave:
197 NYD2_LEAVE;
198 return mp;
201 static void
202 _adopt(struct message *parent, struct message *child, int dist)
204 struct message *mp, *mq;
205 NYD2_ENTER;
207 for (mp = parent; mp != NULL; mp = mp->m_parent)
208 if (mp == child)
209 goto jleave;
211 child->m_level = dist; /* temporarily store distance */
212 child->m_parent = parent;
214 if (parent->m_child != NULL) {
215 mq = NULL;
216 for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
217 if (mp->m_date >= child->m_date) {
218 if (mp->m_elder != NULL)
219 mp->m_elder->m_younger = child;
220 child->m_elder = mp->m_elder;
221 mp->m_elder = child;
222 child->m_younger = mp;
223 if (mp == parent->m_child)
224 parent->m_child = child;
225 goto jleave;
227 mq = mp;
229 mq->m_younger = child;
230 child->m_elder = mq;
231 } else
232 parent->m_child = child;
233 jleave:
234 NYD2_LEAVE;
237 static struct message *
238 _interlink(struct message *m, ui32_t cnt, int nmail)
240 struct message *root;
241 ui32_t n;
242 struct msort *ms;
243 int i, autocollapse;
244 NYD2_ENTER;
246 autocollapse = (!nmail && !(pstate & PS_HOOK_NEWMAIL) &&
247 ok_blook(autocollapse));
248 ms = smalloc(sizeof *ms * cnt);
250 for (n = 0, i = 0; UICMP(32, i, <, cnt); ++i) {
251 if (m[i].m_parent == NULL) {
252 if (autocollapse)
253 _colps(m + i, 1);
254 ms[n].ms_u.ms_long = m[i].m_date;
255 ms[n].ms_n = i;
256 ++n;
260 if (n > 0) {
261 qsort(ms, n, sizeof *ms, &_mlonglt);
262 root = m + ms[0].ms_n;
263 for (i = 1; UICMP(32, i, <, n); ++i) {
264 m[ms[i-1].ms_n].m_younger = m + ms[i].ms_n;
265 m[ms[i].ms_n].m_elder = m + ms[i - 1].ms_n;
267 } else
268 root = NULL;
270 free(ms);
271 NYD2_LEAVE;
272 return root;
275 static void
276 _finalize(struct message *mp)
278 long n;
279 NYD2_ENTER;
281 for (n = 0; mp; mp = next_in_thread(mp)) {
282 mp->m_threadpos = ++n;
283 mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
285 NYD2_LEAVE;
288 #ifdef HAVE_SPAM
289 static int
290 _mui32lt(void const *a, void const *b)
292 struct msort const *xa = a, *xb = b;
293 int i;
294 NYD2_ENTER;
296 i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
297 if (i == 0)
298 i = xa->ms_n - xb->ms_n;
299 NYD2_LEAVE;
300 return i;
302 #endif
304 static int
305 _mlonglt(void const *a, void const *b)
307 struct msort const *xa = a, *xb = b;
308 int i;
309 NYD2_ENTER;
311 i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
312 if (i == 0)
313 i = xa->ms_n - xb->ms_n;
314 NYD2_LEAVE;
315 return i;
318 static int
319 _mcharlt(void const *a, void const *b)
321 struct msort const *xa = a, *xb = b;
322 int i;
323 NYD2_ENTER;
325 i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
326 if (i == 0)
327 i = xa->ms_n - xb->ms_n;
328 NYD2_LEAVE;
329 return i;
332 static void
333 _lookup(struct message *m, struct mitem *mi, ui32_t mprime)
335 struct name *np;
336 struct mitem *ip;
337 char *cp;
338 long dist;
339 NYD2_ENTER;
341 if (m->m_flag & MHIDDEN)
342 goto jleave;
344 dist = 1;
345 if ((cp = hfield1("in-reply-to", m)) != NULL) {
346 if ((np = extract(cp, GREF)) != NULL)
347 do {
348 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
349 ip != NOT_AN_ID) {
350 _adopt(ip->mi_data, m, 1);
351 goto jleave;
353 } while ((np = np->n_flink) != NULL);
356 if ((cp = hfield1("references", m)) != NULL) {
357 if ((np = extract(cp, GREF)) != NULL) {
358 while (np->n_flink != NULL)
359 np = np->n_flink;
360 do {
361 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
362 if (ip == NOT_AN_ID)
363 continue; /* skip dist++ */
364 _adopt(ip->mi_data, m, dist);
365 goto jleave;
367 ++dist;
368 } while ((np = np->n_blink) != NULL);
371 jleave:
372 NYD2_LEAVE;
375 static void
376 _makethreads(struct message *m, ui32_t cnt, int nmail)
378 struct mitem *mt;
379 char *cp;
380 ui32_t i, mprime;
381 NYD2_ENTER;
383 if (cnt == 0)
384 goto jleave;
386 /* It is performance crucial to space this large enough in order to minimize
387 * bucket sharing */
388 mprime = nextprime((cnt < UI32_MAX >> 3) ? cnt << 2 : cnt);
389 mt = scalloc(mprime, sizeof *mt);
391 srelax_hold();
393 for (i = 0; i < cnt; ++i) {
394 if (!(m[i].m_flag & MHIDDEN)) {
395 _mlook(NULL, mt, m + i, mprime);
396 if (m[i].m_date == 0) {
397 if ((cp = hfield1("date", m + i)) != NULL)
398 m[i].m_date = rfctime(cp);
401 m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
402 m[i].m_level = 0;
403 if (!nmail && !(pstate & PS_HOOK_NEWMAIL))
404 m[i].m_collapsed = 0;
405 srelax();
408 /* Most folders contain the eldest messages first. Traversing them in
409 * descending order makes it more likely that younger brothers are found
410 * first, so elder ones can be prepended to the brother list, which is
411 * faster. The worst case is still in O(n^2) and occurs when all but one
412 * messages in a folder are replies to the one message, and are sorted such
413 * that youngest messages occur first */
414 for (i = cnt; i > 0; --i) {
415 _lookup(m + i - 1, mt, mprime);
416 srelax();
419 srelax_rele();
421 threadroot = _interlink(m, cnt, nmail);
422 _finalize(threadroot);
424 for (i = 0; i < mprime; ++i)
425 if (mt[i].mi_id != NULL)
426 free(mt[i].mi_id);
428 free(mt);
429 mb.mb_threaded = 1;
430 jleave:
431 NYD2_LEAVE;
434 static int
435 _colpt(int *msgvec, int cl)
437 int *ip, rv;
438 NYD2_ENTER;
440 if (mb.mb_threaded != 1) {
441 puts("Not in threaded mode.");
442 rv = 1;
443 } else {
444 for (ip = msgvec; *ip != 0; ++ip)
445 _colps(message + *ip - 1, cl);
446 rv = 0;
448 NYD2_LEAVE;
449 return rv;
452 static void
453 _colps(struct message *b, int cl)
455 struct message *m;
456 int cc = 0, uc = 0;
457 NYD2_ENTER;
459 if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
460 goto jleave;
462 if (b->m_child != NULL) {
463 m = b->m_child;
464 _colpm(m, cl, &cc, &uc);
465 for (m = m->m_younger; m != NULL; m = m->m_younger)
466 _colpm(m, cl, &cc, &uc);
469 if (cl) {
470 b->m_collapsed = -cc;
471 for (m = b->m_parent; m != NULL; m = m->m_parent)
472 if (m->m_collapsed <= -uc) {
473 m->m_collapsed += uc;
474 break;
476 } else {
477 if (b->m_collapsed > 0) {
478 b->m_collapsed = 0;
479 ++uc;
481 for (m = b; m != NULL; m = m->m_parent)
482 if (m->m_collapsed <= -uc) {
483 m->m_collapsed += uc;
484 break;
487 jleave:
488 NYD2_LEAVE;
491 static void
492 _colpm(struct message *m, int cl, int *cc, int *uc)
494 NYD2_ENTER;
495 if (cl) {
496 if (m->m_collapsed > 0)
497 ++(*uc);
498 if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
499 m->m_collapsed = 1;
500 if (m->m_collapsed > 0)
501 ++(*cc);
502 } else {
503 if (m->m_collapsed > 0) {
504 m->m_collapsed = 0;
505 ++(*uc);
509 if (m->m_child != NULL) {
510 m = m->m_child;
511 _colpm(m, cl, cc, uc);
512 for (m = m->m_younger; m != NULL; m = m->m_younger)
513 _colpm(m, cl, cc, uc);
515 NYD2_LEAVE;
518 FL int
519 c_thread(void *vp)
521 int rv;
522 NYD_ENTER;
524 if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
525 #ifdef HAVE_IMAP
526 if (mb.mb_type == MB_IMAP)
527 imap_getheaders(1, msgCount);
528 #endif
529 _makethreads(message, msgCount, (vp == (void*)-1));
530 if (mb.mb_sorted != NULL)
531 free(mb.mb_sorted);
532 mb.mb_sorted = sstrdup("thread");
535 if (vp != NULL && vp != (void*)-1 && !(pstate & PS_HOOK_MASK) &&
536 ok_blook(header))
537 rv = print_header_group(vp);
538 else
539 rv = 0;
540 NYD_LEAVE;
541 return rv;
544 FL int
545 c_unthread(void *vp)
547 struct message *m;
548 int rv;
549 NYD_ENTER;
551 mb.mb_threaded = 0;
552 if (mb.mb_sorted != NULL)
553 free(mb.mb_sorted);
554 mb.mb_sorted = NULL;
556 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
557 m->m_collapsed = 0;
559 if (vp && !(pstate & PS_HOOK_MASK) && ok_blook(header))
560 rv = print_header_group(vp);
561 else
562 rv = 0;
563 NYD_LEAVE;
564 return rv;
567 FL struct message *
568 next_in_thread(struct message *mp)
570 struct message *rv;
571 NYD2_ENTER;
573 if ((rv = mp->m_child) != NULL)
574 goto jleave;
575 if ((rv = mp->m_younger) != NULL)
576 goto jleave;
578 while ((rv = mp->m_parent) != NULL) {
579 mp = rv;
580 if ((rv = rv->m_younger) != NULL)
581 goto jleave;
583 jleave:
584 NYD2_LEAVE;
585 return rv;
588 FL struct message *
589 prev_in_thread(struct message *mp)
591 struct message *rv;
592 NYD2_ENTER;
594 if ((rv = mp->m_elder) != NULL) {
595 for (mp = rv; (rv = mp->m_child) != NULL;) {
596 mp = rv;
597 while ((rv = mp->m_younger) != NULL)
598 mp = rv;
600 rv = mp;
601 goto jleave;
603 rv = mp->m_parent;
604 jleave:
605 NYD2_LEAVE;
606 return rv;
609 FL struct message *
610 this_in_thread(struct message *mp, long n)
612 struct message *rv;
613 NYD2_ENTER;
615 if (n == -1) { /* find end of thread */
616 while (mp != NULL) {
617 if ((rv = mp->m_younger) != NULL) {
618 mp = rv;
619 continue;
621 rv = next_in_thread(mp);
622 if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
623 rv = mp;
624 goto jleave;
626 mp = rv;
628 rv = mp;
629 goto jleave;
632 while (mp != NULL && mp->m_threadpos < n) {
633 if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
634 mp = rv;
635 continue;
637 mp = next_in_thread(mp);
639 rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
640 jleave:
641 NYD2_LEAVE;
642 return rv;
645 FL int
646 c_sort(void *vp)
648 enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
649 SORT_TO, SORT_SPAM, SORT_THREAD} method;
650 struct {
651 char const *me_name;
652 enum method me_method;
653 int (*me_func)(void const *, void const *);
654 } const methnames[] = {
655 {"date", SORT_DATE, &_mlonglt},
656 {"from", SORT_FROM, &_mcharlt},
657 {"to", SORT_TO, &_mcharlt},
658 {"subject", SORT_SUBJECT, &_mcharlt},
659 {"size", SORT_SIZE, &_mlonglt},
660 #ifdef HAVE_SPAM
661 {"spam", SORT_SPAM, &_mui32lt},
662 #endif
663 {"status", SORT_STATUS, &_mlonglt},
664 {"thread", SORT_THREAD, NULL}
667 struct str in, out;
668 char *_args[2], *cp, **args = vp;
669 int msgvec[2], i, n;
670 int (*func)(void const *, void const *);
671 struct msort *ms;
672 struct message *mp;
673 bool_t showname;
674 NYD_ENTER;
676 if (vp == NULL || vp == (void*)-1) {
677 _args[0] = savestr((mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
678 _args[1] = NULL;
679 args = _args;
680 } else if (args[0] == NULL) {
681 printf("Current sorting criterion is: %s\n",
682 (mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
683 i = 0;
684 goto jleave;
687 i = 0;
688 for (;;) {
689 if (*args[0] != '\0' && is_prefix(args[0], methnames[i].me_name))
690 break;
691 if (UICMP(z, ++i, >=, NELEM(methnames))) {
692 n_err(_("Unknown sorting method \"%s\"\n"), args[0]);
693 i = 1;
694 goto jleave;
698 if (mb.mb_sorted != NULL)
699 free(mb.mb_sorted);
700 mb.mb_sorted = sstrdup(args[0]);
702 method = methnames[i].me_method;
703 func = methnames[i].me_func;
704 msgvec[0] = (int)PTR2SIZE(dot - message + 1);
705 msgvec[1] = 0;
707 if (method == SORT_THREAD) {
708 i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
709 goto jleave;
712 showname = ok_blook(showname);
713 ms = ac_alloc(sizeof *ms * msgCount);
714 #ifdef HAVE_IMAP
715 switch (method) {
716 case SORT_SUBJECT:
717 case SORT_DATE:
718 case SORT_FROM:
719 case SORT_TO:
720 if (mb.mb_type == MB_IMAP)
721 imap_getheaders(1, msgCount);
722 break;
723 default:
724 break;
726 #endif
728 srelax_hold();
729 for (n = 0, i = 0; i < msgCount; ++i) {
730 mp = message + i;
731 if (!(mp->m_flag & MHIDDEN)) {
732 switch (method) {
733 case SORT_DATE:
734 if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
735 mp->m_date = rfctime(cp);
736 ms[n].ms_u.ms_long = mp->m_date;
737 break;
738 case SORT_STATUS:
739 if (mp->m_flag & MDELETED)
740 ms[n].ms_u.ms_long = 1;
741 else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
742 ms[n].ms_u.ms_long = 90;
743 else if (mp->m_flag & MFLAGGED)
744 ms[n].ms_u.ms_long = 85;
745 else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
746 ms[n].ms_u.ms_long = 70;
747 else if (mp->m_flag & MNEW)
748 ms[n].ms_u.ms_long = 80;
749 else if (mp->m_flag & MREAD)
750 ms[n].ms_u.ms_long = 40;
751 else
752 ms[n].ms_u.ms_long = 60;
753 break;
754 case SORT_SIZE:
755 ms[n].ms_u.ms_long = mp->m_xsize;
756 break;
757 #ifdef HAVE_SPAM
758 case SORT_SPAM:
759 ms[n].ms_u.ms_ui = mp->m_spamscore;
760 break;
761 #endif
762 case SORT_FROM:
763 case SORT_TO:
764 if ((cp = hfield1((method == SORT_FROM ? "from" : "to"), mp)) !=
765 NULL) {
766 ms[n].ms_u.ms_char = sstrdup(showname ? realname(cp) : skin(cp));
767 makelow(ms[n].ms_u.ms_char);
768 } else
769 ms[n].ms_u.ms_char = sstrdup("");
770 break;
771 default:
772 case SORT_SUBJECT:
773 if ((cp = hfield1("subject", mp)) != NULL) {
774 in.s = cp;
775 in.l = strlen(in.s);
776 mime_fromhdr(&in, &out, TD_ICONV);
777 ms[n].ms_u.ms_char = sstrdup(subject_re_trim(out.s));
778 free(out.s);
779 makelow(ms[n].ms_u.ms_char);
780 } else
781 ms[n].ms_u.ms_char = sstrdup("");
782 break;
784 ms[n++].ms_n = i;
786 mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
787 mp->m_level = 0;
788 mp->m_collapsed = 0;
789 srelax();
791 srelax_rele();
793 if (n > 0) {
794 qsort(ms, n, sizeof *ms, func);
795 threadroot = message + ms[0].ms_n;
796 for (i = 1; i < n; ++i) {
797 message[ms[i - 1].ms_n].m_younger = message + ms[i].ms_n;
798 message[ms[i].ms_n].m_elder = message + ms[i - 1].ms_n;
800 } else
801 threadroot = NULL;
803 _finalize(threadroot);
804 mb.mb_threaded = 2;
806 switch (method) {
807 case SORT_FROM:
808 case SORT_TO:
809 case SORT_SUBJECT:
810 for (i = 0; i < n; ++i)
811 free(ms[i].ms_u.ms_char);
812 /* FALLTHRU */
813 default:
814 break;
816 ac_free(ms);
818 i = ((vp != NULL && vp != (void*)-1 && !(pstate & PS_HOOK_MASK) &&
819 ok_blook(header)) ? print_header_group(msgvec) : 0);
820 jleave:
821 NYD_LEAVE;
822 return i;
825 FL int
826 c_collapse(void *v)
828 int rv;
829 NYD_ENTER;
831 rv = _colpt(v, 1);
832 NYD_LEAVE;
833 return rv;
836 FL int
837 c_uncollapse(void *v)
839 int rv;
840 NYD_ENTER;
842 rv = _colpt(v, 0);
843 NYD_LEAVE;
844 return rv;
847 FL void
848 uncollapse1(struct message *mp, int always)
850 NYD_ENTER;
851 if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
852 _colps(mp, 0);
853 NYD_LEAVE;
856 /* s-it-mode */