Unable to do two word search in Zsh TAB for Man
Problem: have tab completion that takes two words and calculates the best match against them for Man and then returns the best matches
Example: The following pseudocode should give me at least a Zsh reverse-menu-complete -command. Right now, I cannot look for guides in guides without zgrep.
man zsh:reverse <TAB>
where ":" is the delimiter I want.
Initial Problem: What files do TAB completion when I hit TAB for one word while looking for Zsh manuals?
a source to share
I will try to give an idea of how the zsh seeding system works and an incomplete problem in solving this problem.
The file that starts when using TAB completion for man
zsh is in the directory /usr/share/zsh/${ZSH_VERSION}/functions
. The tree varies from distribution to distribution, but the file is named _man
and provides completion for man
, apropos
and whatis
.
After calling _man, it works like this (approximate description):
- if completion for
man
and--local-file
was specified as the first flag, call standard file completion (_files
) - build a variable
manpath
from a set of default values /$MANPATH
. Searches will be found here - determine if we call
man
with the section number parameter, if yes - only those sections will be searched - if used
zstyle ':completion:*:manuals' separate-sections true
, separate sections in output (do not mix between them) - invoke
_man_pages
to provide a list of man pages to match -
_man_pages
now a little magic withcompfiles -p pages '' '' "$matcher" '' dummy '*'
.pages
is a variable with all directories containing manpages for the requested partitions. The actual globe pattern is built from the implicit parameter$PREFIX
and the last parameter incompfiles
-*
in this case. This causes it to/usr/share/man/man1
convert to/usr/share/man/man1/foo*
- New list of glob patterns globbed, getting all files matching pattern
-
_man_pages
then removes any suffixes from the files and adds them to the completion widget list usingcompadd
Now, as you can see, the manpages list is directly defined by a variable $PREFIX
. To make it zsh:foo
only list man pages zsh*
that contain a word foo
, it needs to be split into a character :
(if any).
The following addition in _man_pages
part solves the problem (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
}
Changed (see ## 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 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 still doesn't work completely (if you uncomment the line #echo "$p matched $manpage_grep"
, you can see that it creates a list) - I suspect that somewhere inside, the completion system sees that, for example, "zshcompctl" does not match the "zsh" prefix: foo "and does not display the resulting matches. I tried to save the $PREFIX
same as after deleting the grep line but it still doesn't want to work.
If anything, that should at least get you started.
a source to share