Cobra's generated zsh script spawns a subshell per candidate and sends every host through _describe, so <Tab> hangs for seconds with thousands of cached hosts. netssh now ships its own script: subcommands and flags go through _describe, plain hostnames through compadd. 5000 hosts complete in ~0.08 s instead of ~4 s. Add completion.max_results to cap the number of offered hostnames and sort host candidates for a stable order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
3.3 KiB
Go
117 lines
3.3 KiB
Go
package hook
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config"
|
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/storage"
|
|
)
|
|
|
|
const completionMarker = "# netssh command alias completion — generated automatically"
|
|
|
|
// SyncCommandCompletion installs lazy loaders for the configured command alias.
|
|
// The loaders obtain the canonical completion from the current netssh binary,
|
|
// so aliases configured by the wizard work without a separate completion install.
|
|
func SyncCommandCompletion(alias string) error {
|
|
if err := config.ValidateCommandAlias(alias); err != nil {
|
|
return err
|
|
}
|
|
statePath := filepath.Join(filepath.Dir(AliasesPath()), "completion_alias")
|
|
data, err := os.ReadFile(statePath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
previous := strings.TrimSpace(string(data))
|
|
if err := config.ValidateCommandAlias(previous); err != nil {
|
|
return fmt.Errorf("invalid completion alias state: %w", err)
|
|
}
|
|
if alias == "" && previous == "" {
|
|
return nil
|
|
}
|
|
|
|
for _, shell := range []string{"bash", "zsh", "fish"} {
|
|
if previous != "" && previous != alias {
|
|
path := commandCompletionPath(shell, previous)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
// Only delete files owned by netssh; preserve any user replacements.
|
|
if strings.HasPrefix(string(data), completionMarker+"\n") || strings.HasPrefix(string(data), "#compdef "+previous+"\n"+completionMarker+"\n") {
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if alias == "" {
|
|
continue
|
|
}
|
|
if shell == "fish" {
|
|
if _, err := os.Stat(FishConfigDir()); os.IsNotExist(err) {
|
|
continue
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
path := commandCompletionPath(shell, alias)
|
|
script := commandCompletionScript(shell, alias)
|
|
existing, err := os.ReadFile(path)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
if string(existing) == script {
|
|
continue
|
|
}
|
|
if err := storage.WriteFile(path, []byte(script), 0o644); err != nil {
|
|
return fmt.Errorf("writing %s alias completion: %w", shell, err)
|
|
}
|
|
}
|
|
if alias == "" {
|
|
if err := os.Remove(statePath); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return storage.WriteFile(statePath, []byte(alias+"\n"), 0o600)
|
|
}
|
|
|
|
func commandCompletionPath(shell, alias string) string {
|
|
switch shell {
|
|
case "bash":
|
|
return filepath.Join(os.Getenv("HOME"), ".local", "share", "bash-completion", "completions", alias)
|
|
case "zsh":
|
|
return filepath.Join(ZshFuncDir(), "_"+alias)
|
|
default:
|
|
return filepath.Join(FishConfigDir(), "completions", alias+".fish")
|
|
}
|
|
}
|
|
|
|
func commandCompletionScript(shell, alias string) string {
|
|
switch shell {
|
|
case "bash":
|
|
return fmt.Sprintf(`%s
|
|
source <(netssh completion bash)
|
|
if [[ $(type -t compopt) == builtin ]]; then
|
|
complete -o default -F __start_netssh %s
|
|
else
|
|
complete -o default -o nospace -F __start_netssh %s
|
|
fi
|
|
`, completionMarker, alias, alias)
|
|
case "zsh":
|
|
return fmt.Sprintf(`#compdef %s
|
|
%s
|
|
source <(netssh completion zsh)
|
|
compdef _netssh %s
|
|
_netssh "$@"
|
|
`, alias, completionMarker, alias)
|
|
default:
|
|
return fmt.Sprintf(`%s
|
|
netssh completion fish | source
|
|
complete --command %s --wraps netssh
|
|
`, completionMarker, alias)
|
|
}
|
|
}
|