curs_main.c         |   5 ++++
 doc/manual.xml.head |   5 +++-
 enter.c             |  18 +++++++++++++++++
 globals.h           |   1 +
 headers.c           |  46 ++++++++++++++++++++++++++++++++++++++++++++-
 init.c              |  54 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 main.c              |   4 +++
 mutt.h              |   1 +
 protos.h            |   2 +
 9 files changed, 134 insertions(+), 2 deletions(-)


# HG changeset patch
# User David Champion <d...@bikeshed.us>
# Date 1472605301 25200
#      Tue Aug 30 18:01:41 2016 -0700
# Node ID b0982723e4af2aa8d8054f0e513bdb635d32d16b
# Parent  beaac419ca1dd13d0d11fdf33bb0d128fa634045
Adds label completion.

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'.

diff --git a/curs_main.c b/curs_main.c
--- a/curs_main.c
+++ b/curs_main.c
@@ -1256,6 +1256,9 @@
          FREE (&Context);
        }
 
+        if (Labels)
+          hash_destroy(&Labels, NULL);
+
         mutt_sleep (0);
 
        /* Set CurrentMenu to MENU_MAIN before executing any folder
@@ -1270,6 +1273,8 @@
                                        (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
diff --git a/doc/manual.xml.head b/doc/manual.xml.head
--- a/doc/manual.xml.head
+++ b/doc/manual.xml.head
@@ -548,7 +548,7 @@
 <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>
@@ -6064,6 +6064,9 @@
 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
--- a/enter.c
+++ b/enter.c
@@ -566,6 +566,24 @@
            }
            break;
          }
+         else if (flags & MUTT_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 & MUTT_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY)
          {
            /* invoke the query-menu to get more addresses */
diff --git a/globals.h b/globals.h
--- a/globals.h
+++ b/globals.h
@@ -162,6 +162,7 @@
 WHERE const char *ReleaseDate;
 
 WHERE HASH *Groups;
+WHERE HASH *Labels;
 WHERE HASH *ReverseAlias;
 
 WHERE LIST *AutoViewList INITVAL(0);
diff --git a/headers.c b/headers.c
--- a/headers.c
+++ b/headers.c
@@ -212,6 +212,31 @@
   }
 }
 
+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 @@
   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 @@
     strncpy(buf, hdr->env->x_label, LONG_STRING);
   }
 
-  if (mutt_get_field("Label: ", buf, sizeof(buf), 0 /* | MUTT_CLEAR */) != 0)
+  if (mutt_get_field("Label: ", buf, sizeof(buf), MUTT_LABEL /* | MUTT_CLEAR 
*/) != 0)
     return 0;
 
   new = buf;
@@ -269,3 +302,14 @@
 
   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
--- a/init.c
+++ b/init.c
@@ -3492,3 +3492,57 @@
 
   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
--- a/main.c
+++ b/main.c
@@ -1234,12 +1234,16 @@
     if((Context = mx_open_mailbox (folder, ((flags & MUTT_RO) || option 
(OPTREADONLY)) ? MUTT_READONLY : 0, NULL))
        || !explicit_folder)
     {
+      Labels = hash_create (131, 0);
+      mutt_scan_labels(Context);
 #ifdef USE_SIDEBAR
       mutt_sb_set_open_buffy ();
 #endif
       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
--- a/mutt.h
+++ b/mutt.h
@@ -88,6 +88,7 @@
 #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 */
diff --git a/protos.h b/protos.h
--- a/protos.h
+++ b/protos.h
@@ -187,6 +187,8 @@
 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);

Reply via email to