]> granicus.if.org Git - neomutt/commitdiff
Adds label completion.
authorDavid Champion <dgc@bikeshed.us>
Fri, 8 Apr 2016 00:07:41 +0000 (01:07 +0100)
committerRichard Russon <rich@flatcap.org>
Tue, 17 May 2016 16:27:45 +0000 (17:27 +0100)
A global label hash is added, to which labels are added as they're parsed
from a mailbox file or edited manually by the user.  Reference counts are
kept in the hash table so that unused labels are removed from available
completions.  Completion is available in the label editor only, but it
may be feasible to add for search expressions if the preceding text ends
with '~y'.

curs_main.c
doc/manual.xml.head
enter.c
globals.h
headers.c
init.c
main.c
mutt.h
protos.h

index 8eb537b8cb036d340045346be560dc9f8c8595db..be9eb8467e476b066ddb3b5cbbfcf52e9fb00f51 100644 (file)
@@ -1229,6 +1229,9 @@ int mutt_index_menu (void)
          FREE (&Context);
        }
 
+        if (Labels)
+          hash_destroy(&Labels, NULL);
+
         mutt_sleep (0);
 
        /* Set CurrentMenu to MENU_MAIN before executing any folder
@@ -1243,6 +1246,8 @@ int mutt_index_menu (void)
                                        (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ?
                                        M_READONLY : 0, NULL)) != NULL)
        {
+         Labels = hash_create(131, 0);
+         mutt_scan_labels(Context);
          menu->current = ci_first_message ();
        }
        else
index 5cc43d11f38a93dc3de1ccff40daa4e2e82a338e..f556abe56657c512798e2ddaf8c106e106c9767b 100644 (file)
@@ -539,7 +539,7 @@ descriptions.
 <row><entry>^E or &lt;End&gt;</entry><entry><literal>&lt;eol&gt;</literal></entry><entry>move to the end of the line</entry></row>
 <row><entry>^F or &lt;Right&gt;</entry><entry><literal>&lt;forward-char&gt;</literal></entry><entry>move forward one char</entry></row>
 <row><entry>Esc F</entry><entry><literal>&lt;forward-word&gt;</literal></entry><entry>move forward one word</entry></row>
-<row><entry>&lt;Tab&gt;</entry><entry><literal>&lt;complete&gt;</literal></entry><entry>complete filename or alias</entry></row>
+<row><entry>&lt;Tab&gt;</entry><entry><literal>&lt;complete&gt;</literal></entry><entry>complete filename, alias, or label</entry></row>
 <row><entry>^T</entry><entry><literal>&lt;complete-query&gt;</literal></entry><entry>complete address with query</entry></row>
 <row><entry>^K</entry><entry><literal>&lt;kill-eol&gt;</literal></entry><entry>delete to the end of the line</entry></row>
 <row><entry>Esc d</entry><entry><literal>&lt;kill-eow&gt;</literal></entry><entry>delete to the end of the word</entry></row>
@@ -5993,6 +5993,9 @@ procmail and other mail filtering agents.
 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 &lt;complete&gt;
+binding (TAB, by default) will perform completion against all labels
+currently in use.
 </para>
 
 <para>
diff --git a/enter.c b/enter.c
index 23610ae5dc074b451329af0e47fedad1e361457e..f5410d33885b785bd0974fa1fa47577d78d5020e 100644 (file)
--- a/enter.c
+++ b/enter.c
@@ -565,6 +565,24 @@ int _mutt_enter_string (char *buf, size_t buflen, int y, int x,
            }
            break;
          }
+         else if (flags & M_LABEL && ch == OP_EDITOR_COMPLETE)
+         {
+           /* invoke the alias-menu to get more addresses */
+           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 & M_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY)
          {
            /* invoke the query-menu to get more addresses */
index abefade3d1423e253e8ec5422259d7759d03ea9f..6851e2bbd2d7abef4782f146a6450fb5f3652f18 100644 (file)
--- a/globals.h
+++ b/globals.h
@@ -154,6 +154,7 @@ WHERE char *LastFolder;
 WHERE const char *ReleaseDate;
 
 WHERE HASH *Groups;
+WHERE HASH *Labels;
 WHERE HASH *ReverseAlias;
 
 WHERE LIST *AutoViewList INITVAL(0);
index 1009b7241cd464f0cf71ab914782164d66f45249..a5810580056001ff62f0a1816bccf25073b3d22b 100644 (file)
--- a/headers.c
+++ b/headers.c
@@ -212,6 +212,31 @@ void mutt_edit_headers (const char *editor,
   }
 }
 
+static void label_ref_dec(char *label)
+{
+  uintptr_t count;
+
+  count = (uintptr_t)hash_find(Labels, label);
+  if (count)
+  {
+    hash_delete(Labels, label, NULL, NULL);
+    count--;
+    if (count > 0)
+      hash_insert(Labels, label, (void *)count, 0);
+  }
+}
+
+static void label_ref_inc(char *label)
+{
+  uintptr_t count;
+
+  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);
+}
+
 /*
  * add an X-Label: field.
  */
@@ -224,12 +249,20 @@ static int label_message(HEADER *hdr, char *new)
   if (hdr->env->x_label != NULL && new != NULL &&
       strcmp(hdr->env->x_label, new) == 0)
     return 0;
+
   if (hdr->env->x_label != NULL)
+  {
+    label_ref_dec(hdr->env->x_label);
     FREE(&hdr->env->x_label);
+  }
+
   if (new == NULL)
     hdr->env->x_label = NULL;
   else
+  {
     hdr->env->x_label = safe_strdup(new);
+    label_ref_inc(hdr->env->x_label);
+  }
   return hdr->changed = hdr->xlabel_changed = 1;
 }
 
@@ -244,7 +277,7 @@ int mutt_label_message(HEADER *hdr)
     strncpy(buf, hdr->env->x_label, LONG_STRING);
   }
 
-  if (mutt_get_field("Label: ", buf, sizeof(buf), 0 /* | M_CLEAR */) != 0)
+  if (mutt_get_field("Label: ", buf, sizeof(buf), M_LABEL /* | M_CLEAR */) != 0)
     return 0;
 
   new = buf;
@@ -269,3 +302,14 @@ int mutt_label_message(HEADER *hdr)
 
   return changed;
 }
+
+/* scan a context (mailbox) and hash all labels we find */
+void mutt_scan_labels(CONTEXT *ctx)
+{
+  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);
+}
+
diff --git a/init.c b/init.c
index 69e2f765e5f2b384fcb25fda84502d42693e75cb..2aa7342511da4f8d1253ec54e86f793bf30bcece 100644 (file)
--- a/init.c
+++ b/init.c
@@ -3282,3 +3282,57 @@ static const char* myvar_get (const char* var)
 
   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 */
+
+  SKIPWS (buffer);
+  spaces = buffer - pt;
+
+  pt = buffer + pos - spaces;
+  while ((pt > buffer) && !isspace ((unsigned char) *pt))
+    pt--;
+
+  /* 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, Completed, len - spaces);
+
+  return 1;
+}
+
diff --git a/main.c b/main.c
index d0a11281d3ce4616254ff20879488f5ef4a9f3e5..5be7db97d7b58c7e30436e7467acd1f2a5441fe4 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1206,9 +1206,13 @@ int main (int argc, char **argv)
     if((Context = mx_open_mailbox (folder, ((flags & M_RO) || option (OPTREADONLY)) ? M_READONLY : 0, NULL))
        || !explicit_folder)
     {
+      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 ();
diff --git a/mutt.h b/mutt.h
index ca1453ebf5db4a1d80b1140b43639f39a82c3887..51f575b8a09d374a7a7794f11ea87e10dc0bb0bc 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -88,6 +88,7 @@
 #define  M_CLEAR   (1<<5) /* clear input if printable character is pressed */
 #define  M_COMMAND (1<<6) /* do command completion */
 #define  M_PATTERN (1<<7) /* pattern mode - only used for history classes */
+#define  M_LABEL   (1<<8) /* do label completion */
 
 /* flags for mutt_get_token() */
 #define M_TOKEN_EQUAL          1       /* treat '=' as a special */
index 7f7a6e8c28cde6fdeb5b5912f3e9c1da9a2c6415..018b2eeb2507a41a395ac3dc98c5cf99dc5353a0 100644 (file)
--- a/protos.h
+++ b/protos.h
@@ -185,6 +185,8 @@ 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 **);
 int mutt_label_message (HEADER *);
+void mutt_scan_labels (CONTEXT *);
+int mutt_label_complete (char *, size_t, int, int);
 void mutt_curses_error (const char *, ...);
 void mutt_curses_message (const char *, ...);
 void mutt_encode_descriptions (BODY *, short);