@@@@@@@@ @@@@@@ @@@ @@@ @@@@@@@@ @@@ @@@ @@@@@@@
@@@@@@@@ @@@@@@@ @@@ @@@ @@@@@@@@ @@@ @@@ @@@@@@@@
@@! !@@ @@! @@@ @@! @@! !@@ @@! @@@
!@! !@! !@! @!@ !@! !@! @!! !@! @!@
@!! !!@@!! @!@!@!@! @!@!@!@!@ @!!!:! !@@!@! @!@@!@!
!!! !!@!!! !!!@!!!! !!!@!@!!! !!!!!: @!!! !!@!!!
!!: !:! !!: !!! !!: !: :!! !!:
:!: !:! :!: !:! :!: :!: !:! :!:
:: :::: :::: :: :: ::: :: :::: :: ::: ::
: :: : : :: : : : : : : :: :: : :: :
@@@@@@ @@@ @@@ @@@@@@@
@@@@@@@@ @@@@ @@@ @@@@@@@@
@@! @@@ @@!@!@@@ @@! @@@
!@! @!@ !@!!@!@! !@! @!@
@!@!@!@! @!@ !!@! @!@ !@!
!!!@!!!! !@! !!! !@! !!!
!!: !!! !!: !!! !!: !!!
:!: !:! :!: !:! :!: !:!
:: ::: :: :: :::: ::
: : : :: : :: : :
Jack into your shell. Let the machine think faster than you can type.
- What Is This
- Demo
- Neural Expansion Core
- Expansion Rules
- Supported Prefixes and Flags
- Bypass Protocols
- Configuration Matrix
- Key Bindings
- Custom Corrections
- Versus the Competition
- Tabstop Snippets
- Self-Referential Alias Escape
- Correct-Then-Expand
- Expand Inside Quotes
- Suffix Alias Expansion
- Autopair Integration
- History Injection
- Debug Widget
- Expansion Preview
- Expansion Stats
- Command-Position Parser
- Performance
- Test Coverage
- Install
- The Monster Chain
The world's most powerful zsh expansion plugin. Intercepts your spacebar and expands everything in its path -- regular aliases, global aliases, suffix aliases, misspellings, globs, history, parameters, and more. No pipes. No external commands. Pure zsh. Sub-millisecond. 11,000+ tests.
gco<space> => git checkout
sudo gco<space> => sudo git checkout
teh<space> => the
# su, stdbuf, and friends — all parsed:
su -l root gco<space> => su -l root git checkout
stdbuf -oL gco<space> => stdbuf -oL git checkout
su root nice -n 10 nohup gco<space> => ...git checkout
# deep prefix chain — all flags parsed, alias still expanded:
nocorrect time -p command sudo -kE -u root env -0iv -C /tmp \
nice -n 10 rlwrap -acN -f comp nohup gco<space>
=> ...git checkout
# extreme stack — proxy, auth, env, debug, scheduling, sandbox,
# process control, namespace, NUMA, OOM, buffering, all with flags:
torify sudo -kE -u root su -l deploy \
env -0iv -C /tmp \
strace -f -e trace=network stdbuf -oL \
nsenter -t 1 -m -n numactl -C 0-3 \
ionice -c 2 chrt -f 10 choom -n -500 \
taskset -c 0 nice -n 10 caffeinate -i setsid -f \
flock /tmp/lock timeout 30 unbuffer rlwrap -acN \
prlimit --nofile=4096 cpulimit -l 50 \
nohup time -v gco<space>
=> ...git checkout
| Layer | What It Does |
|---|---|
| Alias Expansion | Expands regular aliases in command position and after sudo, env, builtin, command, exec, eval, noglob, nocorrect, nice, nohup, rlwrap, time and arbitrary combos of these with full flag support |
| Global Alias Expansion | Expands global aliases anywhere on the command line |
| Spelling Correction | 290+ built-in misspelling/abbreviation corrections -- teh -> the, cmd -> command, bg -> background -- user-extensible via associative array |
| Native Expansion | Globs, $parameters, $(command substitution), =(process substitution), !history expansion via zle expand-word |
| Tabstop Snippets | Aliases with $ZPWR_TABSTOP placeholders act as templates -- cursor jumps to the placeholder on expansion |
| Self-Referential Alias Escape | alias git="hub" expands to \hub -- backslash-escapes the first word to prevent infinite recursion |
| Correct-Then-Expand | Typo correction chains into alias expansion in a single keypress |
| Quote-Aware Expansion | Optionally expands aliases inside "double" or 'single' quoted strings in argument position (not command position) |
| Suffix Alias Expansion | file.txt<space> -> vim file.txt -- expands suffix aliases (alias -s) at command position |
| Autopair Integration | Detects autopair and delegates space insertion to preserve bracket/quote auto-pairing |
| History Injection | Optionally writes the fully-expanded form of your command into history |
| Debug Widget | Ctrl+\ shows parser state -- prefix chain, command position, expansion action -- without modifying the line |
| Expansion Preview | Ghost text shows what an alias would expand to before you press space -- like fish autosuggestions but for alias expansion |
| Expansion Stats | Tracks every expansion to a stats file -- zpwrExpandStats renders a cyberpunk dashboard with top aliases, keystroke savings, and bar charts |
COMMAND POSITION
alias in first word position => expanded
alias after sudo/env/builtin/etc => expanded (case-insensitive: SUDO, Sudo, sUdO — works on macOS/Windows)
word is a real command/function => NOT corrected (valid commands are left alone)
GLOBAL
global alias anywhere on line => expanded
misspelling anywhere on line => corrected
globs, $params, history => expanded
CURSOR RULES
cursor directly after word => expand
one space after word (menuselect) => expand
two spaces after word => bypass
Shell builtins/keywords (must precede external wrappers):
| Prefix | Flags | Example |
|---|---|---|
nocorrect |
— | nocorrect gco |
time |
-p -l -v |
time -plv gco |
- |
— | - gco |
builtin |
— | builtin gco |
command |
-p |
command -p gco |
exec |
-c -l -a NAME |
exec -cl -a name gco |
eval |
— | eval gco |
noglob |
— | noglob gco |
coproc |
— | coproc gco |
External prefix commands:
| Prefix | Combo flags | Flag-with-arg | Example |
|---|---|---|---|
sudo |
-A -B -b -E -e -H -i -K -k -l -L -N -n -P -S -s -V -v |
-C N -D DIR -g GRP -h HOST -p PROMPT -R DIR -T SEC -U USER -u USER -- |
sudo -kE -u root gco |
doas |
-L -n -s |
-u USER -C CFG -- |
doas -n -u root gco |
env |
-0 -i -v |
-a ARG -C DIR -P PATH -S STR -u VAR -- |
env -0iv -C /tmp -u HOME gco |
nice |
— | -n ADJ -ADJ |
nice -n 10 gco |
time |
-p -l -v |
— | time -v gco |
nohup |
— | — | nohup gco |
rlwrap |
-a -c -E -h -i -I -m -n -N -o -R -r -U -v -W -X |
-b CHARS -C NAME -D N -e CHAR -f FILE -g REGEX -H FILE -l FILE -M EXT -O REGEX -p COLOR -P INPUT -q CHARS -s N -S PROMPT -t NAME -w MS -z FILTER |
rlwrap -acN -f comp -s 500 gco |
timeout |
-f -p -v |
-k DUR -s SIG |
timeout -k 10 30 gco |
strace |
-A -c -C -d -D -f -F -h -i -k -n -N -q -r -t -T -v -V -w -x -y -Y -z -Z |
-a COL -b SYS -e EXPR -E VAR -I N -o FILE -O N -p PID -P PATH -s SZ -S BY -u USER -U COL -X FMT |
strace -cf -s 256 gco |
ltrace |
-b -c -C -f -h -i -L -r -S -t -T -V |
-a COL -A N -D MASK -e FILTER -F PATH -l LIB -n NR -o FILE -p PID -s SZ -u USER -w NR -x FILTER |
ltrace -f -l libfoo gco |
ionice |
-t |
-c CLASS -n LEVEL |
ionice -c 2 -n 7 gco |
caffeinate |
-d -i -m -s -u |
-t SEC -w PID |
caffeinate -i gco |
setsid |
-c -f -w |
— | setsid -f gco |
chrt |
-a -b -d -e -f -i -m -o -p -r -R -v |
-D NS -P NS -T NS |
chrt -f 10 gco |
taskset |
-a -c -p |
— | taskset -c 0-3 gco |
watch |
-b -C -c -d -e -f -g -p -r -t -w -x |
-n INTERVAL -q CYCLES -s DIR |
watch -d -n 1 gco |
flock |
-e -F -n -o -s -u -x |
-c CMD -w SEC -E N |
flock -w 5 /tmp/lock gco |
chroot |
— | — | chroot /path gco |
runuser |
-f -l -m -p -P -T |
-c CMD -g GRP -s SHELL -G GRP -u USER -w LIST |
runuser -u deploy gco |
unshare |
-c -C -f -i -m -n -p -r -T -u -U --fork --map-root-user --map-current-user --map-auto --map-subids --keep-caps --mount-proc[=MP] --mount-binfmt[=MP] --kill-child[=SIG] --mount[=F] --uts[=F] --ipc[=F] --net[=F] --pid[=F] --user[=F] --cgroup[=F] --time[=F] |
-R DIR -w DIR -S UID -G GID -l FILE --propagation MODE --setgroups ALLOW|DENY --map-user UID --map-users MAP --map-group GID --map-groups MAP --owner UID:GID --monotonic OFF --boottime OFF -- |
unshare -p --fork --mount-proc gco |
cpulimit |
-v -i -z |
-e EXE -l LIMIT -p PID |
cpulimit -l 50 gco |
su |
-f -l -m -p -P -T |
-c CMD -s SHELL -g GRP -G GRP -w LIST -- |
su -l root gco |
stdbuf |
— | -i MODE -o MODE -e MODE |
stdbuf -oL gco |
sg |
— | — | sg staff gco |
choom |
— | -n ADJ -p PID |
choom -n -1000 gco |
nsenter |
-a -c -C -e -F -i -m -n -p -r -T -u -U -w -W -Z |
-t PID -S UID -G GID -N FD |
nsenter -t 1 -m gco |
numactl |
-a -b -l -s -H |
-i NODES -C CPUS -N NODES -m NODES -p PID -w NODES -P NODES |
numactl -C 0,1 gco |
prlimit |
— | -o COLS -p PID --RESOURCE=LIMIT |
prlimit --nofile=1024 gco |
setpriv |
-d |
--reuid UID --regid GID --groups LIST --inh-caps CAPS --ambient-caps CAPS --bounding-set CAPS --securebits BITS --selinux-label LBL --apparmor-profile PRF --pdeathsig SIG --no-new-privs --clear-groups --keep-groups --init-groups --reset-env |
setpriv --reuid=1000 --inh-caps=-all gco |
setarch |
-v -3 -B -F -I -L -R -S -T -X -Z |
-p PID |
setarch i386 -R gco |
linux32 linux64 |
-v -3 -B -F -I -L -R -S -T -X -Z |
-p PID |
linux32 -R gco |
runcon |
-c |
-u USER -r ROLE -t TYPE -l RANGE -- |
runcon -t httpd_t gco |
xvfb-run |
-a -l |
-e FILE -f FILE -n NUM -p PROTO -s ARGS -w SEC -- |
xvfb-run -a gco |
chpst |
-v -V -P -0 -1 -2 |
-u USER -U USER -b ARGV0 -e DIR -/ ROOT -n INC -l LOCK -L LOCK -m BYTES -d BYTES -o N -p N -f BYTES -c BYTES -t SEC |
chpst -u www -m 200000 gco |
cgexec |
— | -g CTRL:PATH --sticky |
cgexec -g cpu:mygroup gco |
trickle |
-s -v |
-d RATE -u RATE -w LEN -t SEC -l LEN -n PATH -P PATH |
trickle -d 100 -u 50 gco |
faketime |
-f -m |
-p PID |
faketime '2020-01-01' gco |
proot |
-0 |
-r PATH -b SRC:DST -m SRC:DST -q CMD -w DIR -v LVL -k REL -i UID:GID -R PATH -S PATH |
proot -0 -r /jail gco |
bwrap |
--unshare-* --clearenv --new-session --die-with-parent --as-pid-1 |
--bind SRC DST --ro-bind SRC DST --dev-bind SRC DST --setenv VAR VAL --tmpfs DST --proc DST --dev DST --uid UID --gid GID |
bwrap --ro-bind / / --unshare-net gco |
capsh |
--print --noamb --noenv --quiet |
--caps=TXT --drop=LIST --uid=UID --gid=GID --user=NAME --chroot=PATH --shell=PATH -- |
capsh --drop=cap_net_raw -- gco |
valgrind |
-d -h -q -s -v |
--tool=NAME --log-file=FILE --*=VAL |
valgrind --tool=memcheck gco |
fakeroot |
-h -u -v |
-b FD -i FILE -l LIB -s FILE -- |
fakeroot gco |
unbuffer |
-p |
— | unbuffer -p gco |
chronic |
-e -v |
— | chronic -v gco |
torsocks |
-6 -d -h -i -q |
-a ADDR -p PASS -P PORT -u USER |
torsocks gco |
proxychains4 |
-q |
-f FILE |
proxychains4 -q gco |
daemonize |
-a -v |
-c DIR -e FILE -E VAR -l FILE -o FILE -p FILE -u USER |
daemonize -u www gco |
firejail |
— | -c CMD --*=VAL --* |
firejail --private gco |
sem |
— | -j N -P N --*=VAL --* |
sem -j 4 gco |
systemd-run |
-d -G -h -P -q -r -R -S -t -T -v |
-C CAP -E VAR -H HOST -M MACH -p PROP -u UNIT --*=VAL --* |
systemd-run -t gco |
nocache |
— | -n N |
nocache gco |
fakechroot |
-h -s -v |
-b DIR -c DIR -d LINKER -e ENV -l LIB |
fakechroot gco |
ccache |
-c -C -h -p -s -v -V -x -z |
-d PATH -F NUM -k KEY -M SIZE -o KEY=VAL -X LVL |
ccache gco |
distcc |
-j |
— | distcc gco |
pkexec |
— | -u USER --user USER --disable-internal-agent --keep-cwd |
pkexec -u admin gco |
torify |
-v |
— | torify gco |
dbus-run-session |
— | --config-file FILE --dbus-daemon BIN --*=VAL -- |
dbus-run-session gco |
dbus-launch |
--sh-syntax --csh-syntax --auto-syntax --binary-syntax --close-stderr --exit-with-session --exit-with-x11 |
--autolaunch=ID --config-file=FILE |
dbus-launch gco |
eatmydata |
— | -- |
eatmydata -- gco |
tsocks catchsegv |
— | — | tsocks gco |
All prefixes support \escaped, 'single-quoted', and "double-quoted" forms. sudo/doas/env/nice/time/nohup/rlwrap are case-insensitive. Variable assignments (X=1, PATH=/usr/bin) are stripped automatically at any position:
X=1 sudo gco => X=1 sudo git checkout
sudo X=1 env Y=2 gco => sudo X=1 env Y=2 git checkout
env FOO=bar BAZ=qux gco => env FOO=bar BAZ=qux git checkout
PATH=/usr/bin LANG=C sudo gco => PATH=/usr/bin LANG=C sudo git checkout
Ctrl+Space -- insert a literal space, no expansion.
Blacklist -- permanently exclude aliases from expansion:
export ZPWR_EXPAND_BLACKLIST=(g gco)# -- CORE --
export ZPWR_EXPAND=true # alias expansion in first position
export ZPWR_EXPAND_SECOND_POSITION=true # alias expansion after sudo/env
export ZPWR_EXPAND_NATIVE=true # expand globs, history, $params
# -- CORRECTION --
export ZPWR_CORRECT=true # spelling correction
export ZPWR_CORRECT_EXPAND=true # expand aliases after correction
# -- QUOTES --
export ZPWR_EXPAND_QUOTE_DOUBLE=true # expand inside "double quotes"
export ZPWR_EXPAND_QUOTE_SINGLE=false # expand inside 'single quotes'
# -- HISTORY --
export ZPWR_EXPAND_TO_HISTORY=false # inject expanded form into history
export ZPWR_EXPAND_PRE_EXEC_NATIVE=true # expand globs on accept-line
# -- SUFFIX --
export ZPWR_EXPAND_SUFFIX=true # expand suffix aliases (alias -s)
# -- PREVIEW --
export ZPWR_EXPAND_PREVIEW=false # ghost text showing pending expansion
# -- BLACKLIST --
export ZPWR_EXPAND_BLACKLIST=(g gco) # aliases to never expand| Key | Action |
|---|---|
Space |
Supernatural expand + insert space |
Ctrl+Space |
Insert literal space (bypass) |
Ctrl+Opt+E |
Debug widget -- show parser state without expanding |
The correction dictionary is a plain associative array. Add your own entries after the plugin loads, then rebuild the O(1) reverse lookup:
# map misspellings to the correct word
ZPWR_EXPAND_CORRECT_WORDS[kubernetes]="k8ss kuberntes kuberneets"
ZPWR_EXPAND_CORRECT_WORDS[terraform]="terrafrom terrafomr"
# append to an existing built-in entry
ZPWR_EXPAND_CORRECT_WORDS[echo]+=" oech"
# remove a built-in correction
unset 'ZPWR_EXPAND_CORRECT_WORDS[background]'
# rebuild the reverse lookup table after any changes
zpwrExpandRebuildCorrectReverseThe key is the correct word, the value is a space-separated list of misspellings. Use underscores in keys to expand to spaces (e.g. hello_world expands to hello world).
| Feature | zsh-expand | zsh-abbr | zsh-abbrev-alias | globalias |
|---|---|---|---|---|
Uses native alias / global alias |
yes | no (own abbr cmd) |
no (own abbrev-alias cmd) |
yes |
| Expands after 62 prefix commands with flags | yes | no | no | no |
| Full parser for prefix/flag/arg stripping | yes | no | no | no |
Context-aware VAR=val vs flag-arg handling |
yes | no | no | no |
Positional arg depth limits (su gco != command) |
yes | no | no | no |
| Spelling correction (290+ built-in) | yes | no | no | no |
| User-extensible corrections | yes | no | no | no |
| No correction of valid commands | yes | n/a | no | no |
| Glob / history / param expansion | yes | no | no | yes |
| Suffix alias expansion | yes | no | no | no |
| Tabstop snippets (cursor placement) | yes | no | no | no |
| Self-referential alias escape | yes | no | no | no |
| Correct-then-expand chaining | yes | no | no | no |
| History injection | yes | no | no | no |
| Live expansion preview (ghost text) | yes | no | no | no |
| Expand inside quotes | yes | no | no | no |
| Autopair integration | yes | no | no | no |
| Case-insensitive prefix matching | yes | no | no | no |
| Blacklist / filter | yes | n/a | no | yes |
| Test suite | 11,000+ | yes | no | no |
| Pure zsh (no external deps) | yes | yes | yes | ohmyzsh |
| Active (2026) | yes | yes | slow (2024) | stale (2020) |
Define aliases with $ZPWR_TABSTOP placeholders to turn them into IDE-style snippets. On expansion, the cursor jumps to the placeholder instead of the end of the line:
alias gc="git commit -m ${ZPWR_TABSTOP}"
alias gca="git commit --amend -m ${ZPWR_TABSTOP}"gc<space> => git commit -m | (cursor here)
No other zsh expansion plugin supports cursor placement on expansion.
If an alias expands to something whose first word is the alias name itself, zsh-expand backslash-escapes it to prevent infinite recursion:
alias git="hub"git<space> => \hub (escaped, won't re-expand)
Other plugins either infinite-loop or silently break on this.
When ZPWR_CORRECT_EXPAND=true, typo correction chains into alias expansion in a single keypress:
The spelling corrector fires first, then the corrected word is re-checked for alias expansion -- all before the space character is inserted.
Optionally expand aliases inside quoted strings in argument position. Quoted words at command position are never expanded.
export ZPWR_EXPAND_QUOTE_DOUBLE=true # expand inside "double quotes"
export ZPWR_EXPAND_QUOTE_SINGLE=true # expand inside 'single quotes'echo "gco<space>" => echo "git checkout " (argument — expanded)
"gco"<space> => "gco" (command position — not expanded)
When ZPWR_EXPAND_SUFFIX=true, suffix aliases (alias -s) are expanded at command position:
alias -s txt=vim
alias -s py=python
alias -s json=jqfile.txt<space> => vim file.txt
script.py<space> => python script.py
./data.json<space> => jq ./data.json
Works with history injection -- the expanded form is saved to history on accept-line.
If zsh-autopair is loaded, zsh-expand delegates space insertion to autopair-insert so bracket and quote auto-pairing is preserved during expansion.
When ZPWR_EXPAND_TO_HISTORY=true:
After running a command with unexpanded aliases, the expanded form is saved into history (up 2 lines), while the unexpanded form appears up 1 line and is never written to $HISTFILE. Same expansion rules as spacebar -- sudo gco becomes sudo git checkout in history.
Enable native expansion on accept-line:
export ZPWR_EXPAND_PRE_EXEC_NATIVE=truePress Ctrl+\ to inspect the parser's view of the current line without expanding anything:
┌── zsh-expand debug ────────────────┐
│ input: sudo -kE -u root gco │
│ words: 3 [sudo -kE -u root gco] │
│ prefix: sudo -kE -u root │
│ command: gco │
│ lastword: gco │
│ action: alias -> git checkout │
│ valid: no (gco not found) │
└────────────────────────────────────┘
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Shows the parsed prefix chain, identified command position, what expansion would fire, and whether the command word exists. Useful for debugging why something isn't expanding or verifying the parser is consuming flags correctly.
Ghost text shows what the last word would expand to as you type, before pressing space:
gco → git checkout
The preview appears below the prompt. Press space to commit the expansion, or keep typing to dismiss it. Supports regular aliases, global aliases, suffix aliases, and spelling corrections.
Disabled by default (conflicts with zle -M output from other plugins). Enable with:
export ZPWR_EXPAND_PREVIEW=trueNo other zsh expansion plugin shows a live preview of pending expansions.
Every expansion is logged to a stats file. Run zpwrExpandStats to see a cyberpunk dashboard:
███████╗███████╗██╗ ██╗ ███████╗██╗ ██╗██████╗ █████╗ ███╗ ██╗██████╗
╚══███╔╝██╔════╝██║ ██║ ██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗████╗ ██║██╔══██╗
███╔╝ ███████╗███████║█████╗█████╗ ╚███╔╝ ██████╔╝███████║██╔██╗ ██║██║ ██║
███╔╝ ╚════██║██╔══██║╚════╝██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██║██║╚██╗██║██║ ██║
███████╗███████║██║ ██║ ███████╗██╔╝ ██╗██║ ██║ ██║██║ ╚████║██████╔╝
╚══════╝╚══════╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝
┌── EXPANSION STATS ─────────────────────────────────────────────────────┐
│ ── TRIGGER ────────────────────────────────────────────── │
│ SPACE EXPANSIONS: 124 │
│ HISTORY EXPANSIONS: 12 │
│ │
│ ── TYPE ───────────────────────────────────────────────── │
│ ALIAS: 136 │
│ GLOBAL ALIAS: 0 │
│ SUFFIX ALIAS: 0 │
│ SELF-REF ESCAPE: 0 │
│ NATIVE (glob/hist): 0 │
│ CORRECTIONS: 5 │
│ │
│ ── SUMMARY ────────────────────────────────────────────── │
│ TOTAL EXPANSIONS: 136 │
│ KEYSTROKES SAVED: 1040 │
│ │
│ ── TOP ALIASES ────────────────────────────────────────── │
│ 1. gco ████████████████████ 59 // git checkout │
│ 2. gs ██████████░░░░░░░░░░ 31 // git status │
│ 3. gd ███████░░░░░░░░░░░░░ 22 // git diff │
│ 4. ga █████░░░░░░░░░░░░░░░ 15 // git add │
│ 5. gp ███░░░░░░░░░░░░░░░░░ 9 // git push │
│ │
│ >>> YOUR ALIASES ARE WORKING FOR YOU <<< │
└────────────────────────────────────────────────────────────────────────┘
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Stats persist permanently across sessions in $ZPWR_EXPAND_STATS_FILE (defaults to $ZPWR_LOCAL, $XDG_CACHE_HOME, or ~/.cache). The parent directory of that file is created automatically on first write (fresh CI runners and minimal HOME often have no ~/.cache yet). The dashboard lists the top aliases (default count from ZPWR_EXPAND_STATS_TOP, overridable with zpwrExpandStats -t) with proportional bar charts and each alias’s expansion. Each line is tagged S: (spacebar expansion) or H: (history accept-line / non-space trigger) so space and history paths are tallied separately.
Previous versions used a single POSIX extended regex to match prefix commands and their flags. This worked for simple cases but couldn't scale -- every new command and flag combination made the regex longer and harder to debug. Flag arguments containing = (like strace -e trace=network) were incorrectly stripped as variable assignments.
The current architecture uses a left-to-right parser (zpwrExpandParserFindCommandPosition) that walks the word array and understands shell grammar. Shared helpers _zpwr_bare (strip quotes/backslash for token comparison) and _zpwr_is_assignment (NAME=value detection) are defined once at file scope in zpwrExpandParser.zsh, not recreated on every parse.
Phase 1: consume shell keywords (nocorrect, time -p, builtin, command -p, exec -cl, eval, noglob, coproc, -)
Phase 2: consume privilege-escalation prefixes (sudo, doas, su, ...) and command wrappers (env, nice, strace, timeout, ionice, caffeinate, ...)
Result: everything remaining is the command + arguments
Each command has its own case branch that knows exactly which flags take arguments and which positional args are mandatory. This means:
strace -e trace=network gco-- the parser knows-etakes an argument, sotrace=networkis consumed as a flag value, not stripped as a variable assignment.gcoexpands correctly.env -i FOO=bar gco-- the parser knowsFOO=barafterenvis an environment variable, not a shell assignment.gcoexpands correctly.sudo -kE -u root gco-- the parser knows-utakes an argument (root), so it consumes both.gcoexpands correctly.su gco-- the parser knowsgcois the USER argument, not a command.gcodoes not expand. Butsu root gcoexpands correctly becauserootfills the USER slot andgcois at command position.- Same for
timeout gco(DURATION),chroot gco(PATH),taskset gco(MASK),chrt gco(PRIORITY),sg gco(GROUP),flock gco(FILE) -- mandatory positional args are never mistaken for commands.
Adding a new prefix command is a single case branch:
mynewcmd)
(( pos++ ))
while (( pos <= $#words )); do
_zpwr_bare "$words[$pos]"
case $REPLY in
-[vq]*) (( pos++ )) ;; # combo flags
-[of]) (( pos++ )); (( pos <= $#words )) && (( pos++ )) ;; # flag with arg
*) break ;;
esac
done
;;The parser exposes two results for downstream consumers:
| Variable | Contents | Example |
|---|---|---|
ZPWR_VARS[cachedRegexMatch] |
tail: command + args after all prefixes | gco arg1 arg2 |
ZPWR_VARS[cachedParserPrefix] |
everything consumed as prefix | sudo -kE -u root env -i |
Source files can be compiled to .zwc bytecode for instant loading (zcompile or via your plugin manager). The expansion hot path uses zero external commands and zero subshells -- no sed, awk, grep, or $(...). Every spacebar expansion runs in pure zsh builtins and parameter expansion, keeping latency invisible on every keypress.
When ZPWR_EXPAND=false, the space/accept-line widget returns immediately after clearing any expansion preview: it does not run zpwrExpandParseWords or the command-position parser, so a disabled plugin adds essentially no parse cost on each keypress.
Benchmarks (Apple Silicon, 10,000 iterations):
| Scenario | Per call |
|---|---|
file.txt (suffix alias) |
~31 µs |
sudo file.txt (prefix + suffix) |
~130 µs |
gco (regular alias) |
~36 µs |
Human perception threshold is ~100ms -- these are 770-3200x below that.
Stress test scaling (Apple Silicon, full prefix chain per loop):
| Input size | Tokens | Expand | Time |
|---|---|---|---|
| 7 KB | 1.4K | YES | 22 ms |
| 69 KB | 14K | YES | 536 ms |
| 343 KB | 70K | YES | 10 sec |
| 687 KB | 140K | YES | 39 sec |
| 1.3 MB | 280K | YES | 2.5 min |
| 3.4 MB | 700K | YES | 15.5 min |
The parser never fails -- it just gets slower on inputs no human would ever type. The bottleneck is zsh's (z) word splitter, not the parser itself. Normal command lines are under 1KB and expand in under 1ms.
zpwrExpandParseWords avoids re-running (z) on the whole statement partition when only a copy of the word array is needed; in argument position (quote stripping on the last token), it re-tokenizes only that last word instead of join-splitting the entire line again. Assignment filtering on the partition (FOO=bar, --flag=value) is a single native glob expansion rather than a per-element loop.
The command-position parser short-circuits the overwhelmingly common case (plain commands with no shell keyword or wrapper prefix: git status, ls -la /tmp, vim foo) via an inline precheck on the first word. Both the Phase 1 keyword loop and the Phase 2 wrapper loop would bail immediately on such input via their O(1) hash lookup anyway, so the fast path replicates those two hash lookups inline and skips the array copy, the _zpwr_bare calls, and the loop machinery entirely. In practice this drops parser latency by ~35% on non-prefixed lines; prefixed lines (sudo, env, time ...) still run the full loop and are unaffected.
How deep can you actually chain? The parser handles all of it instantly -- the OS is the limit:
| Command | Max depth | Limiting factor |
|---|---|---|
sudo sudo sudo ... gco |
~512 | PTY allocation (sudo needs a pseudo-terminal for auth) |
env env env ... gco |
~50,000-100,000 | ARG_MAX (1MB kernel limit on execve() argument list) |
| Parser (LBUFFER) | unlimited | Heap memory only -- no kernel limit on zsh's line editor |
A large zunit suite (11,000+ discrete @test blocks). Exact totals change frequently — print them from the repo root with:
zsh scripts/count-tests.zshzunitWhen the plugin is loaded without the full zpwr environment, zpwrLogDebug and zpwrLogConsoleErr are stubbed (the latter prints its message to stderr). Core functions declare local variables at function entry so nested branches do not redeclare them — a zsh quirk that can otherwise leak assignments to stdout.
source "$HOME/.zinit/bin/zinit.zsh"
zinit ice lucid nocompile
zinit load MenkeTechnologies/zsh-expandcd "$HOME/.oh-my-zsh/custom/plugins" && git clone https://github.com/MenkeTechnologies/zsh-expand.git# ~/.zshrc
plugins+=(zsh-expand)git clone https://github.com/MenkeTechnologies/zsh-expand.gitSource zsh-expand.plugin.zsh in ~/.zshrc.
No other expansion plugin can do this. 12 shell builtin permutations up front, then every one of the 62 command wrapper commands duplicated with different flag combos. strace with all 38 flags and ltrace with all 25 flags. Variable assignments scattered everywhere. Shell builtins come first (they only exist inside zsh), then command wrappers chain freely. The parser consumes the entire prefix and gco expands to git checkout:
nocorrect time -p command -p builtin eval noglob coproc \
exec -cl -a p1 \
nocorrect builtin eval noglob coproc time -l command -p \
nocorrect noglob builtin eval coproc time -v command \
builtin nocorrect eval noglob coproc \
eval nocorrect noglob builtin coproc \
noglob nocorrect builtin eval coproc time -p command -p \
exec -c -a p2 builtin eval nocorrect noglob coproc \
coproc nocorrect noglob builtin eval time -l command -p \
builtin eval nocorrect noglob coproc time -v command -p \
eval noglob nocorrect builtin coproc \
noglob coproc nocorrect builtin eval time -p command \
nocorrect eval noglob builtin coproc \
torify -v sudo -elNVvkE -D /chdir -U other -u root \
su -flmpPT -c cmd deploy \
env -a argv0 -0iv -C /tmp VAR1=a VAR2=b \
doas -Lns -u ops env FOO=bar BAZ=qux LANG=C \
sudo -ABbEHiKkNnPS -g wheel -h h1 -p pw -R /ch -D /d1 \
-T 60 -U deploy -u admin \
su -flmT root env -i HOME=/r1 PATH=/b1 TERM=x1 \
doas -s -C /etc/d1 env -u DISPLAY TERM=xterm-256 \
sudo -kKi -u nobody env -v -C /var CC=gcc CFLAGS=-O2 \
sudo -kE -u daemon env -0i -C /opt RUST_LOG=debug \
doas -Ln -u _www env -i SHELL=/bin/zsh EDITOR=vim \
torsocks -diq6 -a 127.0.0.1 -P 9050 -u tor -p pass \
sudo -kE -u test env -i GOPATH=/go \
proxychains4 -q -f /etc/pc.conf \
su -s /bin/zsh -g staff root stdbuf -i 0 -o L -e 0 \
nice -n 10 -- nohup -- nice -n 5 nohup nice -n 1 nohup \
nice -n 19 nohup nice -n 15 nohup nice -n 12 nohup \
rlwrap -acEhiImnNoRrUvWX -b x -C name -D 2 -e ch \
-f /d1 -g pat -H /h1 -l log -M ext -O re -P inp \
-p red -q ch -s 500 -S p1 -t term -w 50 -z filt -- \
timeout -fpv -k 5 -s TERM 30 \
strace -AcCdDfFhiknNqrtTvVwxyYzZ -a 80 -b execve \
-e trace=network -E LD -I 1 -o /t1 -O 50 -p 1 -P /proc \
-s 256 -S time -u strace -U 80 -X raw -- \
ltrace -bcCfhiLrStTV -a 60 -A 10 -D 0xff -e malloc \
-F /etc/ltrace.conf -l libfoo -n 2 -o /t2 -p 2 \
-s 128 -u ltrace -w 3 -x dlopen -- \
ionice -t -c 2 -n 7 -- caffeinate -dimsu -t 60 -w 1234 \
setsid -cfw -- chrt -adepRv -D 1000 -P 2000 -T 500 -f 10 \
taskset -acp 0-3 -- \
watch -bdCcefgprtwx -n 2 -q 5 -s /shots -- \
nsenter -aceiFmnpruUCTwWZ -t 1 -S 0 -G 0 -N 3 -- \
numactl -absHl -C 0-3 -i all -m 0 -N 0 -p 1 -w 0 -P 1 \
choom -n -500 -- sg staff \
flock -eFnosux -c cmd -w 10 -E 2 /tmp/lk1 chroot -- /nr1 \
runuser -fmlpPT -c cmd -g staff -s /bin/bash -G docker \
-u deploy -w 1,2 -- \
unshare -cfmnpuUirCT -R /root -w /wd -S 1000 -G 1000 \
-l /tmp/ns -- \
prlimit --nofile=4096 -o RES -p 1234 \
cpulimit -viz -e myapp -p 1234 -l 50 -- \
setpriv --reuid=1000 --regid=1000 --clear-groups -- \
setarch i386 -vhV3BFILRSTXZ -p 1234 -- \
pkexec -u admin --keep-cwd --disable-internal-agent \
fakeroot -huv -b 10 -i /tmp/save -l /usr/lib/fk.so \
-s /tmp/save -- \
unbuffer -p chronic -ev -- \
valgrind -dhqsv --tool=memcheck --log-file=/tmp/vg -- \
daemonize -av -c /tmp -e /var/log/err -E FOO=bar \
-l /tmp/lock -o /var/log/out -p /var/run/pid -u www -- \
firejail -c cmd --private --net=none -- \
sem -j 4 -P 8 -- \
systemd-run -dGhPqrRSTtv -C cap -E VAR=val -H host \
-M mach -p prop -u unit -- \
nocache -n 2 -- \
fakechroot -hsv -b /base -c /cfg -d /ld -e env -l /lib -- \
ccache -cChpsvVxz -d /cache -F 1000 -k key -M 5G \
-o opt=val -X 80 \
distcc -j \
dbus-launch --exit-with-session --config-file=foo.conf -- \
dbus-run-session --config-file /etc/dbus.conf -- \
torify tsocks catchsegv eatmydata -- \
sudo -kE -u www su root stdbuf -oL -- \
env -a arg -0i -C /v2 PATH=/b2 GOPATH=/go \
doas -Lns -u _httpd \
nice -n 8 -- nohup -- nice -n 3 nohup \
rlwrap -acEiINoRrUvWX -f /d2 -s 1000 -b y -H /h2 \
-p blue -S p2 -C n2 -D 1 -z f2 -- \
timeout -fpv -k 10 -s KILL 60 \
strace -ANnw -e trace=file -s 512 -o /t3 -X raw \
ltrace -L -l libbar -A 5 -n 1 -w 2 -e open -s 64 \
-a 40 -o /t4 \
ionice -t -c 1 -n 3 -- caffeinate -im -t 120 \
setsid -f -- chrt -adepRv -r 20 taskset -acp 0-7 -- \
watch -bdCcefgprtwx -n 5 -q 3 -s /d2 -- \
nsenter -aceZTwW -t 2 -S 1 -G 1 -N 4 -- \
numactl -absH -i all -w 1 -P 2 choom -n -1000 -- \
flock -eFnox -c cmd2 -w 3 -E 3 /tmp/lk2 chroot -- /nr2 \
runuser -fmpPT -u op -c cmd2 -g sys -s /bin/zsh -w 3 -- \
unshare -cfmnpuUirCT -R /nr -S 500 -G 500 -l /ns -- \
prlimit --nproc=512 -o COL -p 5678 \
cpulimit -viz -e app2 -p 5678 -l 75 -- \
pkexec -u op --keep-cwd \
fakeroot -u -s /tmp/s2 -- \
unbuffer -p chronic -v -- \
valgrind -qv --tool=callgrind -- \
daemonize -v -c /srv -u op -- \
firejail --private -- \
sem -j 2 -- systemd-run -dGqt -u u2 -p p2 -- \
nocache -n 1 -- fakechroot -s -l /lib2 -- \
ccache distcc dbus-launch --exit-with-x11 -- \
dbus-run-session -- torify tsocks catchsegv eatmydata -- \
sudo -kE -u root su root stdbuf -o 0 -- \
env -a a2 -0iv -C /final VAR=last FINAL=yes \
doas -Ln -u root \
nice -n 0 -- nohup -- time -v gco<space>
=> ...git checkout
Scales to 96,000+ tokens (468KB) and beyond. ARG_MAX only matters at execve() time -- zsh's line editor is just a string in memory with no kernel limit. Try that with a regex.
And if you want one that looks like it was written by someone who should be institutionalized -- triple-proxied through tor, user-switched twice, namespace-entered, NUMA-pinned, OOM-adjusted, stream-buffered, resource-limited, traced at the syscall AND library level with every flag maxed, CPU limited to 1%, real-time priority 99, pinned to 64 cores, locked, chrooted, jailed, namespaced, sandboxed, daemonized, caffeinated for 24 hours:
nocorrect noglob builtin eval coproc time -p command -p \
exec -cl -a BEAST \
nocorrect builtin eval noglob coproc time -v command \
eval nocorrect noglob builtin coproc \
torify \
sudo -kKEHnPS -g wheel -h fortress -p "password is gco" \
-R /dungeon -r overlord -T 9999 -t unconfined_t -u root \
su -l deploy \
env -0iv -C /dev/null \
DOOM=eternal FEAR=none PATH=/usr/games LANG=chaos \
CC=gcc CXX=g++ CFLAGS=-O666 LDFLAGS=-lmadness \
RUST_LOG=trace GOPATH=/void NODE_ENV=destruction \
EDITOR=ed VISUAL=ed PAGER=cat SHELL=/bin/zsh \
doas -Ls -C /etc/doas.d/monster -u _chaos \
torsocks -diq6 -a 127.0.0.1 -P 9050 -u tor -p chaos \
sudo -kE -u daemon su -T root env -i \
MALLOC_CHECK_=3 LD_PRELOAD=/lib/libevil.so \
ASAN_OPTIONS=detect_leaks=1 \
UBSAN_OPTIONS=print_stacktrace=1 \
proxychains4 -q -f /etc/proxychains.conf \
stdbuf -i 0 -o L -e 0 -- \
nice -n -20 -- nohup -- nice -n 19 nohup \
nice -n 0 nohup \
strace -AcCdDfFhiknNqrtTvVwxyYzZ \
-a 132 -b execve -e trace=all \
-E LD_LIBRARY_PATH -I 1 \
-o /tmp/strace.nightmare.log -O 999 \
-p 1 -P /proc/self/exe \
-s 1048576 -S calls \
-u strace -U 132 -X raw -- \
ltrace -bcCfhiLrStTV \
-a 132 -A 999 -D 0xff -e '*' \
-F /etc/ltrace.conf -l libc \
-n 4 -o /tmp/ltrace.abyss.log \
-p 1 -s 1048576 -u ltrace -w 8 -x dlopen -- \
ionice -c 1 -n 0 -- \
caffeinate -dimsu -t 86400 -w 1 \
setsid -cfw -- \
chrt -adepRv -D 1000 -P 2000 -T 500 -f 99 \
taskset -acp 0-63 -- \
nsenter -aceiFmnpruUCTwWZ -t 1 -S 0 -G 0 -N 3 -- \
numactl -absHl -C 0-63 -i all -m all -N 0 -p 1 -w 0 -P 1 \
choom -n -1000 -- \
sg nogroup \
watch -bdCcefgprtwx -n 1 -q 10 -s /dir -- \
flock -eFnosux -c cmd -w 3600 -E 42 /tmp/lock.of.ages -- \
chroot -- /newroot/of/all/evil \
runuser -fmlpPT -c cmd -g nogroup -s /bin/sh -G wheel \
-u nobody -w 1,2 -- \
unshare -cfmnpuUirCT -R /root -w /wd -S 0 -G 0 -l /ns -- \
prlimit --nofile=1048576 --nproc=unlimited -o RES -p 1 \
cpulimit -viz -e doom -p 1 -l 1 -- \
setpriv --reuid=0 --regid=0 --clear-groups -- \
setarch x86_64 -vhV3BFILRSTXZ -p 1 -- \
rlwrap -acEhiImnNoRrUvWX \
-b '(){}[]<>' -C BEAST -D 2 -e ch \
-f /usr/share/dict/words -g pat \
-H /tmp/history.of.madness -l /tmp/rl.log \
-M ext -O re -P inp -p magenta -q ch \
-s 999999 -S 'MONSTER> ' -t term -w 50 -z filt -- \
timeout -fpv -k 1 -s KILL 86400 \
pkexec -u admin --keep-cwd --disable-internal-agent \
fakeroot -huv -b 10 -i /tmp/save -l /usr/lib/fk.so \
-s /tmp/save -- \
unbuffer -p chronic -ev -- \
valgrind -dhqsv --tool=memcheck --log-file=/tmp/vg -- \
daemonize -av -c /tmp -e /var/log/err -E FOO=bar \
-l /tmp/lock -o /var/log/out -p /var/run/pid -u www -- \
firejail -c cmd --private --net=none -- \
sem -j 4 -P 8 -- \
systemd-run -dGhPqrRSTtv -C cap -E VAR=val -H host \
-M mach -p prop -u unit -- \
nocache -n 2 -- \
fakechroot -hsv -b /base -c /cfg -d /ld -e env -l /lib -- \
ccache -cChpsvVxz -d /cache -F 1000 -k key -M 5G \
-o opt=val -X 80 \
distcc -j \
dbus-launch --exit-with-session --config-file=db.conf -- \
dbus-run-session --config-file /etc/dbus.conf -- \
torify tsocks catchsegv eatmydata -- \
sudo -kE -u www-data su -lT app stdbuf -oL -- \
env -a arg -0iv -C /srv \
DATABASE_URL=postgres://doom:fire@localhost/inferno \
REDIS_URL=redis://localhost:6379/0 \
SECRET_KEY=hunter2 API_KEY=sk-AAAA \
doas -Ln -u _deploy \
nice -n -10 -- nohup -- \
strace -ANnwcfkqrTvV -e trace=network -s 4096 \
-o /tmp/net.log -X raw \
ltrace -bcCfhiLrStTV -l libssl -A 5 -n 1 -w 2 \
-e 'ssl_*' -s 4096 -o /tmp/ssl.log \
ionice -t -c 2 -n 7 -- \
caffeinate -im -t 3600 \
setsid -f -- \
chrt -adepRv -r 50 \
taskset -acp 0 -- \
nsenter -aceZTwW -t 2 -S 1 -G 1 -N 4 -- \
numactl -absH -i all -w 1 -P 2 choom -n 500 -- \
flock -eFx -c cmd -w 60 -E 1 /tmp/final.lock -- \
chroot -- /srv/jail \
runuser -fmpPT -u app -c cmd -g app -s /bin/zsh -w 1 -- \
unshare -cfmnpuUirCT -R /jail -S 500 -G 500 -l /ns -- \
prlimit --memlock=unlimited -o COL -p 2 \
cpulimit -viz -e app -p 2 -l 100 -- \
pkexec -u root --keep-cwd \
fakeroot -u -s /tmp/s2 -- \
unbuffer -p chronic -v -- \
valgrind -qv --tool=callgrind -- \
daemonize -v -c /srv -u root -- \
firejail --private -- \
sem -j 2 -- systemd-run -dGqt -u u2 -p p2 -- \
nocache -n 1 -- fakechroot -s -l /lib2 -- \
ccache distcc dbus-launch --exit-with-x11 -- \
dbus-run-session -- torify tsocks eatmydata -- \
sudo -kE -u root su root stdbuf -o 0 -- \
env -a a2 -i PATH=/usr/bin LAST_WORDS=goodbye \
doas -Ln -u root \
nice -n 0 -- nohup -- time -v gco<space>
=> ...git checkout
// end of line