I am using zsh with completion turned on. When I try to tab-complete, sometimes the command hangs for a long time. After a few seconds it completes and correctly presents my options. On the other hand, if I interrupt it with Ctrl-C, I get the following message:
Killed by signal in _path_commands after 2s
If I try to tab-complete directories (e.g. in ls) it works just fine, there is no lag.
Note that I am running on Windows using WSL2, though I can vaguely recall it happening on other systems, too. Haven't gone back to confirm, but when I just tested on my server, I couldn't reproduce it there, so it's something about the environment.
Providing an answer for my own question to share what I found. If others have a better idea, I would love to accept their answer. However, when googling I could not find anything on this error (not helped by zsh's obscure syntax making it about as easy to google as perl expressions).
The tl;dr solution is as follows: Run
unsetopt pathdirsand the issue should go away. Put it in your~/.zshrcand it should be resolved. What follows is the explanation.Turning on tracing for
_path_commandsto see where it hangs:autoload -t _path_commands:So let's have a look at that function via
which _path_commands(note you need to do a completion once for zsh to load it). I'll provide the relevant snippet:The last line we get when it hangs is
local -a path_dirswhich just defines an empty array. That's likely not it, but if I execute the next command it hangs for a long time:path_dirs=(${^path}/*(/N:t)). Good luck googling that if you're not familiar with the language. I'll explain:( ... )$pathRC_EXPAND_PARAMwith the^chracter${^path}, see 14.3 Parameter Expansion. It's not our culprit so I'll skip the explanation. The only bit to understand is that we have an array here./*. This is the same as if you did this on your command line:ls *, for example. Except here it does it for all elements of the array, like a loop. A good culprit, but if we tryecho ${^path}/*it's still very quick./only returns directoriesNsetsnullglob, basically "remove empty elements":tsets the modifier to remove the full path and leave only thebasenameoutput.If we play around with the full expression e.g.
${^path}/*(/N:t)we notice that it's only slow if the/character is present. Removing it makes everything fast. With some additional debugging you can even find what's slow, e.g. write a loop and see when it hangs:In my case I notice it hanging on a lot of Windows paths (
/mnt/c/Windows/system32, for example). At this point I gave up: I don't know why this expansion is so slow for Windows paths and I don't know how to debug it or do some form of "caching" that speeds it up (it might just be slow due to WSL filesystem issues).Instead, notice how there is a condition:
if [[ -o path_dirs ]]before entering this code path? The conditional test-ochecks for an option, i.e. ifpath_dirsis set. This is described in the options manual:If we can live without this feature (I think I can), we can stop here: Simply turn it off, e.g. via
unsetopt pathdirsand call it a day. Once that's done, this code branch is no longer executed and the problem goes away.