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

fix(bash): Fix bash completion for suggestions that contain special characters. #2126

Closed
wants to merge 8 commits into from

Conversation

JeffFaer
Copy link
Contributor

Special characters include whitepace, so this is more general than
#1743. This should also fix #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.

@CLAassistant
Copy link

CLAassistant commented Mar 21, 2024

CLA assistant check
All committers have signed the CLA.

@ncw
Copy link
Contributor

ncw commented Sep 6, 2024

It would be nice to have this PR merged - many rclone users have noticed that we can't complete file names with spaces in :-(

@marckhouzam
Copy link
Collaborator

Sorry for the delay. I will be reviewing this next.

@marckhouzam
Copy link
Collaborator

@JeffFaer

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

That would be great!

@JeffFaer
Copy link
Contributor Author

@marckhouzam
Copy link
Collaborator

I think there may be some confusion here.
When doing completion for special characters, the characters should be automatically escaped.
So if the completion choice is a>b, doing prog [tab][tab] should yield prog a\>b.
You can rebase #1743 and try it out as it behaves like this.
You can also try doing such completion with zsh and see how it should behave.

…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

Signed-off-by: Jeffrey Faer <[email protected]>
They're not going through compgen so they don't need it.

Signed-off-by: Jeffrey Faer <[email protected]>
Signed-off-by: Jeffrey Faer <[email protected]>
@JeffFaer JeffFaer force-pushed the quote_bash_completions branch from ec43e79 to 9771cb0 Compare December 5, 2024 04:39
@JeffFaer
Copy link
Contributor Author

JeffFaer commented Dec 5, 2024

I've updated these changes to reflect the behavior you're looking for. It automatically escapes the last completion suggestion.

@JeffFaer
Copy link
Contributor Author

JeffFaer commented Dec 5, 2024

Hmm there's actually a bad interaction here with COMP_WORDBREAKS. The default COMP_WORDBREAKS seems to include many special characters by default.

The bash completion script is handling a couple wordbreaks (= and :) "correctly" by removing everything up to and including the wordbreak from proposed COMPREPLYs so that readline doesn't duplicate the same prefix again. That's problematic for special characters that need to be escaped like > because that means the completion script won't be able to escape them anymore...

Signed-off-by: Jeffrey Faer <[email protected]>
@JeffFaer
Copy link
Contributor Author

JeffFaer commented Dec 5, 2024

I took a pass at a way to address the COMP_WORDBREAKS interaction (tl;dr: remove all wordbreaks that would need to be escaped)

@@ -173,8 +169,9 @@ __%[1]s_process_completion_results() {
__%[1]s_handle_completion_types
fi

__%[1]s_handle_special_char "$cur" :
__%[1]s_handle_special_char "$cur" =
__%[1]s_handle_wordbreaks "$cur"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

At surface value, this line looked wrong to me because I didn't think it was correct to handle wordbreaks like this when the user requested menu-completion. But after some manual testing I'm pleasantly surprised to find that this is correct, and menu-completion does still expect the same behavior as "regular" completion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't understand why this change was needed so for safety, I did not take it.

Signed-off-by: Jeffrey Faer <[email protected]>
The other ones should already have been quoted by the completion script.

Signed-off-by: Jeffrey Faer <[email protected]>
@JeffFaer JeffFaer force-pushed the quote_bash_completions branch from 398ef9f to 7a9725b Compare December 6, 2024 05:03
@marckhouzam
Copy link
Collaborator

@JeffFaer Thanks for the update! It looks really good. However, while testing with the updated program of marckhouzam/cobra-completion-testing#25 I'm noticing a subtle behaviour, that I feel is incorrect.

bash-5.2$ ./testprog prefix special-chars bash4>[tab]
bash-5.2$ ./testprog prefix special-chars bash4\>redirect

In the above case, when the user presses [tab] after the redirect symbol > they should get some file completion and not the > being escaped. In the above case, since the user did not escape the > themselves, it means they want to use it as a redirect command, so Cobra should not escape it.

If the user where to escape the > themselves, that is when Cobra should continue the shell completion:

bash-5.2$ ./testprog prefix special-chars bash4\>[tab]
# Below is what I would expect, but this is not how the PR behaves
bash-5.2$ ./testprog prefix special-chars bash4\>redirect

Comparing it to zsh completion helps see the expected behaviour.
So, to summarize, this is what we need:

bash-5.2$ ./testprog prefix special-chars bash4[tab]
bash-5.2$ ./testprog prefix special-chars bash4\>redirect

bash-5.2$ ./testprog prefix special-chars bash4\>[tab]
bash-5.2$ ./testprog prefix special-chars bash4\>redirect

bash-5.2$ ./testprog prefix special-chars bash4>[tab]
# Normal file completion should happen here

The same approach should apply to the other special characters: , |, $, ;.
For #, zsh requires it to be escaped to continue completion (bash5\#<TAB>), but I'm not sure why, but let's do the same.

…ld print them to the command line (COMP_TYPE=9)
@JeffFaer
Copy link
Contributor Author

It sounds like you're looking to have special characters escaped as soon as they end up on the command line instead of after the full word has been completed. I've updated the PR to do that. PTAL. Escaping special characters as soon as they end up on the command line lets us simplify some of the wordbreak complexity, too


63)
# Type: Listing completions after successive tabs
__%[1]s_handle_standard_completion_case
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not escaping here so that the list of results on multiple tabs doesn't include any extra escape characters...is that the behavior you're expecting?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you give an example of the behaviour you are referring too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

testprog prefix special-chars bash<tab><tab>

The list of options it reports is not escaped (i.e. bash4>redirect), but if you were to do bash4<tab> you'd get the escaped version (bash4\>redirect)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should escape it in the list. I will do that in #1743

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I notice that zsh and fish don't show the escaping in their list. I'll think about it...

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with you that we should not escape the completions when printing them as a menu

@marckhouzam
Copy link
Collaborator

marckhouzam commented Jan 18, 2025

@JeffFaer I worked many hours trying to make sure this was safe to merge, and in the end I felt there were too many changes I didn't understand well enough so I tried to simplify the change as much as I could. By the time I was done and had something working, it looked a lot like #1743. So, I feel the fair thing to do is to go back to #1743 and make it work.

I was able to do sort all this out thanks to the great tests you wrote in marckhouzam/cobra-completion-testing#25, so I want to merge that one also.

I'm going to pause working on this one and see if we can get #1743 working.
Thanks for your continued efforts.

@@ -224,20 +225,28 @@ __%[1]s_handle_completion_types() {
# completions at once on the command-line we must remove the descriptions.
# https://github.com/spf13/cobra/issues/1508
local tab=$'\t' comp
while IFS='' read -r comp; do
for comp in "${completions[@]}"; do
[[ -z $comp ]] && continue
# Strip any description
comp=${comp%%%%$tab*}
# Only consider the completions that match
if [[ $comp == "$cur"* ]]; then
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to do the escaping before we filter on "$cur" so that when the user typed bash1\ s it still matches the (escaped) completion

@@ -271,21 +286,49 @@ __%[1]s_handle_standard_completion_case() {
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the loop above, we need to do the escaping before we filter on "$cur" so that when the user typed bash1\ s it still matches the (escaped) completion


63)
# Type: Listing completions after successive tabs
__%[1]s_handle_standard_completion_case
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with you that we should not escape the completions when printing them as a menu

local compgen_words="$(__%[1]s_escape_suggestions "${completions[@]}")"
if [[ "${BASH_VERSION}" = 3* ]]; then
# bash3 compgen doesn't handle escaped # symbols correctly.
compgen_words="${compgen_words//\\#/#}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the end I decided not to escape the # since the shell passes to the go program without problems.

marckhouzam added a commit to kangasta/cobra that referenced this pull request Jan 21, 2025
Also avoid using 'printf -v' by using "read" patterns, inspired by
PR spf13#2126 from of "Jeffrey Faer".

Signed-off-by: Marc Khouzam <[email protected]>
Co-authored-by: Jeffrey Faer <[email protected]>
@@ -224,20 +225,28 @@ __%[1]s_handle_completion_types() {
# completions at once on the command-line we must remove the descriptions.
# https://github.com/spf13/cobra/issues/1508
local tab=$'\t' comp
while IFS='' read -r comp; do
for comp in "${completions[@]}"; do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure why this was needed so I didn't take it.

__%[1]s_escape_compreply
;;

63)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This was elegant. However, because it was still required to escape characters for COMP_TYPE==9 so that we could filter completion on what the user typed, I went with a different approach.

To avoid showing escaped characters in the menu list, I didn't escape the completions when there were more than one. We want to insert in the command-line the escape characters, and since we only insert in the command-line when there is a single completion (except for COMP_TYPE 37 or 42), that seemed like a good way to know when to escape or not.

;;

*)
# Type: complete (normal completion)
__%[1]s_handle_standard_completion_case
__%[1]s_escape_compreply
Copy link
Collaborator

Choose a reason for hiding this comment

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

I liked the elegance of trying to escape the characters at the end. But it turned out we also needed to escape them from within __%[1]s_handle_standard_completion_case, so I decided to do everything from within that function.

# bash3 compgen doesn't handle escaped # symbols correctly.
compgen_words="${compgen_words//\\#/#}"
fi
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${compgen_words}" -- "${cur}")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I ended-up using the logic you wrote here (except for the BASH 3 stuff).
I gave you credit in the commit and put you as a co-author.

@marckhouzam
Copy link
Collaborator

@JeffFaer Thanks again for your hard work. This was very tricky. The version I plan to commit is now posted in #1743 but I made you a co-author because I was able to get things working thanks to different part of your work as well.

If you can give a spin to #1743 and see if you are happy with it, it would help me make sure I didn't forget something you might have noticed during your hard work. Thanks!

I will close this PR in favor of #1743

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Shell completions with whitespace seem to work differently on bash vs. other supported shells
4 participants