import { type CommandFlag, type CommandSpec, flagTokens, } from 'lib/commands/spec.js' import { describeForShell } from './describe.js' export const renderZshCompletion = (spec: CommandSpec): string => { const valuelessTokens = spec.globalFlags .filter(({ takesValue }) => !takesValue) .flatMap(flagTokens) .sort() return `${[ header, renderCase('_seam_subcommands', subcommandBranches(spec)), renderCase('_seam_flags', flagBranches(spec)), renderCase('_seam_flag_values', flagValueBranches(spec)), `_seam_global_flags() {\n _seam_reply+=(${describeFlags(spec.globalFlags)})\n}`, completionFunction(valuelessTokens), dispatch, ].join('\n\n')}\n` } const header = `#compdef seam # zsh completion for the seam command. # # Generated by @seamapi/cli from the Seam API schema. # Do not edit: regenerate with 'seam completion zsh'. # # Load it for the current shell with # # source <(seam completion zsh) # # or install it for every shell with # # seam completion zsh > "\${fpath[1]}/_seam"` const completionFunction = (valuelessTokens: string[]): string => `_seam() { local -a _seam_reply local -a valueless local command previous word local -i index valueless=(${valuelessTokens.join(' ')}) # The command path is the run of words before the first flag. command='' for (( index = 2; index < CURRENT; index++ )); do word="\${words[index]}" if [[ "$word" == -* ]]; then break fi command="\${command:+$command }$word" done previous='' if (( CURRENT > 1 )); then previous="\${words[CURRENT - 1]}" fi # Completing the value of a flag that takes one. if [[ "$previous" == -* ]] && (( \${valueless[(Ie)$previous]} == 0 )); then _seam_flag_values "$command $previous" if (( \${#_seam_reply} )); then _describe -t values 'value' _seam_reply fi return fi if [[ "\${words[CURRENT]}" == -* ]]; then _seam_flags "$command" _seam_global_flags _describe -t options 'option' _seam_reply return fi _seam_subcommands "$command" if (( \${#_seam_reply} )); then _describe -t commands 'command' _seam_reply return fi _seam_flags "$command" _seam_global_flags _describe -t options 'option' _seam_reply }` // The script runs in three ways. Autoloaded from fpath as _seam, it must // complete the in-flight request: funcstack holds _seam. Evaluated by the // loader stub inside the autoloaded _seam, the same applies, but eval pushes // '(eval)' onto funcstack, so search the whole stack rather than the top. // Sourced into a shell, funcstack holds no _seam: register with compdef. const dispatch = `if (( \${funcstack[(I)_seam]} )); then _seam "$@" else compdef _seam seam fi` interface Branch { pattern: string entries: string[] } const subcommandBranches = (spec: CommandSpec): Branch[] => spec.groups.map((group) => ({ pattern: group.path.join(' '), entries: group.subcommands.map(({ name, description }) => describe(name, description), ), })) const flagBranches = (spec: CommandSpec): Branch[] => spec.commands .filter(({ flags }) => flags.length > 0) .map((command) => ({ pattern: command.path.join(' '), entries: [describeFlags(command.flags)], })) const flagValueBranches = (spec: CommandSpec): Branch[] => spec.commands.flatMap((command) => command.flags .filter(({ values }) => values.length > 0) .flatMap((flag) => flagTokens(flag).map((token) => ({ pattern: `${command.path.join(' ')} ${token}`, entries: flag.values.map((value) => `'${value}'`), })), ), ) const describeFlags = (flags: CommandFlag[]): string => flags .flatMap((flag) => flagTokens(flag).map((token) => describe(token, flag.description)), ) .join(' ') const describe = (value: string, description: string): string => { const summary = describeForShell(description) return summary === '' ? `'${value}'` : `'${value}:${summary}'` } const renderCase = (name: string, branches: Branch[]): string => [ `${name}() {`, ` case "$1" in`, ...branches.map( ({ pattern, entries }) => ` ('${pattern}') _seam_reply+=(${entries.join(' ')}) ;;`, ), ` esac`, `}`, ].join('\n')