diff -udpr mutt-1.2.5.orig/OPS mutt-1.2.5.edit_threads/OPS --- mutt-1.2.5.orig/OPS Tue Feb 15 16:28:466 2000 +++ mutt-1.2.5.edit_threads/OPS Sat Sep 1 15:28:03 2001 @@ -87,6 +87,7 @@ OP_LAST_ENTRY "move to the last entry" OP_LIST_REPLY "reply to specified mailing list" OP_MACRO "execute a macro" OP_MAIL "compose a new mail message" +OP_MAIN_BREAK_THREAD "break the thread in two" OP_MAIN_CHANGE_FOLDER "open a different folder" OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode" OP_MAIN_CLEAR_FLAG "clear a status flag from a message" @@ -95,6 +96,7 @@ OP_MAIN_FETCH_MAIL "retrieve mail from P OP_MAIN_FIRST_MESSAGE "move to the first message" OP_MAIN_LAST_MESSAGE "move to the last message" OP_MAIN_LIMIT "show only messages matching a pattern" +OP_MAIN_LINK_THREADS "link tagged messages to the current one" OP_MAIN_NEXT_NEW "jump to the next new message" OP_MAIN_NEXT_SUBTHREAD "jump to the next subthread" OP_MAIN_NEXT_THREAD "jump to the next thread" diff -udpr mutt-1.2.5.orig/copy.c mutt-1.2.5.edit_threads/copy.c --- mutt-1.2.5.orig/copy.c Thu Jun 15 23:066:40 2000 +++ mutt-1.2.5.edit_threads/copy.c Sun Sep 2 15:06:28 2001 @@ -92,6 +92,9 @@ mutt_copy_hdr (FILE *in, FILE *out, long (mutt_strncasecmp ("Content-Length:", buf, 15) == 0 || mutt_strncasecmp ("Lines:", buf, 6) == 0)) continue; + if ((flags & CH_UPDATE_THREADS) && + mutt_strncasecmp ("X-Thread-as-Reply-To:", buf, 21) == 0) + continue; ignore = 0; } @@ -168,6 +171,9 @@ mutt_copy_hdr (FILE *in, FILE *out, long mutt_strncasecmp ("type:", buf + 8, 5) == 0)) || mutt_strncasecmp ("mime-version:", buf, 13) == 0)) continue; + if ((flags & CH_UPDATE_THREADS) && + mutt_strncasecmp ("X-Thread-as-Reply-To:", buf, 21) == 0) + continue; /* Find x -- the array entry where this header is to be saved */ if (flags & CH_REORDER) @@ -276,6 +282,7 @@ mutt_copy_hdr (FILE *in, FILE *out, long CH_UPDATE_LEN write new Content-Length: and Lines: CH_XMIT ignore Lines: and Content-Length: CH_WEED do header weeding + CH_UPDATE_THREADS update the X-Thread-as-Reply-To: header prefix string to use if CH_PREFIX is set @@ -286,6 +293,8 @@ mutt_copy_header (FILE *in, HEADER *h, F { char buffer[SHORT_STRING]; + flags |= (h->threads_changed ? CH_UPDATE_THREADS : 0); + if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) == -1) return (-1); @@ -307,6 +316,26 @@ mutt_copy_header (FILE *in, HEADER *h, F { if ((flags & CH_NOSTATUS) == 0) { + if (h->threads_changed) + { + if (h->env->references) + { + if (fputs ("X-Thread-as-Reply-To: ", out) == EOF) + return (-1); + + if (fputs (h->env->references->data, out) == EOF) + return (-1); + + if (fputc ('\n', out) == EOF) + return (-1); + } + else + { + if (fputs ("X-Thread-as-Reply-To: <>\n", out) == EOF) + return (-1); + } + } + if (h->old || h->read) { if (fputs ("Status: ", out) == EOF) diff -udpr mutt-1.2.5.orig/curs_main.c mutt-1.2.5.edit_threads/curs_main.c --- mutt-1.2.5.orig/curs_main.c Tue Jul 25 10:02:26 2000 +++ mutt-1.2.5.edit_threads/curs_main.c Sun Sep 2 15:09:38 2001 @@ -915,6 +915,102 @@ int mutt_index_menu (void) menu->redraw = REDRAW_INDEX | REDRAW_STATUS; break; + case OP_MAIN_BREAK_THREAD: + + CHECK_MSGCOUNT; + CHECK_READONLY; + + if ((Sort & SORT_MASK) != SORT_THREADS) + { + mutt_error _("Threading is not enabled."); + break; + } + + if (CURHDR->parent == NULL) + { + mutt_error _("No thread to break."); + break; + } + + if (CURHDR->env->old_references == NULL && CURHDR->changed == 0) { + CURHDR->env->old_references = CURHDR->env->references; + CURHDR->env->references = NULL; + } + else + mutt_free_list (&CURHDR->env->references); + CURHDR->threads_changed = CURHDR->changed = 1; + Context->changed = 1; + + { + HEADER *oldcur = CURHDR; + mutt_break_thread (Context, CURHDR); + mutt_sort_threads (Context, 0); + mutt_adjust_virtual (Context); + menu->current = oldcur->virtual; + } + mutt_message _("Thread broken"); + menu->redraw |= REDRAW_STATUS | REDRAW_INDEX | REDRAW_MOTION; + break; + + case OP_MAIN_LINK_THREADS: + + CHECK_MSGCOUNT; + CHECK_READONLY; + + if ((Sort & SORT_MASK) != SORT_THREADS) + { + mutt_error _("Threading is not enabled."); + break; + } + + if (!CURHDR->env->message_id) + mutt_error _("No Message-ID: header available to link thread"); + else + { + int changed = 0; + HEADER *oldcur = CURHDR; + + for (i = 0; i < Context->vcount; i++) + { + HEADER *hdr = Context->hdrs[Context->v2r[i]]; + + if (i != menu->current && hdr->tagged) + { + LIST *tmp = mutt_new_list (); + + tmp->data = safe_strdup (oldcur->env->message_id);; + tmp->next = NULL; + + if (hdr->env->old_references == NULL && hdr->changed == 0) { + hdr->env->old_references = hdr->env->references; + } + hdr->env->references = tmp; + + mutt_set_flag (Context, hdr, M_TAG, 0); + mutt_link_thread (Context, oldcur, hdr); + + hdr->threads_changed = hdr->changed = 1; + changed = 1; + } + } + + if (changed) + { + Context->changed = 1; + + mutt_sort_threads (Context, 0); + mutt_adjust_virtual (Context); + mutt_message _("Threads linked"); + menu->redraw |= REDRAW_STATUS | REDRAW_INDEX | REDRAW_MOTION; + + menu->current = oldcur->virtual; + break; + } + else + mutt_error _("No thread linked"); + } + break; + /* -------------------------------------------------------------------- * The following operations can be performed inside of the pager. */ diff -udpr mutt-1.2.5.orig/doc/manual.sgml.head mutt-1.2.5.edit_threads/doc/manual.sgml.head --- mutt-1.2.5.orig/doc/manual.sgml.head Saat Apr 22 11:33:46 2000 +++ mutt-1.2.5.edit_threads/doc/manual.sgml.head Sat Sep 1 15:29:03 2001 @@ -1952,6 +1952,34 @@ used a threaded news client, this is the with large volume mailing lists easier because you can easily delete uninteresting threads and quickly find topics of value. +Editing threads +

+Mutt has the ability to dynamically restructure threads that are broken +either by misconfigured software or bad behaviour from some +correspondents. This allows to clean your mailboxes from these +annoyances which make it hard to follow a discussion. + +Linking threads +

+ +Some mailers tend to "forget" to correctly set the "In-Reply-To:" and +"References:" headers when replying to a message. This results in broken +discussions because Mutt has not enough information to guess the correct +threading. +You can fix this by tagging the replies to a mail, then go onto this +mail and use the ``link-threads'' function (bound to ``&'' by default). +The replies will then be connected to the "parent" message. + +Breaking threads +

+ +On mailing lists, some people are in the bad habit of starting a new +discussion by hitting "reply" to any message from the list and changing +the subject to a totally unrelated one. +You can fix such threads by using the ``break-thread'' function (bound +by default to ``#''), which will turn the subthread starting from the +current message into a whole different thread. + Delivery Status Notification (DSN) Support

RFC1894 defines a set of MIME content types for relaying information diff -udpr mutt-1.2.5.orig/functions.h mutt-1.2.5.edit_threads/functions.h --- mutt-1.2.5.orig/functions.h Fri Mar 3 10:10:08 2000 +++ mutt-1.2.5.edit_threads/functions.h Sat Sep 1 15:29:03 2001 @@ -66,6 +66,7 @@ struct binding_t OpGeneric[] = { struct binding_t OpMain[] = { { "create-alias", OP_CREATE_ALIAS, "a" }, { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, + { "break-thread", OP_MAIN_BREAK_THREAD, "#" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" }, @@ -89,6 +90,7 @@ struct binding_t OpMain[] = { { "next-undeleted", OP_MAIN_NEXT_UNDELETED, "j" }, { "previous-undeleted", OP_MAIN_PREV_UNDELETED, "k" }, { "limit", OP_MAIN_LIMIT, "l" }, + { "link-threads", OP_MAIN_LINK_THREADS, "&" }, { "list-reply", OP_LIST_REPLY, "L" }, { "mail", OP_MAIL, "m" }, { "toggle-new", OP_TOGGLE_NEW, "N" }, diff -udpr mutt-1.2.5.orig/headers.c mutt-1.2.5.edit_threads/headers.c --- mutt-1.2.5.orig/headers.c Wed Apr 12 188:31:38 2000 +++ mutt-1.2.5.edit_threads/headers.c Sun Sep 2 15:15:01 2001 @@ -110,6 +110,8 @@ void mutt_edit_headers (const char *edit /* restore old info. */ n->references = msg->env->references; msg->env->references = NULL; + n->old_references = msg->env->old_references; + msg->env->old_references = NULL; mutt_free_envelope (&msg->env); msg->env = n; @@ -204,5 +206,8 @@ void mutt_edit_headers (const char *edit } if (!in_reply_to) + { mutt_free_list (&msg->env->references); + mutt_free_list (&msg->env->old_references); + } } diff -udpr mutt-1.2.5.orig/mutt.h mutt-1.2.5.edit_threads/mutt.h --- mutt-1.2.5.orig/mutt.h Thu Jun 8 12:000:14 2000 +++ mutt-1.2.5.edit_threads/mutt.h Sun Sep 2 15:20:12 2001 @@ -67,6 +67,7 @@ #define CH_NOLEN (1<<12) /* don't write Content-Length: and Lines: */ #define CH_WEED_DELIVERED (1<<13) /* weed eventual Delivered-To headers */ #define CH_FORCE_FROM (1<<14) /* give CH_FROM precedence over CH_WEED? */ +#define CH_UPDATE_THREADS (1<<15) /* update X-Thread-as-Reply-To: */ /* flags for mutt_enter_string() */ #define M_ALIAS 1 /* do alias "completion" by calling up the alias-menu */ @@ -464,6 +465,7 @@ typedef struct envelope char *supersedes; char *date; LIST *references; /* message references (in reverse order) */ + LIST *old_references; /* message references before thread editing */ LIST *userhdrs; /* user defined headers */ } ENVELOPE; @@ -562,6 +564,7 @@ typedef struct header 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 threads_changed : 1; /* threads have been broken/linked */ unsigned int recip_valid : 1; /* is_recipient is valid */ unsigned int active : 1; /* message is not to be removed */ diff -udpr mutt-1.2.5.orig/muttlib.c mutt-1.2.5.edit_threads/muttlib.c --- mutt-1.2.5.orig/muttlib.c Fri Jul 7 011:06:16 2000 +++ mutt-1.2.5.edit_threads/muttlib.c Sun Sep 2 15:15:20 2001 @@ -564,6 +564,7 @@ void mutt_free_envelope (ENVELOPE **p) safe_free ((void **) &(*p)->supersedes); safe_free ((void **) &(*p)->date); mutt_free_list (&(*p)->references); + mutt_free_list (&(*p)->old_references); mutt_free_list (&(*p)->userhdrs); safe_free ((void **) p); } diff -udpr mutt-1.2.5.orig/parse.c mutt-1.2.5.edit_threads/parse.c --- mutt-1.2.5.orig/parse.c Sat Apr 22 10:449:30 2000 +++ mutt-1.2.5.edit_threads/parse.c Sun Sep 2 15:50:51 2001 @@ -875,11 +875,13 @@ ENVELOPE *mutt_read_rfc822_header (FILE char *line = safe_malloc (LONG_STRING); char *p; char in_reply_to[STRING]; + char thread_as_reply_to[STRING]; long loc; int matched; size_t linelen = LONG_STRING; in_reply_to[0] = 0; + thread_as_reply_to[0] = 0; if (hdr) { @@ -1181,6 +1183,13 @@ ENVELOPE *mutt_read_rfc822_header (FILE } matched = 1; } + else if (mutt_strcasecmp (line + 1, "-thread-as-reply-to") == 0) + { + strfcpy (thread_as_reply_to, p, sizeof (thread_as_reply_to)); + rfc2047_decode (thread_as_reply_to, thread_as_reply_to, + sizeof (thread_as_reply_to)); + } + break; default: break; @@ -1231,6 +1240,21 @@ ENVELOPE *mutt_read_rfc822_header (FILE } else safe_free ((void **) &p); + } + + if (thread_as_reply_to[0]) + { + e->old_references = e->references; + e->references = NULL; + if (mutt_strcmp (thread_as_reply_to, "<>") != 0 && + (p = extract_message_id (thread_as_reply_to)) != NULL) + { + LIST *tmp = mutt_new_list (); + + tmp->data = p; + tmp->next = NULL; + e->references = tmp; + } } /* do RFC2047 decoding */ diff -udpr mutt-1.2.5.orig/protos.h mutt-1.2.5.edit_threads/protos.h --- mutt-1.2.5.orig/protos.h Tue May 16 17::23:12 2000 +++ mutt-1.2.5.edit_threads/protos.h Sun Sep 2 15:25:53 2001 @@ -126,12 +126,14 @@ char *mutt_make_date (char *, size_t); const char *mutt_fqdn(short); +void mutt_adjust_virtual (CONTEXT *); void mutt_adv_mktemp (char *, size_t); void mutt_alias_menu (char *, size_t, ALIAS *); void mutt_block_signals (void); void mutt_block_signals_system (void); void mutt_body_handler (BODY *, STATE *); void mutt_bounce_message (FILE *fp, HEADER *, ADDRESS *); +void mutt_break_thread (CONTEXT *, HEADER *); void mutt_buffy (char *); void mutt_check_rescore (CONTEXT *); void mutt_clear_error (void); @@ -163,6 +165,7 @@ void mutt_free_parameter (PARAMETER **); void mutt_generate_header (char *, size_t, HEADER *, int); void mutt_help (int); void mutt_linearize_tree (CONTEXT *, int); +void mutt_link_thread (CONTEXT *, HEADER *, HEADER *); void mutt_make_attribution (CONTEXT *ctx, HEADER *cur, FILE *out); void mutt_make_forward_subject (ENVELOPE *env, CONTEXT *ctx, HEADER *cur); void mutt_make_help (char *, size_t, char *, int, int); diff -udpr mutt-1.2.5.orig/send.c mutt-1.2.5.edit_threads/send.c --- mutt-1.2.5.orig/send.c Mon Mar 13 22:366:56 2000 +++ mutt-1.2.5.edit_threads/send.c Sun Sep 2 15:16:29 2001 @@ -525,7 +525,10 @@ LIST *mutt_make_references(ENVELOPE *e) { LIST *t, *l; - l = mutt_copy_list(e->references); + if (e->old_references) + l = mutt_copy_list(e->old_references); + else + l = mutt_copy_list(e->references); if(e->message_id) { diff -udpr mutt-1.2.5.orig/sendlib.c mutt-1.2.5.edit_threads/sendlib.c --- mutt-1.2.5.orig/sendlib.c Sat May 20 088:39:00 2000 +++ mutt-1.2.5.edit_threads/sendlib.c Sun Sep 2 15:29:41 2001 @@ -1345,10 +1345,13 @@ int mutt_write_rfc822_header (FILE *fp, if (mode <= 0) { - if (env->references) + if (env->references || env->old_references) { fputs ("References:", fp); - write_references (env->references, ffp); + if (env->old_references) + write_references (env->old_references, fp); + else + write_references (env->references, fp); fputc('\n', fp); } diff -udpr mutt-1.2.5.orig/sort.c mutt-1.2.5.edit_threads/sort.c --- mutt-1.2.5.orig/sort.c Wed Mar 22 08:244:46 2000 +++ mutt-1.2.5.edit_threads/sort.c Sun Sep 2 15:06:47 2001 @@ -174,6 +174,24 @@ sort_t *mutt_get_sort_func (int method) /* not reached */ } +void mutt_adjust_virtual(CONTEXT *ctx) +{ + int i; + + ctx->vcount = 0; + for (i = 0; i < ctx->msgcount; i++) + { + HEADER *cur = ctx->hdrs[i]; + if (cur->virtual != -1 || (cur->collapsed && (!ctx->pattern || cur->limited))) + { + cur->virtual = ctx->vcount; + ctx->v2r[ctx->vcount] = i; + ctx->vcount++; + } + cur->msgno = i; + } +} + void mutt_sort_headers (CONTEXT *ctx, int init) { int i; @@ -237,18 +255,7 @@ void mutt_sort_headers (CONTEXT *ctx, in qsort ((void *) ctx->hdrs, ctx->msgcount, sizeof (HEADER *), sortfunc); /* adjust the virtual message numbers */ - ctx->vcount = 0; - for (i = 0; i < ctx->msgcount; i++) - { - HEADER *cur = ctx->hdrs[i]; - if (cur->virtual != -1 || (cur->collappsed && (!ctx->pattern || cur->limited))) - { - cur->virtual = ctx->vcount; - ctx->v2r[ctx->vcount] = i; - ctx->vcount++; - } - cur->msgno = i; - } + mutt_adjust_virtual(ctx); /* re-collapse threads marked as collapsed */ if ((Sort & SORT_MASK) == SORT_THREADS) diff -udpr mutt-1.2.5.orig/thread.c mutt-1.2.5.edit_threads/thread.c --- mutt-1.2.5.orig/thread.c Sat Apr 22 11::27:08 2000 +++ mutt-1.2.5.edit_threads/thread.c Sun Sep 2 15:06:47 2001 @@ -888,3 +888,19 @@ int _mutt_traverse_thread (CONTEXT *ctx, #undef CHECK_LIMIT } +void mutt_break_thread (CONTEXT *ctx, HEADER *cur) +{ + unlink_message (&cur->parent->child, cur); + cur->parent = NULL; + insert_message (&ctx->tree, cur, mutt_get_sort_func (SortAux)); +} + +void mutt_link_thread (CONTEXT *ctx, HEADER *cur, HEADER *newchild) +{ + if (newchild->parent) + unlink_message (&newchild->parent->child, newchild); + else + unlink_message (&ctx->tree, newchild); + newchild->parent = cur; + insert_message (&cur->child, newchild, mutt_get_sort_func (SortAux)); +}