Skip to content

Commit

Permalink
Use the empty string as a parameter to the flag being completed
Browse files Browse the repository at this point in the history
Also attempt to standardize the bash and zsh implementations

Next up: tests!
  • Loading branch information
PapaCharlie committed Jun 27, 2019
1 parent 10a7b4e commit 8e7e9bc
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 44 deletions.
23 changes: 15 additions & 8 deletions bash_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,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"
Expand All @@ -282,7 +287,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"
Expand All @@ -296,7 +301,7 @@ __%[1]s_handle_dynamic_flag_completion()
rm "$output"
}
`, name))
`, dynamicFlagCompletionFunc))
}

func writePostscript(buf *bytes.Buffer, name string) {
Expand Down Expand Up @@ -546,17 +551,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())

Expand Down
29 changes: 20 additions & 9 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,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
}

// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
Expand Down Expand Up @@ -893,7 +893,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 {
Expand All @@ -903,10 +902,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)
Expand Down Expand Up @@ -944,6 +943,7 @@ func (c *Command) complete(flagName string, a []string) (err error) {
}

for _, v := range values {
c.OutOrStdout()
fmt.Print(v + "\x00")
}

Expand Down Expand Up @@ -1702,15 +1702,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)
}
}
6 changes: 3 additions & 3 deletions shell_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
41 changes: 17 additions & 24 deletions zsh_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ var (
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
"genZshFlagDynamicCompletionFuncName": zshCompGenDynamicFlagCompletionFuncName,
"hasDynamicCompletions": zshCompHasDynamicCompletions,
}
zshCompletionText = `
{{/* should accept Command (that contains subcommands) as parameter */}}
{{define "argumentsC" -}}
{{ $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}}
Expand Down Expand Up @@ -82,7 +84,7 @@ function {{genZshFuncName .}} {
{{define "Main" -}}
#compdef _{{.Name}} {{.Name}}
{{if hasDynamicCompletions . -}}
{{if .HasDynamicCompletions -}}
function {{genZshFlagDynamicCompletionFuncName .}} {
export COBRA_FLAG_COMPLETION="$1"
Expand All @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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])
}
}

Expand All @@ -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"
}

0 comments on commit 8e7e9bc

Please sign in to comment.