[libvirt] [v4 0/5] virsh: More intelligent auto-completion

This series implements advanced auto-completion for virsh. Using custom option completers (examples in 3/5, 4/5 and 5/5), it is possible to further complete any option with defined completer function. Option completer function should return list of completed names for each desired option. Using C99 initialization, it is easy to enable specific completer for any option. For example, when we want domain completer for "start --domain" option, we write vshDomainCompleter() and along with desired flags, initialize targeted opts struct. static const vshCmdOptDef opts_start[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("name of the inactive domain"), .completer = vshDomainCompleter, .completer_flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE | VIR_CONNECT_LIST_DOMAINS_PERSISTENT }, Auto-completion itself then works like this: virsh # start --domain <TAB> domain1 domain2 domainN virsh # vol-key <TAB> --help --pool --vol vol1 vol2 --- v4: * rewritten to use only option completers v3: https://www.redhat.com/archives/libvir-list/2013-August/msg01294.html v2: https://www.redhat.com/archives/libvir-list/2013-August/msg00992.html v1: https://www.redhat.com/archives/libvir-list/2013-August/msg00371.html Tomas Meszaros (5): virsh: Add vshCmdCompleter and vshOptCompleter virsh: Improve readline generators and readline completion virsh: Add vshDomainCompleter virsh: Add vshSuspendTargetCompleter virsh: Add vshRebootShutdownModeCompleter tools/virsh-domain-monitor.c | 52 +++++-- tools/virsh-domain.c | 310 +++++++++++++++++++++++++++++--------- tools/virsh-snapshot.c | 50 +++++-- tools/virsh.c | 350 +++++++++++++++++++++++++++++++++++++++---- tools/virsh.h | 8 + 5 files changed, 648 insertions(+), 122 deletions(-) -- 1.8.3.1

completer and completer_flags added to the _vshCmdOptDef structure so it will be possible for completion generators to conveniently call option completer functions with desired flags. --- v4 * merged (*vshCmdCompleter) and (*vshOptCompleter) into (*vshCompleter) * deleted completer and completer_flags from the vshCmdDef, now using only opt completer tools/virsh.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/virsh.h b/tools/virsh.h index b5e2715..f978d94 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -148,6 +148,8 @@ typedef struct _vshCmdOptDef vshCmdOptDef; typedef struct _vshControl vshControl; typedef struct _vshCtrlData vshCtrlData; +typedef char **(*vshCompleter)(unsigned int flags); + /* * vshCmdInfo -- name/value pair for information about command * @@ -169,6 +171,8 @@ struct _vshCmdOptDef { unsigned int flags; /* flags */ const char *help; /* non-NULL help string; or for VSH_OT_ALIAS * the name of a later public option */ + vshCompleter completer; /* option completer */ + unsigned int completer_flags; /* option completer flags */ }; /* -- 1.8.3.1

On 09/10/2013 09:54 AM, Tomas Meszaros wrote:
completer and completer_flags added to the _vshCmdOptDef structure so it will be possible for completion generators to conveniently call option completer functions with desired flags.
Subject line no longer matches the patch body.
--- v4 * merged (*vshCmdCompleter) and (*vshOptCompleter) into (*vshCompleter) * deleted completer and completer_flags from the vshCmdDef, now using only opt completer
tools/virsh.h | 4 ++++ 1 file changed, 4 insertions(+)
ACK. I'll probably push this shortly, regardless of how the rest of the series review goes, as it doesn't hurt. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

This patch is rather big and introduces several new functions, but I kept all new functions in the one patch because they are all connected together. I had to extend vshReadlineOptionsGenerator() a lot, so it is possible to fully complete options by calling appropriate opt->completer(). vshReadlineOptionsCompletionGenerator() is simple new function which is used only when we need to fully complete specific option, e.g.: virsh # vol-key --vol <TAB> --- v2 * vshMalloc is now used in vshDetermineCommandName * vshStrdup instead of vshMalloc + snprintf * joined if's in vshReadlineOptionsCompletionGenerator v3 * fixed typo * removed useless if's v4 * rewritten so we can use only option completers instead of cmd+opt completers * added --help auto-completion tools/virsh.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 251 insertions(+), 32 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index bf2fbf8..321ed5d 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -2587,6 +2587,118 @@ vshCloseLogFile(vshControl *ctl) * ----------------- */ +static const vshCmdDef * +vshDetermineCommandName(void) +{ + const vshCmdDef *cmd = NULL; + char *p; + char *cmdname; + + if (!(p = strchr(rl_line_buffer, ' '))) + return NULL; + + cmdname = vshMalloc(NULL, (p - rl_line_buffer) + 1); + memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); + + cmd = vshCmddefSearch(cmdname); + VIR_FREE(cmdname); + + return cmd; +} + +/* + * Check if option with opt_name is already fully completed. + */ +static bool +vshOptFullyCompleted(const char *opt_name) +{ + const vshCmdDef *cmd = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_fully_completed = false; + + if (!opt_name) + return opt_fully_completed; + + cmd = vshDetermineCommandName(); + + if (!cmd) + return opt_fully_completed; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + + if (!STREQ(opt->name, opt_name) || !opt->completer) + continue; + + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name)) + opt_fully_completed = true; + } + virStringFreeList(completed_list); + } + return opt_fully_completed; +} + +/* + * Return option which is present in the rl_line_buffer, but is not fully + * auto-completed (opt->completer() hasn't been used). + */ +static const vshCmdOptDef * +vshGetOptMissingCmp(void) +{ + const vshCmdDef *cmd = NULL; + char *opt_name_prefixed = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_completed; + + cmd = vshDetermineCommandName(); + + if (!cmd) + return NULL; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + opt_name_prefixed = vshMalloc(NULL, strlen(opt->name) + 3); + snprintf(opt_name_prefixed, strlen(opt->name) + 3, "--%s", opt->name); + + if (strstr(rl_line_buffer, opt_name_prefixed) && opt->completer) { + opt_completed = false; + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name)) + opt_completed = true; + } + virStringFreeList(completed_list); + + if (!opt_completed) + return opt; + } + } + + return NULL; +} + /* * Generator function for command completion. STATE lets us * know whether to start from scratch; without any state @@ -2631,28 +2743,27 @@ vshReadlineCommandGenerator(const char *text, int state) return NULL; } +/* + * Generator function for option completion. Provides --option name + * auto-completion and also advanced option completion by using opt->completer() + * functions. + */ static char * vshReadlineOptionsGenerator(const char *text, int state) { - static int list_index, len; - static const vshCmdDef *cmd = NULL; - const char *name; + static int opt_list_index, completed_list_index, len; + static char **completed_list; + static bool help_completed; + static const vshCmdDef *cmd; + char *opt_name_prefixed = NULL; + char *completed_name = NULL; if (!state) { - /* determine command name */ - char *p; - char *cmdname; - - if (!(p = strchr(rl_line_buffer, ' '))) - return NULL; - - cmdname = vshCalloc(NULL, (p - rl_line_buffer) + 1, 1); - memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); - - cmd = vshCmddefSearch(cmdname); - list_index = 0; + cmd = vshDetermineCommandName(); + opt_list_index = 0; + completed_list_index = 0; len = strlen(text); - VIR_FREE(cmdname); + help_completed = false; } if (!cmd) @@ -2661,45 +2772,153 @@ vshReadlineOptionsGenerator(const char *text, int state) if (!cmd->opts) return NULL; - while ((name = cmd->opts[list_index].name)) { - const vshCmdOptDef *opt = &cmd->opts[list_index]; - char *res; + while (cmd->opts[opt_list_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opt_list_index]; + opt_name_prefixed = vshMalloc(NULL, strlen(opt->name) + 3); + snprintf(opt_name_prefixed, strlen(opt->name) + 3, "--%s", opt->name); + + if (strstr(rl_line_buffer, opt_name_prefixed) || + vshOptFullyCompleted(opt->name) || + opt->type == VSH_OT_ARGV) { + /* We want to skip option which has been already auto-completed + * (is present in rl_line_buffer) or fully auto-completed + * (opt->completer() has been successfully applied on this option) + * and also ignore non --option. + */ + opt_list_index++; + continue; + } + + if (opt->flags == VSH_OFLAG_REQ && opt->type == VSH_OT_DATA && + opt->completer && !vshOptFullyCompleted(opt->name)) { + /* Call opt->completer() for option marked as required + * (option itself does not necessarily needs to be already + * auto-completed). + */ + + if (!completed_list_index) + completed_list = opt->completer(opt->completer_flags); + + if (completed_list) { + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + + if (len > 0 && !STRPREFIX(completed_name, text)) { + /* Skip irrelevant names. */ + continue; + } + return vshStrdup(NULL, completed_name); + } + } + virStringFreeList(completed_list); + completed_list_index = 0; + } - list_index++; + if (len > 2 && !STRPREFIX(opt_name_prefixed, text)) { + /* We want to pass options that are only relevant for provided + * @text. + * + * This has to be after the opt->completer() call, because + * completed_name can sometimes partially match --option, + * .e.g. --pool pool1 + */ + opt_list_index++; + continue; + } - if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV) - /* ignore non --option */ + if (len > 0 && !STRPREFIX(text, "-")) { + /* Skip options when user wants to auto-complete something that + * does not starts with prefix "-". + */ + opt_list_index++; continue; + } - if (len > 2) { - if (STRNEQLEN(name, text + 2, len - 2)) - continue; + opt_list_index++; + return opt_name_prefixed; + } + + if (!help_completed) { + /* When appropriate, auto-complete --help option. */ + if ((len > 2 && !STRPREFIX("--help", text)) || + (len > 0 && !STRPREFIX(text, "-")) || + strstr(rl_line_buffer, "--help")) { + return NULL; } - res = vshMalloc(NULL, strlen(name) + 3); - snprintf(res, strlen(name) + 3, "--%s", name); - return res; + help_completed = true; + return vshStrdup(NULL, "--help"); } /* If no names matched, then return NULL. */ return NULL; } +/* + * Generator function for option completion. Provides advanced completion + * for command options. + */ +static char * +vshReadlineOptionsCompletionGenerator(const char *text, int state) +{ + static const vshCmdOptDef *opt = NULL; + static int completed_list_index, len; + static char **completed_list; + char *completed_name; + + if (!state) { + opt = vshGetOptMissingCmp(); + completed_list_index = 0; + len = strlen(text); + } + + if (!opt) + return NULL; + + if (!opt->completer) + return NULL; + + if (!state) + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + return NULL; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + + if (STRNEQLEN(completed_name, text, len)) + /* Skip irrelevant names. */ + continue; + + return vshStrdup(NULL, completed_name); + } + virStringFreeList(completed_list); + + return NULL; +} + static char ** vshReadlineCompletion(const char *text, int start, int end ATTRIBUTE_UNUSED) { char **matches = (char **) NULL; + const vshCmdOptDef *opt_missing_cmp = vshGetOptMissingCmp(); - if (start == 0) + if (start == 0) { /* command name generator */ matches = rl_completion_matches(text, vshReadlineCommandGenerator); - else + } else { /* commands options */ - matches = rl_completion_matches(text, vshReadlineOptionsGenerator); + if (opt_missing_cmp) { + matches = rl_completion_matches(text, vshReadlineOptionsCompletionGenerator); + } else { + matches = rl_completion_matches(text, vshReadlineOptionsGenerator); + } + } + return matches; } - static int vshReadlineInit(vshControl *ctl) { -- 1.8.3.1

On 09/10/2013 09:54 AM, Tomas Meszaros wrote:
This patch is rather big and introduces several new functions, but I kept all new functions in the one patch because they are all connected together.
I had to extend vshReadlineOptionsGenerator() a lot, so it is possible to fully complete options by calling appropriate opt->completer().
vshReadlineOptionsCompletionGenerator() is simple new function which is used only when we need to fully complete specific option, e.g.: virsh # vol-key --vol <TAB>
---
I still think you are mixing a bit too much into one patch, and that you are reimplementing pieces of the parser instead of refactoring what is already existing in other places for easier reuse. On the other hand, I'm seeing some nice improvements. Below, I'm using ^^ to highlight what I typed; best viewed in fixed width font. Pre-patch, I'm seeing: virsh# vol-k<TAB> ^^^^^^^^^^ virsh# vol-key <TAB> ^^^^^ virsh# vol-key --pool <TAB> ^^^^^ virsh# vol-key --pool --pool while post-patch, it changes to: virsh# vol-k<TAB> ^^^^^^^^^^ virsh# vol-key --<TAB> ^^^^^ --help --pool --vol virsh# vol-key --<TAB> ^^^^^ --help --pool --vol virsh# vol-key --p<TAB> ^^^^^^ virsh# vol-key --pool <TAB> ^^^^^ --help --vol Which is definitely better, but still not perfect (--pool takes a mandatory argument, so we should NOT be offering --vol as a valid completion once --pool has been typed, until AFTER the argument of pool has also been typed).
+++ b/tools/virsh.c @@ -2587,6 +2587,118 @@ vshCloseLogFile(vshControl *ctl) * ----------------- */
+static const vshCmdDef * +vshDetermineCommandName(void) +{ + const vshCmdDef *cmd = NULL; + char *p; + char *cmdname; + + if (!(p = strchr(rl_line_buffer, ' '))) + return NULL; + + cmdname = vshMalloc(NULL, (p - rl_line_buffer) + 1); + memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); + + cmd = vshCmddefSearch(cmdname); + VIR_FREE(cmdname); + + return cmd; +}
This particular part of the patch looks like it is just code motion (moving it earlier in the file to avoid having to do a forward reference). If so, please split that into a separate patch. Both pre- and post-patch, I noticed: virsh# e''<TAB> ^^^^^^^^ list of files in current directory starting with e although what I really want is: virsh# e''<TAB> ^^^^^^^^ echo edit emulatorpin exit virsh# e''cho hi<ENTER> ^^^^^^^^^^^^^ hi that is, the virsh parser is already pretty sophisticated about doing quote removal, but the readline parser is not. I think this function needs to take advantage of what parsing we already do. Ideally, you should be using stuff like vshCommandStringGetArg to take rl_line_buffer and split that into de-quoted words, and only then try to figure out what completions to attempt. That is, instead of trying to complete on "e''" (which is not a prefix of any command, and therefore falls back to the default completion of file names), you would be completing on "e" (the results after vshCommandStringGetArg removes quotes). Then again, if you do that, then you also have to figure out the readline hooks so that when the completion is not ambiguous (or when it is ambiguous but has a non-ambiguous prefix and the user has requested that <TAB> auto-type the common prefix), you have to know which part of the completion is the suffix to append to what the user typed without stripping the quotes they already typed. It can get tricky fast, so maybe the current behavior is okay, but food for thought.
+ +/* + * Check if option with opt_name is already fully completed. + */ +static bool +vshOptFullyCompleted(const char *opt_name) +{ + const vshCmdDef *cmd = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_fully_completed = false; + + if (!opt_name) + return opt_fully_completed; + + cmd = vshDetermineCommandName();
Here's an interesting exercise - run under gdb and put a breakpoint on vshDetermineCommandName() (that gets tricky - I find it easiest to start virsh in batch mode in one terminal, then use 'gdb --pid $(pidof lt-virsh)' in another), and see how many times you are calling this function (not all necessarily from this call site, but still instructive). For example, I counted: virsh# e<TAB> => once virsh# ec<TAB> => once virsh# echo <TAB> => seven times virsh# echo --s<TAB> => six times Wow. Once should be sufficient; which means we aren't caching what we've learned about rl_line_buffer. Really, when the user hits <TAB>, we should COMPLETELY parse what they've typed so far (tokenize it into an array of arguments with quotes stripped, with special handling if the final argument is an unterminated quote such as hitting <TAB> after unmatched '), and then ALL of our completion functions should refer back to that cached structure for the remainder of their work, without having to reparse the line. Readline already gives you the hints you need for proper memory management of that cache (argument of 0 when <TAB> is hit, non-zero for all subsequent work until you finally return NULL to end the completion list). (Yeah, I wish it were via an opaque pointer, rather than requiring you to store it in global variables, but such is life when dealing with super-old APIs that fortunately still happen to only need single-threading even today). Your only saving grace is that tab completion is interactive and that we don't have any command with more than 32 options by virtue of how we track which options have been seen so far in an int bitmap, so even though your behavior feels like O(n*2) (or maybe even O(n*3)), we don't necessarily hit the penalties of scale where the user will notice the wasted cpu cycles. On the other hand, I did NOT test is whether you call vshDetermineCommandName() once per volume in the per-volume completer - and there, we KNOW there are users that stick literally thousands of volumes in one pool, where the cost of calling vshDetermineCommandName() per volume would quickly become a noticeable delay. There is no reason why we cannot get the parsing down to O(1) (once per tab).
+ + if (!cmd) + return opt_fully_completed; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + + if (!STREQ(opt->name, opt_name) || !opt->completer) + continue; + + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name))
strstr() is NOT the right function to be using. A demonstration that strstr is not right: virsh# echo "string with --shell in middle" --shell<ENTER> 'string with --shell in middle' virsh# echo "string with --shell in middle" --<TAB> --help --str --xml Oops. Notice that the way virsh parses command lines, it is VALID to put --shell AFTER the first --str argument; but your strstr mistakenly hits on the embedded --shell inside the middle of the (implicit --str) argument and refuses to treat --shell as a valid completion at the point where I hit TAB. Again, I argue that you should parse the complete line into an array of strings, and then evaluate those strings in order, similar to how vshCmddefOptParse is behaving. In fact, I think that your first order of business is to prepare a preliminary patch that refactors the body of vshCmddefOptParse into calls to a helper function, so that both it and your new code share the same parse engine, and the only place they differ is what they do once the parse is complete - vshCmddefOptParse raises errors if an option is missing an argument, if a required option is missing, if an unknown option is found, or invokes the command; while tab completion knows that an option missing an argument or a missing required option means to do the per-option completer for that particular option, unknown options mean that there is no longer anything to complete, and the end result is displaying the completion instead of running the command. I also think it would be wise, for testing purposes, to implement my proposed new 'virsh complete ...' command EARLY in the series, so that you can more easily test scenarios from the command line instead of having to fire up interactive gdb sessions across two terminals (that is, it's easier to debug 'gdb --args virsh complete echo -' than it is to fire up 'virsh' in one window, attach gdb in the other window, type 'echo -<TAB>' in the first window, then interact with gdb in the second). That, and having 'virsh complete' working would let us write unit tests (tests/virshtest.c and tests/virsh-optparse already do a lot of argument parsing testing on 'echo', these tests would be a great starting point for a new test that virsh complete returns what we think it should). Remember, if you parse this exactly once, you would be left with: argv[0] = "echo", associated with cmd name argv[1] = "string with --shell in middle", associated with --str argv[2] = "--", needs completion along with the knowledge that --help, --shell, --str, and --xml are all still valid at that point (--help because it is always valid if we aren't waiting for an option's required argument; --str because it can appear more than once thanks to its VSH_OT_ARGV data-type, and the other two because they haven't been seen yet). At this point, I'm torn on whether it is effective to review the rest of your patch as-is, knowing that parts of it may be irrelevant if you first do the cleanup to fix the algorithms behind the patch. I'm glad that you're making progress, and I want to encourage you to keep trying (especially since I know this is a summer of code project, where you are under a timeline to get something submitted to qualify for benefits outside the scope of this list). And I also apologize that it has taken me a week to review your patch (you posted on the 10th, and it is now the 17th), as that cuts into your timeline - if nothing else, I hope that this serves as a positive learning experience that getting the design right up front is KEY to getting the code approved later with minimal churn (coding first, only to have the design ripped to shreds and having to do more iterations of code, can be frustrating, both to the coder and to the reviewers). Please, even if the summer of code deadlines pass, continue your work on this until it is in - I definitely want this in virsh. Revisiting some examples from my earlier mails:
virsh# vol-key vol <TAB> pool1 pool2 --pool
The one-time parser should split this into: argv[0] = "vol-key", cmd name argv[1] = "vol", argument to implicit --vol argv[2] = "", needs completion at this point, --pool is the next available option, so the completion is the union of the per-option pool completer (but with names beginning with - filtered out, because the parser would treat leading - as the start of an option rather than a pool name) and the possible remaining options (--pool and the ever-present --help)
virsh# vol-key -<TAB> --vol --pool
argv[0] = "vol-key", cmd name argv[1] = "-", needs completion at this point, we know we have to complete an option, so we don't use any per-option completer (neither volume nor pool names can appear here), and use just the option completer (--vol, --pool, and the ever-present --help)
virsh# vol-key v<TAB> vol1 vol2
argv[0] = "vol-key", cmd name argv[1] = "v", needs completion at this point, we know we have to complete a non-option; as no options have been consumed yet, this is the first option that requires an argument, so we treat it as an implicit --vol, and run just the volume name completer (filtered to just names beginning with 'v').
virsh# vol-key --pool <TAB> pool1 pool2
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires argument argv[2] = "", needs completion at this point, we know we have to supply the argument to the --pool option, so we run just the pool name completer, and have nothing to filter on (all pool names are valid, even those beginning with '-'). Guess what - even though I said "--help" is ever-present, here it does not get listed (--help is only present when an option can appear, but here we expect only an argument)
virsh# vol-key --pool pool1 <TAB> vol1 vol2 --vol
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires argument argv[2] = "pool1", provides argument to --pool argv[3] = "", needs completion at this point, we can take either an option or an argument to an implicit option; the next available option needing an argument is --vol, so we run the union of the per-option volume completer (filter out leading '-') and the remaining option completer (filter out --pool, leaving just --vol and the ever-present --help).
virsh# vol-key --pool=[<TAB> --pool=pool1 --pool=pool2
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires an argument argv[2] = "[", needs completion ooh tricky - I split "--pool=<TAB>" into two arguments, and then complete on just the per-option pool completer (filtered to names beginning with 'p'); then have to reconstruct it back into a single string for display. But having the parser normalize the code once, then generate the completion list, then munge the generated list back into display format at the end, will be easier than having to write the generation list to handle '--option arg' and '--option=arg' as equivalent throughout the entire body of completer code. [Oh, and I have homework too - I STILL need to submit my patch for a much simpler ./configure --without-readline. It's a challenge to see who can post the next patch :) ] -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 17/09/13 at 09:22pm, Eric Blake wrote:
On 09/10/2013 09:54 AM, Tomas Meszaros wrote:
This patch is rather big and introduces several new functions, but I kept all new functions in the one patch because they are all connected together.
I had to extend vshReadlineOptionsGenerator() a lot, so it is possible to fully complete options by calling appropriate opt->completer().
vshReadlineOptionsCompletionGenerator() is simple new function which is used only when we need to fully complete specific option, e.g.: virsh # vol-key --vol <TAB>
---
I still think you are mixing a bit too much into one patch, and that you are reimplementing pieces of the parser instead of refactoring what is already existing in other places for easier reuse.
On the other hand, I'm seeing some nice improvements. Below, I'm using ^^ to highlight what I typed; best viewed in fixed width font.
Pre-patch, I'm seeing:
virsh# vol-k<TAB> ^^^^^^^^^^ virsh# vol-key <TAB> ^^^^^ virsh# vol-key --pool <TAB> ^^^^^ virsh# vol-key --pool --pool
while post-patch, it changes to:
virsh# vol-k<TAB> ^^^^^^^^^^ virsh# vol-key --<TAB> ^^^^^ --help --pool --vol virsh# vol-key --<TAB> ^^^^^ --help --pool --vol virsh# vol-key --p<TAB> ^^^^^^ virsh# vol-key --pool <TAB> ^^^^^ --help --vol
Which is definitely better, but still not perfect (--pool takes a mandatory argument, so we should NOT be offering --vol as a valid completion once --pool has been typed, until AFTER the argument of pool has also been typed).
+++ b/tools/virsh.c @@ -2587,6 +2587,118 @@ vshCloseLogFile(vshControl *ctl) * ----------------- */
+static const vshCmdDef * +vshDetermineCommandName(void) +{ + const vshCmdDef *cmd = NULL; + char *p; + char *cmdname; + + if (!(p = strchr(rl_line_buffer, ' '))) + return NULL; + + cmdname = vshMalloc(NULL, (p - rl_line_buffer) + 1); + memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); + + cmd = vshCmddefSearch(cmdname); + VIR_FREE(cmdname); + + return cmd; +}
This particular part of the patch looks like it is just code motion (moving it earlier in the file to avoid having to do a forward reference). If so, please split that into a separate patch.
Yes it is just refactored code. I'll put it in the separate patch.
Both pre- and post-patch, I noticed:
virsh# e''<TAB> ^^^^^^^^ list of files in current directory starting with e
although what I really want is:
virsh# e''<TAB> ^^^^^^^^ echo edit emulatorpin exit virsh# e''cho hi<ENTER> ^^^^^^^^^^^^^ hi
that is, the virsh parser is already pretty sophisticated about doing quote removal, but the readline parser is not. I think this function needs to take advantage of what parsing we already do.
Ideally, you should be using stuff like vshCommandStringGetArg to take rl_line_buffer and split that into de-quoted words, and only then try to figure out what completions to attempt. That is, instead of trying to complete on "e''" (which is not a prefix of any command, and therefore falls back to the default completion of file names), you would be completing on "e" (the results after vshCommandStringGetArg removes quotes). Then again, if you do that, then you also have to figure out the readline hooks so that when the completion is not ambiguous (or when it is ambiguous but has a non-ambiguous prefix and the user has requested that <TAB> auto-type the common prefix), you have to know which part of the completion is the suffix to append to what the user typed without stripping the quotes they already typed. It can get tricky fast, so maybe the current behavior is okay, but food for thought.
I have looked over vshCommandStringGetArg but cannot figure out how to use it to parse rl_line_buffer... can you give me any hints?
+ +/* + * Check if option with opt_name is already fully completed. + */ +static bool +vshOptFullyCompleted(const char *opt_name) +{ + const vshCmdDef *cmd = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_fully_completed = false; + + if (!opt_name) + return opt_fully_completed; + + cmd = vshDetermineCommandName();
Here's an interesting exercise - run under gdb and put a breakpoint on vshDetermineCommandName() (that gets tricky - I find it easiest to start virsh in batch mode in one terminal, then use 'gdb --pid $(pidof lt-virsh)' in another), and see how many times you are calling this function (not all necessarily from this call site, but still instructive). For example, I counted:
virsh# e<TAB> => once virsh# ec<TAB> => once virsh# echo <TAB> => seven times virsh# echo --s<TAB> => six times
Yay, thats a bug. I've fixed it so it does not call vsHDetermineCommandName() over and over again.
Wow. Once should be sufficient; which means we aren't caching what we've learned about rl_line_buffer. Really, when the user hits <TAB>, we should COMPLETELY parse what they've typed so far (tokenize it into an array of arguments with quotes stripped, with special handling if the final argument is an unterminated quote such as hitting <TAB> after unmatched '), and then ALL of our completion functions should refer back to that cached structure for the remainder of their work, without having to reparse the line. Readline already gives you the hints you need for proper memory management of that cache (argument of 0 when <TAB> is hit, non-zero for all subsequent work until you finally return NULL to end the completion list). (Yeah, I wish it were via an opaque pointer, rather than requiring you to store it in global variables, but such is life when dealing with super-old APIs that fortunately still happen to only need single-threading even today).
That's good idea. I will try to reimplement it so it can use tokenized rl_line_buffer. Anyway, we will still need to call vshDetermineCommandName() to get vshCmdDef *cmd of the current command (or we declare another global variable which will take care of this, so we can refer to it every time we want to check current cmd in rl_line_buffer).
Your only saving grace is that tab completion is interactive and that we don't have any command with more than 32 options by virtue of how we track which options have been seen so far in an int bitmap, so even though your behavior feels like O(n*2) (or maybe even O(n*3)), we don't necessarily hit the penalties of scale where the user will notice the wasted cpu cycles. On the other hand, I did NOT test is whether you call vshDetermineCommandName() once per volume in the per-volume completer - and there, we KNOW there are users that stick literally thousands of volumes in one pool, where the cost of calling vshDetermineCommandName() per volume would quickly become a noticeable delay. There is no reason why we cannot get the parsing down to O(1) (once per tab).
+ + if (!cmd) + return opt_fully_completed; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + + if (!STREQ(opt->name, opt_name) || !opt->completer) + continue; + + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name))
strstr() is NOT the right function to be using. A demonstration that strstr is not right:
virsh# echo "string with --shell in middle" --shell<ENTER> 'string with --shell in middle' virsh# echo "string with --shell in middle" --<TAB> --help --str --xml
Oops. Notice that the way virsh parses command lines, it is VALID to put --shell AFTER the first --str argument; but your strstr mistakenly hits on the embedded --shell inside the middle of the (implicit --str) argument and refuses to treat --shell as a valid completion at the point where I hit TAB.
Again, I argue that you should parse the complete line into an array of strings, and then evaluate those strings in order, similar to how vshCmddefOptParse is behaving. In fact, I think that your first order of business is to prepare a preliminary patch that refactors the body of vshCmddefOptParse into calls to a helper function, so that both it and your new code share the same parse engine, and the only place they differ is what they do once the parse is complete - vshCmddefOptParse raises errors if an option is missing an argument, if a required option is missing, if an unknown option is found, or invokes the command; while tab completion knows that an option missing an argument or a missing required option means to do the per-option completer for that particular option, unknown options mean that there is no longer anything to complete, and the end result is displaying the completion instead of running the command.
I also think it would be wise, for testing purposes, to implement my proposed new 'virsh complete ...' command EARLY in the series, so that you can more easily test scenarios from the command line instead of having to fire up interactive gdb sessions across two terminals (that is, it's easier to debug 'gdb --args virsh complete echo -' than it is to fire up 'virsh' in one window, attach gdb in the other window, type 'echo -<TAB>' in the first window, then interact with gdb in the second). That, and having 'virsh complete' working would let us write unit tests (tests/virshtest.c and tests/virsh-optparse already do a lot of argument parsing testing on 'echo', these tests would be a great starting point for a new test that virsh complete returns what we think it should).
If I get it right: "complete" should be new command which would perform auto-completion on it's arguments. So when I type this from shell: $ ./virsh s<TAB> virsh should be called with "complete s" arguments. So it would be enough when I create new command called "complete"? Bash would take care of everything else?
Remember, if you parse this exactly once, you would be left with:
argv[0] = "echo", associated with cmd name argv[1] = "string with --shell in middle", associated with --str argv[2] = "--", needs completion
along with the knowledge that --help, --shell, --str, and --xml are all still valid at that point (--help because it is always valid if we aren't waiting for an option's required argument; --str because it can appear more than once thanks to its VSH_OT_ARGV data-type, and the other two because they haven't been seen yet).
At this point, I'm torn on whether it is effective to review the rest of your patch as-is, knowing that parts of it may be irrelevant if you first do the cleanup to fix the algorithms behind the patch. I'm glad that you're making progress, and I want to encourage you to keep trying (especially since I know this is a summer of code project, where you are under a timeline to get something submitted to qualify for benefits outside the scope of this list). And I also apologize that it has taken me a week to review your patch (you posted on the 10th, and it is now the 17th), as that cuts into your timeline - if nothing else, I hope that this serves as a positive learning experience that getting the design right up front is KEY to getting the code approved later with minimal churn (coding first, only to have the design ripped to shreds and having to do more iterations of code, can be frustrating, both to the coder and to the reviewers). Please, even if the summer of code deadlines pass, continue your work on this until it is in - I definitely want this in virsh.
I definitely want to continue working on this, even after official GSoC deadline.
Revisiting some examples from my earlier mails:
virsh# vol-key vol <TAB> pool1 pool2 --pool
The one-time parser should split this into:
argv[0] = "vol-key", cmd name argv[1] = "vol", argument to implicit --vol argv[2] = "", needs completion at this point, --pool is the next available option, so the completion is the union of the per-option pool completer (but with names beginning with - filtered out, because the parser would treat leading - as the start of an option rather than a pool name) and the possible remaining options (--pool and the ever-present --help)
virsh# vol-key -<TAB> --vol --pool
argv[0] = "vol-key", cmd name argv[1] = "-", needs completion at this point, we know we have to complete an option, so we don't use any per-option completer (neither volume nor pool names can appear here), and use just the option completer (--vol, --pool, and the ever-present --help)
virsh# vol-key v<TAB> vol1 vol2
argv[0] = "vol-key", cmd name argv[1] = "v", needs completion at this point, we know we have to complete a non-option; as no options have been consumed yet, this is the first option that requires an argument, so we treat it as an implicit --vol, and run just the volume name completer (filtered to just names beginning with 'v').
virsh# vol-key --pool <TAB> pool1 pool2
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires argument argv[2] = "", needs completion at this point, we know we have to supply the argument to the --pool option, so we run just the pool name completer, and have nothing to filter on (all pool names are valid, even those beginning with '-'). Guess what - even though I said "--help" is ever-present, here it does not get listed (--help is only present when an option can appear, but here we expect only an argument)
virsh# vol-key --pool pool1 <TAB> vol1 vol2 --vol
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires argument argv[2] = "pool1", provides argument to --pool argv[3] = "", needs completion at this point, we can take either an option or an argument to an implicit option; the next available option needing an argument is --vol, so we run the union of the per-option volume completer (filter out leading '-') and the remaining option completer (filter out --pool, leaving just --vol and the ever-present --help).
virsh# vol-key --pool=[<TAB> --pool=pool1 --pool=pool2
argv[0] = "vol-key", cmd name argv[1] = "--pool", --pool option requires an argument argv[2] = "[", needs completion ooh tricky - I split "--pool=<TAB>" into two arguments, and then complete on just the per-option pool completer (filtered to names beginning with 'p'); then have to reconstruct it back into a single string for display. But having the parser normalize the code once, then generate the completion list, then munge the generated list back into display format at the end, will be easier than having to write the generation list to handle '--option arg' and '--option=arg' as equivalent throughout the entire body of completer code.
Yeah, I understand this and all the logic behind it. I'll try to rewrite my code so it can work over tokenized line buffer instead of parsing actual rl_line_buffer.
[Oh, and I have homework too - I STILL need to submit my patch for a much simpler ./configure --without-readline. It's a challenge to see who can post the next patch :) ]
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
-- Tomas Meszaros

Function vshDomainCompler returns domains names which can be used by various virsh commands, for example: virsh # start --domain <TAB> fedora domain_foo domain_bar --- v2 * global variable __my_conn renamed to vshConn * @name is now const char * * label cleanup renamed to error v3 * removed useless if * used virStringFreeList() instead of iteration * added vshControl *vshCtl instead of virConnectPtr *vshConn because vshCtl is needed in order to call vshReconnect() * moved all .completer = vshDomainCompleter initializations from other patches into this v4 * vshControl *vshCtl marked static * changed vshMalloc to VIR_ALLOC_N * fixed link error if building without USE_READLINE * fixed @domains[] mem leak * vshDomainCompleter .completer and .completer_flags initializers are now only in cmdOptDef * reconnecting now takes place in the vshReadlineOptionsGenerator() and only just before auto-completion happens tools/virsh-domain-monitor.c | 52 ++++++-- tools/virsh-domain.c | 301 +++++++++++++++++++++++++++++++++---------- tools/virsh-snapshot.c | 50 +++++-- tools/virsh.c | 55 ++++++++ tools/virsh.h | 2 + 5 files changed, 373 insertions(+), 87 deletions(-) diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index b29b82a..f00cdfa 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -310,7 +310,9 @@ static const vshCmdOptDef opts_dommemstat[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "period", .type = VSH_OT_DATA, @@ -434,7 +436,10 @@ static const vshCmdOptDef opts_domblkinfo[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "device", .type = VSH_OT_DATA, @@ -489,7 +494,10 @@ static const vshCmdOptDef opts_domblklist[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "inactive", .type = VSH_OT_BOOL, @@ -603,7 +611,10 @@ static const vshCmdOptDef opts_domiflist[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "inactive", .type = VSH_OT_BOOL, @@ -708,7 +719,10 @@ static const vshCmdOptDef opts_domif_getlink[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "interface", .type = VSH_OT_DATA, @@ -823,7 +837,9 @@ static const vshCmdOptDef opts_domcontrol[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -876,7 +892,10 @@ static const vshCmdOptDef opts_domblkstat[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_RUNNING }, {.name = "device", .type = VSH_OT_DATA, @@ -1059,7 +1078,10 @@ static const vshCmdOptDef opts_domifstat[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_RUNNING }, {.name = "interface", .type = VSH_OT_DATA, @@ -1136,7 +1158,9 @@ static const vshCmdOptDef opts_domblkerror[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id, or uuid") + .help = N_("domain name, id, or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -1201,7 +1225,10 @@ static const vshCmdOptDef opts_dominfo[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -1343,7 +1370,10 @@ static const vshCmdOptDef opts_domstate[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "reason", .type = VSH_OT_BOOL, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 74feca1..7747446 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -165,7 +165,8 @@ static const vshCmdOptDef opts_attach_device[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -267,7 +268,9 @@ static const vshCmdOptDef opts_attach_disk[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "source", .type = VSH_OT_DATA, @@ -708,7 +711,9 @@ static const vshCmdOptDef opts_attach_interface[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "type", .type = VSH_OT_DATA, @@ -962,7 +967,10 @@ static const vshCmdOptDef opts_autostart[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "disable", .type = VSH_OT_BOOL, @@ -1018,7 +1026,10 @@ static const vshCmdOptDef opts_blkdeviotune[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "device", .type = VSH_OT_DATA, @@ -1242,7 +1253,10 @@ static const vshCmdOptDef opts_blkiotune[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "weight", .type = VSH_OT_INT, @@ -1505,7 +1519,10 @@ static const vshCmdOptDef opts_block_commit[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "path", .type = VSH_OT_DATA, @@ -1680,7 +1697,10 @@ static const vshCmdOptDef opts_block_copy[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "path", .type = VSH_OT_DATA, @@ -1886,7 +1906,9 @@ static const vshCmdOptDef opts_block_job[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "path", .type = VSH_OT_DATA, @@ -1984,7 +2006,9 @@ static const vshCmdOptDef opts_block_pull[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "path", .type = VSH_OT_DATA, @@ -2150,7 +2174,9 @@ static const vshCmdOptDef opts_block_resize[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "path", .type = VSH_OT_DATA, @@ -2220,7 +2246,10 @@ static const vshCmdOptDef opts_console[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "devname", .type = VSH_OT_STRING, @@ -2317,7 +2346,10 @@ static const vshCmdOptDef opts_domif_setlink[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "interface", .type = VSH_OT_DATA, @@ -2511,7 +2543,10 @@ static const vshCmdOptDef opts_domiftune[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "interface", .type = VSH_OT_DATA, @@ -2698,7 +2733,9 @@ static const vshCmdOptDef opts_suspend[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -2744,7 +2781,10 @@ static const vshCmdOptDef opts_dom_pm_suspend[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_RUNNING }, {.name = "duration", .type = VSH_OT_INT, @@ -2828,7 +2868,9 @@ static const vshCmdOptDef opts_dom_pm_wakeup[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -2877,7 +2919,10 @@ static const vshCmdOptDef opts_undefine[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name or uuid") + .help = N_("domain name or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "managed-save", .type = VSH_OT_BOOL, @@ -3238,7 +3283,10 @@ static const vshCmdOptDef opts_start[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("name of the inactive domain") + .help = N_("name of the inactive domain"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE | + VIR_CONNECT_LIST_DOMAINS_PERSISTENT }, #ifndef WIN32 {.name = "console", @@ -3422,7 +3470,9 @@ static const vshCmdOptDef opts_save[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -3886,7 +3936,11 @@ static const vshCmdOptDef opts_managedsave[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_RUNNING | + VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE }, {.name = "running", .type = VSH_OT_BOOL, @@ -4006,7 +4060,10 @@ static const vshCmdOptDef opts_managedsaveremove[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE | + VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE }, {.name = NULL} }; @@ -4065,7 +4122,10 @@ static const vshCmdOptDef opts_schedinfo[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "weight", .type = VSH_OT_INT, @@ -4408,7 +4468,9 @@ static const vshCmdOptDef opts_dump[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -4538,7 +4600,8 @@ static const vshCmdOptDef opts_screenshot[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -4685,7 +4748,10 @@ static const vshCmdOptDef opts_resume[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_PAUSED }, {.name = NULL} }; @@ -4728,7 +4794,9 @@ static const vshCmdOptDef opts_shutdown[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "mode", .type = VSH_OT_STRING, @@ -4814,7 +4882,9 @@ static const vshCmdOptDef opts_reboot[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "mode", .type = VSH_OT_STRING, @@ -4895,7 +4965,9 @@ static const vshCmdOptDef opts_reset[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -4938,7 +5010,9 @@ static const vshCmdOptDef opts_domjobinfo[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -5150,7 +5224,9 @@ static const vshCmdOptDef opts_domjobabort[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -5226,7 +5302,10 @@ static const vshCmdOptDef opts_vcpucount[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "maximum", .type = VSH_OT_BOOL, @@ -5436,7 +5515,10 @@ static const vshCmdOptDef opts_vcpuinfo[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -5545,7 +5627,10 @@ static const vshCmdOptDef opts_vcpupin[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "vcpu", .type = VSH_OT_INT, @@ -5832,7 +5917,10 @@ static const vshCmdOptDef opts_emulatorpin[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "cpulist", .type = VSH_OT_DATA, @@ -5951,7 +6039,9 @@ static const vshCmdOptDef opts_setvcpus[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "count", .type = VSH_OT_INT, @@ -6289,7 +6379,9 @@ static const vshCmdOptDef opts_cpu_stats[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "total", .type = VSH_OT_BOOL, @@ -6620,7 +6712,9 @@ static const vshCmdOptDef opts_destroy[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "graceful", .type = VSH_OT_BOOL, @@ -6677,7 +6771,10 @@ static const vshCmdOptDef opts_desc[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "live", .type = VSH_OT_BOOL, @@ -6851,7 +6948,9 @@ static const vshCmdOptDef opts_inject_nmi[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -6889,7 +6988,9 @@ static const vshCmdOptDef opts_send_key[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "codeset", .type = VSH_OT_STRING, @@ -6991,7 +7092,9 @@ static const vshCmdOptDef opts_send_process_signal[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "pid", .type = VSH_OT_DATA, @@ -7106,7 +7209,9 @@ static const vshCmdOptDef opts_setmem[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "kilobytes", .type = VSH_OT_ALIAS, @@ -7203,7 +7308,9 @@ static const vshCmdOptDef opts_setmaxmem[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "kilobytes", .type = VSH_OT_ALIAS, @@ -7305,7 +7412,10 @@ static const vshCmdOptDef opts_memtune[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "hard-limit", .type = VSH_OT_INT, @@ -7504,7 +7614,10 @@ static const vshCmdOptDef opts_numatune[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "mode", .type = VSH_OT_DATA, @@ -7651,7 +7764,10 @@ static const vshCmdOptDef opts_qemu_monitor_command[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "hmp", .type = VSH_OT_BOOL, @@ -7801,7 +7917,10 @@ static const vshCmdOptDef opts_qemu_agent_command[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "timeout", .type = VSH_OT_INT, @@ -7930,7 +8049,10 @@ static const vshCmdOptDef opts_lxc_enter_namespace[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "noseclabel", .type = VSH_OT_BOOL, @@ -8068,7 +8190,10 @@ static const vshCmdOptDef opts_dumpxml[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "inactive", .type = VSH_OT_BOOL, @@ -8290,7 +8415,10 @@ static const vshCmdOptDef opts_domid[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name or uuid") + .help = N_("domain name or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -8331,7 +8459,10 @@ static const vshCmdOptDef opts_domuuid[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain id or name") + .help = N_("domain id or name"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -8703,7 +8834,10 @@ static const vshCmdOptDef opts_migrate_setmaxdowntime[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "downtime", .type = VSH_OT_INT, @@ -8757,7 +8891,9 @@ static const vshCmdOptDef opts_migrate_compcache[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "size", .type = VSH_OT_INT, @@ -8819,7 +8955,10 @@ static const vshCmdOptDef opts_migrate_setspeed[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "bandwidth", .type = VSH_OT_INT, @@ -8871,7 +9010,10 @@ static const vshCmdOptDef opts_migrate_getspeed[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -8915,7 +9057,10 @@ static const vshCmdOptDef opts_domdisplay[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "include-password", .type = VSH_OT_BOOL, @@ -9093,7 +9238,9 @@ static const vshCmdOptDef opts_vncdisplay[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -9166,7 +9313,9 @@ static const vshCmdOptDef opts_ttyconsole[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = NULL} }; @@ -9226,7 +9375,10 @@ static const vshCmdOptDef opts_domhostname[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -9389,7 +9541,9 @@ static const vshCmdOptDef opts_detach_device[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -9490,7 +9644,10 @@ static const vshCmdOptDef opts_update_device[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "file", .type = VSH_OT_DATA, @@ -9592,7 +9749,10 @@ static const vshCmdOptDef opts_detach_interface[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "type", .type = VSH_OT_DATA, @@ -9993,7 +10153,10 @@ static const vshCmdOptDef opts_detach_disk[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "target", .type = VSH_OT_DATA, @@ -10103,7 +10266,10 @@ static const vshCmdOptDef opts_edit[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = NULL} }; @@ -10163,7 +10329,10 @@ static const vshCmdOptDef opts_change_media[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "path", .type = VSH_OT_DATA, diff --git a/tools/virsh-snapshot.c b/tools/virsh-snapshot.c index e37a5b3..3f13c7b 100644 --- a/tools/virsh-snapshot.c +++ b/tools/virsh-snapshot.c @@ -125,7 +125,10 @@ static const vshCmdOptDef opts_snapshot_create[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "xmlfile", .type = VSH_OT_DATA, @@ -328,7 +331,10 @@ static const vshCmdOptDef opts_snapshot_create_as[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "name", .type = VSH_OT_DATA, @@ -520,7 +526,10 @@ static const vshCmdOptDef opts_snapshot_edit[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, @@ -642,7 +651,10 @@ static const vshCmdOptDef opts_snapshot_current[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "name", .type = VSH_OT_BOOL, @@ -878,7 +890,10 @@ static const vshCmdOptDef opts_snapshot_info[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, @@ -1435,7 +1450,10 @@ static const vshCmdOptDef opts_snapshot_list[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "parent", .type = VSH_OT_BOOL, @@ -1699,7 +1717,10 @@ static const vshCmdOptDef opts_snapshot_dumpxml[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, @@ -1767,7 +1788,10 @@ static const vshCmdOptDef opts_snapshot_parent[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, @@ -1835,7 +1859,10 @@ static const vshCmdOptDef opts_snapshot_revert[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, @@ -1928,7 +1955,10 @@ static const vshCmdOptDef opts_snapshot_delete[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, - .help = N_("domain name, id or uuid") + .help = N_("domain name, id or uuid"), + .completer = vshDomainCompleter, + .completer_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE | + VIR_CONNECT_LIST_DOMAINS_INACTIVE }, {.name = "snapshotname", .type = VSH_OT_DATA, diff --git a/tools/virsh.c b/tools/virsh.c index 321ed5d..271c841 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -89,6 +89,8 @@ static char *progname; static const vshCmdGrp cmdGroups[]; +static vshControl *vshCtl; + /* Bypass header poison */ #undef strdup @@ -2580,6 +2582,54 @@ vshCloseLogFile(vshControl *ctl) } } +/* ------------- + * Completers + * ------------- + */ + +char ** +vshDomainCompleter(unsigned int completer_flags ATTRIBUTE_UNUSED) +{ +#ifdef USE_READLINE + virDomainPtr *domains; + size_t i; + char **names = NULL; + int ndomains; + + if (!vshCtl->conn) + return NULL; + + ndomains = virConnectListAllDomains(vshCtl->conn, &domains, completer_flags); + + if (ndomains < 0) + return NULL; + + if (VIR_ALLOC_N(names, ndomains + 1) < 0) + return NULL; + + for (i = 0; i < ndomains; i++) { + const char *name = virDomainGetName(domains[i]); + if (VIR_STRDUP(names[i], name) < 0) { + virDomainFree(domains[i]); + goto error; + } + virDomainFree(domains[i]); + } + names[i] = NULL; + VIR_FREE(domains); + return names; + +error: + virStringFreeList(names); + for (i = i+1; i < ndomains; i++) + virDomainFree(domains[i]); + VIR_FREE(domains); + return NULL; +#else + return NULL; +#endif +} + #ifdef USE_READLINE /* ----------------- @@ -2769,6 +2819,10 @@ vshReadlineOptionsGenerator(const char *text, int state) if (!cmd) return NULL; + if ((vshCtl->conn == NULL || disconnected) && + !(cmd->flags & VSH_CMD_FLAG_NOCONNECT)) + vshReconnect(vshCtl); + if (!cmd->opts) return NULL; @@ -3460,6 +3514,7 @@ main(int argc, char **argv) ctl->debug = VSH_DEBUG_DEFAULT; ctl->escapeChar = "^]"; /* Same default as telnet */ + vshCtl = ctl; if (!setlocale(LC_ALL, "")) { perror("setlocale"); diff --git a/tools/virsh.h b/tools/virsh.h index f978d94..845fc17 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -258,6 +258,8 @@ struct _vshCmdGrp { const vshCmdDef *commands; }; +char **vshDomainCompleter(unsigned int flags); + void vshError(vshControl *ctl, const char *format, ...) ATTRIBUTE_FMT_PRINTF(2, 3); void vshOpenLogFile(vshControl *ctl); -- 1.8.3.1

vshSuspendTargetCompleter returns targets available for suspend. This completer can be used for the command option completion (for dompmsuspend, etc.). virsh # dompmsuspend --target <TAB> mem disk hybrid virsh # dompmsuspend --target h<TAB> virsh # dompmsuspend --target hybrid --- v2 * label cleanup renamed to error * vshSuspendTargetCompleter added to opts_dom_pm_suspend v3 * removed useless if * used virStringFreeList() instead of iteration v4 * rewritten using virStringSplit() tools/virsh-domain.c | 3 ++- tools/virsh.c | 6 ++++++ tools/virsh.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 7747446..1406d2e 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -2796,7 +2796,8 @@ static const vshCmdOptDef opts_dom_pm_suspend[] = { .flags = VSH_OFLAG_REQ, .help = N_("mem(Suspend-to-RAM), " "disk(Suspend-to-Disk), " - "hybrid(Hybrid-Suspend)") + "hybrid(Hybrid-Suspend)"), + .completer = vshSuspendTargetCompleter }, {.name = NULL} }; diff --git a/tools/virsh.c b/tools/virsh.c index 271c841..cb89187 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -2630,6 +2630,12 @@ error: #endif } +char ** +vshSuspendTargetCompleter(unsigned int unused_flags ATTRIBUTE_UNUSED) +{ + return virStringSplit("mem disk hybrid", " ", 0); +} + #ifdef USE_READLINE /* ----------------- diff --git a/tools/virsh.h b/tools/virsh.h index 845fc17..c8e3d38 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -259,6 +259,7 @@ struct _vshCmdGrp { }; char **vshDomainCompleter(unsigned int flags); +char **vshSuspendTargetCompleter(unsigned int unused_flags); void vshError(vshControl *ctl, const char *format, ...) ATTRIBUTE_FMT_PRINTF(2, 3); -- 1.8.3.1

vshRebootShutdownModeCompleter returns available shutdown mode names. This can be used for --mode auto completion for commands such as reboot or shutdown. for example: virsh # reboot --mode <TAB> acpi agent initctl signal virsh # reboot --mode i<TAB> virsh # reboot --mode initctl --- v3 * removed useless if * used virStringFreeList() instead of iteration * moved all .completer = vshRebootShutdownModeCompleter initializations into this patch v4 * rewritten using virStringSplit() tools/virsh-domain.c | 6 ++++-- tools/virsh.c | 6 ++++++ tools/virsh.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 1406d2e..69014dd 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -4801,7 +4801,8 @@ static const vshCmdOptDef opts_shutdown[] = { }, {.name = "mode", .type = VSH_OT_STRING, - .help = N_("shutdown mode: acpi|agent|initctl|signal") + .help = N_("shutdown mode: acpi|agent|initctl|signal"), + .completer = vshRebootShutdownModeCompleter }, {.name = NULL} }; @@ -4889,7 +4890,8 @@ static const vshCmdOptDef opts_reboot[] = { }, {.name = "mode", .type = VSH_OT_STRING, - .help = N_("shutdown mode: acpi|agent|initctl|signal") + .help = N_("shutdown mode: acpi|agent|initctl|signal"), + .completer = vshRebootShutdownModeCompleter }, {.name = NULL} }; diff --git a/tools/virsh.c b/tools/virsh.c index cb89187..7e7a0d5 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -2636,6 +2636,12 @@ vshSuspendTargetCompleter(unsigned int unused_flags ATTRIBUTE_UNUSED) return virStringSplit("mem disk hybrid", " ", 0); } +char ** +vshRebootShutdownModeCompleter(unsigned int unused_flags ATTRIBUTE_UNUSED) +{ + return virStringSplit("acpi agent initctl signal", " ", 0); +} + #ifdef USE_READLINE /* ----------------- diff --git a/tools/virsh.h b/tools/virsh.h index c8e3d38..4763db3 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -260,6 +260,7 @@ struct _vshCmdGrp { char **vshDomainCompleter(unsigned int flags); char **vshSuspendTargetCompleter(unsigned int unused_flags); +char **vshRebootShutdownModeCompleter(unsigned int unused_flags); void vshError(vshControl *ctl, const char *format, ...) ATTRIBUTE_FMT_PRINTF(2, 3); -- 1.8.3.1
participants (2)
-
Eric Blake
-
Tomas Meszaros