Implement bash completions!

This commit is contained in:
PapaCharlie 2019-06-19 14:22:09 -07:00
parent 684fd47788
commit 979059b9c4
4 changed files with 81 additions and 17 deletions

View file

@ -15,6 +15,7 @@ import (
const (
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
BashCompCustom = "cobra_annotation_bash_completion_custom"
BashCompDynamic = "cobra_annotation_bash_completion_dynamic"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
)
@ -279,6 +280,29 @@ __%[1]s_handle_word()
__%[1]s_handle_word
}
__%[1]s_handle_dynamic_flag_completion()
{
export COBRA_FLAG_COMPLETION="$1"
local output
if ! output="$(mktemp)" ; then
return $?
fi
if ! error_message="$($COMP_LINE > "$output")" ; then
local st="$?"
echo "$error_message"
return "$st"
fi
while read -r -d '' line ; do
COMPREPLY+=("$line")
done < "$output"
unset COBRA_FLAG_COMPLETION
rm "$output"
}
`, name))
}
@ -354,6 +378,9 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
} else {
buf.WriteString(" flags_completion+=(:)\n")
}
case BashCompDynamic:
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", value[0]))
case BashCompSubdirsInDir:
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
@ -526,6 +553,12 @@ func gen(buf *bytes.Buffer, cmd *Command) {
// GenBashCompletion generates bash completion file and writes to the passed writer.
func (c *Command) GenBashCompletion(w io.Writer) error {
visitAllFlagsWithCompletions(c, func(f *pflag.Flag) {
if f.Annotations == nil {
f.Annotations = make(map[string][]string)
}
f.Annotations[BashCompDynamic] = []string{"__" + c.Root().Name() + "_handle_dynamic_flag_completion " + f.Name}
})
buf := new(bytes.Buffer)
writePreamble(buf, c.Name())
if len(c.BashCompletionFunction) > 0 {

View file

@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
@ -897,6 +898,10 @@ func (c *Command) complete(flagName string, a []string) (err error) {
c.Flags().AddFlag(f)
}
})
if flagToComplete == nil {
log.Panicln(flagName, "is not a known flag")
}
if flagToComplete.Shorthand != "" {
c.Flags().StringVarP(&currentCompletionValue, flagName, flagToComplete.Shorthand, "", "")
} else {
@ -953,7 +958,7 @@ func (c *Command) complete(flagName string, a []string) (err error) {
}
for _, v := range values {
c.Print(v + "\x00")
fmt.Print(v + "\x00")
}
return nil
@ -1735,3 +1740,16 @@ func (c *Command) updateParentsPflags() {
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
})
}
func visitAllFlagsWithCompletions(c *Command, fn func(*flag.Flag)) {
filterFunc := func(f *flag.Flag) {
if _, ok := c.flagCompletions[f]; ok {
fn(f)
}
}
c.Flags().VisitAll(filterFunc)
c.PersistentFlags().VisitAll(filterFunc)
for _, sc := range c.Commands() {
visitAllFlagsWithCompletions(sc, fn)
}
}

View file

@ -93,7 +93,7 @@ type DynamicFlagCompletion func(currentValue string) (suggestedValues []string,
// RunPreRunsDuringCompletion is set to true. All flags other than the flag currently being completed will be parsed
// according to their type. The flag being completed is parsed as a raw string with no format requirements
//
// Shell Completion compatibility matrix: zsh
// Shell Completion compatibility matrix: bash, zsh
func (c *Command) MarkDynamicFlagCompletion(name string, completion DynamicFlagCompletion) error {
flag := c.Flag(name)
if flag == nil {
@ -107,9 +107,6 @@ func (c *Command) MarkDynamicFlagCompletion(name string, completion DynamicFlagC
c.flagCompletions = make(map[*pflag.Flag]DynamicFlagCompletion)
}
c.flagCompletions[flag] = completion
if flag.Annotations == nil {
flag.Annotations = map[string][]string{}
}
flag.Annotations[zshCompDynamicCompletion] = []string{zshCompGenFlagCompletionFuncName(c)}
return nil
}

View file

@ -26,9 +26,8 @@ var (
"extractFlags": zshCompExtractFlag,
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
"genZshFlagDynamicCompletionFuncName": zshCompGenFlagCompletionFuncName,
"genZshFlagDynamicCompletionFuncName": zshCompGenDynamicFlagCompletionFuncName,
"hasDynamicCompletions": zshCompHasDynamicCompletions,
"flagCompletionsEnvVar": func() string { return FlagCompletionEnvVar },
}
zshCompletionText = `
{{/* should accept Command (that contains subcommands) as parameter */}}
@ -86,16 +85,27 @@ function {{genZshFuncName .}} {
{{if hasDynamicCompletions . -}}
function {{genZshFlagDynamicCompletionFuncName .}} {
export COBRA_FLAG_COMPLETION="$1"
if suggestions="$("$words[@]" 2>&1)" ; then
local -a args
while read -d $'\0' line ; do
args+="$line"
done <<< "$suggestions"
_values "$1" "$args[@]"
else
_message "Exception occurred during completion: $suggestions"
local output
if ! output="$(mktemp)" ; then
return $?
fi
if ! error_message="$("${tokens[@]}" 2>&1 > "$output")" ; then
local st="$?"
_message "Exception occurred during completion: $error_message"
return "$st"
fi
local -a args
while read -r -d '' line ; do
args+="$line"
done < "$output"
_values "$1" "$args[@]"
unset COBRA_FLAG_COMPLETION
rm "$output"
}{{- end}}
{{template "selectCmdTemplate" .}}
@ -131,6 +141,12 @@ func (c *Command) GenZshCompletionFile(filename string) error {
// writer. The completion always run on the root command regardless of the
// command it was called from.
func (c *Command) GenZshCompletion(w io.Writer) error {
visitAllFlagsWithCompletions(c, func(f *pflag.Flag) {
if f.Annotations == nil {
f.Annotations = make(map[string][]string)
}
f.Annotations[zshCompDynamicCompletion] = []string{zshCompGenDynamicFlagCompletionFuncName(c)}
})
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
if err != nil {
return fmt.Errorf("error creating zsh completion template: %v", err)
@ -269,7 +285,7 @@ func zshCompGenFuncName(c *Command) string {
return "_" + c.Name()
}
func zshCompGenFlagCompletionFuncName(c *Command) string {
func zshCompGenDynamicFlagCompletionFuncName(c *Command) string {
return "_" + c.Root().Name() + "-flag-completion"
}