4

I'm writing a zsh completion script for the swift tools (compiler and package manager). This command can be used with or without specifying a subcommand. For example

# calls to the compiler
# usage: swift [options] <inputs>
1>  swift my_file.swift
2>  swift -a 1 -b 2 my_file.swift

# calls to build subcommand
3>  swift build
4>  swift build -c 1

# calls to test subcommand
5>  swift test
6>  swift test -d 4

So far, I've got the following completion script. It mostly works, however for the bare call the autocomplete doesn't quite work allright. It does enter the _compiler method, but it doesn't autocomplete correctly after swift -a. It autocompletes as if it has forgotten about -a. So swift -a <TAB><TAB> should show the menu for picking 1-2-3; but instead it shows a file listing. For swift -a -a <TAB><TAB> it shows the correct completions, but repeating -a is not valid.

It probably has something to do with the state that has been altered, while it shouldn't have for this specific use-case. However for the subcommands (build & test), the state should be altered for their respective completions to work correctly.

I've went through the documentation, but it is very cryptic. I've checked various other completion scripts, but couldn't figure out the missing link. So how can I specify a completion for a command that has optional subcommands which have separate sets of flags (and also having subsubcommands themselves)?

#compdef swift
local context state state_descr line
typeset -A opt_args

_swift() {
    _arguments -C \
        ': :->command' \
        '*:: :->arg'

    case $state in
        (command)
            local tools
            tools=(
                'build:description for build'
                'test:description for test'
            )
            _alternative \
                'tools:common:{_describe "tool" tools }' \
                'compiler: :_compiler'
            ;;
        (arg)
            case ${words[1]} in
                (build)
                    _build
                    ;;
                (test)
                    _test
                    ;;
                (*)
                    _compiler
                    ;;
            esac
            ;;
    esac
}

_compiler() {
    _arguments -C \
        '(-a)-a[description for a]: :(1 2 3)' \
        '(-b)-b[description for b]: :(4 5 6)' \
        '*: :_files'
}

_build() {
    _arguments -C \
        '-c[description for c]: :(1 2 3)'    
}

_test() {
    _arguments -C \
        '-d[description for d]: :(4 5 6)'    
}
Bouke
  • 11,768
  • 7
  • 68
  • 102

1 Answers1

0

You could do it using _regex_words and _regex_arguments :

local -a abopts buildopts testopts cmds aargs bargs cargs dargs
local matchany=/$'[^\0]##\0'/
aargs=(/$'(1|2|3)\0'/ ':number:number:(1 2 3)')
bargs=(/$'(4|5|6)\0'/ ':number:number:(4 5 6)')
cargs=(/$'(1|2|3)\0'/ ':number:number:(1 2 3)')
dargs=(/$'(4|5|6)\0'/ ':number:number:(4 5 6)')
_regex_words opt 'options' '-a:description for a:$aargs' '-b:description for b:$bargs'
abopts=("${reply[@]}")
_regex_words opt 'options' '-c:description for c:$cargs'
buildopts=("${reply[@]}")
_regex_words opt 'options' '-d:description for d:$dargs'
testopts=("${reply[@]}")
_regex_words cmd 'commands' 'build:build command:$buildopts' 'test:test command:$testopts'
cmds=("${reply[@]}")

_regex_arguments _swift "$matchany" \( "${cmds[@]}" \| "${abopts[@]}" "$matchany" ":file:file:{_files}" \)
_swift "$@"

You can read about how to use those functions here: http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-Functions or here: https://github.com/vapniks/zsh-completions/blob/master/zsh-completions-howto.org

Ben
  • 251
  • 1
  • 7
  • When I use this, it only suggests files on ``swift ``. Also it doesn't complete ``swift bui`` to ``swift build ``. – Bouke Oct 05 '16 at 16:42
  • Hmm, strange, `swift bui` works for me. If you want to be able to complete a filename as the first arg before any options, add another alternative for the part in brackets in the arguments to _regex_arguments: `_regex_arguments _swift "$matchany" \( "${cmds[@]}" \| "${abopts[@]}" "$matchany" ":file:file:{_files}" \| "$matchany" ":file:file:{_files}" \)` (the \| acts like "or") – Ben Oct 10 '16 at 05:28
  • Did you copy and paste my code exactly as it is? Any small typo will probably break it. – Ben Oct 12 '16 at 12:38
  • I did copy it exactly as-is, but it isn't working :(. It is only suggesting file completions, but no commands. – Bouke Oct 12 '16 at 18:52
  • Sounds like it hasn't been loaded properly, make sure the completion file is in a directory in your `$fpath`, and try entering the following commands before testing: `unfunction _swift; autoload -z _swift; compdef _swift swift` – Ben Oct 13 '16 at 16:39
  • You should also add `#compdef swift` to the top of the file if you want it loaded automatically on startup. – Ben Oct 13 '16 at 16:44
  • What version of zsh are you using? – Ben Oct 13 '16 at 21:07