summaryrefslogtreecommitdiff
path: root/alpine/mailcmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'alpine/mailcmd.c')
-rw-r--r--alpine/mailcmd.c364
1 files changed, 310 insertions, 54 deletions
diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c
index 905ddb19..0dd4c50b 100644
--- a/alpine/mailcmd.c
+++ b/alpine/mailcmd.c
@@ -1,10 +1,6 @@
-#if !defined(lint) && !defined(DOS)
-static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
-#endif
-
/*
* ========================================================================
- * Copyright 2013-2021 Eduardo Chappa
+ * Copyright 2013-2022 Eduardo Chappa
* Copyright 2006-2009 University of Washington
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -111,9 +107,11 @@ SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
int select_by_status(MAILSTREAM *, SEARCHSET **);
int select_by_rule(MAILSTREAM *, SEARCHSET **);
int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
-char *choose_a_rule(int);
+char *choose_a_rule(int, char *);
+int rulenick_complete(int, char *, int);
int select_by_keyword(MAILSTREAM *, SEARCHSET **);
-char *choose_a_keyword(void);
+char *choose_a_keyword(char *);
+int keyword_complete(char *, int);
int select_sort(struct pine *, int, SortOrder *, int *);
int print_index(struct pine *, MSGNO_S *, int);
@@ -332,10 +330,18 @@ static ESCKEY_S sel_size_opt[] = {
{-1, 0, NULL, NULL}
};
-static ESCKEY_S sel_key_opt[] = {
+static ESCKEY_S sel_rule_opt[] = {
{0, 0, NULL, NULL},
{ctrl('T'), 14, "^T", N_("To List")},
+ {0, 0, NULL, NULL}, /* Reserved for TAB completion */
+ {'!', '!', "!", N_("Not")},
+ {-1, 0, NULL, NULL}
+};
+
+static ESCKEY_S sel_key_opt[] = {
{0, 0, NULL, NULL},
+ {ctrl('T'), 14, "^T", N_("To List")},
+ {0, 0, NULL, NULL}, /* Reserved for TAB completion */
{'!', '!', "!", N_("Not")},
{-1, 0, NULL, NULL}
};
@@ -367,11 +373,14 @@ int
alpine_get_password(char *prompt, char *pass, size_t len)
{
int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
+ int rv;
flags |= OE_DISALLOW_HELP;
pass[0] = '\0';
- return optionally_enter(pass,
+ rv = optionally_enter(pass,
-(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
0, len, prompt, NULL, NO_HELP, &flags);
+ if(rv == 1) ps_global->user_says_cancel = 1;
+ return rv;
}
int
@@ -662,6 +671,8 @@ view_text:
thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
if(thrd && thrd->top)
topthrd = fetch_thread(stream, thrd->top);
+ else
+ topthrd = NULL;
if(topthrd)
j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
@@ -1340,7 +1351,7 @@ get_out:
if(del_count > 0L){
state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
- plural(del_count), MAX_SCREEN_COLS+1-40,
+ plural(del_count), MAX_SCREEN_COLS+1-45,
pretty_fn(state->cur_folder));
prompt[sizeof(prompt)-1] = '\0';
if(F_ON(F_FULL_AUTO_EXPUNGE, state)
@@ -1718,6 +1729,7 @@ cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
char *keyword_array[2];
int user_defined_flags = 0, mailbox_flags = 0;
int directly_to_maint_screen = 0;
+ int use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
long unflagged, flagged, flags, rawno;
MESSAGECACHE *mc = NULL;
KEYWORD_S *kw;
@@ -1949,11 +1961,8 @@ go_again:
else
#endif
{
- int use_maint_screen;
int keyword_shortcut = 0;
- use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
-
if(!use_maint_screen){
/*
* We're going to call cmd_flag_prompt(). We need
@@ -2761,7 +2770,7 @@ save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr
SaveDel *dela, SavePreserveOrder *prea)
{
int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
- int delindex, preindex, r;
+ int delindex = 0, preindex = 0, r;
char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
char *buf = tmp_20k_buf;
char shortbuf[200];
@@ -3255,7 +3264,7 @@ create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
int
cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
{
- long del_count, prefilter_del_count;
+ long del_count, prefilter_del_count = 0;
int we_cancel = 0, rv = 0;
char prompt[MAX_SCREEN_COLS+1];
char *sequence;
@@ -3587,12 +3596,12 @@ cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
{
char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
char nmsgs[80];
- int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
+ int r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE;
int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
ENVELOPE *env;
MESSAGECACHE *mc;
BODY *b;
- long i, count = 0L, start_of_append, rawno;
+ long i, count = 0L, start_of_append = 0, rawno;
gf_io_t pc;
STORE_S *store;
struct variable *vars = state ? ps_global->vars : NULL;
@@ -3866,7 +3875,7 @@ cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
}
ok = 0;
- snprintf(dir, sizeof(dir), "%s.d", full_filename);
+ snprintf(dir, sizeof(dir), "%.*s.d", MAXPATH-2, full_filename);
dir[sizeof(dir)-1] = '\0';
do {
@@ -3887,7 +3896,7 @@ cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
goto fini;
}
- snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
+ snprintf(dir, sizeof(dir), "%.*s.d_%s", MAXPATH- (int) strlen(long2string((long) tries))-3, full_filename,
long2string((long) tries));
dir[sizeof(dir)-1] = '\0';
break;
@@ -3960,24 +3969,33 @@ cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
* and if so, we write a counter name in the file name, just before the
* extension of the file, and separate it with an underscore.
*/
- snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
+ snprintf(filename, sizeof(filename), "%.*s%.*s%.*s", (int) strlen(dir), dir,
+ (int) strlen(S_FILESEP), S_FILESEP,
+ MAXPATH - (int) strlen(dir) - (int) strlen(S_FILESEP), lfile);
filename[sizeof(filename)-1] = '\0';
while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
- char *ext;
- snprintf(filename, sizeof(filename), "%d", counter);
- if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
- > sizeof(filename)){
+ char *ext, count[MAXPATH+1];
+ unsigned long total;
+ snprintf(count, sizeof(count), "%d", counter);
+ if((ext = strrchr(lfile, '.')) != NULL)
+ *ext = '\0';
+ total = strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(count) + 3
+ + (ext ? strlen(ext+1) : 0);
+ if(total > sizeof(filename)){
dprint((2,
"FAILED Att Export: name too long: %s\n",
dir, S_FILESEP, lfile));
errs++;
continue;
}
- if((ext = strrchr(lfile, '.')) != NULL)
- *ext = '\0';
- snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
- dir, S_FILESEP, lfile,
- ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
+ snprintf(filename, sizeof(filename), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
+ (int) strlen(dir), dir, (int) strlen(S_FILESEP), S_FILESEP,
+ (int) strlen(lfile), lfile,
+ ext ? 1 : 0, ext ? "_" : "",
+ (int) strlen(count), counter++,
+ ext ? 1 : 0, ext ? "." : "",
+ ext ? (int) (sizeof(filename) - total) : 0,
+ ext ? ext+1 : "");
filename[sizeof(filename)-1] = '\0';
}
@@ -8266,11 +8284,11 @@ select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCH
int
select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
{
- int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
+ int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
int not = 0, me = 0;
- char sstring[80], tmp[128];
+ char sstring[512], tmp[128];
char *p, *sval = NULL;
- char buftmp[MAILTMPLEN], namehdr[80];
+ char namehdr[80];
ESCKEY_S ekey[8];
ENVELOPE *env = NULL;
HelpType help;
@@ -8446,7 +8464,7 @@ select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **
flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
- 79, tmp, ekey, help, &flags);
+ sizeof(sstring), tmp, ekey, help, &flags);
if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
me = 0;
@@ -8848,7 +8866,7 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
{
char rulenick[1000], *nick;
PATGRP_S *patgrp;
- int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
+ int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
| ROLE_DO_INCOLS
| ROLE_DO_ROLES
| ROLE_DO_SCORES
@@ -8858,6 +8876,16 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
rulenick[0] = '\0';
ps_global->mangled_footer = 1;
+ if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
+ sel_rule_opt[2].ch = TAB;
+ sel_rule_opt[2].rval = 15;
+ sel_rule_opt[2].name = "TAB";
+ sel_rule_opt[2].label = N_("Complete");
+ }
+ else{
+ memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
+ }
+
do{
int oe_flags;
@@ -8866,11 +8894,11 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
sizeof(rulenick),
not ? _("Rule to NOT match: ")
: _("Rule to match: "),
- sel_key_opt, NO_HELP, &oe_flags);
+ sel_rule_opt, NO_HELP, &oe_flags);
if(r == 14){
/* select rulenick from a list */
- if((nick=choose_a_rule(rflags)) != NULL){
+ if((nick=choose_a_rule(rflags, NULL)) != NULL){
strncpy(rulenick, nick, sizeof(rulenick)-1);
rulenick[sizeof(rulenick)-1] = '\0';
fs_give((void **) &nick);
@@ -8878,6 +8906,23 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
else
r = 4;
}
+ else if(r == 15){
+ int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
+
+ if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
+ /* double tab with multiple completions: select from list */
+ if((nick=choose_a_rule(rflags, rulenick)) != NULL){
+ strncpy(rulenick, nick, sizeof(rulenick)-1);
+ rulenick[sizeof(rulenick)-1] = '\0';
+ fs_give((void **) &nick);
+ r = 0;
+ }
+ else
+ r = 4;
+ }
+ else if(n != 1)
+ Writechar(BELL, 0);
+ }
else if(r == '!')
not = !not;
@@ -8891,8 +8936,9 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
}
removing_leading_and_trailing_white_space(rulenick);
+ last_r = r;
- }while(r == 3 || r == 4 || r == '!');
+ }while(r == 3 || r == 4 || r == 15 || r == '!');
/*
@@ -8934,16 +8980,21 @@ select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
/*
* Allow user to choose a rule from their list of rules.
*
+ * Args rflags -- Pattern types to choose from
+ * prefix -- Only list rules matching the given prefix
+ *
* Returns an allocated rule nickname on success, NULL otherwise.
*/
char *
-choose_a_rule(int rflags)
+choose_a_rule(int rflags, char *prefix)
{
char *choice = NULL;
char **rule_list, **lp;
int cnt = 0;
+ int prefix_length = 0;
PAT_S *pat;
PAT_STATE pstate;
+ void (*redraw)(void) = ps_global->redrawer;
if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
q_status_message(SM_ORDER, 3, 3,
@@ -8955,8 +9006,15 @@ choose_a_rule(int rflags)
* Build a list of rules to choose from.
*/
- for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
- cnt++;
+ if(prefix)
+ prefix_length = strlen(prefix);
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
+ cnt++;
+ }
if(cnt <= 0){
q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
@@ -8966,9 +9024,12 @@ choose_a_rule(int rflags)
lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
- for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
- *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
- ? pat->patgrp->nick : "?");
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
+ *lp++ = cpystr(pat->patgrp->nick);
+ }
/* TRANSLATORS: SELECT A RULE is a screen title
TRANSLATORS: Print something1 using something2.
@@ -8977,8 +9038,10 @@ choose_a_rule(int rflags)
_("rules"), h_select_rule_screen,
_("HELP FOR SELECTING A RULE NICKNAME"), NULL);
- if(!choice)
+ if(!choice){
q_status_message(SM_ORDER, 1, 4, "No choice");
+ ps_global->redrawer = redraw;
+ }
free_list_array(&rule_list);
@@ -8987,6 +9050,80 @@ choose_a_rule(int rflags)
/*
+ * Complete a partial rule name against the user's list of rules.
+ *
+ * Args rflags -- Pattern types to choose from
+ * nick -- Current rule nickname to complete
+ * nick_size -- Maximum length of the nick array to store completion in
+ *
+ * Returns the number of valid completions, i.e.:
+ *
+ * 0 if there are no valid completions. The value of the nick argument has not
+ * been changed.
+ * 1 if there is only a single valid completion. The value of the nick argument
+ * has been replaced with the full text of that completion.
+ * >1 if there is more than one valid completion. The value of the nick argument
+ * has been replaced with the longest substring common to all the appropriate
+ * completions.
+ */
+int
+rulenick_complete(int rflags, char *nick, int nick_size)
+{
+ char *candidate = NULL;
+ int cnt = 0;
+ int common_prefix_length = 0;
+ int nick_length;
+ PAT_S *pat;
+ PAT_STATE pstate;
+
+ if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
+ return 0;
+
+ nick_length = strlen(nick);
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(!pat->patgrp || !pat->patgrp->nick)
+ continue;
+ if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
+ /* This is a candidate for completion. */
+ cnt++;
+ if(!candidate){
+ /* This is the first candidate. Keep it as a future reference to
+ * compare against to find the longest common prefix length of
+ * all the matches. */
+ candidate = pat->patgrp->nick;
+ common_prefix_length = strlen(pat->patgrp->nick);
+ }
+ else{
+ /* Find the common prefix length between the first candidate and
+ * this one. */
+ int i;
+ for(i = 0; i < common_prefix_length; i++){
+ if(pat->patgrp->nick[i] != candidate[i]){
+ /* In the event that we ended up in the middle of a
+ * UTF-8 code point, backtrack to the byte before the
+ * start of this code point. */
+ while(i > 0 && (candidate[i] & 0xC0) == 0x80)
+ i--;
+ common_prefix_length = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if(cnt > 0){
+ int length = MIN(nick_size, common_prefix_length);
+ strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
+ nick[length] = '\0';
+ }
+
+ return cnt;
+}
+
+
+/*
* Select by current thread.
* Sets searched bits in mail_elts for this entire thread
*
@@ -9038,7 +9175,7 @@ select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
int
select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
{
- int r, not = 0, we_cancel = 0;
+ int r, last_r = 0, not = 0, we_cancel = 0;
char keyword[MAXUSERFLAG+1], *kword;
char *error = NULL, *p, *prompt;
HelpType help;
@@ -9047,6 +9184,16 @@ select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
keyword[0] = '\0';
ps_global->mangled_footer = 1;
+ if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
+ sel_key_opt[2].ch = TAB;
+ sel_key_opt[2].rval = 15;
+ sel_key_opt[2].name = "TAB";
+ sel_key_opt[2].label = N_("Complete");
+ }
+ else{
+ memset(&sel_key_opt[2], 0, sizeof(sel_key_opt[2]));
+ }
+
help = NO_HELP;
do{
int oe_flags;
@@ -9076,7 +9223,7 @@ select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
if(r == 14){
/* select keyword from a list */
- if((kword=choose_a_keyword()) != NULL){
+ if((kword=choose_a_keyword(NULL)) != NULL){
strncpy(keyword, kword, sizeof(keyword)-1);
keyword[sizeof(keyword)-1] = '\0';
fs_give((void **) &kword);
@@ -9084,6 +9231,23 @@ select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
else
r = 4;
}
+ else if(r == 15){
+ int n = keyword_complete(keyword, sizeof(keyword));
+
+ if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
+ /* double tab with multiple completions: select from list */
+ if((kword=choose_a_keyword(keyword)) != NULL){
+ strncpy(keyword, kword, sizeof(keyword)-1);
+ keyword[sizeof(keyword)-1] = '\0';
+ fs_give((void **) &kword);
+ r = 0;
+ }
+ else
+ r = 4;
+ }
+ else if(n != 1)
+ Writechar(BELL, 0);
+ }
else if(r == '!')
not = !not;
@@ -9095,8 +9259,9 @@ select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
}
removing_leading_and_trailing_white_space(keyword);
+ last_r = r;
- }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
+ }while(r == 3 || r == 4 || r == 15 || r == '!' || keyword_check(keyword, &error));
if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
@@ -9152,22 +9317,32 @@ select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
/*
* Allow user to choose a keyword from their list of keywords.
*
+ * Args prefix -- Only list keywords matching the given prefix
+ *
* Returns an allocated keyword on success, NULL otherwise.
*/
char *
-choose_a_keyword(void)
+choose_a_keyword(char *prefix)
{
char *choice = NULL;
char **keyword_list, **lp;
int cnt;
+ int prefix_length = 0;
KEYWORD_S *kw;
+ void (*redraw)(void) = ps_global->redrawer;
/*
* Build a list of keywords to choose from.
*/
- for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
- cnt++;
+ if(prefix)
+ prefix_length = strlen(prefix);
+
+ for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next){
+ char *kw_name = kw->nick ? kw->nick : kw->kw;
+ if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
+ cnt++;
+ }
if(cnt <= 0){
q_status_message(SM_ORDER, 3, 4,
@@ -9178,8 +9353,13 @@ choose_a_keyword(void)
lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
- for(kw = ps_global->keywords; kw; kw = kw->next)
- *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ char *kw_name = kw->nick ? kw->nick : kw->kw;
+ if(!kw_name)
+ continue;
+ if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
+ *lp++ = cpystr(kw_name);
+ }
/* TRANSLATORS: SELECT A KEYWORD is a screen title
TRANSLATORS: Print something1 using something2.
@@ -9188,8 +9368,10 @@ choose_a_keyword(void)
_("keywords"), h_select_keyword_screen,
_("HELP FOR SELECTING A KEYWORD"), NULL);
- if(!choice)
+ if(!choice){
q_status_message(SM_ORDER, 1, 4, "No choice");
+ ps_global->redrawer = redraw;
+ }
free_list_array(&keyword_list);
@@ -9198,6 +9380,77 @@ choose_a_keyword(void)
/*
+ * Complete a partial keyword name against the user's list of keywords.
+ *
+ * Args keyword -- Current keyword to complete
+ * keyword_size -- Maximum length of the keyword array to store
+ * completion in
+ *
+ * Returns the number of valid completions, i.e.:
+ *
+ * 0 if there are no valid completions. The value of the keyword argument has
+ * not been changed.
+ * 1 if there is only a single valid completion. The value of the keyword
+ * argument has been replaced with the full text of that completion.
+ * >1 if there is more than one valid completion. The value of the keyword
+ * argument has been replaced with the longest substring common to all the
+ * appropriate completions.
+ */
+int
+keyword_complete(char *keyword, int keyword_size)
+{
+ char *candidate = NULL;
+ int cnt = 0;
+ int common_prefix_length = 0;
+ int keyword_length;
+ KEYWORD_S *kw;
+
+ keyword_length = strlen(keyword);
+
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ char *kw_name = kw->nick ? kw->nick : kw->kw;
+ if(!kw_name)
+ continue;
+ if(strncmp(kw_name, keyword, keyword_length) == 0){
+ /* This is a candidate for completion. */
+ cnt++;
+ if(!candidate){
+ /* This is the first candidate. Keep it as a future reference to
+ * compare against to find the longest common prefix length of
+ * all the matches. */
+ candidate = kw_name;
+ common_prefix_length = strlen(candidate);
+ }
+ else{
+ /* Find the common prefix length between the first candidate and
+ * this one. */
+ int i;
+ for(i = 0; i < common_prefix_length; i++){
+ if(kw_name[i] != candidate[i]){
+ /* In the event that we ended up in the middle of a
+ * UTF-8 code point, backtrack to the byte before the
+ * start of this code point. */
+ while(i > 0 && (candidate[i] & 0xC0) == 0x80)
+ i--;
+ common_prefix_length = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if(cnt > 0){
+ int length = MIN(keyword_size, common_prefix_length);
+ strncpy(keyword + keyword_length, candidate + keyword_length, length - keyword_length);
+ keyword[length] = '\0';
+ }
+
+ return cnt;
+}
+
+
+/*
* Allow user to choose a list of keywords from their list of keywords.
*
* Returns allocated list.
@@ -9268,6 +9521,7 @@ choose_a_charset(int which_charsets)
char **charset_list, **lp;
const CHARSET *cs;
int cnt;
+ void (*redraw)(void) = ps_global->redrawer;
/*
* Build a list of charsets to choose from.
@@ -9311,8 +9565,10 @@ choose_a_charset(int which_charsets)
_("character sets"), h_select_charset_screen,
_("HELP FOR SELECTING A CHARACTER SET"), NULL);
- if(!choice)
+ if(!choice){
q_status_message(SM_ORDER, 1, 4, "No choice");
+ ps_global->redrawer = redraw;
+ }
free_list_array(&charset_list);