1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/list_context.go

440 lines
15 KiB
Go
Raw Normal View History

2019-11-16 05:00:27 +02:00
package gui
2020-08-19 13:51:50 +02:00
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
)
2019-11-16 05:00:27 +02:00
2020-08-17 13:58:30 +02:00
type ListContext struct {
2020-08-16 02:05:45 +02:00
ViewName string
2020-08-16 06:16:28 +02:00
ContextKey string
2020-08-16 02:05:45 +02:00
GetItemsLength func() int
GetSelectedLineIdxPtr func() *int
2020-08-19 13:51:50 +02:00
GetDisplayStrings func() [][]string
2020-08-16 02:05:45 +02:00
OnFocus func() error
2020-08-16 05:58:29 +02:00
OnFocusLost func() error
2020-08-16 02:05:45 +02:00
OnClickSelectedItem func() error
2020-08-20 00:24:35 +02:00
GetItems func() []ListItem
2020-08-19 10:06:51 +02:00
Gui *Gui
RendersToMainView bool
Kind int
2019-11-16 05:00:27 +02:00
}
2020-08-20 00:24:35 +02:00
type ListItem interface {
ID() string
}
func (lc *ListContext) GetSelectedItem() ListItem {
items := lc.GetItems()
if len(items) == 0 {
return nil
}
selectedLineIdx := *lc.GetSelectedLineIdxPtr()
if selectedLineIdx > len(items)-1 {
return nil
}
item := items[selectedLineIdx]
return item
}
func (lc *ListContext) GetSelectedItemId() string {
item := lc.GetSelectedItem()
if item == nil {
return ""
}
return item.ID()
}
2020-08-19 13:51:50 +02:00
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (lc *ListContext) OnRender() error {
view, err := lc.Gui.g.View(lc.ViewName)
if err != nil {
return nil
}
if lc.GetDisplayStrings != nil {
lc.Gui.refreshSelectedLine(lc.GetSelectedLineIdxPtr(), lc.GetItemsLength())
lc.Gui.renderDisplayStrings(view, lc.GetDisplayStrings())
}
return nil
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) GetKey() string {
return lc.ContextKey
2020-08-16 05:58:29 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) GetKind() int {
return lc.Kind
2020-08-16 05:58:29 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) GetViewName() string {
return lc.ViewName
2020-08-16 05:58:29 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) HandleFocusLost() error {
if lc.OnFocusLost != nil {
return lc.OnFocusLost()
2020-08-16 05:58:29 +02:00
}
return nil
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) HandleFocus() error {
2020-08-19 14:27:31 +02:00
if lc.Gui.popupPanelFocused() {
return nil
}
if lc.Gui.inDiffMode() {
return lc.Gui.renderDiff()
}
2020-08-20 00:24:35 +02:00
// every time you select an item we need to store that item's ID on the context (a string). After a state refresh, after we update the selected line, we need to check if the selected item is new, in which case we will reset the origin. In the case of the merge panel we set the origin in a custom way, so it can't be as simple as just resetting the origin. for files we need to know whether we're dealing with a file with merge conflicts, and if so, we need to scroll to the file in a custom way, after rendering to the main view.
// we can use this id to know what to do once we're actually in the merging context, so that we're not affected by outside state changes.
2020-08-19 14:27:31 +02:00
if lc.OnFocus != nil {
return lc.OnFocus()
}
return nil
2020-08-16 05:58:29 +02:00
}
2020-08-19 10:06:51 +02:00
func (lc *ListContext) HandleRender() error {
return lc.OnRender()
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handlePrevLine(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(-1)
2019-11-16 05:00:27 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleNextLine(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(1)
2019-11-16 05:00:27 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleLineChange(change int) error {
if !lc.Gui.isPopupPanel(lc.ViewName) && lc.Gui.popupPanelFocused() {
2019-11-16 05:00:27 +02:00
return nil
}
2020-08-17 13:58:30 +02:00
view, err := lc.Gui.g.View(lc.ViewName)
2020-08-16 01:18:57 +02:00
if err != nil {
return err
}
2020-08-17 13:58:30 +02:00
lc.Gui.changeSelectedLine(lc.GetSelectedLineIdxPtr(), lc.GetItemsLength(), change)
view.FocusPoint(0, *lc.GetSelectedLineIdxPtr())
2019-11-16 05:00:27 +02:00
2020-08-17 13:58:30 +02:00
if lc.RendersToMainView {
if err := lc.Gui.resetOrigin(lc.Gui.getMainView()); err != nil {
2019-11-16 05:00:27 +02:00
return err
}
2020-08-17 13:58:30 +02:00
if err := lc.Gui.resetOrigin(lc.Gui.getSecondaryView()); err != nil {
2020-08-16 01:18:57 +02:00
return err
}
2019-11-16 05:00:27 +02:00
}
2020-08-19 14:27:31 +02:00
return lc.HandleFocus()
2019-11-16 05:00:27 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleNextPage(g *gocui.Gui, v *gocui.View) error {
view, err := lc.Gui.g.View(lc.ViewName)
if err != nil {
return nil
}
_, height := view.Size()
delta := height - 1
if delta == 0 {
delta = 1
}
2020-08-17 13:58:30 +02:00
return lc.handleLineChange(delta)
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleGotoTop(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(-lc.GetItemsLength())
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleGotoBottom(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(lc.GetItemsLength())
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handlePrevPage(g *gocui.Gui, v *gocui.View) error {
view, err := lc.Gui.g.View(lc.ViewName)
if err != nil {
return nil
}
_, height := view.Size()
delta := height - 1
if delta == 0 {
delta = 1
}
2020-08-17 13:58:30 +02:00
return lc.handleLineChange(-delta)
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) handleClick(g *gocui.Gui, v *gocui.View) error {
if !lc.Gui.isPopupPanel(lc.ViewName) && lc.Gui.popupPanelFocused() {
return nil
}
2020-08-17 13:58:30 +02:00
selectedLineIdxPtr := lc.GetSelectedLineIdxPtr()
prevSelectedLineIdx := *selectedLineIdxPtr
newSelectedLineIdx := v.SelectedLineIdx()
2020-08-16 01:18:57 +02:00
// we need to focus the view
2020-08-17 13:58:30 +02:00
if err := lc.Gui.switchContext(lc); err != nil {
2020-08-16 01:18:57 +02:00
return err
}
2020-08-17 13:58:30 +02:00
if newSelectedLineIdx > lc.GetItemsLength()-1 {
2020-08-16 05:58:29 +02:00
return nil
}
*selectedLineIdxPtr = newSelectedLineIdx
2020-08-17 13:58:30 +02:00
prevViewName := lc.Gui.currentViewName()
if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == lc.ViewName && lc.OnClickSelectedItem != nil {
return lc.OnClickSelectedItem()
}
2020-08-19 14:27:31 +02:00
return lc.HandleFocus()
2020-08-16 01:18:57 +02:00
}
2020-08-17 13:58:30 +02:00
func (lc *ListContext) onSearchSelect(selectedLineIdx int) error {
*lc.GetSelectedLineIdxPtr() = selectedLineIdx
2020-08-19 14:27:31 +02:00
return lc.HandleFocus()
2020-08-16 01:18:57 +02:00
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) menuListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "menu",
2020-08-16 06:16:28 +02:00
ContextKey: "menu",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return gui.getMenuView().LinesHeight() },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Menu.SelectedLine },
OnFocus: gui.handleMenuSelect,
2020-08-16 01:18:57 +02:00
// need to add a layer of indirection here because the callback changes during runtime
2020-08-16 02:05:45 +02:00
OnClickSelectedItem: func() error { return gui.State.Panels.Menu.OnPress(gui.g, nil) },
Gui: gui,
RendersToMainView: false,
2020-08-16 05:58:29 +02:00
Kind: PERSISTENT_POPUP,
2020-08-20 00:24:35 +02:00
// GetItems:
2020-08-19 13:51:50 +02:00
// no GetDisplayStrings field because we do a custom render on menu creation
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) filesListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "files",
2020-08-16 06:16:28 +02:00
ContextKey: "files",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.Files) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Files.SelectedLine },
OnFocus: gui.focusAndSelectFile,
OnClickSelectedItem: gui.handleFilePress,
Gui: gui,
RendersToMainView: false,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) branchesListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "branches",
2020-08-16 06:16:28 +02:00
ContextKey: "local-branches",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.Branches) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Branches.SelectedLine },
OnFocus: gui.handleBranchSelect,
2020-08-19 10:41:57 +02:00
Gui: gui,
RendersToMainView: true,
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) remotesListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "branches",
2020-08-16 06:16:28 +02:00
ContextKey: "remotes",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.Remotes) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Remotes.SelectedLine },
2020-08-19 11:31:58 +02:00
OnFocus: gui.handleRemoteSelect,
2020-08-16 02:05:45 +02:00
OnClickSelectedItem: gui.handleRemoteEnter,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) remoteBranchesListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "branches",
2020-08-16 06:16:28 +02:00
ContextKey: "remote-branches",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.RemoteBranches) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.RemoteBranches.SelectedLine },
OnFocus: gui.handleRemoteBranchSelect,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) tagsListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "branches",
2020-08-16 06:16:28 +02:00
ContextKey: "tags",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.Tags) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Tags.SelectedLine },
OnFocus: gui.handleTagSelect,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) branchCommitsListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "commits",
2020-08-16 06:16:28 +02:00
ContextKey: "branch-commits",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.Commits) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Commits.SelectedLine },
OnFocus: gui.handleCommitSelect,
OnClickSelectedItem: gui.handleSwitchToCommitFilesPanel,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap(), gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) reflogCommitsListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "commits",
2020-08-16 06:16:28 +02:00
ContextKey: "reflog-commits",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.ReflogCommits.SelectedLine },
OnFocus: gui.handleReflogCommitSelect,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetReflogCommitListDisplayStrings(gui.State.FilteredReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) stashListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "stash",
2020-08-16 06:16:28 +02:00
ContextKey: "stash",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.StashEntries) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Stash.SelectedLine },
OnFocus: gui.handleStashEntrySelect,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) commitFilesListContext() *ListContext {
return &ListContext{
2020-08-16 02:05:45 +02:00
ViewName: "commitFiles",
2020-08-19 14:59:35 +02:00
ContextKey: "commitFiles",
2020-08-16 02:05:45 +02:00
GetItemsLength: func() int { return len(gui.State.CommitFiles) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.CommitFiles.SelectedLine },
OnFocus: gui.handleCommitFileSelect,
Gui: gui,
RendersToMainView: true,
2020-08-16 05:58:29 +02:00
Kind: SIDE_CONTEXT,
2020-08-19 13:51:50 +02:00
GetDisplayStrings: func() [][]string {
return presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles, gui.State.Diff.Ref)
},
2020-08-16 01:18:57 +02:00
}
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) getListContexts() []*ListContext {
return []*ListContext{
gui.menuListContext(),
gui.filesListContext(),
gui.branchesListContext(),
gui.remotesListContext(),
gui.remoteBranchesListContext(),
gui.tagsListContext(),
gui.branchCommitsListContext(),
gui.reflogCommitsListContext(),
gui.stashListContext(),
gui.commitFilesListContext(),
2019-11-16 05:00:27 +02:00
}
}
2020-08-17 14:05:15 +02:00
func (gui *Gui) getListContextKeyBindings() []*Binding {
bindings := make([]*Binding, 0)
for _, listContext := range gui.getListContexts() {
bindings = append(bindings, []*Binding{
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.prevItem-alt"), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.prevItem"), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.nextItem-alt"), Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.nextItem"), Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.prevPage"), Modifier: gocui.ModNone, Handler: listContext.handlePrevPage, Description: gui.Tr.SLocalize("prevPage")},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.nextPage"), Modifier: gocui.ModNone, Handler: listContext.handleNextPage, Description: gui.Tr.SLocalize("nextPage")},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gui.getKey("universal.gotoTop"), Modifier: gocui.ModNone, Handler: listContext.handleGotoTop, Description: gui.Tr.SLocalize("gotoTop")},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
{ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listContext.handleClick},
}...)
// the commits panel needs to lazyload things so it has a couple of its own handlers
openSearchHandler := gui.handleOpenSearch
gotoBottomHandler := listContext.handleGotoBottom
if listContext.ViewName == "commits" {
openSearchHandler = gui.handleOpenSearchForCommitsPanel
gotoBottomHandler = gui.handleGotoBottomForCommitsPanel
}
bindings = append(bindings, []*Binding{
{
ViewName: listContext.ViewName,
Contexts: []string{listContext.ContextKey},
Key: gui.getKey("universal.startSearch"),
Handler: openSearchHandler,
Description: gui.Tr.SLocalize("startSearch"),
},
{
ViewName: listContext.ViewName,
Contexts: []string{listContext.ContextKey},
Key: gui.getKey("universal.gotoBottom"),
Handler: gotoBottomHandler,
Description: gui.Tr.SLocalize("gotoBottom"),
},
}...)
}
return bindings
}