Skip to content

Commit

Permalink
Support using command output directly in menuFromCommand custom comma…
Browse files Browse the repository at this point in the history
…nd prompt

The menuFromCommand option is a little complicated, so I'm adding an easy way to just use the command output directly,
where each line becomes a suggestion, as-is.

Now that we support suggestions in the input prompt, there's less of a need for menuFromCommand, but it probably still
serves some purpose.

In future I want to support this filter/valueFormat/labelFormat thing for suggestions too. I would like to think a little more
about the interface though: is using a regex like we currently do really the simplest approach?
  • Loading branch information
jesseduffield committed May 29, 2023
1 parent 036a1ea commit 1de876e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 25 deletions.
14 changes: 14 additions & 0 deletions docs/Custom_Command_Keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pkg/gui/services/custom_commands/handler_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
57 changes: 37 additions & 20 deletions pkg/gui/services/custom_commands/menu_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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)
Expand Down
32 changes: 28 additions & 4 deletions pkg/gui/services/custom_commands/menu_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -24,7 +24,7 @@ func TestMenuGenerator(t *testing.T) {
"(?P<remote>[a-z_]+)/(?P<branch>.*)",
"{{ .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)
Expand All @@ -36,7 +36,7 @@ func TestMenuGenerator(t *testing.T) {
"(?P<remote>[a-z]*)/(?P<branch>.*)",
"{{ .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)
Expand All @@ -48,12 +48,36 @@ func TestMenuGenerator(t *testing.T) {
"(?P<remote>[a-z]*)/(?P<branch>.*)",
"{{ .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 {
Expand Down

0 comments on commit 1de876e

Please sign in to comment.