From 3a8cfd031b8676b4b8bf760eb63fb2f4714c8549 Mon Sep 17 00:00:00 2001 From: Thomas Roessler Date: Wed, 31 Oct 2001 09:20:38 +0000 Subject: [PATCH] Advanced threading, v 5.1. From Daniel Eisenbud . --- commands.c | 10 +- curs_main.c | 52 ++- flags.c | 14 +- hdrline.c | 16 +- imap/message.c | 17 +- init.h | 13 + mbox.c | 25 +- menu.c | 3 + mh.c | 7 +- mutt.h | 28 +- mx.c | 104 ++--- mx.h | 2 +- parse.c | 58 ++- pop.c | 5 +- postpone.c | 2 + protos.h | 6 +- sort.c | 19 +- sort.h | 2 +- thread.c | 1044 ++++++++++++++++++++++++++---------------------- 19 files changed, 825 insertions(+), 602 deletions(-) diff --git a/commands.c b/commands.c index 0880cc4b6..115c3367d 100644 --- a/commands.c +++ b/commands.c @@ -535,8 +535,10 @@ void mutt_enter_command (void) BUFFER err, token; char buffer[LONG_STRING], errbuf[SHORT_STRING]; int r; - int old_strictthreads = option (OPTSTRICTTHREADS); - int old_sortre = option (OPTSORTRE); + int old_strictthreads = option (OPTSTRICTTHREADS); + int old_sortre = option (OPTSORTRE); + int old_hidemissing = option (OPTHIDEMISSING); + int old_threadreceived = option (OPTTHREADRECEIVED); buffer[0] = 0; if (mutt_get_field (":", buffer, sizeof (buffer), M_COMMAND) != 0 || !buffer[0]) @@ -557,7 +559,9 @@ void mutt_enter_command (void) mutt_error ("%s", errbuf); } if (option (OPTSTRICTTHREADS) != old_strictthreads || - option (OPTSORTRE) != old_sortre) + option (OPTSORTRE) != old_sortre || + option (OPTHIDEMISSING) != old_hidemissing || + option (OPTTHREADRECEIVED) != old_threadreceived) set_option (OPTNEEDRESORT); } diff --git a/curs_main.c b/curs_main.c index 01b1e6ea0..32a122110 100644 --- a/curs_main.c +++ b/curs_main.c @@ -107,12 +107,12 @@ void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num) { format_flag flag = M_FORMAT_MAKEPRINT | M_FORMAT_ARROWCURSOR | M_FORMAT_INDEX; int edgemsgno, reverse = Sort & SORT_REVERSE; - HEADER *tmp, *h = Context->hdrs[Context->v2r[num]]; + HEADER *h = Context->hdrs[Context->v2r[num]]; + THREAD *tmp; if ((Sort & SORT_MASK) == SORT_THREADS && h->tree) { flag |= M_FORMAT_TREE; /* display the thread tree */ - if (h->display_subject) flag |= M_FORMAT_FORCESUBJ; else @@ -127,26 +127,32 @@ void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num) else edgemsgno = Context->v2r[menu->top]; - for (tmp = h->parent; tmp; tmp = tmp->parent) + for (tmp = h->thread->parent; tmp; tmp = tmp->parent) { + if (!tmp->message) + continue; + /* if no ancestor is visible on current screen, provisionally force * subject... */ - if (reverse ? tmp->msgno > edgemsgno : tmp->msgno < edgemsgno) + if (reverse ? tmp->message->msgno > edgemsgno : tmp->message->msgno < edgemsgno) { flag |= M_FORMAT_FORCESUBJ; break; } - else if (tmp->virtual >= 0) + else if (tmp->message->virtual >= 0) break; } - if ((flag & M_FORMAT_FORCESUBJ) && h->prev) + if (flag & M_FORMAT_FORCESUBJ) { - for (tmp = h->prev; tmp; tmp = tmp->prev) + for (tmp = h->thread->prev; tmp; tmp = tmp->prev) { + if (!tmp->message) + continue; + /* ...but if a previous sibling is available, don't force it */ - if (reverse ? tmp->msgno > edgemsgno : tmp->msgno < edgemsgno) + if (reverse ? tmp->message->msgno > edgemsgno : tmp->message->msgno < edgemsgno) break; - else if (tmp->virtual >= 0) + else if (tmp->message->virtual >= 0) { flag &= ~M_FORMAT_FORCESUBJ; break; @@ -305,21 +311,21 @@ static void update_index (MUTTMENU *menu, CONTEXT *ctx, int check, /* if the mailbox was reopened, need to rethread from scratch */ mutt_sort_headers (Context, (check == M_REOPENED)); - + /* uncollapse threads with new mail */ if ((Sort & SORT_MASK) == SORT_THREADS) { if (check == M_REOPENED) { - HEADER *h; + THREAD *h, *j; - h = Context->tree; Context->collapsed = 0; - while (h) + for (h = Context->tree; h; h = h->next) { - mutt_uncollapse_thread (Context, h); - h = h->next; + for (j = h; !j->message; j = j->child) + ; + mutt_uncollapse_thread (Context, j->message); } mutt_set_virtual (Context); } @@ -793,7 +799,8 @@ int mutt_index_menu (void) else menu->current = 0; menu->redraw = REDRAW_INDEX | REDRAW_STATUS; - mutt_linearize_tree (Context, 0); + if (Sort & SORT_THREADS) + mutt_linearize_tree (Context, 0); } break; @@ -1537,6 +1544,7 @@ int mutt_index_menu (void) { HEADER *h, *base; + THREAD *thread, *top; int final; if (CURHDR->collapsed) @@ -1548,10 +1556,14 @@ int mutt_index_menu (void) base = Context->hdrs[Context->v2r[final]]; - h = Context->tree; + top = Context->tree; Context->collapsed = !Context->collapsed; - while (h) + while ((thread = top) != NULL) { + while (!thread->message) + thread = thread->child; + h = thread->message; + if (h->collapsed != Context->collapsed) { if (h->collapsed) @@ -1559,7 +1571,7 @@ int mutt_index_menu (void) else if (option (OPTCOLLAPSEUNREAD) || !UNREAD (h)) mutt_collapse_thread (Context, h); } - h = h->next; + top = top->next; } mutt_set_virtual (Context); @@ -1573,8 +1585,8 @@ int mutt_index_menu (void) } menu->redraw = REDRAW_INDEX | REDRAW_STATUS; - break; } + break; /* -------------------------------------------------------------------- * These functions are invoked directly from the internal-pager diff --git a/flags.c b/flags.c index 60ae4c7e2..a231eca0e 100644 --- a/flags.c +++ b/flags.c @@ -240,10 +240,9 @@ void mutt_tag_set_flag (int flag, int bf) if (Context->hdrs[Context->v2r[j]]->tagged) mutt_set_flag (Context, Context->hdrs[Context->v2r[j]], flag, bf); } - -int mutt_thread_set_flag (HEADER *cur, int flag, int bf, int subthread) +int mutt_thread_set_flag (HEADER *hdr, int flag, int bf, int subthread) { - HEADER *start; + THREAD *start, *cur = hdr->thread; if ((Sort & SORT_MASK) != SORT_THREADS) { @@ -256,12 +255,17 @@ int mutt_thread_set_flag (HEADER *cur, int flag, int bf, int subthread) cur = cur->parent; start = cur; - mutt_set_flag (Context, cur, flag, bf); + if (cur->message) + mutt_set_flag (Context, cur->message, flag, bf); + if ((cur = cur->child) == NULL) return (0); + FOREVER { - mutt_set_flag (Context, cur, flag, bf); + if (cur->message) + mutt_set_flag (Context, cur->message, flag, bf); + if (cur->child) cur = cur->child; else if (cur->next) diff --git a/hdrline.c b/hdrline.c index 0717261a0..54fae0fed 100644 --- a/hdrline.c +++ b/hdrline.c @@ -411,12 +411,12 @@ hdr_format_str (char *dest, case 'e': snprintf (fmt, sizeof (fmt), "%%%sd", prefix); - snprintf (dest, destlen, fmt, mutt_msgno_in_thread(hdr) + 1); + snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr, 1)); break; case 'E': snprintf (fmt, sizeof (fmt), "%%%sd", prefix); - snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr)); + snprintf (dest, destlen, fmt, mutt_messages_in_thread(hdr, 0)); break; case 'f': @@ -653,11 +653,13 @@ hdr_format_str (char *dest, i = 1; /* reduce reuse recycle */ htmp = NULL; if (flags & M_FORMAT_TREE - && (hdr->prev && hdr->prev->env->x_label)) - htmp = hdr->prev; + && (hdr->thread->prev && hdr->thread->prev->message + && hdr->thread->prev->message->env->x_label)) + htmp = hdr->thread->prev->message; else if (flags & M_FORMAT_TREE - && (hdr->parent && hdr->parent->env->x_label)) - htmp = hdr->parent; + && (hdr->thread->parent && hdr->thread->parent->message + && hdr->thread->parent->message->env->x_label)) + htmp = hdr->thread->parent->message; if (htmp && mutt_strcasecmp (hdr->env->x_label, htmp->env->x_label) == 0) i = 0; @@ -674,7 +676,7 @@ hdr_format_str (char *dest, mutt_format_s (dest, destlen, prefix, ""); break; - + default: snprintf (dest, destlen, "%%%s%c", prefix, op); break; diff --git a/imap/message.c b/imap/message.c index a77c44d42..62510b0c1 100644 --- a/imap/message.c +++ b/imap/message.c @@ -53,7 +53,7 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend) char tempfile[_POSIX_PATH_MAX]; int msgno; IMAP_HEADER h; - int rc, mfhrc; + int rc, mfhrc, oldmsgcount; int fetchlast = 0; const char *want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO LINES X-LABEL"; @@ -120,6 +120,8 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend) memset (&h, 0, sizeof (h)); h.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA)); + oldmsgcount = ctx->msgcount; + /* this DO loop does two things: * 1. handles untagged messages, so we can try again on the same msg * 2. fetches the tagged response at the end of the last message. @@ -167,11 +169,14 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend) /* content built as a side-effect of mutt_read_rfc822_header */ ctx->hdrs[msgno]->content->length = h.content_length; - mx_update_context (ctx); /* increments ->msgcount */ + ctx->msgcount++; } while ((rc != IMAP_CMD_OK) && ((mfhrc == -1) || ((msgno + 1) >= fetchlast))); + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + if ((mfhrc < -1) || ((rc != IMAP_CMD_CONTINUE) && (rc != IMAP_CMD_OK))) { imap_free_header_data ((void**) &h.data); @@ -343,15 +348,15 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) * mutt_free_envelope gets called, but keep their spots in the hash. This * confuses threading. Alternatively we could try to merge the new * envelope into the old one. Also messy and lowlevel. */ - if (h->env->message_id) + if (ctx->id_hash && h->env->message_id) hash_delete (ctx->id_hash, h->env->message_id, h, NULL); - if (h->env->real_subj) + if (ctx->subj_hash && h->env->real_subj) hash_delete (ctx->subj_hash, h->env->real_subj, h, NULL); mutt_free_envelope (&h->env); h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0); - if (h->env->message_id) + if (ctx->id_hash && h->env->message_id) hash_insert (ctx->id_hash, h->env->message_id, h, 0); - if (h->env->real_subj) + if (ctx->subj_hash && h->env->real_subj) hash_insert (ctx->subj_hash, h->env->real_subj, h, 1); /* see above. We want the new status in h->read, so we unset it manually diff --git a/init.h b/init.h index 96c6f024a..228d27575 100644 --- a/init.h +++ b/init.h @@ -640,6 +640,13 @@ struct option_t MuttVars[] = { ** affect the generation of Message-IDs, and it will not lead to the ** cut-off of first-level domains. */ + { "hide_missing", DT_BOOL, R_NONE, OPTHIDEMISSING, 1 }, + /* + ** .pp + ** When set, mutt will not indicate the presence of missing messages + ** whose ancestors neither have siblings nor are in the current mailbox, + ** in the thread tree. + */ { "history", DT_NUM, R_NONE, UL &HistSize, 10 }, /* ** .pp @@ -2160,6 +2167,12 @@ struct option_t MuttVars[] = { ** .pp ** Note that $$indent_string is ignored when this option is set. */ + { "thread_received", DT_BOOL, R_NONE, OPTTHREADRECEIVED, 0 }, + /* + ** .pp + ** When set, mutt uses the date received rather than the date sent + ** to thread messages by subject. + */ { "thorough_search", DT_BOOL, R_NONE, OPTTHOROUGHSRC, 0 }, /* ** .pp diff --git a/mbox.c b/mbox.c index 4a00d761a..9c72c3246 100644 --- a/mbox.c +++ b/mbox.c @@ -82,7 +82,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx) { char buf[HUGE_STRING]; char return_path[LONG_STRING]; - int count = 0; + int count = 0, oldmsgcount = ctx->msgcount; int lines; time_t t, tz; long loc, tmploc; @@ -138,6 +138,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx) if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) { + /* TODO: memory leak??? */ dprint (1, (debugfile, "mmdf_parse_mailbox: unexpected EOF\n")); break; } @@ -201,7 +202,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx) if (!hdr->env->from) hdr->env->from = rfc822_cpy_adr (hdr->env->return_path); - mx_update_context (ctx); + ctx->msgcount++; if(ctx->magic == M_KENDRA && feof(ctx->fp)) break; } @@ -213,7 +214,10 @@ int mmdf_parse_mailbox (CONTEXT *ctx) } } - return 0; + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + + return (0); } /* Note that this function is also called when new mail is appended to the @@ -359,7 +363,7 @@ int mbox_parse_mailbox (CONTEXT *ctx) } } - mx_update_context (ctx); + ctx->msgcount++; if (!curhdr->env->return_path && return_path[0]) curhdr->env->return_path = rfc822_parse_adrlist (curhdr->env->return_path, return_path); @@ -392,6 +396,8 @@ int mbox_parse_mailbox (CONTEXT *ctx) if (!PREV->lines) PREV->lines = lines ? lines - 1 : 0; + + mx_update_context (ctx, count); } return (0); @@ -1074,8 +1080,11 @@ int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint) old_msgcount = 0; /* simulate a close */ - hash_destroy (&ctx->id_hash, NULL); - hash_destroy (&ctx->subj_hash, NULL); + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + mutt_clear_threads (ctx); safe_free ((void **) &ctx->v2r); if (ctx->readonly) { @@ -1100,8 +1109,8 @@ int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint) ctx->unread = 0; ctx->flagged = 0; ctx->changed = 0; - ctx->id_hash = hash_create (1031); - ctx->subj_hash = hash_create (1031); + ctx->id_hash = NULL; + ctx->subj_hash = NULL; switch (ctx->magic) { diff --git a/menu.c b/menu.c index bccf2c285..7bea43ca7 100644 --- a/menu.c +++ b/menu.c @@ -100,6 +100,9 @@ static void print_enriched_string (int attr, unsigned char *s, int do_color) case M_TREE_HIDDEN: addch ('&'); break; + case M_TREE_MISSING: + addch ('?'); + break; } s++, n--; } diff --git a/mh.c b/mh.c index b65aa8311..e6983d7ad 100644 --- a/mh.c +++ b/mh.c @@ -668,6 +668,8 @@ static int maildir_parse_dir(CONTEXT *ctx, struct maildir ***last, static void maildir_add_to_context(CONTEXT *ctx, struct maildir *md) { + int oldmsgcount = ctx->msgcount; + while(md) { @@ -692,10 +694,13 @@ static void maildir_add_to_context(CONTEXT *ctx, struct maildir *md) md->h->content->length + md->h->content->offset - md->h->content->hdr_offset; md->h = NULL; - mx_update_context(ctx); + ctx->msgcount++; } md = md->next; } + + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); } static void maildir_move_to_context(CONTEXT *ctx, struct maildir **md) diff --git a/mutt.h b/mutt.h index 40d365209..6b2821215 100644 --- a/mutt.h +++ b/mutt.h @@ -154,7 +154,8 @@ typedef enum #define M_TREE_RARROW 7 #define M_TREE_STAR 8 #define M_TREE_HIDDEN 9 -#define M_TREE_MAX 10 +#define M_TREE_MISSING 10 +#define M_TREE_MAX 11 #define M_THREAD_COLLAPSE (1<<0) #define M_THREAD_UNCOLLAPSE (1<<1) @@ -329,6 +330,7 @@ enum OPTHEADER, OPTHELP, OPTHIDDENHOST, + OPTHIDEMISSING, OPTIGNORELISTREPLYTO, #ifdef USE_IMAP OPTIMAPLSUB, @@ -385,6 +387,7 @@ enum OPTSUSPEND, OPTTEXTFLOWED, OPTTHOROUGHSRC, + OPTTHREADRECEIVED, OPTTILDE, OPTUNCOLLAPSEJUMP, OPTUSE8BITMIME, @@ -617,8 +620,6 @@ typedef struct header unsigned int replied : 1; unsigned int subject_changed : 1; /* used for threading */ unsigned int display_subject : 1; /* used for threading */ - unsigned int fake_thread : 1; /* no ref matched, but subject did */ - unsigned int threaded : 1; /* message has been threaded */ unsigned int recip_valid : 1; /* is_recipient is valid */ unsigned int active : 1; /* message is not to be removed */ @@ -652,13 +653,8 @@ typedef struct header BODY *content; /* list of MIME parts */ char *path; - /* the following are used for threading support */ - struct header *parent; - struct header *child; /* decendants of this message */ - struct header *next; /* next message in this thread */ - struct header *prev; /* previous message in thread */ - struct header *last_sort; /* last message in subthread, for secondary SORT_LAST */ char *tree; /* character string to print thread tree */ + struct thread *thread; #ifdef MIXMASTER LIST *chain; @@ -673,6 +669,17 @@ typedef struct header #endif } HEADER; +typedef struct thread +{ + unsigned int fake_thread : 1; + struct thread *parent; + struct thread *child; + struct thread *next; + struct thread *prev; + HEADER *message; + HEADER *sort_key; +} THREAD; + #include "mutt_regex.h" /* flag to mutt_pattern_comp() */ @@ -705,9 +712,10 @@ typedef struct char *pattern; /* limit pattern string */ pattern_t *limit_pattern; /* compiled limit pattern */ HEADER **hdrs; - HEADER *tree; /* top of thread tree */ + THREAD *tree; /* top of thread tree */ HASH *id_hash; /* hash table by msg id */ HASH *subj_hash; /* hash table by subject */ + HASH *thread_hash; /* hash table for threading */ int *v2r; /* mapping from virtual to real msgno */ int hdrmax; /* number of pointers in hdrs */ int msgcount; /* number of messages in the mailbox */ diff --git a/mx.c b/mx.c index 16de23ed7..5d394fe18 100644 --- a/mx.c +++ b/mx.c @@ -671,10 +671,6 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) */ set_option (OPTFORCEREFRESH); - /* create hash tables */ - ctx->id_hash = hash_create (1031); - ctx->subj_hash = hash_create (1031); - if (!ctx->quiet) mutt_message (_("Reading %s..."), ctx->path); @@ -754,6 +750,7 @@ void mx_fastclose_mailbox (CONTEXT *ctx) hash_destroy (&ctx->subj_hash, NULL); if (ctx->id_hash) hash_destroy (&ctx->id_hash, NULL); + mutt_clear_threads (ctx); for (i = 0; i < ctx->msgcount; i++) mutt_free_header (&ctx->hdrs[i]); safe_free ((void **) &ctx->hdrs); @@ -1090,9 +1087,9 @@ void mx_update_tables(CONTEXT *ctx, int committing) ctx->hdrs[i]->content->offset - ctx->hdrs[i]->content->hdr_offset); /* remove message from the hash tables */ - if (ctx->hdrs[i]->env->real_subj) + if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj) hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL); - if (ctx->hdrs[i]->env->message_id) + if (ctx->id_hash && ctx->hdrs[i]->env->message_id) hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL); mutt_free_header (&ctx->hdrs[i]); } @@ -1552,60 +1549,69 @@ void mx_alloc_memory (CONTEXT *ctx) /* this routine is called to update the counts in the context structure for * the last message header parsed. */ -void mx_update_context (CONTEXT *ctx) +void mx_update_context (CONTEXT *ctx, int new_messages) { - HEADER *h = ctx->hdrs[ctx->msgcount]; + HEADER *h; + int msgno; + for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) + { + h = ctx->hdrs[msgno]; #ifdef HAVE_PGP - /* NOTE: this _must_ be done before the check for mailcap! */ - h->pgp = pgp_query (h->content); + /* NOTE: this _must_ be done before the check for mailcap! */ + h->pgp = pgp_query (h->content); #endif /* HAVE_PGP */ - if (!ctx->pattern) - { - ctx->v2r[ctx->vcount] = ctx->msgcount; - h->virtual = ctx->vcount++; - } - else - h->virtual = -1; - h->msgno = ctx->msgcount; - ctx->msgcount++; - - if (h->env->supersedes) - { - HEADER *h2 = hash_find (ctx->id_hash, h->env->supersedes); - - /* safe_free (&h->env->supersedes); should I ? */ - if (h2) + if (!ctx->pattern) { - h2->superseded = 1; - if (option (OPTSCORE)) - mutt_score_message (ctx, h2, 1); + ctx->v2r[ctx->vcount] = msgno; + h->virtual = ctx->vcount++; } - } + else + h->virtual = -1; + h->msgno = msgno; - /* add this message to the hash tables */ - if (h->env->message_id) - hash_insert (ctx->id_hash, h->env->message_id, h, 0); - if (h->env->real_subj) - hash_insert (ctx->subj_hash, h->env->real_subj, h, 1); + if (h->env->supersedes) + { + HEADER *h2; - if (option (OPTSCORE)) - mutt_score_message (ctx, h, 0); - - if (h->changed) - ctx->changed = 1; - if (h->flagged) - ctx->flagged++; - if (h->deleted) - ctx->deleted++; - if (!h->read) - { - ctx->unread++; - if (!h->old) - ctx->new++; + if (!ctx->id_hash) + ctx->id_hash = mutt_make_id_hash (ctx); + + h2 = hash_find (ctx->id_hash, h->env->supersedes); + + /* safe_free (&h->env->supersedes); should I ? */ + if (h2) + { + h2->superseded = 1; + if (option (OPTSCORE)) + mutt_score_message (ctx, h2, 1); + } + } + + /* add this message to the hash tables */ + if (ctx->id_hash && h->env->message_id) + hash_insert (ctx->id_hash, h->env->message_id, h, 0); + if (ctx->subj_hash && h->env->real_subj) + hash_insert (ctx->subj_hash, h->env->real_subj, h, 1); + + if (option (OPTSCORE)) + mutt_score_message (ctx, h, 0); + + if (h->changed) + ctx->changed = 1; + if (h->flagged) + ctx->flagged++; + if (h->deleted) + ctx->deleted++; + if (!h->read) + { + ctx->unread++; + if (!h->old) + ctx->new++; + } } } diff --git a/mx.h b/mx.h index f1548651c..08c444d76 100644 --- a/mx.h +++ b/mx.h @@ -77,7 +77,7 @@ int mbox_strict_cmp_headers (const HEADER *, const HEADER *); int mutt_reopen_mailbox (CONTEXT *, int *); void mx_alloc_memory (CONTEXT *); -void mx_update_context (CONTEXT *); +void mx_update_context (CONTEXT *, int); void mx_update_tables (CONTEXT *, int); diff --git a/parse.c b/parse.c index fd51f14f8..4aa3173ec 100644 --- a/parse.c +++ b/parse.c @@ -90,23 +90,65 @@ static char *read_rfc822_line (FILE *f, char *line, size_t *linelen) /* not reached */ } -static LIST *mutt_parse_references (char *s) +static LIST *mutt_parse_references (char *s, int in_reply_to) { LIST *t, *lst = NULL; + int m, n = 0; + char *o = NULL, *new, *at; - while ((s = strtok (s, " \t")) != NULL) + while ((s = strtok (s, " \t;")) != NULL) { /* * some mail clients add other garbage besides message-ids, so do a quick * check to make sure this looks like a valid message-id + * some idiotic clients also break their message-ids between lines, deal + * with that too (give up if it's more than two lines, though) */ + t = NULL; + new = NULL; + if (*s == '<') { - t = (LIST *)safe_malloc (sizeof (LIST)); - t->data = safe_strdup (s); - t->next = lst; - lst = t; + n = strlen (s); + if (s[n-1] != '>') + { + o = s; + s = NULL; + continue; + } + + new = safe_strdup (s); + } + else if (o) + { + m = strlen (s); + if (s[m - 1] == '>') + { + new = safe_malloc (sizeof (char) * (n + m + 1)); + strcpy (new, o); /* __STRCPY_CHECKED__ */ + strcpy (new + n, s); /* __STRCPY_CHECKED__ */ + } + } + if (new) + { + /* make sure that this really does look like a message-id. + * it should have exactly one @, and if we're looking at + * an in-reply-to header, make sure that the part before + * the @ has more than eight characters or it's probably + * an email address + */ + if (!(at = strchr (new, '@')) || strchr (at + 1, '@') + || (in_reply_to && at - new <= 8)) + safe_free ((void **) &new); + else + { + t = (LIST *) safe_malloc (sizeof (LIST)); + t->data = new; + t->next = lst; + lst = t; + } } + o = NULL; s = NULL; } @@ -1009,7 +1051,7 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short if (!ascii_strcasecmp (line+1, "n-reply-to")) { mutt_free_list (&e->in_reply_to); - e->in_reply_to = mutt_parse_references (p); + e->in_reply_to = mutt_parse_references (p, 1); matched = 1; } break; @@ -1058,7 +1100,7 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short if (!ascii_strcasecmp (line + 1, "eferences")) { mutt_free_list (&e->references); - e->references = mutt_parse_references (p); + e->references = mutt_parse_references (p, 0); matched = 1; } else if (!ascii_strcasecmp (line + 1, "eply-to")) diff --git a/pop.c b/pop.c index 6d7289d82..a0ff55b70 100644 --- a/pop.c +++ b/pop.c @@ -213,8 +213,11 @@ static int pop_fetch_headers (CONTEXT *ctx) if (ret < 0) break; - mx_update_context (ctx); + ctx->msgcount++; } + + if (i > old_count) + mx_update_context (ctx, i - old_count); } if (ret < 0) diff --git a/postpone.c b/postpone.c index b78e4c080..5ed79e855 100644 --- a/postpone.c +++ b/postpone.c @@ -291,6 +291,8 @@ int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, char *fcc, size the user attempted to reply to is in this mailbox */ p = tmp->data + 18; SKIPWS (p); + if (!ctx->id_hash) + ctx->id_hash = mutt_make_id_hash (ctx); *cur = hash_find (ctx->id_hash, p); } diff --git a/protos.h b/protos.h index 85c7c388b..33eb48157 100644 --- a/protos.h +++ b/protos.h @@ -96,6 +96,9 @@ BODY *mutt_read_mime_header (FILE *, int); CONTENT *mutt_get_content_info (const char *fname, BODY *b); +HASH *mutt_make_id_hash (CONTEXT *); +HASH *mutt_make_subj_hash (CONTEXT *); + LIST *mutt_make_references(ENVELOPE *e); ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short); @@ -280,8 +283,7 @@ int mutt_is_list_recipient (int, ADDRESS *, ADDRESS *); int mutt_is_subscribed_list (ADDRESS *); int mutt_is_text_type (int, char *); int mutt_is_valid_mailbox (const char *); -int mutt_messages_in_thread (HEADER *); -int mutt_msgno_in_thread (HEADER *); +int mutt_messages_in_thread (HEADER *, int); int mutt_multi_choice (char *prompt, char *letters); int mutt_needs_mailcap (BODY *); int mutt_num_postponed (int); diff --git a/sort.c b/sort.c index 9c5c0a451..2cff93395 100644 --- a/sort.c +++ b/sort.c @@ -178,6 +178,7 @@ void mutt_sort_headers (CONTEXT *ctx, int init) { int i; HEADER *h; + THREAD *thread, *top; sort_t *sortfunc; unset_option (OPTNEEDRESORT); @@ -200,8 +201,8 @@ void mutt_sort_headers (CONTEXT *ctx, int init) mutt_message _("Sorting mailbox..."); /* threads may be bogus, so clear the links */ - if (init) - mutt_clear_threads (ctx); + /* XXX do this always, for the moment */ + mutt_clear_threads (ctx); if (option (OPTNEEDRESCORE) && option (OPTSCORE)) { @@ -214,16 +215,18 @@ void mutt_sort_headers (CONTEXT *ctx, int init) if ((Sort & SORT_MASK) == SORT_THREADS) { AuxSort = NULL; +#if 0 /*XXX*/ /* if $sort_aux changed after the mailbox is sorted, then all the subthreads need to be resorted */ if (option (OPTSORTSUBTHREADS)) { i = Sort; Sort = SortAux; - ctx->tree = mutt_sort_subthreads (ctx->tree, mutt_get_sort_func (SortAux)); + ctx->tree = mutt_sort_subthreads (ctx->tree); Sort = i; unset_option (OPTSORTSUBTHREADS); } +#endif mutt_sort_threads (ctx, init); } else if ((sortfunc = mutt_get_sort_func (Sort)) == NULL || @@ -253,12 +256,16 @@ void mutt_sort_headers (CONTEXT *ctx, int init) /* re-collapse threads marked as collapsed */ if ((Sort & SORT_MASK) == SORT_THREADS) { - h = ctx->tree; - while (h) + top = ctx->tree; + while ((thread = top) != NULL) { + while (!thread->message) + thread = thread->child; + h = thread->message; + if (h->collapsed) mutt_collapse_thread (ctx, h); - h = h->next; + top = top->next; } mutt_set_virtual (ctx); } diff --git a/sort.h b/sort.h index da32278ea..f6f7466e5 100644 --- a/sort.h +++ b/sort.h @@ -40,7 +40,7 @@ void mutt_clear_threads (CONTEXT *); void mutt_sort_headers (CONTEXT *, int); void mutt_sort_threads (CONTEXT *, int); int mutt_select_sort (int); -HEADER *mutt_sort_subthreads (HEADER *, sort_t *); +THREAD *mutt_sort_subthreads (THREAD *); WHERE short BrowserSort INITVAL (SORT_SUBJECT); WHERE short Sort INITVAL (SORT_DATE); diff --git a/thread.c b/thread.c index a729baebf..9a9aca34f 100644 --- a/thread.c +++ b/thread.c @@ -22,76 +22,37 @@ #include #include -/* returns 1 if `a' is a descendant (child) of thread `b' */ -static int is_descendant (HEADER *a, HEADER *b) -{ - /* find the top parent of the thread */ - while (a->parent) - a = a->parent; - return (a == b); -} - -/* This function makes use of the fact that Mutt stores message references in - * reverse order (i.e., last to first). This is optiminal since we would like - * to find the most recent message to which "cur" refers itself. - */ +#define VISIBLE(hdr, ctx) (hdr->virtual >= 0 || (hdr->collapsed && (!ctx->pattern || hdr->limited))) -static HEADER *find_ref (LIST *refs, HEADER *cur, CONTEXT *ctx) +/* determine whether a is a descendant of b */ +static int is_descendant (THREAD *a, THREAD *b) { - HEADER *ptr; - - for (; refs; refs = refs->next) + while (a) { - /* ups, this message is in a reference loop. bad. */ - if (cur->env->message_id && !strcmp (cur->env->message_id, refs->data)) - continue; - - if ((ptr = hash_find (ctx->id_hash, refs->data))) - { - if (is_descendant (ptr, cur)) - continue; - - return ptr; - } + if (a == b) + return (1); + a = a->parent; } - - return NULL; -} - -/* - * In-Reply-To contains the direct parents according to RFC 2822. - * References is second best, since it may contain indirect parents, - * too. - */ - -static HEADER *find_reference (HEADER *cur, CONTEXT *ctx) -{ - HEADER *ptr; - - if ((ptr = find_ref (cur->env->in_reply_to, cur, ctx))) - return ptr; - if ((ptr = find_ref (cur->env->references, cur, ctx))) - return ptr; - - return NULL; + return (0); } /* Determines whether to display a message's subject. */ -static int need_display_subject (CONTEXT *ctx, HEADER *tree) +static int need_display_subject (CONTEXT *ctx, HEADER *hdr) { - HEADER *tmp; + THREAD *tmp, *tree = hdr->thread; /* if our subject is different from our parent's, display it */ - if (tree->subject_changed) + if (hdr->subject_changed) return (1); /* if our subject is different from that of our closest previously displayed * sibling, display the subject */ for (tmp = tree->prev; tmp; tmp = tmp->prev) { - if (tmp->virtual >= 0 || (tmp->collapsed && (!ctx->pattern || tmp->limited))) + hdr = tmp->message; + if (hdr && VISIBLE (hdr, ctx)) { - if (tmp->subject_changed) + if (hdr->subject_changed) return (1); else break; @@ -102,10 +63,14 @@ static int need_display_subject (CONTEXT *ctx, HEADER *tree) * closest displayed ancestor, display the subject */ for (tmp = tree->parent; tmp; tmp = tmp->parent) { - if (tmp->virtual >= 0 || (tmp->collapsed && (!ctx->pattern || tmp->limited))) - return (0); - else if (tmp->subject_changed) - return (1); + hdr = tmp->message; + if (hdr) + { + if (VISIBLE (hdr, ctx)) + return (0); + else if (hdr->subject_changed) + return (1); + } } /* if we have no visible parent or previous sibling, display the subject */ @@ -116,16 +81,18 @@ static int need_display_subject (CONTEXT *ctx, HEADER *tree) * sibling is displayed. */ -static int is_next_displayed (CONTEXT *ctx, HEADER *tree) +static int is_next_displayed (CONTEXT *ctx, THREAD *tree) { int depth = 0; + HEADER *hdr; if ((tree = tree->next) == NULL) return (0); FOREVER { - if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited))) + hdr = tree->message; + if (hdr && VISIBLE (hdr, ctx)) return (1); if (tree->child) @@ -162,19 +129,23 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) char corner = Sort & SORT_REVERSE ? M_TREE_ULCORNER : M_TREE_LLCORNER; int depth = 0, start_depth = 0, max_depth = 0, max_width = 0; int nextdisp = 0, visible; - HEADER *tree = ctx->tree; + THREAD *tree = ctx->tree; HEADER **array = ctx->hdrs + (Sort & SORT_REVERSE ? ctx->msgcount - 1 : 0); - - /* A NULL tree should never be passed here, but may occur if there is - * a cycle. - */ - if (!tree) - return; + HEADER *hdr; FOREVER { - if ((visible = (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited))))) - tree->display_subject = need_display_subject (ctx, tree); + hdr = tree->message; + + if (hdr) + { + if ((visible = VISIBLE (hdr, ctx)) != 0) + hdr->display_subject = need_display_subject (ctx, hdr); + + safe_free ((void **) &hdr->tree); + } + else + visible = 0; if (depth >= max_depth) safe_realloc ((void **) &pfx, @@ -184,8 +155,6 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) safe_realloc ((void **) &arrow, (max_width += 16) * 2 * sizeof (char)); - safe_free ((void **) &tree->tree); - if (depth) { myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * 2; @@ -194,8 +163,8 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) if (depth && start_depth == depth) myarrow[0] = nextdisp ? M_TREE_LTEE : corner; else - myarrow[0] = M_TREE_HIDDEN; - myarrow[1] = tree->fake_thread ? M_TREE_STAR : M_TREE_HLINE; + myarrow[0] = tree->parent->message ? M_TREE_HIDDEN : M_TREE_MISSING; + myarrow[1] = (tree->fake_thread) ? M_TREE_STAR : M_TREE_HLINE; if (visible) { myarrow[2] = M_TREE_RARROW; @@ -204,21 +173,21 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) if (visible) { - tree->tree = safe_malloc ((2 + depth * 2) * sizeof (char)); + hdr->tree = safe_malloc ((2 + depth * 2) * sizeof (char)); if (start_depth > 1) { - strncpy (tree->tree, pfx, (start_depth - 1) * 2); - strfcpy (tree->tree + (start_depth - 1) * 2, + strncpy (hdr->tree, pfx, (start_depth - 1) * 2); + strfcpy (hdr->tree + (start_depth - 1) * 2, arrow, (2 + depth - start_depth) * 2); } else - strfcpy (tree->tree, arrow, 2 + depth * 2); + strfcpy (hdr->tree, arrow, 2 + depth * 2); } } - if (linearize) + if (linearize && hdr) { - *array = tree; + *array = hdr; array += Sort & SORT_REVERSE ? -1 : 1; } @@ -230,25 +199,33 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) mypfx[0] = nextdisp ? M_TREE_VLINE : M_TREE_SPACE; mypfx[1] = M_TREE_SPACE; } - depth++; + if (depth || !option (OPTHIDEMISSING) + || tree->message || tree->child->next) + depth++; if (visible) start_depth = depth; tree = tree->child; + hdr = tree->message; } else { while (!tree->next && tree->parent) { - if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited))) + if (hdr && VISIBLE (hdr, ctx)) start_depth = depth; tree = tree->parent; - if (start_depth == depth) - start_depth--; - depth--; + hdr = tree->message; + if (depth) + { + if (start_depth == depth) + start_depth--; + depth--; + } } - if (tree->virtual >= 0 || (tree->collapsed && (!ctx->pattern || tree->limited))) + if (hdr && VISIBLE (hdr, ctx)) start_depth = depth; - if ((tree = tree->next) == NULL) + tree = tree->next; + if (!tree) break; } } @@ -261,9 +238,12 @@ void mutt_linearize_tree (CONTEXT *ctx, int linearize) * assumes that `tree' is the first element in the list, and not some * element in the middle of the list. */ -static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc) + +static void insert_message (THREAD **tree, THREAD *msg) { - HEADER *tmp; +#if 0 /* XXX */ + THREAD *tmp; +#endif /* NOTE: we do NOT clear the `msg->child' link here because when we do * the pseudo-threading, we want to preserve any sub-threads. So we clear @@ -279,13 +259,16 @@ static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc) } /* check to see if this message belongs at the beginning of the list */ +#if 0 /* XXX */ if (!sortFunc || sortFunc ((void *) &msg, (void *) tree) < 0) { +#endif (*tree)->prev = msg; msg->next = *tree; msg->prev = NULL; *tree = msg; return; +#if 0 /* XXX */ } /* search for the correct spot in the list to insert */ @@ -303,47 +286,114 @@ static void insert_message (HEADER **tree, HEADER *msg, sort_t *sortFunc) tmp->next = msg; msg->prev = tmp; msg->next = NULL; +#endif } +static LIST *make_subject_list (THREAD *cur, time_t *dateptr) +{ + THREAD *start = cur; + ENVELOPE *env; + time_t thisdate; + LIST *curlist, *oldlist, *newlist, *subjects = NULL; + int rc = 0; + + FOREVER + { + while (!cur->message) + cur = cur->child; + + if (dateptr) + { + thisdate = option (OPTTHREADRECEIVED) + ? cur->message->received : cur->message->date_sent; + if (!*dateptr || thisdate < *dateptr) + *dateptr = thisdate; + } + + env = cur->message->env; + if (env->real_subj && + ((env->real_subj != env->subject) || (!option (OPTSORTRE)))) + { + for (curlist = subjects, oldlist = NULL; + curlist; oldlist = curlist, curlist = curlist->next) + { + rc = mutt_strcmp (env->real_subj, curlist->data); + if (rc >= 0) + break; + } + if (!curlist || rc > 0) + { + newlist = safe_calloc (1, sizeof (LIST)); + newlist->data = env->real_subj; + if (oldlist) + { + newlist->next = oldlist->next; + oldlist->next = newlist; + } + else + { + newlist->next = subjects; + subjects = newlist; + } + } + } + + while (!cur->next && cur != start) + { + cur = cur->parent; + } + if (cur == start) + break; + cur = cur->next; + } + + return (subjects); +} /* find the best possible match for a parent mesage based upon subject. * if there are multiple matches, the one which was sent the latest, but * before the current message, is used. */ - -static HEADER *find_subject (CONTEXT *ctx, HEADER *cur) +static THREAD *find_subject (CONTEXT *ctx, THREAD *cur) { struct hash_elem *ptr; - HEADER *tmp, *last = NULL; - ENVELOPE *env = cur->env; + THREAD *tmp, *last = NULL; int hash; + LIST *subjects = NULL, *oldlist; + time_t date = 0; - if (env->real_subj && - ((env->real_subj != env->subject) || (!option (OPTSORTRE)))) + subjects = make_subject_list (cur, &date); + + while (subjects) { - hash = hash_string ((unsigned char *) env->real_subj, ctx->subj_hash->nelem); + hash = hash_string ((unsigned char *) subjects->data, ctx->subj_hash->nelem); for (ptr = ctx->subj_hash->table[hash]; ptr; ptr = ptr->next) { - tmp = ptr->data; + tmp = ((HEADER *) ptr->data)->thread; if (tmp != cur && /* don't match the same message */ !tmp->fake_thread && /* don't match pseudo threads */ - tmp->subject_changed && /* only match interesting replies */ + tmp->message->subject_changed && /* only match interesting replies */ !is_descendant (tmp, cur) && /* don't match in the same thread */ - ( ( (SORT_MASK & SortAux) == SORT_RECEIVED) ? - cur->received >= tmp->received : - cur->date_sent >= tmp->date_sent) && - (!last || (last->date_sent <= tmp->date_sent)) && - tmp->env->real_subj && - mutt_strcmp (env->real_subj, tmp->env->real_subj) == 0) - { + (date >= (option (OPTTHREADRECEIVED) ? + tmp->message->received : + tmp->message->date_sent)) && + (!last || + (option (OPTTHREADRECEIVED) ? + (last->message->received < tmp->message->received) : + (last->message->date_sent < tmp->message->date_sent))) && + tmp->message->env->real_subj && + mutt_strcmp (subjects->data, tmp->message->env->real_subj) == 0) last = tmp; /* best match so far */ - } } + + oldlist = subjects; + subjects = subjects->next; + safe_free ((void **) &oldlist); } - return last; + return (last); } -static void unlink_message (HEADER **top, HEADER *cur) +static void unlink_message (THREAD **top, THREAD *cur) { if (cur->prev) { @@ -360,225 +410,208 @@ static void unlink_message (HEADER **top, HEADER *cur) } } -static void pseudo_threads (CONTEXT *ctx, sort_t *sortFunc) +static void pseudo_threads (CONTEXT *ctx) { - HEADER *tree = ctx->tree; - HEADER *top = tree, *cur, *tmp, *curchild, *nextchild; + THREAD *tree = ctx->tree, *top = tree; + THREAD *tmp, *cur, *parent, *curchild, *nextchild; + + if (!ctx->subj_hash) + ctx->subj_hash = mutt_make_subj_hash (ctx); while (tree) { cur = tree; tree = tree->next; - if ((tmp = find_subject (ctx, cur)) != NULL) + if ((parent = find_subject (ctx, cur)) != NULL) { - /* detach this message from it's current location */ + /* detach this message from its current location */ unlink_message (&top, cur); - cur->subject_changed = 0; cur->fake_thread = 1; - cur->parent = tmp; - insert_message (&tmp->child, cur, sortFunc); + cur->parent = parent; + insert_message (&parent->child, cur); - /* if the message we're attaching has pseudo-children, they - need to be attached to its parent, so move them up a level. */ - for (curchild = cur->child; curchild; ) - { - nextchild = curchild->next; - if (curchild->fake_thread) - { - /* detach this message from its current location */ - unlink_message (&cur->child, curchild); - curchild->parent = tmp; - insert_message (&tmp->child, curchild, sortFunc); - } - curchild = nextchild; - } - } - } - ctx->tree = top; -} + tmp = cur; -static HEADER *sort_last (HEADER *top) -{ - HEADER *tree; - HEADER *tmp; - HEADER *first; - HEADER *last; - HEADER *nextsearch; - sort_t *usefunc; - - usefunc = mutt_get_sort_func (Sort); - - tree = top; - FOREVER - { - if (tree->child) - tree = tree->child; - else - { - while (!tree->next) + FOREVER { - first = last = tree; - nextsearch = tree->prev; - first->prev = NULL; - last->next = NULL; - while ((tree = nextsearch) != NULL) + while (!tmp->message) + tmp = tmp->child; + + if (tmp == cur + || !mutt_strcmp (tmp->message->env->real_subj, + parent->message->env->real_subj)) { - tmp = last; - nextsearch = nextsearch->prev; - while (tmp && (*usefunc) ((void *) &tree->last_sort, - (void *) &tmp->last_sort) < 0) - tmp = tmp->prev; - if (tmp) - { - if ((tree->next = tmp->next) != NULL) - tmp->next->prev = tree; - else - last = tree; - tmp->next = tree; - tree->prev = tmp; - } - else + tmp->message->subject_changed = 0; + + /* if the message we're attaching has pseudo-children, they + * need to be attached to its parent, so move them up a level. */ + for (curchild = tmp->child; curchild; ) { - tree->next = first; - first->prev = tree; - first = tree; - tree->prev = NULL; + nextchild = curchild->next; + if (curchild->fake_thread) + { + /* detach this message from its current location */ + unlink_message (&tmp->child, curchild); + curchild->parent = parent; + /* we care in this case that insert_message inserts the + * message at the beginning of the list! */ + insert_message (&parent->child, curchild); + } + curchild = nextchild; } } - if (first->parent) + + while (!tmp->next && tmp != cur) { - first->parent->child = first; - tree = first->parent; - if (Sort & SORT_REVERSE) - { - if ((*usefunc) ((void *) &tree->last_sort, - (void *) &first->last_sort) > 0) - tree->last_sort = first->last_sort; - } - else - { - if ((*usefunc) ((void *) &tree->last_sort, - (void *) &last->last_sort) < 0) - tree->last_sort = last->last_sort; - } + tmp = tmp->parent; } - else - { - top = first; - tree = last; + if (tmp == cur) break; - } + tmp = tmp->next; } - if ((tree = tree->next) == NULL) - break; } } - return top; + ctx->tree = top; } -static int matches_in_reply_to (HEADER *t, const char *p) -{ - LIST *irt; - - for (irt = t->env->in_reply_to; irt; irt = irt->next) - if (mutt_strcmp (irt->data, p) == 0) - return 1; - - return 0; -} -static void move_descendants (HEADER **tree, HEADER *cur, sort_t *usefunc) +void mutt_clear_threads (CONTEXT *ctx) { - HEADER *ptr, *tmp = *tree; + int i; - while (tmp) + for (i = 0; i < ctx->msgcount; i++) { - /* Look at the entire in-reply-to header, and at the last (!) reference. */ - if (matches_in_reply_to (tmp, cur->env->message_id) || - (tmp->env->references && mutt_strcmp (tmp->env->references->data, cur->env->message_id) == 0)) - { - /* remove message from current location */ - unlink_message (tree, tmp); - - tmp->parent = cur; - if (cur->env->real_subj && tmp->env->real_subj) - tmp->subject_changed = mutt_strcmp (tmp->env->real_subj, cur->env->real_subj) ? 1 : 0; - else - tmp->subject_changed = (cur->env->real_subj || tmp->env->real_subj) ? 1 : 0; - tmp->fake_thread = 0; /* real reference */ - - ptr = tmp; - tmp = tmp->next; - insert_message (&cur->child, ptr, usefunc); - } - else - tmp = tmp->next; + ctx->hdrs[i]->thread = NULL; } + ctx->tree = NULL; + + if (ctx->thread_hash) + hash_destroy (&ctx->thread_hash, *free); } -void mutt_clear_threads (CONTEXT *ctx) +int compare_threads (const void *a, const void *b) { - int i; + static sort_t *sort_func = NULL; - for (i = 0; i < ctx->msgcount; i++) + if (a || b) + return ((*sort_func) (&(*((THREAD **) a))->sort_key, + &(*((THREAD **) b))->sort_key)); + /* a hack to let us reset sort_func even though we can't + * have extra arguments because of qsort + */ + else { - ctx->hdrs[i]->parent = NULL; - ctx->hdrs[i]->next = NULL; - ctx->hdrs[i]->prev = NULL; - ctx->hdrs[i]->child = NULL; - ctx->hdrs[i]->threaded = 0; - ctx->hdrs[i]->fake_thread = 0; + sort_func = NULL; + sort_func = mutt_get_sort_func (Sort); + return (sort_func ? 1 : 0); } - ctx->tree = NULL; } -HEADER *mutt_sort_subthreads (HEADER *hdr, sort_t *func) +THREAD *mutt_sort_subthreads (THREAD *thread) { - static HEADER **array = NULL; - static unsigned array_size = 0, array_alloc = 0; - unsigned array_alloc_base; - unsigned items, item; + THREAD **array, *sort_key; + int i, array_size; - if (!func || !hdr) - return hdr; + Sort ^= SORT_REVERSE; + if (!thread || !compare_threads (NULL, NULL)) + return thread; + + /* we put things into the array backwards to save some cycles, + * but we want to have to move less stuff around if we're + * resorting, so we sort backwards and then put them back + * in reverse order so they're forwards + */ - array_alloc_base = array_alloc; - for (item = array_alloc_base; hdr; hdr = hdr->next) + array = safe_malloc ((array_size = 256) * sizeof (THREAD *)); + while (1) { - if (item >= array_size) + while (thread->child) { - array_size = 2 * MAX(array_size, 0x100); - safe_realloc ((void **) &array, array_size * sizeof (*array)); + thread->sort_key = thread->message; + thread = thread->child; } - array[item++] = hdr; - if (hdr->child) + + while (thread->next && !thread->child) { - array_alloc = item; - hdr->child = mutt_sort_subthreads (hdr->child, func); + thread->sort_key = thread->message; + thread = thread->next; } - } - array_alloc = array_alloc_base; - items = item - array_alloc_base; - qsort ((void *) (array + array_alloc_base), items, sizeof (*array), func); + if (thread->child) + continue; - array[array_alloc_base ]->prev = NULL; - array[array_alloc_base + items-1]->next = NULL; + thread->sort_key = thread->message; - for (item = array_alloc_base; item < array_alloc_base + items-1; item++) - { - array[item ]->next = array[item+1]; - array[item+1]->prev = array[item ]; - } + while (!thread->next) + { + if (thread->prev) + { + for (i = 0; thread; i++, thread = thread->prev) + { + if (i >= array_size) + safe_realloc ((void **) &array, (array_size *= 2) * sizeof (THREAD *)); + + array[i] = thread; + } + + qsort ((void *) array, i, sizeof (THREAD *), *compare_threads); + + array[0]->next = NULL; + + thread = array[i - 1]; + thread->prev = NULL; - return array[array_alloc_base]; + if (thread->parent) + { + thread->parent->child = thread; + sort_key = array[(!(Sort & SORT_LAST) ^ !(Sort & SORT_REVERSE)) ? i - 1 : 0]; + } + + while (--i) + { + array[i - 1]->prev = array[i]; + array[i]->next = array[i - 1]; + } + } + else + sort_key = thread; + + + if (thread->parent) + { + if (Sort & SORT_LAST) + { + if (!thread->parent->sort_key + || ((((Sort & SORT_REVERSE) ? 1 : -1) + * compare_threads ((void *) &thread->parent, + (void *) &sort_key)) + > 0)) + thread->parent->sort_key = sort_key->sort_key; + } + else if (!thread->parent->sort_key) + thread->parent->sort_key = sort_key->sort_key; + + thread = thread->parent; + } + else + { + Sort ^= SORT_REVERSE; + return (thread); + } + } + + thread = thread->next; + } } void mutt_sort_threads (CONTEXT *ctx, int init) { - HEADER *tmp, *CUR; - int i, oldsort; + HEADER *cur; + int i, oldsort, using_refs = 0; + HASH *id_hash; + THREAD *thread, *new, *tmp; + LIST *ref = NULL; /* set Sort to the secondary method to support the set sort_aux=reverse-* * settings. The sorting functions just look at the value of @@ -587,83 +620,155 @@ void mutt_sort_threads (CONTEXT *ctx, int init) oldsort = Sort; Sort = SortAux; + id_hash = hash_create (ctx->msgcount * 2); + ctx->tree = safe_calloc (1, sizeof (THREAD)); for (i = 0; i < ctx->msgcount; i++) { - CUR = ctx->hdrs[i]; + cur = ctx->hdrs[i]; + thread = safe_calloc (1, sizeof (THREAD)); + thread->message = cur; + cur->thread = thread; + hash_insert (id_hash, cur->env->message_id ? cur->env->message_id : "", thread, 1); + } - if (CUR->fake_thread) - { - /* Move pseudo threads back to the top level thread so that they can - * can be moved later if they are descendants of messages that were - * just delivered. - */ - CUR->fake_thread = 0; - CUR->subject_changed = 1; - unlink_message (&CUR->parent->child, CUR); - CUR->parent = NULL; - insert_message (&ctx->tree, CUR, NULL); - } - else if (!CUR->threaded) + for (i = 0; i < ctx->msgcount; i++) + { + cur = ctx->hdrs[i]; + thread = cur->thread; + using_refs = 0; + + while (1) { - if ((tmp = find_reference (CUR, ctx)) != NULL) + if (using_refs == 0) { - CUR->parent = tmp; - if (CUR->env->real_subj && tmp->env->real_subj) - CUR->subject_changed = mutt_strcmp (tmp->env->real_subj, CUR->env->real_subj) ? 1 : 0; + /* look at the beginning of in-reply-to: */ + if ((ref = cur->env->in_reply_to) != NULL) + { + using_refs = 1; + } else - CUR->subject_changed = (CUR->env->real_subj || tmp->env->real_subj) ? 1 : 0; + { + ref = cur->env->references; + using_refs = 2; + } + } + else if (using_refs == 1) + { + /* if there's no references header, use all the in-reply-to: + * data that we have. otherwise, use the first reference + * if it's different than the first in-reply-to, otherwise use + * the second reference (since at least eudora puts the most + * recent reference in in-reply-to and the rest in references + */ + if (!cur->env->references) + ref = ref->next; + else + { + if (mutt_strcmp (ref->data, cur->env->references->data)) + ref = cur->env->references; + else + ref = cur->env->references->next; + + using_refs = 2; + } } else - CUR->subject_changed = 1; + ref = ref->next; + + if (!ref) + break; - if (!init) + if ((new = hash_find (id_hash, ref->data)) == NULL) { - /* Search the children of `tmp' for decendants of `cur'. This is only - * done when the mailbox has already been threaded since we don't have - * to worry about the tree being threaded wrong (because of a missing - * parent) during the initial threading. + new = safe_calloc (1, sizeof (THREAD)); + hash_insert (id_hash, ref->data, new, 1); + } + else if (is_descendant (new, thread)) /* no loops! */ + break; + + /* make new the parent of thread */ + if (thread->parent) + { + /* this can only happen if thread is attached directly to ctx->tree. + * detach it. */ - if (CUR->env->message_id) - move_descendants (tmp ? &tmp->child : &ctx->tree, CUR, NULL); + if (thread->prev) + thread->prev->next = thread->next; + if (thread->next) + thread->next->prev = thread->prev; + if (thread->parent->child == thread) + thread->parent->child = thread->next; } - insert_message (tmp ? &tmp->child : &ctx->tree, CUR, NULL); - CUR->threaded = 1; - } - } - if (!option (OPTSTRICTTHREADS)) - pseudo_threads (ctx, NULL); + thread->parent = new; + thread->prev = NULL; + if ((thread->next = new->child) != NULL) + thread->next->prev = thread; + new->child = thread; + + thread = new; + + if (thread->message || (thread->parent && thread->parent != ctx->tree)) + break; + } - /* if the SORT_LAST bit is not set, sort the whole tree now */ - if (!(Sort & SORT_LAST)) - ctx->tree = mutt_sort_subthreads (ctx->tree, mutt_get_sort_func (Sort)); + if (!thread->parent) + { + if ((thread->next = ctx->tree->child) != NULL) + { + thread->next->prev = thread; + } + ctx->tree->child = thread; + thread->parent = ctx->tree; + } + } - /* now that the whole tree is put together, we can sort by last-* */ - if (Sort & SORT_LAST) + for (thread = ctx->tree->child; thread; thread = thread->next) { - for (i = 0; i < ctx->msgcount; i++) - ctx->hdrs[i]->last_sort = ctx->hdrs[i]; - ctx->tree = sort_last (ctx->tree); + thread->parent = NULL; } + tmp = ctx->tree; + ctx->tree = ctx->tree->child; + safe_free ((void **) &tmp); + + for (i = 0; i < ctx->msgcount; i++) + { + cur = ctx->hdrs[i]; + tmp = cur->thread->parent; + while (tmp && !tmp->message) + { + tmp = tmp->parent; + } + + if (!tmp) + cur->subject_changed = 1; + else if (cur->env->real_subj && tmp->message->env->real_subj) + cur->subject_changed = mutt_strcmp (cur->env->real_subj, tmp->message->env->real_subj) ? 1 : 0; + else + cur->subject_changed = (cur->env->real_subj || tmp->message->env->real_subj) ? 1 : 0; + } + + if (!option (OPTSTRICTTHREADS)) + pseudo_threads (ctx); + + ctx->tree = mutt_sort_subthreads (ctx->tree); + /* restore the oldsort order. */ Sort = oldsort; - /* Put the list into an array. If we are reverse sorting, give the - * offset of the last message, and work backwards (tested for and - * done inside the function), so that the threads go backwards. - * This, of course, means the auxillary sort has to go forwards - * because we map it backwards here. - */ + /* Put the list into an array. */ mutt_linearize_tree (ctx, 1); + + ctx->thread_hash = id_hash; } -static HEADER *find_virtual (HEADER *cur) +static HEADER *find_virtual (THREAD *cur) { - HEADER *top; + THREAD *top; - if (cur->virtual >= 0) - return (cur); + if (cur->message && cur->message->virtual >= 0) + return (cur->message); top = cur; if ((cur = cur->child) == NULL) @@ -671,8 +776,8 @@ static HEADER *find_virtual (HEADER *cur) FOREVER { - if (cur->virtual >= 0) - return (cur); + if (cur->message && cur->message->virtual >= 0) + return (cur->message); if (cur->child) cur = cur->child; @@ -694,6 +799,7 @@ static HEADER *find_virtual (HEADER *cur) int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads) { + THREAD *cur; HEADER *tmp; if ((Sort & SORT_MASK) != SORT_THREADS) @@ -702,22 +808,24 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads) return (hdr->virtual); } + cur = hdr->thread; + if (!subthreads) { - while (hdr->parent) - hdr = hdr->parent; + while (cur->parent) + cur = cur->parent; } else { if ((dir != 0) ^ ((Sort & SORT_REVERSE) != 0)) { - while (!hdr->next && hdr->parent) - hdr = hdr->parent; + while (!cur->next && cur->parent) + cur = cur->parent; } else { - while (!hdr->prev && hdr->parent) - hdr = hdr->parent; + while (!cur->prev && cur->parent) + cur = cur->parent; } } @@ -725,20 +833,20 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads) { do { - hdr = hdr->next; - if (!hdr) - return (-1); - tmp = find_virtual (hdr); + cur = cur->next; + if (!cur) + return (-1); + tmp = find_virtual (cur); } while (!tmp); } else { do { - hdr = hdr->prev; - if (!hdr) - return (-1); - tmp = find_virtual (hdr); + cur = cur->prev; + if (!cur) + return (-1); + tmp = find_virtual (cur); } while (!tmp); } @@ -747,54 +855,52 @@ int _mutt_aside_thread (HEADER *hdr, short dir, short subthreads) int mutt_parent_message (CONTEXT *ctx, HEADER *hdr) { + THREAD *thread; + if ((Sort & SORT_MASK) != SORT_THREADS) { mutt_error _("Threading is not enabled."); return (hdr->virtual); } - if (hdr->parent) - { - while ((hdr = hdr->parent)) - { - if (hdr->virtual >= 0 && hdr->collapsed && (!ctx->pattern || hdr->limited)) - return (hdr->virtual); - } - mutt_error _("Parent message is not visible in limited view"); - return -1; - } - else + + thread = hdr->thread; + while ((thread = thread->parent)) { - mutt_error _("Parent message is not available."); - return -1; + hdr = thread->message; + if (hdr && VISIBLE (hdr, ctx)) + return (hdr->virtual); } + + mutt_error _("Parent message is not available."); + return (-1); } void mutt_set_virtual (CONTEXT *ctx) { int i; + HEADER *cur; ctx->vcount = 0; ctx->vsize = 0; -#define THIS_BODY cur->content for (i = 0; i < ctx->msgcount; i++) { - HEADER *cur = ctx->hdrs[i]; - if (cur->virtual != -1) + cur = ctx->hdrs[i]; + if (cur->virtual >= 0) { cur->virtual = ctx->vcount; ctx->v2r[ctx->vcount] = i; ctx->vcount++; - ctx->vsize += THIS_BODY->length + THIS_BODY->offset - THIS_BODY->hdr_offset; + ctx->vsize += cur->content->length + cur->content->offset - cur->content->hdr_offset; cur->num_hidden = mutt_get_hidden (ctx, cur); } } -#undef THIS_BODY } int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) { - HEADER *roothdr = NULL, *top; + THREAD *thread, *top; + HEADER *roothdr = NULL; int final, reverse = (Sort & SORT_REVERSE), minmsgno; int num_hidden = 0, new = 0, old = 0; int min_unread_msgno = INT_MAX, min_unread = cur->virtual; @@ -807,11 +913,14 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) } final = cur->virtual; - while (cur->parent) - cur = cur->parent; - top = cur; + thread = cur->thread; + while (thread->parent) + thread = thread->parent; + top = thread; + while (!thread->message) + thread = thread->child; + cur = thread->message; minmsgno = cur->msgno; - if (!cur->read && CHECK_LIMIT) { @@ -841,7 +950,7 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) } } - if ((cur = cur->child) == NULL) + if ((thread = thread->child) == NULL) { /* return value depends on action requested */ if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE)) @@ -856,63 +965,68 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) FOREVER { - if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE)) + cur = thread->message; + + if (cur) { - cur->pair = 0; /* force index entry's color to be re-evaluated */ - cur->collapsed = flag & M_THREAD_COLLAPSE; - if (!roothdr && CHECK_LIMIT) + if (flag & (M_THREAD_COLLAPSE | M_THREAD_UNCOLLAPSE)) { - roothdr = cur; - if (flag & M_THREAD_COLLAPSE) - final = roothdr->virtual; - } + cur->pair = 0; /* force index entry's color to be re-evaluated */ + cur->collapsed = flag & M_THREAD_COLLAPSE; + if (!roothdr && CHECK_LIMIT) + { + roothdr = cur; + if (flag & M_THREAD_COLLAPSE) + final = roothdr->virtual; + } - if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT) - { - minmsgno = cur->msgno; - final = cur->virtual; - } + if (reverse && (flag & M_THREAD_COLLAPSE) && (cur->msgno < minmsgno) && CHECK_LIMIT) + { + minmsgno = cur->msgno; + final = cur->virtual; + } - if (flag & M_THREAD_COLLAPSE) - { - if (cur != roothdr) - cur->virtual = -1; - } - else - { - if (CHECK_LIMIT) - cur->virtual = cur->msgno; + if (flag & M_THREAD_COLLAPSE) + { + if (cur != roothdr) + cur->virtual = -1; + } + else + { + if (CHECK_LIMIT) + cur->virtual = cur->msgno; + } } - } - if (!cur->read && CHECK_LIMIT) - { - if (cur->old) - old = 2; - else - new = 1; - if (cur->msgno < min_unread_msgno) + if (!cur->read && CHECK_LIMIT) { - min_unread = cur->virtual; - min_unread_msgno = cur->msgno; + if (cur->old) + old = 2; + else + new = 1; + if (cur->msgno < min_unread_msgno) + { + min_unread = cur->virtual; + min_unread_msgno = cur->msgno; + } } - } - if (cur->virtual == -1 && CHECK_LIMIT) - num_hidden++; + if (cur->virtual == -1 && CHECK_LIMIT) + num_hidden++; + } - if (cur->child) - cur = cur->child; - else if (cur->next) - cur = cur->next; + if (thread->child) + thread = thread->child; + else if (thread->next) + thread = thread->next; else { int done = 0; - while (!cur->next) + while (!thread->next) { - cur = cur->parent; - if (cur == top) + thread = thread->parent; + if (thread == top) { done = 1; break; @@ -920,7 +1034,7 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) } if (done) break; - cur = cur->next; + thread = thread->next; } } @@ -938,89 +1052,71 @@ int _mutt_traverse_thread (CONTEXT *ctx, HEADER *cur, int flag) #undef CHECK_LIMIT } -int mutt_messages_in_thread (HEADER *cur) + +/* if flag is 0, we want to know how many messages + * are in the thread. if flag is 1, we want to know + * our position in the thread. */ +int mutt_messages_in_thread (HEADER *hdr, int flag) { - HEADER *top; - int n = 1; - + THREAD *threads[2]; + int i; + if ((Sort & SORT_MASK) != SORT_THREADS) - { - /* mutt_error _("Threading is not enabled."); */ return (1); - } - /* find top parent */ - while (cur->parent) - cur = cur->parent; - top = cur; - - /* return if there are no children */ - if ((cur = cur->child) == NULL) - return n; + threads[0] = hdr->thread; + while (threads[0]->parent) + threads[0] = threads[0]->parent; + while (threads[0]->prev) + threads[0] = threads[0]->prev; - FOREVER + if (flag) + threads[1] = hdr->thread; + else + threads[1] = threads[0]->next; + + for (i = 0; i < flag ? 1 : 2; i++) { - n++; - if (cur->child) - cur = cur->child; - else if (cur->next) - cur = cur->next; - else - { - while (!cur->next) - { - cur = cur->parent; - if (cur == top) - return n; - } - cur = cur->next; - } - } - /* not reached */ + while (!threads[i]->message) + threads[i] = threads[i]->child; + } + + return (((Sort & SORT_REVERSE ? -1 : 1) + * threads[1]->message->msgno - threads[0]->message->msgno) + (flag ? 1 : 0)); } -int mutt_msgno_in_thread (HEADER *cur) +HASH *mutt_make_id_hash (CONTEXT *ctx) { - HEADER *top; - HEADER *target; - int n = 0; + int i; + HEADER *hdr; + HASH *hash; - if ((Sort & SORT_MASK) != SORT_THREADS) - { - /* mutt_error _("Threading is not enabled."); */ - return (0); - } + hash = hash_create (ctx->msgcount * 2); - /* save target */ - target = cur; + for (i = 0; i < ctx->msgcount; i++) + { + hdr = ctx->hdrs[i]; + if (hdr->env->message_id) + hash_insert (hash, hdr->env->message_id, hdr, 0); + } - /* find top parent */ - while (cur->parent) - cur = cur->parent; - top = cur; + return hash; +} - /* return if single message */ - if (top == target) - return n; +HASH *mutt_make_subj_hash (CONTEXT *ctx) +{ + int i; + HEADER *hdr; + HASH *hash; - /* step into first child */ - cur = cur->child; + hash = hash_create (ctx->msgcount * 2); - FOREVER + for (i = 0; i < ctx->msgcount; i++) { - n++; - if (cur == target) - return n; - if (cur->child) - cur = cur->child; - else if (cur->next) - cur = cur->next; - else - { - while (!cur->next) - cur = cur->parent; - cur = cur->next; - } - } - /* not reached */ + hdr = ctx->hdrs[i]; + if (hdr->env->real_subj) + hash_insert (hash, hdr->env->real_subj, hdr, 1); + } + + return hash; } -- 2.40.0