Search code examples
bashzshoh-my-zshbash-completion

Bash completion does not work in ZSH/Oh-My-ZSH because COMP_WORDS is not an array


I'm writing a bash command line tool for which I want to enable bash completion using completely.

I have the following bash completion. After I eval "$(./cli completion)" (which outputs the below), completions work fine in 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
    'completions'*) COMPREPLY=($(compgen -W "--help -h" -- "$cur")) ;;
    'download'*) COMPREPLY=($(compgen -W "--force --help -f -h" -- "$cur")) ;;
    ''*) COMPREPLY=($(compgen -W "--help --version -h -v completions download" -- "$cur")) ;;
  esac
}

complete -F _cli_completions cli

However, they do not work right in ZSH. I have identified the problem to be that COMP_WORDS is not an array when I'm inside ZSH, but it is inside bash. This then breaks the line local comp_line="${COMP_WORDS[*]:1}".

E.g. in the situation ./cli download <tab>, comp_line should be download, but in ZSH it's /cli download (only the . is removed), so I always end up in the last case ''*.

I'm using ZSH 5.8 with Oh-My-ZSH. Bash completions generally seem to work in ZSH. Oh-My-ZSH sets up completions using

fpath=($ZSH/functions $ZSH/completions $fpath)
autoload -U compaudit compinit

I have tried setting setopt shwordsplit, hoping that that would cause COMP_WORDS to be treated as an array, but it had no effect.

Is there a ZSH configuration that I can change to make this completion script work? Or is there a bash compatible change to the completion script that would make it work in both shells?


Solution

  • This issue is discussed in this Github ticket.

    ZSH should be totally capable of handling bash completions when it is configured to do so by adding these two lines in ~/.zshrc:

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

    In addition, the script in question, generated by the completely gem, seems to have a problem.

    Replacing * with @ in the below line seems to make it work on both bash and zsh.

    local comp_line="${COMP_WORDS[*]:1}"   # broken
    local comp_line="${COMP_WORDS[@]:1}"   # works
    

    This change is now a part of both bashly and completely gems.