Treat command like another for completion purposes

autocompletezsh

My zshrc includes the following function to create a directory and then enter it:

function mcd () {
    mkdir -p "$*" && cd "$*"
}

The function itself works fine but I get odd behavior with completion. If I start typing e.g. mcd ~/ and then press Tab, the message

_mtools_drives:3: command not found: mtoolstest

is inserted at the insertion point and nothing is completed. What I want is for the command to be completed just as mkdir would be: zsh should offer me the names of existing directories.

How do I tell zsh that for completion purposes, it should treat mcd the same as mkdir?

Best Answer

The following snippet causes mcd to be completed like mkdir:

compdefas () {
  if (($+_comps[$1])); then
    compdef $_comps[$1] ${^@[2,-1]}=$1
  fi
}
compdefas mkdir mcd

The way it works is to look up the current completion setting for mkdir. The completion code for a function (generally the name of a completion function) is stored in the associative array _comps. Thus compdef $_comps[mkdir] mcd declares that mcd should be completed in the same way that mkdir is completed right now.

The function above adds a few niceties:

  • The test for (($+_comps[$1])) ensures that if $1 doesn't have a specified completion method then no completion method is set for the other arguments.
  • ${@[2,-1]} is the list of arguments to the function starting with the second one, so you can specify more than one command name to define completions for. It's actually ${^@[a,-1]} so that the text around the array expansion is replicated for each array element.
  • =$1 sets the service name to use. This matters only for a few commands whose completion function handles several closely-related commands. For example the completion function _gzip handles both gzip and gunzip as well as pigz and unpigz; compdef _gzip foo makes foo use the default behavior of _gzip while compdef _gzip foo=pigz makes foo use the behavior of _gzip when it completes for pigz.

Turning to your specific case, the default completion for mkdir not only offers directories, but also options, which your function does not support. So you'd actually be better off defining mcd as just completing existing directories. Zsh comes with a helper function for that (an undocumented wrapper around _files).

compdef _directories mcd

The reason you were getting these bizarre-looking completions for mcd is that it's the name of a command from a once moderately widespread suite of commands mtools.

Related Question