39

I'm trying to write completion functions for some custom functions I wrote, but seem to be really struggling with even the most basic ones.

An example function is:

function eb_instances() {
    if [ "$#" -ne 2 ]; then
        echo "Usage eb_instances <aws profile name> <environment name>"
        echo "e.g.:"
        echo " eb_instances production kraken-prod-api"
        return 1
    fi

    aws ec2 describe-instances --filters  "Name=instance-state-name,Values=running"   "Name=tag:Name,Values=$2" --profile=$1 --output=json | jq -r ".Reservations[].Instances[].PrivateIpAddress"
}

This has two positional arguments, <aws profile name> and <environment name>

I want the completion options for <aws profile name> to be dynamically available by running sed -n -E 's/\[([a-zA-Z0-9_\-]+)\]/\1/p' ~/.aws/credentials | tr \\n ' ', and the completions for <environment name> to be dynamically available by running another function I have called eb_names.

I'm finding the documentation quite sparse and difficult to follow. I've also seen the zsh-completions repo for similar commands but can't seem to find something similar to what I need.

Any help getting started would be much appreciated!

Update

Based on @cuonglm's answer, I used:

#compdef ebinstances

_ebinstances() {
  local state

  _arguments \
    '1: :->aws_profile'\
    '*: :->eb_name'

  case $state in
    (aws_profile) _arguments '1:profiles:($(sed -n -E "s/\[([a-zA-Z0-9_\-]+)\]/\1/p" ~/.aws/credentials | tr \\n " "))' ;;
              (*) compadd "$@" foo bar
  esac
}

_ebinstances "$@"

What I forgot to mention in the original question was, that I also wanted the completion of the second argument be dependent on the first one (both of which are dynamic based executing some code), for example:

$ eb_instances <cursor>TAB
cuonglm  test

gets the completions I want. Once I select say the first one, and try to auto complete:

$ eb_instances cuonglm <cursor>TAB

I want to generate the completion options by executing eb_names cuonglm, and if possible, also, drilldown on the completions for example if the correct candidate was foo-bar,

$ eb_instances cuonglm foo<cursor>TAB

I want to generate the completion options by executing eb_names cuonglm foo

1 Answer 1

49
+100

At first, the zsh Completion System seems to be very complex and difficult to grasp. Let's try an example.

The first thing you need to know is that the zsh completion system will load completion functions from $fpath. Make sure your completions directory appear in:

print -rl -- $fpath

(If you're using oh-my-zsh, .oh-my-zsh/completions is in $fpath. You can just create it and put your completion functions there.)

Now, you must create a completion file for your function. Its name must start with underscore (_) plus your function name. In your case, its name would be _eb_instances.

Add these lines to the _eb_instances file:

#compdef eb_instances

_eb_instances() {
  local state

  _arguments \
    '1: :->aws_profile'\
    '*: :->eb_name'

  case $state in
    (aws_profile) _arguments '1:profiles:(cuonglm test)' ;;
              (*) compadd "$@" prod staging dev
  esac
}

_eb_instances "$@"

You are done. Save the file and start a new session to test completion. You will see something like this:

$ eb_instances <cursor>TAB
cuonglm  test

$ eb_instances cuonglm <cursor>TAB
dev      prod     staging

You can read the zsh completion system documentation about the _arguments function and state variable. Also, you will need to change (cuonglm test) to your sed command and change prod staging dev to your eb_names function.

If you want to generate the 2nd argument based on what 1st argument was passed, you can use the $words[2] variable:

case $state in
  (aws_profile) _arguments '1:profiles:(cuonglm test)' ;;
            (*) compadd "$@" $(echo $words[2]) ;;
esac

Replace echo with your real function. In your case, it's $(eb_names $words[2]).

If you still have trouble with this, just define _eb_instances and eb_instances in your .zshrc then call completion as:

compdef _eb_instances eb_instances

You need to initialize the completion system with:

autoload -U compinit
compinit

(If you used oh-my-zsh, it have been loaded)

8
  • Thanks a lot for the detailed response. The problem though that I can't seem to find a well documented way to do, is what if my completion options are dynamic, or come from other functions/scripts, ie, in your example, what if I wanted cuonglm test to come from sed -n -E 's/\[([a-zA-Z0-9_\-]+)\]/\1/p' ~/.aws/credentials | tr \\n ' '
    – zsquare
    Commented Nov 2, 2015 at 6:50
  • @zsquare: Just replace (cuonglm test) with ($(sed -n -E 's/\[([a-zA-Z0-9_\-]+)\]/\1/p' ~/.aws/credentials | tr \\n ' ')), change compadd "$@" prod staging dev to compadd "$@" $(eb_names).
    – cuonglm
    Commented Nov 2, 2015 at 6:51
  • this may be stretching it a little, but, I forgot to mention, that need to pass eb_names the first argument, ie, production or staging, how do I do that? Thanks a lot, I've gotten quite a few things sorted with this.
    – zsquare
    Commented Nov 2, 2015 at 18:32
  • I mean't I wanted to use the first argument in to pass to eb_names. I tried referencing $1 or $2, but no dice.
    – zsquare
    Commented Nov 2, 2015 at 18:57
  • 4
    You're a wizard, Harry. Commented Aug 23, 2019 at 15:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .