OP_DISPLAY_ADDRESS "display full address of sender"
OP_DISPLAY_HEADERS "display message and toggle header weeding"
OP_DISPLAY_MESSAGE "display a message"
+OP_EDIT_LABEL "add, change, or delete a message's label"
OP_EDIT_MESSAGE "edit the raw message"
OP_EDITOR_BACKSPACE "delete the char in front of the cursor"
OP_EDITOR_BACKWARD_CHAR "move the cursor one character to the left"
int method = Sort; /* save the current method in case of abort */
switch (mutt_multi_choice (reverse ?
- _("Rev-Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: ") :
- _("Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: "),
- _("dfrsotuzcp")))
+ _("Rev-Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: ") :
+ _("Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: "),
+ _("dfrsotuzcpl")))
{
case -1: /* abort - don't resort */
return -1;
case 10: /* s(p)am */
Sort = SORT_SPAM;
break;
+
+ case 11: /* (l)abel */
+ Sort = SORT_LABEL;
+ break;
}
if (reverse)
Sort |= SORT_REVERSE;
ignore = 0;
}
+ if (flags & CH_UPDATE_LABEL)
+ {
+ if ((mutt_strncasecmp ("X-Label:", buf, 8) == 0) ||
+ (mutt_strncasecmp ("X-Keywords:", buf, 11) == 0) ||
+ (mutt_strncasecmp ("X-Mozilla-Keys:", buf, 15) == 0) ||
+ (mutt_strncasecmp ("Keywords:", buf, 9) == 0))
+ continue;
+ }
+
if (!ignore && fputs (buf, out) == EOF)
return (-1);
}
fprintf (out, "Lines: %d\n", h->lines);
}
+ if (flags & CH_UPDATE_LABEL && h->label_changed)
+ {
+ h->label_changed = 0;
+ if (h->env->labels != NULL)
+ {
+ char buf[HUGE_STRING];
+ char *tmp = NULL;
+ int fail = 0;
+
+ if (fail == 0 &&
+ ((h->env->kwtypes & MUTT_X_LABEL) || (h->env->kwtypes == 0)) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, XlabelDelim);
+ tmp = safe_strdup(buf);
+ rfc2047_encode_string(&tmp);
+ fail = fprintf(out, "X-Label: %s\n", tmp) != 10 + strlen(tmp);
+ FREE(&tmp);
+ }
+
+ if (fail == 0 && (h->env->kwtypes & MUTT_X_KEYWORDS) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, " ");
+ tmp = safe_strdup(buf);
+ rfc2047_encode_string(&tmp);
+ fail = fprintf(out, "X-Keywords: %s\n", tmp) != 13 + strlen(tmp);
+ FREE(&tmp);
+ }
+
+ if (fail == 0 && (h->env->kwtypes & MUTT_X_MOZILLA_KEYS) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, " ");
+ tmp = safe_strdup(buf);
+ rfc2047_encode_string(&tmp);
+ fail = fprintf(out, "X-Mozilla-Keys: %s\n", tmp) != 17 + strlen(tmp);
+ FREE(&tmp);
+ }
+
+ if (fail == 0 && ((h->env->kwtypes & MUTT_KEYWORDS) ||
+ option(OPTKEYWORDSSTANDARD)))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, NULL);
+ tmp = safe_strdup(buf);
+ rfc2047_encode_string(&tmp);
+ fail = fprintf(out, "Keywords: %s\n", tmp) != 11 + strlen(tmp);
+ FREE(&tmp);
+ }
+
+ if (fail)
+ return -1;
+ }
+ }
+
if ((flags & CH_NONEWLINE) == 0)
{
if (flags & CH_PREFIX)
_mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), Context, hdr, 0);
}
+ if (hdr->label_changed)
+ chflags |= CH_UPDATE_LABEL;
+
if ((flags & MUTT_CM_NOHEADER) == 0)
{
if (flags & MUTT_CM_PREFIX)
#define CH_UPDATE_IRT (1<<16) /* update In-Reply-To: */
#define CH_UPDATE_REFS (1<<17) /* update References: */
#define CH_DISPLAY (1<<18) /* display result to user */
+#define CH_UPDATE_LABEL (1<<19) /* update X-Label: from hdr->env->x_label? */
int mutt_copy_hdr (FILE *, FILE *, LOFF_T, LOFF_T, int, const char *);
FREE (&Context);
}
+ if (Labels)
+ hash_destroy(&Labels, NULL);
+
mutt_sleep (0);
/* Set CurrentMenu to MENU_MAIN before executing any folder
(option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ?
MUTT_READONLY : 0, NULL)) != NULL)
{
+ Labels = hash_create(131, 0);
+ mutt_scan_labels(Context);
menu->current = ci_first_message ();
}
else
menu->redraw = REDRAW_FULL;
break;
+ case OP_EDIT_LABEL:
+
+ CHECK_MSGCOUNT;
+ CHECK_READONLY;
+ rc = mutt_label_message(tag ? NULL : CURHDR);
+ if (rc > 0) {
+ Context->changed = 1;
+ menu->redraw = REDRAW_FULL;
+ mutt_message ("%d label%s changed.", rc, rc == 1 ? "" : "s");
+ }
+ else {
+ mutt_message _("No labels changed.");
+ }
+ break;
+
case OP_LIST_REPLY:
CHECK_ATTACH;
}
break;
}
+ else if (flags & MUTT_LABEL && ch == OP_EDITOR_COMPLETE)
+ {
+ for (i = state->curpos; i && state->wbuf[i-1] != ',' &&
+ state->wbuf[i-1] != ':'; i--)
+ ;
+ for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
+ ;
+ my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
+ r = mutt_label_complete (buf, buflen, i, state->tabs);
+ replace_part (state, i, buf);
+ if (!r)
+ {
+ rv = 1;
+ goto bye;
+ }
+ break;
+ }
+ else if (flags & MUTT_PATTERN && ch == OP_EDITOR_COMPLETE)
+ {
+ char *p;
+ for (i = state->curpos; i && state->wbuf[i-1] != ',' &&
+ state->wbuf[i-1] != ':'; i--)
+ ;
+ for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
+ ;
+ my_wcstombs (buf, buflen, state->wbuf + i, state->curpos - i);
+ p = &buf[i];
+ while (p > buf && *(p-1) != '~')
+ p--;
+ if (*p == '~' && *(p+1) == 'y')
+ {
+ r = mutt_label_complete (buf, buflen, i, state->tabs);
+ replace_part (state, i, buf);
+ if (!r)
+ {
+ rv = 1;
+ goto bye;
+ }
+ }
+ else
+ goto self_insert;
+ break;
+ }
else if (flags & MUTT_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY)
{
/* invoke the query-menu to get more addresses */
{ "delete-thread", OP_DELETE_THREAD, "\004" },
{ "delete-subthread", OP_DELETE_SUBTHREAD, "\033d" },
{ "edit", OP_EDIT_MESSAGE, "e" },
+ { "edit-label", OP_EDIT_LABEL, NULL },
{ "edit-type", OP_EDIT_TYPE, "\005" },
{ "forward-message", OP_FORWARD_MESSAGE, "f" },
{ "flag-message", OP_FLAG_MESSAGE, "F" },
{ "set-flag", OP_MAIN_SET_FLAG, "w" },
{ "clear-flag", OP_MAIN_CLEAR_FLAG, "W" },
{ "edit", OP_EDIT_MESSAGE, "e" },
+ { "edit-label", OP_EDIT_LABEL, NULL },
{ "edit-type", OP_EDIT_TYPE, "\005" },
{ "forward-message", OP_FORWARD_MESSAGE, "f" },
{ "flag-message", OP_FLAG_MESSAGE, "F" },
#endif
WHERE char *Inbox;
WHERE char *Ispell;
+WHERE char *KeywordsSave;
WHERE char *Locale;
WHERE char *MailcapPath;
WHERE char *Maildir;
WHERE short TSSupported;
WHERE char *Username;
WHERE char *Visual;
+WHERE char *XlabelDelim;
WHERE char *CurrentFolder;
WHERE char *LastFolder;
WHERE const char *ReleaseDate;
WHERE HASH *Groups;
+WHERE HASH *Labels;
WHERE HASH *ReverseAlias;
WHERE LIST *AutoViewList INITVAL(0);
FREE (&pptr->table);
FREE (ptr); /* __FREE_CHECKED__ */
}
+
+struct hash_elem *hash_walk(const HASH *table, struct hash_walk_state *state)
+{
+ if (state->last && state->last->next)
+ {
+ state->last = state->last->next;
+ return state->last;
+ }
+
+ if (state->last)
+ state->index++;
+
+ while (state->index < table->nelem)
+ {
+ if (table->table[state->index])
+ {
+ state->last = table->table[state->index];
+ return state->last;
+ }
+ state->index++;
+ }
+
+ state->index = 0;
+ state->last = NULL;
+ return NULL;
+}
+
void (*destroy) (void *));
void hash_destroy (HASH ** hash, void (*destroy) (void *));
+struct hash_walk_state {
+ int index;
+ struct hash_elem *last;
+};
+
+struct hash_elem *hash_walk(const HASH *table, struct hash_walk_state *state);
+
#endif
d = dump_char(e->message_id, d, off, 0);
d = dump_char(e->supersedes, d, off, 0);
d = dump_char(e->date, d, off, 0);
- d = dump_char(e->x_label, d, off, convert);
d = dump_buffer(e->spam, d, off, convert);
d = dump_list(e->references, d, off, 0);
d = dump_list(e->in_reply_to, d, off, 0);
d = dump_list(e->userhdrs, d, off, convert);
+ d = dump_list(e->labels, d, off, convert);
return d;
}
restore_char(&e->message_id, d, off, 0);
restore_char(&e->supersedes, d, off, 0);
restore_char(&e->date, d, off, 0);
- restore_char(&e->x_label, d, off, convert);
restore_buffer(&e->spam, d, off, convert);
restore_list(&e->references, d, off, 0);
restore_list(&e->in_reply_to, d, off, 0);
restore_list(&e->userhdrs, d, off, convert);
+ restore_list(&e->labels, d, off, convert);
}
static int
case 'y':
if (optional)
- optional = hdr->env->x_label ? 1 : 0;
+ optional = hdr->env->labels ? 1 : 0;
- mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
+ mutt_format_s (dest, destlen, prefix,
+ mutt_labels(NULL, 0, hdr->env, NULL));
break;
case 'Y':
- if (hdr->env->x_label)
+ if (hdr->env->labels == NULL)
{
- i = 1; /* reduce reuse recycle */
- htmp = NULL;
- if (flags & MUTT_FORMAT_TREE
- && (hdr->thread->prev && hdr->thread->prev->message
- && hdr->thread->prev->message->env->x_label))
- htmp = hdr->thread->prev->message;
- else if (flags & MUTT_FORMAT_TREE
- && (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;
+ if (optional)
+ optional = 0;
+ mutt_format_s(dest, destlen, prefix, "");
+ break;
}
else
- i = 0;
-
- if (optional)
- optional = i;
-
- if (i)
- mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
- else
- mutt_format_s (dest, destlen, prefix, "");
+ {
+ char labels[HUGE_STRING];
+ char labelstmp[HUGE_STRING];
+
+ i = 1; /* reduce reuse recycle */
+ htmp = NULL;
+ if ((flags & MUTT_FORMAT_TREE) &&
+ hdr->thread->prev &&
+ hdr->thread->prev->message &&
+ hdr->thread->prev->message->env->labels)
+ htmp = hdr->thread->prev->message;
+ else if ((flags & MUTT_FORMAT_TREE) &&
+ hdr->thread->parent &&
+ hdr->thread->parent->message &&
+ hdr->thread->parent->message->env->labels)
+ htmp = hdr->thread->parent->message;
+
+ mutt_labels(labels, sizeof(labels), hdr->env, NULL);
+ if (htmp)
+ {
+ mutt_labels(labelstmp, sizeof(labelstmp), htmp->env, NULL);
+ if (htmp && mutt_strcasecmp (labels, labelstmp) == 0)
+ i = 0;
+ }
+
+ if (optional)
+ optional = i;
+
+ if (i)
+ mutt_format_s (dest, destlen, prefix, labels);
+ else
+ mutt_format_s (dest, destlen, prefix, "");
+ }
break;
}
}
}
+
+void mutt_label_ref_dec(ENVELOPE *env)
+{
+ uintptr_t count;
+ LIST *label;
+
+ if (!env || !env->labels || !Labels)
+ return;
+
+ for (label = env->labels; label; label = label->next)
+ {
+ if (label->data == NULL)
+ continue;
+ count = (uintptr_t)hash_find(Labels, label->data);
+ if (count)
+ {
+ hash_delete(Labels, label->data, NULL, NULL);
+ count--;
+ if (count > 0)
+ hash_insert(Labels, label->data, (void *)count, 0);
+ }
+ dprint(1, (debugfile, "--label %s: %d\n", label->data, count));
+ }
+}
+
+void mutt_label_ref_inc(ENVELOPE *env)
+{
+ uintptr_t count;
+ LIST *label;
+
+ if (!env || !env->labels || !Labels)
+ return;
+
+ for (label = env->labels; label; label = label->next)
+ {
+ if (label->data == NULL)
+ continue;
+ count = (uintptr_t)hash_find(Labels, label->data);
+ if (count)
+ hash_delete(Labels, label->data, NULL, NULL);
+ count++; /* was zero if not found */
+ hash_insert(Labels, label->data, (void *)count, 0);
+ dprint(1, (debugfile, "++label %s: %d\n", label->data, count));
+ }
+}
+
+/*
+ * set labels on a message
+ */
+static int label_message(HEADER *hdr, char *new)
+{
+ if (hdr == NULL)
+ return 0;
+ if (hdr->env->labels == NULL && new == NULL)
+ return 0;
+ if (hdr->env->labels != NULL && new != NULL)
+ {
+ char old[HUGE_STRING];
+ mutt_labels(old, sizeof(old), hdr->env, NULL);
+ if (!strcmp(old, new))
+ return 0;
+ }
+
+ if (hdr->env->labels != NULL)
+ {
+ mutt_label_ref_dec(hdr->env);
+ mutt_free_list(&hdr->env->labels);
+ }
+
+ if (new == NULL)
+ hdr->env->labels = NULL;
+ else
+ {
+ char *last, *label;
+
+ for (label = strtok_r(new, ",", &last); label;
+ label = strtok_r(NULL, ",", &last))
+ {
+ SKIPWS(label);
+ if (mutt_find_list(hdr->env->labels, label))
+ continue;
+ if (hdr->env->labels == NULL)
+ {
+ hdr->env->labels = mutt_new_list();
+ hdr->env->labels->data = safe_strdup(label);
+ }
+ else
+ mutt_add_list(hdr->env->labels, label);
+ }
+ mutt_label_ref_inc(hdr->env);
+ }
+ return hdr->changed = hdr->label_changed = 1;
+}
+
+int mutt_label_message(HEADER *hdr)
+{
+ char buf[LONG_STRING], *new;
+ int i;
+ int changed;
+
+ *buf = '\0';
+ if (hdr != NULL && hdr->env->labels != NULL)
+ mutt_labels(buf, sizeof(buf)-2, hdr->env, NULL);
+
+ /* add a comma-space so that new typing is a new keyword */
+ if (buf[0])
+ strcat(buf, ", "); /* __STRCAT_CHECKED__ */
+
+ if (mutt_get_field("Label: ", buf, sizeof(buf), MUTT_LABEL /* | MUTT_CLEAR */) != 0)
+ return 0;
+
+ new = buf;
+ SKIPWS(new);
+ if (new && *new)
+ {
+ char *p;
+ int len = strlen(new);
+ p = &new[len]; /* '\0' */
+ while (p > new)
+ {
+ if (!isspace((unsigned char)*(p-1)) && *(p-1) != ',')
+ break;
+ p--;
+ }
+ *p = '\0';
+ }
+ if (*new == '\0')
+ new = NULL;
+
+ changed = 0;
+ if (hdr != NULL) {
+ changed += label_message(hdr, new);
+ } else {
+#define HDR_OF(index) Context->hdrs[Context->v2r[(index)]]
+ for (i = 0; i < Context->vcount; ++i) {
+ if (HDR_OF(i)->tagged)
+ if (label_message(HDR_OF(i), new)) {
+ ++changed;
+ }
+ }
+ }
+
+ return changed;
+}
+
+/* scan a context (mailbox) and hash all labels we find */
+void mutt_scan_labels(CONTEXT *ctx)
+{
+ int i;
+
+ if (!ctx)
+ return;
+
+ for (i = 0; i < ctx->msgcount; i++)
+ if (ctx->hdrs[i]->env->labels)
+ mutt_label_ref_inc(ctx->hdrs[i]->env);
+}
+
+
+char *mutt_labels(char *dst, int sz, ENVELOPE *env, char *sep)
+{
+ static char sbuf[HUGE_STRING];
+ int off = 0;
+ int len;
+ LIST *label;
+
+ if (sep == NULL)
+ sep = ", ";
+
+ if (dst == NULL)
+ {
+ dst = sbuf;
+ sz = sizeof(sbuf);
+ }
+
+ *dst = '\0';
+
+ for (label = env->labels; label; label = label->next)
+ {
+ if (label->data == NULL)
+ continue;
+ len = MIN(mutt_strlen(label->data), sz-off);
+ strfcpy(&dst[off], label->data, len+1);
+ off += len;
+ if (label->next)
+ {
+ len = MIN(mutt_strlen(sep), sz-off);
+ strfcpy(&dst[off], sep, len+1);
+ off += len;
+ }
+ }
+
+ return dst;
+}
* we delete the message and reupload it.
* This works better if we're expunging, of course. */
if ((h->env && (h->env->refs_changed || h->env->irt_changed)) ||
- h->attach_del)
+ h->attach_del || h->label_changed)
{
mutt_message (_("Saving changed messages... [%d/%d]"), n+1,
ctx->msgcount);
dprint (1, (debugfile, "imap_sync_mailbox: Error opening mailbox in append mode\n"));
else
_mutt_save_message (h, appendctx, 1, 0, 0);
+ h->label_changed = 0;
}
}
}
int rc, mfhrc, oldmsgcount;
int fetchlast = 0;
int maxuid = 0;
- static const char * const want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO LINES LIST-POST X-LABEL";
+ static const char * const want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO LINES LIST-POST X-LABEL X-KEYWORDS X-MOZILLA-KEYS KEYWORDS";
progress_t progress;
int retval = -1;
IMAP_CACHE *cache;
int read;
int rc;
+
/* Sam's weird courier server returns an OK response even when FETCH
* fails. Thanks Sam. */
short fetched = 0;
return NULL;
}
+
+int mutt_label_complete (char *buffer, size_t len, int pos, int numtabs)
+{
+ char *pt = buffer;
+ int spaces; /* keep track of the number of leading spaces on the line */
+ int prefix;
+
+ SKIPWS (buffer);
+ spaces = buffer - pt;
+
+ for (pt = buffer; pt && *pt && *(pt+1); pt++);
+ for (; pt > buffer && !isspace(*(pt-1)); pt--);
+ prefix = pt - buffer;
+
+ /* first TAB. Collect all the matches */
+ if (numtabs == 1)
+ {
+ struct hash_elem *entry;
+ struct hash_walk_state state;
+
+ Num_matched = 0;
+ strfcpy (User_typed, pt, sizeof (User_typed));
+ memset (Matches, 0, Matches_listsize);
+ memset (Completed, 0, sizeof (Completed));
+ memset (&state, 0, sizeof(state));
+ while ((entry = hash_walk(Labels, &state)))
+ candidate (Completed, User_typed, entry->key, sizeof (Completed));
+ matches_ensure_morespace (Num_matched);
+ qsort(Matches, Num_matched, sizeof(char *), (sort_t *) mutt_strcasecmp);
+ Matches[Num_matched++] = User_typed;
+
+ /* All matches are stored. Longest non-ambiguous string is ""
+ * i.e. dont change 'buffer'. Fake successful return this time */
+ if (User_typed[0] == 0)
+ return 1;
+ }
+
+ if (Completed[0] == 0 && User_typed[0])
+ return 0;
+
+ /* Num_matched will _always_ be atleast 1 since the initial
+ * user-typed string is always stored */
+ if (numtabs == 1 && Num_matched == 2)
+ snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
+ else if (numtabs > 1 && Num_matched > 2)
+ /* cycle thru all the matches */
+ snprintf(Completed, sizeof(Completed), "%s",
+ Matches[(numtabs - 2) % Num_matched]);
+
+ /* return the completed label */
+ strncpy (&buffer[prefix], Completed, len - spaces);
+
+ return 1;
+}
+
** from your spool mailbox to your $$mbox mailbox, or as a result of
** a ``$mbox-hook'' command.
*/
+ { "keywords_legacy", DT_BOOL, R_NONE, OPTKEYWORDSLEGACY, 1 },
+ /*
+ ** .pp
+ ** If \fIset\fP, keywords/labels/tags will be written to whatever
+ ** legacy, nonstandard headers (X-Label, X-Keywords, X-Mozilla-Keys)
+ ** they were sourced from.
+ ** .pp
+ ** If both ``$$keywords_legacy'' and
+ ** ``$$keywords_standard'' are \fCfalse\fP, mutt will save keywords
+ ** to legacy headers to ensure that it does not lose your labels.
+ */
+ { "keywords_standard", DT_BOOL, R_NONE, OPTKEYWORDSSTANDARD, 0 },
+ /*
+ ** .pp
+ ** If \fIset\fP, keywords/labels/tags will be written to the
+ ** RFC2822-standard Keywords: header; this may imply a conversion from
+ ** legacy headers.
+ ** .pp
+ ** If both ``$$keywords_legacy'' and
+ ** ``$$keywords_standard'' are \fCfalse\fP, mutt will save keywords
+ ** to legacy headers to ensure that it does not lose your labels.
+ */
{ "locale", DT_STR, R_BOTH, UL &Locale, UL "C" },
/*
** .pp
{"xterm_set_titles", DT_SYN, R_NONE, UL "ts_enabled", 0 },
/*
*/
+ { "xlabel_delimiter", DT_STR, R_NONE, UL &XlabelDelim, UL "" },
+ /*
+ ** .pp
+ ** The character used to delimit distinct keywords in X-Label headers.
+ ** X-Label is primarily a Mutt artifact, and the semantics of the field
+ ** were never defined: it is free-form text. However interaction with
+ ** X-Keywords:, X-Mozilla-Keys:, and Keywords: requires that we adopt
+ ** some means of identifying separate keywords within the field. Set
+ ** this to your personal convention.
+ ** .pp
+ ** This affect both parsing existing X-Label headers and writing new
+ ** X-Label headers. You can modify this variable in runtime to accomplish
+ ** various kinds of conversion.
+ */
/*--*/
{ NULL, 0, 0, 0, 0 }
};
{ "to", SORT_TO },
{ "score", SORT_SCORE },
{ "spam", SORT_SPAM },
+ { "label", SORT_LABEL },
{ NULL, 0 }
};
{ "to", SORT_TO },
{ "score", SORT_SCORE },
{ "spam", SORT_SPAM },
+ { "label", SORT_LABEL },
{ NULL, 0 }
};
#ifdef USE_SIDEBAR
mutt_sb_set_open_buffy ();
#endif
+ Labels = hash_create (131, 0);
+ mutt_scan_labels(Context);
mutt_index_menu ();
if (Context)
FREE (&Context);
+ if (Labels)
+ hash_destroy(&Labels, NULL);
}
#ifdef USE_IMAP
imap_logout_all ();
{
HEADER *h = ctx->hdrs[msgno];
- if (h->attach_del ||
+ if (h->attach_del || h->label_changed ||
(h->env && (h->env->refs_changed || h->env->irt_changed)))
if (mh_rewrite_message (ctx, msgno) != 0)
return -1;
{
HEADER *h = ctx->hdrs[msgno];
- if (h->attach_del ||
+ if (h->attach_del || h->label_changed ||
(h->env && (h->env->refs_changed || h->env->irt_changed)))
{
/* when doing attachment deletion/rethreading, fall back to the MH case. */
}
}
else if (ctx->hdrs[i]->changed || ctx->hdrs[i]->attach_del ||
+ ctx->hdrs[i]->label_changed ||
(ctx->magic == MUTT_MAILDIR
&& (option (OPTMAILDIRTRASH) || ctx->hdrs[i]->trash)
&& (ctx->hdrs[i]->deleted != ctx->hdrs[i]->trash)))
#define MUTT_CLEAR (1<<5) /* clear input if printable character is pressed */
#define MUTT_COMMAND (1<<6) /* do command completion */
#define MUTT_PATTERN (1<<7) /* pattern mode - only used for history classes */
+#define MUTT_LABEL (1<<8) /* do label completion */
/* flags for mutt_get_token() */
#define MUTT_TOKEN_EQUAL 1 /* treat '=' as a special */
#define MUTT_SPAM 1
#define MUTT_NOSPAM 2
+/* flags for keywords headers */
+#define MUTT_X_LABEL (1<<0) /* introduced to mutt in 2000 */
+#define MUTT_X_KEYWORDS (1<<1) /* used in c-client, dovecot */
+#define MUTT_X_MOZILLA_KEYS (1<<2) /* tbird */
+#define MUTT_KEYWORDS (1<<3) /* rfc2822 */
+
/* boolean vars */
enum
{
OPTIMPLICITAUTOVIEW,
OPTINCLUDEONLYFIRST,
OPTKEEPFLAGGED,
+ OPTKEYWORDSLEGACY,
+ OPTKEYWORDSSTANDARD,
OPTMAILCAPSANITIZE,
OPTMAILCHECKRECENT,
OPTMAILCHECKSTATS,
char *message_id;
char *supersedes;
char *date;
- char *x_label;
BUFFER *spam;
LIST *references; /* message references (in reverse order) */
LIST *in_reply_to; /* in-reply-to header content */
LIST *userhdrs; /* user defined headers */
+ LIST *labels;
+ int kwtypes;
unsigned int irt_changed : 1; /* In-Reply-To changed to link/break threads */
unsigned int refs_changed : 1; /* References changed to break thread */
* This flag is used by the maildir_trash
* option.
*/
+ unsigned int label_changed : 1; /* editable - used for syncing */
/* timezone of the sender of this message */
unsigned int zhours : 5;
FREE (&(*p)->message_id);
FREE (&(*p)->supersedes);
FREE (&(*p)->date);
- FREE (&(*p)->x_label);
mutt_buffer_free (&(*p)->spam);
mutt_free_list (&(*p)->references);
mutt_free_list (&(*p)->in_reply_to);
mutt_free_list (&(*p)->userhdrs);
+ mutt_label_ref_dec ((*p));
+ mutt_free_list (&(*p)->labels);
FREE (p); /* __FREE_CHECKED__ */
}
MOVE_ELEM(message_id);
MOVE_ELEM(supersedes);
MOVE_ELEM(date);
- MOVE_ELEM(x_label);
+ MOVE_ELEM(labels);
if (!base->refs_changed)
{
MOVE_ELEM(references);
redraw = REDRAW_FULL;
break;
+ case OP_EDIT_LABEL:
+ CHECK_MODE(IsHeader (extra));
+ rc = mutt_label_message(extra->hdr);
+ if (rc > 0) {
+ Context->changed = 1;
+ redraw = REDRAW_FULL;
+ mutt_message ("%d label%s changed.", rc, rc == 1 ? "" : "s");
+ }
+ else {
+ mutt_message _("No labels changed.");
+ }
+ break;
case OP_MAIL_KEY:
if (!(WithCrypto & APPLICATION_PGP))
{
int matched = 0;
LIST *last = NULL;
+ int kwtype = 0;
if (lastp)
last = *lastp;
matched = 1;
}
break;
-
+
+ case 'k':
+ if (!ascii_strcasecmp (line+1, "eywords"))
+ {
+ kwtype = MUTT_KEYWORDS;
+ }
+ break;
+
case 'l':
if (!ascii_strcasecmp (line + 1, "ines"))
{
}
else if (ascii_strcasecmp (line+1, "-label") == 0)
{
- FREE(&e->x_label);
- e->x_label = safe_strdup(p);
- matched = 1;
+ kwtype = MUTT_X_LABEL;
}
-
+ else if (!ascii_strcasecmp (line+1, "-keywords"))
+ {
+ kwtype = MUTT_X_KEYWORDS;
+ }
+ else if (!ascii_strcasecmp (line+1, "-mozilla-keys"))
+ {
+ kwtype = MUTT_X_MOZILLA_KEYS;
+ }
+
default:
break;
}
-
+
/* Keep track of the user-defined headers */
if (!matched && user_hdrs)
{
rfc2047_decode (&last->data);
}
+ if (kwtype)
+ {
+ char *last, *label;
+ char *text = safe_strdup(p);
+ char *sep;
+
+ if (kwtype == MUTT_KEYWORDS)
+ sep = ",";
+ else if (kwtype == MUTT_X_LABEL)
+ sep = XlabelDelim;
+ else
+ sep = " ";
+
+ rfc2047_decode(&text);
+ if (sep == NULL || *sep == '\0')
+ {
+ SKIPWS(text);
+ if (!mutt_find_list(e->labels, text))
+ {
+ if (e->labels)
+ mutt_add_list(e->labels, text);
+ else
+ {
+ e->labels = mutt_new_list();
+ e->labels->data = safe_strdup(text);
+ }
+ }
+ }
+ else for (label = strtok_r(text, sep, &last); label;
+ label = strtok_r(NULL, sep, &last))
+ {
+ SKIPWS(label);
+ if (mutt_find_list(e->labels, label))
+ continue;
+ if (e->labels)
+ mutt_add_list(e->labels, label);
+ else
+ {
+ e->labels = mutt_new_list();
+ e->labels->data = safe_strdup(label);
+ }
+ }
+ e->kwtypes |= kwtype;
+ kwtype = 0;
+ matched = 1;
+ }
+
done:
*lastp = last;
return matched;
}
-
+
/* mutt_read_rfc822_header() -- parses a RFC822 header
*
rfc2047_decode_adrlist (e->mail_followup_to);
rfc2047_decode_adrlist (e->return_path);
rfc2047_decode_adrlist (e->sender);
- rfc2047_decode (&e->x_label);
if (e->subject)
{
break;
return (pat->not ^ ((h->security & APPLICATION_PGP) && (h->security & PGPKEY)));
case MUTT_XLABEL:
- return (pat->not ^ (h->env->x_label && patmatch (pat, h->env->x_label) == 0));
+ {
+ LIST *label;
+ int result = 0;
+ for (label = h->env->labels; label; label = label->next)
+ {
+ if (label->data == NULL)
+ continue;
+ result = patmatch (pat, label->data) == 0;
+ if (result)
+ break;
+ }
+ return pat->not ^ result;
+ }
case MUTT_HORMEL:
return (pat->not ^ (h->env->spam && h->env->spam->data && patmatch (pat, h->env->spam->data) == 0));
case MUTT_DUPLICATED:
void mutt_edit_file (const char *, const char *);
void mutt_edit_headers (const char *, const char *, HEADER *, char *, size_t);
int mutt_filter_unprintable (char **);
+void mutt_label_ref_dec(ENVELOPE *);
+void mutt_label_ref_inc(ENVELOPE *);
+int mutt_label_message (HEADER *);
+void mutt_scan_labels (CONTEXT *);
+int mutt_label_complete (char *, size_t, int, int);
+char *mutt_labels(char *, int, ENVELOPE *, char *);
void mutt_curses_error (const char *, ...);
void mutt_curses_message (const char *, ...);
void mutt_encode_descriptions (BODY *, short);
rfc2047_encode_adrlist (env->from, "From");
rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
rfc2047_encode_adrlist (env->reply_to, "Reply-To");
- rfc2047_encode_string (&env->x_label);
if (env->subject)
{
rfc2047_decode_adrlist (env->from);
rfc2047_decode_adrlist (env->reply_to);
rfc2047_decode (&env->subject);
- rfc2047_decode (&env->x_label);
}
static int _mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to, const char *resent_from,
return (SORTCODE(result));
}
+int compare_label (const void *a, const void *b)
+{
+ HEADER **ppa = (HEADER **) a;
+ HEADER **ppb = (HEADER **) b;
+ int ahas, bhas, result = 0;
+ LIST *la, *lb;
+
+ /* As with compare_spam, not all messages will have the x-label
+ * property. Blank X-Labels are treated as null in the index
+ * display, so we'll consider them as null for sort, too. */
+ ahas = (*ppa)->env && (*ppa)->env->labels;
+ bhas = (*ppb)->env && (*ppb)->env->labels;
+
+ /* First we bias toward a message with a label, if the other does not. */
+ if (ahas && !bhas)
+ return (SORTCODE(-1));
+ if (!ahas && bhas)
+ return (SORTCODE(1));
+
+ /* If neither has a label, use aux sort. */
+ if (!ahas && !bhas)
+ {
+ AUXSORT(result, a, b);
+ return (SORTCODE(result));
+ }
+
+ /* If both have a label, we just do a lexical compare. */
+ for (la = (*ppa)->env->labels, lb = (*ppb)->env->labels;
+ la && la->data && lb && lb->data && result == 0;
+ la = la->next, lb = lb->next)
+ {
+ result = mutt_strcasecmp(la->data, lb->data);
+ }
+ if (result == 0 && la == NULL)
+ return (SORTCODE(-1));
+ if (result == 0 && lb == NULL)
+ return (SORTCODE(1));
+ return (SORTCODE(result));
+}
+
sort_t *mutt_get_sort_func (int method)
{
switch (method & SORT_MASK)
return (compare_score);
case SORT_SPAM:
return (compare_spam);
+ case SORT_LABEL:
+ return (compare_label);
default:
return (NULL);
}
#define SORT_COUNT_NEW 16
#define SORT_FLAGGED 17
#define SORT_PATH 18
+#define SORT_LABEL 19
/* dgc: Sort & SortAux are shorts, so I'm bumping these bitflags up from
* bits 4 & 5 to bits 8 & 9 to make room for more sort keys in the future. */