From a7b2f69813f0aed52483768a06e6c8b5b5ecd1b8 Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Sat, 25 Nov 2023 11:00:44 -0500 Subject: [PATCH] implement zsh completions --- src/Command.ts | 9 ++ src/internal/cliApp.ts | 7 +- src/internal/command.ts | 163 ++++++++++++++++++++++++++++++++ src/internal/options.ts | 75 +++++++++++++++ src/internal/primitive.ts | 47 +++++++++ test/Command.test.ts | 127 +++---------------------- test/snapshots/bash-completions | 91 ++++++++++++++++++ test/snapshots/fish-completions | 22 +++++ test/snapshots/zsh-completions | 108 +++++++++++++++++++++ 9 files changed, 533 insertions(+), 116 deletions(-) create mode 100644 test/snapshots/bash-completions create mode 100644 test/snapshots/fish-completions create mode 100644 test/snapshots/zsh-completions diff --git a/src/Command.ts b/src/Command.ts index c181538..8cd5ff2 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -138,6 +138,15 @@ export const getFishCompletions: ( programName: string ) => Effect> = InternalCommand.getFishCompletions +/** + * @since 1.0.0 + * @category combinators + */ +export const getZshCompletions: ( + self: Command, + programName: string +) => Effect> = InternalCommand.getZshCompletions + /** * @since 1.0.0 * @category combinators diff --git a/src/internal/cliApp.ts b/src/internal/cliApp.ts index 8121723..55794ed 100644 --- a/src/internal/cliApp.ts +++ b/src/internal/cliApp.ts @@ -155,21 +155,22 @@ const handleBuiltInOption = ( case "ShowCompletions": { const commandNames = ReadonlyArray.fromIterable(InternalCommand.getNames(self.command)) if (ReadonlyArray.isNonEmptyReadonlyArray(commandNames)) { + const programName = ReadonlyArray.headNonEmpty(commandNames) switch (builtIn.shellType) { case "bash": { - const programName = ReadonlyArray.headNonEmpty(commandNames) return InternalCommand.getBashCompletions(self.command, programName).pipe( Effect.flatMap((completions) => Console.log(ReadonlyArray.join(completions, "\n"))) ) } case "fish": { - const programName = ReadonlyArray.headNonEmpty(commandNames) return InternalCommand.getFishCompletions(self.command, programName).pipe( Effect.flatMap((completions) => Console.log(ReadonlyArray.join(completions, "\n"))) ) } case "zsh": - throw new Error("Zsh completions not implemented ... yet") + return InternalCommand.getZshCompletions(self.command, programName).pipe( + Effect.flatMap((completions) => Console.log(ReadonlyArray.join(completions, "\n"))) + ) } } throw new Error( diff --git a/src/internal/command.ts b/src/internal/command.ts index dc7bd92..b2211d8 100644 --- a/src/internal/command.ts +++ b/src/internal/command.ts @@ -192,6 +192,13 @@ export const getFishCompletions = ( ): Effect.Effect> => getFishCompletionsInternal(self as Instruction, programName) +/** @internal */ +export const getZshCompletions = ( + self: Command.Command, + programName: string +): Effect.Effect> => + getZshCompletionsInternal(self as Instruction, programName) + /** @internal */ export const getSubcommands = ( self: Command.Command @@ -1205,3 +1212,159 @@ const getFishCompletionsInternal = ( ReadonlyArray.appendAll(subcommandCompletions(conditionals)) )) }) + +const getZshCompletionsInternal = ( + self: Instruction, + rootCommand: string +): Effect.Effect> => + traverseCommand(self, ReadonlyArray.empty(), (state, info) => { + const preformatted = ReadonlyArray.isEmptyReadonlyArray(info.parentCommands) + ? ReadonlyArray.of(info.command.name) + : pipe( + info.parentCommands, + ReadonlyArray.append(info.command.name), + ReadonlyArray.map((command) => command.replace("-", "__")) + ) + const underscoreName = ReadonlyArray.join(preformatted, "__") + const spaceName = ReadonlyArray.join(preformatted, " ") + const subcommands = pipe( + info.subcommands, + ReadonlyArray.map(([name, subcommand]) => { + const desc = getShortDescription(subcommand) + return `'${name}:${desc}' \\` + }) + ) + const commands = ReadonlyArray.isEmptyReadonlyArray(subcommands) + ? `commands=()` + : `commands=(\n${ReadonlyArray.join(indentAll(subcommands, 8), "\n")}\n )` + const handlerLines = [ + `(( $+functions[_${underscoreName}_commands] )) ||`, + `_${underscoreName}_commands() {`, + ` local commands; ${commands}`, + ` _describe -t commands '${spaceName} commands' commands "$@"`, + "}" + ] + return Effect.succeed(ReadonlyArray.appendAll(state, handlerLines)) + }).pipe(Effect.map((handlers) => { + const cases = getZshSubcommandCases(self, ReadonlyArray.empty(), ReadonlyArray.empty()) + const scriptName = `_${rootCommand}_zsh_completions` + return [ + `#compdef ${rootCommand}`, + "", + "autoload -U is-at-least", + "", + `function ${scriptName}() {`, + " typeset -A opt_args", + " typeset -a _arguments_options", + " local ret=1", + "", + " if is-at-least 5.2; then", + " _arguments_options=(-s -S -C)", + " else", + " _arguments_options=(-s -C)", + " fi", + "", + " local context curcontext=\"$curcontext\" state line", + ...indentAll(cases, 4), + "}", + "", + ...handlers, + "", + `if [ "$funcstack[1]" = "${scriptName}" ]; then`, + ` ${scriptName} "$@"`, + "else", + ` compdef ${scriptName} ${rootCommand}`, + "fi" + ] + })) + +const getZshSubcommandCases = ( + self: Instruction, + parentCommands: ReadonlyArray, + subcommands: ReadonlyArray<[string, Standard | GetUserInput]> +): ReadonlyArray => { + switch (self._tag) { + case "Standard": + case "GetUserInput": { + const options = isStandard(self) + ? InternalOptions.all([InternalBuiltInOptions.builtIns, self.options]) + : InternalBuiltInOptions.builtIns + const optionCompletions = pipe( + InternalOptions.getZshCompletions(options as InternalOptions.Instruction), + ReadonlyArray.map((completion) => `'${completion}' \\`) + ) + if (ReadonlyArray.isEmptyReadonlyArray(parentCommands)) { + return [ + "_arguments \"${_arguments_options[@]}\" \\", + ...indentAll(optionCompletions, 4), + ` ":: :_${self.name}_commands" \\`, + ` "*::: :->${self.name}" \\`, + " && ret=0" + ] + } + if (ReadonlyArray.isEmptyReadonlyArray(subcommands)) { + return [ + `(${self.name})`, + "_arguments \"${_arguments_options[@]}\" \\", + ...indentAll(optionCompletions, 4), + " && ret=0", + ";;" + ] + } + return [ + `(${self.name})`, + "_arguments \"${_arguments_options[@]}\" \\", + ...indentAll(optionCompletions, 4), + ` ":: :_${ReadonlyArray.append(parentCommands, self.name).join("__")}_commands" \\`, + ` "*::: :->${self.name}" \\`, + " && ret=0" + ] + } + case "Map": { + return getZshSubcommandCases(self.command as Instruction, parentCommands, subcommands) + } + case "OrElse": { + const left = getZshSubcommandCases(self.left as Instruction, parentCommands, subcommands) + const right = getZshSubcommandCases(self.right as Instruction, parentCommands, subcommands) + return ReadonlyArray.appendAll(left, right) + } + case "Subcommands": { + const nextSubcommands = getImmediateSubcommands(self.child as Instruction) + const parentName = Array.from(getNamesInternal(self.parent as Instruction))[0] + const parentLines = getZshSubcommandCases( + self.parent as Instruction, + parentCommands, + ReadonlyArray.appendAll(subcommands, nextSubcommands) + ) + const childCases = getZshSubcommandCases( + self.child as Instruction, + ReadonlyArray.append(parentCommands, parentName), + subcommands + ) + const hyphenName = pipe( + ReadonlyArray.append(parentCommands, parentName), + ReadonlyArray.join("-") + ) + const childLines = pipe( + [ + "case $state in", + ` (${parentName})`, + ` words=($line[1] "\${words[@]}")`, + " (( CURRENT += 1 ))", + ` curcontext="\${curcontext%:*:*}:${hyphenName}-command-$line[1]:"`, + ` case $line[1] in`, + ...indentAll(childCases, 8), + " esac", + " ;;", + "esac" + ], + ReadonlyArray.appendAll( + ReadonlyArray.isEmptyReadonlyArray(parentCommands) + ? ReadonlyArray.empty() + : ReadonlyArray.of(";;") + ) + ) + return ReadonlyArray.appendAll(parentLines, childLines) + } + } +} diff --git a/src/internal/options.ts b/src/internal/options.ts index a165291..4228b34 100644 --- a/src/internal/options.ts +++ b/src/internal/options.ts @@ -1486,6 +1486,19 @@ const wizardInternal = (self: Instruction, config: CliConfig.CliConfig): Effect. const CLUSTERED_REGEX = /^-{1}([^-]{2,}$)/ const FLAG_REGEX = /^(--[^=]+)(?:=(.+))?$/ +const escape = (string: string): string => { + return string + .replaceAll("\\", "\\\\") + .replaceAll("'", "'\\''") + .replaceAll("[", "\\[") + .replaceAll("]", "\\]") + .replaceAll(":", "\\:") + .replaceAll("$", "\\$") + .replaceAll("`", "\\`") + .replaceAll("(", "\\(") + .replaceAll(")", "\\)") +} + const processArgs = ( args: ReadonlyArray ): Effect.Effect> => { @@ -1809,3 +1822,65 @@ export const getFishCompletions = (self: Instruction): ReadonlyArray => } } } + +interface ZshCompletionState { + readonly conflicts: ReadonlyArray + readonly multiple: boolean +} + +/** @internal */ +export const getZshCompletions = ( + self: Instruction, + state: ZshCompletionState = { conflicts: ReadonlyArray.empty(), multiple: false } +): ReadonlyArray => { + switch (self._tag) { + case "Empty": { + return ReadonlyArray.empty() + } + case "Single": { + const names = getNames(self) + const description = getShortDescription(self) + const possibleValues = InternalPrimitive.getZshCompletions( + self.primitiveType as InternalPrimitive.Instruction + ) + const multiple = state.multiple ? "*" : "" + const conflicts = ReadonlyArray.isNonEmptyReadonlyArray(state.conflicts) + ? `(${ReadonlyArray.join(state.conflicts, " ")})` + : "" + return ReadonlyArray.map( + names, + (name) => `${conflicts}${multiple}${name}[${escape(description)}]${possibleValues}` + ) + } + case "KeyValueMap": { + return getZshCompletions(self.argumentOption as Instruction, { ...state, multiple: true }) + } + case "Map": + case "WithDefault": { + return getZshCompletions(self.options as Instruction, state) + } + case "Both": { + const left = getZshCompletions(self.left as Instruction, state) + const right = getZshCompletions(self.right as Instruction, state) + return ReadonlyArray.appendAll(left, right) + } + case "OrElse": { + const leftNames = getNames(self.left as Instruction) + const rightNames = getNames(self.right as Instruction) + const left = getZshCompletions( + self.left as Instruction, + { ...state, conflicts: ReadonlyArray.appendAll(state.conflicts, rightNames) } + ) + const right = getZshCompletions( + self.right as Instruction, + { ...state, conflicts: ReadonlyArray.appendAll(state.conflicts, leftNames) } + ) + return ReadonlyArray.appendAll(left, right) + } + case "Variadic": { + return Option.isSome(self.max) && self.max.value > 1 + ? getZshCompletions(self.argumentOption as Instruction, { ...state, multiple: true }) + : getZshCompletions(self.argumentOption as Instruction, state) + } + } +} diff --git a/src/internal/primitive.ts b/src/internal/primitive.ts index f4358e2..7f2adef 100644 --- a/src/internal/primitive.ts +++ b/src/internal/primitive.ts @@ -648,3 +648,50 @@ export const getFishCompletions = (self: Instruction): ReadonlyArray => } } } + +/** @internal */ +export const getZshCompletions = (self: Instruction): string => { + switch (self._tag) { + case "Bool": { + return "" + } + case "Choice": { + const choices = pipe( + ReadonlyArray.map(self.alternatives, ([name]) => name), + ReadonlyArray.join(" ") + ) + return `:CHOICE:(${choices})` + } + case "DateTime": { + return "" + } + case "Float": { + return "" + } + case "Integer": { + return "" + } + case "Path": { + switch (self.pathType) { + case "file": { + return self.pathExists === "yes" || self.pathExists === "either" + ? ":PATH:_files" + : "" + } + case "directory": { + return self.pathExists === "yes" || self.pathExists === "either" + ? ":PATH:_files -/" + : "" + } + case "either": { + return self.pathExists === "yes" || self.pathExists === "either" + ? ":PATH:_files" + : "" + } + } + } + case "Text": { + return "" + } + } +} diff --git a/test/Command.test.ts b/test/Command.test.ts index af1f2f7..6dba092 100644 --- a/test/Command.test.ts +++ b/test/Command.test.ts @@ -453,124 +453,25 @@ describe("Command", () => { it("should create completions for the bash shell", () => Effect.gen(function*(_) { const result = yield* _(Command.getBashCompletions(command, "forge")) - expect(result).toEqual([ - "function _forge_bash_completions() {", - " local i cur prev opts cmd", - " COMPREPLY=()", - " cur=\"${COMP_WORDS[COMP_CWORD]}\"", - " prev=\"${COMP_WORDS[COMP_CWORD-1]}\"", - " cmd=\"\"", - " opts=\"\"", - " for i in \"${COMP_WORDS[@]}\"; do", - " case \"${cmd},${i}\" in", - " \",$1\")", - " cmd=\"forge\"", - " ;;", - " forge,cache)", - " cmd=\"forge__cache\"", - " ;;", - " forge,cache,clean)", - " cmd=\"forge__cache__clean\"", - " ;;", - " forge,cache,ls)", - " cmd=\"forge__cache__ls\"", - " ;;", - " *)", - " ;;", - " esac", - " done", - " case \"${cmd}\" in", - " forge)", - " opts=\"-h --completions --help --wizard --version cache\"", - " if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " fi", - " case \"${prev}\" in", - " *)", - " COMPREPLY=()", - " ;;", - " esac", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " ;;", - " forge__cache)", - " opts=\"-h --verbose --completions --help --wizard --version clean ls\"", - " if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " fi", - " case \"${prev}\" in", - " --verbose)", - " COMPREPLY=( \"${cur}\" )", - " return 0", - " ;;", - " *)", - " COMPREPLY=()", - " ;;", - " esac", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " ;;", - " forge__cache__clean)", - " opts=\"-h --completions --help --wizard --version\"", - " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " fi", - " case \"${prev}\" in", - " *)", - " COMPREPLY=()", - " ;;", - " esac", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " ;;", - " forge__cache__ls)", - " opts=\"-h --completions --help --wizard --version\"", - " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " fi", - " case \"${prev}\" in", - " *)", - " COMPREPLY=()", - " ;;", - " esac", - " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", - " return 0", - " ;;", - " esac", - "}", - "complete -F _forge_bash_completions -o nosort -o bashdefault -o default forge" - ]) + yield* _( + Effect.promise(() => expect(result).toMatchFileSnapshot("./snapshots/bash-completions")) + ) + }).pipe(runEffect)) + + it("should create completions for the zsh shell", () => + Effect.gen(function*(_) { + const result = yield* _(Command.getZshCompletions(command, "forge")) + yield* _( + Effect.promise(() => expect(result).toMatchFileSnapshot("./snapshots/zsh-completions")) + ) }).pipe(runEffect)) it("should create completions for the fish shell", () => Effect.gen(function*(_) { const result = yield* _(Command.getFishCompletions(command, "forge")) - expect(result).toEqual([ - "complete -c forge -n \"__fish_use_subcommand\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", - "complete -c forge -n \"__fish_use_subcommand\" -s h -l help -d 'Show the help documentation for a command'", - "complete -c forge -n \"__fish_use_subcommand\" -l wizard -d 'Start wizard mode for a command'", - "complete -c forge -n \"__fish_use_subcommand\" -l version -d 'Show the version of the application'", - "complete -c forge -n \"__fish_use_subcommand\" -f -a \"cache\" -d 'The cache command does cache things'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -s h -l help -d 'Show the help documentation for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l wizard -d 'Start wizard mode for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l version -d 'Show the version of the application'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l verbose -d 'Output in verbose mode'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -f -a \"clean\"", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -f -a \"ls\"", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -s h -l help -d 'Show the help documentation for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l wizard -d 'Start wizard mode for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l version -d 'Show the version of the application'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -s h -l help -d 'Show the help documentation for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l wizard -d 'Start wizard mode for a command'", - "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l version -d 'Show the version of the application'" - ]) + yield* _( + Effect.promise(() => expect(result).toMatchFileSnapshot("./snapshots/fish-completions")) + ) }).pipe(runEffect)) }) }) diff --git a/test/snapshots/bash-completions b/test/snapshots/bash-completions new file mode 100644 index 0000000..ae70077 --- /dev/null +++ b/test/snapshots/bash-completions @@ -0,0 +1,91 @@ +[ + "function _forge_bash_completions() {", + " local i cur prev opts cmd", + " COMPREPLY=()", + " cur=\"${COMP_WORDS[COMP_CWORD]}\"", + " prev=\"${COMP_WORDS[COMP_CWORD-1]}\"", + " cmd=\"\"", + " opts=\"\"", + " for i in \"${COMP_WORDS[@]}\"; do", + " case \"${cmd},${i}\" in", + " \",$1\")", + " cmd=\"forge\"", + " ;;", + " forge,cache)", + " cmd=\"forge__cache\"", + " ;;", + " forge,cache,clean)", + " cmd=\"forge__cache__clean\"", + " ;;", + " forge,cache,ls)", + " cmd=\"forge__cache__ls\"", + " ;;", + " *)", + " ;;", + " esac", + " done", + " case \"${cmd}\" in", + " forge)", + " opts=\"-h --completions --help --wizard --version cache\"", + " if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " fi", + " case \"${prev}\" in", + " *)", + " COMPREPLY=()", + " ;;", + " esac", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " ;;", + " forge__cache)", + " opts=\"-h --verbose --completions --help --wizard --version clean ls\"", + " if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " fi", + " case \"${prev}\" in", + " --verbose)", + " COMPREPLY=( \"${cur}\" )", + " return 0", + " ;;", + " *)", + " COMPREPLY=()", + " ;;", + " esac", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " ;;", + " forge__cache__clean)", + " opts=\"-h --completions --help --wizard --version\"", + " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " fi", + " case \"${prev}\" in", + " *)", + " COMPREPLY=()", + " ;;", + " esac", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " ;;", + " forge__cache__ls)", + " opts=\"-h --completions --help --wizard --version\"", + " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " fi", + " case \"${prev}\" in", + " *)", + " COMPREPLY=()", + " ;;", + " esac", + " COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )", + " return 0", + " ;;", + " esac", + "}", + "complete -F _forge_bash_completions -o nosort -o bashdefault -o default forge", +] \ No newline at end of file diff --git a/test/snapshots/fish-completions b/test/snapshots/fish-completions new file mode 100644 index 0000000..9c78bc4 --- /dev/null +++ b/test/snapshots/fish-completions @@ -0,0 +1,22 @@ +[ + "complete -c forge -n \"__fish_use_subcommand\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", + "complete -c forge -n \"__fish_use_subcommand\" -s h -l help -d 'Show the help documentation for a command'", + "complete -c forge -n \"__fish_use_subcommand\" -l wizard -d 'Start wizard mode for a command'", + "complete -c forge -n \"__fish_use_subcommand\" -l version -d 'Show the version of the application'", + "complete -c forge -n \"__fish_use_subcommand\" -f -a \"cache\" -d 'The cache command does cache things'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -s h -l help -d 'Show the help documentation for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l wizard -d 'Start wizard mode for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l version -d 'Show the version of the application'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -l verbose -d 'Output in verbose mode'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -f -a \"clean\"", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from clean; and not __fish_seen_subcommand_from ls\" -f -a \"ls\"", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -s h -l help -d 'Show the help documentation for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l wizard -d 'Start wizard mode for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from clean\" -l version -d 'Show the version of the application'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l completions -r -f -a \"{sh'',bash'',fish'',zsh''}\"", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -s h -l help -d 'Show the help documentation for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l wizard -d 'Start wizard mode for a command'", + "complete -c forge -n \"__fish_seen_subcommand_from cache; and __fish_seen_subcommand_from ls\" -l version -d 'Show the version of the application'", +] \ No newline at end of file diff --git a/test/snapshots/zsh-completions b/test/snapshots/zsh-completions new file mode 100644 index 0000000..9932ca2 --- /dev/null +++ b/test/snapshots/zsh-completions @@ -0,0 +1,108 @@ +[ + "#compdef forge", + "", + "autoload -U is-at-least", + "", + "function _forge_zsh_completions() {", + " typeset -A opt_args", + " typeset -a _arguments_options", + " local ret=1", + "", + " if is-at-least 5.2; then", + " _arguments_options=(-s -S -C)", + " else", + " _arguments_options=(-s -C)", + " fi", + "", + " local context curcontext=\"$curcontext\" state line", + " _arguments \"${_arguments_options[@]}\" \\", + " '--completions[]:CHOICE:(sh bash fish zsh)' \\", + " '-h[Show the help documentation for a command]' \\", + " '--help[Show the help documentation for a command]' \\", + " '--wizard[Start wizard mode for a command]' \\", + " '--version[Show the version of the application]' \\", + " \":: :_forge_commands\" \\", + " \"*::: :->forge\" \\", + " && ret=0", + " case $state in", + " (forge)", + " words=($line[1] \"${words[@]}\")", + " (( CURRENT += 1 ))", + " curcontext=\"${curcontext%:*:*}:forge-command-$line[1]:\"", + " case $line[1] in", + " (cache)", + " _arguments \"${_arguments_options[@]}\" \\", + " '--completions[]:CHOICE:(sh bash fish zsh)' \\", + " '-h[Show the help documentation for a command]' \\", + " '--help[Show the help documentation for a command]' \\", + " '--wizard[Start wizard mode for a command]' \\", + " '--version[Show the version of the application]' \\", + " '--verbose[Output in verbose mode]' \\", + " \":: :_forge__cache_commands\" \\", + " \"*::: :->cache\" \\", + " && ret=0", + " case $state in", + " (cache)", + " words=($line[1] \"${words[@]}\")", + " (( CURRENT += 1 ))", + " curcontext=\"${curcontext%:*:*}:forge-cache-command-$line[1]:\"", + " case $line[1] in", + " (clean)", + " _arguments \"${_arguments_options[@]}\" \\", + " '--completions[]:CHOICE:(sh bash fish zsh)' \\", + " '-h[Show the help documentation for a command]' \\", + " '--help[Show the help documentation for a command]' \\", + " '--wizard[Start wizard mode for a command]' \\", + " '--version[Show the version of the application]' \\", + " && ret=0", + " ;;", + " (ls)", + " _arguments \"${_arguments_options[@]}\" \\", + " '--completions[]:CHOICE:(sh bash fish zsh)' \\", + " '-h[Show the help documentation for a command]' \\", + " '--help[Show the help documentation for a command]' \\", + " '--wizard[Start wizard mode for a command]' \\", + " '--version[Show the version of the application]' \\", + " && ret=0", + " ;;", + " esac", + " ;;", + " esac", + " ;;", + " esac", + " ;;", + " esac", + "}", + "", + "(( $+functions[_forge_commands] )) ||", + "_forge_commands() {", + " local commands; commands=( + 'cache:The cache command does cache things' \\ + )", + " _describe -t commands 'forge commands' commands \"$@\"", + "}", + "(( $+functions[_forge__cache_commands] )) ||", + "_forge__cache_commands() {", + " local commands; commands=( + 'clean:' \\ + 'ls:' \\ + )", + " _describe -t commands 'forge cache commands' commands \"$@\"", + "}", + "(( $+functions[_forge__cache__clean_commands] )) ||", + "_forge__cache__clean_commands() {", + " local commands; commands=()", + " _describe -t commands 'forge cache clean commands' commands \"$@\"", + "}", + "(( $+functions[_forge__cache__ls_commands] )) ||", + "_forge__cache__ls_commands() {", + " local commands; commands=()", + " _describe -t commands 'forge cache ls commands' commands \"$@\"", + "}", + "", + "if [ \"$funcstack[1]\" = \"_forge_zsh_completions\" ]; then", + " _forge_zsh_completions \"$@\"", + "else", + " compdef _forge_zsh_completions forge", + "fi", +] \ No newline at end of file