Repository: jesseduffield/lazygit
File: pkg/gui/custom_commands.go (Go)

Code before:
					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{}
					temp := template.Must(template.New("format").Parse(prompt.Format))
					for _, str := range strings.Split(string(message), "\n") {
						if str == "" {
							continue
						}
						buff := bytes.NewBuffer(nil)
						groupNames := reg.SubexpNames()
						tmplData := map[string]string{}
						for matchNum, match := range reg.FindAllStringSubmatch(str, -1) {
							if len(match) > 0 {
								for groupIdx, group := range match {
									// Record matched group with group and match ids
									matchName := "group_" + strconv.Itoa(groupIdx) + "_" + strconv.Itoa(matchNum)
									tmplData[matchName] = group
									// Record last named group non-empty matches as group matches
									name := groupNames[groupIdx]
									_, ok := tmplData[name]
									if name != "" && group != "" && !ok {
										tmplData[name] = group
									}
								}
							}
						}
						temp.Execute(buff, tmplData)
						candidates = append(candidates, strings.TrimSpace(buff.String()))
					}

					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()
							},

Review comment (performance):
I doubt if we want loop over every match, from the programming perspective this is an obvious set but i think users expect the program to only to use the first match.
This complicates the `match_..._...` and i think it's not directly clear to many users what the second argument is as they probably do not expect to match multiple times.  
I would say the extra complexity and code outweighs the advantages.

Code after:
	}

	// 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)
	if err != nil {
		return gui.surfaceError(err)
	}

	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))

		f := func() error {
			cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
			if err != nil {
				return gui.surfaceError(err)
			}

			if customCommand.Subprocess {
				return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr))
			}

			loadingText := customCommand.LoadingText
			if loadingText == "" {
				loadingText = gui.Tr.LcRunningCustomCommandStatus
			}