Add command-specific argument completions

Johannes Altmanninger created

Complete bug IDs, bridges, users, labels where appropriate.

This works in bash and fish. ZSH is not yet supported by Cobra.
In fish, descriptions (the part of a completion after the "\t") are shown
as completion label, and can be searched with Ctrl+S.

Reproduce with

	fish -C 'source misc/fish_completion/git-bug'
	git bug select ^I

(tested with fish version 3.3.1)

Also works with bash, but only for "git-bug" (with the dash)

	bash --rcfile <(echo source misc/bash_completion/git-bug)
	git-bug select ^I

Closes #493

Change summary

commands/bridge_auth_addtoken.go |   2 
commands/bridge_auth_rm.go       |   3 
commands/bridge_auth_show.go     |   3 
commands/bridge_configure.go     |   1 
commands/bridge_pull.go          |   3 
commands/bridge_push.go          |   3 
commands/bridge_rm.go            |   3 
commands/comment.go              |   1 
commands/comment_add.go          |   1 
commands/helper_completion.go    | 350 ++++++++++++++++++++++++++++++++++
commands/label.go                |   1 
commands/label_add.go            |   1 
commands/label_rm.go             |   1 
commands/ls.go                   |  11 +
commands/pull.go                 |   1 
commands/push.go                 |   1 
commands/rm.go                   |   1 
commands/select.go               |   1 
commands/show.go                 |   6 
commands/status.go               |   1 
commands/title.go                |   1 
commands/title_edit.go           |   1 
commands/user.go                 |   6 
commands/user_adopt.go           |   1 
commands/user_ls.go              |   1 
misc/bash_completion/git-bug     |  77 +++++++
26 files changed, 475 insertions(+), 7 deletions(-)

Detailed changes

commands/bridge_auth_addtoken.go 🔗

@@ -41,10 +41,12 @@ func newBridgeAuthAddTokenCommand() *cobra.Command {
 
 	flags.StringVarP(&options.target, "target", "t", "",
 		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
+	cmd.RegisterFlagCompletionFunc("target", completeFrom(bridge.Targets()))
 	flags.StringVarP(&options.login,
 		"login", "l", "", "The login in the remote bug-tracker")
 	flags.StringVarP(&options.user,
 		"user", "u", "", "The user to add the token to. Default is the current user")
+	cmd.RegisterFlagCompletionFunc("user", completeUser(env))
 
 	return cmd
 }

commands/bridge_auth_rm.go 🔗

@@ -16,7 +16,8 @@ func newBridgeAuthRm() *cobra.Command {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runBridgeAuthRm(env, args)
 		},
-		Args: cobra.ExactArgs(1),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: completeBridgeAuth(env),
 	}
 
 	return cmd

commands/bridge_auth_show.go 🔗

@@ -21,7 +21,8 @@ func newBridgeAuthShow() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runBridgeAuthShow(env, args)
 		}),
-		Args: cobra.ExactArgs(1),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: completeBridgeAuth(env),
 	}
 
 	return cmd

commands/bridge_configure.go 🔗

@@ -97,6 +97,7 @@ git bug bridge configure \
 	flags.StringVarP(&options.name, "name", "n", "", "A distinctive name to identify the bridge")
 	flags.StringVarP(&options.target, "target", "t", "",
 		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
+	cmd.RegisterFlagCompletionFunc("target", completeFrom(bridge.Targets()))
 	flags.StringVarP(&options.params.URL, "url", "u", "", "The URL of the remote repository")
 	flags.StringVarP(&options.params.BaseURL, "base-url", "b", "", "The base URL of your remote issue tracker")
 	flags.StringVarP(&options.params.Login, "login", "l", "", "The login on your remote issue tracker")

commands/bridge_pull.go 🔗

@@ -32,7 +32,8 @@ func newBridgePullCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runBridgePull(env, options, args)
 		}),
-		Args: cobra.MaximumNArgs(1),
+		Args:              cobra.MaximumNArgs(1),
+		ValidArgsFunction: completeBridge(env),
 	}
 
 	flags := cmd.Flags()

commands/bridge_push.go 🔗

@@ -23,7 +23,8 @@ func newBridgePushCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runBridgePush(env, args)
 		}),
-		Args: cobra.MaximumNArgs(1),
+		Args:              cobra.MaximumNArgs(1),
+		ValidArgsFunction: completeBridge(env),
 	}
 
 	return cmd

commands/bridge_rm.go 🔗

@@ -16,7 +16,8 @@ func newBridgeRm() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runBridgeRm(env, args)
 		}),
-		Args: cobra.ExactArgs(1),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: completeBridge(env),
 	}
 
 	return cmd

commands/comment.go 🔗

@@ -18,6 +18,7 @@ func newCommentCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runComment(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	cmd.AddCommand(newCommentAddCommand())

commands/comment_add.go 🔗

@@ -25,6 +25,7 @@ func newCommentAddCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runCommentAdd(env, options, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	flags := cmd.Flags()

commands/helper_completion.go 🔗

@@ -0,0 +1,350 @@
+package commands
+
+import (
+	"sort"
+	"strings"
+
+	"github.com/spf13/cobra"
+
+	"github.com/MichaelMure/git-bug/bridge"
+	"github.com/MichaelMure/git-bug/bridge/core/auth"
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/cache"
+	_select "github.com/MichaelMure/git-bug/commands/select"
+)
+
+type validArgsFunction func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective)
+
+func completionHandlerError(err error) (completions []string, directives cobra.ShellCompDirective) {
+	return nil, cobra.ShellCompDirectiveError
+}
+
+func completeBridge(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		bridges, err := bridge.ConfiguredBridges(env.backend)
+		if err != nil {
+			return completionHandlerError(err)
+		}
+
+		completions = make([]string, len(bridges))
+		for i, bridge := range bridges {
+			completions[i] = bridge + "\t" + "Bridge"
+		}
+
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeBridgeAuth(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		creds, err := auth.List(env.backend)
+		if err != nil {
+			return completionHandlerError(err)
+		}
+
+		completions = make([]string, len(creds))
+		for i, cred := range creds {
+			meta := make([]string, 0, len(cred.Metadata()))
+			for k, v := range cred.Metadata() {
+				meta = append(meta, k+":"+v)
+			}
+			sort.Strings(meta)
+			metaFmt := strings.Join(meta, ",")
+
+			completions[i] = cred.ID().Human() + "\t" + cred.Target() + " " + string(cred.Kind()) + " " + metaFmt
+		}
+
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeBug(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		allIds := env.backend.AllBugsIds()
+		bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
+		for i, id := range allIds {
+			var err error
+			bugExcerpt[i], err = env.backend.ResolveBugExcerpt(id)
+			if err != nil {
+				return completionHandlerError(err)
+			}
+		}
+
+		completions = make([]string, len(allIds))
+		for i, id := range allIds {
+			completions[i] = id.Human() + "\t" + bugExcerpt[i].Title
+		}
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeBugAndLabels(env *Env, addOrRemove bool) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) == 0 {
+			return completeBug(env)(cmd, args, toComplete)
+		}
+
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		b, args, err := _select.ResolveBug(env.backend, args)
+		if err != nil {
+			return completionHandlerError(err)
+		}
+
+		snap := b.Snapshot()
+
+		seenLabels := map[bug.Label]bool{}
+		for _, label := range args {
+			seenLabels[bug.Label(label)] = addOrRemove
+		}
+
+		var labels []bug.Label
+		if addOrRemove {
+			for _, label := range snap.Labels {
+				seenLabels[label] = true
+			}
+
+			allLabels := env.backend.ValidLabels()
+			labels = make([]bug.Label, 0, len(allLabels))
+			for _, label := range allLabels {
+				if !seenLabels[label] {
+					labels = append(labels, label)
+				}
+			}
+		} else {
+			labels = make([]bug.Label, 0, len(snap.Labels))
+			for _, label := range snap.Labels {
+				if seenLabels[label] {
+					labels = append(labels, label)
+				}
+			}
+		}
+
+		completions = make([]string, len(labels))
+		for i, label := range labels {
+			completions[i] = string(label) + "\t" + "Label"
+		}
+
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeFrom(choices []string) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return choices, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeGitRemote(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		remoteMap, err := env.backend.GetRemotes()
+		if err != nil {
+			return completionHandlerError(err)
+		}
+		completions = make([]string, 0, len(remoteMap))
+		for remote, url := range remoteMap {
+			completions = append(completions, remote+"\t"+"Remote: "+url)
+		}
+		sort.Strings(completions)
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeLabel(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		labels := env.backend.ValidLabels()
+		completions = make([]string, len(labels))
+		for i, label := range labels {
+			completions[i] = string(label) + "\t" + "Label"
+		}
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeLs(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if strings.HasPrefix(toComplete, "status:") {
+			completions = append(completions, "status:open\tOpen bugs")
+			completions = append(completions, "status:closed\tClosed bugs")
+			return completions, cobra.ShellCompDirectiveDefault
+		}
+
+		byPerson := []string{"author:", "participant:", "actor:"}
+		byLabel := []string{"label:", "no:"}
+		needBackend := false
+		for _, key := range append(byPerson, byLabel...) {
+			if strings.HasPrefix(toComplete, key) {
+				needBackend = true
+			}
+		}
+
+		if needBackend {
+			if err := loadBackend(env)(cmd, args); err != nil {
+				return completionHandlerError(err)
+			}
+			defer func() {
+				_ = env.backend.Close()
+			}()
+		}
+
+		for _, key := range byPerson {
+			if !strings.HasPrefix(toComplete, key) {
+				continue
+			}
+			ids := env.backend.AllIdentityIds()
+			completions = make([]string, len(ids))
+			for i, id := range ids {
+				user, err := env.backend.ResolveIdentityExcerpt(id)
+				if err != nil {
+					return completionHandlerError(err)
+				}
+				var handle string
+				if user.Login != "" {
+					handle = user.Login
+				} else {
+					// "author:John Doe" does not work yet, so use the first name.
+					handle = strings.Split(user.Name, " ")[0]
+				}
+				completions[i] = key + handle + "\t" + user.DisplayName()
+			}
+			return completions, cobra.ShellCompDirectiveDefault
+		}
+
+		for _, key := range byLabel {
+			if !strings.HasPrefix(toComplete, key) {
+				continue
+			}
+			labels := env.backend.ValidLabels()
+			completions = make([]string, len(labels))
+			for i, label := range labels {
+				completions[i] = key + string(label)
+			}
+			return completions, cobra.ShellCompDirectiveDefault
+		}
+
+		completions = []string{
+			"actor:\tFilter by actor",
+			"author:\tFilter by author",
+			"label:\tFilter by label",
+			"no:\tExclude bugs by label",
+			"participant:\tFilter by participant",
+			"status:\tFilter by open/close status",
+			"title:\tFilter by title",
+		}
+		return completions, cobra.ShellCompDirectiveNoSpace
+	}
+}
+
+func completeUser(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		ids := env.backend.AllIdentityIds()
+		completions = make([]string, len(ids))
+		for i, id := range ids {
+			user, err := env.backend.ResolveIdentityExcerpt(id)
+			if err != nil {
+				return completionHandlerError(err)
+			}
+			completions[i] = user.Id.Human() + "\t" + user.DisplayName()
+		}
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}
+
+func completeUserForQuery(env *Env) validArgsFunction {
+	return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+		if len(args) > 0 {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+
+		if err := loadBackend(env)(cmd, args); err != nil {
+			return completionHandlerError(err)
+		}
+		defer func() {
+			_ = env.backend.Close()
+		}()
+
+		ids := env.backend.AllIdentityIds()
+		completions = make([]string, len(ids))
+		for i, id := range ids {
+			user, err := env.backend.ResolveIdentityExcerpt(id)
+			if err != nil {
+				return completionHandlerError(err)
+			}
+			var handle string
+			if user.Login != "" {
+				handle = user.Login
+			} else {
+				// "author:John Doe" does not work yet, so use the first name.
+				handle = strings.Split(user.Name, " ")[0]
+			}
+			completions[i] = handle + "\t" + user.DisplayName()
+		}
+		return completions, cobra.ShellCompDirectiveDefault
+	}
+}

commands/label.go 🔗

@@ -16,6 +16,7 @@ func newLabelCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runLabel(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	cmd.AddCommand(newLabelAddCommand())

commands/label_add.go 🔗

@@ -17,6 +17,7 @@ func newLabelAddCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runLabelAdd(env, args)
 		}),
+		ValidArgsFunction: completeBugAndLabels(env, true),
 	}
 
 	return cmd

commands/label_rm.go 🔗

@@ -17,6 +17,7 @@ func newLabelRmCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runLabelRm(env, args)
 		}),
+		ValidArgsFunction: completeBugAndLabels(env, false),
 	}
 
 	return cmd

commands/ls.go 🔗

@@ -56,6 +56,7 @@ git bug ls status:open --by creation "foo bar" baz
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runLs(env, options, args)
 		}),
+		ValidArgsFunction: completeLs(env),
 	}
 
 	flags := cmd.Flags()
@@ -63,26 +64,36 @@ git bug ls status:open --by creation "foo bar" baz
 
 	flags.StringSliceVarP(&options.statusQuery, "status", "s", nil,
 		"Filter by status. Valid values are [open,closed]")
+	cmd.RegisterFlagCompletionFunc("status", completeFrom([]string{"open", "closed"}))
 	flags.StringSliceVarP(&options.authorQuery, "author", "a", nil,
 		"Filter by author")
 	flags.StringSliceVarP(&options.metadataQuery, "metadata", "m", nil,
 		"Filter by metadata. Example: github-url=URL")
+	cmd.RegisterFlagCompletionFunc("author", completeUserForQuery(env))
 	flags.StringSliceVarP(&options.participantQuery, "participant", "p", nil,
 		"Filter by participant")
+	cmd.RegisterFlagCompletionFunc("participant", completeUserForQuery(env))
 	flags.StringSliceVarP(&options.actorQuery, "actor", "A", nil,
 		"Filter by actor")
+	cmd.RegisterFlagCompletionFunc("actor", completeUserForQuery(env))
 	flags.StringSliceVarP(&options.labelQuery, "label", "l", nil,
 		"Filter by label")
+	cmd.RegisterFlagCompletionFunc("label", completeLabel(env))
 	flags.StringSliceVarP(&options.titleQuery, "title", "t", nil,
 		"Filter by title")
 	flags.StringSliceVarP(&options.noQuery, "no", "n", nil,
 		"Filter by absence of something. Valid values are [label]")
+	cmd.RegisterFlagCompletionFunc("no", completeLabel(env))
 	flags.StringVarP(&options.sortBy, "by", "b", "creation",
 		"Sort the results by a characteristic. Valid values are [id,creation,edit]")
+	cmd.RegisterFlagCompletionFunc("by", completeFrom([]string{"id", "creation", "edit"}))
 	flags.StringVarP(&options.sortDirection, "direction", "d", "asc",
 		"Select the sorting direction. Valid values are [asc,desc]")
+	cmd.RegisterFlagCompletionFunc("direction", completeFrom([]string{"asc", "desc"}))
 	flags.StringVarP(&options.outputFormat, "format", "f", "default",
 		"Select the output formatting style. Valid values are [default,plain,json,org-mode]")
+	cmd.RegisterFlagCompletionFunc("format",
+		completeFrom([]string{"default", "plain", "json", "org-mode"}))
 
 	return cmd
 }

commands/pull.go 🔗

@@ -18,6 +18,7 @@ func newPullCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runPull(env, args)
 		}),
+		ValidArgsFunction: completeGitRemote(env),
 	}
 
 	return cmd

commands/push.go 🔗

@@ -16,6 +16,7 @@ func newPushCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runPush(env, args)
 		}),
+		ValidArgsFunction: completeGitRemote(env),
 	}
 
 	return cmd

commands/rm.go 🔗

@@ -17,6 +17,7 @@ func newRmCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runRm(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	flags := cmd.Flags()

commands/select.go 🔗

@@ -31,6 +31,7 @@ The complementary command is "git bug deselect" performing the opposite operatio
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runSelect(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	return cmd

commands/show.go 🔗

@@ -29,13 +29,17 @@ func newShowCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runShow(env, options, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	flags := cmd.Flags()
 	flags.SortFlags = false
 
+	fields := []string{"author", "authorEmail", "createTime", "lastEdit", "humanId",
+		"id", "labels", "shortId", "status", "title", "actors", "participants"}
 	flags.StringVarP(&options.fields, "field", "", "",
-		"Select field to display. Valid values are [author,authorEmail,createTime,lastEdit,humanId,id,labels,shortId,status,title,actors,participants]")
+		"Select field to display. Valid values are ["+strings.Join(fields, ",")+"]")
+	cmd.RegisterFlagCompletionFunc("by", completeFrom(fields))
 	flags.StringVarP(&options.format, "format", "f", "default",
 		"Select the output formatting style. Valid values are [default,json,org-mode]")
 

commands/status.go 🔗

@@ -15,6 +15,7 @@ func newStatusCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runStatus(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	cmd.AddCommand(newStatusCloseCommand())

commands/title.go 🔗

@@ -15,6 +15,7 @@ func newTitleCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runTitle(env, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	cmd.AddCommand(newTitleEditCommand())

commands/title_edit.go 🔗

@@ -24,6 +24,7 @@ func newTitleEditCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runTitleEdit(env, options, args)
 		}),
+		ValidArgsFunction: completeBug(env),
 	}
 
 	flags := cmd.Flags()

commands/user.go 🔗

@@ -3,6 +3,7 @@ package commands
 import (
 	"errors"
 	"fmt"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -24,6 +25,7 @@ func newUserCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runUser(env, options, args)
 		}),
+		ValidArgsFunction: completeUser(env),
 	}
 
 	cmd.AddCommand(newUserAdoptCommand())
@@ -33,8 +35,10 @@ func newUserCommand() *cobra.Command {
 	flags := cmd.Flags()
 	flags.SortFlags = false
 
+	fields := []string{"email", "humanId", "id", "lastModification", "lastModificationLamports", "login", "metadata", "name"}
 	flags.StringVarP(&options.fields, "field", "f", "",
-		"Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamports,login,metadata,name]")
+		"Select field to display. Valid values are ["+strings.Join(fields, ",")+"]")
+	cmd.RegisterFlagCompletionFunc("field", completeFrom(fields))
 
 	return cmd
 }

commands/user_adopt.go 🔗

@@ -15,6 +15,7 @@ func newUserAdoptCommand() *cobra.Command {
 		RunE: closeBackend(env, func(cmd *cobra.Command, args []string) error {
 			return runUserAdopt(env, args)
 		}),
+		ValidArgsFunction: completeUser(env),
 	}
 
 	return cmd

commands/user_ls.go 🔗

@@ -32,6 +32,7 @@ func newUserLsCommand() *cobra.Command {
 
 	flags.StringVarP(&options.format, "format", "f", "default",
 		"Select the output formatting style. Valid values are [default,json]")
+	cmd.RegisterFlagCompletionFunc("format", completeFrom([]string{"default", "json"}))
 
 	return cmd
 }

misc/bash_completion/git-bug 🔗

@@ -420,7 +420,11 @@ _git-bug_bridge_auth_add-token()
 
     flags+=("--target=")
     two_word_flags+=("--target")
+    flags_with_completion+=("--target")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-t")
+    flags_with_completion+=("-t")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--target")
     local_nonpersistent_flags+=("--target=")
     local_nonpersistent_flags+=("-t")
@@ -432,7 +436,11 @@ _git-bug_bridge_auth_add-token()
     local_nonpersistent_flags+=("-l")
     flags+=("--user=")
     two_word_flags+=("--user")
+    flags_with_completion+=("--user")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-u")
+    flags_with_completion+=("-u")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--user")
     local_nonpersistent_flags+=("--user=")
     local_nonpersistent_flags+=("-u")
@@ -459,6 +467,7 @@ _git-bug_bridge_auth_rm()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -479,6 +488,7 @@ _git-bug_bridge_auth_show()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -527,7 +537,11 @@ _git-bug_bridge_configure()
     local_nonpersistent_flags+=("-n")
     flags+=("--target=")
     two_word_flags+=("--target")
+    flags_with_completion+=("--target")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-t")
+    flags_with_completion+=("-t")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--target")
     local_nonpersistent_flags+=("--target=")
     local_nonpersistent_flags+=("-t")
@@ -608,6 +622,7 @@ _git-bug_bridge_pull()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -628,6 +643,7 @@ _git-bug_bridge_push()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -648,6 +664,7 @@ _git-bug_bridge_rm()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -731,6 +748,7 @@ _git-bug_comment_add()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -787,6 +805,7 @@ _git-bug_comment()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -827,6 +846,7 @@ _git-bug_label_add()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -847,6 +867,7 @@ _git-bug_label_rm()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -869,6 +890,7 @@ _git-bug_label()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -888,13 +910,21 @@ _git-bug_ls()
 
     flags+=("--status=")
     two_word_flags+=("--status")
+    flags_with_completion+=("--status")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-s")
+    flags_with_completion+=("-s")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--status")
     local_nonpersistent_flags+=("--status=")
     local_nonpersistent_flags+=("-s")
     flags+=("--author=")
     two_word_flags+=("--author")
+    flags_with_completion+=("--author")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-a")
+    flags_with_completion+=("-a")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--author")
     local_nonpersistent_flags+=("--author=")
     local_nonpersistent_flags+=("-a")
@@ -906,19 +936,31 @@ _git-bug_ls()
     local_nonpersistent_flags+=("-m")
     flags+=("--participant=")
     two_word_flags+=("--participant")
+    flags_with_completion+=("--participant")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-p")
+    flags_with_completion+=("-p")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--participant")
     local_nonpersistent_flags+=("--participant=")
     local_nonpersistent_flags+=("-p")
     flags+=("--actor=")
     two_word_flags+=("--actor")
+    flags_with_completion+=("--actor")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-A")
+    flags_with_completion+=("-A")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--actor")
     local_nonpersistent_flags+=("--actor=")
     local_nonpersistent_flags+=("-A")
     flags+=("--label=")
     two_word_flags+=("--label")
+    flags_with_completion+=("--label")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-l")
+    flags_with_completion+=("-l")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--label")
     local_nonpersistent_flags+=("--label=")
     local_nonpersistent_flags+=("-l")
@@ -930,31 +972,48 @@ _git-bug_ls()
     local_nonpersistent_flags+=("-t")
     flags+=("--no=")
     two_word_flags+=("--no")
+    flags_with_completion+=("--no")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-n")
+    flags_with_completion+=("-n")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--no")
     local_nonpersistent_flags+=("--no=")
     local_nonpersistent_flags+=("-n")
     flags+=("--by=")
     two_word_flags+=("--by")
+    flags_with_completion+=("--by")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-b")
+    flags_with_completion+=("-b")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--by")
     local_nonpersistent_flags+=("--by=")
     local_nonpersistent_flags+=("-b")
     flags+=("--direction=")
     two_word_flags+=("--direction")
+    flags_with_completion+=("--direction")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-d")
+    flags_with_completion+=("-d")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--direction")
     local_nonpersistent_flags+=("--direction=")
     local_nonpersistent_flags+=("-d")
     flags+=("--format=")
     two_word_flags+=("--format")
+    flags_with_completion+=("--format")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-f")
+    flags_with_completion+=("-f")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--format")
     local_nonpersistent_flags+=("--format=")
     local_nonpersistent_flags+=("-f")
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1015,6 +1074,7 @@ _git-bug_pull()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1035,6 +1095,7 @@ _git-bug_push()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1055,6 +1116,7 @@ _git-bug_rm()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1075,6 +1137,7 @@ _git-bug_select()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1105,6 +1168,7 @@ _git-bug_show()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1167,6 +1231,7 @@ _git-bug_status()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1215,6 +1280,7 @@ _git-bug_title_edit()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1236,6 +1302,7 @@ _git-bug_title()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1256,6 +1323,7 @@ _git-bug_user_adopt()
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }
 
@@ -1315,7 +1383,11 @@ _git-bug_user_ls()
 
     flags+=("--format=")
     two_word_flags+=("--format")
+    flags_with_completion+=("--format")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-f")
+    flags_with_completion+=("-f")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--format")
     local_nonpersistent_flags+=("--format=")
     local_nonpersistent_flags+=("-f")
@@ -1344,13 +1416,18 @@ _git-bug_user()
 
     flags+=("--field=")
     two_word_flags+=("--field")
+    flags_with_completion+=("--field")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     two_word_flags+=("-f")
+    flags_with_completion+=("-f")
+    flags_completion+=("__git-bug_handle_go_custom_completion")
     local_nonpersistent_flags+=("--field")
     local_nonpersistent_flags+=("--field=")
     local_nonpersistent_flags+=("-f")
 
     must_have_one_flag=()
     must_have_one_noun=()
+    has_completion_function=1
     noun_aliases=()
 }