diff --git a/bash_completions.go b/bash_completions.go index e8774e61..e04a817c 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -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()) diff --git a/command.go b/command.go index e083417a..26b97ddf 100644 --- a/command.go +++ b/command.go @@ -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(¤tCompletionValue, 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) } } diff --git a/shell_completions.go b/shell_completions.go index 1e9ee5d4..aeac1a1c 100644 --- a/shell_completions.go +++ b/shell_completions.go @@ -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 } diff --git a/zsh_completions.go b/zsh_completions.go index 41b62643..acbe1f43 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -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" }