Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature dynamic autocomplete for target orgs #614

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5b5325d
feat: get all authorized orgs from command 'sf org auth', and display…
gilgourevitch Jan 25, 2024
7e179fa
feat: works for bash
gilgourevitch Jan 26, 2024
b453c89
feat: refine zsh version for colon-separated commands
gilgourevitch Jan 29, 2024
3fb293a
feat: remove unecessary code from index
gilgourevitch Jan 29, 2024
cd7d261
feat: powershell version
gilgourevitch Jan 31, 2024
52d727e
feat: sort org list for all shells
gilgourevitch Jan 31, 2024
82dd62e
feat: remove warnings from cli when retrieving orgs
gilgourevitch Jan 31, 2024
5c4159e
test: tests #bashCompletionFunction and #bashCompletionFunction with …
gilgourevitch Feb 1, 2024
5769b1d
test: bash generates a valid completion file
gilgourevitch Feb 1, 2024
a846c01
test: bash generates a valid completion file with an alias
gilgourevitch Feb 1, 2024
eb9c41d
test: bash generates a valid completion file with multiple aliases
gilgourevitch Feb 1, 2024
b9d0fb8
test: #zshCompletionFunction
gilgourevitch Feb 1, 2024
c6ed843
test: zsh generates a valid completion file
gilgourevitch Feb 1, 2024
42f4699
test: generates a valid completion file with a bin alias
gilgourevitch Feb 1, 2024
5615e3c
test: generates a valid completion file with multiple bin aliases
gilgourevitch Feb 1, 2024
418e3a7
test: powershell generates a valid completion file
gilgourevitch Feb 1, 2024
93bb0a9
test: powershell generates a valid completion file with a bin alias
gilgourevitch Feb 1, 2024
d73229a
test: powershell generates a valid completion file with multiple bin …
gilgourevitch Feb 1, 2024
38ffe82
fix: use $null instead of null to mask warnings
gilgourevitch Feb 1, 2024
c2c6c41
feat: use a new flag to display up-to-date dynamic org list, but have…
gilgourevitch Feb 2, 2024
0f88fe0
test: bash tests fixed
gilgourevitch Feb 5, 2024
76b67b7
test: powershell tests fixed
gilgourevitch Feb 5, 2024
bef1088
test: zsh tests fixed
gilgourevitch Feb 5, 2024
b2fd8b0
test: fix indentation for installations instructions
gilgourevitch Feb 5, 2024
f9d7e2e
test: private methods tests fixed
gilgourevitch Feb 5, 2024
727cf8a
Merge branch 'main' into feature--dynamic-autocomplete-for-target-org
gilgourevitch Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions src/autocomplete/bash-spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@ _<CLI_BIN>_autocomplete()
<BASH_COMMANDS_WITH_FLAGS_LIST>
"

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
Expand All @@ -38,26 +59,31 @@ _<CLI_BIN>_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
Expand All @@ -69,9 +95,15 @@ _<CLI_BIN>_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 _<CLI_BIN>_autocomplete <CLI_BIN>
Expand Down
36 changes: 34 additions & 2 deletions src/autocomplete/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,33 @@ _<CLI_BIN>_autocomplete()
<BASH_COMMANDS_WITH_FLAGS_LIST>
"

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
Expand All @@ -22,9 +47,16 @@ _<CLI_BIN>_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

Expand Down
76 changes: 65 additions & 11 deletions src/autocomplete/powershell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -194,6 +233,13 @@ $scriptblock = {
# Complete flags
# \`cli config list -<TAB>\`
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\`).
Expand All @@ -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 {
Expand Down
38 changes: 35 additions & 3 deletions src/autocomplete/zsh.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 {
Expand Down Expand Up @@ -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}() {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) => {
Expand Down
12 changes: 11 additions & 1 deletion src/commands/autocomplete/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}"`
})
Expand Down Expand Up @@ -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}
Expand Down
Loading