[libvirt] [PATCH 0/2] tools: Introduce virshNodedevCapabilityNameCompleter

Yet again, couple of patches I have on my local branch for the feature I'm working on and which can go separately. Michal Prívozník (2): virsh-completer: Separate comma list construction into a function tools: Introduce virshNodedevCapabilityNameCompleter tools/virsh-completer.c | 147 ++++++++++++++++++++++++++++------------ tools/virsh-completer.h | 4 ++ tools/virsh-nodedev.c | 1 + 3 files changed, 109 insertions(+), 43 deletions(-) -- 2.21.0

There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-) diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */ +/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC + * + * This helper function takes care of that. In this specific case + * it would be called as follows: + * + * virshCommaStringListComplete("str1,str2,str3", + * {"strA", "strB", "strC", NULL}); + * + * Returns: string list of completions on success, + * NULL otherwise. + */ +static char ** +virshCommaStringListComplete(const char *input, + const char **options) +{ + const size_t optionsLen = virStringListLength(options); + VIR_AUTOFREE(char *) inputCopy = NULL; + VIR_AUTOSTRINGLIST inputList = NULL; + VIR_AUTOSTRINGLIST ret = NULL; + size_t nret = 0; + size_t i; + + if (STREQ_NULLABLE(input, " ")) + input = NULL; + + if (input) { + char *comma = NULL; + + if (VIR_STRDUP(inputCopy, input) < 0) + return NULL; + + if ((comma = strrchr(inputCopy, ','))) + *comma = '\0'; + else + VIR_FREE(inputCopy); + } + + if (inputCopy && !(inputList = virStringSplit(inputCopy, ",", 0))) + return NULL; + + if (VIR_ALLOC_N(ret, optionsLen + 1) < 0) + return NULL; + + for (i = 0; i < optionsLen; i++) { + if (virStringListHasString((const char **)inputList, options[i])) + continue; + + if ((inputCopy && virAsprintf(&ret[nret], "%s,%s", inputCopy, options[i]) < 0) || + (!inputCopy && VIR_STRDUP(ret[nret], options[i]) < 0)) + return NULL; + + nret++; + } + + VIR_RETURN_PTR(ret); +} + + char ** virshDomainNameCompleter(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED, @@ -993,52 +1066,13 @@ virshDomainShutdownModeCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags) { - const char *modes[] = {"acpi", "agent", "initctl", "signal", "paravirt"}; - size_t i; - char **ret = NULL; - size_t ntmp = 0; - VIR_AUTOSTRINGLIST tmp = NULL; - const char *modeConst = NULL; - VIR_AUTOFREE(char *) mode = NULL; - VIR_AUTOSTRINGLIST modesSpecified = NULL; + const char *modes[] = {"acpi", "agent", "initctl", "signal", "paravirt", NULL}; + const char *mode = NULL; virCheckFlags(0, NULL); - if (vshCommandOptStringQuiet(ctl, cmd, "mode", &modeConst) < 0) + if (vshCommandOptStringQuiet(ctl, cmd, "mode", &mode) < 0) return NULL; - if (STREQ_NULLABLE(modeConst, " ")) - modeConst = NULL; - - if (modeConst) { - char *modeTmp = NULL; - - if (VIR_STRDUP(mode, modeConst) < 0) - return NULL; - - if ((modeTmp = strrchr(mode, ','))) - *modeTmp = '\0'; - else - VIR_FREE(mode); - } - - if (mode && !(modesSpecified = virStringSplit(mode, ",", 0))) - return NULL; - - if (VIR_ALLOC_N(tmp, ARRAY_CARDINALITY(modes) + 1) < 0) - return NULL; - - for (i = 0; i < ARRAY_CARDINALITY(modes); i++) { - if (virStringListHasString((const char **)modesSpecified, modes[i])) - continue; - - if ((mode && virAsprintf(&tmp[ntmp], "%s,%s", mode, modes[i]) < 0) || - (!mode && VIR_STRDUP(tmp[ntmp], modes[i]) < 0)) - return NULL; - - ntmp++; - } - - VIR_STEAL_PTR(ret, tmp); - return ret; + return virshCommaStringListComplete(mode, modes); } -- 2.21.0

On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended. Erik
+ * + * This helper function takes care of that. In this specific case + * it would be called as follows: + * + * virshCommaStringListComplete("str1,str2,str3", + * {"strA", "strB", "strC", NULL}); + * + * Returns: string list of completions on success, + * NULL otherwise. + */ +static char ** +virshCommaStringListComplete(const char *input, + const char **options) +{ + const size_t optionsLen = virStringListLength(options); + VIR_AUTOFREE(char *) inputCopy = NULL; + VIR_AUTOSTRINGLIST inputList = NULL; + VIR_AUTOSTRINGLIST ret = NULL; + size_t nret = 0; + size_t i; + + if (STREQ_NULLABLE(input, " ")) + input = NULL; + + if (input) { + char *comma = NULL; + + if (VIR_STRDUP(inputCopy, input) < 0) + return NULL; + + if ((comma = strrchr(inputCopy, ','))) + *comma = '\0'; + else + VIR_FREE(inputCopy); + } + + if (inputCopy && !(inputList = virStringSplit(inputCopy, ",", 0))) + return NULL; + + if (VIR_ALLOC_N(ret, optionsLen + 1) < 0) + return NULL; + + for (i = 0; i < optionsLen; i++) { + if (virStringListHasString((const char **)inputList, options[i])) + continue; + + if ((inputCopy && virAsprintf(&ret[nret], "%s,%s", inputCopy, options[i]) < 0) || + (!inputCopy && VIR_STRDUP(ret[nret], options[i]) < 0)) + return NULL; + + nret++; + } + + VIR_RETURN_PTR(ret); +} + + char ** virshDomainNameCompleter(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED, @@ -993,52 +1066,13 @@ virshDomainShutdownModeCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags) { - const char *modes[] = {"acpi", "agent", "initctl", "signal", "paravirt"}; - size_t i; - char **ret = NULL; - size_t ntmp = 0; - VIR_AUTOSTRINGLIST tmp = NULL; - const char *modeConst = NULL; - VIR_AUTOFREE(char *) mode = NULL; - VIR_AUTOSTRINGLIST modesSpecified = NULL; + const char *modes[] = {"acpi", "agent", "initctl", "signal", "paravirt", NULL}; + const char *mode = NULL;
virCheckFlags(0, NULL);
- if (vshCommandOptStringQuiet(ctl, cmd, "mode", &modeConst) < 0) + if (vshCommandOptStringQuiet(ctl, cmd, "mode", &mode) < 0) return NULL;
- if (STREQ_NULLABLE(modeConst, " ")) - modeConst = NULL; - - if (modeConst) { - char *modeTmp = NULL; - - if (VIR_STRDUP(mode, modeConst) < 0) - return NULL; - - if ((modeTmp = strrchr(mode, ','))) - *modeTmp = '\0'; - else - VIR_FREE(mode); - } - - if (mode && !(modesSpecified = virStringSplit(mode, ",", 0))) - return NULL; - - if (VIR_ALLOC_N(tmp, ARRAY_CARDINALITY(modes) + 1) < 0) - return NULL; - - for (i = 0; i < ARRAY_CARDINALITY(modes); i++) { - if (virStringListHasString((const char **)modesSpecified, modes[i])) - continue; - - if ((mode && virAsprintf(&tmp[ntmp], "%s,%s", mode, modes[i]) < 0) || - (!mode && VIR_STRDUP(tmp[ntmp], modes[i]) < 0)) - return NULL; - - ntmp++; - } - - VIR_STEAL_PTR(ret, tmp); - return ret; + return virshCommaStringListComplete(mode, modes); } -- 2.21.0
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 6/19/19 12:59 PM, Erik Skultety wrote:
On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended.
So IIUC, for 'shutdown --mode <TAB><TAB>' you want to see: "acpi", "agent", "initctl", "signal", "paravirt" and for 'shutdown --mode acpi,agent,<TAB><TAB>' you want to see the same list again (optionally with already specified strings removed)? Yep, that would be great but I don't think that is how readline works. At least, I don't know how to achieve that. Do you perhaps have an idea? Michal

On Wed, Jun 19, 2019 at 01:50:14PM +0200, Michal Privoznik wrote:
On 6/19/19 12:59 PM, Erik Skultety wrote:
On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended.
So IIUC, for 'shutdown --mode <TAB><TAB>' you want to see:
"acpi", "agent", "initctl", "signal", "paravirt"
and for 'shutdown --mode acpi,agent,<TAB><TAB>' you want to see the same list again (optionally with already specified strings removed)? Yep, that would be great but I don't think that is how readline works. At least, I don't know how to achieve that. Do you perhaps have an idea?
It very well may be the case that it doesn't work the way we'd like to and I don't understand how it actually works, but why does readline even matter here? Readline calls our completers which generate the output presented to the user, so we should be in charge what is returned, so why do we need to prepend the user input then? In fact, I found there's a function called vshCompleterFilter which removes the whole output list if the items are not prepended with the original user input, why is that? When I commented out the bit dropping items from the list and stopped pre-pending the user input, I achieved what I suggested in my original response to this series, a context-based list without unnecessary prefixes. I also tried a few other random completions to see whether I didn't break something by stripping some code from vshCompleterFilter and it looks like it worked, so the question is, what was the reason for that function in the first place, since I haven't experienced the effects described by commit d4e63aff5d0 which introduced it? Thanks, Erik

On 7/15/19 1:07 PM, Erik Skultety wrote:
On Wed, Jun 19, 2019 at 01:50:14PM +0200, Michal Privoznik wrote:
On 6/19/19 12:59 PM, Erik Skultety wrote:
On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended.
So IIUC, for 'shutdown --mode <TAB><TAB>' you want to see:
"acpi", "agent", "initctl", "signal", "paravirt"
and for 'shutdown --mode acpi,agent,<TAB><TAB>' you want to see the same list again (optionally with already specified strings removed)? Yep, that would be great but I don't think that is how readline works. At least, I don't know how to achieve that. Do you perhaps have an idea?
It very well may be the case that it doesn't work the way we'd like to and I don't understand how it actually works, but why does readline even matter here? Readline calls our completers which generate the output presented to the user, so we should be in charge what is returned, so why do we need to prepend the user input then? In fact, I found there's a function called vshCompleterFilter which removes the whole output list if the items are not prepended with the original user input, why is that? When I commented out the bit dropping items from the list and stopped pre-pending the user input, I achieved what I suggested in my original response to this series, a context-based list without unnecessary prefixes.
This very likely did not work and only gave impression it is working. I've just tried what you suggest here and find it not working. The reason is that if we return only one option to complete it replaces the whole argument with that string. Or, if we return more strings then the argument is replaced with their longest shared prefix. For instance, if our completer would return only {"testA", "testB", NULL}, then the following input: virsh # start t<TAB><TAB> would be overwritten to: virsh # start test testA testB This is expected and in fact desired. But things get tricky when we start dealing with out argument lists: virsh # shutdown --mode <TAB><TAB> gets you: virsh # shutdown --mode a acpi agent So far so good. But then you introduce comma: virsh # shutdown --mode agent,a<TAB><TAB> Now, there is only one possible completion = "acpi". So readline saves you some typing and turns that into: virsh # shutdown --mode acpi Problem is that readline does not handle comma as a separator. Okay, we can fix that. It's easy to put comma at the end of @break_characters in vshReadlineInit(). But that breaks our option lookup because then @text == "a" in vshReadlineParse(). On one hand we want @text == "a" because that means that readline split user's input at the comma, on the other hand we can't now properly identify which --option is user trying to autocomplete because neither --option has "a" as its value (--mode has "agent,a").
I also tried a few other random completions to see whether I didn't break something by stripping some code from vshCompleterFilter and it looks like it worked, so the question is, what was the reason for that function in the first place, since I haven't experienced the effects described by commit d4e63aff5d0 which introduced it?
The reason for existence of vshCompleterFilter() is to filter out non-relevant options. For instance, in aforementioned shutdown mode completer - we want, I want completers to be as simple as possible. Therefore, the shutdown mode completer returns all five strings, regardless of user's input. Then the filter function prunes out those strings (=options) which do not share prefix with user's input. For instance, if user's input is "a" then "initctl", "signal" and "paravirt" will be filtered out. If they weren't, and they would be returned back to readline, it would present them to the user for complete and it would look like this: virsh # shutdown --mode a<TAB><TAB> acpi agent initctl paravirt signal And I believe we can both agree that this is bad behaviour. Maybe solution is to not call rl_completion_matches() in vshReadlineCompletion() and utilize @start and @end arguments which point at the beginning and end of the word that user is trying to complete (although how would we use them to find the corresponding --option is something I do not know). But that's something I haven't tried. Michal

On Mon, Jul 15, 2019 at 04:27:04PM +0200, Michal Privoznik wrote:
On 7/15/19 1:07 PM, Erik Skultety wrote:
On Wed, Jun 19, 2019 at 01:50:14PM +0200, Michal Privoznik wrote:
On 6/19/19 12:59 PM, Erik Skultety wrote:
On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended.
So IIUC, for 'shutdown --mode <TAB><TAB>' you want to see:
"acpi", "agent", "initctl", "signal", "paravirt"
and for 'shutdown --mode acpi,agent,<TAB><TAB>' you want to see the same list again (optionally with already specified strings removed)? Yep, that would be great but I don't think that is how readline works. At least, I don't know how to achieve that. Do you perhaps have an idea?
It very well may be the case that it doesn't work the way we'd like to and I don't understand how it actually works, but why does readline even matter here? Readline calls our completers which generate the output presented to the user, so we should be in charge what is returned, so why do we need to prepend the user input then? In fact, I found there's a function called vshCompleterFilter which removes the whole output list if the items are not prepended with the original user input, why is that? When I commented out the bit dropping items from the list and stopped pre-pending the user input, I achieved what I suggested in my original response to this series, a context-based list without unnecessary prefixes.
This very likely did not work and only gave impression it is working. I've just tried what you suggest here and find it not working. The reason is that if we return only one option to complete it replaces the whole argument with that string. Or, if we return more strings then the argument is replaced with their longest shared prefix. For instance, if our completer would return only {"testA", "testB", NULL}, then the following input:
virsh # start t<TAB><TAB>
would be overwritten to:
virsh # start test testA testB
This is expected and in fact desired. But things get tricky when we start dealing with out argument lists:
virsh # shutdown --mode <TAB><TAB>
gets you:
virsh # shutdown --mode a acpi agent
So far so good. But then you introduce comma:
virsh # shutdown --mode agent,a<TAB><TAB>
Now, there is only one possible completion = "acpi". So readline saves you some typing and turns that into:
virsh # shutdown --mode acpi
Problem is that readline does not handle comma as a separator. Okay, we can fix that. It's easy to put comma at the end of @break_characters in vshReadlineInit(). But that breaks our option lookup because then @text == "a" in vshReadlineParse(). On one hand we want @text == "a" because that means that readline split user's input at the comma, on the other hand we can't now properly identify which --option is user trying to autocomplete because neither --option has "a" as its value (--mode has "agent,a").
I also tried a few other random completions to see whether I didn't break something by stripping some code from vshCompleterFilter and it looks like it worked, so the question is, what was the reason for that function in the first place, since I haven't experienced the effects described by commit d4e63aff5d0 which introduced it?
The reason for existence of vshCompleterFilter() is to filter out non-relevant options. For instance, in aforementioned shutdown mode completer - we want, I want completers to be as simple as possible. Therefore, the shutdown mode completer returns all five strings, regardless of user's input. Then the filter function prunes out those strings (=options) which do not share prefix with user's input. For instance, if user's input is "a" then "initctl", "signal" and "paravirt" will be filtered out. If they weren't, and they would be returned back to readline, it would present them to the user for complete and it would look like this:
virsh # shutdown --mode a<TAB><TAB> acpi agent initctl paravirt signal
And I believe we can both agree that this is bad behaviour.
Maybe solution is to not call rl_completion_matches() in vshReadlineCompletion() and utilize @start and @end arguments which point at the beginning and end of the word that user is trying to complete (although how would we use them to find the corresponding --option is something I do not know). But that's something I haven't tried.
So I understand what Erik wants to have. And yes, it would be a very nice from the UX POV, but AFAIK it is nearly impossible to do with readline. [OK, I'll stop with the acronyms now.] I think we all agree that rewriting readline does not make sense, and I think we might be on the same page (or at least getting there) regarding to working around the limitations in readline completion functions. Let me fill up some info here that Michal maybe thought is common knowledge or maybe forgot to mention it here, just so we (hopefully) get on the same page. What I use, for example, is completing various things not only based on the prefix, let me give you an actual example: Let's say you are in a directory with files like my_first_file_hash_cb6a4f64efbc.txt my_second_file_hash_1b162a344123.txt my_second_file_hash_cacbf987dcb.json And you want to print second file ending in txt. What you can do is write: cat second.txt<TAB> And then you get the only completion that has the characters "second.txt" somewhere in the filename in this order, but not necessarily right next to each other, which is what you wanted. This is called a fuzzy completion and it is an absolutely amazing thing to speed up your workflow when you start using it. Back to the thing we're talking about here and now. If the readline function just accepted the list of strings that can be appended to the current command line, then you would not be able to do this. And it's not only needed for this fuzzy completion, it might be used to do other tricks as well. So basically the readline API, as far as I understand, takes the list of possible completions that the current string would be transformed to, not appended. There is a possibility of telling readline to break words on comma as well, but then you wouldn't know whether you are completing an extra value for the current parameter or another parameter. There can be ways around it, but that gets me to the main two points in this reply: 1) I do not think it is worth it. 2) Even if someone wanted to do that, I don't think the discussion should be happening in a reply to a patch that just moves some existing code around. Hope that helps with reaching the rough consensus on this series. Have a nice day, Martin
Michal
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On Thu, Jul 18, 2019 at 11:36:27AM +0200, Martin Kletzander wrote:
On Mon, Jul 15, 2019 at 04:27:04PM +0200, Michal Privoznik wrote:
On 7/15/19 1:07 PM, Erik Skultety wrote:
On Wed, Jun 19, 2019 at 01:50:14PM +0200, Michal Privoznik wrote:
On 6/19/19 12:59 PM, Erik Skultety wrote:
On Tue, Jun 18, 2019 at 03:46:15PM +0200, Michal Privoznik wrote:
There are more arguments than 'shutdown --mode' that accept a list of strings separated by commas. 'nodedev-list --cap' is one of them. To avoid duplicating code, let's separate interesting bits of virshDomainShutdownModeCompleter() into a function that can then be reused.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 120 ++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-)
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 7d5cf8cb90..ef2f39320e 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -69,6 +69,79 @@ */
+/** + * virshCommaStringListComplete: + * @input: user input so far + * @options: ALL options available for argument + * + * Some arguments to our commands accept the following form: + * + * virsh command --arg str1,str2,str3 + * + * This does not play nicely with our completer funtions, because + * they have to return strings prepended with user's input. For + * instance: + * + * str1,str2,str3,strA + * str1,str2,str3,strB + * str1,str2,str3,strC
^This sounds rather sub-optimal. I wouldn't even insist on making the suggestions contextual like it is now, IOW not suggesting options which have already been specified and would rather return the same list of possible options than a string with the user input prepended.
So IIUC, for 'shutdown --mode <TAB><TAB>' you want to see:
"acpi", "agent", "initctl", "signal", "paravirt"
and for 'shutdown --mode acpi,agent,<TAB><TAB>' you want to see the same list again (optionally with already specified strings removed)? Yep, that would be great but I don't think that is how readline works. At least, I don't know how to achieve that. Do you perhaps have an idea?
It very well may be the case that it doesn't work the way we'd like to and I don't understand how it actually works, but why does readline even matter here? Readline calls our completers which generate the output presented to the user, so we should be in charge what is returned, so why do we need to prepend the user input then? In fact, I found there's a function called vshCompleterFilter which removes the whole output list if the items are not prepended with the original user input, why is that? When I commented out the bit dropping items from the list and stopped pre-pending the user input, I achieved what I suggested in my original response to this series, a context-based list without unnecessary prefixes.
This very likely did not work and only gave impression it is working. I've just tried what you suggest here and find it not working. The reason is that if we return only one option to complete it replaces the whole argument with that string. Or, if we return more strings then the argument is replaced with their longest shared prefix. For instance, if our completer would return only {"testA", "testB", NULL}, then the following input:
virsh # start t<TAB><TAB>
would be overwritten to:
virsh # start test testA testB
This is expected and in fact desired. But things get tricky when we start dealing with out argument lists:
virsh # shutdown --mode <TAB><TAB>
gets you:
virsh # shutdown --mode a acpi agent
So far so good. But then you introduce comma:
virsh # shutdown --mode agent,a<TAB><TAB>
Now, there is only one possible completion = "acpi". So readline saves you some typing and turns that into:
virsh # shutdown --mode acpi
Problem is that readline does not handle comma as a separator. Okay, we can fix that. It's easy to put comma at the end of @break_characters in vshReadlineInit(). But that breaks our option lookup because then @text == "a" in vshReadlineParse(). On one hand we want @text == "a" because that means that readline split user's input at the comma, on the other hand we can't now properly identify which --option is user trying to autocomplete because neither --option has "a" as its value (--mode has "agent,a").
I also tried a few other random completions to see whether I didn't break something by stripping some code from vshCompleterFilter and it looks like it worked, so the question is, what was the reason for that function in the first place, since I haven't experienced the effects described by commit d4e63aff5d0 which introduced it?
The reason for existence of vshCompleterFilter() is to filter out non-relevant options. For instance, in aforementioned shutdown mode completer - we want, I want completers to be as simple as possible. Therefore, the shutdown mode completer returns all five strings, regardless of user's input. Then the filter function prunes out those strings (=options) which do not share prefix with user's input. For instance, if user's input is "a" then "initctl", "signal" and "paravirt" will be filtered out. If they weren't, and they would be returned back to readline, it would present them to the user for complete and it would look like this:
virsh # shutdown --mode a<TAB><TAB> acpi agent initctl paravirt signal
And I believe we can both agree that this is bad behaviour.
Maybe solution is to not call rl_completion_matches() in vshReadlineCompletion() and utilize @start and @end arguments which point at the beginning and end of the word that user is trying to complete (although how would we use them to find the corresponding --option is something I do not know). But that's something I haven't tried.
So I understand what Erik wants to have. And yes, it would be a very nice from the UX POV, but AFAIK it is nearly impossible to do with readline.
[OK, I'll stop with the acronyms now.]
I think we all agree that rewriting readline does not make sense, and I think we might be on the same page (or at least getting there) regarding to working around the limitations in readline completion functions. Let me fill up some info here that Michal maybe thought is common knowledge or maybe forgot to mention it here, just so we (hopefully) get on the same page.
What I use, for example, is completing various things not only based on the prefix, let me give you an actual example:
Let's say you are in a directory with files like
my_first_file_hash_cb6a4f64efbc.txt my_second_file_hash_1b162a344123.txt my_second_file_hash_cacbf987dcb.json
And you want to print second file ending in txt. What you can do is write:
cat second.txt<TAB>
And then you get the only completion that has the characters "second.txt" somewhere in the filename in this order, but not necessarily right next to each other, which is what you wanted. This is called a fuzzy completion and it is an absolutely amazing thing to speed up your workflow when you start using it.
Back to the thing we're talking about here and now. If the readline function just accepted the list of strings that can be appended to the current command line, then you would not be able to do this. And it's not only needed for this fuzzy completion, it might be used to do other tricks as well.
So basically the readline API, as far as I understand, takes the list of possible completions that the current string would be transformed to, not appended. There is a possibility of telling readline to break words on comma as well, but then you wouldn't know whether you are completing an extra value for the current parameter or another parameter. There can be ways around it, but that gets me to the main two points in this reply:
1) I do not think it is worth it.
2) Even if someone wanted to do that, I don't think the discussion should be happening in a reply to a patch that just moves some existing code around.
This was a fair enough comment. Okay then, consider this: Reviewed-by: Erik Skultety <eskultet@redhat.com>

This is a very simple completer for completing --cap argument of nodedev-list command. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-completer.c | 27 +++++++++++++++++++++++++++ tools/virsh-completer.h | 4 ++++ tools/virsh-nodedev.c | 1 + 3 files changed, 32 insertions(+) diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index ef2f39320e..37f946d4b6 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -34,6 +34,7 @@ #include "virmacaddr.h" #include "virstring.h" #include "virxml.h" +#include "conf/node_device_conf.h" /** @@ -975,6 +976,32 @@ virshNodedevEventNameCompleter(vshControl *ctl ATTRIBUTE_UNUSED, } +char ** +virshNodedevCapabilityNameCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags) +{ + VIR_AUTOSTRINGLIST tmp = NULL; + const char *cap_str = NULL; + size_t i = 0; + + virCheckFlags(0, NULL); + + if (vshCommandOptStringQuiet(ctl, cmd, "cap", &cap_str) < 0) + return NULL; + + if (VIR_ALLOC_N(tmp, VIR_NODE_DEV_CAP_LAST + 1) < 0) + return NULL; + + for (i = 0; i < VIR_NODE_DEV_CAP_LAST; i++) { + if (VIR_STRDUP(tmp[i], virNodeDevCapTypeToString(i)) < 0) + return NULL; + } + + return virshCommaStringListComplete(cap_str, (const char **)tmp); +} + + char ** virshCellnoCompleter(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED, diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h index cd3cc9ecae..d6da6e69f3 100644 --- a/tools/virsh-completer.h +++ b/tools/virsh-completer.h @@ -107,6 +107,10 @@ char ** virshNodedevEventNameCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); +char ** virshNodedevCapabilityNameCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags); + char ** virshDomainDeviceAliasCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c index 0dfefe3a2f..e8372a4daf 100644 --- a/tools/virsh-nodedev.c +++ b/tools/virsh-nodedev.c @@ -375,6 +375,7 @@ static const vshCmdOptDef opts_node_list_devices[] = { }, {.name = "cap", .type = VSH_OT_STRING, + .completer = virshNodedevCapabilityNameCompleter, .help = N_("capability names, separated by comma") }, {.name = NULL} -- 2.21.0

On Tue, Jun 18, 2019 at 03:46:16PM +0200, Michal Privoznik wrote:
This is a very simple completer for completing --cap argument of nodedev-list command.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- Reviewed-by: Erik Skultety <eskultet@redhat.com>

On Tue, Jun 18, 2019 at 03:46:14PM +0200, Michal Privoznik wrote:
Yet again, couple of patches I have on my local branch for the feature I'm working on and which can go separately.
Michal Prívozník (2): virsh-completer: Separate comma list construction into a function tools: Introduce virshNodedevCapabilityNameCompleter
tools/virsh-completer.c | 147 ++++++++++++++++++++++++++++------------ tools/virsh-completer.h | 4 ++ tools/virsh-nodedev.c | 1 + 3 files changed, 109 insertions(+), 43 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (4)
-
Erik Skultety
-
Ján Tomko
-
Martin Kletzander
-
Michal Privoznik