diff --git a/pkg/gui/discard_changes_menu_panel.go b/pkg/gui/discard_changes_menu_panel.go new file mode 100644 index 00000000000..b5a40b9e9c0 --- /dev/null +++ b/pkg/gui/discard_changes_menu_panel.go @@ -0,0 +1,42 @@ +package gui + +import ( + "github.com/jesseduffield/gocui" +) + +func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error { + file, err := gui.getSelectedFile(g) + if err != nil { + if err != gui.Errors.ErrNoFiles { + return err + } + return nil + } + + menuItems := []*menuItem{ + { + displayStrings: []string{gui.Tr.SLocalize("discardAllChanges")}, + onPress: func() error { + if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil { + return err + } + return gui.refreshFiles() + }, + }, + } + + if file.HasStagedChanges && file.HasUnstagedChanges { + menuItems = append(menuItems, &menuItem{ + displayStrings: []string{gui.Tr.SLocalize("discardUnstagedChanges")}, + onPress: func() error { + if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil { + return err + } + + return gui.refreshFiles() + }, + }) + } + + return gui.createMenuNew(file.Name, menuItems, createMenuOptions{showCancel: true}) +} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index d7a5466cbba..c2f3b9206f3 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -540,67 +540,6 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool { return false } -type discardOption struct { - handler func(fileName *commands.File) error - description string -} - -// GetDisplayStrings is a function. -func (r *discardOption) GetDisplayStrings(isFocused bool) []string { - return []string{r.description} -} - -func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error { - file, err := gui.getSelectedFile(g) - if err != nil { - if err != gui.Errors.ErrNoFiles { - return err - } - return nil - } - - options := []*discardOption{ - { - description: gui.Tr.SLocalize("discardAllChanges"), - handler: func(file *commands.File) error { - return gui.GitCommand.DiscardAllFileChanges(file) - }, - }, - { - description: gui.Tr.SLocalize("cancel"), - handler: func(file *commands.File) error { - return nil - }, - }, - } - - if file.HasStagedChanges && file.HasUnstagedChanges { - discardUnstagedChanges := &discardOption{ - description: gui.Tr.SLocalize("discardUnstagedChanges"), - handler: func(file *commands.File) error { - return gui.GitCommand.DiscardUnstagedFileChanges(file) - }, - } - - options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...) - } - - handleMenuPress := func(index int) error { - file, err := gui.getSelectedFile(g) - if err != nil { - return err - } - - if err := options[index].handler(file); err != nil { - return err - } - - return gui.refreshFiles() - } - - return gui.createMenu(file.Name, options, len(options), handleMenuPress) -} - func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error { return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), "", func(g *gocui.Gui, v *gocui.View) error { command := gui.trimmedContent(v) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 526f66d7837..94a889506cd 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,6 +8,11 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) +type menuItem struct { + displayStrings []string + onPress func() error +} + // list panel functions func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error { @@ -38,6 +43,75 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } +type createMenuOptions struct { + showCancel bool +} + +func (gui *Gui) createMenuNew(title string, items []*menuItem, createMenuOptions createMenuOptions) error { + if createMenuOptions.showCancel { + // this is mutative but I'm okay with that for now + items = append(items, &menuItem{ + displayStrings: []string{gui.Tr.SLocalize("cancel")}, + onPress: func() error { + return nil + }, + }) + } + + gui.State.MenuItemCount = len(items) + + stringArrays := make([][]string, len(items)) + for i, item := range items { + stringArrays[i] = item.displayStrings + } + + list := utils.RenderDisplayStrings(stringArrays) + + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list) + menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) + menuView.Title = title + menuView.FgColor = theme.GocuiDefaultTextColor + menuView.Clear() + fmt.Fprint(menuView, list) + gui.State.Panels.Menu.SelectedLine = 0 + + wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { + selectedLine := gui.State.Panels.Menu.SelectedLine + if err := items[selectedLine].onPress(); err != nil { + return err + } + + if _, err := gui.g.View("menu"); err == nil { + if _, err := gui.g.SetViewOnBottom("menu"); err != nil { + return err + } + } + + return gui.returnFocus(gui.g, menuView) + } + + gui.State.Panels.Menu.OnPress = wrappedHandlePress + + for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} { + _ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone) + + if err := gui.g.SetKeybinding("menu", nil, key, gocui.ModNone, wrappedHandlePress); err != nil { + return err + } + } + + gui.g.Update(func(g *gocui.Gui) error { + if _, err := gui.g.View("menu"); err == nil { + if _, err := g.SetViewOnTop("menu"); err != nil { + return err + } + } + currentView := gui.g.CurrentView() + return gui.switchFocus(gui.g, currentView, menuView) + }) + return nil +} + func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error { isFocused := gui.g.CurrentView().Name() == "menu" gui.State.MenuItemCount = itemCount diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 26d1b9021e5..093f8069e58 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -151,14 +151,14 @@ func renderDisplayableList(items []Displayable, isFocused bool) (string, error) stringArrays := getDisplayStringArrays(items, isFocused) - if !displayArraysAligned(stringArrays) { - return "", errors.New("Each item must return the same number of strings to display") - } + return RenderDisplayStrings(stringArrays), nil +} - padWidths := getPadWidths(stringArrays) - paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths) +func RenderDisplayStrings(displayStringsArr [][]string) string { + padWidths := getPadWidths(displayStringsArr) + paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths) - return strings.Join(paddedDisplayStrings, "\n"), nil + return strings.Join(paddedDisplayStrings, "\n") } // Decolorise strips a string of color @@ -168,10 +168,13 @@ func Decolorise(str string) string { } func getPadWidths(stringArrays [][]string) []int { - if len(stringArrays[0]) <= 1 { - return []int{} + maxWidth := 0 + for _, stringArray := range stringArrays { + if len(stringArray) > maxWidth { + maxWidth = len(stringArray) + } } - padWidths := make([]int, len(stringArrays[0])-1) + padWidths := make([]int, maxWidth-1) for i := range padWidths { for _, strings := range stringArrays { uncoloredString := Decolorise(strings[i]) @@ -190,8 +193,14 @@ func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string continue } for j, padWidth := range padWidths { + if len(stringArray)-1 < j { + continue + } paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " } + if len(stringArray)-1 < len(padWidths) { + continue + } paddedDisplayStrings[i] += stringArray[len(padWidths)] } return paddedDisplayStrings