Hi David,

On Tue, Aug 30, 2016 at 06:03:37PM -0700, [email protected] wrote:
>  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 <[email protected]>
> # 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);

Why 131? Could you document/explain this value?

> +       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] != ',' && 

Trailing whitespace, also this code use tabulations.

> +              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;

A bit off topic, what do people think of the `WHERE` macro? To me, it
only makes tools like cscope confused and doesn't improve readability, I
would really love to get rid of them at some point.

>  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;

Why uintptr_t and not int or unsigned?

> +
> +  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)

You should take this opportunity to remove the commented option here.

>      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", 

Trailing whitespace.

> +             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);

It would make more sense to group them under a common "mutt_label"
"namespace". So I would go with mutt_label_scan(_all eventually) and
mutt_label_complete. It makes easier to find functions that work
together.

>  void mutt_curses_error (const char *, ...);
>  void mutt_curses_message (const char *, ...);
>  void mutt_encode_descriptions (BODY *, short);
> 

Thanks,
-- 
Damien

Reply via email to