#ifdef THREAD_SUPPORT
#include <sys/cdefs.h>
#ifndef __lint__
__RCSID("$NetBSD: thread.c,v 1.17 2026/06/25 22:27:52 kre Exp $");
#endif
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <util.h>
#include "def.h"
#include "glob.h"
#include "extern.h"
#include "format.h"
#include "thread.h"
struct thread_s {
struct message *t_head;
struct message **t_msgtbl;
int t_msgCount;
};
#define THREAD_INIT {NULL, NULL, 0}
typedef int state_t;
#define S_STATE_INIT 0
#define S_EXPOSE 1
#define S_RESTRICT 2
#define S_IS_EXPOSE(a) ((a) & S_EXPOSE)
#define S_IS_RESTRICT(a) ((a) & S_RESTRICT)
static struct thread_s message_array = THREAD_INIT;
static struct thread_s current_thread = THREAD_INIT;
static state_t state = S_STATE_INIT;
PUBLIC int
thread_hidden(void)
{
return !S_IS_EXPOSE(state);
}
#ifdef THREAD_DEBUG
static void
show_msg(struct message *mp)
{
if (mp == NULL)
return;
(void)printf("%3d (%p):"
" flink=%p blink=%p clink=%p plink=%p"
" depth=%d flags=0x%03x\n",
mp->m_index, mp,
mp->m_flink, mp->m_blink, mp->m_clink, mp->m_plink,
mp->m_depth, mp->m_flag);
}
#ifndef __lint__
__unused
static void
show_thread(struct message *mp)
{
(void)printf("current_thread.t_head=%p\n", current_thread.t_head);
for (; mp; mp = next_message(mp))
show_msg(mp);
}
#endif
PUBLIC int
thread_showcmd(void *v)
{
int *ip;
(void)printf("current_thread.t_head=%p\n", current_thread.t_head);
for (ip = v; *ip; ip++)
show_msg(get_message(*ip));
return 0;
}
#endif
static int
is_tagged_core(struct message *mp)
{
if (S_IS_EXPOSE(state))
return 1;
for (; mp; mp = mp->m_flink)
if ((mp->m_flag & MTAGGED) == 0 ||
is_tagged_core(mp->m_clink) == 0)
return 0;
return 1;
}
static int
is_tagged(struct message *mp)
{
return mp->m_flag & MTAGGED && is_tagged_core(mp->m_clink);
}
static int
has_parent(struct message *mp)
{
return mp->m_plink != NULL &&
mp->m_plink->m_clink != current_thread.t_head;
}
static struct message *
next_message1(struct message *mp)
{
if (mp == NULL)
return NULL;
if (S_IS_EXPOSE(state) == 0)
return mp->m_flink;
if (mp->m_clink)
return mp->m_clink;
while (mp->m_flink == NULL && has_parent(mp))
mp = mp->m_plink;
return mp->m_flink;
}
static struct message *
prev_message1(struct message *mp)
{
if (mp == NULL)
return NULL;
if (S_IS_EXPOSE(state) && mp->m_blink == NULL && has_parent(mp))
return mp->m_plink;
return mp->m_blink;
}
PUBLIC struct message *
next_message(struct message *mp)
{
if (S_IS_RESTRICT(state) == 0)
return next_message1(mp);
while ((mp = next_message1(mp)) != NULL && is_tagged(mp))
continue;
return mp;
}
PUBLIC struct message *
prev_message(struct message *mp)
{
if (S_IS_RESTRICT(state) == 0)
return prev_message1(mp);
while ((mp = prev_message1(mp)) != NULL && is_tagged(mp))
continue;
return mp;
}
static struct message *
first_message(struct message *mp)
{
if (S_IS_RESTRICT(state) && is_tagged(mp))
mp = next_message(mp);
return mp;
}
PUBLIC struct message *
get_message(int msgnum)
{
struct message *mp;
if (msgnum < 1 || msgnum > current_thread.t_msgCount)
return NULL;
mp = current_thread.t_msgtbl[msgnum - 1];
assert(mp->m_index == msgnum);
return mp;
}
PUBLIC int
get_msgnum(struct message *mp)
{
return mp ? mp->m_index : 0;
}
PUBLIC int
get_msgCount(void)
{
return current_thread.t_msgCount;
}
PUBLIC int
get_abs_msgCount(void)
{
return message_array.t_msgCount;
}
PUBLIC struct message *
get_abs_message(int msgnum)
{
if (msgnum < 1 || msgnum > message_array.t_msgCount)
return NULL;
return &message_array.t_head[msgnum - 1];
}
PUBLIC struct message *
next_abs_message(struct message *mp)
{
int i;
i = (int)(mp - message_array.t_head);
if (i < 0 || i + 1 >= message_array.t_msgCount)
return NULL;
return &message_array.t_head[i + 1];
}
PUBLIC int
do_recursion(void)
{
return S_IS_EXPOSE(state) == 0 && value(ENAME_RECURSIVE_CMDS) != NULL;
}
static int
thread_recursion_flist(struct message *mp, int (*fn)(struct message *, void *), void *args)
{
int retval;
for (; mp; mp = mp->m_flink) {
if (S_IS_RESTRICT(state) && is_tagged(mp))
continue;
if ((retval = fn(mp, args)) != 0 ||
(retval = thread_recursion_flist(mp->m_clink, fn, args)) != 0)
return retval;
}
return 0;
}
PUBLIC int
thread_recursion(struct message *mp, int (*fn)(struct message *, void *), void *args)
{
int retval;
assert(mp != NULL);
if ((retval = fn(mp, args)) != 0)
return retval;
if (do_recursion() &&
(retval = thread_recursion_flist(mp->m_clink, fn, args)) != 0)
return retval;
return 0;
}
PUBLIC int
thread_depth(void)
{
return current_thread.t_head ? current_thread.t_head->m_depth : 0;
}
static int
reindex_core(struct message *mp)
{
int i;
assert(mp->m_blink == NULL);
i = 0;
for (mp = first_message(mp); mp; mp = mp->m_flink) {
assert(mp->m_flink == NULL || mp == mp->m_flink->m_blink);
assert(mp->m_blink == NULL || mp == mp->m_blink->m_flink);
assert(mp->m_size != 0);
if (S_IS_RESTRICT(state) == 0 || !is_tagged(mp))
mp->m_index = ++i;
if (mp->m_clink)
(void)reindex_core(mp->m_clink);
}
return i;
}
static void
reindex(struct thread_s *tp)
{
struct message *mp;
int i;
assert(tp != NULL);
if ((mp = tp->t_head) == NULL || mp->m_size == 0)
return;
assert(mp->m_blink == NULL);
if (S_IS_EXPOSE(state) == 0) {
i = reindex_core(tp->t_head);
}
else {
i = 0;
for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
mp->m_index = ++i;
}
assert(i <= message_array.t_msgCount);
tp->t_msgCount = i;
i = 0;
for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
tp->t_msgtbl[i++] = mp;
}
static void
redepth_core(struct message *mp, int depth, struct message *parent)
{
assert(mp->m_blink == NULL);
assert((parent == NULL && depth == 0) ||
(parent != NULL && depth != 0 && depth == parent->m_depth + 1));
for (; mp; mp = mp->m_flink) {
assert(mp->m_plink == parent);
assert(mp->m_flink == NULL || mp == mp->m_flink->m_blink);
assert(mp->m_blink == NULL || mp == mp->m_blink->m_flink);
assert(mp->m_size != 0);
mp->m_depth = depth;
if (mp->m_clink)
redepth_core(mp->m_clink, depth + 1, mp);
}
}
static void
redepth(struct thread_s *thread)
{
int depth;
struct message *mp;
assert(thread != NULL);
if ((mp = thread->t_head) == NULL || mp->m_size == 0)
return;
depth = mp->m_plink ? mp->m_plink->m_depth + 1 : 0;
#ifndef NDEBUG
{
struct message *tp;
int i;
i = 0;
for (tp = mp->m_plink; tp; tp = tp->m_plink)
i++;
assert(i == depth);
}
#endif
redepth_core(mp, depth, mp->m_plink);
}
PUBLIC void
thread_save_old_links(struct message *omessage, int omsgCount)
{
int i;
#define SAVE_LINK(u) do {\
u.offset = (ptrdiff_t)( u.link != NULL ? u.link - omessage : -1 ); \
} while (0)
#define SAVE_LINK_PTR(p) do {\
p = (struct message *)(ptrdiff_t)((p) != NULL ? (p) - omessage : -1 ); \
} while (0)
SAVE_LINK_PTR(current_thread.t_head);
for (i = 0; i < omsgCount; i++) {
SAVE_LINK(omessage[i].m_b);
SAVE_LINK(omessage[i].m_f);
SAVE_LINK(omessage[i].m_c);
SAVE_LINK(omessage[i].m_p);
}
for (i = 0; i < current_thread.t_msgCount; i++)
SAVE_LINK_PTR(current_thread.t_msgtbl[i]);
#undef SAVE_LINK
#undef SAVE_LINK_PTR
}
PUBLIC void
thread_fix_old_links(struct message *nmessage, int omsgCount)
{
int i;
#ifndef NDEBUG
message_array.t_head = nmessage;
#endif
# define UPD_LINK(u) do {\
u.link = u.offset == -1 ? NULL : nmessage + u.offset;\
} while (0);
# define UPD_PTR(p) do {\
p = (ptrdiff_t)(p) == -1 ? NULL : nmessage + (ptrdiff_t)(p);\
} while (0);
UPD_PTR(current_thread.t_head);
for (i = 0; i < omsgCount; i++) {
UPD_LINK(nmessage[i].m_b);
UPD_LINK(nmessage[i].m_f);
UPD_LINK(nmessage[i].m_c);
UPD_LINK(nmessage[i].m_p);
}
for (i = 0; i < current_thread.t_msgCount; i++)
UPD_PTR(current_thread.t_msgtbl[i]);
# undef UPD_LINK
# undef UPD_PTR
}
static void
thread_init(struct thread_s *tp, struct message *mp, int msgCount)
{
int i;
if (tp->t_msgtbl == NULL || msgCount > tp->t_msgCount) {
if (tp->t_msgtbl)
free(tp->t_msgtbl);
tp->t_msgtbl = ecalloc((size_t)msgCount, sizeof(tp->t_msgtbl[0]));
}
tp->t_head = mp;
tp->t_msgCount = msgCount;
for (i = 0; i < msgCount; i++)
tp->t_msgtbl[i] = &mp[i];
}
PUBLIC void
thread_fix_new_links(struct message *message, int omsgCount, int msgCount)
{
int i;
struct message *lastmp;
assert(omsgCount == 0 || message->m_plink == NULL);
assert(omsgCount == 0 || message_array.t_msgCount == omsgCount);
assert(message_array.t_head == message);
message_array.t_head = message;
message_array.t_msgCount = msgCount;
assert(message_array.t_msgtbl == NULL);
lastmp = NULL;
if (omsgCount) {
for (i = 0; i < omsgCount; i++) {
if (message_array.t_head[i].m_depth == 0 &&
message_array.t_head[i].m_flink == NULL) {
lastmp = &message_array.t_head[i];
break;
}
}
#ifndef NDEBUG
for (i++; i < omsgCount; i++)
assert(message_array.t_head[i].m_depth != 0 ||
message_array.t_head[i].m_flink != NULL);
assert(lastmp != NULL);
#endif
}
for (i = omsgCount; i < msgCount; i++) {
message[i].m_index = i + 1;
message[i].m_depth = 0;
message[i].m_blink = lastmp;
message[i].m_flink = NULL;
message[i].m_clink = NULL;
message[i].m_plink = NULL;
if (lastmp)
lastmp->m_flink = &message[i];
lastmp = &message[i];
}
if (omsgCount == 0) {
thread_init(¤t_thread, message, msgCount);
}
else {
current_thread.t_msgtbl =
erealloc(current_thread.t_msgtbl,
msgCount * sizeof(*current_thread.t_msgtbl));
assert(current_thread.t_head != NULL);
if (current_thread.t_head->m_depth == 0)
reindex(¤t_thread);
}
}
static state_t
set_state(int and_bits, int xor_bits)
{
state_t old_state;
old_state = state;
state &= and_bits;
state ^= xor_bits;
reindex(¤t_thread);
redepth(¤t_thread);
return old_state;
}
static struct message *
first_visible_message(struct message *mp)
{
struct message *oldmp;
if (mp == NULL)
mp = current_thread.t_head;
if (mp == NULL)
return NULL;
oldmp = mp;
if ((S_IS_RESTRICT(state) && is_tagged(mp)) || mp->m_flag & MDELETED)
mp = next_message(mp);
if (mp == NULL) {
mp = oldmp;
if ((S_IS_RESTRICT(state) && is_tagged(mp)) || mp->m_flag & MDELETED)
mp = prev_message(mp);
}
if (mp == NULL)
mp = current_thread.t_head;
return mp;
}
static void
restore_state(state_t new_state)
{
state = new_state;
reindex(¤t_thread);
redepth(¤t_thread);
dot = first_visible_message(dot);
}
static struct message *
thread_top(struct message *mp)
{
while (mp && mp->m_plink) {
if (mp->m_plink->m_clink == current_thread.t_head)
break;
mp = mp->m_plink;
}
return mp;
}
static void
thread_announce(void *v)
{
int vec[2];
if (v == NULL)
return;
if (dot == NULL) {
(void)printf("No applicable messages\n");
return;
}
vec[0] = get_msgnum(dot);
vec[1] = 0;
if (get_msgCount() > 0 && value(ENAME_NOHEADER) == NULL)
(void)headers(vec);
sawcom = 0;
}
static void
flattencmd_core(struct message *mp)
{
struct message **marray;
size_t mcount;
struct message *tp;
struct message *nextmp;
size_t i;
if (mp == NULL)
return;
mcount = 1;
for (tp = next_message(mp); tp && tp->m_depth > mp->m_depth; tp = next_message(tp))
mcount++;
if (tp && tp->m_depth < mp->m_depth)
nextmp = NULL;
else
nextmp = tp;
if (mcount == 1)
return;
marray = csalloc(mcount, sizeof(*marray));
tp = mp;
for (i = 0; i < mcount; i++) {
marray[i] = tp;
tp = next_message(tp);
}
mp->m_clink = NULL;
for (i = 1; i < mcount; i++) {
marray[i]->m_depth = mp->m_depth;
marray[i]->m_plink = mp->m_plink;
marray[i]->m_clink = NULL;
marray[i]->m_blink = marray[i - 1];
marray[i - 1]->m_flink = marray[i];
}
marray[i - 1]->m_flink = nextmp;
if (nextmp)
nextmp->m_blink = marray[i - 1];
}
PUBLIC int
flattencmd(void *v)
{
int *msgvec;
int *ip;
msgvec = v;
if (*msgvec) {
for (ip = msgvec; *ip; ip++) {
struct message *mp;
mp = get_message(*ip);
if (mp != NULL)
flattencmd_core(mp);
}
}
else {
struct message *mp;
for (mp = first_message(current_thread.t_head);
mp; mp = next_message(mp))
flattencmd_core(mp);
}
redepth(¤t_thread);
thread_announce(v);
return 0;
}
struct key_sort_s {
struct message *mp;
union {
char *str;
long lines;
off_t size;
time_t time;
} key;
int index;
};
static struct {
int inv;
int (*fn)(const void *, const void *);
} cmp;
static int
qsort_cmpfn(const void *left, const void *right)
{
int delta;
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
delta = cmp.fn(left, right);
return delta ? cmp.inv ? - delta : delta : lp->index - rp->index;
}
static void
link_array(struct key_sort_s *marray, size_t mcount)
{
size_t i;
struct message *lastmp;
lastmp = NULL;
for (i = 0; i < mcount; i++) {
marray[i].mp->m_index = (int)i + 1;
marray[i].mp->m_blink = lastmp;
marray[i].mp->m_flink = NULL;
if (lastmp)
lastmp->m_flink = marray[i].mp;
lastmp = marray[i].mp;
}
if (current_thread.t_head->m_plink)
current_thread.t_head->m_plink->m_clink = marray[0].mp;
current_thread.t_head = marray[0].mp;
}
static void
cut_array(struct key_sort_s *marray, size_t beg, size_t end)
{
size_t i;
if (beg + 1 < end) {
assert(marray[beg].mp->m_clink == NULL);
marray[beg].mp->m_clink = marray[beg + 1].mp;
marray[beg + 1].mp->m_blink = NULL;
marray[beg].mp->m_flink = marray[end].mp;
if (marray[end].mp)
marray[end].mp->m_blink = marray[beg].mp;
marray[end - 1].mp->m_flink = NULL;
for (i = beg + 1; i < end; i++)
marray[i].mp->m_plink = marray[beg].mp;
}
}
static void
thread_array(struct key_sort_s *marray, size_t mcount, int cutit)
{
struct message *parent;
if (mcount == 0)
return;
parent = marray[0].mp->m_plink;
qsort(marray, mcount, sizeof(*marray), qsort_cmpfn);
link_array(marray, mcount);
if (cutit) {
size_t i, j;
for (i = 0; i < mcount; i++) {
marray[i].mp->m_plink = parent;
marray[i].mp->m_clink = NULL;
}
i = 0;
for (j = 1; j < mcount; j++) {
if (cmp.fn(&marray[i], &marray[j]) != 0) {
cut_array(marray, i, j);
i = j;
}
}
cut_array(marray, i, j);
}
}
static void
adopt_child(struct message *parent, struct message *child)
{
if (child->m_blink != NULL) {
child->m_blink->m_flink = child->m_flink;
}
if (child->m_flink != NULL) {
child->m_flink->m_blink = child->m_blink;
}
if (parent->m_clink == NULL) {
parent->m_clink = child;
child->m_blink = NULL;
}
else {
struct message *t;
for (t = parent->m_clink; t && t->m_flink; t = t->m_flink)
continue;
t->m_flink = child;
child->m_blink = t;
}
child->m_flink = NULL;
child->m_plink = parent;
}
static char *
get_parent_id(struct message *mp)
{
struct name *refs;
if ((refs = extract(hfield("references", mp), 0)) != NULL) {
char *id;
while (refs->n_flink)
refs = refs->n_flink;
id = skin(refs->n_name);
if (*id != '\0')
return id;
}
return skin(hfield("in-reply-to", mp));
}
static void
thread_on_reference(struct message *mp)
{
struct {
struct message *mp;
char *message_id;
char *parent_id;
} *marray;
struct message *parent;
state_t oldstate;
size_t mcount, i;
assert(mp == current_thread.t_head);
oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE);
mcount = get_msgCount();
if (mcount < 2)
goto done;
marray = csalloc(mcount + 1, sizeof(*marray));
for (i = 0; i < mcount; i++) {
marray[i].mp = mp;
marray[i].message_id = skin(hfield("message-id", mp));
marray[i].parent_id = get_parent_id(mp);
mp = next_message(mp);
}
parent = marray[0].mp->m_plink;
marray[0].mp->m_clink = NULL;
for (i = 1; i < mcount; i++) {
marray[i].mp->m_depth = marray[0].mp->m_depth;
marray[i].mp->m_plink = marray[0].mp->m_plink;
marray[i].mp->m_clink = NULL;
marray[i].mp->m_blink = marray[i - 1].mp;
marray[i - 1].mp->m_flink = marray[i].mp;
}
marray[i - 1].mp->m_flink = NULL;
for (i = 0; i < mcount; i++) {
struct message *child;
char *parent_id;
size_t j;
if ((parent_id = marray[i].parent_id) == NULL)
continue;
child = marray[i].mp;
for (j = 0; j < mcount; j++) {
if (marray[j].message_id == NULL)
continue;
if (equal(marray[j].message_id, parent_id)) {
if (current_thread.t_head == child) {
current_thread.t_head = child->m_flink;
assert(current_thread.t_head != NULL);
}
adopt_child(marray[j].mp, child);
break;
}
}
}
if (parent)
parent->m_clink = current_thread.t_head;
if (!S_IS_EXPOSE(oldstate))
dot = thread_top(dot);
done:
restore_state(oldstate);
}
static int
tag1(int *msgvec, int and_bits, int xor_bits)
{
int *ip;
for (ip = msgvec; *ip != 0; ip++)
(void)set_m_flag(*ip, and_bits, xor_bits);
reindex(¤t_thread);
return 0;
}
PUBLIC int
tagcmd(void *v)
{
return tag1(v, ~MTAGGED, MTAGGED);
}
PUBLIC int
untagcmd(void *v)
{
return tag1(v, ~MTAGGED, 0);
}
PUBLIC int
invtagscmd(void *v)
{
return tag1(v, ~0, MTAGGED);
}
PUBLIC int
tagbelowcmd(void *v)
{
int *msgvec;
struct message *mp;
state_t oldstate;
int depth;
msgvec = v;
oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE);
mp = get_message(*msgvec);
if (mp) {
depth = mp->m_depth;
for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp))
if (mp->m_depth > depth) {
mp->m_flag |= MTAGGED;
touch(mp);
}
}
restore_state(oldstate);
return 0;
}
PUBLIC int
hidetagscmd(void *v)
{
(void)set_state(~S_RESTRICT, S_RESTRICT);
dot = first_visible_message(dot);
thread_announce(v);
return 0;
}
PUBLIC int
showtagscmd(void *v)
{
(void)set_state(~S_RESTRICT, 0);
dot = first_visible_message(dot);
thread_announce(v);
return 0;
}
PUBLIC int
exposecmd(void *v)
{
(void)set_state(~S_EXPOSE, S_EXPOSE);
dot = first_visible_message(dot);
thread_announce(v);
return 0;
}
PUBLIC int
hidecmd(void *v)
{
dot = thread_top(dot);
(void)set_state(~S_EXPOSE, 0);
dot = first_visible_message(dot);
thread_announce(v);
return 0;
}
PUBLIC int
upcmd(void *v)
{
char *str;
int upcnt;
int upone;
str = v;
str = skip_WSP(str);
if (*str == '\0')
upcnt = 1;
else
upcnt = atoi(str);
if (upcnt < 1) {
(void)printf("Sorry, argument must be > 0.\n");
return 0;
}
if (dot == NULL) {
(void)printf("No applicable messages\n");
return 0;
}
if (dot->m_plink == NULL) {
(void)printf("top thread\n");
return 0;
}
upone = 0;
while (upcnt-- > 0) {
struct message *parent;
parent = current_thread.t_head->m_plink;
if (parent == NULL) {
(void)printf("top thread\n");
break;
}
else {
struct message *mp;
assert(current_thread.t_head->m_depth > 0);
for (mp = parent; mp && mp->m_blink; mp = mp->m_blink)
continue;
current_thread.t_head = mp;
dot = parent;
upone = 1;
}
}
if (upone) {
reindex(¤t_thread);
thread_announce(v);
}
return 0;
}
PUBLIC int
downcmd(void *v)
{
struct message *child;
struct message *mp;
int *msgvec = v;
if ((mp = get_message(*msgvec)) == NULL ||
(child = mp->m_clink) == NULL)
(void)printf("no sub-thread\n");
else {
current_thread.t_head = child;
dot = child;
reindex(¤t_thread);
thread_announce(v);
}
return 0;
}
PUBLIC int
tsetcmd(void *v)
{
struct message *mp;
int *msgvec = v;
if ((mp = get_message(*msgvec)) == NULL)
(void)printf("invalid message\n");
else {
for (; mp->m_blink; mp = mp->m_blink)
continue;
current_thread.t_head = mp;
reindex(¤t_thread);
thread_announce(v);
}
return 0;
}
static void
reversecmd_core(struct thread_s *tp)
{
struct message *thread_start;
struct message *mp;
struct message *lastmp;
struct message *old_flink;
thread_start = tp->t_head;
assert(thread_start->m_blink == NULL);
lastmp = NULL;
for (mp = thread_start; mp; mp = old_flink) {
old_flink = mp->m_flink;
mp->m_flink = mp->m_blink;
mp->m_blink = old_flink;
lastmp = mp;
}
if (thread_start->m_plink)
thread_start->m_plink->m_clink = lastmp;
current_thread.t_head = lastmp;
reindex(tp);
}
PUBLIC int
reversecmd(void *v)
{
reversecmd_core(¤t_thread);
thread_announce(v);
return 0;
}
#define MF_IGNCASE 1
#define MF_REVERSE 2
#define MF_SKIN 4
static int
get_modifiers(char **str)
{
int modflags;
char *p;
modflags = 0;
for (p = *str; p && *p; p++) {
switch (*p) {
case '!':
modflags |= MF_REVERSE;
break;
case '^':
modflags |= MF_IGNCASE;
break;
case '-':
modflags |= MF_SKIN;
break;
case ' ':
case '\t':
break;
default:
goto done;
}
}
done:
*str = p;
return modflags;
}
static int
keystrcmp(const void *left, const void *right)
{
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
lp = left;
rp = right;
if (rp->key.str == NULL && lp->key.str == NULL)
return 0;
else if (rp->key.str == NULL)
return -1;
else if (lp->key.str == NULL)
return 1;
else
return strcmp(lp->key.str, rp->key.str);
}
static int
keystrcasecmp(const void *left, const void *right)
{
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
if (rp->key.str == NULL && lp->key.str == NULL)
return 0;
else if (rp->key.str == NULL)
return -1;
else if (lp->key.str == NULL)
return 1;
else
return strcasecmp(lp->key.str, rp->key.str);
}
static int
keylongcmp(const void *left, const void *right)
{
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
if (lp->key.lines > rp->key.lines)
return 1;
if (lp->key.lines < rp->key.lines)
return -1;
return 0;
}
static int
keyoffcmp(const void *left, const void *right)
{
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
if (lp->key.size > rp->key.size)
return 1;
if (lp->key.size < rp->key.size)
return -1;
return 0;
}
static int
keytimecmp(const void *left, const void *right)
{
double delta;
const struct key_sort_s *lp = left;
const struct key_sort_s *rp = right;
delta = difftime(lp->key.time, rp->key.time);
if (delta > 0)
return 1;
if (delta < 0)
return -1;
return 0;
}
static void
field_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key, int skin_it)
{
size_t i;
for (i = 0; i < mcount; i++) {
marray[i].mp = mp;
marray[i].key.str =
skin_it ? skin(hfield(key, mp)) : hfield(key, mp);
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static void
subj_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key __unused, int flags __unused)
{
size_t i;
#ifdef __lint__
flags = flags;
key = key;
#endif
for (i = 0; i < mcount; i++) {
char *subj = hfield(key, mp);
while (strncasecmp(subj, "Re:", 3) == 0)
subj = skip_WSP(subj + 3);
marray[i].mp = mp;
marray[i].key.str = subj;
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static void
lines_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key __unused, int flags)
{
size_t i;
int use_blines;
int use_hlines;
#ifdef __lint__
key = key;
#endif
#define HLINES 1
#define BLINES 2
#define TLINES 3
use_hlines = flags == HLINES;
use_blines = flags == BLINES;
for (i = 0; i < mcount; i++) {
marray[i].mp = mp;
marray[i].key.lines = use_hlines ? mp->m_lines - mp->m_blines :
use_blines ? mp->m_blines : mp->m_lines;
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static void
size_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key __unused, int flags __unused)
{
size_t i;
#ifdef __lint__
flags = flags;
key = key;
#endif
for (i = 0; i < mcount; i++) {
marray[i].mp = mp;
marray[i].key.size = mp->m_size;
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static void __unused
date_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key __unused, int flags)
{
size_t i;
int use_hl_date;
int zero_hour_min_sec;
#ifdef __lint__
key = key;
#endif
#define RDAY 1
#define SDAY 2
#define RDATE 3
#define SDATE 4
use_hl_date = (flags == RDAY || flags == RDATE);
zero_hour_min_sec = (flags == RDAY || flags == SDAY);
for (i = 0; i < mcount; i++) {
struct tm tm;
(void)dateof(&tm, mp, use_hl_date);
if (zero_hour_min_sec) {
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 0;
}
marray[i].mp = mp;
marray[i].key.time = mktime(&tm);
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static void
from_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
const char *key __unused, int flags __unused)
{
size_t i;
#ifdef __lint__
flags = flags;
key = key;
#endif
for (i = 0; i < mcount; i++) {
marray[i].mp = mp;
marray[i].key.str = nameof(mp, 0);
marray[i].index = mp->m_index;
mp = next_message(mp);
}
}
static const struct key_tbl_s {
const char *key;
void (*loadfn)(struct key_sort_s *, size_t, struct message *, const char *, int);
int flags;
int (*cmpfn)(const void*, const void*);
int (*casecmpfn)(const void*, const void*);
} key_tbl[] = {
{"blines", lines_load, BLINES, keylongcmp, keylongcmp},
{"hlines", lines_load, HLINES, keylongcmp, keylongcmp},
{"tlines", lines_load, TLINES, keylongcmp, keylongcmp},
{"size", size_load, 0, keyoffcmp, keyoffcmp},
{"sday", date_load, SDAY, keytimecmp, keytimecmp},
{"rday", date_load, RDAY, keytimecmp, keytimecmp},
{"sdate", date_load, SDATE, keytimecmp, keytimecmp},
{"rdate", date_load, RDATE, keytimecmp, keytimecmp},
{"from", from_load, 0, keystrcasecmp, keystrcasecmp},
{"subject", subj_load, 0, keystrcmp, keystrcasecmp},
{NULL, field_load, 0, keystrcmp, keystrcasecmp},
};
#ifdef USE_EDITLINE
PUBLIC const char *
thread_next_key_name(const void **cookie)
{
const struct key_tbl_s *kp;
kp = *cookie;
if (kp == NULL)
kp = key_tbl;
*cookie = kp->key ? &kp[1] : NULL;
return kp->key;
}
#endif
static const struct key_tbl_s *
get_key(const char *key)
{
const struct key_tbl_s *kp;
for (kp = key_tbl; kp->key != NULL; kp++)
if (strcmp(kp->key, key) == 0)
return kp;
return kp;
}
static int (*
get_cmpfn(const struct key_tbl_s *kp, int ignorecase)
)(const void*, const void*)
{
if (ignorecase)
return kp->casecmpfn;
else
return kp->cmpfn;
}
static void
thread_current_on(char *str, int modflags, int cutit)
{
const struct key_tbl_s *kp;
struct key_sort_s *marray;
size_t mcount;
state_t oldstate;
oldstate = set_state(~(S_RESTRICT | S_EXPOSE), cutit ? S_EXPOSE : 0);
kp = get_key(str);
mcount = get_msgCount();
marray = csalloc(mcount + 1, sizeof(*marray));
kp->loadfn(marray, mcount, current_thread.t_head, str,
kp->flags ? kp->flags : modflags & MF_SKIN);
cmp.fn = get_cmpfn(kp, modflags & MF_IGNCASE);
cmp.inv = modflags & MF_REVERSE;
thread_array(marray, mcount, cutit);
if (!S_IS_EXPOSE(oldstate))
dot = thread_top(dot);
restore_state(oldstate);
}
PUBLIC int
threadcmd(void *v)
{
char *str;
str = v;
if (*str == '\0')
thread_on_reference(current_thread.t_head);
else {
int modflags;
modflags = get_modifiers(&str);
thread_current_on(str, modflags, 1);
}
thread_announce(v);
return 0;
}
PUBLIC int
unthreadcmd(void *v)
{
thread_fix_new_links(message_array.t_head, 0, message_array.t_msgCount);
thread_announce(v);
return 0;
}
PUBLIC int
sortcmd(void *v)
{
int modflags;
char *str;
str = v;
modflags = get_modifiers(&str);
if (*str != '\0')
thread_current_on(str, modflags, 0);
else {
if (modflags & MF_REVERSE)
reversecmd_core(¤t_thread);
else {
(void)printf("sort on what?\n");
return 0;
}
}
thread_announce(v);
return 0;
}
PUBLIC int
deldupscmd(void *v __unused)
{
struct message *mp;
int depth;
state_t oldstate;
oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE);
thread_current_on(__UNCONST("Message-Id"), 0, 1);
reindex(¤t_thread);
redepth(¤t_thread);
depth = current_thread.t_head->m_depth;
for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp)) {
if (mp->m_depth > depth) {
mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
mp->m_flag |= MDELETED | MTOUCH;
touch(mp);
}
}
dot = thread_top(dot);
restore_state(oldstate);
return 0;
}
#endif