ignore = 0;
}
- if (flags & CH_UPDATE_LABEL &&
- mutt_strncasecmp ("X-Label:", buf, 8) == 0)
- continue;
+ 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->xlabel_changed)
+ if (flags & CH_UPDATE_LABEL && h->label_changed)
{
- h->xlabel_changed = 0;
- if (h->env->x_label != NULL)
- if (fprintf(out, "X-Label: %s\n", h->env->x_label) !=
- 10 + strlen(h->env->x_label))
+ 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 & M_X_LABEL) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, XlabelDelim);
+ tmp = 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 & M_X_KEYWORDS) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, " ");
+ tmp = 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 & M_X_MOZILLA_KEYS) &&
+ (option(OPTKEYWORDSLEGACY) || option(OPTKEYWORDSSTANDARD) == 0))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, " ");
+ tmp = 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 & M_KEYWORDS) ||
+ option(OPTKEYWORDSSTANDARD)))
+ {
+ mutt_labels(buf, sizeof(buf), h->env, NULL);
+ tmp = 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)
_mutt_make_string (prefix, sizeof (prefix), NONULL (Prefix), Context, hdr, 0);
}
- if (hdr->xlabel_changed)
+ if (hdr->label_changed)
chflags |= CH_UPDATE_LABEL;
if ((flags & M_CM_NOHEADER) == 0)
symbol (<literal>=</literal>) as a numeric prefix (like the minus
above), it will force the string to be centered within its minimum space
range. For example, <literal>%=14y</literal> will reserve 14 characters
-for the %y expansion — that's the X-Label: header, in <link
-linkend="index-format">$index_format</link>. If the expansion results in
+for the %y expansion — that's the set of message keywords (formerly
+X-Label). If the expansion results in
a string less than 14 characters, it will be centered in a 14-character
space. If the X-Label for a message were <quote>test</quote>, that
expansion would look like
<row><entry>~V</entry><entry>cryptographically verified messages</entry></row>
<row><entry>~x <emphasis>EXPR</emphasis></entry><entry>messages which contain <emphasis>EXPR</emphasis> in the <quote>References</quote> or <quote>In-Reply-To</quote> field</entry></row>
<row><entry>~X [<emphasis>MIN</emphasis>]-[<emphasis>MAX</emphasis>]</entry><entry>messages with <emphasis>MIN</emphasis> to <emphasis>MAX</emphasis> attachments *)</entry></row>
-<row><entry>~y <emphasis>EXPR</emphasis></entry><entry>messages which contain <emphasis>EXPR</emphasis> in the <quote>X-Label</quote> field</entry></row>
+<row><entry>~y <emphasis>EXPR</emphasis></entry><entry>messages which contain <emphasis>EXPR</emphasis> in their keywords</entry></row>
<row><entry>~z [<emphasis>MIN</emphasis>]-[<emphasis>MAX</emphasis>]</entry><entry>messages with a size in the range <emphasis>MIN</emphasis> to <emphasis>MAX</emphasis> *) **)</entry></row>
<row><entry>~=</entry><entry>duplicated messages (see <link linkend="duplicate-threads">$duplicate_threads</link>)</entry></row>
<row><entry>~$</entry><entry>unreferenced messages (requires threaded view)</entry></row>
<quote>Reply-To</quote> field will be used when present.
</para>
-<para>
-The <quote>X-Label:</quote> header field can be used to further identify
-mailing lists or list subject matter (or just to annotate messages
-individually). The <link linkend="index-format">$index_format</link>
-variable's <quote>%y</quote> and <quote>%Y</quote> expandos can be used
-to expand <quote>X-Label:</quote> fields in the index, and Mutt's
-pattern-matcher can match regular expressions to <quote>X-Label:</quote>
-fields with the <quote>~y</quote> selector. <quote>X-Label:</quote> is
-not a standard message header field, but it can easily be inserted by
-procmail and other mail filtering agents.
-</para>
-
-<para>
-You can change or delete the <quote>X-Label:</quote> field within
-Mutt using the <quote>edit-label</quote> command, bound to the
-<quote>y</quote> key by default. This works for tagged messages, too.
-While in the edit-label function, pressing the <complete>
-binding (TAB, by default) will perform completion against all labels
-currently in use.
-</para>
-
<para>
Lastly, Mutt has the ability to <link linkend="sort">sort</link> the
mailbox into <link linkend="threads">threads</link>. A thread is a
</sect1>
+<sect1 id="using-keywords">
+<title>Keyword Management</title>
+
+<para>
+Mutt has supported textual labels (usually known as X-Labels after
+the header that we use to store them) for many years. Since we
+initially added support for X-Lanels, however, the larger community
+has evolved more common ways of using and managing such labels, often
+known as <quote>keywords</quote> or <quote>tags</quote>.
+</para>
+
+<para>
+If you are new to Mutt or to using keywords in Mutt, you only need
+to know that the <edit-label> binding (<literal>y</literal> by
+default) will edit keywords, and that you can search for keywords
+using the <literal>~y</literal> pattern, and use the <literal>%y</literal>
+expando to display it in your <literal>$index_format</literal>. You also
+can sort by keyword. Keywords that you set will be stored to the
+<literal>X-Label:</literal> header by default.
+</para>
+
+<para>
+If you've been using X-Labels for a while, things have grown slightly.
+Mutt still supports X-Labels much as it has since 2000, but the scope
+of this support has expanded to support three additional header-based
+techniques for storing keyword metadata on messages:
+</para>
+
+<variablelist>
+
+<varlistentry>
+<term>X-Keywords</term>
+<listitem>
+<para>
+Informal design; space-delimited keywords
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>X-Mozilla-Keys</term>
+<listitem>
+<para>
+Informal design used by Mozilla-based agents; space-delimited keywords
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>Keywords</term>
+<listitem>
+<para>
+Standardized in RFC2822 (2001); comma-space-delimited keywords
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>X-Label</term>
+<listitem>
+<para>
+Mutt-specific design; freeform text (but see <link linkend="xlabel-delimiter">$xlabel_delimiter</link>)
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+
+<para>
+With X-Label, mutt's only notion of a message keyword was the literal
+string value of the X-Label header. Under the new, integrated support,
+each message may have a list of distinct message keywords. When reading
+keywords from one of the headers in the list above, the header value is
+split on the indicated delimiter (space or comma-space) for X-Keywords:,
+X-Mozilla-Keys:, and Keywords:. By default, X-Label: is parsed as a
+single keyword. By setting $xlabel_delimiter, you can force splitting
+of X-Label: as well.
+</para>
+
+<para>
+Two boolean variables control how keywords are saved when writing
+messages to a mailbox. The default settings preserve backward
+compatibility within mutt completely, but by changing these
+values you can transition to more standard keyword storage. <link
+linkend="keywords-legacy">$keywords_legacy</link>, if set, will tell
+mutt to use only "legacy" headers -- i.e., <literal>X-Keywords:</literal>,
+<literal>X-Mozilla-Keys</literal>, <literal>Keywords</literal>, or
+<literal>X-Label:</literal>. Keywords will be saved to whichever
+header was in use by the message the keyword was read from. If
+<link linkend="keywords-standard">$keywords_standard</link> is
+set, keywords will be saved without exception to the standard
+<literal>Keywords:</literal> header. (If both are set, both will be used;
+if both are unset, legacy headers are used.) Additionally, <link
+linkend="xlabel-delimiter">$xlabel_delimiter</link> is used to format
+the X-Label: header on saves.
+</para>
+
+<para>
+To migrate completely to the new standard,
+unset <literal>$keywords_legacy</literal> and set
+<literal>$keywords_standard</literal>, and set
+<literal>$xlabel_delimiter</literal> either to what you currently use to
+delimit keywords in X-Labels, or to <quote>, </quote> (comma
+space).
+</para>
+
+<para>
+Note that it is common practice to insert <literal>X-Label:</literal> or
+other keyword headers from proxmail or other mail filters. This is
+a useful trick for categorizing messages en masse as they are delivered
+to your inbox, and it is fully compatible with the new keywords code.
+</para>
+
+</sect1>
+
<sect1 id="new-mail">
<title>New Mail Detection</title>
#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;
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 & M_FORMAT_TREE
- && (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->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 & M_FORMAT_TREE) &&
+ hdr->thread->prev &&
+ hdr->thread->prev->message &&
+ hdr->thread->prev->message->env->labels)
+ htmp = hdr->thread->prev->message;
+ else if ((flags & M_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;
}
}
-static void label_ref_dec(char *label)
+void mutt_label_ref_dec(ENVELOPE *env)
{
uintptr_t count;
+ LIST *label;
- count = (uintptr_t)hash_find(Labels, label);
- if (count)
+ for (label = env->labels; label; label = label->next)
{
- hash_delete(Labels, label, NULL, NULL);
- count--;
- if (count > 0)
- hash_insert(Labels, label, (void *)count, 0);
+ 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));
}
}
-static void label_ref_inc(char *label)
+void mutt_label_ref_inc(ENVELOPE *env)
{
uintptr_t count;
+ LIST *label;
- count = (uintptr_t)hash_find(Labels, label);
- if (count)
- hash_delete(Labels, label, NULL, NULL);
- count++; /* was zero if not found */
- hash_insert(Labels, label, (void *)count, 0);
+ 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));
+ }
}
/*
- * add an X-Label: field.
+ * set labels on a message
*/
static int label_message(HEADER *hdr, char *new)
{
if (hdr == NULL)
return 0;
- if (hdr->env->x_label == NULL && new == NULL)
- return 0;
- if (hdr->env->x_label != NULL && new != NULL &&
- strcmp(hdr->env->x_label, new) == 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->x_label != NULL)
+ if (hdr->env->labels != NULL)
{
- label_ref_dec(hdr->env->x_label);
- FREE(&hdr->env->x_label);
+ mutt_label_ref_dec(hdr->env);
+ mutt_free_list(&hdr->env->labels);
}
if (new == NULL)
- hdr->env->x_label = NULL;
+ hdr->env->labels = NULL;
else
{
- hdr->env->x_label = safe_strdup(new);
- label_ref_inc(hdr->env->x_label);
+ 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->xlabel_changed = 1;
+ return hdr->changed = hdr->label_changed = 1;
}
int mutt_label_message(HEADER *hdr)
int changed;
*buf = '\0';
- if (hdr != NULL && hdr->env->x_label != NULL) {
- strncpy(buf, hdr->env->x_label, LONG_STRING);
- }
+ 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), M_LABEL /* | M_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;
int i;
for (i = 0; i < ctx->msgcount; i++)
- if (ctx->hdrs[i]->env->x_label)
- label_ref_inc(ctx->hdrs[i]->env->x_label);
+ 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->xlabel_changed)
+ 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->xlabel_changed = 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;
- char *x_label = NULL;
+
/* Sam's weird courier server returns an OK response even when FETCH
* fails. Thanks Sam. */
short fetched = 0;
{
char *pt = buffer;
int spaces; /* keep track of the number of leading spaces on the line */
+ int prefix;
SKIPWS (buffer);
spaces = buffer - pt;
- pt = buffer + pos - spaces;
- while ((pt > buffer) && !isspace ((unsigned char) *pt))
- 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)
Matches[(numtabs - 2) % Num_matched]);
/* return the completed label */
- strncpy (buffer, Completed, len - spaces);
+ 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 }
};
{
HEADER *h = ctx->hdrs[msgno];
- if (h->attach_del || h->xlabel_changed ||
+ 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 || h->xlabel_changed ||
+ 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]->xlabel_changed ||
+ ctx->hdrs[i]->label_changed ||
(ctx->magic == M_MAILDIR
&& (option (OPTMAILDIRTRASH) || ctx->hdrs[i]->trash)
&& (ctx->hdrs[i]->deleted != ctx->hdrs[i]->trash)))
#define M_SPAM 1
#define M_NOSPAM 2
+/* flags for keywords headers */
+#define M_X_LABEL (1<<0) /* introduced to mutt in 2000 */
+#define M_X_KEYWORDS (1<<1) /* used in c-client, dovecot */
+#define M_X_MOZILLA_KEYS (1<<2) /* tbird */
+#define M_KEYWORDS (1<<3) /* rfc2822 */
+
/* boolean vars */
enum
{
OPTIMPLICITAUTOVIEW,
OPTINCLUDEONLYFIRST,
OPTKEEPFLAGGED,
+ OPTKEYWORDSLEGACY,
+ OPTKEYWORDSSTANDARD,
OPTMAILCAPSANITIZE,
OPTMAILCHECKRECENT,
OPTMAILDIRTRASH,
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 xlabel_changed : 1; /* editable - used for syncing */
+ 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);
{
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 = M_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 = M_X_LABEL;
}
-
+ else if (!ascii_strcasecmp (line+1, "-keywords"))
+ {
+ kwtype = M_X_KEYWORDS;
+ }
+ else if (!ascii_strcasecmp (line+1, "-mozilla-keys"))
+ {
+ kwtype = M_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 = strdup(p);
+ char *sep;
+
+ if (kwtype == M_KEYWORDS)
+ sep = ",";
+ else if (kwtype == M_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 M_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 M_HORMEL:
return (pat->not ^ (h->env->spam && h->env->spam->data && patmatch (pat, h->env->spam->data) == 0));
case M_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,
HEADER **ppa = (HEADER **) a;
HEADER **ppb = (HEADER **) b;
int ahas, bhas, result;
+ 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->x_label && *((*ppa)->env->x_label);
- bhas = (*ppb)->env && (*ppb)->env->x_label && *((*ppb)->env->x_label);
+ 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)
}
/* If both have a label, we just do a lexical compare. */
- result = mutt_strcasecmp((*ppa)->env->x_label, (*ppb)->env->x_label);
+ 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));
}