Use the empty string as a parameter to the flag being completed

Also attempt to standardize the bash and zsh implementations

Next up: tests!
This commit is contained in:
PapaCharlie 2019-06-20 13:41:21 -07:00
parent 53e4e28648
commit ee1287cc3f
4 changed files with 55 additions and 44 deletions

View file

@ -280,7 +280,12 @@ __%[1]s_handle_word()
__%[1]s_handle_word
}
__%[1]s_handle_dynamic_flag_completion()
`, name))
}
func writeDynamicFlagCompletionFunction(buf *bytes.Buffer, dynamicFlagCompletionFunc string) {
buf.WriteString(fmt.Sprintf(`
%s()
{
export COBRA_FLAG_COMPLETION="$1"
@ -289,7 +294,7 @@ __%[1]s_handle_dynamic_flag_completion()
return $?
fi
if ! error_message="$($COMP_LINE > "$output")" ; then
if ! error_message="$("${COMP_WORDS[@]}" > "$output")" ; then
local st="$?"
echo "$error_message"
return "$st"
@ -303,7 +308,7 @@ __%[1]s_handle_dynamic_flag_completion()
rm "$output"
}
`, name))
`, dynamicFlagCompletionFunc))
}
func writePostscript(buf *bytes.Buffer, name string) {
@ -553,17 +558,19 @@ 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}
dynamicFlagCompletionFunc := "__" + c.Root().Name() + "_handle_dynamic_flag_completion"
c.Root().visitAllFlagsWithCompletions(func(f *pflag.Flag) {
f.Annotations[BashCompDynamic] = []string{dynamicFlagCompletionFunc + " " + f.Name}
})
buf := new(bytes.Buffer)
writePreamble(buf, c.Name())
if len(c.BashCompletionFunction) > 0 {
buf.WriteString(c.BashCompletionFunction + "\n")
}
if c.HasDynamicCompletions() {
writeDynamicFlagCompletionFunction(buf, dynamicFlagCompletionFunc)
}
gen(buf, c)
writePostscript(buf, c.Name())

View file

@ -212,9 +212,9 @@ type Command struct {
// errWriter is a writer defined by the user that replaces stderr
errWriter io.Writer
// flagCompletions is a map of flag to a function that returns a list of values to suggest during tab completion for
// this flag
flagCompletions map[*flag.Flag]DynamicFlagCompletion
// dynamicFlagCompletions is a map of flag to a function that returns a list of values to suggest during tab
// completion for this flag
dynamicFlagCompletions map[*flag.Flag]DynamicFlagCompletion
}
// Context returns underlying command context. If command wasn't
@ -907,7 +907,6 @@ func (c *Command) complete(flagName string, a []string) (err error) {
} else {
c.Flags().StringVar(&currentCompletionValue, flagName, "", "")
}
c.Flag(flagName).NoOptDefVal = "_hack_"
err = c.ParseFlags(a)
if err != nil {
@ -917,10 +916,10 @@ func (c *Command) complete(flagName string, a []string) (err error) {
c.preRun()
currentCommand := c
completionFunc := currentCommand.flagCompletions[flagToComplete]
completionFunc := currentCommand.dynamicFlagCompletions[flagToComplete]
for completionFunc == nil && currentCommand.HasParent() {
currentCommand = currentCommand.Parent()
completionFunc = currentCommand.flagCompletions[flagToComplete]
completionFunc = currentCommand.dynamicFlagCompletions[flagToComplete]
}
if completionFunc == nil {
return fmt.Errorf("%s does not have completions enabled", flagName)
@ -958,6 +957,7 @@ func (c *Command) complete(flagName string, a []string) (err error) {
}
for _, v := range values {
c.OutOrStdout()
fmt.Print(v + "\x00")
}
@ -1741,15 +1741,26 @@ func (c *Command) updateParentsPflags() {
})
}
func visitAllFlagsWithCompletions(c *Command, fn func(*flag.Flag)) {
func (c *Command) HasDynamicCompletions() bool {
hasCompletions := false
c.visitAllFlagsWithCompletions(func(*flag.Flag) { hasCompletions = true })
return hasCompletions
}
// visitAllFlagsWithCompletions recursively visits all flags and persistent flags that have dynamic completions enabled.
// Initializes the flag's Annotations map if nil before calling fn
func (c Command) visitAllFlagsWithCompletions(fn func(*flag.Flag)) {
filterFunc := func(f *flag.Flag) {
if _, ok := c.flagCompletions[f]; ok {
if _, ok := c.dynamicFlagCompletions[f]; ok {
if f.Annotations == nil {
f.Annotations = make(map[string][]string)
}
fn(f)
}
}
c.Flags().VisitAll(filterFunc)
c.PersistentFlags().VisitAll(filterFunc)
for _, sc := range c.Commands() {
visitAllFlagsWithCompletions(sc, fn)
sc.visitAllFlagsWithCompletions(fn)
}
}

View file

@ -103,10 +103,10 @@ func (c *Command) MarkDynamicFlagCompletion(name string, completion DynamicFlagC
return fmt.Errorf("%s takes no parameters", name)
}
if c.flagCompletions == nil {
c.flagCompletions = make(map[*pflag.Flag]DynamicFlagCompletion)
if c.dynamicFlagCompletions == nil {
c.dynamicFlagCompletions = make(map[*pflag.Flag]DynamicFlagCompletion)
}
c.flagCompletions[flag] = completion
c.dynamicFlagCompletions[flag] = completion
return nil
}

View file

@ -27,7 +27,6 @@ var (
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
"genZshFlagDynamicCompletionFuncName": zshCompGenDynamicFlagCompletionFuncName,
"hasDynamicCompletions": zshCompHasDynamicCompletions,
}
zshCompletionText = `
{{/* should accept Command (that contains subcommands) as parameter */}}
@ -35,6 +34,9 @@ var (
{{ $cmdPath := genZshFuncName .}}
function {{$cmdPath}} {
local -a commands
{{/* If we are at the root, save a copy of the $words array as it contains the full command, including any empty
strings and other parameters */}}
{{ if (not .HasParent) and .HasDynamicCompletions }} full_command=("${(@)words}"){{- end}}
_arguments -C \{{- range extractFlags .}}
{{genFlagEntryForZshArguments .}} \{{- end}}
@ -82,7 +84,7 @@ function {{genZshFuncName .}} {
{{define "Main" -}}
#compdef _{{.Name}} {{.Name}}
{{if hasDynamicCompletions . -}}
{{if .HasDynamicCompletions -}}
function {{genZshFlagDynamicCompletionFuncName .}} {
export COBRA_FLAG_COMPLETION="$1"
@ -91,7 +93,7 @@ function {{genZshFlagDynamicCompletionFuncName .}} {
return $?
fi
if ! error_message="$($tokens 2>&1 > "$output")" ; then
if ! error_message="$("${(@)full_command}" 2>&1 > "$output")" ; then
local st="$?"
_message "Exception occurred during completion: $error_message"
return "$st"
@ -102,7 +104,11 @@ function {{genZshFlagDynamicCompletionFuncName .}} {
args+="$line"
done < "$output"
_values "$1" $args
if [[ $#args -gt 0 ]] ; then
_values "$1" "${(@)args}"
else
_message "No matching completion for $descr: $opt_args"
fi
unset COBRA_FLAG_COMPLETION
rm "$output"
@ -141,12 +147,11 @@ 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)}
dynamicFlagCompletionFuncName := zshCompGenDynamicFlagCompletionFuncName(c.Root())
c.Root().visitAllFlagsWithCompletions(func(f *pflag.Flag) {
f.Annotations[zshCompDynamicCompletion] = []string{dynamicFlagCompletionFuncName + " " + f.Name}
})
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
if err != nil {
return fmt.Errorf("error creating zsh completion template: %v", err)
@ -285,10 +290,6 @@ func zshCompGenFuncName(c *Command) string {
return "_" + c.Name()
}
func zshCompGenDynamicFlagCompletionFuncName(c *Command) string {
return "_" + c.Root().Name() + "-flag-completion"
}
func zshCompExtractFlag(c *Command) []*pflag.Flag {
var flags []*pflag.Flag
c.LocalFlags().VisitAll(func(f *pflag.Flag) {
@ -360,7 +361,7 @@ func zshCompGenFlagEntryExtras(f *pflag.Flag) string {
extras = extras + fmt.Sprintf(` -g "%s"`, pattern)
}
case zshCompDynamicCompletion:
extras += fmt.Sprintf(":{%s %s}", values[0], f.Name)
extras += fmt.Sprintf(":{%s}", values[0])
}
}
@ -376,14 +377,6 @@ func zshCompQuoteFlagDescription(s string) string {
return strings.Replace(s, "'", `'\''`, -1)
}
func zshCompHasDynamicCompletions(c *Command) bool {
if len(c.flagCompletions) > 0 {
return true
}
for _, subcommand := range c.Commands() {
if zshCompHasDynamicCompletions(subcommand) {
return true
}
}
return false
func zshCompGenDynamicFlagCompletionFuncName(c *Command) string {
return "_" + c.Root().Name() + "-handle-dynamic-flag-completion"
}