···11+export function generate(name: string, includeDescription: boolean) {
22+}
+2
powershell.ts
···11+export function generate(name: string, includeDescription: boolean) {
22+}
+75
shared.ts
···11+22+// ShellCompRequestCmd is the name of the hidden command that is used to request
33+// completion results from the program. It is used by the shell completion scripts.
44+export const ShellCompRequestCmd: string = "__complete";
55+66+// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
77+// completion results without their description. It is used by the shell completion scripts.
88+export const ShellCompNoDescRequestCmd: string = "__completeNoDesc";
99+1010+// ShellCompDirective is a bit map representing the different behaviors the shell
1111+// can be instructed to have once completions have been provided.
1212+export const ShellCompDirective = {
1313+ // ShellCompDirectiveError indicates an error occurred and completions should be ignored.
1414+ ShellCompDirectiveError: 1 << 0,
1515+1616+ // ShellCompDirectiveNoSpace indicates that the shell should not add a space
1717+ // after the completion even if there is a single completion provided.
1818+ ShellCompDirectiveNoSpace: 1 << 1,
1919+2020+ // ShellCompDirectiveNoFileComp indicates that the shell should not provide
2121+ // file completion even when no completion is provided.
2222+ ShellCompDirectiveNoFileComp: 1 << 2,
2323+2424+ // ShellCompDirectiveFilterFileExt indicates that the provided completions
2525+ // should be used as file extension filters.
2626+ // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
2727+ // is a shortcut to using this directive explicitly. The BashCompFilenameExt
2828+ // annotation can also be used to obtain the same behavior for flags.
2929+ ShellCompDirectiveFilterFileExt: 1 << 3,
3030+3131+ // ShellCompDirectiveFilterDirs indicates that only directory names should
3232+ // be provided in file completion. To request directory names within another
3333+ // directory, the returned completions should specify the directory within
3434+ // which to search. The BashCompSubdirsInDir annotation can be used to
3535+ // obtain the same behavior but only for flags.
3636+ ShellCompDirectiveFilterDirs: 1 << 4,
3737+3838+ // ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
3939+ // in which the completions are provided.
4040+ ShellCompDirectiveKeepOrder: 1 << 5,
4141+4242+ // ===========================================================================
4343+4444+ // All directives using iota (or equivalent in Go) should be above this one.
4545+ // For internal use.
4646+ shellCompDirectiveMaxValue: 1 << 6,
4747+4848+ // ShellCompDirectiveDefault indicates to let the shell perform its default
4949+ // behavior after completions have been provided.
5050+ // This one must be last to avoid messing up the iota count.
5151+ ShellCompDirectiveDefault: 0,
5252+};
5353+5454+5555+5656+type Completion = {
5757+ action: string;
5858+ description?: string;
5959+};
6060+6161+export type Callback = (
6262+ previousArgs: string[],
6363+ toComplete: string,
6464+) => Completion[] | Promise<Completion[]>;
6565+6666+export type Positional = {
6767+ required: boolean;
6868+ variadic: boolean;
6969+ completion: Callback;
7070+};
7171+7272+7373+export const positionalMap = new Map<string, Positional[]>();
7474+7575+export const flagMap = new Map<string, Callback>();
+220
zsh.ts
···11+import { ShellCompDirective, ShellCompNoDescRequestCmd, ShellCompRequestCmd } from "./shared";
22+33+export function generate(name: string, exec: string, includeDescription: boolean) {
44+ let compCmd = ShellCompRequestCmd;
55+ if (!includeDescription) {
66+ compCmd = ShellCompNoDescRequestCmd;
77+ }
88+99+ return `#compdef ${name}
1010+compdef _${name} ${name}
1111+1212+# zsh completion for ${name} -*- shell-script -*-
1313+1414+__${name}_debug() {
1515+ local file="$BASH_COMP_DEBUG_FILE"
1616+ if [[ -n \${file} ]]; then
1717+ echo "$*" >> "\${file}"
1818+ fi
1919+}
2020+2121+_${name}() {
2222+ local shellCompDirectiveError=${ShellCompDirective.ShellCompDirectiveError}
2323+ local shellCompDirectiveNoSpace=${ShellCompDirective.ShellCompDirectiveNoSpace}
2424+ local shellCompDirectiveNoFileComp=${ShellCompDirective.ShellCompDirectiveNoFileComp}
2525+ local shellCompDirectiveFilterFileExt=${ShellCompDirective.ShellCompDirectiveFilterFileExt}
2626+ local shellCompDirectiveFilterDirs=${ShellCompDirective.ShellCompDirectiveFilterDirs}
2727+ local shellCompDirectiveKeepOrder=${ShellCompDirective.ShellCompDirectiveKeepOrder}
2828+2929+ local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder
3030+ local -a completions
3131+3232+ __${name}_debug "\\n========= starting completion logic =========="
3333+ __${name}_debug "CURRENT: \${CURRENT}, words[*]: \${words[*]}"
3434+3535+ # The user could have moved the cursor backwards on the command-line.
3636+ # We need to trigger completion from the \$CURRENT location, so we need
3737+ # to truncate the command-line (\$words) up to the \$CURRENT location.
3838+ # (We cannot use \$CURSOR as its value does not work when a command is an alias.)
3939+ words=( "\${=words[1,CURRENT]}" )
4040+ __${name}_debug "Truncated words[*]: \${words[*]},"
4141+4242+ lastParam=\${words[-1]}
4343+ lastChar=\${lastParam[-1]}
4444+ __${name}_debug "lastParam: \${lastParam}, lastChar: \${lastChar}"
4545+4646+ # For zsh, when completing a flag with an = (e.g., ${name} -n=<TAB>)
4747+ # completions must be prefixed with the flag
4848+ setopt local_options BASH_REMATCH
4949+ if [[ "\${lastParam}" =~ '-.*=' ]]; then
5050+ # We are dealing with a flag with an =
5151+ flagPrefix="-P \${BASH_REMATCH}"
5252+ fi
5353+5454+ # Prepare the command to obtain completions
5555+ requestComp="${exec} ${compCmd} -- \${words[2,-1]}"
5656+ if [ "\${lastChar}" = "" ]; then
5757+ # If the last parameter is complete (there is a space following it)
5858+ # We add an extra empty parameter so we can indicate this to the go completion code.
5959+ __${name}_debug "Adding extra empty parameter"
6060+ requestComp="\${requestComp} ''"
6161+ fi
6262+6363+ __${name}_debug "About to call: eval \${requestComp}"
6464+6565+ # Use eval to handle any environment variables and such
6666+ out=\$(eval \${requestComp} 2>/dev/null)
6767+ __${name}_debug "completion output: \${out}"
6868+6969+ # Extract the directive integer following a : from the last line
7070+ local lastLine
7171+ while IFS='\n' read -r line; do
7272+ lastLine=\${line}
7373+ done < <(printf "%s\n" "\${out[@]}")
7474+ __${name}_debug "last line: \${lastLine}"
7575+7676+ if [ "\${lastLine[1]}" = : ]; then
7777+ directive=\${lastLine[2,-1]}
7878+ # Remove the directive including the : and the newline
7979+ local suffix
8080+ (( suffix=\${#lastLine}+2))
8181+ out=\${out[1,-\$suffix]}
8282+ else
8383+ # There is no directive specified. Leave \$out as is.
8484+ __${name}_debug "No directive found. Setting to default"
8585+ directive=0
8686+ fi
8787+8888+ __${name}_debug "directive: \${directive}"
8989+ __${name}_debug "completions: \${out}"
9090+ __${name}_debug "flagPrefix: \${flagPrefix}"
9191+9292+ if [ \$((directive & shellCompDirectiveError)) -ne 0 ]; then
9393+ __${name}_debug "Completion received error. Ignoring completions."
9494+ return
9595+ fi
9696+9797+ local activeHelpMarker="%"
9898+ local endIndex=\${#activeHelpMarker}
9999+ local startIndex=\$((\${#activeHelpMarker}+1))
100100+ local hasActiveHelp=0
101101+ while IFS='\n' read -r comp; do
102102+ # Check if this is an activeHelp statement (i.e., prefixed with \$activeHelpMarker)
103103+ if [ "\${comp[1,\$endIndex]}" = "\$activeHelpMarker" ];then
104104+ __${name}_debug "ActiveHelp found: \$comp"
105105+ comp="\${comp[\$startIndex,-1]}"
106106+ if [ -n "\$comp" ]; then
107107+ compadd -x "\${comp}"
108108+ __${name}_debug "ActiveHelp will need delimiter"
109109+ hasActiveHelp=1
110110+ fi
111111+ continue
112112+ fi
113113+114114+ if [ -n "\$comp" ]; then
115115+ # If requested, completions are returned with a description.
116116+ # The description is preceded by a TAB character.
117117+ # For zsh's _describe, we need to use a : instead of a TAB.
118118+ # We first need to escape any : as part of the completion itself.
119119+ comp=\${comp//:/\\:}
120120+121121+ local tab="\$(printf '\\t')"
122122+ comp=\${comp//\$tab/:}
123123+124124+ __${name}_debug "Adding completion: \${comp}"
125125+ completions+=\${comp}
126126+ lastComp=\$comp
127127+ fi
128128+ done < <(printf "%s\n" "\${out[@]}")
129129+130130+ # Add a delimiter after the activeHelp statements, but only if:
131131+ # - there are completions following the activeHelp statements, or
132132+ # - file completion will be performed (so there will be choices after the activeHelp)
133133+ if [ \$hasActiveHelp -eq 1 ]; then
134134+ if [ \${#completions} -ne 0 ] || [ \$((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
135135+ __${name}_debug "Adding activeHelp delimiter"
136136+ compadd -x "--"
137137+ hasActiveHelp=0
138138+ fi
139139+ fi
140140+141141+ if [ \$((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
142142+ __${name}_debug "Activating nospace."
143143+ noSpace="-S ''"
144144+ fi
145145+146146+ if [ \$((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
147147+ __${name}_debug "Activating keep order."
148148+ keepOrder="-V"
149149+ fi
150150+151151+ if [ \$((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
152152+ # File extension filtering
153153+ local filteringCmd
154154+ filteringCmd='_files'
155155+ for filter in \${completions[@]}; do
156156+ if [ \${filter[1]} != '*' ]; then
157157+ # zsh requires a glob pattern to do file filtering
158158+ filter="\\*.\$filter"
159159+ fi
160160+ filteringCmd+=" -g \$filter"
161161+ done
162162+ filteringCmd+=" \${flagPrefix}"
163163+164164+ __${name}_debug "File filtering command: \$filteringCmd"
165165+ _arguments '*:filename:'"\$filteringCmd"
166166+ elif [ \$((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
167167+ # File completion for directories only
168168+ local subdir
169169+ subdir="\${completions[1]}"
170170+ if [ -n "\$subdir" ]; then
171171+ __${name}_debug "Listing directories in \$subdir"
172172+ pushd "\${subdir}" >/dev/null 2>&1
173173+ else
174174+ __${name}_debug "Listing directories in ."
175175+ fi
176176+177177+ local result
178178+ _arguments '*:dirname:_files -/'" \${flagPrefix}"
179179+ result=\$?
180180+ if [ -n "\$subdir" ]; then
181181+ popd >/dev/null 2>&1
182182+ fi
183183+ return \$result
184184+ else
185185+ __${name}_debug "Calling _describe"
186186+ if eval _describe \$keepOrder "completions" completions -Q \${flagPrefix} \${noSpace}; then
187187+ __${name}_debug "_describe found some completions"
188188+189189+ # Return the success of having called _describe
190190+ return 0
191191+ else
192192+ __${name}_debug "_describe did not find completions."
193193+ __${name}_debug "Checking if we should do file completion."
194194+ if [ \$((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
195195+ __${name}_debug "deactivating file completion"
196196+197197+ # We must return an error code here to let zsh know that there were no
198198+ # completions found by _describe; this is what will trigger other
199199+ # matching algorithms to attempt to find completions.
200200+ # For example zsh can match letters in the middle of words.
201201+ return 1
202202+ else
203203+ # Perform file completion
204204+ __${name}_debug "Activating file completion"
205205+206206+ # We must return the result of this command, so it must be the
207207+ # last command, or else we must store its result to return it.
208208+ _arguments '*:filename:_files'" \${flagPrefix}"
209209+ fi
210210+ fi
211211+ fi
212212+}
213213+214214+# don't run the completion function when being sourced or eval-ed
215215+if [ "\${funcstack[1]}" = "_${name}" ]; then
216216+ _${name}
217217+fi
218218+`;
219219+}
220220+