mirror of https://gitee.com/openkylin/libvirt.git
vsh: Rework how option to complete is found
The way that auto completion works currently is that user's input is parsed, and then we try to find the first --option (in the parsed structure) that has the same value as user's input around where <TAB> was pressed. For instance, for the following input: virsh # command --arg1 hello --arg2 world<TAB> we will see "world" as text that user is trying to autocomplete (this is affected by rl_basic_word_break_characters which readline uses internally to break user's input into individual words) and find that it is --arg2 that user is trying to autocomplete. So far so good, for this naive approach. But consider the following example: virsh # command --arg1 world --arg2 world<TAB> Here, both arguments have the same value and because we see "world" as text that user is trying to autocomplete we would think that it is --arg1 that user wants to autocomplete. This is obviously wrong. Fortunately, readline stores the current position of cursor (into rl_point) and we can use that when parsing user's input: whenever we reach a position that matches the cursor then we know that that is the place where <TAB> was pressed and hence that is the --option that user wants to autocomplete. Readline stores the cursor position as offset (numbered from 1) from the beginning of user's input. We store this input into @parser->pos initially, but then advance it as we tokenize it. Therefore, what we need is to store the original position too. Thanks to Martin who helped me with this. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com>
This commit is contained in:
parent
f61a4e91ef
commit
22904b5702
|
@ -777,7 +777,7 @@ virshParseArgv(vshControl *ctl, int argc, char **argv)
|
||||||
ctl->imode = false;
|
ctl->imode = false;
|
||||||
if (argc - optind == 1) {
|
if (argc - optind == 1) {
|
||||||
vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
|
vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
|
||||||
return vshCommandStringParse(ctl, argv[optind], NULL);
|
return vshCommandStringParse(ctl, argv[optind], NULL, 0);
|
||||||
} else {
|
} else {
|
||||||
return vshCommandArgvParse(ctl, argc - optind, argv + optind);
|
return vshCommandArgvParse(ctl, argc - optind, argv + optind);
|
||||||
}
|
}
|
||||||
|
@ -915,7 +915,7 @@ main(int argc, char **argv)
|
||||||
if (*ctl->cmdstr) {
|
if (*ctl->cmdstr) {
|
||||||
vshReadlineHistoryAdd(ctl->cmdstr);
|
vshReadlineHistoryAdd(ctl->cmdstr);
|
||||||
|
|
||||||
if (vshCommandStringParse(ctl, ctl->cmdstr, NULL))
|
if (vshCommandStringParse(ctl, ctl->cmdstr, NULL, 0))
|
||||||
vshCommandRun(ctl, ctl->cmd);
|
vshCommandRun(ctl, ctl->cmd);
|
||||||
}
|
}
|
||||||
VIR_FREE(ctl->cmdstr);
|
VIR_FREE(ctl->cmdstr);
|
||||||
|
|
|
@ -1364,7 +1364,7 @@ vshAdmParseArgv(vshControl *ctl, int argc, char **argv)
|
||||||
ctl->imode = false;
|
ctl->imode = false;
|
||||||
if (argc - optind == 1) {
|
if (argc - optind == 1) {
|
||||||
vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
|
vshDebug(ctl, VSH_ERR_INFO, "commands: \"%s\"\n", argv[optind]);
|
||||||
return vshCommandStringParse(ctl, argv[optind], NULL);
|
return vshCommandStringParse(ctl, argv[optind], NULL, 0);
|
||||||
} else {
|
} else {
|
||||||
return vshCommandArgvParse(ctl, argc - optind, argv + optind);
|
return vshCommandArgvParse(ctl, argc - optind, argv + optind);
|
||||||
}
|
}
|
||||||
|
@ -1594,7 +1594,7 @@ main(int argc, char **argv)
|
||||||
if (*ctl->cmdstr) {
|
if (*ctl->cmdstr) {
|
||||||
vshReadlineHistoryAdd(ctl->cmdstr);
|
vshReadlineHistoryAdd(ctl->cmdstr);
|
||||||
|
|
||||||
if (vshCommandStringParse(ctl, ctl->cmdstr, NULL))
|
if (vshCommandStringParse(ctl, ctl->cmdstr, NULL, 0))
|
||||||
vshCommandRun(ctl, ctl->cmd);
|
vshCommandRun(ctl, ctl->cmd);
|
||||||
}
|
}
|
||||||
VIR_FREE(ctl->cmdstr);
|
VIR_FREE(ctl->cmdstr);
|
||||||
|
|
58
tools/vsh.c
58
tools/vsh.c
|
@ -1318,6 +1318,8 @@ struct _vshCommandParser {
|
||||||
char **, bool);
|
char **, bool);
|
||||||
/* vshCommandStringGetArg() */
|
/* vshCommandStringGetArg() */
|
||||||
char *pos;
|
char *pos;
|
||||||
|
const char *originalLine;
|
||||||
|
size_t point;
|
||||||
/* vshCommandArgvGetArg() */
|
/* vshCommandArgvGetArg() */
|
||||||
char **arg_pos;
|
char **arg_pos;
|
||||||
char **arg_end;
|
char **arg_end;
|
||||||
|
@ -1426,6 +1428,10 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser, vshCmd **partial)
|
||||||
arg->data = tkdata;
|
arg->data = tkdata;
|
||||||
tkdata = NULL;
|
tkdata = NULL;
|
||||||
arg->next = NULL;
|
arg->next = NULL;
|
||||||
|
|
||||||
|
if (parser->pos - parser->originalLine == parser->point - 1)
|
||||||
|
arg->completeThis = true;
|
||||||
|
|
||||||
if (!first)
|
if (!first)
|
||||||
first = arg;
|
first = arg;
|
||||||
if (last)
|
if (last)
|
||||||
|
@ -1477,6 +1483,9 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser, vshCmd **partial)
|
||||||
arg->next = NULL;
|
arg->next = NULL;
|
||||||
tkdata = NULL;
|
tkdata = NULL;
|
||||||
|
|
||||||
|
if (parser->pos - parser->originalLine == parser->point)
|
||||||
|
arg->completeThis = true;
|
||||||
|
|
||||||
if (!first)
|
if (!first)
|
||||||
first = arg;
|
first = arg;
|
||||||
if (last)
|
if (last)
|
||||||
|
@ -1588,7 +1597,7 @@ vshCommandArgvGetArg(vshControl *ctl G_GNUC_UNUSED,
|
||||||
bool
|
bool
|
||||||
vshCommandArgvParse(vshControl *ctl, int nargs, char **argv)
|
vshCommandArgvParse(vshControl *ctl, int nargs, char **argv)
|
||||||
{
|
{
|
||||||
vshCommandParser parser;
|
vshCommandParser parser = { 0 };
|
||||||
|
|
||||||
if (nargs <= 0)
|
if (nargs <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
@ -1675,15 +1684,38 @@ vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, char **res,
|
||||||
return VSH_TK_ARG;
|
return VSH_TK_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vshCommandStringParse:
|
||||||
|
* @ctl virsh control structure
|
||||||
|
* @cmdstr: string to parse
|
||||||
|
* @partial: store partially parsed command here
|
||||||
|
* @point: position of cursor (rl_point)
|
||||||
|
*
|
||||||
|
* Parse given string @cmdstr as a command and store it under
|
||||||
|
* @ctl->cmd. For readline completion, if @partial is not NULL on
|
||||||
|
* the input then errors in parsing are ignored (because user is
|
||||||
|
* still in progress of writing the command string) and partially
|
||||||
|
* parsed command is stored at *@partial (caller has to free it
|
||||||
|
* afterwards). Among with @partial, caller must set @point which
|
||||||
|
* is the position of cursor in @cmdstr (offset, numbered from 1).
|
||||||
|
* Parser will then set @completeThis attribute to true for the
|
||||||
|
* vshCmdOpt that appeared under the cursor.
|
||||||
|
*/
|
||||||
bool
|
bool
|
||||||
vshCommandStringParse(vshControl *ctl, char *cmdstr, vshCmd **partial)
|
vshCommandStringParse(vshControl *ctl,
|
||||||
|
char *cmdstr,
|
||||||
|
vshCmd **partial,
|
||||||
|
size_t point)
|
||||||
{
|
{
|
||||||
vshCommandParser parser;
|
vshCommandParser parser = { 0 };
|
||||||
|
|
||||||
if (cmdstr == NULL || *cmdstr == '\0')
|
if (cmdstr == NULL || *cmdstr == '\0')
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
parser.pos = cmdstr;
|
parser.pos = cmdstr;
|
||||||
|
parser.originalLine = cmdstr;
|
||||||
|
parser.point = point;
|
||||||
parser.getNextArg = vshCommandStringGetArg;
|
parser.getNextArg = vshCommandStringGetArg;
|
||||||
return vshCommandParse(ctl, &parser, partial);
|
return vshCommandParse(ctl, &parser, partial);
|
||||||
}
|
}
|
||||||
|
@ -2634,28 +2666,20 @@ vshReadlineOptionsGenerator(const char *text,
|
||||||
|
|
||||||
|
|
||||||
static const vshCmdOptDef *
|
static const vshCmdOptDef *
|
||||||
vshReadlineCommandFindOpt(const vshCmd *partial,
|
vshReadlineCommandFindOpt(const vshCmd *partial)
|
||||||
const char *text)
|
|
||||||
{
|
{
|
||||||
const vshCmd *tmp = partial;
|
const vshCmd *tmp = partial;
|
||||||
|
|
||||||
while (tmp && tmp->next) {
|
while (tmp) {
|
||||||
if (tmp->def == tmp->next->def &&
|
|
||||||
!tmp->next->opts)
|
|
||||||
break;
|
|
||||||
tmp = tmp->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmp && tmp->opts) {
|
|
||||||
const vshCmdOpt *opt = tmp->opts;
|
const vshCmdOpt *opt = tmp->opts;
|
||||||
|
|
||||||
while (opt) {
|
while (opt) {
|
||||||
if (STREQ_NULLABLE(opt->data, text) ||
|
if (opt->completeThis)
|
||||||
STREQ_NULLABLE(opt->data, " "))
|
|
||||||
return opt->def;
|
return opt->def;
|
||||||
|
|
||||||
opt = opt->next;
|
opt = opt->next;
|
||||||
}
|
}
|
||||||
|
tmp = tmp->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2717,7 +2741,7 @@ vshReadlineParse(const char *text, int state)
|
||||||
|
|
||||||
*(line + rl_point) = '\0';
|
*(line + rl_point) = '\0';
|
||||||
|
|
||||||
vshCommandStringParse(NULL, line, &partial);
|
vshCommandStringParse(NULL, line, &partial, rl_point);
|
||||||
|
|
||||||
if (partial) {
|
if (partial) {
|
||||||
cmd = partial->def;
|
cmd = partial->def;
|
||||||
|
@ -2735,7 +2759,7 @@ vshReadlineParse(const char *text, int state)
|
||||||
cmd = NULL;
|
cmd = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
opt = vshReadlineCommandFindOpt(partial, text);
|
opt = vshReadlineCommandFindOpt(partial);
|
||||||
|
|
||||||
if (!cmd) {
|
if (!cmd) {
|
||||||
list = vshReadlineCommandGenerator(text);
|
list = vshReadlineCommandGenerator(text);
|
||||||
|
|
|
@ -152,6 +152,8 @@ struct _vshCmdOptDef {
|
||||||
struct _vshCmdOpt {
|
struct _vshCmdOpt {
|
||||||
const vshCmdOptDef *def; /* non-NULL pointer to option definition */
|
const vshCmdOptDef *def; /* non-NULL pointer to option definition */
|
||||||
char *data; /* allocated data, or NULL for bool option */
|
char *data; /* allocated data, or NULL for bool option */
|
||||||
|
bool completeThis; /* true if this is the option user's wishing to
|
||||||
|
autocomplete */
|
||||||
vshCmdOpt *next;
|
vshCmdOpt *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -292,7 +294,8 @@ int vshBlockJobOptionBandwidth(vshControl *ctl,
|
||||||
unsigned long *bandwidth);
|
unsigned long *bandwidth);
|
||||||
bool vshCommandOptBool(const vshCmd *cmd, const char *name);
|
bool vshCommandOptBool(const vshCmd *cmd, const char *name);
|
||||||
bool vshCommandRun(vshControl *ctl, const vshCmd *cmd);
|
bool vshCommandRun(vshControl *ctl, const vshCmd *cmd);
|
||||||
bool vshCommandStringParse(vshControl *ctl, char *cmdstr, vshCmd **partial);
|
bool vshCommandStringParse(vshControl *ctl, char *cmdstr,
|
||||||
|
vshCmd **partial, size_t point);
|
||||||
|
|
||||||
const vshCmdOpt *vshCommandOptArgv(vshControl *ctl, const vshCmd *cmd,
|
const vshCmdOpt *vshCommandOptArgv(vshControl *ctl, const vshCmd *cmd,
|
||||||
const vshCmdOpt *opt);
|
const vshCmdOpt *opt);
|
||||||
|
|
Loading…
Reference in New Issue