6

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?

bdesham
  • 1,307
  • 2
  • 13
  • 23
  • While that doesn't solve your problem, you can use take <directory_name> to achieve the same behaviour as your mcd function. – Haltarys Jan 10 '23 at 11:36

2 Answers2

6

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.

  • If I issue command mcd without any argument, then it echos mkdir: cannot create directory ‘’: No such file or directory. mkdir will give mkdir: missing operand which look nicer than our mcd, how to have the same behavior as mkdir? – Tuyen Pham Jan 27 '19 at 06:31
  • 1
    @TuyenPham See if my function inspires you. – Gilles 'SO- stop being evil' Jan 27 '19 at 18:54
  • Thanks, that's truly mkdir wrapper, but should we still need to have compdefas? As I test your mkcd function, it works well with completion. – Tuyen Pham Jan 28 '19 at 02:03
  • Should this have double-quotes around things, and if so, where? – Adam Katz Oct 26 '19 at 19:56
  • @Adam In zsh, you don't need double quotes around $foo if the resulting word is not empty. compdefas mkdir mcd is equivalent to compdef _mkdir mcd with the advantage of working with commands that don't obey the completion function naming convention of pretending an underscore. – Gilles 'SO- stop being evil' Oct 26 '19 at 21:20
5

compdef uses the following form:

compdef [ -ane ] function name

so one idea would be to point the name of mcd at the _mkdir function via the following command:

compdef _mkdir mcd

however this is slightly incorrect as mkdir completes various flags that should probably not also be given to cd. More direct would be to complete on directories:

compdef _directories mcd
thrig
  • 34,938