This commit is contained in:
Jeremy Melanson 2026-06-15 11:51:43 +02:00 committed by GitHub
commit 901f1f02b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 277 additions and 0 deletions

40
plugins/iw/README.md Normal file
View File

@ -0,0 +1,40 @@
# iw plugin
This plugin adds tab completion for [`iw`](https://wireless.wiki.kernel.org/en/users/documentation/iw),
the standard Linux command-line tool for configuring wireless network interfaces.
To use it, add `iw` to the plugins array in your zshrc file:
```zsh
plugins=(... iw)
```
## Completion
Completion is generated dynamically by parsing the output of `iw help` and cached for
performance. The cache is stored in `$ZSH_CACHE_DIR/_iw_cache` and is automatically
regenerated when the installed version of `iw` changes.
The following command structure is completed:
| Level | Examples |
| ----- | -------- |
| Top-level commands | `dev`, `phy`, `wdev`, `reg`, `list`, `event`, … (discovered dynamically) |
| Interface / phy names | `wlan0`, `phy0` (discovered from `/sys/class/net` and `/sys/class/ieee80211`) |
| Subcommands | `dev <if> scan`, `dev <if> station`, `phy <phy> set`, … |
| Sub-subcommands | `dev <if> scan dump`, `dev <if> station get`, `phy <phy> set txpower`, … |
## Functions
| Function | Description |
| -------- | ----------- |
| `iw-clear-cache` | Delete the cached completion data (useful after upgrading `iw`) |
## Requirements
- `iw` must be installed and in `$PATH`
- Completion for a differently-named `iw` binary can be enabled with:
```zsh
compdef _iw my-iw-binary
```

220
plugins/iw/_iw Normal file
View File

@ -0,0 +1,220 @@
#compdef iw
#
# ZSH completion for iw (Linux wireless configuration utility).
# Parses 'iw help' output to discover commands and caches the result.
#
# $service is used as the iw binary name. This allows this completion to work
# for differently-named iw binaries: compdef _iw my-iw-binary
# Parse 'iw help' output and write a sourceable cache file.
_iw_build_cache() {
local iw_cmd="$1"
local version="$2"
local cache_file="$3"
local help_output
help_output=$($iw_cmd help 2>&1) || return 1
local -a top_cmds dev_subcmds phy_subcmds wdev_subcmds reg_subcmds
local -A dev_subs phy_subs wdev_subs
local line
local -a words lines
# Single pass: collect top-level commands, subcmds, and sub-subcommands.
# words[4] entries starting with '<', '[', or '-' are placeholders, not completions.
lines=(${(f)help_output})
for line in $lines; do
words=(${=line})
[[ $line == $'\t'[a-z]* && $line != $'\t\t'* ]] && top_cmds+=($words[1])
case $words[1] in
dev)
if [[ $words[2] == '<devname>' && $words[3] == [a-z]* ]]; then
dev_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) dev_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
phy)
if [[ $words[2] == '<phyname>' && $words[3] == [a-z]* ]]; then
phy_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) phy_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
wdev)
if [[ $words[2] == '<idx>' && $words[3] == [a-z]* ]]; then
wdev_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) wdev_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
reg)
[[ $words[2] == [a-z]* ]] && reg_subcmds+=($words[2])
;;
esac
done
# (ou): sorted + unique, replacing sort -u
top_cmds=(${(ou)top_cmds})
dev_subcmds=(${(ou)dev_subcmds})
phy_subcmds=(${(ou)phy_subcmds})
wdev_subcmds=(${(ou)wdev_subcmds})
reg_subcmds=(${(ou)reg_subcmds})
local subcmd subs_str
local -a subs_arr tmp_arr
{
print "# iw completion cache"
print "# version: ${version}"
print "_iw_top_cmds=(${top_cmds[*]})"
print "_iw_dev_subcmds=(${dev_subcmds[*]})"
for subcmd in $dev_subcmds; do
if [[ -n "${dev_subs[$subcmd]}" ]]; then
tmp_arr=(${=dev_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_dev_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_phy_subcmds=(${phy_subcmds[*]})"
for subcmd in $phy_subcmds; do
if [[ -n "${phy_subs[$subcmd]}" ]]; then
tmp_arr=(${=phy_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_phy_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_wdev_subcmds=(${wdev_subcmds[*]})"
for subcmd in $wdev_subcmds; do
if [[ -n "${wdev_subs[$subcmd]}" ]]; then
tmp_arr=(${=wdev_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_wdev_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_reg_subcmds=(${reg_subcmds[*]})"
} >| "$cache_file"
}
_iw() {
local context state line curcontext="$curcontext"
integer ret=1
# Declare cache variables; populated by sourcing the cache file below.
local -a _iw_top_cmds _iw_dev_subcmds _iw_phy_subcmds _iw_wdev_subcmds _iw_reg_subcmds
local -A _iw_dev_subsubcmds _iw_phy_subsubcmds _iw_wdev_subsubcmds
local cache_file="${ZSH_CACHE_DIR:-${HOME}/.cache}/_iw_cache"
local current_version ver_output
ver_output=$(${service} --version 2>&1)
[[ $ver_output =~ '([0-9]+\.[0-9]+)' ]] && current_version=$match[1] || current_version=""
local cache_ver="" _l1 _l2
if [[ -f "$cache_file" ]]; then
{ IFS= read -r _l1; IFS= read -r _l2; } < "$cache_file" 2>/dev/null
cache_ver=${_l2#\# version: }
fi
if [[ $cache_ver != $current_version ]]; then
_iw_build_cache "${service}" "$current_version" "$cache_file"
fi
source "$cache_file" 2>/dev/null
_arguments -C \
'--debug[enable netlink debugging]' \
'(- 1 2 3 4)--version[show version]' \
'1:: :->cmd' \
'2:: :->arg2' \
'3:: :->arg3' \
'4:: :->arg4' \
&& return 0
case "$state" in
cmd)
local -A _iw_cmd_descs
_iw_cmd_descs=(
dev 'network interface commands'
phy 'wireless hardware commands'
wdev 'wireless device commands'
reg 'regulatory domain commands'
list 'list all wireless devices and their capabilities'
event 'monitor kernel events'
features 'list supported features'
commands 'list all known commands'
help 'show command usage'
)
local -a top_cmds
local _cmd
for _cmd in $_iw_top_cmds; do
if [[ -n "${_iw_cmd_descs[$_cmd]}" ]]; then
top_cmds+=("${_cmd}:${_iw_cmd_descs[$_cmd]}")
else
top_cmds+=("$_cmd")
fi
done
_describe -t commands 'iw command' top_cmds && ret=0
;;
arg2)
case "$line[1]" in
dev)
local -a wlan_devs
wlan_devs=(/sys/class/net/*/phy80211(N:h:t))
compadd -a wlan_devs && ret=0
;;
phy)
local -a phy_devs
phy_devs=(/sys/class/ieee80211/*(N:t))
compadd -a phy_devs && ret=0
;;
reg)
compadd -a _iw_reg_subcmds && ret=0
;;
esac
;;
arg3)
case "$line[1]" in
dev) compadd -a _iw_dev_subcmds && ret=0 ;;
phy) compadd -a _iw_phy_subcmds && ret=0 ;;
wdev) compadd -a _iw_wdev_subcmds && ret=0 ;;
esac
;;
arg4)
local -a subs
case "$line[1]" in
dev) subs=(${=_iw_dev_subsubcmds[$line[3]]}) ;;
phy) subs=(${=_iw_phy_subsubcmds[$line[3]]}) ;;
wdev) subs=(${=_iw_wdev_subsubcmds[$line[3]]}) ;;
esac
(( ${#subs} > 0 )) && compadd -a subs && ret=0
;;
esac
return ret
}
_iw "$@"

17
plugins/iw/iw.plugin.zsh Normal file
View File

@ -0,0 +1,17 @@
# iw Oh-My-ZSH plugin
# Provides tab completion for iw (Linux wireless configuration utility).
if (( ! $+commands[iw] )); then
return
fi
# Remove the cached iw completion data (useful after upgrading iw).
iw-clear-cache() {
local cache_file="${ZSH_CACHE_DIR:-${HOME}/.cache}/_iw_cache"
if [[ -f "$cache_file" ]]; then
rm -f "$cache_file"
print "iw completion cache cleared."
else
print "No iw completion cache found."
fi
}