-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add similar whitespace escape logic to bash v2 completions than in other completions #1743
base: main
Are you sure you want to change the base?
Add similar whitespace escape logic to bash v2 completions than in other completions #1743
Conversation
Thanks @kangasta for the contribution 🎉 |
Ok, I've started looking into this and I started by running some tests and the PR seems to work as expected. Next step was to add a test to the bash v2 testing of cobra-completion-testing: marckhouzam/cobra-completion-testing#22. So now we can see that if a single completion has a space, the space character is not escaped. Running the same test with the PR shows that the space character is escaped. I think I have to add a couple more tests to cover each possible scenario, so I will keep working on it. One issue is that the changes cause a slow down in the completion logic. I'm a little surprised because the changes are isolated to when there is a single completion left, so I'll need to dig into it. More to come... |
bash_completionsV2.go
Outdated
compgen_words=$(printf "%%s\n" "${completions[@]}") | ||
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "$cur") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this cause the performance loss?
This also feels bit clumsy to me, but I was not able to get completions with whitespace to be separate indices of COMPREPLY
without printf
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's almost certainly here. One starting point for debugging would be to just with strategically placed time
s, e.g. time compgen_words=...
.
printf -v "arr[index]" ...
in a loop would avoid a subshell, but the net result could be even slower as this doesn't create subshells in a loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need printf
at all. Wouldn't something like this work?
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "$cur")
IFS=$'\n' compgen ...
sets IFS
for just the compgen
call.
IFS=$'\n'; compgen ...
sets IFS
for both the compgen
call and the "${completions[*]}"
string
And since that change is in a subshell, it won't affect IFS
for the rest of the script
You can see what this does with this simpler example:
$ arr=( 1 2 3 )
$ IFS=$'\n' echo "${arr[*]}"
$ IFS=$'\n'; echo "${arr[*]}"
bash_completionsV2.go
Outdated
@@ -220,7 +220,14 @@ __%[1]s_handle_standard_completion_case() { | |||
|
|||
# Short circuit to optimize if we don't have descriptions | |||
if [[ "${completions[*]}" != *$tab* ]]; then | |||
IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur") | |||
compgen_words=$(printf "%%s\n" "${completions[@]}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compgen_words
should be made local
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
bash_completionsV2.go
Outdated
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "$cur") | ||
|
||
# If there is a single completion left, escape the completion | ||
if [ ${#COMPREPLY[*]} -eq 1 ]; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style, see PR #1702
if [ ${#COMPREPLY[*]} -eq 1 ]; then | |
if ((${#COMPREPLY[*]} == 1)); then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
# If there is a single completion left, escape the completion | ||
if [ ${#COMPREPLY[*]} -eq 1 ]; then | ||
COMPREPLY[0]=$(printf %%q "${COMPREPLY[0]}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can eliminate a subshell here.
COMPREPLY[0]=$(printf %%q "${COMPREPLY[0]}") | |
printf -v "COMPREPLY[0]" %%q "${COMPREPLY[0]}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, actually printf -v
to an array index is a bash 4.1+ thing, so as/if older compatibility is needed, then scratch this and similar comments of mine elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we try to keep it working with bash 3, as much as possible.
if [ ${#COMPREPLY[*]} -eq 1 ]; then | ||
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" | ||
comp="${COMPREPLY[0]%%%%$tab*}" | ||
__%[1]s_debug "Removed description from single completion, which is now: ${comp}" | ||
COMPREPLY[0]=$comp | ||
COMPREPLY[0]=$(printf %%q "${comp}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above
COMPREPLY[0]=$(printf %%q "${comp}") | |
printf -v "COMPREPLY[0]" %%q "$comp" |
ac82162
to
5f27234
Compare
The Cobra project currently lacks enough contributors to adequately respond to all PRs. This bot triages issues and PRs according to the following rules:
|
This ensures completions with whitespace are completed as a single argument. Signed-off-by: Toni Kangas <toni.kangas@upcloud.com>
This ensures that completions with whitespace are treated as single completion. Signed-off-by: Toni Kangas <toni.kangas@upcloud.com>
5f27234
to
3f2dad7
Compare
Rebased this on top of @marckhouzam how big of a performance loss this caused? One option to get this forward could be to split the fix so that optimization for completions without Or is there something I could help with on the testing side? |
I'll try to get back to this PR next week. Thanks for your patience. |
The Cobra project currently lacks enough contributors to adequately respond to all PRs. This bot triages issues and PRs according to the following rules:
|
Adding a comment to keep this from being closed. |
Wow, I completely lost track of this. Sorry @kangasta. I just ran |
Yeah, I have not much ideas on how to optimize this further. There were comments from @scop about unnecessary subshells, but those would require dropping support for older bash versions. We have a hack in |
@@ -233,7 +233,14 @@ __%[1]s_handle_standard_completion_case() { | |||
|
|||
# Short circuit to optimize if we don't have descriptions | |||
if [[ "${completions[*]}" != *$tab* ]]; then | |||
IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur") | |||
local compgen_words=$(printf "%%s\n" "${completions[@]}") | |||
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "$cur") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to support other bash special characters, we need more quoting:
local compgen_words="$(printf "%%q\n" "${completions[@]}")"
cur="$(printf "%%q" "${cur}")"
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "${cur}")
compgen
's -W
honors shell quoting, so a completion like foo\>bar
would become foo>bar
to compgen. We also have to quote ${cur}
because compgen
appears to honor shell quoting after checking to make sure each word begins with ${cur}
Similarly, we need quoting up above: https://github.com/spf13/cobra/blob/main/bash_completionsV2.go#L62-L84
$ args=( echo "foo\>bar" )
$ eval "${args[*]}"
foo>bar
eval
also honors shell quoting, so if we want to pass the actual quoted value through to the __complete
call, we need to quote the args in requestComp
$ args=( echo "foo\>bar" )
$ eval "$(printf "%q " "${args[@]}")"
foo\>bar
…haracters. Special characters include whitepace, so this is more general than spf13#1743. I added some test cases to cobra-completion-testing. This PR makes them pass. It also doesn't trip any of the performance regression tests.
@@ -216,7 +216,7 @@ __%[1]s_handle_completion_types() { | |||
comp=${comp%%%%$tab*} | |||
# Only consider the completions that match | |||
if [[ $comp == "$cur"* ]]; then | |||
COMPREPLY+=("$comp") | |||
COMPREPLY+=( "$(printf %%q "$comp")" ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm reading cobra-completion-testing right, this line is the performance regression. We're creating a subshell to quote each individual completion
We could instead batch all of the completions up into another array, and then quote them all at once kinda like how we're doing for the input to this loop in the first place
…haracters. Special characters include whitepace, so this is more general than spf13#1743. I added some test cases to cobra-completion-testing. This PR makes them pass. It also doesn't trip any of the performance regression tests. I'm happy to submit those tests as a PR as well. - https://github.com/JeffFaer/cobra-completion-testing/tree/special_characters - JeffFaer/cobra-completion-testing@52254c1
…haracters. Special characters include whitepace, so this is more general than spf13#1743. This should also fix spf13#1740. I added some test cases to cobra-completion-testing. This PR makes them pass. It also doesn't trip any of the performance regression tests. I'm happy to submit those tests as a PR as well. - https://github.com/JeffFaer/cobra-completion-testing/tree/special_characters - JeffFaer/cobra-completion-testing@52254c1
This PR would escape bash v2 completions when there are only one completion left and use line-break to separate completions when passing them to
compgen
. This should fix issues described in #1740 and only affect completions with special characters, such as whitespace.