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

Autocompletion in ZSH #168

Closed
DannyBen opened this issue Jan 22, 2022 · 14 comments · Fixed by #169
Closed

Autocompletion in ZSH #168

DannyBen opened this issue Jan 22, 2022 · 14 comments · Fixed by #169
Assignees
Labels
bug Something isn't working

Comments

@DannyBen
Copy link
Owner

DannyBen commented Jan 22, 2022

BTW, in zsh, completions doesn't work for flags. In bash, works fine. Since it is primarily targeted to bash, I am not sure if it is a bug or if an issue is needed.

Originally posted by @insign in #165 (comment)

@DannyBen
Copy link
Owner Author

DannyBen commented Jan 22, 2022

@insign - autocompletion should wok in zsh in a similar way. Zsh needs to be configured to know how to handle bash-style completions.

If you are using Oh-My-Zsh, I believe it should work out of the box.
If not, you need to ensure that your ~/.zshrc contains these lines:

# Load bash completion functions
autoload -Uz +X compinit && compinit
autoload -Uz +X bashcompinit && bashcompinit

Finally - I suggest the following test case, to try and find out if the problem is something in your bashly configuration, or in the code that generates the completions.

  1. Go to a new empty directory
  2. Run bashly init to create a minimal bashly config
  3. Run bashly add comp script to create a minimal completions file named completions.bash
  4. Run source ./completions.bash

Now, you should be able to see completions for cli download and cli upload.
Try cli download --<TAB> to see if it completes.

For me, when running this in a containerized ZSH shell, it works.

@ndepal
Copy link

ndepal commented Jan 27, 2022

I have also noticed this problem in the past. I'm using Oh-My-Zsh, and as you say I also think bash completions should work in general.

Following your instructions above, I get partially working suggestions:

cli <TAB>
download   -h         --help     upload     -v         --version

But subcommands don't work right:

cli download <TAB>
download   -h         --help     upload     -v         --version

I get the same suggestions as just for cli.

When I was investigating this a few months ago, I believe I tracked it down to the following line in the completions function:

local comp_line="${COMP_WORDS[*]:1}"

This does not result in the correct value being assigned to com_line because array indexing works differently in ZSH.

@DannyBen
Copy link
Owner Author

Thanks for the additional info, but I cannot reproduce the problem.

I am running a clean ZSH in a container, here is a screencast of how it works for me.

asciicast

The steps below do not reproduce the problem.

If anyone either has a solution, or a way for me to reproduce it, I can try and fix it.

  1. Create this file, call it completions.bash (this is the result of bashly init && bashly add comp script)
#!/usr/bin/env bash
_cli_completions() {
  local cur=${COMP_WORDS[COMP_CWORD]}
  local comp_line="${COMP_WORDS[*]:1}"

  case "$comp_line" in
    'download'*) COMPREPLY=($(compgen -W "--force --help -f -h" -- "$cur")) ;;
    'upload'*) COMPREPLY=($(compgen -W "--help --password --user -h -p -u" -- "$cur")) ;;
    ''*) COMPREPLY=($(compgen -W "--help --version -h -v download upload" -- "$cur")) ;;
  esac
}

complete -F _cli_completions cli
  1. Run this command to get a clean ZSH in a container
$ docker run --rm -it -v $PWD:/app dannyben/zsh
  1. Inside the container, run:
$ source completions.bash
$ cli<tab>
$ cli download<tab>
$ cli download --<tab>
# and so on

@DannyBen
Copy link
Owner Author

I have now also tried it with a non-docerized zsh, with and without Oh My Zsh - both work as expected.

If anyone using zsh can test with the above provided completions.bash, it would help - the only reason I can think of that this does not reproduce for me, and fails for others is that you might be using an older version of bashly or completely.

@DannyBen DannyBen added the help wanted Extra attention is needed label Jan 27, 2022
@ndepal
Copy link

ndepal commented Jan 28, 2022

I tried following your instructions. I can reproduce the issue that way as well: https://asciinema.org/a/6ksQG74HFh71HYznsdEBrA7OW

I believe your recording actually also has the issue.
the completions for cli download --<TAB> look (mostly) right by coincidence. But they're the suggestions that apply for cli <TAB>. You're also getting the suggestion --version. Isn't that wrong cli download --version?

You can make it more obvious by adding flags to the cli download command that don't exist elsewhere. And as shown in my recording, cli download <TAB> (without already adding --) will suggest download and upload again.

This is consistent with what I saw before. And I believe it's because in ZSH local comp_line="${COMP_WORDS[*]:1}" is always empty, so the switch always goes to ''*).

@DannyBen
Copy link
Owner Author

Ok, nice - thanks for recording.
Do you happen to know what is the alternative to local comp_line="${COMP_WORDS[*]:1}" that works in both ZSH and abash?

@DannyBen
Copy link
Owner Author

DannyBen commented Jan 28, 2022

@DannyBen
Copy link
Owner Author

DannyBen commented Jan 28, 2022

I think I found a solution that seems to work in both bash and zsh as expected:
https://asciinema.org/a/7xiSMSxZEFmAUtLeFkXnHFWn3

source this file:

#!/usr/bin/env bash

_cli_completions() {
  local cur=${COMP_WORDS[COMP_CWORD]}
  # local comp_line="${COMP_WORDS[*]:1}"          # OLD
  local comp_line="${COMP_WORDS[@]:1}"            # NEW
  echo "COMP_WORDS: ${COMP_WORDS[*]}" >> out.txt  # DEBUG
  echo "comp_line:  ${comp_line[*]}" >> out.txt   # DEBUG

  case "$comp_line" in
    'download'*) COMPREPLY=($(compgen -W "--force --help -f -h" -- "$cur")) ;;
    'upload'*) COMPREPLY=($(compgen -W "--help --password --user -h -p -u" -- "$cur")) ;;
    ''*) COMPREPLY=($(compgen -W "--help --version -h -v download upload" -- "$cur")) ;;
  esac
}

complete -F _cli_completions cli

If you can confirm, I will bake this into completely and bashly.

@DannyBen DannyBen added the bug Something isn't working label Jan 28, 2022
@DannyBen DannyBen self-assigned this Jan 28, 2022
@ndepal
Copy link

ndepal commented Jan 28, 2022

Yes that's me 😄

Your suggested change works for me as well.

@DannyBen
Copy link
Owner Author

DannyBen commented Jan 28, 2022

Cool! I will merge the change into completely, release a version, and then release a bashly version that forces the fixed dependency in its gemspec, followed by a docker release.

Thanks for the help!

@DannyBen
Copy link
Owner Author

Version 0.7.2 is released on RubyGems and Docker Hub with the fix.

@ndepal
Copy link

ndepal commented Jan 28, 2022

Awesome, thanks for the quick fix.

I do think there is another issue with the completions, but probably not ZSH related.

With the following bashly.yml:

name: cli
help: Sample application
version: 0.1.0

commands:
- name: download
  short: d
  help: Download a file

  flags:
  - long: --arch
    arg: arch
    help: set the arch
    allowed: [amd64, arm64]

I get the following completions.bash:

#!/usr/bin/env bash

# This bash completions script was generated by
# completely (https://github.com/dannyben/completely)
# Modifying it manually is not recommended
_cli_completions() {
  local cur=${COMP_WORDS[COMP_CWORD]}
  local comp_line="${COMP_WORDS[@]:1}"

  case "$comp_line" in
    'download'*) COMPREPLY=($(compgen -W "--arch --help -h amd64 arm64" -- "$cur")) ;;
    ''*) COMPREPLY=($(compgen -W "--help --version -h -v download" -- "$cur")) ;;
  esac
}

complete -F _cli_completions cli

So now with the newest bashly/completely, I get the following behavior:

./cli download <TAB>
amd64   --arch  arm64   -h      --help

but amd64 and arm64 are not valid suggestions at this point, they should be suggested at

./cli download --arch <TAB>
amd64 arm64

right?

@DannyBen
Copy link
Owner Author

DannyBen commented Jan 28, 2022

No. This is the intended behavior.

You can see the intended output clearly by running bashly add comp yaml. This generates the input file to the completely gem:

---
cli:
- "--help"
- "--version"
- "-h"
- "-v"
- download
cli download:
- "--arch"
- "--help"
- "-h"
- amd64
- arm64

The completely gem creates a simplified completions script. It only sets completion arrays for commands and subcommands, and these arrays are offered regardless of their allowed position.

The reason it is done like this, is simple: In order to also support positional logic, you end up with a completions script that it sometimes thousands of lines long. See the completions script for git or docker.

For me, writing this kind of scripts for completion is unacceptable, and this is the reason I created completely: To allow a simple way to create usable completions. The price is that they are not perfect.

Also - I found that removing the suggestion list completely from my shell, and just let it complete my line, is much easier to work with. I am doing so by adding this to my ~/.inputrc:

# Change tab behavior and add shift tab for old behavior
TAB: menu-complete
"\e[Z": complete

so in your case, these will complete nicely:

$ cli downl<tab>
$ cli downlload --a<tab>
$ cli downlload --arch a<tab>

If there is any suggestion to make this better, I am open to it (and it should be probably done in completely).

@ndepal
Copy link

ndepal commented Jan 28, 2022

Alright, fair enough. Thanks for the explanation and the tip.

@DannyBen DannyBen removed the help wanted Extra attention is needed label Jan 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants