1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-28 09:08:41 +02:00

Make menuFromCommand format menu items and their description

This commit is contained in:
Elwardi 2021-08-05 15:24:17 +01:00
parent d626bcac00
commit a8ec044f0e
4 changed files with 65 additions and 38 deletions

View File

@ -47,7 +47,8 @@ customCommands:
title: 'Remote branch:'
command: 'git branch -r --list {{index .PromptResponses 0}}/*'
filter: '.*{{index .PromptResponses 0}}/(?P<branch>.*)'
format: '{{ .branch }}'
itemFormat: '{{ .branch }}'
descriptionFormat: ''
```
Looking at the command assigned to the 'n' key, here's what the result looks like:
@ -92,19 +93,24 @@ The permitted contexts are:
The permitted prompt fields are:
| _field_ | _description_ | _required_ |
| ------------ | -------------------------------------------------------------------------------- | ---------- |
| type | one of 'input' or 'menu' | yes |
| title | the title to display in the popup panel | no |
| initialValue | (only applicable to 'input' prompts) the initial value to appear in the text box | no |
| options | (only applicable to 'menu' prompts) the options to display in the menu | no |
| command | (only applicable to 'menuFromCommand' prompts) the command to run to generate | yes |
| | menu options | |
| filter | (only applicable to 'menuFromCommand' prompts) the regexp to run specifying | yes |
| | groups which are going to be kept from the command's output | |
| format | (only applicable to 'menuFromCommand' prompts) how to format matched groups from | yes |
| | the filter. You can use named groups, or `{{ .group_GROUPID }}`. | yes |
| | PS: named groups keep first match only | yes |
| _field_ | _description_ | _required_ |
| ------------ | -------------------------------------------------------------------------------- | ---------- |
| type | one of 'input' or 'menu' | yes |
| title | the title to display in the popup panel | no |
| initialValue | (only applicable to 'input' prompts) the initial value to appear in the text box | no |
| options | (only applicable to 'menu' prompts) the options to display in the menu | no |
| command | (only applicable to 'menuFromCommand' prompts) the command to run to generate | yes |
| | menu options | |
| filter | (only applicable to 'menuFromCommand' prompts) the regexp to run specifying | yes |
| | groups which are going to be kept from the command's output | |
| itemFormat | (only applicable to 'menuFromCommand' prompts) how to format matched groups from | yes |
| | the filter to construct a menu item. You can use named groups, | yes |
| | or `{{ .group_GROUPID }}`. | |
| | PS: named groups keep first match only | yes |
| descriptionFormat | (only applicable to 'menuFromCommand' prompts) how to format matched groups from | yes |
| | the filter to construct a menu item's description. You can use named groups, | yes |
| | or `{{ .group_GROUPID }}`. | |
| | PS: named groups keep first match only | yes |
The permitted option fields are:
| _field_ | _description_ | _required_ |

View File

@ -285,7 +285,8 @@ type CustomCommandPrompt struct {
// this only applies to menuFromCommand
Command string `yaml:"command"`
Filter string `yaml:"filter"`
Format string `yaml:"format"`
TFormat string `yaml:"itemFormat"`
DFormat string `yaml:"descriptionFormat"`
}
type CustomCommandMenuOption struct {

View File

@ -118,16 +118,22 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
}
func (gui *Gui) GenerateMenuCandidates(commandOutput string, filter string, format string) ([]string, error) {
func (gui *Gui) GenerateMenuCandidates(commandOutput string, filter string, tFormat string, dFormat string) ([]string, []string, error) {
candidates := []string{}
descriptions := []string{}
reg, err := regexp.Compile(filter)
if err != nil {
return candidates, gui.surfaceError(errors.New("unable to parse filter regex, error: " + err.Error()))
return candidates, descriptions, gui.surfaceError(errors.New("unable to parse filter regex, error: " + err.Error()))
}
buff := bytes.NewBuffer(nil)
temp, err := template.New("format").Parse(format)
buffTitle := bytes.NewBuffer(nil)
tempTitle, err := template.New("format").Parse(tFormat)
if err != nil {
return candidates, gui.surfaceError(errors.New("unable to parse format, error: " + err.Error()))
return candidates, descriptions, gui.surfaceError(errors.New("unable to parse item format, error: " + err.Error()))
}
buffDescr := bytes.NewBuffer(nil)
tempDescr, err := template.New("format").Parse(dFormat)
if err != nil {
return candidates, descriptions, gui.surfaceError(errors.New("unable to parse item description format, error: " + err.Error()))
}
for _, str := range strings.Split(string(commandOutput), "\n") {
if str == "" {
@ -146,15 +152,21 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput string, filter string, form
}
}
}
err = temp.Execute(buff, tmplData)
err = tempTitle.Execute(buffTitle, tmplData)
if err != nil {
return candidates, gui.surfaceError(err)
return candidates, descriptions, gui.surfaceError(err)
}
err = tempDescr.Execute(buffDescr, tmplData)
if err != nil {
return candidates, descriptions, gui.surfaceError(err)
}
candidates = append(candidates, strings.TrimSpace(buff.String()))
buff.Reset()
candidates = append(candidates, strings.TrimSpace(buffTitle.String()))
descriptions = append(descriptions, strings.TrimSpace(buffDescr.String()))
buffTitle.Reset()
buffDescr.Reset()
}
return candidates, err
return candidates, descriptions, err
}
func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
@ -177,7 +189,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
}
// Need to make a menu out of what the cmd has displayed
candidates, err := gui.GenerateMenuCandidates(message, filter, prompt.Format)
candidates, descriptions, err := gui.GenerateMenuCandidates(message, filter, prompt.TFormat, prompt.DFormat)
if err != nil {
return gui.surfaceError(err)
}
@ -185,7 +197,8 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
menuItems := make([]*menuItem, len(candidates))
for i := range candidates {
menuItems[i] = &menuItem{
displayStrings: []string{candidates[i]},
// Put in candidate and its description
displayStrings: []string{candidates[i], style.FgYellow.Sprint(descriptions[i])},
onPress: func() error {
promptResponses[responseIdx] = candidates[i]
return wrappedF()

View File

@ -86,29 +86,34 @@ func TestGuiGenerateMenuCandidates(t *testing.T) {
testName string
cmdOut string
filter string
format string
test func([]string, error)
tFormat string
dFormat string
test func([]string, []string, error)
}
scenarios := []scenario{
{
"Extract remote branch name",
"upstream/pr-1",
"upstream/(?P<branch>.*)",
"(?P<remote>[a-z_]+)/(?P<branch>.*)",
"{{ .branch }}",
func(actual []string, err error) {
"Remote: {{ .remote }}",
func(actualCandidate []string, actualDescr []string, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "pr-1", actual[0])
assert.EqualValues(t, "pr-1", actualCandidate[0])
assert.EqualValues(t, "Remote: upstream", actualDescr[0])
},
},
{
"Multiple named groups",
"Multiple named groups with empty description",
"upstream/pr-1",
"(?P<remote>[a-z]*)/(?P<branch>.*)",
"{{ .branch }}|{{ .remote }}",
func(actual []string, err error) {
"",
func(actualCandidate []string, actualDescr []string, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "pr-1|upstream", actual[0])
assert.EqualValues(t, "pr-1|upstream", actualCandidate[0])
assert.EqualValues(t, "", actualDescr[0])
},
},
{
@ -116,16 +121,18 @@ func TestGuiGenerateMenuCandidates(t *testing.T) {
"upstream/pr-1",
"(?P<remote>[a-z]*)/(?P<branch>.*)",
"{{ .group_2 }}|{{ .group_1 }}",
func(actual []string, err error) {
"Remote: {{ .group_1 }}",
func(actualCandidate []string, actualDescr []string, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "pr-1|upstream", actual[0])
assert.EqualValues(t, "pr-1|upstream", actualCandidate[0])
assert.EqualValues(t, "Remote: upstream", actualDescr[0])
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(NewDummyGui().GenerateMenuCandidates(s.cmdOut, s.filter, s.format))
s.test(NewDummyGui().GenerateMenuCandidates(s.cmdOut, s.filter, s.tFormat, s.dFormat))
})
}
}