]> granicus.if.org Git - mutt/commitdiff
Adds label completion.
authorDavid Champion <dgc@bikeshed.us>
Sun, 29 Jan 2017 02:47:57 +0000 (18:47 -0800)
committerDavid Champion <dgc@bikeshed.us>
Sun, 29 Jan 2017 02:47:57 +0000 (18:47 -0800)
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 43417188e50655b944a9d2e8fe33117fb214d934..68deab859cbc97eaabed2417b8eb7c58b0f1ecaf 100644 (file)
@@ -1268,6 +1268,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
@@ -1282,6 +1285,8 @@ int mutt_index_menu (void)
                                        (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
index f49862c99b7ba5d54a9579c245059426b31ee8f9..89c136494b4ee9796e638942e65cb55c308c5900 100644 (file)
@@ -548,7 +548,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>
@@ -6067,6 +6067,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 2f3ebdd5d4b5912fb08a49b4bcb2f1ec7438e801..9dd134e45169a5381350ebff0c8b1e7a56fde2d3 100644 (file)
--- a/enter.c
+++ b/enter.c
@@ -566,6 +566,24 @@ int _mutt_enter_string (char *buf, size_t buflen, int col,
            }
            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 */
index a68338a85258a2a3d5802023a86842852bfd0b79..d0d347e97974c25dcd0d9f4e441ededf805fed9b 100644 (file)
--- a/globals.h
+++ b/globals.h
@@ -162,6 +162,7 @@ WHERE char *LastFolder;
 WHERE const char *ReleaseDate;
 
 WHERE HASH *Groups;
+WHERE HASH *Labels;
 WHERE HASH *ReverseAlias;
 
 WHERE LIST *AutoViewList INITVAL(0);
index ada4aad49c04f41d23bd3695ced59359faf8cbec..0cf3a6cc10f98977fc3bd8cab0acffc453eb4c6c 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.
  */
@@ -221,7 +246,11 @@ static int label_message(HEADER *hdr, char *new)
     return 0;
   if (mutt_strcmp (hdr->env->x_label, new) == 0)
     return 0;
+  if (hdr->env->x_label != NULL)
+    label_ref_dec(hdr->env->x_label);
   mutt_str_replace (&hdr->env->x_label, new);
+  if (hdr->env->x_label != NULL)
+    label_ref_inc(hdr->env->x_label);
   return hdr->changed = hdr->xlabel_changed = 1;
 }
 
@@ -236,7 +265,7 @@ int mutt_label_message(HEADER *hdr)
     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;
@@ -261,3 +290,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 cf2813569189cf6a9e1721f0675f7430d366b4ac..b61cf766ce37e96ce72005b69b1e3d67bb85e3fb 100644 (file)
--- a/init.c
+++ b/init.c
@@ -3629,3 +3629,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.strkey, 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 cafb830deb9fd870074c9f74bf6563b57f514159..bc46d610ecfb0655c1069113d67a0f0ba10e3802 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1240,12 +1240,16 @@ int main (int argc, char **argv, char **environ)
     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
index 8a8a0266cf0c10782d34819f61b8150c9a0dc47f..cbd28eb4fd13a119d675be0fca65447d5a4c8c7a 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -93,6 +93,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 */
index a989c5f88997515066883739bbde55a6b9db4ec3..cfb4d7c668d9a7feefb60e21b1f3b3de1222d4c6 100644 (file)
--- a/protos.h
+++ b/protos.h
@@ -187,6 +187,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);