diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md index d6e796c66..7565b9f3a 100644 --- a/docs/Custom_Command_Keybindings.md +++ b/docs/Custom_Command_Keybindings.md @@ -264,6 +264,20 @@ Here's an example using unnamed groups: labelFormat: '{{ .group_1 | green }}' ``` +Here's an example using a command but not specifying anything else: so each line from the command becomes the value and label of the menu items + +```yml + - key : 'a' + description: 'Checkout a remote branch as FETCH_HEAD' + command: "open {{.Form.File | quote}}" + context: 'global' + prompts: + - type: 'menuFromCommand' + title: 'File:' + key: 'File' + command: 'ls' +``` + ## Placeholder values Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects: diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 8e6700f72..4dc6bc86e 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -210,7 +210,7 @@ func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPr return self.c.Error(err) } - menuItems := slices.Map(candidates, func(candidate *commandMenuEntry) *types.MenuItem { + menuItems := slices.Map(candidates, func(candidate *commandMenuItem) *types.MenuItem { return &types.MenuItem{ LabelColumns: []string{candidate.label}, OnPress: func() error { diff --git a/pkg/gui/services/custom_commands/menu_generator.go b/pkg/gui/services/custom_commands/menu_generator.go index 5bec1db91..4a73fe433 100644 --- a/pkg/gui/services/custom_commands/menu_generator.go +++ b/pkg/gui/services/custom_commands/menu_generator.go @@ -22,12 +22,41 @@ func NewMenuGenerator(c *common.Common) *MenuGenerator { return &MenuGenerator{c: c} } -type commandMenuEntry struct { +type commandMenuItem struct { label string value string } -func (self *MenuGenerator) call(commandOutput, filter, valueFormat, labelFormat string) ([]*commandMenuEntry, error) { +func (self *MenuGenerator) call(commandOutput, filter, valueFormat, labelFormat string) ([]*commandMenuItem, error) { + menuItemFromLine, err := self.getMenuItemFromLinefn(filter, valueFormat, labelFormat) + if err != nil { + return nil, err + } + + menuItems := []*commandMenuItem{} + for _, line := range strings.Split(commandOutput, "\n") { + if line == "" { + continue + } + + menuItem, err := menuItemFromLine(line) + if err != nil { + return nil, err + } + menuItems = append(menuItems, menuItem) + } + + return menuItems, nil +} + +func (self *MenuGenerator) getMenuItemFromLinefn(filter string, valueFormat string, labelFormat string) (func(line string) (*commandMenuItem, error), error) { + if filter == "" && valueFormat == "" && labelFormat == "" { + // showing command output lines as-is in suggestions panel + return func(line string) (*commandMenuItem, error) { + return &commandMenuItem{label: line, value: line}, nil + }, nil + } + regex, err := regexp.Compile(filter) if err != nil { return nil, errors.New("unable to parse filter regex, error: " + err.Error()) @@ -51,37 +80,25 @@ func (self *MenuGenerator) call(commandOutput, filter, valueFormat, labelFormat labelTemplate = valueTemplate } - candidates := []*commandMenuEntry{} - for _, line := range strings.Split(commandOutput, "\n") { - if line == "" { - continue - } - - candidate, err := self.generateMenuCandidate( + return func(line string) (*commandMenuItem, error) { + return self.generateMenuItem( line, regex, valueTemplate, labelTemplate, ) - if err != nil { - return nil, err - } - - candidates = append(candidates, candidate) - } - - return candidates, err + }, nil } -func (self *MenuGenerator) generateMenuCandidate( +func (self *MenuGenerator) generateMenuItem( line string, regex *regexp.Regexp, valueTemplate *TrimmerTemplate, labelTemplate *TrimmerTemplate, -) (*commandMenuEntry, error) { +) (*commandMenuItem, error) { tmplData := self.parseLine(line, regex) - entry := &commandMenuEntry{} + entry := &commandMenuItem{} var err error entry.value, err = valueTemplate.execute(tmplData) diff --git a/pkg/gui/services/custom_commands/menu_generator_test.go b/pkg/gui/services/custom_commands/menu_generator_test.go index 7dd3e58e8..c17f7e93a 100644 --- a/pkg/gui/services/custom_commands/menu_generator_test.go +++ b/pkg/gui/services/custom_commands/menu_generator_test.go @@ -14,7 +14,7 @@ func TestMenuGenerator(t *testing.T) { filter string valueFormat string labelFormat string - test func([]*commandMenuEntry, error) + test func([]*commandMenuItem, error) } scenarios := []scenario{ @@ -24,7 +24,7 @@ func TestMenuGenerator(t *testing.T) { "(?P[a-z_]+)/(?P.*)", "{{ .branch }}", "Remote: {{ .remote }}", - func(actualEntry []*commandMenuEntry, err error) { + func(actualEntry []*commandMenuItem, err error) { assert.NoError(t, err) assert.EqualValues(t, "pr-1", actualEntry[0].value) assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) @@ -36,7 +36,7 @@ func TestMenuGenerator(t *testing.T) { "(?P[a-z]*)/(?P.*)", "{{ .branch }}|{{ .remote }}", "", - func(actualEntry []*commandMenuEntry, err error) { + func(actualEntry []*commandMenuItem, err error) { assert.NoError(t, err) assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) assert.EqualValues(t, "pr-1|upstream", actualEntry[0].label) @@ -48,12 +48,36 @@ func TestMenuGenerator(t *testing.T) { "(?P[a-z]*)/(?P.*)", "{{ .group_2 }}|{{ .group_1 }}", "Remote: {{ .group_1 }}", - func(actualEntry []*commandMenuEntry, err error) { + func(actualEntry []*commandMenuItem, err error) { assert.NoError(t, err) assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) }, }, + { + "No named groups", + "upstream/pr-1", + "([a-z]*)/(.*)", + "{{ .group_2 }}|{{ .group_1 }}", + "Remote: {{ .group_1 }}", + func(actualEntry []*commandMenuItem, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value) + assert.EqualValues(t, "Remote: upstream", actualEntry[0].label) + }, + }, + { + "No filter", + "upstream/pr-1", + "", + "", + "", + func(actualEntry []*commandMenuItem, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "upstream/pr-1", actualEntry[0].value) + assert.EqualValues(t, "upstream/pr-1", actualEntry[0].label) + }, + }, } for _, s := range scenarios {