diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index c258b600f..e9aeedb4f 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -54,6 +54,149 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s return utils.ResolveTemplate(templateStr, objects) } +func (gui *Gui) inputPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error { + title, err := gui.resolveTemplate(prompt.Title, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + return gui.prompt(promptOpts{ + title: title, + initialContent: initialValue, + handleConfirm: func(str string) error { + promptResponses[responseIdx] = str + return wrappedF() + }, + }) +} + +func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error { + // need to make a menu here some how + menuItems := make([]*menuItem, len(prompt.Options)) + for i, option := range prompt.Options { + option := option + + nameTemplate := option.Name + if nameTemplate == "" { + // this allows you to only pass values rather than bother with names/descriptions + nameTemplate = option.Value + } + name, err := gui.resolveTemplate(nameTemplate, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + description, err := gui.resolveTemplate(option.Description, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + value, err := gui.resolveTemplate(option.Value, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + menuItems[i] = &menuItem{ + displayStrings: []string{name, utils.ColoredString(description, color.FgYellow)}, + onPress: func() error { + promptResponses[responseIdx] = value + return wrappedF() + }, + } + } + + title, err := gui.resolveTemplate(prompt.Title, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true}) +} +func (gui *Gui) generateMenuCandidates(commandOutput string, filter string, format string) ([]string, error) { + candidates := []string{} + reg, err := regexp.Compile(filter) + if err != nil { + return candidates, gui.surfaceError(errors.New("unable to parse filter regex, error: " + err.Error())) + } + buff := bytes.NewBuffer(nil) + temp, err := template.New("format").Parse(format) + if err != nil { + return candidates, gui.surfaceError(errors.New("unable to parse format, error: " + err.Error())) + } + for _, str := range strings.Split(string(commandOutput), "\n") { + if str == "" { + continue + } + tmplData := map[string]string{} + out := reg.FindAllStringSubmatch(str, -1) + if len(out) > 0 { + for groupIdx, group := range reg.SubexpNames() { + // Record matched group with group ids + matchName := "group_" + strconv.Itoa(groupIdx) + tmplData[matchName] = group + // Record last named group non-empty matches as group matches + if group != "" { + tmplData[group] = out[0][groupIdx] + } + } + } + err = temp.Execute(buff, tmplData) + if err != nil { + return candidates, gui.surfaceError(err) + } + + candidates = append(candidates, strings.TrimSpace(buff.String())) + buff.Reset() + } + return candidates, err +} + +func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error { + // Collect cmd to run from config + cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + // Collect Filter regexp + filter, err := gui.resolveTemplate(prompt.Filter, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + // Run and save output + message, err := gui.GitCommand.RunCommandWithOutput(cmdStr) + if err != nil { + return gui.surfaceError(err) + } + + // Need to make a menu out of what the cmd has displayed + candidates, err := gui.generateMenuCandidates(message, filter, prompt.Format) + + menuItems := make([]*menuItem, len(candidates)) + for i := range candidates { + menuItems[i] = &menuItem{ + displayStrings: []string{candidates[i]}, + onPress: func() error { + promptResponses[responseIdx] = candidates[i] + return wrappedF() + }, + } + } + + title, err := gui.resolveTemplate(prompt.Title, promptResponses) + if err != nil { + return gui.surfaceError(err) + } + + return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true}) +} + func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand) func() error { return func() error { promptResponses := make([]string, len(customCommand.Prompts)) @@ -94,144 +237,15 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand switch prompt.Type { case "input": f = func() error { - title, err := gui.resolveTemplate(prompt.Title, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - return gui.prompt(promptOpts{ - title: title, - initialContent: initialValue, - handleConfirm: func(str string) error { - promptResponses[idx] = str - - return wrappedF() - }, - }) + return gui.inputPrompt(prompt, promptResponses, idx, wrappedF) } case "menu": f = func() error { - // need to make a menu here some how - menuItems := make([]*menuItem, len(prompt.Options)) - for i, option := range prompt.Options { - option := option - - nameTemplate := option.Name - if nameTemplate == "" { - // this allows you to only pass values rather than bother with names/descriptions - nameTemplate = option.Value - } - name, err := gui.resolveTemplate(nameTemplate, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - description, err := gui.resolveTemplate(option.Description, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - value, err := gui.resolveTemplate(option.Value, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - menuItems[i] = &menuItem{ - displayStrings: []string{name, utils.ColoredString(description, color.FgYellow)}, - onPress: func() error { - promptResponses[idx] = value - - return wrappedF() - }, - } - } - - title, err := gui.resolveTemplate(prompt.Title, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true}) + return gui.menuPrompt(prompt, promptResponses, idx, wrappedF) } case "menuFromCommand": f = func() error { - // Collect cmd to run from config - cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - // Collect Filter regexp - filter, err := gui.resolveTemplate(prompt.Filter, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - reg, err := regexp.Compile(filter) - if err != nil { - return gui.surfaceError(errors.New("unable to parse filter regex, error: " + err.Error())) - } - - // Run and save output - message, err := gui.GitCommand.RunCommandWithOutput(cmdStr) - if err != nil { - return gui.surfaceError(err) - } - - // Need to make a menu out of what the cmd has displayed - candidates := []string{} - buff := bytes.NewBuffer(nil) - temp, err := template.New("format").Parse(prompt.Format) - if err != nil { - return gui.surfaceError(errors.New("unable to parse format, error: " + err.Error())) - } - for _, str := range strings.Split(string(message), "\n") { - if str == "" { - continue - } - tmplData := map[string]string{} - out := reg.FindAllStringSubmatch(str, -1) - if len(out) > 0 { - for groupIdx, group := range reg.SubexpNames() { - // Record matched group with group ids - matchName := "group_" + strconv.Itoa(groupIdx) - tmplData[matchName] = group - // Record last named group non-empty matches as group matches - if group != "" { - tmplData[group] = out[0][idx] - } - } - } - err = temp.Execute(buff, tmplData) - if err != nil { - return gui.surfaceError(err) - } - - candidates = append(candidates, strings.TrimSpace(buff.String())) - buff.Reset() - } - - menuItems := make([]*menuItem, len(candidates)) - for i := range candidates { - menuItems[i] = &menuItem{ - displayStrings: []string{candidates[i]}, - onPress: func() error { - promptResponses[idx] = candidates[i] - return wrappedF() - }, - } - } - - title, err := gui.resolveTemplate(prompt.Title, promptResponses) - if err != nil { - return gui.surfaceError(err) - } - - return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true}) + return gui.menuPromptFromCommand(prompt, promptResponses, idx, wrappedF) } default: return gui.createErrorPanel("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")