Files
ssh-netbox-wrapper/internal/hook/zsh.go
T
Sebastian UnterschützandClaude Opus 5.5 6243514def fix(completion): accept instance-qualified hosts for a bare name in zsh
With several NetBox instances, netssh offers "prod/fsn1-bx1…" for the
typed text "fsn1". compadd discarded these matches because they do not
start with the typed word, so <Tab> found nothing. The matcher
'l:|=*' lets zsh accept text before the typed word; netssh has already
filtered the candidates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:44:54 +01:00

101 lines
3.0 KiB
Go

package hook
import (
"fmt"
"os"
"path/filepath"
)
// ZshCompletionScript replaces Cobra's generated zsh completion. Cobra's script
// spawns a subshell per candidate and passes every host through _describe,
// which takes tens of seconds for thousands of hosts. This script splits the
// candidates with parameter expansion only: entries with a description
// (subcommands, flags) go through _describe, plain hostnames through compadd.
//
// The file works both from fpath (autoloaded as _netssh) and when sourced.
const ZshCompletionScript = `#compdef netssh
compdef _netssh netssh
# netssh zsh completion — generated by netssh, do not edit.
_netssh() {
local -a lines described plain
local out directive=0 entry ret=1
local -a nospace
out=$(netssh __complete "${(@)words[2,CURRENT]}" 2>/dev/null) || return 1
lines=("${(@f)out}")
if [[ ${lines[-1]} == :<-> ]]; then
directive=${lines[-1]#:}
lines[-1]=()
fi
# Directive 1: error.
(( directive & 1 )) && return 1
lines=(${lines:#_activeHelp_*})
lines=(${lines:#})
# Directive 2: no trailing space.
(( directive & 2 )) && nospace=(-S '')
# Directive 8: the lines are file extensions to filter by.
if (( directive & 8 )); then
local -a globs
for entry in $lines; do globs+=("*.${entry}"); done
_files -g "(${(j:|:)globs})"
return
fi
# Directive 16: complete directories only.
if (( directive & 16 )); then
_files -/
return
fi
described=(${(M)lines:#*$'\t'*})
plain=(${lines:#*$'\t'*})
if (( $#described )); then
local -a items
for entry in $described; do
items+=("${${entry%%$'\t'*}//:/\\:}:${entry#*$'\t'}")
done
_describe -t commands 'netssh command' items $nospace && ret=0
fi
if (( $#plain )); then
# netssh already filtered the hosts, also by the name after an instance
# prefix: "fsn1" offers "prod/fsn1-…". 'l:|=*' lets zsh accept text
# before the typed word instead of discarding these matches.
local -a group=(-J hosts)
# Directive 32: keep the order supplied by netssh.
(( directive & 32 )) && group=(-V hosts)
compadd $group -X '%Bhosts%b' -M 'l:|=*' $nospace -- $plain && ret=0
fi
# Directive 4: no file completion as a fallback.
if (( ! $#lines && ! (directive & 4) )); then
_files && ret=0
fi
return ret
}
if [ "$funcstack[1]" = "_netssh" ]; then
_netssh "$@"
fi
`
// ZshCompletionNote tells the user how zsh picks up a completion file in dir.
func ZshCompletionNote(dir string) string {
dump := filepath.Join(zshDir(), ".zcompdump")
return fmt.Sprintf(`zsh loads it from fpath. The netssh block in your .zshrc adds %s
(netssh hook install). Otherwise add this before compinit:
fpath=(%s $fpath)
Then refresh the completion cache: rm -f %s* && exec zsh`, dir, dir, dump)
}
func zshDir() string {
if dir := os.Getenv("ZDOTDIR"); dir != "" {
return dir
}
return os.Getenv("HOME")
}
// ZshFuncDir is the user directory netssh installs zsh completion files into.
func ZshFuncDir() string {
return filepath.Join(os.Getenv("HOME"), ".zfunc")
}