Skip to content

Commit 24896de

Browse files
committed
Implement bash completions!
1 parent 98160e5 commit 24896de

File tree

4 files changed

+81
-17
lines changed

4 files changed

+81
-17
lines changed

bash_completions.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
const (
1616
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
1717
BashCompCustom = "cobra_annotation_bash_completion_custom"
18+
BashCompDynamic = "cobra_annotation_bash_completion_dynamic"
1819
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
1920
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
2021
)
@@ -272,6 +273,29 @@ __%[1]s_handle_word()
272273
__%[1]s_handle_word
273274
}
274275
276+
__%[1]s_handle_dynamic_flag_completion()
277+
{
278+
export COBRA_FLAG_COMPLETION="$1"
279+
280+
local output
281+
if ! output="$(mktemp)" ; then
282+
return $?
283+
fi
284+
285+
if ! error_message="$($COMP_LINE > "$output")" ; then
286+
local st="$?"
287+
echo "$error_message"
288+
return "$st"
289+
fi
290+
291+
while read -r -d '' line ; do
292+
COMPREPLY+=("$line")
293+
done < "$output"
294+
295+
unset COBRA_FLAG_COMPLETION
296+
rm "$output"
297+
}
298+
275299
`, name))
276300
}
277301

@@ -347,6 +371,9 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
347371
} else {
348372
buf.WriteString(" flags_completion+=(:)\n")
349373
}
374+
case BashCompDynamic:
375+
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
376+
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", value[0]))
350377
case BashCompSubdirsInDir:
351378
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
352379

@@ -519,6 +546,12 @@ func gen(buf *bytes.Buffer, cmd *Command) {
519546

520547
// GenBashCompletion generates bash completion file and writes to the passed writer.
521548
func (c *Command) GenBashCompletion(w io.Writer) error {
549+
visitAllFlagsWithCompletions(c, func(f *pflag.Flag) {
550+
if f.Annotations == nil {
551+
f.Annotations = make(map[string][]string)
552+
}
553+
f.Annotations[BashCompDynamic] = []string{"__" + c.Root().Name() + "_handle_dynamic_flag_completion " + f.Name}
554+
})
522555
buf := new(bytes.Buffer)
523556
writePreamble(buf, c.Name())
524557
if len(c.BashCompletionFunction) > 0 {

command.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"bytes"
2020
"fmt"
2121
"io"
22+
"log"
2223
"os"
2324
"path/filepath"
2425
"sort"
@@ -883,6 +884,10 @@ func (c *Command) complete(flagName string, a []string) (err error) {
883884
c.Flags().AddFlag(f)
884885
}
885886
})
887+
if flagToComplete == nil {
888+
log.Panicln(flagName, "is not a known flag")
889+
}
890+
886891
if flagToComplete.Shorthand != "" {
887892
c.Flags().StringVarP(&currentCompletionValue, flagName, flagToComplete.Shorthand, "", "")
888893
} else {
@@ -939,7 +944,7 @@ func (c *Command) complete(flagName string, a []string) (err error) {
939944
}
940945

941946
for _, v := range values {
942-
c.Print(v + "\x00")
947+
fmt.Print(v + "\x00")
943948
}
944949

945950
return nil
@@ -1696,3 +1701,16 @@ func (c *Command) updateParentsPflags() {
16961701
c.parentsPflags.AddFlagSet(parent.PersistentFlags())
16971702
})
16981703
}
1704+
1705+
func visitAllFlagsWithCompletions(c *Command, fn func(*flag.Flag)) {
1706+
filterFunc := func(f *flag.Flag) {
1707+
if _, ok := c.flagCompletions[f]; ok {
1708+
fn(f)
1709+
}
1710+
}
1711+
c.Flags().VisitAll(filterFunc)
1712+
c.PersistentFlags().VisitAll(filterFunc)
1713+
for _, sc := range c.Commands() {
1714+
visitAllFlagsWithCompletions(sc, fn)
1715+
}
1716+
}

shell_completions.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ type DynamicFlagCompletion func(currentValue string) (suggestedValues []string,
9393
// RunPreRunsDuringCompletion is set to true. All flags other than the flag currently being completed will be parsed
9494
// according to their type. The flag being completed is parsed as a raw string with no format requirements
9595
//
96-
// Shell Completion compatibility matrix: zsh
96+
// Shell Completion compatibility matrix: bash, zsh
9797
func (c *Command) MarkDynamicFlagCompletion(name string, completion DynamicFlagCompletion) error {
9898
flag := c.Flag(name)
9999
if flag == nil {
@@ -107,9 +107,6 @@ func (c *Command) MarkDynamicFlagCompletion(name string, completion DynamicFlagC
107107
c.flagCompletions = make(map[*pflag.Flag]DynamicFlagCompletion)
108108
}
109109
c.flagCompletions[flag] = completion
110-
if flag.Annotations == nil {
111-
flag.Annotations = map[string][]string{}
112-
}
113-
flag.Annotations[zshCompDynamicCompletion] = []string{zshCompGenFlagCompletionFuncName(c)}
110+
114111
return nil
115112
}

zsh_completions.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ var (
2626
"extractFlags": zshCompExtractFlag,
2727
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
2828
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
29-
"genZshFlagDynamicCompletionFuncName": zshCompGenFlagCompletionFuncName,
29+
"genZshFlagDynamicCompletionFuncName": zshCompGenDynamicFlagCompletionFuncName,
3030
"hasDynamicCompletions": zshCompHasDynamicCompletions,
31-
"flagCompletionsEnvVar": func() string { return FlagCompletionEnvVar },
3231
}
3332
zshCompletionText = `
3433
{{/* should accept Command (that contains subcommands) as parameter */}}
@@ -86,16 +85,27 @@ function {{genZshFuncName .}} {
8685
{{if hasDynamicCompletions . -}}
8786
function {{genZshFlagDynamicCompletionFuncName .}} {
8887
export COBRA_FLAG_COMPLETION="$1"
89-
if suggestions="$("$words[@]" 2>&1)" ; then
90-
local -a args
91-
while read -d $'\0' line ; do
92-
args+="$line"
93-
done <<< "$suggestions"
94-
_values "$1" "$args[@]"
95-
else
96-
_message "Exception occurred during completion: $suggestions"
88+
89+
local output
90+
if ! output="$(mktemp)" ; then
91+
return $?
92+
fi
93+
94+
if ! error_message="$("${tokens[@]}" 2>&1 > "$output")" ; then
95+
local st="$?"
96+
_message "Exception occurred during completion: $error_message"
97+
return "$st"
9798
fi
99+
100+
local -a args
101+
while read -r -d '' line ; do
102+
args+="$line"
103+
done < "$output"
104+
105+
_values "$1" "$args[@]"
106+
98107
unset COBRA_FLAG_COMPLETION
108+
rm "$output"
99109
}{{- end}}
100110
101111
{{template "selectCmdTemplate" .}}
@@ -131,6 +141,12 @@ func (c *Command) GenZshCompletionFile(filename string) error {
131141
// writer. The completion always run on the root command regardless of the
132142
// command it was called from.
133143
func (c *Command) GenZshCompletion(w io.Writer) error {
144+
visitAllFlagsWithCompletions(c, func(f *pflag.Flag) {
145+
if f.Annotations == nil {
146+
f.Annotations = make(map[string][]string)
147+
}
148+
f.Annotations[zshCompDynamicCompletion] = []string{zshCompGenDynamicFlagCompletionFuncName(c)}
149+
})
134150
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
135151
if err != nil {
136152
return fmt.Errorf("error creating zsh completion template: %v", err)
@@ -269,7 +285,7 @@ func zshCompGenFuncName(c *Command) string {
269285
return "_" + c.Name()
270286
}
271287

272-
func zshCompGenFlagCompletionFuncName(c *Command) string {
288+
func zshCompGenDynamicFlagCompletionFuncName(c *Command) string {
273289
return "_" + c.Root().Name() + "-flag-completion"
274290
}
275291

0 commit comments

Comments
 (0)