diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..73f69e09 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml new file mode 100644 index 00000000..541945bb --- /dev/null +++ b/.idea/jsLinters/eslint.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..1763e153 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..9114e226 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/plugin-autocomplete.iml b/.idea/plugin-autocomplete.iml new file mode 100644 index 00000000..d6ebd480 --- /dev/null +++ b/.idea/plugin-autocomplete.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/base.ts b/src/base.ts index f051c4a2..21bb289f 100644 --- a/src/base.ts +++ b/src/base.ts @@ -33,7 +33,7 @@ export abstract class AutocompleteBase extends Command { this.error('Missing required argument shell') } this.errorIfWindows() - if (!['bash', 'zsh'].includes(shell)) { + if (!['bash', 'fish', 'zsh'].includes(shell)) { throw new Error(`${shell} is not a supported shell for autocomplete`) } } diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index 755e8b97..cb3d1af3 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -1,3 +1,4 @@ +import * as child_process from 'child_process' import * as fs from 'fs-extra' import * as path from 'path' @@ -51,6 +52,10 @@ export default class Create extends AutocompleteBase { await fs.writeFile(this.bashCompletionFunctionPath, this.bashCompletionFunction) await fs.writeFile(this.zshSetupScriptPath, this.zshSetupScript) await fs.writeFile(this.zshCompletionFunctionPath, this.zshCompletionFunction) + if (this.config.shell === 'fish') { + debug(`fish shell detected, writing completion to ${this.fishCompletionFunctionPath}`) + await fs.writeFile(this.fishCompletionFunctionPath, this.fishCompletionFunction) + } } private get bashSetupScriptPath(): string { @@ -78,6 +83,12 @@ export default class Create extends AutocompleteBase { return path.join(this.bashFunctionsDir, `${this.cliBin}.bash`) } + private get fishCompletionFunctionPath(): string { + // dynamically load path to completions file + const dir = child_process.execSync('pkg-config --variable completionsdir fish').toString().trimRight() + return `${dir}/${this.cliBin}.fish` + } + private get zshCompletionFunctionPath(): string { // /autocomplete/functions/zsh/_ return path.join(this.zshFunctionsDir, `_${this.cliBin}`) @@ -221,13 +232,51 @@ complete -o default -F _${cliBin} ${cliBin} ` } + private get fishCompletionFunction(): string { + const cliBin = this.cliBin + const completions = [] + completions.push(` +function __fish_${cliBin}_needs_command + set cmd (commandline -opc) + if [ (count $cmd) -eq 1 ] + return 0 + else + return 1 + end +end + +function __fish_${cliBin}_using_command + set cmd (commandline -opc) + if [ (count $cmd) -gt 1 ] + if [ $argv[1] = $cmd[2] ] + return 0 + end + end + return 1 +end`, + ) + + for (const command of this.commands) { + completions.push(`complete -f -c ${cliBin} -n '__fish_${cliBin}_needs_command' -a ${command.id} -d "${command.description}"`) + const flags = command.flags || {} + Object.keys(flags) + .filter(flag => flags[flag] && !flags[flag].hidden) + .forEach(flag => { + const f = flags[flag] || {} + const shortFlag = f.char ? `-s ${f.char}` : '' + const description = f.description ? `-d "${f.description}"` : '' + const options = f.options ? `-r -a "${f.options.join(' ')}"` : '' + completions.push(`complete -f -c ${cliBin} -n ' __fish_${cliBin}_using_command ${command.id}' -l ${flag} ${shortFlag} ${options} ${description}`) + }) + } + return completions.join('\n') + } + private get zshCompletionFunction(): string { const cliBin = this.cliBin const allCommandsMeta = this.genAllCommandsMetaString const caseStatementForFlagsMeta = this.genCaseStatementForFlagsMetaString - return `#compdef ${cliBin} - _${cliBin} () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index 4a991680..45b634a5 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -18,13 +18,14 @@ export default class Index extends AutocompleteBase { static examples = [ '$ <%= config.bin %> autocomplete', '$ <%= config.bin %> autocomplete bash', + '$ <%= config.bin %> autocomplete fish', '$ <%= config.bin %> autocomplete zsh', '$ <%= config.bin %> autocomplete --refresh-cache', ] async run() { const {args, flags} = this.parse(Index) - const shell = args.shell || this.determineShell(this.config.shell) + const shell = args.shell || this.config.shell this.errorIfNotSupportedShell(shell) cli.action.start(`${chalk.bold('Building the autocomplete cache')}`) @@ -33,14 +34,14 @@ export default class Index extends AutocompleteBase { if (!flags['refresh-cache']) { const bin = this.config.bin - const tabStr = shell === 'bash' ? '' : '' - const note = shell === 'zsh' ? `After sourcing, you can run \`${chalk.cyan('$ compaudit -D')}\` to ensure no permissions conflicts are present` : 'If your terminal starts as a login shell you may need to print the init script into ~/.bash_profile or ~/.profile.' + const tabStr = this.getTabStr(shell) + const setupStep = this.getSetupStep(shell, bin) + const note = this.getNote(shell) this.log(` ${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)} -1) Add the autocomplete env var to your ${shell} profile and source it -${chalk.cyan(`$ printf "eval $(${bin} autocomplete:script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc`)} +1) ${setupStep} NOTE: ${note} @@ -52,4 +53,43 @@ Enjoy! `) } } + + private getSetupStep(shell: string, bin: string): string { + switch (shell) { + case 'bash': + case 'zsh': + return `Add the autocomplete env var to your ${shell} profile and source it +${chalk.cyan(`$ printf "$(${bin} autocomplete:script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc`)}` + case 'fish': + return `Update your shell to load the new completions +${chalk.cyan('source ~/.config/fish/config.fish')}` + default: + return '' + } + } + + private getNote(shell: string): string { + switch (shell) { + case 'bash': + return 'If your terminal starts as a login shell you may need to print the init script into ~/.bash_profile or ~/.profile.' + case 'fish': + return 'This assumes your Fish configuration is stored at ~/.config/fish/config.fish' + case 'zsh': + return `After sourcing, you can run \`${chalk.cyan('$ compaudit -D')}\` to ensure no permissions conflicts are present` + default: + return '' + } + } + + private getTabStr(shell: string): string { + switch (shell) { + case 'bash': + return '' + case 'fish': + case 'zsh': + return '' + default: + return '' + } + } } diff --git a/test/commands/autocomplete/create.test.ts b/test/commands/autocomplete/create.test.ts index 9490cc61..02cf7049 100644 --- a/test/commands/autocomplete/create.test.ts +++ b/test/commands/autocomplete/create.test.ts @@ -101,7 +101,6 @@ complete -o default -F _oclif-example oclif-example\n`) it('#zshCompletionFunction', () => { /* eslint-disable no-useless-escape */ expect(cmd.zshCompletionFunction).to.eq(`#compdef oclif-example - _oclif-example () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} diff --git a/test/commands/autocomplete/index.test.ts b/test/commands/autocomplete/index.test.ts index 710bb763..6405d412 100644 --- a/test/commands/autocomplete/index.test.ts +++ b/test/commands/autocomplete/index.test.ts @@ -13,7 +13,7 @@ skipWindows('autocomplete index', () => { Setup Instructions for OCLIF-EXAMPLE CLI Autocomplete --- 1) Add the autocomplete env var to your bash profile and source it -$ printf \"eval $(oclif-example autocomplete:script bash)\" >> ~/.bashrc; source ~/.bashrc +$ printf \"$(oclif-example autocomplete:script bash)\" >> ~/.bashrc; source ~/.bashrc NOTE: If your terminal starts as a login shell you may need to print the init script into ~/.bash_profile or ~/.profile. @@ -23,8 +23,7 @@ $ oclif-example command -- # Flag completion Enjoy! -`, - ) +`) }) test @@ -35,7 +34,7 @@ Enjoy! Setup Instructions for OCLIF-EXAMPLE CLI Autocomplete --- 1) Add the autocomplete env var to your zsh profile and source it -$ printf \"eval $(oclif-example autocomplete:script zsh)\" >> ~/.zshrc; source ~/.zshrc +$ printf \"$(oclif-example autocomplete:script zsh)\" >> ~/.zshrc; source ~/.zshrc NOTE: After sourcing, you can run \`$ compaudit -D\` to ensure no permissions conflicts are present @@ -45,6 +44,28 @@ $ oclif-example command -- # Flag completion Enjoy! +`, + ) + }) + + test + .stdout() + .command(['autocomplete', 'fish']) + .it('provides fish instructions', ctx => { + expect(ctx.stdout).to.equal(` +Setup Instructions for OCLIF-EXAMPLE CLI Autocomplete --- + +1) Update your shell to load the new completions +source ~/.config/fish/config.fish + +NOTE: This assumes your Fish configuration is stored at ~/.config/fish/config.fish + +2) Test it out, e.g.: +$ oclif-example # Command completion +$ oclif-example command -- # Flag completion + +Enjoy! + `, ) }) diff --git a/test/commands/autocomplete/script.test.ts b/test/commands/autocomplete/script.test.ts index 094f4cf2..0de7e5be 100644 --- a/test/commands/autocomplete/script.test.ts +++ b/test/commands/autocomplete/script.test.ts @@ -31,8 +31,12 @@ OCLIF_EXAMPLE_AC_ZSH_SETUP_PATH=${ test .stdout() .command(['autocomplete:script', 'fish']) - .catch(error => { - expect(error.message).to.contain('fish is not a supported shell for autocomplete') + .it('outputs fish profile config', ctx => { + expect(ctx.stdout).to.contain(` +OCLIF_EXAMPLE_AC_FISH_SETUP_PATH=${ + ctx.config.cacheDir +}/autocomplete/fish_setup && test -f $OCLIF_EXAMPLE_AC_FISH_SETUP_PATH && source $OCLIF_EXAMPLE_AC_FISH_SETUP_PATH; # oclif-example autocomplete setup +`, + ) }) - .it('errors on unsupported shell') })