main
1#!/usr/bin/env zsh
2# shellcheck disable=all
3# Simple zsh completion capture using _complete_help
4# Usage: zsh-capture.zsh "command line" "/path/to/cwd"
5
6emulate -L zsh
7setopt no_beep
8
9local cmdline="$1"
10local cwd="$2"
11
12cd "$cwd" 2>/dev/null || exit 1
13
14# Initialize completion system (use user's zcompdump)
15autoload -Uz compinit
16compinit -C 2>/dev/null
17
18# Parse command line
19local -a words
20words=("${(@Q)${(z)cmdline}}")
21
22# If line ends with space, we're completing a new word
23if [[ "$cmdline" == *" " ]]; then
24 words+=("")
25fi
26
27local cmd="${words[1]}"
28local current="${words[-1]}"
29
30# Helper to output completions
31output() {
32 local val="$1" desc="$2"
33 if [[ -n "$desc" ]]; then
34 print -r -- "${val}"$'\t'"${desc}"
35 else
36 print -r -- "${val}"
37 fi
38}
39
40# Git completions
41if [[ "$cmd" == "git" ]]; then
42 if (( ${#words} == 2 )); then
43 # Git subcommands
44 git --list-cmds=main,others 2>/dev/null | while read -r subcmd; do
45 [[ -z "$current" || "$subcmd" == "$current"* ]] && output "$subcmd"
46 done
47 else
48 local subcmd="${words[2]}"
49 case "$subcmd" in
50 checkout|switch|merge|rebase|branch|log)
51 # Branches
52 git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null | while read -r b; do
53 [[ -z "$current" || "$b" == "$current"* ]] && output "$b" "branch"
54 done
55 git for-each-ref --format='%(refname:short)' refs/remotes 2>/dev/null | while read -r b; do
56 [[ "$b" == */HEAD ]] && continue
57 local short="${b#*/}"
58 [[ -z "$current" || "$short" == "$current"* ]] && output "$short" "remote"
59 done
60 ;;
61 add|diff|restore|reset)
62 # Modified files
63 git diff --name-only 2>/dev/null | while read -r f; do
64 [[ -z "$current" || "$f" == "$current"* ]] && output "$f" "modified"
65 done
66 git diff --cached --name-only 2>/dev/null | while read -r f; do
67 [[ -z "$current" || "$f" == "$current"* ]] && output "$f" "staged"
68 done
69 ;;
70 push|pull|fetch)
71 if (( ${#words} == 3 )); then
72 git remote 2>/dev/null | while read -r r; do
73 [[ -z "$current" || "$r" == "$current"* ]] && output "$r" "remote"
74 done
75 fi
76 ;;
77 stash)
78 for sub in apply drop list pop show push; do
79 [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
80 done
81 ;;
82 esac
83 fi
84 exit 0
85fi
86
87# SSH/SCP completions - hosts
88if [[ "$cmd" == "ssh" || "$cmd" == "scp" || "$cmd" == "sftp" ]]; then
89 {
90 [[ -f ~/.ssh/config ]] && awk '/^Host / && !/\*/{for(i=2;i<=NF;i++)print $i}' ~/.ssh/config
91 [[ -f ~/.ssh/known_hosts ]] && awk -F'[, ]' '{print $1}' ~/.ssh/known_hosts
92 } 2>/dev/null | sort -u | while read -r h; do
93 [[ -z "$current" || "$h" == "$current"* ]] && output "$h" "host"
94 done
95 exit 0
96fi
97
98# Make completions
99if [[ "$cmd" == "make" ]]; then
100 local mf
101 for f in GNUmakefile Makefile makefile; do
102 [[ -f "$f" ]] && mf="$f" && break
103 done
104 if [[ -n "$mf" ]]; then
105 awk -F: '/^[a-zA-Z_][a-zA-Z0-9_-]*:/ && !/^\./{print $1}' "$mf" 2>/dev/null | while read -r t; do
106 [[ -z "$current" || "$t" == "$current"* ]] && output "$t" "target"
107 done
108 fi
109 exit 0
110fi
111
112# NPM/Yarn/PNPM completions
113if [[ "$cmd" == "npm" || "$cmd" == "yarn" || "$cmd" == "pnpm" ]]; then
114 if (( ${#words} == 2 )); then
115 for sub in install add remove run build test start dev publish; do
116 [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
117 done
118 elif [[ "${words[2]}" == "run" && -f package.json ]]; then
119 jq -r '.scripts // {} | keys[]' package.json 2>/dev/null | while read -r s; do
120 [[ -z "$current" || "$s" == "$current"* ]] && output "$s" "script"
121 done
122 fi
123 exit 0
124fi
125
126# Docker completions
127if [[ "$cmd" == "docker" ]]; then
128 if (( ${#words} == 2 )); then
129 for sub in build compose exec images logs ps pull push rm rmi run start stop; do
130 [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
131 done
132 fi
133 exit 0
134fi
135
136# Fallback: file completion
137if [[ -n "$current" ]]; then
138 local -a matches
139 matches=( ${current}*(N) )
140 for f in "${matches[@]:0:20}"; do
141 [[ -d "$f" ]] && output "${f}/" "directory" || output "$f" "file"
142 done
143else
144 local -a matches
145 matches=( *(N) )
146 for f in "${matches[@]:0:20}"; do
147 [[ -d "$f" ]] && output "${f}/" "directory" || output "$f" "file"
148 done
149fi