Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] Completion: Improve _man
- X-seq: zsh-workers 42977
- From: dana <dana@xxxxxxx>
- To: Zsh workers <zsh-workers@xxxxxxx>
- Subject: [PATCH] Completion: Improve _man
- Date: Sun, 10 Jun 2018 01:06:22 -0500
- Dkim-signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dana-is.20150623.gappssmtp.com; s=20150623; h=from:content-transfer-encoding:mime-version:subject:message-id:date :to; bh=UGXRMSCJkZ4c0/m1aesy9rtlcOoVt5UkJZ5Z66z1XoA=; b=eHC9FyRqg4iCABm3c3vXGO1dUkdFEmceLgeQK/Dw7Swf4c7lRAm8Xsd9bjUJ/PXoeW KW0s0wRn+AeiMptr7VCaqgDxxaQigiAX4J5UlJ2VMsl8D3PIC0JAgbaw51xGSL5wkqh2 UECVNSW6P845lOcaxCE4aHFaucWkJqX1p9SWi2hkmRoFDOCAtdKLrjM+gWYJZx6S56Ye AN/nxjD9ez8CgXNphsw+XZVkN8QBrPX9YMMYmo6aFqCB2HzdCgDW/zymC9vJEQxjO83Y j9HXv2/BuhEndVhl8PUQDIuGPbdEuwd1E7cozaE4TjOFW8e1kM4JLM38HZ4u9mO1W1fi jgsw==
- List-help: <mailto:zsh-workers-help@zsh.org>
- List-id: Zsh Workers List <zsh-workers.zsh.org>
- List-post: <mailto:zsh-workers@zsh.org>
- List-unsubscribe: <mailto:zsh-workers-unsubscribe@zsh.org>
- Mailing-list: contact zsh-workers-help@xxxxxxx; run by ezmlm
Here's a *much* more complicated change. It enhances _man as follows:
* Complete all options to most major man variants
* More accurately match sections
* Better handle certain edge cases (see below)
* Show descriptions for sections (suggested by Mikael i think)
Notes:
* The function would previously use the provided section name as the leading
part of a directory pattern, so that on Solaris for example if you gave it 3p
it would also complete pages from 3pool, 3proc, &c. I couldn't think of a
reason this functionality would be desirable (man won't show you a page from a
section it's not in anyway), so i made it match exactly
* The function previously behaved kind of strangely when given multiple operands
(where the first one is potentially an unknown section name). For example, if
you do `man zz <TAB>` on macOS it just keeps completing the word 'fuzzy'
because it wants to use zz as part of a glob to match page names. I changed
that so that if the section name (given either as the first operand or as an
argument to -s/-S) doesn't seem valid, it's treated as a page name, and
subsequent completion possibilities are thus returned from all sections
This can still be a problem — `man 2to3 <TAB>` doesn't work well, for example,
because the function thinks 2to3 looks like a (Solaris-style) section name. I
know this can be fixed, but see my last two notes :/
* It looks like someone put some special effort into making the function at
least partially usable on AIX, so i tried to account for that platform as
well... but i don't actually have access to it myself, so the extent of my
testing there was just OSTYPE=aix
* I chose sections to describe pretty arbitrarily. Solaris variants have 9034234
different sections and i didn't want to enumerate every single one, but some
of them seem like they might come up a lot in regular use. It may be nice in
the future to give separate tags to the described and undescribed ones, so
that navigation is a little nicer for people with `group-name ''`
* There were a bunch of options whose purpose wasn't very clear to me, so i
couldn't complete them as well as i wanted to. I put @todos where this was the
case. If anyone knows how they actually work, feel free to change them
accordingly
* There are some bits that were (or are now) redundant or otherwise not great —
see the todo note someone left about the sects array for example — but i just
left them alone because i was afraid of breaking something
* Could someone please review the way i return from _arguments? I don't think
it's correct (because it doesn't account for prefix-needed?), but i've been
looking at this for too long
I've tested this on several different platforms, but it's still a pretty major
alteration to a function that i imagine sees a lot of traffic, so i'm a little
nervous about it. If someone else could try it out that might be a good idea
dana
diff --git a/Completion/Unix/Command/_man b/Completion/Unix/Command/_man
index 67810e1dc..14e9e75b5 100644
--- a/Completion/Unix/Command/_man
+++ b/Completion/Unix/Command/_man
@@ -1,16 +1,169 @@
#compdef man apropos whatis
+# Notes:
+# - Solaris is seemingly the only OS that doesn't allow the `man n page` syntax;
+# you must use `man -s n page`
+# - We assume that Linux distributions are using either man-db or mandoc
+# - @todo Option exclusivity isn't super accurate
+# - @todo Solaris man accepts a single hyphen as the first option to disable
+# paging (like AIX's -c); we don't support that
+# - @todo Linux apropos/whatis take options; we don't complete them yet
+
_man() {
- local dirs expl mrd awk
+ local dirs expl mrd awk variant noinsert
+ local -a context line state state_descr args modes
+ local -aU sects
+ local -A opt_args val_args sect_descs
- if (( $words[(I)-M] == (( $CURRENT - 1 )) )); then
- _directories && return 0
- fi
+ if [[ $service == man ]]; then
+ # We'll treat all mandoc-based systems (Alpine, various Illumos distros,
+ # etc.) as OpenBSD
+ _pick_variant -r variant openbsd='-S subsection' $OSTYPE ---
+
+ modes=(
+ -f -K -k -l -R -w -W
+ --apropos
+ --global-apropos
+ --local-file
+ --location
+ --location-cat
+ --recode
+ --whatis
+ --where
+ --where-cat
+ )
+ [[ $variant == darwin* ]] && modes+=( -t )
+
+ args=(
+ "(${(j< >)modes})"{-f,--whatis}'[display short description (like whatis)]'
+ "(${(j< >)modes})"{-k,--apropos}'[search for keyword (like apropos)]'
+ '(-M --manpath)'{-M+,--manpath=}'[specify manual search path]:manual search path:_sequence -s\: _directories'
+ )
+ if [[ $variant == (darwin|dragonfly|freebsd|linux)* ]]; then
+ args+=(
+ '(-a -S -s --all --sections)'{-a,--all}'[display all matching pages]'
+ '(-P --pager)'{-P+,--pager=}'[specify output pager]:pager:_path_commands'
+ # @todo Could enumerate these
+ '(-p --preprocessor)'{-p+,--preprocessor=}'[specify roff preprocessor sequence]:preprocessor sequence'
+ )
+ else
+ args+=( '(-s)-a[display all matching pages]' )
+ fi
+ [[ $variant == (aix|solaris)* ]] || args+=(
+ '(-C --config-file)'{-C+,--config-file=}'[specify configuration file]:configuration file:_files'
+ "(${(j< >)modes})"{-w,--path,--where}'[display file locations]'
+ )
+ [[ $variant == (aix|netbsd|openbsd)* ]] || args+=(
+ # @todo FreeBSD allows this to be given multiple times
+ '(-d --debug)'{-d,--debug}'[display debugging information]'
+ )
+ [[ $variant == (darwin|dragonfly|freebsd|linux|solaris|aix)* ]] && args+=(
+ '(-7 -H -t --ascii --html --troff)'{-t,--troff}'[format man page using troff]'
+ )
+ [[ $variant == (darwin|linux)* ]] && args+=(
+ "(${(j< >)modes})"{-K,--global-apropos}'[search for keyword in all pages]'
+ '(-m --systems)'{-m+,--systems=}'[search manual of specified system]:operating system'
+ )
+ [[ $variant == (darwin|dragonfly|freebsd)* ]] && args+=(
+ '(: -)-h[display help information]'
+ '(-a)-S+[specify manual sections to search]: :->sects'
+ )
+ [[ $variant == (dragonfly|freebsd)* ]] && args+=(
+ # @todo Could enumerate these
+ '-m[search manual of specified architecture]:architecture'
+ '-o[use non-localized man pages]'
+ )
+ [[ $variant == (netbsd|openbsd)* ]] && args+=(
+ '-c[disable paging]'
+ '-m[augment manual search path]:manual search path:_sequence -s\: _directories'
+ '(-a)-s+[specify manual section to search]: :->sects'
+ )
+ [[ $variant == linux* ]] && args+=(
+ '(: -)'{-\?,--help}'[display help information]'
+ '(-7 -t -H -T -Z --ascii --html --troff --troff-device --ditroff)'{-7,--asci}'[translate man pages for 7-bit terminal]'
+ '(-D --default)'{-D,--default}'[reset man to default options]'
+ # @todo Could enumerate these
+ '(-E --encoding)'{-E+,--encoding=}'[specify output encoding]:encoding'
+ '(-e --extension)'{-e+,--extension=}'[specify sub-extension]:sub-extension'
+ '(-H --html)'{-H-,--html=-}'[produce HTML output for specified browser]::Web browser:_path_commands'
+ '(-i -I --ignore-case --match-case)'{-i,--ignore-case}'[search case-insensitively]'
+ '(-i -I --ignore-case --match-case)'{-I,--match-case}'[search case-sensitively]'
+ '(-L --locale)'{-L+,--locale=}'[specify locale]:locale:_locales'
+ "(${(j< >)modes})"{-l+,--local-file=}'[format and display specified file]:*:::manual file:_files'
+ "!(${(j< >)modes})"{--location,--location-cat}
+ '--names-only[match only page names (with --regex or --wildcard)]'
+ '(--nh --no-hyphenation)'{--nh,--no-hyphenation}'[disable hyphenation]'
+ '(--nj --no-justification)'{--nj,--no-justification}'[disable justification]'
+ '--no-subpages[do not combine pairs of page names into single page name]'
+ # @todo Could enumerate these
+ "(${(j< >)modes})"{-R+,--recode=}'[output man page in specified encoding]:encoding'
+ '(-r --prompt)'{-r+,--prompt=}'[specify prompt for less]:less prompt'
+ '(-a --all --wildcard)--regex[treat page name as regular expression]'
+ '(-a -S -s --all --sections)'{-S+,-s+,--sections=}'[specify manual sections to search]: :->sects'
+ # @todo Could enumerate these
+ '(-T -t --troff --troff-device)'{-T-,--troff-device=-}'[specify roff output device]::roff output device'
+ '(-u --update)'{-u,--update}'[update database caches]'
+ '(: -)--usage[display brief usage information]'
+ '(: -)'{-V,--version}'[display version information]'
+ "(${(j< >)modes})"{-W,--where-cat}'[display cat file locations]'
+ '--warnings=[enable specified groff warnings]:groff warnings'
+ '(-a --all --regex)--wildcard[treat page name as shell glob]'
+ # @todo Could enumerate these
+ '(-X --gxditview)'{-X-,--gxditview=-}'[display output in gxditview using specified DPI (default: 75)]::DPI'
+ # @todo Post-process how?
+ '(-t --troff -Z --ditroff)'{-Z,--ditroff}'[post-process output for chosen device]'
+ )
+ [[ $variant == darwin* ]] && args+=(
+ # We use _files here because browsers are usually in /Applications, which
+ # typically isn't in PATH
+ '-B+[specify browser to use for HTML files]:Web browser:_files'
+ '-c[reformat source man page]'
+ # @todo -d should be exclusive with this above
+ '(-d)-D[display man page along with debugging information]'
+ '(-D -F --preformat)'{-F,--preformat}'[format man page only (do not display)]'
+ '-H+[specify command to render HTML as text]:HTML pager:_path_commands'
+ # --help and --version are undocumented but functional
+ '(: -)--help[display help information]'
+ # -s is also undocumented; it's provided for compatibility with Solaris
+ '!(-S)-s+: :->sects'
+ '(: -)'{-v,--version}'[display version information]'
+ "(${(j< >)modes})-W[display file locations, one per line, with no other information]"
+ )
+ [[ $variant == netbsd* ]] && args+=(
+ '-h[display only synopsis lines]'
+ '(: -)-p[display manual search path]'
+ '-S+[display only man pages with file names matching specified string]:search string'
+ )
+ [[ $variant == openbsd* ]] && args+=(
+ "(${(j< >)modes})-l+[format and display specified file]:*:::manual file:_files"
+ # @todo Could enumerate these
+ '-S[search manual of specified architecture]:architecture'
+ )
+ [[ $variant == solaris* ]] && args+=(
+ "(${(j< >)modes})-l[display file locations]"
+ '-r[format man page only (do not display)]'
+ '(-a)-s+[specify manual sections to search]: :->sects'
+ # @todo Does this in fact want a file path?
+ '-T+[format man page using specified macro package]:macro package:_files'
+ )
+ [[ $variant == aix* ]] && args+=(
+ '-c[display man page using cat]'
+ '-F[display only first matching entry]'
+ '-m[only search paths specified by -M/MANPATH]'
+ '-r[search remotely]'
+ )
- if [[ $service == man ]] && (( $words[(I)-l] + $words[(I)--local-file] )); then
- _files || return 0
+ # Strip (most) long options from non-Linux platforms
+ if [[ $variant == darwin* ]]; then
+ args=( ${(M)args:#((#s)|*\))(\*|)(-[^-]|--(help|path|pref|vers))*} )
+ elif [[ $variant != linux* ]]; then
+ args=( ${(M)args:#((#s)|*\))(\*|)-[^-]*} )
+ fi
fi
+ _arguments -s -S : $args '*::: :->man' && return 0
+ [[ -n $state ]] || return 1
+
if (( ! $#_manpath )); then
local mp
mp=( ${(s.:.)$(manpath 2>/dev/null)} )
@@ -23,14 +176,16 @@ _man() {
fi
(( $#_manpath )) ||
- _manpath=( /usr/man(-/) /(opt|usr)/(pkg|dt|share|X11R6|local)/(cat|)man(-/) )
+ _manpath=( /usr/man(-/) /(opt|usr)/(pkg|dt|share|X11R6|local)/(cat|)man(-/) )
- integer index=$words[(I)-M]
- if (( index )); then
- local opt
- opt=$words[index+1]
- _manpath=($opt)
- fi
+ # Override man path
+ [[ -n ${opt_args[-M]} ]] &&
+ _manpath=( ${(s<:>)opt_args[-M]} )
+
+ # Augment man path
+ [[ $variant == (netbsd|openbsd)* ]] &&
+ [[ -n ${opt_args[-m]} ]] &&
+ _manpath+=( ${(s<:>)opt_args[-m]} )
# `sman' is the SGML manual directory for Solaris 7.
# 1M is system administrator commands on SVR4
@@ -44,52 +199,158 @@ _man() {
# $sect_dirname is from the filesystem, the "3" in "/usr/share/man/man3"
# These are used by _man_pages
local sect sect_dirname
- if [[ $OSTYPE = solaris* ]]; then
- sect=${${words[(R)-s*]#-s}:-$words[$words[(i)-s]+1]}
- sect="${sect//,/|}"
- elif [[ -n ${sect:=$words[$words[(i)-S]+1]} || -n ${sect:=$MANSECT} ]]; then
- sect="${sect//:/|}"
- sect="${sect//,/|}"
- elif (( CURRENT > 2 )); then
- case $words[2] in
- (-a) sect='*';;
- (-*) ;;
- (*) sect=$words[2];;
- esac
+
+ # Take care: We can't use the sections from these options until we've finished
+ # completing them; otherwise (e.g.) -s1:<TAB> will give no results
+ if
+ [[ $service != man ]] || [[ $state == sects ]] || (( $+opt_args[-a] ))
+ then
+ sect='*'
+ elif
+ [[ $variant == (darwin|linux)* ]] &&
+ [[ -n ${opt_args[(i)-S|-s|--sections]} ]]
+ then
+ noinsert=1
+ sect=${opt_args[${opt_args[(i)-S|-s|--sections]}]//[:,]/|}
+ elif
+ [[ $variant == (netbsd|openbsd|solaris)* ]] && (( $+opt_args[-s] ))
+ then
+ noinsert=1
+ sect=${opt_args[-s]//,/|}
+ elif [[ $variant == (dragonfly|freebsd)* ]] && (( $+opt_args[-S] )); then
+ noinsert=1
+ sect=${opt_args[-S]//:/|}
+ elif (( CURRENT > 1 )) && [[ $variant != solaris* ]]; then
+ noinsert=1
+ sect=$words[1]
+ elif [[ -n ${sect:=$MANSECT} ]]; then
+ sect=${sect//:/|}
fi
- if [[ $sect = (<->*|1M|l|n) || $sect = *\|* ]]; then
- () {
- local -a sects=( ${(s.|.)sect} )
- if [[ $sect != (l|n) ]]; then
- sects=( ${sects%%[^0-9]#} )
- fi
- dirs=( $^_manpath/(sman|man|cat)${^sects}*/ )
- }
- if [[ $sect == *\|* ]]; then sect="($sect)"; fi
+ # Colons may have been escaped
+ sect=${(Q)sect}
+
+ if [[ $sect = (<->*|[lnopx]) || $sect = *\|* ]]; then
+ sects=( ${(s.|.)sect} )
+ dirs=( $^_manpath/(sman|man|cat)${^sects}/ )
+ sect=${(j<|>)sects}
+ [[ $sect == *'|'* ]] && sect="($sect)"
awk="\$2 == \"$sect\" {print \$1}"
else
+ sect=
dirs=( $^_manpath/(sman|man|cat)*/ )
awk='{print $1}'
fi
+
+ # Ignore directories with no pages inside
+ dirs=( ${^dirs}(#qFN) )
+
# Solaris 11 and on have a man-index directory that doesn't contain manpages
dirs=( ${dirs:#*/man-index/} )
- if [[ $OSTYPE = solaris* && ( $words[CURRENT] = -s* || $words[CURRENT-1] == -s ) ]]; then
- [[ $words[CURRENT] = -s* ]] && compset -P '-s'
- sects=( ${(o)${dirs##*(man|cat)}%/} )
- _wanted sections expl 'section' compadd -a sects
- elif zstyle -t ":completion:${curcontext}:manuals" separate-sections; then
- typeset -U sects
- local ret=1
+ sects=( ${(o)${dirs##*(man|cat)}%/} )
+
+ # If we've got this far, we can build our look-up table for descriptions of
+ # the more common sections. Unless otherwise labelled, the more specific ones
+ # come from Solaris or one of its variants
+ (( $#sects )) && () {
+ sect_descs=(
+ 0 'library headers'
+ 1 'general commands'
+ 1cups 'CUPS commands'
+ 1m 'maintenance commands'
+ 1openssl 'OpenSSL commands'
+ 2 'system calls'
+ 3 'library functions'
+ 3c 'C library functions'
+ 3curses 'curses library functions'
+ 3elf 'ELF library functions'
+ 3f 'Fortran library functions'
+ 3lua 'Lua features' # NetBSD
+ 3mail 'mailbox library functions'
+ 3openssl 'OpenSSL library functions'
+ 3pam 'PAM library functions'
+ 3pool 'pool configuration library functions'
+ 3proc 'process control library functions'
+ 3x11 'Xlib functions'
+ 3xcurses 'curses library functions [X/Open]'
+ 4 'devices and drivers'
+ 5 'file formats and conventions'
+ 3openssl 'OpenSSL configuration files'
+ 6 'games'
+ 7 'miscellanea'
+ 8 'maintenance commands and procedures'
+ 9 'kernel features'
+ 9lua 'Lua kernel bindings' # NetBSD
+ l 'local documentation' # AIX, etc.
+ n 'new documentation' # AIX, etc.
+ o 'old documentation' # AIX, etc.
+ p 'public documentation' # AIX, etc.
+ x 'X11 features'
+ )
+
+ # Add POSIX variants
+ for 1 in ${(k)sect_descs}; do
+ [[ $1 == <-> ]] || continue
+ sect_descs+=( "${1}p" "${sect_descs[$1]} [POSIX]" )
+ done
+
+ # Add OS-specific stuff that's too risky for or overrides the general list
+ [[ $OSTYPE == darwin* ]] && sect_descs+=( n 'Tcl/Tk features' )
+ [[ $OSTYPE == openbsd* ]] && sect_descs+=( 3p 'Perl features' )
+ [[ $OSTYPE == solaris* ]] && sect_descs+=(
+ 1t 'Tcl/Tk features'
+ 3m 'mathematical library functions'
+ 4 'file formats and conventions'
+ 5 'miscellanea'
+ 7 'special files'
+ 7d 'devices'
+ 7fs 'file systems'
+ 7i 'ioctl requests'
+ 7m 'STREAMS modules'
+ 7p 'protocols'
+ 9e 'driver entry points'
+ 9f 'driver functions'
+ 9p 'driver properties'
+ 9s 'driver data structures'
+ )
+ }
- sects=( ${(o)${dirs##*(man|cat)}%/} )
+ [[ $state == sects ]] && {
+ local s
+ local -a specs
+
+ (( $#sects )) || {
+ _message 'manual section'
+ return 1
+ }
+
+ # Build specs from descriptions
+ for s in $sects; do
+ specs+=( "${s}[${(b)sect_descs[$s]}]" )
+ done
+
+ if [[ $variant == (darwin|dragonfly|freebsd|linux)* ]]; then
+ _values -s : 'manual section' $specs
+ elif [[ $variant == solaris* ]]; then
+ _values -s , 'manual section' $specs
+ else
+ _values 'manual section' $specs
+ fi
+ return
+ }
+
+ if zstyle -t ":completion:${curcontext}:manuals" separate-sections; then
+ local d ret=1
(( $#sects )) || return 1
_tags manuals.${^sects}
while _tags; do
for sect_dirname in $sects; do
- _requested manuals.$sect_dirname expl "manual page, section $sect_dirname" _man_pages &&
+ d=$sect_dirname
+ (( $+sect_descs[$d] )) && d+=" (${sect_descs[$d]})"
+
+ _requested manuals.$sect_dirname expl "manual page, section $d" _man_pages &&
ret=0
done
(( ret )) || return 0
@@ -113,7 +374,7 @@ _man_pages() {
local pages sopt
# What files corresponding to manual pages can end in.
- local suf='.((?|<->*)(|.gz|.bz2|.Z|.lzma))'
+ local suf='.((?|<->*|ntcl)(|.gz|.bz2|.Z|.lzma))'
if [[ $PREFIX$SUFFIX = */* ]]; then
# Easy way to test for versions of man that allow file names.
@@ -138,8 +399,8 @@ _man_pages() {
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
- [[ $OSTYPE = solaris* ]] && sopt='-s '
- if ((CURRENT > 2)) ||
+ [[ $variant = solaris* ]] && sopt='-s '
+ if ((CURRENT > 1 || noinsert)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect_dirname" insert-sections
then
compadd "$@" - ${pages%$~suf}
Messages sorted by:
Reverse Date,
Date,
Thread,
Author