Commit f58e89c8 authored by David Champion's avatar David Champion

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'.
parent f538c3ca
......@@ -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
......
......@@ -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>
......
......@@ -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 */
......
......@@ -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);
......
......@@ -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);
}
......@@ -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;
}
......@@ -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 ();
......
......@@ -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 */
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment