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
> <End></entry><entry><literal><eol></literal></entry><entry>move
> to the end of the line</entry></row>
> <row><entry>^F or
> <Right></entry><entry><literal><forward-char></literal></entry><entry>move
> forward one char</entry></row>
> <row><entry>Esc
> F</entry><entry><literal><forward-word></literal></entry><entry>move
> forward one word</entry></row>
> -<row><entry><Tab></entry><entry><literal><complete></literal></entry><entry>complete
> filename or alias</entry></row>
> +<row><entry><Tab></entry><entry><literal><complete></literal></entry><entry>complete
> filename, alias, or label</entry></row>
>
> <row><entry>^T</entry><entry><literal><complete-query></literal></entry><entry>complete
> address with query</entry></row>
>
> <row><entry>^K</entry><entry><literal><kill-eol></literal></entry><entry>delete
> to the end of the line</entry></row>
> <row><entry>Esc
> d</entry><entry><literal><kill-eow></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 <complete>
> +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