This change introduces IMAP keyword support.
* index_format gets %g, %G, %J and %Gx to display them
* pattern gets g to search/match imap keywords
* colors.c gets index_tag/index_tags to color them
* OPS gets modify-labels to edit them
* tag-transforms/tag-formats/hidden_tags can be display them in more sexy manner
Most of the code is shared with the notmuch backend.
Upstream: https://dev.mutt.org/trac/ticket/3210
Closes neomutt/neomutt#225
break;
}
+#endif
+#if defined(USE_NOTMUCH) || defined(USE_IMAP)
case OP_MAIN_MODIFY_LABELS:
case OP_MAIN_MODIFY_LABELS_THEN_HIDE:
{
- if (!Context || (Context->magic != MUTT_NOTMUCH))
+ if (!Context || ((Context->magic != MUTT_NOTMUCH) && (Context->magic != MUTT_IMAP)))
{
mutt_message(_("No virtual folder, aborting."));
break;
}
- CHECK_MSGCOUNT;
- CHECK_VISIBLE;
- *buf = '\0';
- if (mutt_get_field("Add/remove labels: ", buf, sizeof(buf), MUTT_NM_TAG) || !*buf)
+#ifdef USE_IMAP
+ if (Context->magic == MUTT_IMAP)
{
- mutt_message(_("No label specified, aborting."));
+ CHECK_MSGCOUNT;
+ CHECK_VISIBLE;
+ CHECK_READONLY;
+ rc = imap_keywords_message(tag ? NULL : CURHDR, op == OP_MAIN_MODIFY_LABELS_THEN_HIDE);
+ if (rc > 0)
+ {
+ Context->changed = 1;
+ menu->redraw = REDRAW_FULL;
+ mutt_message(_("%d keywords changed."), rc);
+ }
+ else if (rc == 0)
+ mutt_message(_("No keywords changed."));
break;
}
- if (tag)
+#endif
+#ifdef USE_NOTMUCH
+ if (Context->magic == MUTT_NOTMUCH)
{
- char msgbuf[STRING];
- struct Progress progress;
- int px;
-
- if (!Context->quiet)
+ CHECK_MSGCOUNT;
+ CHECK_VISIBLE;
+ *buf = '\0';
+ if (mutt_get_field("Add/remove labels: ", buf, sizeof(buf), MUTT_NM_TAG) || !*buf)
{
- snprintf(msgbuf, sizeof(msgbuf), _("Update labels..."));
- mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, 1, Context->tagged);
+ mutt_message(_("No label specified, aborting."));
+ break;
}
- nm_longrun_init(Context, true);
- for (px = 0, j = 0; j < Context->vcount; j++)
+ if (tag)
{
- if (Context->hdrs[Context->v2r[j]]->tagged)
- {
- if (!Context->quiet)
- mutt_progress_update(&progress, ++px, -1);
- nm_modify_message_tags(Context, Context->hdrs[Context->v2r[j]], buf);
+ char msgbuf[STRING];
+ struct Progress progress;
+ int px;
- bool still_queried =
- nm_message_is_still_queried(Context, Context->hdrs[Context->v2r[j]]);
- if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+ if (!Context->quiet)
+ {
+ snprintf(msgbuf, sizeof(msgbuf), _("Update labels..."));
+ mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, 1,
+ Context->tagged);
+ }
+ nm_longrun_init(Context, true);
+ for (px = 0, j = 0; j < Context->vcount; j++)
+ {
+ if (Context->hdrs[Context->v2r[j]]->tagged)
{
- Context->hdrs[Context->v2r[j]]->quasi_deleted = !still_queried;
- Context->changed = true;
+ if (!Context->quiet)
+ mutt_progress_update(&progress, ++px, -1);
+ nm_modify_message_tags(Context, Context->hdrs[Context->v2r[j]], buf);
+ bool still_queried =
+ nm_message_is_still_queried(Context, Context->hdrs[Context->v2r[j]]);
+ if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+ {
+ Context->hdrs[Context->v2r[j]]->quasi_deleted = !still_queried;
+ Context->changed = true;
+ }
}
}
+ nm_longrun_done(Context);
+ menu->redraw = REDRAW_STATUS | REDRAW_INDEX;
}
- nm_longrun_done(Context);
- menu->redraw = REDRAW_STATUS | REDRAW_INDEX;
- }
- else
- {
- if (nm_modify_message_tags(Context, CURHDR, buf))
- {
- mutt_message(_("Failed to modify labels, aborting."));
- break;
- }
- if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
- {
- bool still_queried = nm_message_is_still_queried(Context, CURHDR);
- CURHDR->quasi_deleted = !still_queried;
- Context->changed = true;
- }
- if (menu->menu == MENU_PAGER)
- {
- op = OP_DISPLAY_MESSAGE;
- continue;
- }
- if (option(OPT_RESOLVE))
+ else
{
- if ((menu->current = ci_next_undeleted(menu->current)) == -1)
+ if (nm_modify_message_tags(Context, CURHDR, buf))
{
- menu->current = menu->oldcurrent;
- menu->redraw = REDRAW_CURRENT;
+ mutt_message(_("Failed to modify labels, aborting."));
+ break;
+ }
+ if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+ {
+ bool still_queried = nm_message_is_still_queried(Context, CURHDR);
+ CURHDR->quasi_deleted = !still_queried;
+ Context->changed = true;
+ }
+ if (menu->menu == MENU_PAGER)
+ {
+ op = OP_DISPLAY_MESSAGE;
+ continue;
+ }
+ if (option(OPT_RESOLVE))
+ {
+ if ((menu->current = ci_next_undeleted(menu->current)) == -1)
+ {
+ menu->current = menu->oldcurrent;
+ menu->redraw = REDRAW_CURRENT;
+ }
+ else
+ menu->redraw = REDRAW_MOTION_RESYNCH;
}
else
- menu->redraw = REDRAW_MOTION_RESYNCH;
+ menu->redraw = REDRAW_CURRENT;
}
- else
- menu->redraw = REDRAW_CURRENT;
}
+#endif
menu->redraw |= REDRAW_STATUS;
break;
}
+#endif
+#ifdef USE_NOTMUCH
case OP_MAIN_VFOLDER_FROM_QUERY:
buf[0] = '\0';
if (mutt_get_field("Query: ", buf, sizeof(buf), MUTT_NM_QUERY) != 0 || !buf[0])
* | \%E | number of messages in current thread
* | \%f | entire from line
* | \%F | like %n, unless from self
- * | \%g | message tags (e.g. notmuch tags)
- * | \%Gx | individual message tag (e.g. notmuch tags)
+ * | \%g | message tags (e.g. notmuch tags/imap flags)
+ * | \%Gx | individual message tag (e.g. notmuch tags/imap flags)
* | \%J | message tags (if present, tree unfolded, and != parent's keywords)
* | \%i | message-id
* | \%I | initials of author
#include "message.h"
#include "mutt_curses.h"
#include "mutt_socket.h"
+#include "mutt_tags.h"
#include "mx.h"
#include "options.h"
#include "pattern.h"
imap_set_flag(idata, MUTT_ACL_DELETE, HEADER_DATA(hdr)->deleted, "\\Deleted ",
flags, sizeof(flags));
- /* now make sure we don't lose custom tags */
if (mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE))
- imap_add_keywords(flags, hdr, &idata->flags, sizeof(flags));
+ {
+ /* restore system keywords */
+ if (HEADER_DATA(hdr)->keywords_system)
+ safe_strcat(flags, sizeof(flags), HEADER_DATA(hdr)->keywords_system);
+ /* set custom keywords */
+ if (hdr_tags_get_with_hidden(hdr))
+ safe_strcat(flags, sizeof(flags), hdr_tags_get_with_hidden(hdr));
+ }
mutt_remove_trailing_ws(flags);
imap_set_flag(idata, MUTT_ACL_DELETE, !HEADER_DATA(hdr)->deleted,
"\\Deleted ", flags, sizeof(flags));
+ /* erase custom keywords */
+ if (mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE) && HEADER_DATA(hdr)->keywords_remote)
+ safe_strcat(flags, sizeof(flags), HEADER_DATA(hdr)->keywords_remote);
+
mutt_remove_trailing_ws(flags);
mutt_buffer_addstr(cmd, " -FLAGS.SILENT (");
}
}
+ /* server have now the updated keywords */
+ FREE(&HEADER_DATA(hdr)->keywords_remote);
+ HEADER_DATA(hdr)->keywords_remote = safe_strdup(hdr_tags_get_with_hidden(hdr));
+
hdr->active = true;
if (hdr->deleted == HEADER_DATA(hdr)->deleted)
hdr->changed = false;
}
/**
+ * imap_sync_keywords - Add/Change/Remove keywords from headers
+ * @param idata: pointer to a struct ImapData
+ * @param h: pointer to a header struct
+ *
+ * @retval 0 Success
+ * @retval -1 Error
+ *
+ * This method update the server flags on the server by
+ * removing the last know custom keywords of a header
+ * and adds the local keywords
+ *
+ * If everything success we push the local keywords to the
+ * last know custom keywords (keywords_remote).
+ *
+ * Also this method check that each keywords is support by the server
+ * first and remove unsupported one.
+ */
+static int imap_sync_keywords(struct ImapData *idata, struct Header *h)
+{
+ struct Buffer *cmd = NULL;
+ char uid[11];
+
+ if (!mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE))
+ return 0;
+
+ snprintf(uid, sizeof(uid), "%u", HEADER_DATA(h)->uid);
+
+ /* Remove old custom keywords */
+ if (HEADER_DATA(h)->keywords_remote)
+ {
+ if (!(cmd = mutt_buffer_new()))
+ {
+ mutt_debug(1, "imap_sync_keywords: unable to allocate buffer\n");
+ return -1;
+ }
+ cmd->dptr = cmd->data;
+ mutt_buffer_addstr(cmd, "UID STORE ");
+ mutt_buffer_addstr(cmd, uid);
+ mutt_buffer_addstr(cmd, " -FLAGS.SILENT (");
+ mutt_buffer_addstr(cmd, HEADER_DATA(h)->keywords_remote);
+ mutt_buffer_addstr(cmd, ")");
+
+ /* Should we return here, or we are fine and we could
+ * continue to add new keywords *
+ */
+ if (imap_exec(idata, cmd->data, 0) != 0)
+ return -1;
+
+ mutt_buffer_free(&cmd);
+ }
+
+ /* Add new custom keywords */
+ if (hdr_tags_get_with_hidden(h))
+ {
+ if (!(cmd = mutt_buffer_new()))
+ {
+ mutt_debug(1, "imap_sync_keywords: fail to remove old keywords\n");
+ return -1;
+ }
+ cmd->dptr = cmd->data;
+ mutt_buffer_addstr(cmd, "UID STORE ");
+ mutt_buffer_addstr(cmd, uid);
+ mutt_buffer_addstr(cmd, " +FLAGS.SILENT (");
+ mutt_buffer_addstr(cmd, hdr_tags_get_with_hidden(h));
+ mutt_buffer_addstr(cmd, ")");
+
+ if (imap_exec(idata, cmd->data, 0) != 0)
+ {
+ mutt_debug(1, "imap_sync_keywords: fail to add new keywords\n");
+ return -1;
+ }
+
+ mutt_buffer_free(&cmd);
+ }
+
+ /* We are good sync them and only keep those supported by the server */
+ FREE(&HEADER_DATA(h)->keywords_remote);
+ HEADER_DATA(h)->keywords_remote = safe_strdup(hdr_tags_get_with_hidden(h));
+ return 0;
+}
+
+/*
* imap_sync_mailbox - Sync all the changes to the server
* @param ctx the current context
* @param expunge 0 or 1 - do expunge?
#endif
}
+ if (mutt_strcmp(HEADER_DATA(h)->keywords_remote, hdr_tags_get_with_hidden(h)) != 0)
+ {
+ if (imap_sync_keywords(idata, h) != 0)
+ mutt_error(_("Error syncing keywords"));
+ }
+
if (h->active && h->changed)
{
#ifdef USE_HCACHE
/* message.c */
int imap_append_message(struct Context *ctx, struct Message *msg);
int imap_copy_messages(struct Context *ctx, struct Header *h, char *dest, int delete);
+int imap_keywords_message(struct Header *h, bool quasi_deleted);
/* socket.c */
void imap_logout_all(void);
int imap_cmd_idle(struct ImapData *idata);
/* message.c */
-void imap_add_keywords(char *s, struct Header *keywords, struct ListHead *mailbox_flags, size_t slen);
void imap_free_header_data(struct ImapHeaderData **data);
int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned int msn_end);
char *imap_set_flags(struct ImapData *idata, struct Header *h, char *s, int *server_changes);
#include "mailbox.h"
#include "mutt_curses.h"
#include "mutt_socket.h"
+#include "mutt_tags.h"
#include "mx.h"
#include "options.h"
#include "protos.h"
static struct ImapHeaderData *imap_new_header_data(void)
{
struct ImapHeaderData *d = safe_calloc(1, sizeof(struct ImapHeaderData));
- STAILQ_INIT(&d->keywords);
return d;
}
}
s++;
- mutt_list_free(&hd->keywords);
+ FREE(&hd->keywords_system);
+ FREE(&hd->keywords_remote);
+
hd->deleted = hd->flagged = hd->replied = hd->read = hd->old = false;
/* start parsing */
}
else
{
- /* store custom flags as well */
char ctmp;
char *flag_word = s;
+ bool is_system_keyword = (mutt_strncasecmp("\\", s, 1) == 0);
while (*s && !ISSPACE(*s) && *s != ')')
s++;
+
ctmp = *s;
*s = '\0';
- mutt_list_insert_tail(&hd->keywords, safe_strdup(flag_word));
+
+ /* store other system flags as well (mainly \\Draft) */
+ if (is_system_keyword)
+ mutt_str_append_item(&hd->keywords_system, flag_word, 32);
+ /* store custom flags as well */
+ else
+ mutt_str_append_item(&hd->keywords_remote, flag_word, 32);
+
*s = ctmp;
}
SKIPWS(s);
}
/**
+ * imap_keywords_message - Add/Change/Remove keywords from headers
+ * @param[in] h: pointer to a header struct or NULL to modify
+ * all headers of the Context
+ *
+ * @return int the number of headers changed
+ * @retval -1 in case of error
+ *
+ * This method prompts the user to edit keywords of current header
+ * And update keywords_local with the new keywords list
+ *
+ * It also ensure each keyword are valid IMAP rfc822 atom
+ */
+int imap_keywords_message(struct Header *h, bool quasi_deleted)
+{
+ char buf[LONG_STRING], *new, *checker;
+ int i;
+ int changed;
+
+ if (!Context || Context->magic != MUTT_IMAP)
+ return -1;
+
+ /* Check for \* flags capability */
+ struct ImapData* idata = (struct ImapData*) Context->data;
+ if (!imap_has_flag(&idata->flags, NULL))
+ {
+ mutt_error(_("IMAP server doesn't support custom keywords"));
+ return -1;
+ }
+
+ *buf = '\0';
+ if (h && hdr_tags_get(h))
+ {
+ strncpy(buf, hdr_tags_get_with_hidden(h), LONG_STRING);
+ }
+
+ if (mutt_get_field("Keywords: ", buf, sizeof(buf), 0) != 0)
+ return 0;
+
+ /* each keyword must be atom defined by rfc822 as:
+ *
+ * atom = 1*<any CHAR except specials, SPACE and CTLs>
+ * CHAR = ( 0.-127. )
+ * specials = "(" / ")" / "<" / ">" / "@"
+ * / "," / ";" / ":" / "\" / <">
+ * / "." / "[" / "]"
+ * SPACE = ( 32. )
+ * CTLS = ( 0.-31., 127.)
+ *
+ * And must be separated by one space.
+ */
+
+ new = buf;
+ checker = buf;
+ SKIPWS(checker);
+ while (*checker != '\0')
+ {
+ if (*checker < 32 || *checker >= 127 || // We allow space because it's the separator
+ *checker == 40 || // (
+ *checker == 41 || // )
+ *checker == 60 || // <
+ *checker == 62 || // >
+ *checker == 64 || // @
+ *checker == 44 || // ,
+ *checker == 59 || // ;
+ *checker == 58 || // :
+ *checker == 92 || // backslash
+ *checker == 34 || // "
+ *checker == 46 || // .
+ *checker == 91 || // [
+ *checker == 93) // ]
+ {
+ mutt_error(_("Invalid IMAP keyworks"));
+ mutt_sleep(2);
+ return 0;
+ }
+
+ /* Skip duplicate space */
+ while (*checker == ' ' && *(checker + 1) == ' ')
+ checker++;
+
+ /* copy char to new and go the next one */
+ *new ++ = *checker++;
+ }
+ *new = '\0';
+ new = buf; /* rewind */
+ mutt_remove_trailing_ws(new);
+
+ if (*new == '\0')
+ new = NULL;
+
+ changed = 0;
+ if (h != NULL)
+ {
+ changed += hdr_tags_replace(h, new);
+ if (changed)
+ h->quasi_deleted = quasi_deleted;
+ }
+ else
+ {
+#define HDR_OF(index) Context->hdrs[Context->v2r[(index)]]
+ for (i = 0; i < Context->vcount; ++i)
+ {
+ if (HDR_OF(i)->tagged && hdr_tags_replace(HDR_OF(i), new))
+ {
+ HDR_OF(i)->quasi_deleted = quasi_deleted;
+ ++changed;
+ mutt_set_flag(Context, HDR_OF(i), MUTT_TAG, 0);
+ }
+ }
+ }
+
+ return changed;
+}
+
+/*
* imap_read_headers - Read headers from the server
*
* Changed to read many headers instead of just one. It will return the msn of
ctx->hdrs[idx]->changed = h.data->changed;
/* ctx->hdrs[msgno]->received is restored from mutt_hcache_restore */
ctx->hdrs[idx]->data = (void *) (h.data);
+ hdr_tags_init(ctx->hdrs[idx]);
+ hdr_tags_replace(ctx->hdrs[idx], safe_strdup(h.data->keywords_remote));
ctx->msgcount++;
ctx->size += ctx->hdrs[idx]->content->length;
/* make sure we don't get remnants from older larger message headers */
fputs("\n\n", fp);
+
if (h.data->msn < 1 || h.data->msn > fetch_msn_end)
{
mutt_debug(1, "imap_read_headers: skipping FETCH response for "
ctx->hdrs[idx]->changed = h.data->changed;
ctx->hdrs[idx]->received = h.received;
ctx->hdrs[idx]->data = (void *) (h.data);
+ hdr_tags_init(ctx->hdrs[idx]);
+ hdr_tags_replace(ctx->hdrs[idx], safe_strdup(h.data->keywords_remote));
if (maxuid < h.data->uid)
maxuid = h.data->uid;
return 0;
}
-/**
- * imap_add_keywords - concatenate custom IMAP tags to list
- *
- * If the tags appear in the folder flags list. Why wouldn't they?
- */
-void imap_add_keywords(char *s, struct Header *h, struct ListHead *mailbox_flags, size_t slen)
-{
- struct ListHead *keywords = &HEADER_DATA(h)->keywords;
-
- if (STAILQ_EMPTY(mailbox_flags) || !HEADER_DATA(h) || STAILQ_EMPTY(keywords))
- return;
-
- struct ListNode *np;
- STAILQ_FOREACH(np, keywords, entries)
- {
- if (imap_has_flag(mailbox_flags, np->data))
- {
- safe_strcat(s, slen, np->data);
- safe_strcat(s, slen, " ");
- }
- }
-}
-
/**
* imap_free_header_data - free ImapHeader structure
*/
if (*data)
{
/* this should be safe even if the list wasn't used */
- mutt_list_free(&(*data)->keywords);
+ FREE(&((*data)->keywords_system));
+ FREE(&((*data)->keywords_remote));
FREE(data);
}
}
if ((s = msg_parse_flags(&newh, s)) == NULL)
return NULL;
+ /* Update tags system */
+ hdr_tags_replace(h, safe_strdup(hd->keywords_remote));
+
/* YAUH (yet another ugly hack): temporarily set context to
* read-write even if it's read-only, so *server* updates of
* flags can be processed by mutt_set_flag. ctx->changed must
unsigned int uid; /**< 32-bit Message UID */
unsigned int msn; /**< Message Sequence Number */
- struct ListHead keywords;
+
+ char *keywords_system;
+ char *keywords_remote;
};
/**