From: David Champion Date: Sun, 29 Jan 2017 02:47:57 +0000 (-0800) Subject: Adds label completion. X-Git-Tag: mutt-1-8-rel~32 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f58e89c8466b36d11ecabddc8baf9727f59e6d69;p=mutt 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 index 43417188..68deab85 100644 --- a/curs_main.c +++ b/curs_main.c @@ -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 diff --git a/doc/manual.xml.head b/doc/manual.xml.head index f49862c9..89c13649 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -548,7 +548,7 @@ descriptions. ^E or <End><eol>move to the end of the line ^F or <Right><forward-char>move forward one char Esc F<forward-word>move forward one word -<Tab><complete>complete filename or alias +<Tab><complete>complete filename, alias, or label ^T<complete-query>complete address with query ^K<kill-eol>delete to the end of the line Esc d<kill-eow>delete to the end of the word @@ -6067,6 +6067,9 @@ procmail and other mail filtering agents. You can change or delete the X-Label: field within Mutt using the edit-label command, bound to the y 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. diff --git a/enter.c b/enter.c index 2f3ebdd5..9dd134e4 100644 --- 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 */ diff --git a/globals.h b/globals.h index a68338a8..d0d347e9 100644 --- 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); diff --git a/headers.c b/headers.c index ada4aad4..0cf3a6cc 100644 --- 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 cf281356..b61cf766 100644 --- 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 cafb830d..bc46d610 100644 --- 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 8a8a0266..cbd28eb4 100644 --- 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 */ diff --git a/protos.h b/protos.h index a989c5f8..cfb4d7c6 100644 --- 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);