diff --git a/src/autocomplete/bash-spaces.ts b/src/autocomplete/bash-spaces.ts index 2bd3ad98..fc365d05 100644 --- a/src/autocomplete/bash-spaces.ts +++ b/src/autocomplete/bash-spaces.ts @@ -14,6 +14,27 @@ __autocomplete() " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + function __trim_colon_commands() { # Turn $commands into an array @@ -38,26 +59,31 @@ __autocomplete() } if [[ "$cur" != "-"* ]]; then - # Command - __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else - # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") - normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" + # Command + __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) - # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") - colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") + normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" - if [[ -z "$normalizedCommand" ]]; then - # If there is no normalizedCommand yet the user hasn't typed in a full command - # So we should trim all subcommands & flags from $commands so we can suggest all top level commands - opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') - else - # Filter $commands to just the ones that match the $normalizedCommand and turn into an array - commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) - # Trim higher level and subcommands from the subcommands to suggest - __trim_colon_commands "$colonPrefix" + # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") + colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + + if [[ -z "$normalizedCommand" ]]; then + # If there is no normalizedCommand yet the user hasn't typed in a full command + # So we should trim all subcommands & flags from $commands so we can suggest all top level commands + opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') + else + # Filter $commands to just the ones that match the $normalizedCommand and turn into an array + commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) + # Trim higher level and subcommands from the subcommands to suggest + __trim_colon_commands "$colonPrefix" - opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + fi fi else # Flag @@ -69,9 +95,15 @@ __autocomplete() # The line below finds the command in $commands using grep # Then, using sed, it removes everything from the found command before the --flags (e.g. "command:subcommand:subsubcom --flag1 --flag2" -> "--flag1 --flag2") opts=$(printf "%s " "\${commands[@]}" | grep "\${normalizedCommand}" | sed -n "s/^\${normalizedCommand} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi - COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + fi } complete -F __autocomplete diff --git a/src/autocomplete/bash.ts b/src/autocomplete/bash.ts index a1de4d90..432310ed 100644 --- a/src/autocomplete/bash.ts +++ b/src/autocomplete/bash.ts @@ -10,8 +10,33 @@ __autocomplete() " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + + if [[ "$cur" != "-"* ]]; then + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ \${COMP_WORDS[2]} == ":" ]]; then @@ -22,9 +47,16 @@ __autocomplete() __COMP_WORDS="\${COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "\${__COMP_WORDS}" | sed -n "s/^\${__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + fi __ltrim_colon_completions "$cur" return 0 diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 287f2151..b02bdd83 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -126,6 +126,45 @@ ${hashtables.join('\n')} using namespace System.Management.Automation using namespace System.Management.Automation.Language +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -194,6 +233,13 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -206,21 +252,29 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index afa5de8c..49af3893 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -1,5 +1,6 @@ import {Command, Config, Interfaces} from '@oclif/core' import * as ejs from 'ejs' +import {execSync} from 'node:child_process' import * as util from 'node:util' const argTemplate = ' "%s")\n %s\n ;;\n' @@ -26,12 +27,15 @@ export default class ZshCompWithSpaces { private commands: CommandCompletion[] + private orgs: string[] + private topics: Topic[] - constructor(config: Config) { + constructor(config: Config, orgs?: string[]) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() + this.orgs = orgs || this.getOrgs() } public generate(): string { @@ -84,6 +88,11 @@ export default class ZshCompWithSpaces { return `#compdef ${this.config.bin} ${this.config.binAliases?.map((a) => `compdef ${a}=${this.config.bin}`).join('\n') ?? ''} +_orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + _describe -t orgs 'orgs' orgs && return 0 +} + ${this.topics.map((t) => this.genZshTopicCompFun(t.name)).join('\n')} _${this.config.bin}() { @@ -124,6 +133,10 @@ _${this.config.bin} return this._coTopics } + private genOrgs(): string { + return this.orgs.join('\n') + } + private genZshFlagArgumentsBlock(flags?: CommandFlags): string { // if a command doesn't have flags make it only complete files // also add comp for the global `--help` flag. @@ -159,7 +172,11 @@ _${this.config.bin} flagSpec += `"[${flagSummary}]` - flagSpec += f.options ? `:${f.name} options:(${f.options?.join(' ')})"` : ':file:_files"' + flagSpec += f.options + ? `:${f.name} options:(${f.options?.join(' ')})"` + : f.name === 'target-org' + ? ':org:_orgs"' + : ':file:_files"' } else { if (f.multiple) { // this flag can be present multiple times on the line @@ -168,7 +185,11 @@ _${this.config.bin} flagSpec += `--${f.name}"[${flagSummary}]:` - flagSpec += f.options ? `${f.name} options:(${f.options.join(' ')})"` : 'file:_files"' + flagSpec += f.options + ? `${f.name} options:(${f.options.join(' ')})"` + : f.name === 'target-org' + ? ':org:_orgs"' + : ':file:_files"' } } else if (f.char) { // Flag.Boolean @@ -379,6 +400,17 @@ _${this.config.bin} return cmds } + private getOrgs(): string[] { + const orgsJson = JSON.parse(execSync('sf org list auth --json 2>/dev/null').toString()) + const result: string[] = [] + for (const element of orgsJson.result) { + if (element.alias) result.push(element.alias) + else result.push(element.username) + } + + return result.sort() + } + private getTopics(): Topic[] { const topics = this.config.topics .filter((topic: Interfaces.Topic) => { diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index 3510e3f1..d76036d1 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -199,7 +199,12 @@ export default class Create extends AutocompleteBase { const isOption = f.type === 'option' const name = isBoolean ? flag : `${flag}=-` const multiple = isOption && f.multiple ? '*' : '' - const valueCmpl = isBoolean ? '' : ':' + let valueCmpl = isBoolean ? '' : ':' + + if (name === 'target-org=-') { + valueCmpl += `array_values:((\${(@)$(_orgs)}))` + } + const completion = `${multiple}--${name}[${sanitizeDescription(f.summary || f.description)}]${valueCmpl}` return `"${completion}"` }) @@ -229,6 +234,11 @@ _${cliBin} () { local _cur=\${words[CURRENT]} local -a _command_flags=() + _orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + echo "$orgs" + } + ## public cli commands & flags local -a _all_commands=( ${allCommandsMeta} diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index 72458e69..f1e9ce54 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -1,9 +1,10 @@ -import {Args, Flags, ux} from '@oclif/core' +import {Args, Flags} from '@oclif/core' import chalk from 'chalk' +import {existsSync, readFileSync, readdirSync} from 'node:fs' import {EOL} from 'node:os' +import {default as path} from 'node:path' import {AutocompleteBase} from '../../base.js' -import Create from './create.js' export default class Index extends AutocompleteBase { static args = { @@ -25,6 +26,7 @@ export default class Index extends AutocompleteBase { ] static flags = { + 'display-orgs': Flags.boolean({char: 'd', description: 'Display authenticated orgs.'}), 'refresh-cache': Flags.boolean({char: 'r', description: 'Refresh cache (ignores displaying instructions)'}), } @@ -38,9 +40,30 @@ export default class Index extends AutocompleteBase { ) } - ux.action.start(`${chalk.bold('Building the autocomplete cache')}`) - await Create.run([], this.config) - ux.action.stop() + if (flags['display-orgs']) { + const sfDir = path.join(this.config.home, '.sfdx') + const orgs: string[] = readdirSync(sfDir) + .filter((element) => element.match(/^.*@.*\.json/) !== null) + .map((element) => element.replace('.json', '')) + + let orgsAliases = [] + const aliasFilename = path.join(sfDir, 'alias.json') + if (existsSync(aliasFilename)) { + orgsAliases = JSON.parse(readFileSync(aliasFilename).toString()).orgs + for (const [alias, username] of Object.entries(orgsAliases)) { + const i = orgs.indexOf(username as string) + if (i > -1) { + orgs[i] = alias + } + } + } + + this.log( + orgs.join(` +`), + ) + flags['refresh-cache'] = true + } if (!flags['refresh-cache']) { this.printShellInstructions(shell) diff --git a/test/autocomplete/bash.test.ts b/test/autocomplete/bash.test.ts index d14331a2..63107da7 100644 --- a/test/autocomplete/bash.test.ts +++ b/test/autocomplete/bash.test.ts @@ -199,15 +199,40 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -218,9 +243,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 @@ -243,15 +275,40 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + + if [[ "$cur" != "-"* ]]; then + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -262,9 +319,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 @@ -288,15 +352,40 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -307,9 +396,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 diff --git a/test/autocomplete/powershell.test.ts b/test/autocomplete/powershell.test.ts index e83a8253..5c12a608 100644 --- a/test/autocomplete/powershell.test.ts +++ b/test/autocomplete/powershell.test.ts @@ -207,6 +207,45 @@ describe('powershell completion', () => { using namespace System.Management.Automation using namespace System.Management.Automation.Language +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -259,6 +298,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -338,6 +378,13 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -350,21 +397,29 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { @@ -399,6 +454,45 @@ Register-ArgumentCompleter -Native -CommandName test-cli -ScriptBlock $scriptblo using namespace System.Management.Automation using namespace System.Management.Automation.Language +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -451,6 +545,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -530,6 +625,13 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -542,21 +644,29 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { @@ -591,6 +701,45 @@ Register-ArgumentCompleter -Native -CommandName @("test","test-cli") -ScriptBloc using namespace System.Management.Automation using namespace System.Management.Automation.Language +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -643,6 +792,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -722,6 +872,13 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -734,21 +891,29 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index 53e3c821..7dac6b98 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -212,6 +212,11 @@ skipWindows('zsh comp', () => { expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli +_orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args @@ -345,6 +350,11 @@ _test-cli expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli compdef testing=test-cli +_orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args @@ -479,6 +489,11 @@ _test-cli compdef testing=test-cli compdef testing2=test-cli +_orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args diff --git a/test/commands/autocomplete/create.test.ts b/test/commands/autocomplete/create.test.ts index 2e40073a..8046c383 100644 --- a/test/commands/autocomplete/create.test.ts +++ b/test/commands/autocomplete/create.test.ts @@ -77,8 +77,33 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ \${COMP_WORDS[2]} == ":" ]]; then @@ -89,9 +114,16 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json __COMP_WORDS="\${COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "\${__COMP_WORDS}" | sed -n "s/^\${__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + fi __ltrim_colon_completions "$cur" return 0 @@ -131,6 +163,27 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + function __trim_colon_commands() { # Turn $commands into an array @@ -155,26 +208,31 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json } if [[ "$cur" != "-"* ]]; then - # Command - __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else - # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") - normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" + # Command + __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) - # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") - colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") + normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" - if [[ -z "$normalizedCommand" ]]; then - # If there is no normalizedCommand yet the user hasn't typed in a full command - # So we should trim all subcommands & flags from $commands so we can suggest all top level commands - opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') - else - # Filter $commands to just the ones that match the $normalizedCommand and turn into an array - commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) - # Trim higher level and subcommands from the subcommands to suggest - __trim_colon_commands "$colonPrefix" + # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") + colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + + if [[ -z "$normalizedCommand" ]]; then + # If there is no normalizedCommand yet the user hasn't typed in a full command + # So we should trim all subcommands & flags from $commands so we can suggest all top level commands + opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') + else + # Filter $commands to just the ones that match the $normalizedCommand and turn into an array + commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) + # Trim higher level and subcommands from the subcommands to suggest + __trim_colon_commands "$colonPrefix" - opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + fi fi ${'else '} # Flag @@ -186,9 +244,15 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json # The line below finds the command in $commands using grep # Then, using sed, it removes everything from the found command before the --flags (e.g. "command:subcommand:subsubcom --flag1 --flag2" -> "--flag1 --flag2") opts=$(printf "%s " "\${commands[@]}" | grep "\${normalizedCommand}" | sed -n "s/^\${normalizedCommand} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi - COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + fi } complete -F _oclif-example_autocomplete oclif-example\n`) @@ -203,6 +267,11 @@ _oclif-example () { local _cur=\${words[CURRENT]} local -a _command_flags=() + _orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + echo "$orgs" + } + ## public cli commands & flags local -a _all_commands=( "autocomplete:display autocomplete instructions" diff --git a/test/helpers/orgruntest.ts b/test/helpers/orgruntest.ts new file mode 100644 index 00000000..376e7430 --- /dev/null +++ b/test/helpers/orgruntest.ts @@ -0,0 +1 @@ +export const testOrgs = ['org1alias', 'org2.username@org.com', 'org3alias']