From ae7af106d07c8a014ea3fd60a175b2b722d8f3e2 Mon Sep 17 00:00:00 2001 From: jayree Date: Tue, 28 Feb 2023 20:40:16 +0100 Subject: [PATCH 1/2] fix: coTopics completion --- src/autocomplete/zsh.ts | 204 +++++++++++++++++----------------- test/autocomplete/zsh.test.ts | 98 +++++++--------- 2 files changed, 146 insertions(+), 156 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 814b4d81..5d30277f 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -5,15 +5,15 @@ function sanitizeSummary(description?: string): string { if (description === undefined) { return '' } - return description - .replace(/([`"])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes - // eslint-disable-next-line no-useless-escape - .replace(/([\[\]])/g, '\\\\$1') // square brackets require double-backslashes - .split('\n')[0] // only use the first line + return ( + description + .replace(/([`"])/g, '\\\\\\$1') // backticks and double-quotes require triple-backslashes + // eslint-disable-next-line no-useless-escape + .replace(/([\[\]])/g, '\\\\$1') // square brackets require double-backslashes + .split('\n')[0] + ) // only use the first line } -const argTemplate = ' "%s")\n %s\n ;;\n' - type CommandCompletion = { id: string; summary: string; @@ -30,34 +30,37 @@ type Topic = { } export default class ZshCompWithSpaces { - protected config: Config; + protected config: Config private topics: Topic[] private commands: CommandCompletion[] - private _coTopics?: string[] + private coTopics: string[] constructor(config: Config) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() + this.coTopics = this.getCoTopics() } public generate(): string { const firstArgs: {id: string; summary?: string}[] = [] this.topics.forEach(t => { - if (!t.name.includes(':')) firstArgs.push({ - id: t.name, - summary: t.description, - }) + if (!t.name.includes(':')) + firstArgs.push({ + id: t.name, + summary: t.description, + }) }) this.commands.forEach(c => { - if (!firstArgs.find(a => a.id === c.id) && !c.id.includes(':')) firstArgs.push({ - id: c.id, - summary: c.summary, - }) + if (!firstArgs.find(a => a.id === c.id) && !c.id.includes(':')) + firstArgs.push({ + id: c.id, + summary: c.summary, + }) }) const mainArgsCaseBlock = () => { @@ -66,7 +69,7 @@ export default class ZshCompWithSpaces { for (const arg of firstArgs) { if (this.coTopics.includes(arg.id)) { // coTopics already have a completion function. - caseBlock += `${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` + caseBlock += ` ${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` } else { const cmd = this.commands.find(c => c.id === arg.id) @@ -74,16 +77,16 @@ export default class ZshCompWithSpaces { // if it's a command and has flags, inline flag completion statement. // skip it from the args statement if it doesn't accept any flag. if (Object.keys(cmd.flags).length > 0) { - caseBlock += `${arg.id})\n${this.genZshFlagArgumentsBlock(cmd.flags)} ;; \n` + caseBlock += ` ${arg.id})\n ${this.genZshFlagArgumentsBlock(cmd.flags)} ;; \n` } } else { // it's a topic, redirect to its completion function. - caseBlock += `${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` + caseBlock += ` ${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` } } } - caseBlock += 'esac\n' + caseBlock += ' esac' return caseBlock } @@ -92,7 +95,6 @@ export default class ZshCompWithSpaces { `#compdef ${this.config.bin} ${this.topics.map(t => this.genZshTopicCompFun(t.name)).join('\n')} - _${this.config.bin}() { local context state state_descr line typeset -A opt_args @@ -101,11 +103,11 @@ _${this.config.bin}() { case "$state" in cmds) - ${this.genZshValuesBlock(firstArgs)} - ;; + ${this.genZshValuesBlock({subArgs: firstArgs})} + ;; args) ${mainArgsCaseBlock()} - ;; + ;; esac } @@ -114,10 +116,10 @@ _${this.config.bin} return compFunc } - private genZshFlagArgumentsBlock(flags?: CommandFlags): string { + private genZshFlagArguments(flags?: CommandFlags): string { // if a command doesn't have flags make it only complete files // also add comp for the global `--help` flag. - if (!flags) return '_arguments -S \\\n --help"[Show help for command]" "*: :_files' + if (!flags) return '--help"[Show help for command]" "*: :_files' const flagNames = Object.keys(flags) @@ -125,7 +127,7 @@ _${this.config.bin} // Do not complete flags after a ‘--’ appearing on the line, and ignore the ‘--’. For example, with -S, in the line: // foobar -x -- -y // the ‘-x’ is considered a flag, the ‘-y’ is considered an argument, and the ‘--’ is considered to be neither. - let argumentsBlock = '_arguments -S \\\n' + let argumentsBlock = '' for (const flagName of flagNames) { const f = flags[flagName] @@ -145,27 +147,11 @@ _${this.config.bin} } else { flagSpec += `"(-${f.char} --${f.name})"{-${f.char},--${f.name}}` } - - flagSpec += `"[${f.summary}]` - - if (f.options) { - flagSpec += `:${f.name} options:(${f.options?.join(' ')})"` - } else { - flagSpec += ':file:_files"' - } + } else if (f.multiple) { + // this flag can be present multiple times on the line + flagSpec += `"*"--${f.name}` } else { - if (f.multiple) { - // this flag can be present multiple times on the line - flagSpec += '"*"' - } - - flagSpec += `--${f.name}"[${f.summary}]:` - - if (f.options) { - flagSpec += `${f.name} options:(${f.options.join(' ')})"` - } else { - flagSpec += 'file:_files"' - } + flagSpec += `--${f.name}` } } else if (f.char) { // Flag.Boolean @@ -175,6 +161,16 @@ _${this.config.bin} flagSpec += `--${f.name}"[${f.summary}]"` } + if (f.type === 'option') { + flagSpec += `"[${f.summary}]` + + if (f.options) { + flagSpec += `:${f.name} options:(${f.options?.join(' ')})"` + } else { + flagSpec += ':file:_files"' + } + } + flagSpec += ' \\\n' argumentsBlock += flagSpec } @@ -186,65 +182,78 @@ _${this.config.bin} return argumentsBlock } - private genZshValuesBlock(subArgs: {id: string; summary?: string}[]): string { - let valuesBlock = '_values "completions" \\\n' - - subArgs.forEach(subArg => { - valuesBlock += `"${subArg.id}[${subArg.summary}]" \\\n` + private genZshFlagArgumentsBlock(flags?: CommandFlags): string { + let argumentsBlock = '_arguments -S \\' + this.genZshFlagArguments(flags) + .split('\n') + .forEach(f => { + argumentsBlock += `\n ${f}` }) - return valuesBlock + return argumentsBlock } - private genZshTopicCompFun(id: string): string { - const coTopics: string[] = [] + private genZshValuesBlock(options: {id?: string; subArgs: Array<{id: string; summary?: string}>}): string { + let valuesBlock = '_values "completions"' + const {id, subArgs} = options - for (const topic of this.topics) { - for (const cmd of this.commands) { - if (topic.name === cmd.id) { - coTopics.push(topic.name) - } + subArgs.forEach(subArg => { + valuesBlock += ` \\\n "${subArg.id}[${subArg.summary}]"` + }) + + if (id) { + const cflags = this.commands.find(c => c.id === id)?.flags + + if (cflags) { + // eslint-disable-next-line no-template-curly-in-string + valuesBlock += ' \\\n "${flags[@]}"' } } - const flagArgsTemplate = ' "%s")\n %s\n ;;\n' + return valuesBlock + } + private genZshTopicCompFun(id: string): string { const underscoreSepId = id.replace(/:/g, '_') const depth = id.split(':').length - const isCotopic = coTopics.includes(id) + const isCotopic = this.coTopics.includes(id) - if (isCotopic) { - const compFuncName = `${this.config.bin}_${underscoreSepId}` + let flags = '' - const coTopicCompFunc = -`_${compFuncName}() { - _${compFuncName}_flags() { - local context state state_descr line - typeset -A opt_args + if (id) { + const cflags = this.commands.find(c => c.id === id)?.flags - ${this.genZshFlagArgumentsBlock(this.commands.find(c => c.id === id)?.flags)} - } + if (cflags) { + flags += '\n' + this.genZshFlagArguments(cflags) + .split('\n') + .forEach(f => { + flags += ` ${f}\n` + }) + flags += ' ' + } + } + if (isCotopic) { + const coTopicCompFunc = `_${this.config.bin}_${underscoreSepId}() { local context state state_descr line typeset -A opt_args + local -a flags=(%s) + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) - if [[ "\${words[CURRENT]}" == -* ]]; then - _${compFuncName}_flags - else -%s - fi + %s ;; args) - case $line[1] in -%s - *) - _${compFuncName}_flags - ;; + case $line[1] in%s + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac @@ -264,13 +273,13 @@ _${this.config.bin} summary: t.description, }) - argsBlock += util.format(argTemplate, subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) + argsBlock += util.format('\n "%s")\n %s\n ;;', subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) }) this.commands .filter(c => c.id.startsWith(id + ':') && c.id.split(':').length === depth + 1) .forEach(c => { - if (coTopics.includes(c.id)) return + if (this.coTopics.includes(c.id)) return const subArg = c.id.split(':')[depth] subArgs.push({ @@ -278,10 +287,10 @@ _${this.config.bin} summary: c.summary, }) - argsBlock += util.format(flagArgsTemplate, subArg, this.genZshFlagArgumentsBlock(c.flags)) + argsBlock += util.format('\n "%s")\n _arguments -C "*::arg:->args"\n %s\n ;;', subArg, this.genZshFlagArgumentsBlock(c.flags)) }) - return util.format(coTopicCompFunc, this.genZshValuesBlock(subArgs), argsBlock) + return util.format(coTopicCompFunc, flags, this.genZshValuesBlock({id, subArgs}), argsBlock) } let argsBlock = '' @@ -296,13 +305,13 @@ _${this.config.bin} summary: t.description, }) - argsBlock += util.format(argTemplate, subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) + argsBlock += util.format('\n "%s")\n %s\n ;;', subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) }) this.commands .filter(c => c.id.startsWith(id + ':') && c.id.split(':').length === depth + 1) .forEach(c => { - if (coTopics.includes(c.id)) return + if (this.coTopics.includes(c.id)) return const subArg = c.id.split(':')[depth] subArgs.push({ @@ -310,7 +319,7 @@ _${this.config.bin} summary: c.summary, }) - argsBlock += util.format(flagArgsTemplate, subArg, this.genZshFlagArgumentsBlock(c.flags)) + argsBlock += util.format(`\n "%s")${flags ? '\n _arguments -C "*::arg:->args"' : ''}\n %s\n ;;`, subArg, this.genZshFlagArgumentsBlock(c.flags)) }) const topicCompFunc = @@ -322,22 +331,19 @@ _${this.config.bin} case "$state" in cmds) -%s + %s ;; args) - case $line[1] in -%s + case $line[1] in%s esac ;; - esac + esac } ` - return util.format(topicCompFunc, this.genZshValuesBlock(subArgs), argsBlock) + return util.format(topicCompFunc, this.genZshValuesBlock({subArgs}), argsBlock) } - private get coTopics(): string [] { - if (this._coTopics) return this._coTopics - + private getCoTopics(): string[] { const coTopics: string[] = [] for (const topic of this.topics) { @@ -348,9 +354,7 @@ _${this.config.bin} } } - this._coTopics = coTopics - - return this._coTopics + return coTopics } private getTopics(): Topic[] { diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index 86b8cb09..a42bdbe2 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -185,19 +185,17 @@ _test-cli_app() { case "$state" in cmds) -_values "completions" \\ -"execute[execute code]" \\ - + _values "completions" \\ + "execute[execute code]" ;; args) case $line[1] in "execute") _test-cli_app_execute ;; - esac ;; - esac + esac } _test-cli_app_execute() { @@ -208,70 +206,60 @@ _test-cli_app_execute() { case "$state" in cmds) -_values "completions" \\ -"code[execute code]" \\ - + _values "completions" \\ + "code[execute code]" ;; args) case $line[1] in "code") _arguments -S \\ ---help"[Show help for command]" \\ -"*: :_files" - ;; - + --help"[Show help for command]" \\ + "*: :_files" + ;; esac ;; - esac + esac } _test-cli_deploy() { - _test-cli_deploy_flags() { - local context state state_descr line - typeset -A opt_args - - _arguments -S \\ -"*"{-m,--metadata}"[]:file:_files" \\ -"(-a --api-version)"{-a,--api-version}"[]:file:_files" \\ ---json"[Format output as json.]" \\ -"(-i --ignore-errors)"{-i,--ignore-errors}"[Ignore errors.]" \\ ---help"[Show help for command]" \\ -"*: :_files" - } - local context state state_descr line typeset -A opt_args + local -a flags=( + "*"{-m,--metadata}"[]:file:_files" \\ + "(-a --api-version)"{-a,--api-version}"[]:file:_files" \\ + --json"[Format output as json.]" \\ + "(-i --ignore-errors)"{-i,--ignore-errors}"[Ignore errors.]" \\ + --help"[Show help for command]" \\ + "*: :_files" + ) + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) - if [[ "\${words[CURRENT]}" == -* ]]; then - _test-cli_deploy_flags - else -_values "completions" \\ -"functions[Deploy a function.]" \\ - - fi + _values "completions" \\ + "functions[Deploy a function.]" \\ + "\${flags[@]}" ;; args) case $line[1] in "functions") + _arguments -C "*::arg:->args" _arguments -S \\ -"(-b --branch)"{-b,--branch}"[]:file:_files" \\ ---help"[Show help for command]" \\ -"*: :_files" - ;; - - *) - _test-cli_deploy_flags - ;; + "(-b --branch)"{-b,--branch}"[]:file:_files" \\ + --help"[Show help for command]" \\ + "*: :_files" + ;; + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac } - _test-cli() { local context state state_descr line typeset -A opt_args @@ -281,22 +269,20 @@ _test-cli() { case "$state" in cmds) _values "completions" \\ -"app[execute code]" \\ -"deploy[Deploy a project]" \\ -"search[Search for a command]" \\ - - ;; + "app[execute code]" \\ + "deploy[Deploy a project]" \\ + "search[Search for a command]" + ;; args) case $line[1] in -app) - _test-cli_app - ;; -deploy) - _test-cli_deploy - ;; -esac - - ;; + app) + _test-cli_app + ;; + deploy) + _test-cli_deploy + ;; + esac + ;; esac } From 6baae455c93e5ec601b939620861c5c2475e3e56 Mon Sep 17 00:00:00 2001 From: jayree Date: Wed, 1 Mar 2023 16:12:12 +0100 Subject: [PATCH 2/2] chore: restore default help --- src/autocomplete/zsh.ts | 197 ++++++++++++---------------------- test/autocomplete/zsh.test.ts | 46 ++++++-- 2 files changed, 106 insertions(+), 137 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 5d30277f..a0b362dd 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -46,7 +46,7 @@ export default class ZshCompWithSpaces { } public generate(): string { - const firstArgs: {id: string; summary?: string}[] = [] + const firstArgs: Array<{id: string; summary?: string}> = [] this.topics.forEach(t => { if (!t.name.includes(':')) @@ -64,12 +64,12 @@ export default class ZshCompWithSpaces { }) const mainArgsCaseBlock = () => { - let caseBlock = 'case $line[1] in\n' + let caseBlock = '' for (const arg of firstArgs) { if (this.coTopics.includes(arg.id)) { // coTopics already have a completion function. - caseBlock += ` ${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` + caseBlock += `\n ${arg.id})\n _arguments -C "*::arg:->args"\n _${this.config.bin}_${arg.id}\n ;;` } else { const cmd = this.commands.find(c => c.id === arg.id) @@ -77,57 +77,61 @@ export default class ZshCompWithSpaces { // if it's a command and has flags, inline flag completion statement. // skip it from the args statement if it doesn't accept any flag. if (Object.keys(cmd.flags).length > 0) { - caseBlock += ` ${arg.id})\n ${this.genZshFlagArgumentsBlock(cmd.flags)} ;; \n` + caseBlock += `\n ${arg.id})\n _arguments -C "*::arg:->args"\n ${this.genZshFlagArgumentsBlock(cmd.flags)} ;;` } } else { // it's a topic, redirect to its completion function. - caseBlock += ` ${arg.id})\n _${this.config.bin}_${arg.id}\n ;;\n` + caseBlock += `\n ${arg.id})\n _arguments -C "*::arg:->args"\n _${this.config.bin}_${arg.id}\n ;;` } } } - caseBlock += ' esac' - return caseBlock } - const compFunc = -`#compdef ${this.config.bin} + let flags = '\n --help"[Show help]" \\' + flags += '\n --version"[Show version]"\n ' + + const compFunc = `#compdef ${this.config.bin} ${this.topics.map(t => this.genZshTopicCompFun(t.name)).join('\n')} _${this.config.bin}() { local context state state_descr line typeset -A opt_args - _arguments -C "1: :->cmds" "*::arg:->args" + local -a flags=(%s) + + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) - ${this.genZshValuesBlock({subArgs: firstArgs})} + %s \\ + "\${flags[@]}" ;; args) - ${mainArgsCaseBlock()} + case $line[1] in%s + *) + _arguments -S \\ + "\${flags[@]}" + ;; + esac ;; esac } _${this.config.bin} ` - return compFunc + return util.format(compFunc, flags, this.genZshValuesBlock(firstArgs), mainArgsCaseBlock()) } - private genZshFlagArguments(flags?: CommandFlags): string { + private genZshFlagArguments(flags?: CommandFlags): string[] { // if a command doesn't have flags make it only complete files // also add comp for the global `--help` flag. - if (!flags) return '--help"[Show help for command]" "*: :_files' + if (!flags) return ['--help"[Show help for command]" \\', '"*: :_files"'] const flagNames = Object.keys(flags) - // `-S`: - // Do not complete flags after a ‘--’ appearing on the line, and ignore the ‘--’. For example, with -S, in the line: - // foobar -x -- -y - // the ‘-x’ is considered a flag, the ‘-y’ is considered an argument, and the ‘--’ is considered to be neither. - let argumentsBlock = '' + const argumentsArray: string[] = [] for (const flagName of flagNames) { const f = flags[flagName] @@ -171,45 +175,37 @@ _${this.config.bin} } } - flagSpec += ' \\\n' - argumentsBlock += flagSpec + flagSpec += ' \\' + argumentsArray.push(flagSpec) } // add global `--help` flag - argumentsBlock += '--help"[Show help for command]" \\\n' + argumentsArray.push('--help"[Show help for command]" \\') // complete files if `-` is not present on the current line - argumentsBlock += '"*: :_files"' + argumentsArray.push('"*: :_files"') - return argumentsBlock + return argumentsArray } private genZshFlagArgumentsBlock(flags?: CommandFlags): string { + // `-S`: + // Do not complete flags after a ‘--’ appearing on the line, and ignore the ‘--’. For example, with -S, in the line: + // foobar -x -- -y + // the ‘-x’ is considered a flag, the ‘-y’ is considered an argument, and the ‘--’ is considered to be neither. let argumentsBlock = '_arguments -S \\' - this.genZshFlagArguments(flags) - .split('\n') - .forEach(f => { + this.genZshFlagArguments(flags).forEach(f => { argumentsBlock += `\n ${f}` }) return argumentsBlock } - private genZshValuesBlock(options: {id?: string; subArgs: Array<{id: string; summary?: string}>}): string { + private genZshValuesBlock(subArgs: Array<{id: string; summary?: string}>): string { let valuesBlock = '_values "completions"' - const {id, subArgs} = options subArgs.forEach(subArg => { valuesBlock += ` \\\n "${subArg.id}[${subArg.summary}]"` }) - if (id) { - const cflags = this.commands.find(c => c.id === id)?.flags - - if (cflags) { - // eslint-disable-next-line no-template-curly-in-string - valuesBlock += ' \\\n "${flags[@]}"' - } - } - return valuesBlock } @@ -217,84 +213,17 @@ _${this.config.bin} const underscoreSepId = id.replace(/:/g, '_') const depth = id.split(':').length - const isCotopic = this.coTopics.includes(id) - let flags = '' - if (id) { - const cflags = this.commands.find(c => c.id === id)?.flags - - if (cflags) { - flags += '\n' - this.genZshFlagArguments(cflags) - .split('\n') - .forEach(f => { - flags += ` ${f}\n` - }) - flags += ' ' - } - } - - if (isCotopic) { - const coTopicCompFunc = `_${this.config.bin}_${underscoreSepId}() { - local context state state_descr line - typeset -A opt_args - - local -a flags=(%s) - - _arguments -C "1: :->cmds" "*: :->args" - - case "$state" in - cmds) - %s - ;; - args) - case $line[1] in%s - *) - _arguments -S \\ - "\${flags[@]}" - ;; - esac - ;; - esac -} -` - const subArgs: {id: string; summary?: string}[] = [] - - let argsBlock = '' - - this.topics - .filter(t => t.name.startsWith(id + ':') && t.name.split(':').length === depth + 1) - .forEach(t => { - const subArg = t.name.split(':')[depth] - - subArgs.push({ - id: subArg, - summary: t.description, - }) - - argsBlock += util.format('\n "%s")\n %s\n ;;', subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) - }) - - this.commands - .filter(c => c.id.startsWith(id + ':') && c.id.split(':').length === depth + 1) - .forEach(c => { - if (this.coTopics.includes(c.id)) return - const subArg = c.id.split(':')[depth] - - subArgs.push({ - id: subArg, - summary: c.summary, - }) - - argsBlock += util.format('\n "%s")\n _arguments -C "*::arg:->args"\n %s\n ;;', subArg, this.genZshFlagArgumentsBlock(c.flags)) - }) + const cflags = this.commands.find(c => c.id === id)?.flags + this.genZshFlagArguments(cflags).forEach(f => { + flags += `\n ${f}` + }) + flags += '\n ' - return util.format(coTopicCompFunc, flags, this.genZshValuesBlock({id, subArgs}), argsBlock) - } let argsBlock = '' - const subArgs: {id: string; summary?: string}[] = [] + const subArgs: Array<{id: string; summary?: string}> = [] this.topics .filter(t => t.name.startsWith(id + ':') && t.name.split(':').length === depth + 1) .forEach(t => { @@ -305,42 +234,48 @@ _${this.config.bin} summary: t.description, }) - argsBlock += util.format('\n "%s")\n %s\n ;;', subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) + argsBlock += util.format('\n "%s")\n _arguments -C "*::arg:->args"\n %s\n ;;', subArg, `_${this.config.bin}_${underscoreSepId}_${subArg}`) }) - this.commands - .filter(c => c.id.startsWith(id + ':') && c.id.split(':').length === depth + 1) - .forEach(c => { - if (this.coTopics.includes(c.id)) return - const subArg = c.id.split(':')[depth] + for (const c of this.commands.filter(c => c.id.startsWith(id + ':') && c.id.split(':').length === depth + 1)) { + if (!this.coTopics?.includes(c.id)) { + const subArg = c.id.split(':')[depth] - subArgs.push({ - id: subArg, - summary: c.summary, - }) + subArgs.push({ + id: subArg, + summary: c.summary, + }) - argsBlock += util.format(`\n "%s")${flags ? '\n _arguments -C "*::arg:->args"' : ''}\n %s\n ;;`, subArg, this.genZshFlagArgumentsBlock(c.flags)) - }) + const block = this.genZshFlagArgumentsBlock(c.flags) + argsBlock += util.format('\n "%s")\n _arguments -C "*::arg:->args"\n %s\n ;;', subArg, block) + } + } - const topicCompFunc = -`_${this.config.bin}_${underscoreSepId}() { + const topicCompFunc = `_${this.config.bin}_${underscoreSepId}() { local context state state_descr line typeset -A opt_args - _arguments -C "1: :->cmds" "*::arg:->args" + local -a flags=(%s) + + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) - %s + %s \\ + "\${flags[@]}" ;; args) case $line[1] in%s + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac } ` - return util.format(topicCompFunc, this.genZshValuesBlock({subArgs}), argsBlock) + return util.format(topicCompFunc, flags, this.genZshValuesBlock(subArgs), argsBlock) } private getCoTopics(): string[] { @@ -358,7 +293,8 @@ _${this.config.bin} } private getTopics(): Topic[] { - const topics = this.config.topics.filter((topic: Interfaces.Topic) => { + const topics = this.config.topics + .filter((topic: Interfaces.Topic) => { // it is assumed a topic has a child if it has children const hasChild = this.config.topics.some(subTopic => subTopic.name.includes(`${topic.name}:`)) return hasChild @@ -430,4 +366,3 @@ _${this.config.bin} return cmds } } - diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index a42bdbe2..179c7701 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -181,18 +181,29 @@ _test-cli_app() { local context state state_descr line typeset -A opt_args - _arguments -C "1: :->cmds" "*::arg:->args" + local -a flags=( + --help"[Show help for command]" \\ + "*: :_files" + ) + + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) _values "completions" \\ - "execute[execute code]" + "execute[execute code]" \\ + "\${flags[@]}" ;; args) case $line[1] in "execute") + _arguments -C "*::arg:->args" _test-cli_app_execute ;; + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac @@ -202,20 +213,31 @@ _test-cli_app_execute() { local context state state_descr line typeset -A opt_args - _arguments -C "1: :->cmds" "*::arg:->args" + local -a flags=( + --help"[Show help for command]" \\ + "*: :_files" + ) + + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) _values "completions" \\ - "code[execute code]" + "code[execute code]" \\ + "\${flags[@]}" ;; args) case $line[1] in "code") + _arguments -C "*::arg:->args" _arguments -S \\ --help"[Show help for command]" \\ "*: :_files" ;; + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac @@ -264,23 +286,35 @@ _test-cli() { local context state state_descr line typeset -A opt_args - _arguments -C "1: :->cmds" "*::arg:->args" + local -a flags=( + --help"[Show help]" \\ + --version"[Show version]" + ) + + _arguments -C "1: :->cmds" "*: :->args" case "$state" in cmds) _values "completions" \\ "app[execute code]" \\ "deploy[Deploy a project]" \\ - "search[Search for a command]" + "search[Search for a command]" \\ + "\${flags[@]}" ;; args) case $line[1] in app) + _arguments -C "*::arg:->args" _test-cli_app ;; deploy) + _arguments -C "*::arg:->args" _test-cli_deploy ;; + *) + _arguments -S \\ + "\${flags[@]}" + ;; esac ;; esac