You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I don't think this is worth spending time on right now. But it might not be so hard, given that we could just steal the battle-tested completion scripts.
To illustrate the technique, let's look at how gh completion works:
$ gh --versiongh version 2.14.3 (2022-08-02)https://github.com/cli/cli/releases/tag/v2.14.3
$ gh completion --helpGenerate shell completion scripts for GitHub CLI commands.When installing GitHub CLI through a package manager, it's possible thatno additional shell configuration is necessary to gain completion support. ForHomebrew, see <https://docs.brew.sh/Shell-Completion>If you need to set up completions manually, follow the instructions below. The exactconfig file locations might vary based on your system. Make sure to restart yourshell before testing whether completions are working.### bashFirst, ensure that you install `bash-completion` using your package manager.After, add this to your `~/.bash_profile`: eval "$(gh completion -s bash)"### zshGenerate a `_gh` completion script and put it somewhere in your `$fpath`: gh completion -s zsh > /usr/local/share/zsh/site-functions/_ghEnsure that the following is present in your `~/.zshrc`: autoload -U compinit compinit -iZsh version 5.7 or later is recommended.### fishGenerate a `gh.fish` completion script: gh completion -s fish > ~/.config/fish/completions/gh.fish### PowerShellOpen your profile script with: mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue notepad $profileAdd the line and save the file: Invoke-Expression -Command $(gh completion -s powershell | Out-String)USAGE gh completion -s <shell>FLAGS -s, --shell string Shell type: {bash|zsh|fish|powershell}INHERITED FLAGS --help Show help for commandLEARN MORE Use 'gh <command> <subcommand> --help' for more information about a command. Read the manual at https://cli.github.com/manual
So what happens when we run gh completion -s bash? It outputs a script that, instead of hard coding the completions, calls gh __complete internally.
For example, if you type gh issue c and then press Tab, the completion script calls gh __complete issue c.
$ gh __complete issue cclose Close issuecomment Add a comment to an issuecreate Create a new issue:4Completion ended with directive: ShellCompDirectiveNoFileComp
The advantage is that the end user can:
Install some version of gh and its completion script
Upgrade the gh version, which gains new command foo
Have up-to-date completions, including for the new command foo, even without updating their installed completion script
This technique is used by https://github.com/spf13/cobra, which is a Go CLI library that is used by some prominent tools like the GitHub CLI, Kubernetes, and Hugo.
gh completion -s bash
Outputs:
# bash completion V2 for gh -*- shell-script -*-__gh_debug()
{
if [[ -n${BASH_COMP_DEBUG_FILE:-} ]];thenecho"$*">>"${BASH_COMP_DEBUG_FILE}"fi
}
# Macs have bash3 for which the bash-completion package doesn't include# _init_completion. This is a minimal version of that function.__gh_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
# This function calls the gh program to obtain the completion# results and the directive. It fills the 'out' and 'directive' vars.__gh_get_completion_results() {
local requestComp lastParam lastChar args
# Prepare the command to request completions for the program.# Calling ${words[0]} instead of directly gh allows to handle aliases
args=("${words[@]:1}")
requestComp="${words[0]} __complete ${args[*]}"
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
__gh_debug "lastParam ${lastParam}, lastChar ${lastChar}"if [ -z"${cur}" ] && [ "${lastChar}"!="=" ];then# If the last parameter is complete (there is a space following it)# We add an extra empty parameter so we can indicate this to the go method.
__gh_debug "Adding extra empty parameter"
requestComp="${requestComp} ''"fi# When completing a flag with an = (e.g., gh -n=<TAB>)# bash focuses on the part after the =, so we need to remove# the flag part from $curif [[ "${cur}"== -*=* ]];then
cur="${cur#*=}"fi
__gh_debug "Calling ${requestComp}"# Use eval to handle any environment variables and such
out=$(eval "${requestComp}"2>/dev/null)# Extract the directive integer at the very end of the output following a colon (:)
directive=${out##*:}# Remove the directive
out=${out%:*}if [ "${directive}"="${out}" ];then# There is not directive specified
directive=0
fi
__gh_debug "The completion directive is: ${directive}"
__gh_debug "The completions are: ${out[*]}"
}
__gh_process_completion_results() {
local shellCompDirectiveError=1
local shellCompDirectiveNoSpace=2
local shellCompDirectiveNoFileComp=4
local shellCompDirectiveFilterFileExt=8
local shellCompDirectiveFilterDirs=16
if [ $((directive & shellCompDirectiveError))-ne 0 ];then# Error code. No completion.
__gh_debug "Received error from custom completion go code"returnelseif [ $((directive & shellCompDirectiveNoSpace))-ne 0 ];thenif [[ $(type -t compopt)="builtin" ]];then
__gh_debug "Activating no space"
compopt -o nospace
else
__gh_debug "No space directive not supported in this version of bash"fifiif [ $((directive & shellCompDirectiveNoFileComp))-ne 0 ];thenif [[ $(type -t compopt)="builtin" ]];then
__gh_debug "Activating no file completion"
compopt +o default
else
__gh_debug "No file completion directive not supported in this version of bash"fififiif [ $((directive & shellCompDirectiveFilterFileExt))-ne 0 ];then# File extension filteringlocal fullFilter filter filteringCmd
# Do not use quotes around the $out variable or else newline# characters will be kept.forfilterin${out[*]};do
fullFilter+="$filter|"done
filteringCmd="_filedir $fullFilter"
__gh_debug "File filtering command: $filteringCmd"$filteringCmdelif [ $((directive & shellCompDirectiveFilterDirs))-ne 0 ];then# File completion for directories only# Use printf to strip any trailing newlinelocal subdir
subdir=$(printf "%s""${out[0]}")if [ -n"$subdir" ];then
__gh_debug "Listing directories in $subdir"pushd"$subdir">/dev/null 2>&1&& _filedir -d &&popd>/dev/null 2>&1||returnelse
__gh_debug "Listing directories in ."
_filedir -d
fielse
__gh_handle_completion_types
fi
__gh_handle_special_char "$cur":
__gh_handle_special_char "$cur" =
}
__gh_handle_completion_types() {
__gh_debug "__gh_handle_completion_types: COMP_TYPE is $COMP_TYPE"case$COMP_TYPEin
37|42)
# Type: menu-complete/menu-complete-backward and insert-completions# If the user requested inserting one completion at a time, or all# completions at once on the command-line we must remove the descriptions.# https://github.com/spf13/cobra/issues/1508local tab comp
tab=$(printf '\t')while IFS=''read -r comp;do# Strip any description
comp=${comp%%$tab*}# Only consider the completions that match
comp=$(compgen -W "$comp" -- "$cur")if [ -n"$comp" ];then
COMPREPLY+=("$comp")
fidone<<(printf "%s\n""${out[@]}")
;;
*)
# Type: complete (normal completion)
__gh_handle_standard_completion_case
;;
esac
}
__gh_handle_standard_completion_case() {
local tab comp
tab=$(printf '\t')local longest=0
# Look for the longest completion so that we can format things nicelywhile IFS=''read -r comp;do# Strip any description before checking the length
comp=${comp%%$tab*}# Only consider the completions that match
comp=$(compgen -W "$comp" -- "$cur")if((${#comp}>longest));then
longest=${#comp}fidone<<(printf "%s\n""${out[@]}")local completions=()
while IFS=''read -r comp;doif [ -z"$comp" ];thencontinuefi
__gh_debug "Original comp: $comp"
comp="$(__gh_format_comp_descriptions "$comp""$longest")"
__gh_debug "Final comp: $comp"
completions+=("$comp")
done<<(printf "%s\n""${out[@]}")while IFS=''read -r comp;do
COMPREPLY+=("$comp")
done<<(compgen -W "${completions[*]}" -- "$cur")# If there is a single completion left, remove the description textif [ ${#COMPREPLY[*]}-eq 1 ];then
__gh_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
comp="${COMPREPLY[0]%%*}"
__gh_debug "Removed description from single completion, which is now: ${comp}"
COMPREPLY=()
COMPREPLY+=("$comp")
fi
}
__gh_handle_special_char()
{
local comp="$1"local char=$2if [[ "$comp"==*${char}*&&"$COMP_WORDBREAKS"==*${char}* ]];thenlocal word=${comp%"${comp##*${char}}"}local idx=${#COMPREPLY[*]}while [[ $((--idx))-ge 0 ]];do
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}donefi
}
__gh_format_comp_descriptions()
{
local tab
tab=$(printf '\t')local comp="$1"local longest=$2# Properly format the description string which follows a tab character if there is oneif [[ "$comp"==*$tab* ]];then
desc=${comp#*$tab}
comp=${comp%%$tab*}# $COLUMNS stores the current shell width.# Remove an extra 4 because we add 2 spaces and 2 parentheses.
maxdesclength=$(( COLUMNS - longest -4))# Make sure we can fit a description of at least 8 characters# if we are to align the descriptions.if [[ $maxdesclength-gt 8 ]];then# Add the proper number of spaces to align the descriptionsfor((i =${#comp} ; i < longest ; i++));do
comp+=""doneelse# Don't pad the descriptions so we can fit more text after the completion
maxdesclength=$(( COLUMNS -${#comp}-4))fi# If there is enough space for any description text,# truncate the descriptions that are too long for the shell widthif [ $maxdesclength-gt 0 ];thenif [ ${#desc}-gt$maxdesclength ];then
desc=${desc:0:$(( maxdesclength - 1 ))}
desc+="…"fi
comp+=" ($desc)"fifi# Must use printf to escape all special charactersprintf"%q""${comp}"
}
__start_gh()
{
local cur prev words cword split
COMPREPLY=()
# Call _init_completion from the bash-completion package# to prepare the arguments properlyifdeclare -F _init_completion >/dev/null 2>&1;then
_init_completion -n "=:"||returnelse
__gh_init_completion -n "=:"||returnfi
__gh_debug
__gh_debug "========= starting completion logic =========="
__gh_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"# The user could have moved the cursor backwards on the command-line.# We need to trigger completion from the $cword location, so we need# to truncate the command-line ($words) up to the $cword location.
words=("${words[@]:0:$cword+1}")
__gh_debug "Truncated words[*]: ${words[*]},"local out directive
__gh_get_completion_results
__gh_process_completion_results
}
if [[ $(type -t compopt)="builtin" ]];thencomplete -o default -F __start_gh gh
elsecomplete -o default -o nospace -F __start_gh gh
fi# ex: ts=4 sw=4 et filetype=sh
The text was updated successfully, but these errors were encountered:
I don't think this is worth spending time on right now. But it might not be so hard, given that we could just steal the battle-tested completion scripts.
To illustrate the technique, let's look at how
gh completion
works:So what happens when we run
gh completion -s bash
? It outputs a script that, instead of hard coding the completions, callsgh __complete
internally.For example, if you type
gh issue c
and then press Tab, the completion script callsgh __complete issue c
.The advantage is that the end user can:
gh
and its completion scriptgh
version, which gains new commandfoo
foo
, even without updating their installed completion scriptThis technique is used by https://github.com/spf13/cobra, which is a Go CLI library that is used by some prominent tools like the GitHub CLI, Kubernetes, and Hugo.
Outputs:
The text was updated successfully, but these errors were encountered: