From 6b0f09ddb22440a02758a88dbab18f810d55fa41 Mon Sep 17 00:00:00 2001 From: Joe Bloggs Date: Fri, 14 Mar 2014 21:48:06 +0000 Subject: [PATCH] Completion for ps command, for iw and a how-to guide for creating completion files --- src/_iw | 247 +++++++++++++++++++++ src/_ps | 102 +++++++++ zsh-completions-howto.org | 440 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 789 insertions(+) create mode 100644 src/_iw create mode 100644 src/_ps create mode 100644 zsh-completions-howto.org diff --git a/src/_iw b/src/_iw new file mode 100644 index 0000000..3cc3c59 --- /dev/null +++ b/src/_iw @@ -0,0 +1,247 @@ +#compdef iw + +# Some parameters to hold patterns that will be used later. +local xp='[[:xdigit:]][[:xdigit:]]' +local -a bssid devices flags fouraddr fouronoff frequency high_throughput ifacename ifacetype ifacetypes +local -a matchany matchnum key lladdr lladdrs meshid meshidval ssid value +# matches any word +matchany=(/$'[^\0]##\0'/) +# matches any number +matchnum=(/$'[[:digit:]]##\0'/) +# matches any BSSID +bssid=($matchany ':bssid:BSSID:') +# matches any devices +devices=( /$'[[:alpha:]]##[[:digit:]]##(\.[[:digit:]]##)#\0'/ ':interfaces:network_interface:_net_interfaces' ) +# matches the word 'flags' followed by a list of some of the following words: none fcsfail control otherbss cook +flags=(/$'flags\0'/ ':flags:flags:(flags)' $matchany ':flags:flags:(none fcsfail control otherbss cook)' \#) +# matches the word '4addr' followed by either 'on' or 'off' +fouronoff=(/$'(on|off)\0'/ ':4addr:4addr:(on off)') +fouraddr=( /$'4addr\0'/ ':4addr:4addr:(4addr)' $fouronoff ) +# matches any number (description is 'frequency') +frequency=($matchnum ':freq:frequency:') +# matches one of HT20 HT40+ or HT40- +high_throughput=(/$'HT[0-9]##(+|-)#\0'/ ':HT:high throughput:(HT20 HT40+ HT40-)') +# matches any name (description is 'name') +ifacename=($matchany ':name:name:') +# matches the word 'type' followed by one of: managed ibss monitor mesh wds +ifacetypes=(/$'(managed|ibss|monitor|mesh|wds)\0'/ ':type:type:(managed ibss monitor mesh wds)') +ifacetype=(/$'type\0'/ ':type:type:(type)' $ifacetypes) +# matches any word (description is 'key') +key=($matchany ':key:key:') +# matches a MAC address (i.e. a sequence of six 2-digit numbers separated by spaces), $xp is defined earlier. +lladdr=(/"${xp}:${xp}:${xp}:${xp}:${xp}:${xp}"$'\0'/ ':lladdress:link layer (MAC) address:') +# as above but with a different description +lladdrs=(/"${xp}:${xp}:${xp}:${xp}:${xp}:${xp}"$'\0'/ ':lladdress:link layer (MAC) addresses (use - to match any octet):' \#) +# matches the word 'mesh_id' followed by a number +meshidval=($matchnum ':meshid:mesh ID:') +meshid=(/$'mesh_id\0'/ ':meshid:meshid:(mesh_id)' $meshidval) +# matches any word (description 'SSID') +ssid=($matchany ':ssid:SSID:') +# matches any word (description 'value') +value=($matchany ':val:value:') +# matches any number (description 'value') +numvalue=($matchnum ':val:value:') + +# Use the _regex_words function to create a variable containing command words to go after "iw reg" +local -a reg_cmds +_regex_words regulatory-commands "reg command" \ + 'g*et:print out the kernels current regulatory domain information'\ + 's*et:notify the kernel about the current regulatory domain' +reg_cmds=("$reply[@]") + +# Options for to go after "iw event" +local -a event_opts +_regex_words event-options "event option" \ + '-t:print timestamp' \ + '-r:print relative timstamp' \ + '-f:print full frame for auth/assoc etc' +event_opts=("$reply[@]") + +# Commands to go after "iw phy wowlan enable" +local -a phy_wowlan_enable_cmds +_regex_words phy-wowlan-enable-commands "phy WoWLAN enable commands" \ + '4*way-handshake:enable WoWLAN with 4way handshake trigger' \ + 'a*ny:enable WoWLAN with any trigger' \ + 'd*isconnect:enable WoWLAN with disconnect trigger' \ + 'e*ap-identity-request:enable WoWLAN with EAP identity request trigger' \ + 'g*tk-rekey-failure:enable WoWLAN with gtk rekey failure trigger' \ + 'm*agic-packet:enable WoWLAN with magic packet trigger' \ + 'p*atterns:MAC address triggers:$lladdrs' \ + 'r*fkill-release:enable WoWLAN with rfkill release trigger' +phy_wowlan_enable_cmds=("$reply[@]") + +# Commands to go after "iw phy wowlan" +local -a phy_wowlan_cmds +_regex_words phy-wowlan-commands "phy WoWLAN commands" \ + 's*how:show WoWLAN status' \ + 'd*isable:disable WoWLAN' \ + 'e*nable:enable WoWLAN:$phy_wowlan_enable_cmds' +phy_wowlan_cmds=("$reply[@]") + +# Some parameters to hold patterns that will be used for "iw phy set" commands +# (not perfect, but mostly OK). +local -a phy_set_antenna phy_set_channel phy_set_coverage phy_set_frag phy_set_freq phy_set_distance +local -a phy_set_name phy_set_netns phy_set_rts phy_set_txpower phy_txpower_opt +phy_set_antenna=($matchany ':antenna:bitmap:') +phy_set_channel=($matchnum ':channel:channel (1-14):' $high_throughput) +phy_set_coverage=($matchnum ':coverage:coverage class (0-255):') +phy_set_distance=($matchnum ':distance:valid values\: 0 - 114750:') +phy_set_frag=(/$'([0-9]##|off)\0'/ ':channel:channel (1-14):(1 2 3 4 5 6 7 8 9 10 11 12 13 14 off)') +phy_set_freq=($frequency $high_throughput) +phy_set_name=($matchany ':name:device name:') +phy_set_netns=($matchany ':netns:network namespace:') +phy_set_rts=($matchnum ':rts:rts threshold:') +phy_txpower_opt=($matchany ':txpower:tx power in mBm:') +_regex_words phy-set-txpower "set txpower" \ + 'a*uto:auto:' \ + 'f*ixed:fixed:$phy_txpower_opt' \ + 'l*imit:limit:$phy_txpower_opt' +phy_set_txpower=("$reply[@]") + +# Commands to go after "iw phy set" +local -a phy_set_cmds +_regex_words phy-set-commands "phy set commands" \ + 'a*ntenna:set a bitmap of allowed antennas to use for TX and RX:$phy_set_antenna' \ + 'ch*annel:set channel:$phy_set_channel' \ + 'co*verage:set coverage class (1 for every 3 usec of air propagation time):$phy_set_coverage' \ + 'd*istance:set appropriate coverage class for given link distance in meters:$phy_set_distance' \ + 'fra*g:set fragmentation threshold:$phy_set_frag' \ + 'fre*q:set frequency/channel the hardware is using, including HT configuration:$phy_set_freq' \ + 'na*me:rename this wireless device:$phy_set_name' \ + 'ne*tns:set network namespace:$phy_set_netns' \ + 'r*ts:set rts threshold:$phy_set_rts' \ + 't*xpower:specify transmit power level and setting type:$phy_set_txpower' +phy_set_cmds=("$reply[@]") + +# Commands to go after "iw phy interface" +local -a phy_interface_cmds +# This needs work (should not offer meshid, fouraddr of flags more than once, and need to accomodate multiple flag options) +phy_interface_cmds=(\( /$'add\0'/ ':add:add a new virtual interface with the given configuration:(add)' $ifacename $ifacetype \ + \( $meshid \| $fouraddr \| $flags \) \# \)) + +# Commands to go after "iw phy " +local -a phy_cmds +_regex_words phy-commands "phy command" \ + 's*et:set/configure interface properties:$phy_set_cmds' \ + 'inf*o:show capabilities for the specified wireless device' \ + 'int*erface:add a new virtual interface with the given configuration:$phy_interface_cmds' \ + 'w*owlan:WoWLAN commands:$phy_wowlan_cmds' +phy_cmds=("$reply[@]") + +# Parameters to hold patterns for dev commands +local -a dev_cmds_connect dev_cmds_cqm dev_cmds_get dev_cmds_ibss dev_cmds_interface dev_cmds_mesh dev_cmds_scan_options +local -a dev_cmds_mpath dev_cmds_offchannel dev_cmds_roc dev_cmds_scan dev_cmds_set dev_cmds_station dev_cmds_survey +dev_cmds_connect=($ssid $frequency $bssid $key) +dev_cmds_cqm=(/$'rssi\0'/ ':rssi:rssi:(rssi)' $matchnum ':thresh:threshold:' $matchnum ':hysteresis:hysteresis:') +dev_cmds_get=(/$'(mesh_param|power_save)\0'/ ':get:parameter:(mesh_param power_save)' $value) +# TODO: THIS NEEDS WORK! THE FINAL OPTIONS FOR JOIN NEED WORK +# dev ibss join [fixed-freq] [] [beacon-interval ] [basic-rates ] [mcast-rate ] [key d:0:abcde] +dev_cmds_ibss=(\( /$'leave\0'/ ':cmd:command:((leave:"Leave the IBSS cell"))' \| \( /$'join\0'/ ':cmd:command:((join\:"Join an IBSS cell"))' $ssid $frequency \( /$'fixed-freq\0'/ ':opt:option:((fixed-freq\:"fixed frequency (no args)"))' \| $bssid \| /$'beacon-interval\0'/ ':opt:option:((beacon-interval\:"beacon interval (takes single arg)"))' $numvalue \| /$'basic-rates\0'/ ':opt:option:((basic-rates\:"basic rates (comma separated list of rates)"))' $numvalue \| /$'mcast-rate\0'/ ':opt:option:((mcast-rate\:"multicast rate (takes single arg)"))' $numvalue \| $key \) \# \) \)) +dev_cmds_interface=(/$'add\0'/ ':add:add:(add)' $ifacename $ifacetype \( $meshid \| $fouraddr \| $flags \) \# ) +dev_cmds_mesh=(\( /$'leave\0'/ ':leave:leave a mesh:(leave)' \| /$'join\0'/ ':join:join a mesh:(join)' \ + $matchnum ':meshid:mesh ID:' $matchany ':parameter:mesh parameters [=]*:' \# \)) +dev_cmds_mpath=(\( /$'(del|get)\0'/ ':mpath:mesh path command:((del\:"remove the mesh path to the given node" \ +get\:"get information on mesh path to the given node"))' $lladdr \| /$'(new|set)\0'/ ':mpath:mesh path \ +command:((new\:"create a new mesh path (instead of relying on automatic discovery)" set\:"set an existing mesh \ +paths next hop"))' $lladdr /$'next_hop\0'/ ':nexthop:next hop:(next_hop)' $lladdr \| /$'dump\0'/ ':mpath:mesh path \ +command:((dump\:"list known mesh paths"))' \)) +dev_cmds_offchannel=($frequency $matchnum ':duration:duration:') +dev_cmds_roc=(/$'start\0'/ ':start:start:(start)' $frequency $matchnum ':time:time:') +dev_cmds_scan_options=(/$'freq\0'/ ':freq:freq:(freq)' $frequency $frequency \# /$'ies\0'/ ':ies:ies:(ies)' $lladdr \( /$'ssid\0'/ ':ssid:ssid:(ssid)' $ssid \# \| /$'passive\0'/ ':opt:passive:(passive)' \)) +dev_cmds_scan=(\( $dev_cmds_scan_options \| $matchany -'! [[ $match[1] =~ "dump|trigger" ]]' ':opt:option:((-u\:"include unknown data in results" \:""))' $dev_cmds_scan_options \| /$'dump\0'/ ':cmd:command:((dump\:"dump the current scan results"))' $matchany ':opt:option:((-u\:"include unknown data in results" \:""))' \| /$'trigger\0'/ ':cmd:command:((trigger\:"trigger a scan on the given frequencies with probing for the given SSIDs (or wildcard if not given) unless passive scanning is requested"))' $dev_cmds_scan_options \)) +local -a dev_cmds_set_bitrates dev_cmds_set_freq dev_cmds_set_mesh_param +local -a dev_cmds_set_monitor dev_cmds_set_peer dev_cmds_set_power_save +local -a dev_cmds_set_type dev_cmds_set_txpower +# dev set bitrates [legacy-<2.4|5> *] +dev_cmds_set_bitrates=(/$'legacy-*\0'/ ':opt:legacy:(legacy-2.4 legacy-2.5)' $matchnum ':rate:legacy rate in Mbps:') +# dev set freq [HT20|HT40+|HT40-] +dev_cmds_set_freq=($frequency $high_throughput) +# dev set mesh_param = [=]* +dev_cmds_set_mesh_param=( $matchany ':val:param=value:' \# ) +# dev set monitor * +dev_cmds_set_monitor=( $matchany ':flag:flag:((none\:"no special flags" fcsfail\:"show frames with FCS errors"\ + control\:"show control frames" otherbss\:"show frames from other BSSes" cook\:"use cooked mode"))' \# ) +# dev set peer +dev_cmds_set_peer=($lladdr) +# dev set power_save +dev_cmds_set_power_save=(/$'(on|off)\0'/ ':opt:power save mode:(on off)') + +_regex_words setcmds "dev set commands" \ + '4*addr:set interface 4addr (WDS) mode:$fouronoff'\ + 'b*itrates:set/clear specified rate masks:$dev_cmds_set_bitrates'\ + 'c*hannel:set channel:$phy_set_channel'\ + 'f*req:set frequency:$dev_cmds_set_freq'\ + 'mesh_param:set mesh parameters:$dev_cmds_set_mesh_param'\ + 'meshid:set mesh id:$meshidval'\ + 'mo*nitor:set monitor flags:$dev_cmds_set_monitor'\ + 'pe*er:set interface WDS peer MAC address:$dev_cmds_set_peer'\ + 'po*wer_save:set power save on/off:$dev_cmds_set_power_save'\ + 'tx*power:set transmission power:$phy_set_txpower'\ + 'ty*pe:set type:$ifacetypes' +dev_cmds_set=("$reply[@]") + +local -a dev_cmds_station_plink dev_cmds_station_vlan +dev_cmds_station_plink=(/$'(open|block)\0'/ ':opt::(open block)') +dev_cmds_station_vlan=($matchnum ':val:ifindex:') + +local -a dev_cmds_station_set +_regex_words stationsetcmds "dev station set commands"\ + 'plink_action:set peer link action:$dev_cmds_station_plink'\ + 'vlan:set AP VLAN:$dev_cmds_station_vlan' +dev_cmds_station_set=(\( $lladdr \) "$reply[@]") + +_regex_words stationcmds "dev station commands" \ + 'del:remove the given station entry (use with caution!):$lladdr'\ + 'dump:list all stations known, e.g. the AP on managed interfaces:'\ + 'get:get information for a specific station:$lladdr'\ + 'set:set AP VLAN or mesh peer link action:$dev_cmds_station_set' +dev_cmds_station=("$reply[@]") + +dev_cmds_survey=(/$'dump\0'/ ':dump:list all gathered channel survey data:(dump)') + +local -a dev_cmds +_regex_words dev-commands "dev commands" \ + 'co*nnect:join a network:$dev_cmds_connect' \ + 'cq*m:set connection quality monitor RSSI threshold:$dev_cmds_cqm' \ + 'de*l:remove this virtual interface' \ + 'di*sconnect:disconnect from the current network' \ + 'g*et:retrieve mesh parameter / power save state:$dev_cmds_get' \ + 'ib*ss:join/leave IBSS cell:$dev_cmds_ibss' \ + 'inf*o:show information for this interface' \ + 'int*erface:add an interface:$dev_cmds_interface' \ + 'l*ink:print information about the current link, if any' \ + 'me*sh:join/leave a mesh:$dev_cmds_mesh' \ + 'mp*ath:mesh path commands:$dev_cmds_mpath' \ + 'o*ffchannel:leave operating channel and go to the given channel for a while:$dev_cmds_offchannel' \ + 'r*oc:roc:$dev_cmds_roc' \ + 'sc*an:scan:$dev_cmds_scan' \ + 'se*t:set interface parameter:$dev_cmds_set' \ + 'st*ation:station commands:$dev_cmds_station' \ + 'su*rvey:list all gathered channel survey data:$dev_cmds_survey' +dev_cmds=( $devices "$reply[@]") + +# Arguments to _regex_arguments, built up in array $args. +local -a args reply +# Command word. Don't care what that is. +args=( $matchany ) + +local -a phydevs +phy_devs=( \( $(iw list|grep '^[[:alnum:]]\+'|cut -f 2 -d ' ') \) ) +phy_cmds=( \( $matchany ":test:test:$phy_devs[*]" \) "$phy_cmds[@]" ) + +_regex_words commands "iw command" \ + 'd*ev:commands to control/list the software devices:$dev_cmds' \ + 'e*vent:monitor events from the kernel:$event_opts' \ + 'h*elp:print usage for each command' \ + 'l*ist:list all wireless devices and their capabilities' \ + 'p*hy:commands to control the physical device:$phy_cmds' \ + 'r*eg:get/set regulatory domain:$reg_cmds' +args+=("$reply[@]") + +_regex_arguments _iw "${args[@]}" + +_iw "$@" + +# Local Variables: +# mode:shell-script +# End: diff --git a/src/_ps b/src/_ps new file mode 100644 index 0000000..a0755d4 --- /dev/null +++ b/src/_ps @@ -0,0 +1,102 @@ +#compdef ps + +# This works with procps version 3.2.8 + +local context state state_descr line +typeset -A opt_args +local filterexcl="(-A -a -C -d -e -g -G --group --Group -p --pid --ppid -s --sid -t --tty -u -U --user --User)" + +_arguments -s -w \ + "$filterexcl-A[all processes]"\ + "$filterexcl-a[all w/ tty except session leaders]"\ + "--cumulative[include some dead child process data (as a sum with the parent)]"\ + "(-f -F --format -l -o -O)--context[display security context format (for SE Linux)]"\ + "-c[show different scheduler information]"\ + "$filterexcl-C[by command name]:processes:->pname"\ + "(--columns)--cols[set screen width]:width:( )"\ + "(--cols)--columns[set screen width]:width:( )"\ + "--deselect[negate selection]"\ + "$filterexcl-d[all except session leaders]"\ + "$filterexcl-e[all processes]"\ + "--forest[ASCII art process tree]"\ + "(--context -F --format -o -O)-f[full format listing]"\ + "(--context -f --format -o -O)-F[extra full format]"\ + "(--context -f -F -j -l -o -O)--format[user-defined format]:output format:->format"\ + "$filterexcl-g[by session OR by effective group name]:groups:_groups"\ + "$filterexcl-G[by real group ID (supports names)]:groups:->rgid"\ + "$filterexcl--group[by session OR by effective group name]:groups:_groups"\ + "$filterexcl--Group[by real group ID (supports names)]:groups:->rgid"\ + "-H[show process hierarchy]"\ + "(--no-heading)--heading[repeat header lines, one per page of output]"\ + "-j[jobs format]"\ + "-l[long format. the -y option is often useful with this]"\ + "-L[show threads, possibly with LWP and NLWP columns]"\ + "-m[show threads after processes]"\ + "-M[add a column of security data]"\ + "-N[negate selection]"\ + "(--heading --header)--no-heading[omit header lines]"\ + "(--context -f -F --format -j -l)-o[user-defined format]:output format:->format"\ + "(--context -f -F --format -j -l)-O[preloaded -o (user-defined with some)]:output format:->format"\ + "$filterexcl-p[by process ID]:process IDs:->pid"\ + "$filterexcl--pid[by process ID]:process IDs:->pid"\ + "$filterexcl--ppid[select by parent process ID]:process IDs:->ppid"\ + "--rows[set screen height.]:height:( )"\ + "$filterexcl-s[by session IDs]:sessions:->sid"\ + "$filterexcl--sid[by session IDs]:sessions:->sid"\ + "--sort[specify sorting order]:sort specs:->sortspec"\ + "$filterexcl-t[by tty]:ttys:->tty"\ + "-T[show threads, possibly with SPID column]"\ + "$filterexcl--tty[by tty]:ttys:->tty"\ + "$filterexcl-u[by effective user ID (supports names)]:users:_users"\ + "$filterexcl-U[by real user ID (supports names)]:users:_users"\ + "$filterexcl--user[by effective user ID (supports names)]:users:_users"\ + "$filterexcl--User[by real user ID (supports names)]:users:_users"\ + "(--version)-V[print the procps version]"\ + "(-V)--version[print the procps version]"\ + "-w[wide output]"\ + "-y[use with -l, do not show flags, show rss in place of addr]"\ + "1:BSD-style options (complete - to see unix & gnu-style options):((T\:'all processes on this terminal' a\:'all w/ tty, including other users' r\:'only running processes' x\:'processes w/o controlling ttys' t\:'list by tty' j\:'BSD jobs control format' l\:'BSD long format' s\:'signal format' v\:'virtual memory format' u\:'user-oriented format' X\:'register format' L\:'list format codes' S\:'sum info of children into parents' c\:'true command name' n\:'numeric WCHAN & UID' H\:'show threads as if they were processes' U\:'by effective user ID' p\:'by process ID'))"\ + - debug "--info[print debugging info]" + +case "$state" in + pid) + _values -s , 'process id' "${(uonzf)$(ps -A o pid=)}" + ;; + rgid) + _values -s , 'process group id' "${(uonzf)$(ps -A o rgid=)}" + ;; + pname) + local ispat="pattern matching " + if (( ${+opt_args[-x]} )) + then + ispat="" + fi + if (( ${+opt_args[-f]} )) + then + _wanted pname expl $ispat'process command line' compadd ${(u)${(f)"$(ps -A o cmd=)"}} + else + _wanted pname expl $ispat'process name' compadd ${(u)${(f)"$(ps -A co cmd=)"}} + fi + ;; + sid) + _values -s , 'session id' "${(uonzf)$(ps -A o sid=)}" + ;; + tty) + local -a ttys + ttys=( /dev/tty*(N) /dev/pts/*(N) ) + _values -s , 'terminal device' ${ttys#/dev/} + ;; + ppid) + _values -s , 'parent process id' "${(uonzf)$(ps -A o ppid=)}" + ;; + sortspec) + _values -s , 'format specifier (prefix with - for decreasing order)' "${(uozf)$(ps L|cut -f 1 -d" ")}" + ;; + format) + _values -s , 'format specifier' "${(uozf)$(ps L|cut -f 1 -d" ")}" + ;; +esac + +# Local Variables: +# mode:shell-script +# End: diff --git a/zsh-completions-howto.org b/zsh-completions-howto.org new file mode 100644 index 0000000..d494a10 --- /dev/null +++ b/zsh-completions-howto.org @@ -0,0 +1,440 @@ +* Intro +The official documentation for writing zsh completion functions is difficult to understand, and doesn't give many examples. +At the time of writing this document I was able to find a few other tutorials on the web, however those tutorials only +explain a small portion of the capabilities of the completion system. This document aims to cover areas not explained elsewhere, +with examples, so that you can learn how to write more advanced completion functions. I do not go into all the details, but will +give enough information and examples to get you up and running. If you need more details you can look it up for yourself in the + [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System][official documentation]]. + +Please make any scripts that you create publically available for others (e.g. by forking this repo and making a [[id:64bcd501-b0f0-48c7-b8e2-07af708b95ec][pull request]]). +Also if you have any more information to add or improvements to make to this tutorial, please do. +* Getting started +** Telling zsh which function to use for completing a command +Completion functions for commands are stored in files with names beginning with an underscore _, and these files should +be placed in a directory listed in the $fpath variable. +You can add a directory to $fpath by adding a line like this to your ~/.zshrc file: +#+BEGIN_SRC sh +fpath=(~/newdir $fpath) +#+END_SRC +The first line of a completion function file can look something like this: +#+BEGIN_SRC sh +#compdef foobar +#+END_SRC +This tells zsh that the file contains code for completing the foobar command. +This is the format that you will use most often for the first line, but you can also use the same file for completing +several different functions if you want. See [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Autoloaded-files][here]] for more details. + +You can also use the compdef command directly (e.g. in your ~/.zshrc file) to tell zsh which function to use for completing +a command like this: +#+BEGIN_SRC sh +> compdef _function foobar +#+END_SRC +or to use the same completions for several commands: +#+BEGIN_SRC sh +> compdef _function foobar goocar hoodar +#+END_SRC +or if you want to supply arguments: +#+BEGIN_SRC sh +> compdef '_function arg1 arg2' foobar +#+END_SRC +See [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Functions-4][here]] for more details. +** Completing generic gnu commands +Many [[http://www.gnu.org/][gnu]] commands have a standardized way of listing option descriptions (when the --help option is used). +For these commands you can use the _gnu_generic function for automatically creating completions, like this: +#+BEGIN_SRC sh +> compdef _gnu_generic foobar +#+END_SRC +or to use _gnu_generic with several different commands: +#+BEGIN_SRC sh +> compdef _gnu_generic foobar goocar hoodar +#+END_SRC +This line can be placed in your ~/.zshrc file. +** Copying completions from another command +If you want a command, say cmd1, to have the same completions as another, say cmd2, which has already had +completions defined for it, you can do this: +#+BEGIN_SRC sh +> compdef cmd1=cmd2 +#+END_SRC +This can be useful for example if you have created an alias for a command to help you remember it. +* Writing your own completion functions +A good way to get started is to look at some already defined completion functions. +On my linux installation these are found in /usr/share/zsh/functions/Completion/Unix +and /usr/share/zsh/functions/Completion/Linux and a few other subdirs. + +You will notice that the _arguments function is used a lot in these files. +This is a utility function that makes it easy to write simple completion functions. +The _arguments function is a wrapper around the compadd builtin function. +The compadd builtin is the core function used to add completion words to the command line, and control its behaviour. +However, most of the time you will not need to use compadd, since there are many utility functions such as _arguments +and _describe which are easier to use. + +For very basic completions the _describe function should be adequate + +** Utility functions +Here is a list of some of the utility functions that may be of use. +The full list of utility functions, with full explanations, is available [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-Functions][here]]. +Examples of how to use these functions are given in the next section. + +*** main utility functions for overall completion +| _alternative | Can be used to generate completion candidates from other utility functions or shell code. | +| _arguments | Used to specify how to complete individual options & arguments for a command with unix style options. | +| _describe | Used for creating simple completions consisting of single words with descriptions (but no actions). Easier to use than _arguments | +| _gnu_generic | Can be used to complete options for commands that understand the `--help' option. | +| _regex_arguments | Creates a function for matching commandline arguments with regular expressions, and then performing actions/completions. | +*** functions for performing complex completions of single words +| _values | Used for completing arbitrary keywords (values) and their arguments, or comma separated lists of such combinations. | +| _combination | Used to complete combinations of values, for example pairs of hostnames and usernames. | +| _multi_parts | Used for completing multiple parts of words separately where each part is separated by some char, e.g. for completing partial filepaths: /u/i/sy -> /usr/include/sys | +| _sep_parts | Like _multi_parts but allows different separators at different parts of the completion. | +*** functions for completing specific types of objects +| _path_files | Used to complete filepaths. Take several options to control behaviour. | +| _files | Calls _path_files with all options except -g and -/. These options depend on file-patterns style setting. | +| _net_interfaces | Used for completing network interface names | +| _users | Used for completing user names | +| _groups | Used for completing group names | +| _options | Used for completing the names of shell options. | +| _parameters | Used for completing the names of shell parameters/variables (can restrict to those matching a pattern). | +*** functions for handling cached completions +If you have a very large number of completions you can save them in a cache file so that the completions load quickly. +| _cache_invalid | indicates whether the completions cache corresponding to a given cache identifier needs rebuilding | +| _retrieve_cache | retrieves completion information from a cache file | +| _store_cache | store completions corresponding to a given cache identifier in a cache file | +*** other functions +| _message | Used for displaying help messages in places where no completions can be generated. | +| _regex_words | Can be used to generate arguments for the _regex_arguments command. This is easier than writing the arguments manually. | +| _guard | Can be used in the ACTION of specifications for _arguments and similar functions to check the word being completed. | +*** Actions +Many of the utility functions such as _arguments, _regex_arguments, _alternative and _values may include an action +at the end of an option/argument specification. This action indicates how to complete the corresponding argument. +The actions can take one of the following forms: +| ( ) | Argument is required but no matches are generated for it. | +| (ITEM1 ITEM2) | List of possible matches | +| ((ITEM1\:'DESC1' ITEM2\:'DESC2')) | List of possible matches, with descriptions. Make sure to use different quotes than those around the whole specification. | +| ->STRING | Set $state to STRING and continue ($state can be checked in a case statement after the utility function call) | +| FUNCTION | Name of a function to call for generating matches or performing some other action, e.g. _files or _message | +| {EVAL-STRING} | Evaluate string as shell code to generate matches. This can be used to call a utility function with arguments, e.g. _values or _describe | +| =ACTION | Inserts a dummy word into completion command line without changing the point at which completion takes place. | +Not all action types are available for all utility functions that use them. For example the ->STRING type is not available in the +_regex_arguments or _alternative functions. +** Writing simple completion functions using _describe +The _describe function can be used for simple completions where the order and position of the options/arguments is +not important. You just need to create an array parameter to hold the options & their descriptions, and then pass +the parameter name as an argument to _describe. The following example creates completion candidates -c and -d, with +the descriptions (note this should be put in a file called _cmd in some directory listed in $fpath). +#+BEGIN_SRC sh +#compdef cmd +local -a options +options=('-c:description for -c opt' '-d:description for -d opt') +_describe 'values' options +#+END_SRC + +You can use several different lists separated by a double hyphen e.g. like this: +#+BEGIN_SRC sh +local -a options arguments +options=('-c:description for -c opt' '-d:description for -d opt') +arguments=('e:description for e arg' 'f:description for f arg') +_describe 'values' options -- arguments +#+END_SRC + +The _describe function can be used in an ACTION as part of a specification for _alternative, _arguments or _regex_arguments. +In this case you will have to put it in braces with its arguments, e.g. 'TAG:DESCRIPTION:{_describe 'values' options}' +** Writing completion functions using _alternative +Like _describe, this function performs simple completions where the order and position of options/arguments is not important. +However, unlike _describe, you can call execute shell code or call functions to obtain the completion candidates. + +As arguments it takes a list of specifications each in the form 'TAG:DESCRIPTION:ACTION' where TAG is a tag name, +DESCRIPTION is a description, and ACTION is one of the action types listed previously (apart from the ->STRING and =ACTION forms). +For example: +#+BEGIN_SRC sh +_alternative 'args:custom args:(a b c)' 'files:filenames:_files' +#+END_SRC +The first specification adds completion candidates a, b & c, and the second specification calls the _files function +for completing filepaths. + +We could split the specifications over several lines with \ and add descriptions to each of the custom args like this: +#+BEGIN_SRC sh +_alternative 'args:custom args:((a\:"description a" b\:"description b" c\:"description c"))'\ + 'files:filenames:_files' +#+END_SRC + +If we want to call _files with arguments we can put it in braces, like this: +#+BEGIN_SRC sh +_alternative 'args:custom args:((a\:"description a" b\:"description b" c\:"description c"))'\ + 'files:filenames:{_files -/}' +#+END_SRC + +To use parameter expansion to create our list of completions we must use double quotes to quote the specifications, +e.g: +#+BEGIN_SRC sh +_alternative "dirs:user directories:($userdirs)"\ + "pids:process IDs:($(ps -A o pid=))" +#+END_SRC +In this case the first specification adds the words stored in the $userdirs variable, and the second specification +evaluates 'ps -A o pid=' to get a list of pids to use as completion candidates. + +We can use other utility functions such as _values in the ACTION to perform more complex completions, e.g: +#+BEGIN_SRC sh +_alternative "dirs:user directories:($userdirs)"\ + 'opts:comma separated opts:{_values -s , a b c}' +#+END_SRC +this will complete the items in $userdirs, aswell as a comma separated list containing a, b &/or c. + +As with _describe, the _alternative function can itself be used in an ACTION as part of a specification for _arguments +or _regex_arguments. +** Writing completion functions using _arguments +With the _arguments function you can create more sophisticated completion functions. +Like the _alternative function, _arguments takes a list of specification strings as arguments. +These specification strings can be for specifying options and any corresponding option arguments (e.g. -f filename), +or command arguments. + +Basic option specifications take the form '-OPT[DESCRIPTION]', e.g. like this: +#+BEGIN_SRC sh +_arguments '-s[sort output]' '--l[long output]' '-l[long output]' +#+END_SRC +Arguments for the option can be specified after the option description in this form '-OPT[DESCRIPTION]:MESSAGE:ACTION', +where MESSAGE is a message to display and ACTION can be any of the forms mentioned in the ACTIONS section above. +For example: +#+BEGIN_SRC sh +_arguments '-f[input file]:filename:_files' +#+END_SRC + +Command argument specifications take the form 'N:MESSAGE:ACTION' where N indicates that it is the Nth command argument, +and MESSAGE & ACTION are as before. If the N is omitted then it just means the next command argument (after any that have +already been specified). If a double colon is used at the start (after N) then the argument is optional. +For example: +#+BEGIN_SRC sh +_arguments '-s[sort output]' '1:first arg:_net_interfaces' '::optional arg:_files' ':next arg:(a b c)' +#+END_SRC +here the first arg is a network interface, the next optional arg is a file name, the last arg can be either a, b or c, +and the -s option may be completed at any position. + +The _arguments function allows the full set of ACTION forms listed in the ACTION section above. +This means that you can use actions for selecting case statement branches like this: +#+BEGIN_SRC sh +_arguments '-m[music file]:filename:->files' '-f[flags]:flag:->flags' +case "$state" in + files) + local -a music_files + music_files=( Music/**/*.{mp3,wav,flac,ogg} ) + _multi_parts / music_files + ;; + flags) + _values -s , 'flags' a b c d e + ;; +esac +#+END_SRC +In this case paths to music files are completed stepwise descending down directories using the _multi_parts function, +and the flags are completed as a comma separated list using the _values function. + +I have just given you the basics of _arguments specifications here, you can also specify mutually exclusive options, +repeated options & arguments, options beginning with + insead of -, etc. For more details see the [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System][official documentation]]. +Also have a look at the tutorials mentioned at the end of this document, and the completion functions in the [[https://github.com/vapniks/zsh-completions/tree/master/src][src directory]]. +** Writing completion functions using _regex_arguments and _regex_words +If you have a complex command line specification with several different possible argument sequences then +the _regex_arguments function may be what you need. + +_regex_arguments creates a completion function whose name is given by the first argument. +Hence you need to first call _regex_arguments to create the completion function, and then call that function, +e.g. like this: +#+BEGIN_SRC sh +_regex_arguments _cmd OTHER_ARGS.. +_cmd "$@" +#+END_SRC + +The OTHER_ARGS should be sequences of specifications for matching & completing words on the command line. +These sequences can be separated by '|' to represent alternative sequences of words. +You can use bracketing to arbitrary depth to specify alternate subsequences, but the brackets must be backslashed like this \( \) +or quoted like this '(' ')'. + +For example: +#+BEGIN_SRC sh +_regex_arguments _cmd SEQ1 '|' SEQ2 \( SEQ2a '|' SEQ2b \) +_cmd "$@" +#+END_SRC +this specifies a command line matching either SEQ1, or SEQ2 followed by SEQ2a or SEQ2b. + +Each specification in a sequence must contain a / PATTERN/ part at the start followed by an optional ':TAG:DESCRIPTION:ACTION' +part. + +Each PATTERN is a regular expression to match a word on the command line. These patterns are processed sequentially +until we reach a pattern that doesn't match at which point any corresponding ACTION is performed to obtain completions +for that word. Note that there needs to be a pattern to match the initial command itself. +See below for further explanation about PATTERNs. + +The ':TAG:DESCRIPTION:ACTION' part is interpreted in the same way as for the _alternative function specifications, +except that it has an extra : at the start, and now all of the possible ACTION formats listed previously are allowed. + +Here is an example: +#+BEGIN_SRC sh +_regex_arguments _hello /$'[^\0]##\0'/ \( /$'word1(a|b|c)\0'/ ':word:first word:(word1a word1b word1c)' '|'\ + /$'word11(a|b|c)\0'/ ':word:first word:(word11a word11b word11c)' \( /$'word2(a|b|c)\0'/ ':word:second word:(word2a word2b word2c)'\ + '|' /$'word22(a|b|c)\0'/ ':word:second word:(word22a word22b word22c)' \) \) +_cmd "$@" +#+END_SRC +in this case the first word can be word1 or word11 followed by an a, b or c, and if the first word contains 11 then a second +word is allowed which can be word2 followed by and a, b, or c, or a filename. + +If this sounds too complicated a much simpler alternative is to use the _regex_words function for creating +specifications for _regex_arguments. +*** Patterns +You may notice that the / PATTERN/ specs in the previous example don't look like normal regular expressions. +Often a string parameter in the form $'foo\0' is used. This is so that the \0 in the string is interpreted correctly +as a null char which is used to separate words in the internal representation. If you don't include the \0 at the end +of the pattern you may get problems matching the next word. If you need to use the contents of a variable in a pattern, +you can double quote it so that it gets expanded and then put a string parameter containing a null char afterwards, +like this: "$somevar"$'\0' + +The regular expression syntax for patterns seems to be a bit different from normal regular expressions, +and I can't find documentation anywhere. +However I have managed to work out what the following special chars are for: +| * | wildcard - any number of chars | +| ? | wildcard - single char | +| # | zero or more of the previous char (like * in a normal regular expression) | +| ## | one or more of the previous char (like + in a normal regular expression) | +*** _regex_words +The _regex_words function makes it much easier to create specifications for _regex_arguments. +The results of calling _regex_words can be stored in a variable which can then be used instead +of a specification for _regex_arguments. + +To create a specification using _regex_words you supply it with a tag followed by a description followed by a list +of specifications for individual words. These specifications take the form 'WORD:DESCRIPTION:SPEC' where WORD is the +word to be completed, DESCRIPTION is a description for it, and SPEC can be another variable created by _regex_words +specifying words that come after the current word or blank if there are no further words. +For example: +#+BEGIN_SRC sh +_regex_words firstword 'The first word' 'word1a:a word:' 'word1b:b word:' 'word1c:c word' +#+END_SRC +the results of this function call will be stored in the $reply array, and so we should store it in another array +before $reply gets changed again, like this: +#+BEGIN_SRC sh +local -a firstword +_regex_words word 'The first word' 'word1a:a word:' 'word1b:b word:' 'word1c:c word' +firstword="$reply[@]" +#+END_SRC +we could then use it with _regex_arguments like this: +#+BEGIN_SRC sh +_regex_arguments _cmd /$'[^\0]##\0'/ "$firstword[@]" +_cmd "$@" +#+END_SRC +Note that I have added an extra pattern for the initial command word itself. + +Here is a more complex example where we call _regex_words for different words on the command line +#+BEGIN_SRC sh +local -a firstword firstword2 secondword secondword2 +_regex_words word1 'The second word' 'woo:tang clan' 'hoo:not me' +secondword=("$reply[@]") +_regex_words word2 'Another second word' 'yee:thou' 'haa:very funny!' +secondword2=("$reply[@]") +_regex_words commands 'The first word' 'foo:do foo' 'man:yeah man' 'chu:at chu' +firstword=("$reply[@]") +_regex_words word4 'Another first word' 'boo:scare somebody:$secondword' 'ga:baby noise:$secondword'\ + 'loo:go to the toilet:$secondword2' +firstword2=("$reply[@]") + +_regex_arguments _hello /$'[^\0]##\0'/ "${firstword[@]}" "${firstword2[@]}" +_hello "$@" +#+END_SRC +In this case the first word can be one of "foo", "man", "chu", "boo", "ga" or "loo". +If the first word is "boo" or "ga" then the second word can be "woo" or "hoo", +and if the first word is "loo" then the second word can be "yee" or "haa", in the other +cases there is no second word. + +For a good example of the usage of _regex_words have a look at the _ip function. +** complex completions with _values, _sep_parts, & _multi_parts +The _values, _sep_parts & _multi_parts functions can be used either on their own, or as ACTIONs in specifications for +_alternative, _arguments or _regex_arguments. The following examples may be instructive. +See the [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System][official documentation]] for more info. + +Space separated list of mp3 files: +#+BEGIN_SRC sh +_values 'mp3 files' ~/*.mp3 +#+END_SRC + +Comma separated list of session id numbers: +#+BEGIN_SRC sh +_values -s , 'session id' "${(uonzf)$(ps -A o sid=)}" +#+END_SRC + +Completes foo@news:woo, or foo@news:laa, or bar@news:woo, etc: +#+BEGIN_SRC sh +_sep_parts '(foo bar)' @ '(news ftp)' : '(woo laa)' +#+END_SRC + +Complete some MAC addresses one octet at a time: +#+BEGIN_SRC sh +_multi_parts : '(00:11:22:33:44:55 00:23:34:45:56:67 00:23:45:56:67:78)' +#+END_SRC + +** Adding completion words directly using compadd +For more fine grained control you can use the builtin compadd function to add completion words directly. +This function has many different options for controlling how completions are displayed and how text on the command line +can be altered when words are completed. Read the [[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System][official documentation]] for full details. +Here I just give a few simple examples. + +Add some words to the list of possible completions: +#+BEGIN_SRC sh +compadd foo bar blah +#+END_SRC + +As above but also display an explanation: +#+BEGIN_SRC sh +compadd -X 'Some completions' foo bar blah +#+END_SRC + +As above but automatically insert a prefix of "what_" before the completed word: +#+BEGIN_SRC sh +compadd -P what_ foo bar blah +#+END_SRC + +As above but automatically insert a suffix of "_todo" after the completed word: +#+BEGIN_SRC sh +compadd -S _todo foo bar blah +#+END_SRC + +As above but automatically remove the "_todo" suffix if a blank char is typed after the suffix: +#+BEGIN_SRC sh +compadd -P _todo -q foo bar blah +#+END_SRC + +Add words in array $wordsarray to the list of possible completions +#+BEGIN_SRC sh +compadd -a wordsarray +#+END_SRC + +* Testing & debugging +To reload a completion function: +#+BEGIN_SRC sh +> unfunction _func +> autoload -U _func +#+END_SRC + +The following functions can be called to obtain useful information. +If the default keybindings don't work you can try pressing Alt+x and then enter the command name. +| Function | Default keybinding | Description | +|-----------------+--------------------+--------------------------------------------------------------------------------------------------------------------------------| +| _complete_help | Ctrl+x h | displays information about context names, tags, and completion functions used when completing at the current cursor position | +| _complete_help | Alt+2 Ctrl+x h | as above but displays even more information | +| _complete_debug | Ctrl+x ? | performs ordinary completion, but captures in a temporary file a trace of the shell commands executed by the completion system | +* Gotchas (things to watch out for) +Remember to include a #compdef line at the beginning of the file containing the completion function. + +Take care to use the correct type of quoting for specifications to _arguments or _regex_arguments: +use double quotes if there is a parameter that needs to be expanded in the specification, single quotes otherwise, +and make sure to use different quotes around item descriptions. + +Check that you have the correct number of :'s in the correct places for specifications for _arguments, +_alternative, _regex_arguments, etc. + +Remember to include an initial pattern to match the command word when using _regex_arguments (it does not need a matching action). + +Remember to put a null char $'\0' at the end of any PATTERN argument for _regex_arguments +* Tips +Sometimes you have a situation where there is just one option that can come after a subcommand, and zsh will complete this +automatically when tab is pressed after the subcommand. If instead you want it listed with its description before completing +you can add another empty option (i.e. \:) to the ACTION like this ':TAG:DESCRIPTION:((opt1\:"description for opt1" \:))' +Note this only applies to utility functions that use ACTIONs in their specification arguments (_arguments, _regex_arguments, etc.) + +* Other resources +[[http://wikimatze.de/writing-zsh-completion-for-padrino.html][Here]] is a nicely formatted short tutorial showing basic usage of the _arguments function, +and [[http://www.linux-mag.com/id/1106/][here]] is a slightly more advanced tutorial using the _arguments function. +[[http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System][Here]] is the zshcompsys man page.