Skip to content
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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kangasta
Copy link

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.

@CLAassistant
Copy link

CLAassistant commented Jun 23, 2022

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions bot added the size/S Denotes a PR that chanes 10-24 lines label Jun 23, 2022
@marckhouzam
Copy link
Collaborator

Thanks @kangasta for the contribution 🎉
I will need to take the time to give this proper attention but I will not have time for the next couple of weeks. I'll get back to this as soon as I can. Thanks for your patience.

@marckhouzam
Copy link
Collaborator

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...

Comment on lines 223 to 237
compgen_words=$(printf "%%s\n" "${completions[@]}")
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n' compgen -W "${compgen_words}" -- "$cur")
Copy link
Author

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.

Copy link
Contributor

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 times, 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.

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[*]}"

@@ -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[@]}")
Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style, see PR #1702

Suggested change
if [ ${#COMPREPLY[*]} -eq 1 ]; then
if ((${#COMPREPLY[*]} == 1)); then

Copy link
Author

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]}")
Copy link
Contributor

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.

Suggested change
COMPREPLY[0]=$(printf %%q "${COMPREPLY[0]}")
printf -v "COMPREPLY[0]" %%q "${COMPREPLY[0]}"

Copy link
Contributor

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.

Copy link
Collaborator

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}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

Suggested change
COMPREPLY[0]=$(printf %%q "${comp}")
printf -v "COMPREPLY[0]" %%q "$comp"

@kangasta kangasta force-pushed the fix/bash-v2-completion-with-whitespace branch from ac82162 to 5f27234 Compare August 25, 2022 14:23
@github-actions
Copy link

The Cobra project currently lacks enough contributors to adequately respond to all PRs. This bot triages issues and PRs according to the following rules:

  • After 60d of inactivity, lifecycle/stale is applied. - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the PR is closed.
    You can:
  • Make a comment to remove the stale label and show your support. The 60 days reset. - If a PR has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interseted in reopening.

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>
@kangasta kangasta force-pushed the fix/bash-v2-completion-with-whitespace branch from 5f27234 to 3f2dad7 Compare October 27, 2022 10:54
@github-actions github-actions bot removed the area/shell-completion All shell completions label Oct 27, 2022
@kangasta
Copy link
Author

Rebased this on top of main

@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 \t, from where the performance loss likely comes, would be addresses in separate PR.

Or is there something I could help with on the testing side?

@marckhouzam
Copy link
Collaborator

I'll try to get back to this PR next week. Thanks for your patience.

@github-actions
Copy link

The Cobra project currently lacks enough contributors to adequately respond to all PRs. This bot triages issues and PRs according to the following rules:

  • After 60d of inactivity, lifecycle/stale is applied. - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the PR is closed.
    You can:
  • Make a comment to remove the stale label and show your support. The 60 days reset. - If a PR has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interested in reopening.

@kangasta
Copy link
Author

Adding a comment to keep this from being closed.

@marckhouzam
Copy link
Collaborator

Wow, I completely lost track of this. Sorry @kangasta.
The slow down is about 3 to 4 times slower.

I just ran make bash in https://github.com/marckhouzam/cobra-completion-testing

@marckhouzam marckhouzam modified the milestones: 1.8.0, 1.9.0 Nov 15, 2023
@kangasta
Copy link
Author

kangasta commented Feb 6, 2024

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 upctl to bypass this issue that replaces spaces with non-breaking spaces (UpCloudLtd/upcloud-cli#204), but it would be nice to get rid of that.

@@ -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")

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

JeffFaer added a commit to JeffFaer/cobra that referenced this pull request Mar 21, 2024
…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")" )

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

JeffFaer added a commit to JeffFaer/cobra that referenced this pull request Mar 21, 2024
…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
JeffFaer added a commit to JeffFaer/cobra that referenced this pull request Mar 21, 2024
…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
@marckhouzam marckhouzam removed this from the 1.8.1 milestone Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size/S Denotes a PR that chanes 10-24 lines
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants