I will attempt to provide an insight to how zsh completion system works and an incomplete go at this problem.
The file that runs when you use TAB completion for man
in zsh is located under the /usr/share/zsh/${ZSH_VERSION}/functions
directory. The tree varies across distributions, but the file is named _man
, and provides completion for man
, apropos
and whatis
.
After _man is invoked, it works as following (rough description):
- if completing for
man
and --local-file
was specified as first flag, invoke standard files completion (_files
)
- construct
manpath
variable from a set of defaults / $MANPATH
. This is where the manpages will be searched
- determine if we invoked
man
with a section number parameter, if yes - only those sections will be searched
- if the
zstyle ':completion:*:manuals' separate-sections true
was used, separate sections in output (don't mix between them)
- invoke
_man_pages
to provide a list of man pages for the match
_man_pages
now does a bit of magic with compfiles -p pages '' '' "$matcher" '' dummy '*'
. pages
is the variable with all the directories containing manpages for requested section(s). The actual globbing pattern is constructed from the implicit parameter $PREFIX
and the last parameter to compfiles
- *
in this case. This results in /usr/share/man/man1
to be transformed into /usr/share/man/man1/foo*
- The new list of glob patterns is globbed, obtaining all files which match the pattern
_man_pages
then strips any suffixes from the files and adds them to the completion widget list of choices by using compadd
Now, as you can see, the list of manpages is directly determined by $PREFIX
variable. In order to make zsh:foo
to list only man pages of zsh*
which contain the word foo
, it needs to be split across :
character (if any).
The following addition in _man_pages
partially solve the issue (zsh 4.3.4):
Original:
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
compfiles -p pages '' '' "$matcher" '' dummy '*'
pages=( ${^~pages}(N:t) )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
Modified (look for ##mod comments):
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
##mod
# split components by the ":" character
local pref_words manpage_grep orig_prefix
# save original prefix (just in case)
orig_prefix=${PREFIX}
# split $PREFIX by ':' and make it an array
pref_words=${PREFIX//:/ }
set -A pref_words ${=pref_words}
# if there are both manpage name and grep string, use both
if (( $#pref_words == 2 )); then
manpage_grep=$pref_words[2]
# PREFIX is used internally by compfiles
PREFIX=$pref_words[1]
elif (( $#pref_words == 1 )) && [[ ${PREFIX[1,1]} == ":" ]]; then
# otherwise, prefix is empty and only grep string exists
PREFIX=
manpage_grep=$pref_words[1]
fi
compfiles -p pages '' '' "$matcher" '' dummy '*'
##mod: complete, but don't strip path names
pages=( ${^~pages} )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
##mod: grep pages
# Build a list of matching pages that pass the grep
local matching_pages
typeset -a matching_pages
# manpage_grep exists and not empty
if (( ${#manpage_grep} > 0 )); then
for p in ${pages}; do
zgrep "${manpage_grep}" $p > /dev/null
if (( $? == 0 )); then
#echo "$p matched $manpage_grep"
matching_pages+=($p)
fi
done
else
# there's no manpage_grep, so all pages match
matching_pages=( ${pages} )
fi
#echo "\nmatching manpages: "${matching_pages}
pages=( ${matching_pages}(N:t) )
# keep the stripped prefix for now
#PREFIX=${orig_prefix}
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
However, it's still not fully working (if you uncomment the #echo "$p matched $manpage_grep"
line, you can see that it does build the list) - I suspect that somewhere internally, the completion system sees that, for instance, "zshcompctl" is not matched by prefix "zsh:foo", and does not display the resulting matches. I've tried to keep $PREFIX
as it is after stripping the grep string, but it still does not want to work.
At any rate, this at least should get you started.