22

In zsh, the cd command has a two-argument form: cd OLD NEW changes to ${PWD/OLD/NEW}. With the new-style completion system, zsh is capable of completing NEW: the second argument is completed based on what OLD can be substituted with to obtain an existing directory. But the first argument is only completed to existing directories.

How can I get zsh to offer completions that are possible values for OLD, in addition to completing existing directories?

For example, if the current directory is /path/to/foo and there are also directories /also/to/foo and /path/to/foo/prime, then cd pTab completes p to prime. If I intend to run cd path also then I'd like zsh to also offer path as a completion. How?

Using already-typed values of the second argument to limit the possibilities for the first argument would be a plus, but completing the first argument independently would be fine too.

  • Would your 2-argument completion example be: cd p also <Tab>, or cd p also <left arrow x 5> <Tab>? – Jeff Schaller May 11 '16 at 0:41
  • @JeffSchaller The second-argument-sensitive example would be cd p also<Left*5><Tab>: if I press <Tab> while the cursor is in also, I expect also to be completed, not p. – Gilles May 11 '16 at 7:29
  • If you type cd t<tab> should it offer th/to/foo/prime and to/foo/prime? Or should it just limit itself to directory boundaries? – Barmar May 11 '16 at 18:30
  • @Barmar I think limiting to directory boundaries would be the most convenient. – Gilles May 11 '16 at 18:51
  • INteresting question... – wogsland Mar 19 '17 at 15:27
1

I guess you could add the components of $PWD to the cd completion list, though this appears to require fiddling with _cd; that is, a customized version of _cd must appear first in $fpath.

% cd && mkdir zcomp
% cp $fpath[-1]/_cd zcomp
% fpath=(~/zcomp $fapth)

Then up at the top of ~/zcomp/_cd add a function

_our_pwd() {
  _values ourpwd ${(ps:/:)PWD}
}

and then just before the _alternative line add what that returns to the list of alternatives

  ...
  alt=("$service-options:$service option:_cd_options" "$alt[@]")
fi

alt=(ourpwd:pwd:_our_pwd "$alt[@]")

_alternative "$alt[@]" && ret=0

return ret
...

though this will always add the pwd components to cd completions:

% cd
Users    jdoe    Applications/  Desktop/  Documents/  Downloads/  Library/
...

with additional logic you could only add the $PWD components when there is already a second argument present instead of always.

However! This always messes up the cd completion and requires that we monkey patch the upstream _cd completion. Another option would be to create a new name for the function provided by the two-arg cd, perhaps called cdsub, and only have the completion of PWD components appear for that. Add this to ~/.zshrc

function cdsub { builtin cd "$@" }

And then a gutted _cd completion for _cdsub to be placed somewhere in $fpath:

#compdef cdsub
#
# Modified version of _cd from ZSH 5.3.1 with specific support for the
# `cd old new` form whereby PWD elements are provided for completion.

_cd_options() {
  _arguments -s \
  '-q[quiet, no output or use of hooks]' \
  '-s[refuse to use paths with symlinks]' \
  '(-P)-L[retain symbolic links ignoring CHASE_LINKS]' \
  '(-L)-P[resolve symbolic links as CHASE_LINKS]'
}

setopt localoptions nonomatch

local expl ret=1 curarg
integer argstart=2 noopts

if (( CURRENT > 1 )); then
  # if not in command position, may have options.
  # Careful: -<-> is not an option.
  while [[ $words[$argstart] = -* && argstart -lt CURRENT ]]; do
    curarg=$words[$argstart]
    [[ $curarg = -<-> ]] && break
    (( argstart++ ))
    [[ $curarg = -- ]] && noopts=1 && break
  done
fi

if [[ CURRENT -eq $((argstart+1)) ]]; then
  # cd old new: look for old in $PWD and see what can replace it
  local rep
  # Get possible completions using word in position 2
  rep=(${~PWD/$words[$argstart]/*}~$PWD(-/))
  # Now remove all the common parts of $PWD and the completions from this
  rep=(${${rep#${PWD%%$words[$argstart]*}}%${PWD#*$words[$argstart]}})
  (( $#rep )) && _wanted -C replacement strings expl replacement compadd -a rep
else
  _values ourpwd ${(ps:/:)PWD} && ret=0
  return ret
fi

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.