Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autocomplete a command's args #106

Merged
merged 3 commits into from
Mar 14, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,10 +670,10 @@ func (a *Application) completionOptions(context *ParseContext) []string {
}
}
return options
} else {
// Perform completion for sub commands.
return target.CmdCompletion()
}

// Perform completion for sub commands and arguments.
return target.CmdCompletion()
}

func (a *Application) generateBashCompletion(context *ParseContext) {
Expand Down
36 changes: 33 additions & 3 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,21 @@ func TestBashCompletionOptions(t *testing.T) {
two.Flag("flag-2", "").String()
two.Flag("flag-3", "").HintOptions("opt4", "opt5", "opt6").String()

three := a.Command("three", "")
three.Flag("flag-4", "").String()
three.Arg("arg-1", "").String()
three.Arg("arg-2", "").HintOptions("arg-2-opt-1", "arg-2-opt-2").String()
three.Arg("arg-3", "").HintAction(func() []string {
return []string{"arg-3-opt-1", "arg-3-opt-2"}
}).String()

cases := []struct {
Args string
ExpectedOptions []string
}{
{
Args: "--completion-bash",
ExpectedOptions: []string{"help", "one", "two"},
ExpectedOptions: []string{"help", "one", "three", "two"},
},
{
Args: "--completion-bash --",
Expand All @@ -263,7 +271,7 @@ func TestBashCompletionOptions(t *testing.T) {
{
// No options available for flag-0, return to cmd completion
Args: "--completion-bash --flag-0",
ExpectedOptions: []string{"help", "one", "two"},
ExpectedOptions: []string{"help", "one", "three", "two"},
},
{
Args: "--completion-bash --flag-0 --",
Expand All @@ -279,7 +287,7 @@ func TestBashCompletionOptions(t *testing.T) {
},
{
Args: "--completion-bash --flag-1 opt1",
ExpectedOptions: []string{"help", "one", "two"},
ExpectedOptions: []string{"help", "one", "three", "two"},
},
{
Args: "--completion-bash --flag-1 opt1 --",
Expand Down Expand Up @@ -334,6 +342,28 @@ func TestBashCompletionOptions(t *testing.T) {
Args: "--completion-bash two --flag-3 opt4 --",
ExpectedOptions: []string{"--help", "--flag-2", "--flag-3", "--flag-0", "--flag-1"},
},

// Args complete
{
// After a command with arg options
Args: "--completion-bash three ",
ExpectedOptions: []string{"arg-2-opt-1", "arg-2-opt-2", "arg-3-opt-1", "arg-3-opt-2"},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, these hints shouldn't be available here, as (unlike flags) args are positional. At this point it should be completing for arg-1 only which has no hints and thus should not be completing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll have to spend a bit more time understanding the parsing logic to plumb down where in the process it is to the code that knows where to get completion options from. I'll get on that.

},
{
// But not after flag start
Args: "--completion-bash three --",
ExpectedOptions: []string{"--flag-0", "--flag-1", "--flag-4", "--help"},
},
{
// Completes args after one already listed
Args: "--completion-bash three firstArg ",
ExpectedOptions: []string{"arg-2-opt-1", "arg-2-opt-2", "arg-3-opt-1", "arg-3-opt-2"},
},
{
// Completes args after a flag
Args: "--completion-bash three firstArg --flag-4 ",
ExpectedOptions: []string{"arg-2-opt-1", "arg-2-opt-2", "arg-3-opt-1", "arg-3-opt-2"},
},
}

for _, c := range cases {
Expand Down
15 changes: 15 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (a *argGroup) init() error {
type ArgClause struct {
actionMixin
parserMixin
completionsMixin
name string
help string
defaultValues []string
Expand Down Expand Up @@ -107,6 +108,20 @@ func (a *ArgClause) PreAction(action Action) *ArgClause {
return a
}

// HintAction registers a HintAction (function) for the arg to provide completions
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
a.addHintAction(action)
return a
}

// HintOptions registers any number of options for the flag to provide completions
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
a.addHintAction(func() []string {
return options
})
return a
}

func (a *ArgClause) init() error {
if a.required && len(a.defaultValues) > 0 {
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
Expand Down
21 changes: 11 additions & 10 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ type cmdMixin struct {
}

func (c *cmdMixin) CmdCompletion() []string {
rv := []string{}
if len(c.cmdGroup.commandOrder) > 0 {
// This command has subcommands. We should
// show these to the user.
for _, option := range c.cmdGroup.commandOrder {
rv = append(rv, option.name)
}
} else {
// No subcommands
rv = nil
var rv []string

// If this command has subcommands, we should show these to the user.
for _, option := range c.cmdGroup.commandOrder {
rv = append(rv, option.name)
}

// Add any completions from args as well
for _, arg := range c.argGroup.args {
rv = append(rv, arg.resolveCompletions()...)
}

return rv
}

Expand Down