Commit d1677111 authored by Cedric Duval's avatar Cedric Duval

Add thread editing commands.

parent 44e50ead
......@@ -96,6 +96,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"
......@@ -105,6 +106,7 @@ OP_MAIN_FETCH_MAIL "retrieve mail from POP server"
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 message to the current one"
OP_MAIN_NEXT_NEW "jump to the next new message"
OP_MAIN_NEXT_NEW_THEN_UNREAD "jump to the next new or unread message"
OP_MAIN_NEXT_SUBTHREAD "jump to the next subthread"
......
......@@ -99,6 +99,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long off_start, long off_end, int flags,
(ascii_strncasecmp ("Content-Length:", buf, 15) == 0 ||
ascii_strncasecmp ("Lines:", buf, 6) == 0))
continue;
if ((flags & CH_UPDATE_REFS) &&
ascii_strncasecmp ("References:", buf, 11) == 0)
continue;
if ((flags & CH_UPDATE_IRT) &&
ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
continue;
ignore = 0;
}
......@@ -197,6 +203,12 @@ mutt_copy_hdr (FILE *in, FILE *out, long off_start, long off_end, int flags,
ascii_strncasecmp ("type:", buf + 8, 5) == 0)) ||
ascii_strncasecmp ("mime-version:", buf, 13) == 0))
continue;
if ((flags & CH_UPDATE_REFS) &&
ascii_strncasecmp ("References:", buf, 11) == 0)
continue;
if ((flags & CH_UPDATE_IRT) &&
ascii_strncasecmp ("In-Reply-To:", buf, 12) == 0)
continue;
/* Find x -- the array entry where this header is to be saved */
if (flags & CH_REORDER)
......@@ -330,6 +342,8 @@ mutt_copy_hdr (FILE *in, FILE *out, long off_start, long off_end, int flags,
CH_XMIT ignore Lines: and Content-Length:
CH_WEED do header weeding
CH_NOQFROM ignore ">From " line
CH_UPDATE_IRT update the In-Reply-To: header
CH_UPDATE_REFS update the References: header
prefix
string to use if CH_PREFIX is set
......@@ -339,6 +353,9 @@ int
mutt_copy_header (FILE *in, HEADER *h, FILE *out, int flags, const char *prefix)
{
char buffer[SHORT_STRING];
flags |= (h->irt_changed ? CH_UPDATE_IRT : 0)
| (h->refs_changed ? CH_UPDATE_REFS : 0);
if (mutt_copy_hdr (in, out, h->offset, h->content->offset, flags, prefix) == -1)
return (-1);
......@@ -362,7 +379,56 @@ mutt_copy_header (FILE *in, HEADER *h, FILE *out, int flags, const char *prefix)
if (flags & CH_UPDATE)
{
if ((flags & CH_NOSTATUS) == 0)
#ifdef USE_IMAP
#define NEW_ENV new_env
#else
#define NEW_ENV env
#endif
{
if (h->irt_changed && h->NEW_ENV->in_reply_to)
{
LIST *listp = h->NEW_ENV->in_reply_to;
if (fputs ("In-Reply-To: ", out) == EOF)
return (-1);
for (; listp; listp = listp->next)
if ((fputs (listp->data, out) == EOF) || (fputc (' ', out) == EOF))
return (-1);
if (fputc ('\n', out) == EOF)
return (-1);
}
if (h->refs_changed && h->NEW_ENV->references)
{
LIST *listp = h->NEW_ENV->references, *refs = NULL, *t;
if (fputs ("References: ", out) == EOF)
return (-1);
/* Mutt stores references in reverse order, thus we create
* a reordered refs list that we can put in the headers */
for (; listp; listp = listp->next, refs = t)
{
t = (LIST *)safe_malloc (sizeof (LIST));
t->data = listp->data;
t->next = refs;
}
for (; refs; refs = refs->next)
if ((fputs (refs->data, out) == EOF) || (fputc (' ', out) == EOF))
return (-1);
/* clearing refs from memory */
for (t = refs; refs; refs = t->next, t = refs)
safe_free ((void **)&refs);
if (fputc ('\n', out) == EOF)
return (-1);
}
#undef NEW_ENV
if (h->old || h->read)
{
if (fputs ("Status: ", out) == EOF)
......
......@@ -936,6 +936,11 @@ CHECK_IMAP_ACL(IMAP_ACL_DELETE);
else
{
mutt_set_flag (Context, CURHDR, M_TAG, !CURHDR->tagged);
Context->last_tag = CURHDR->tagged ? CURHDR :
((Context->last_tag == CURHDR && !CURHDR->tagged)
? NULL : Context->last_tag);
menu->redraw = REDRAW_STATUS;
if (option (OPTRESOLVE) && menu->current < Context->vcount - 1)
{
......@@ -1176,6 +1181,77 @@ CHECK_IMAP_ACL(IMAP_ACL_DELETE);
}
break;
case OP_MAIN_BREAK_THREAD:
CHECK_MSGCOUNT;
CHECK_VISIBLE;
CHECK_READONLY;
if ((Sort & SORT_MASK) != SORT_THREADS)
mutt_error _("Threading is not enabled.");
else
{
{
HEADER *oldcur = CURHDR;
mutt_break_thread (CURHDR);
mutt_sort_headers (Context, 1);
menu->current = oldcur->virtual;
}
Context->changed = 1;
mutt_message _("Thread broken");
if (menu->menu == MENU_PAGER)
{
op = OP_DISPLAY_MESSAGE;
continue;
}
else
menu->redraw |= REDRAW_INDEX;
}
break;
case OP_MAIN_LINK_THREADS:
CHECK_MSGCOUNT;
CHECK_VISIBLE;
CHECK_READONLY;
if ((Sort & SORT_MASK) != SORT_THREADS)
mutt_error _("Threading is not enabled.");
else if (!CURHDR->env->message_id)
mutt_error _("No Message-ID: header available to link thread");
else if (!tag && (!Context->last_tag || !Context->last_tag->tagged))
mutt_error _("First, please tag a message to be linked here");
else
{
HEADER *oldcur = CURHDR;
if (mutt_link_threads (CURHDR, tag ? NULL : Context->last_tag,
Context))
{
mutt_sort_headers (Context, 1);
menu->current = oldcur->virtual;
Context->changed = 1;
mutt_message _("Threads linked");
}
else
mutt_error _("No thread linked");
}
if (menu->menu == MENU_PAGER)
{
op = OP_DISPLAY_MESSAGE;
continue;
}
else
menu->redraw |= REDRAW_STATUS | REDRAW_INDEX;
break;
case OP_EDIT_TYPE:
CHECK_MSGCOUNT;
......
......@@ -2334,8 +2334,43 @@ used a threaded news client, this is the same concept. It makes dealing
with large volume mailing lists easier because you can easily delete
uninteresting threads and quickly find topics of value.
<sect1>Editing threads
<p>
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 formats) from these
annoyances which make it hard to follow a discussion.
If you want to use these functions with IMAP, you need to compile Mutt
with the <em/--enable-imap-edit-threads/ configure flag.
<sect2>Linking threads
<p>
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 reply, then moving to the parent message
and using the ``link-threads'' function (bound to & by default). The
reply will then be connected to this "parent" message.
You can also connect multiple childs at once, tagging them and using the
tag-prefix command (';') or the auto_tag option.
<sect2>Breaking threads
<p>
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.
<sect1>Delivery Status Notification (DSN) Support
<p>
RFC1894 defines a set of MIME content types for relaying information
about the status of electronic mail messages. These can be thought of as
``return receipts.'' Berkeley sendmail 8.8.x currently has some command
......
......@@ -69,6 +69,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" },
......@@ -95,6 +96,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" },
......@@ -153,6 +155,7 @@ struct binding_t OpMain[] = {
};
struct binding_t OpPager[] = {
{ "break-thread", OP_MAIN_BREAK_THREAD, "#" },
{ "create-alias", OP_CREATE_ALIAS, "a" },
{ "bounce-message", OP_BOUNCE_MESSAGE, "b" },
{ "change-folder", OP_MAIN_CHANGE_FOLDER, "c" },
......@@ -175,6 +178,7 @@ struct binding_t OpPager[] = {
{ "next-entry", OP_NEXT_ENTRY, "J" },
{ "previous-undeleted",OP_MAIN_PREV_UNDELETED, "k" },
{ "previous-entry", OP_PREV_ENTRY, "K" },
{ "link-threads", OP_MAIN_LINK_THREADS, "&" },
{ "list-reply", OP_LIST_REPLY, "L" },
{ "redraw-screen", OP_REDRAW, "\014" },
{ "mail", OP_MAIL, "m" },
......
......@@ -1065,9 +1065,11 @@ int imap_sync_mailbox (CONTEXT* ctx, int expunge, int* index_hint)
mutt_message (_("Saving message status flags... [%d/%d]"), n+1,
ctx->msgcount);
/* if attachments have been deleted we delete the message and reupload
* it. This works better if we're expunging, of course. */
if (ctx->hdrs[n]->attach_del)
/* if the message has been rethreaded or attachments have been deleted
* we delete the message and reupload it.
* This works better if we're expunging, of course. */
if (ctx->hdrs[n]->refs_changed || ctx->hdrs[n]->irt_changed ||
ctx->hdrs[n]->attach_del)
{
dprint (3, (debugfile, "imap_sync_mailbox: Attachments to be deleted, falling back to _mutt_save_message\n"));
if (!appendctx)
......
......@@ -1382,7 +1382,7 @@ static int mh_sync_message (CONTEXT * ctx, int msgno)
{
HEADER *h = ctx->hdrs[msgno];
if (h->attach_del)
if (h->attach_del || h->refs_changed || h->irt_changed)
if (mh_rewrite_message (ctx, msgno) != 0)
return -1;
......@@ -1393,9 +1393,9 @@ static int maildir_sync_message (CONTEXT * ctx, int msgno)
{
HEADER *h = ctx->hdrs[msgno];
if (h->attach_del)
if (h->attach_del || h->refs_changed || h->irt_changed)
{
/* when doing attachment deletion, fall back to the MH case. */
/* when doing attachment deletion/rethreading, fall back to the MH case. */
if (mh_rewrite_message (ctx, msgno) != 0)
return (-1);
}
......
......@@ -94,6 +94,8 @@
#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_NOQFROM (1<<15) /* give CH_FROM precedence over CH_WEED? */
#define CH_UPDATE_IRT (1<<16) /* update In-Reply-To: */
#define CH_UPDATE_REFS (1<<17) /* update References: */
/* flags for mutt_enter_string() */
#define M_ALIAS 1 /* do alias "completion" by calling up the alias-menu */
......@@ -551,6 +553,7 @@ typedef struct spam_list_t
void mutt_free_list (LIST **);
void mutt_free_rx_list (RX_LIST **);
void mutt_free_spam_list (SPAM_LIST **);
LIST *mutt_copy_list (LIST *);
int mutt_matches_ignore (const char *, LIST *);
/* add an element to a list */
......@@ -706,6 +709,8 @@ typedef struct header
unsigned int subject_changed : 1; /* used for threading */
unsigned int threaded : 1; /* used for threading */
unsigned int display_subject : 1; /* used for threading */
unsigned int irt_changed : 1; /* In-Reply-To changed to link/break threads */
unsigned int refs_changed : 1; /* References changed to break thread */
unsigned int recip_valid : 1; /* is_recipient is valid */
unsigned int active : 1; /* message is not to be removed */
unsigned int trash : 1; /* message is marked as trashed on disk.
......@@ -746,6 +751,10 @@ typedef struct header
char *tree; /* character string to print thread tree */
struct thread *thread;
#ifdef USE_IMAP
ENVELOPE *new_env; /* envelope information for rethreading */
#endif
#ifdef MIXMASTER
LIST *chain;
#endif
......@@ -810,6 +819,7 @@ typedef struct
char *pattern; /* limit pattern string */
pattern_t *limit_pattern; /* compiled limit pattern */
HEADER **hdrs;
HEADER *last_tag; /* last tagged msg. used to link threads */
THREAD *tree; /* top of thread tree */
HASH *id_hash; /* hash table by msg id */
HASH *subj_hash; /* hash table by subject */
......
......@@ -1165,6 +1165,8 @@ int mx_sync_mailbox (CONTEXT *ctx, int *index_hint)
ctx->deleted = 0;
}
}
else if (ctx->last_tag && ctx->last_tag->deleted)
ctx->last_tag = NULL; /* reset last tagged msg now useless */
}
/* really only for IMAP - imap_sync_mailbox results in a call to
......
......@@ -2498,6 +2498,11 @@ CHECK_IMAP_ACL(IMAP_ACL_WRITE);
case OP_TAG:
CHECK_MODE(IsHeader (extra));
mutt_set_flag (Context, extra->hdr, M_TAG, !extra->hdr->tagged);
Context->last_tag = extra->hdr->tagged ? extra->hdr :
((Context->last_tag == extra->hdr && !extra->hdr->tagged)
? NULL : Context->last_tag);
redraw = REDRAW_STATUS | REDRAW_INDEX;
if (option (OPTRESOLVE))
{
......
......@@ -165,6 +165,7 @@ void mutt_block_signals (void);
void mutt_block_signals_system (void);
void mutt_body_handler (BODY *, STATE *);
int mutt_bounce_message (FILE *fp, HEADER *, ADDRESS *);
void mutt_break_thread (HEADER *);
void mutt_buffy (char *, size_t);
int mutt_buffy_list (void);
void mutt_canonical_charset (char *, size_t, const char *);
......@@ -307,6 +308,7 @@ int mutt_is_list_recipient (int, ADDRESS *, ADDRESS *);
int mutt_is_subscribed_list (ADDRESS *);
int mutt_is_text_part (BODY *);
int mutt_is_valid_mailbox (const char *);
int mutt_link_threads (HEADER *, HEADER *, CONTEXT *);
int mutt_lookup_mime_type (BODY *, const char *);
int mutt_match_rx_list (const char *, RX_LIST *);
int mutt_match_spam_list (const char *, SPAM_LIST *, char *, int);
......
......@@ -1344,3 +1344,105 @@ HASH *mutt_make_subj_hash (CONTEXT *ctx)
return hash;
}
static void clean_references (THREAD *brk, THREAD *cur)
{
THREAD *p;
LIST *ref = NULL;
int done = 0;
for (; cur; cur = cur->next, done = 0)
{
/* parse subthread recursively */
clean_references (brk, cur->child);
if (!cur->message)
break; /* skip pseudo-message */
/* Looking for the first bad reference according to the new threading.
* Optimal since Mutt stores the references in reverse order, and the
* first loop should match immediatly for mails respecting RFC2822. */
for (p = brk; !done && p; p = p->parent)
for (ref = cur->message->env->references; p->message && ref; ref = ref->next)
if (!mutt_strcasecmp (ref->data, p->message->env->message_id))
{
done = 1;
break;
}
if (done)
{
HEADER *h = cur->message;
/* clearing the References: header from obsolete Message-Id(s) */
mutt_free_list (&ref->next);
#ifdef USE_IMAP
if (h->new_env)
mutt_free_list (&h->new_env->references);
else
h->new_env = mutt_new_envelope ();
h->new_env->references = mutt_copy_list (h->env->references);
#endif
h->refs_changed = h->changed = 1;
}
}
}
void mutt_break_thread (HEADER *hdr)
{
mutt_free_list (&hdr->env->in_reply_to);
mutt_free_list (&hdr->env->references);
hdr->irt_changed = hdr->refs_changed = hdr->changed = 1;
#ifdef USE_IMAP
if (hdr->new_env)
{
mutt_free_list (&hdr->new_env->in_reply_to);
mutt_free_list (&hdr->new_env->references);
}
else
hdr->new_env = mutt_new_envelope ();
#endif
clean_references (hdr->thread, hdr->thread->child);
}
static int link_threads (HEADER *parent, HEADER *child, CONTEXT *ctx)
{
if (child == parent)
return 0;
mutt_break_thread (child);
child->env->in_reply_to = mutt_new_list ();
child->env->in_reply_to->data = safe_strdup (parent->env->message_id);
#ifdef USE_IMAP
child->new_env->in_reply_to = mutt_new_list ();
child->new_env->in_reply_to->data = safe_strdup (parent->env->message_id);
#endif
mutt_set_flag (ctx, child, M_TAG, 0);
child->irt_changed = child->changed = 1;
return 1;
}
int mutt_link_threads (HEADER *cur, HEADER *last, CONTEXT *ctx)
{
int i, changed = 0;
if (!last)
{
for (i = 0; i < ctx->vcount; i++)
if (ctx->hdrs[Context->v2r[i]]->tagged)
changed |= link_threads (cur, ctx->hdrs[Context->v2r[i]], ctx);
}
else
changed = link_threads (cur, last, ctx);
return changed;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment