1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-03 15:02:52 +02:00
lazygit/pkg/gui/list_context.go

402 lines
14 KiB
Go
Raw Normal View History

2019-11-16 14:00:27 +11:00
package gui
2020-08-19 21:51:50 +10:00
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
)
2019-11-16 14:00:27 +11:00
2020-08-17 21:58:30 +10:00
type ListContext struct {
2020-08-16 10:05:45 +10:00
ViewName string
2020-08-16 14:16:28 +10:00
ContextKey string
2020-08-16 10:05:45 +10:00
GetItemsLength func() int
GetSelectedLineIdxPtr func() *int
2020-08-19 21:51:50 +10:00
GetDisplayStrings func() [][]string
2020-08-16 10:05:45 +10:00
OnFocus func() error
2020-08-16 13:58:29 +10:00
OnFocusLost func() error
2020-08-16 10:05:45 +10:00
OnClickSelectedItem func() error
2020-08-19 18:06:51 +10:00
Gui *Gui
RendersToMainView bool
Kind int
2019-11-16 14:00:27 +11:00
}
2020-08-19 21:51:50 +10: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 21:58:30 +10:00
func (lc *ListContext) GetKey() string {
return lc.ContextKey
2020-08-16 13:58:29 +10:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) GetKind() int {
return lc.Kind
2020-08-16 13:58:29 +10:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) GetViewName() string {
return lc.ViewName
2020-08-16 13:58:29 +10:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) HandleFocusLost() error {
if lc.OnFocusLost != nil {
return lc.OnFocusLost()
2020-08-16 13:58:29 +10:00
}
return nil
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) HandleFocus() error {
2020-08-19 22:27:31 +10:00
if lc.Gui.popupPanelFocused() {
return nil
}
if lc.Gui.inDiffMode() {
return lc.Gui.renderDiff()
}
if lc.OnFocus != nil {
return lc.OnFocus()
}
return nil
2020-08-16 13:58:29 +10:00
}
2020-08-19 18:06:51 +10:00
func (lc *ListContext) HandleRender() error {
return lc.OnRender()
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) handlePrevLine(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(-1)
2019-11-16 14:00:27 +11:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) handleNextLine(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(1)
2019-11-16 14:00:27 +11:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) handleLineChange(change int) error {
if !lc.Gui.isPopupPanel(lc.ViewName) && lc.Gui.popupPanelFocused() {
2019-11-16 14:00:27 +11:00
return nil
}
2020-08-17 21:58:30 +10:00
view, err := lc.Gui.g.View(lc.ViewName)
2020-08-16 09:18:57 +10:00
if err != nil {
return err
}
2020-08-17 21:58:30 +10:00
lc.Gui.changeSelectedLine(lc.GetSelectedLineIdxPtr(), lc.GetItemsLength(), change)
view.FocusPoint(0, *lc.GetSelectedLineIdxPtr())
2019-11-16 14:00:27 +11:00
2020-08-17 21:58:30 +10:00
if lc.RendersToMainView {
if err := lc.Gui.resetOrigin(lc.Gui.getMainView()); err != nil {
2019-11-16 14:00:27 +11:00
return err
}
2020-08-17 21:58:30 +10:00
if err := lc.Gui.resetOrigin(lc.Gui.getSecondaryView()); err != nil {
2020-08-16 09:18:57 +10:00
return err
}
2019-11-16 14:00:27 +11:00
}
2020-08-19 22:27:31 +10:00
return lc.HandleFocus()
2019-11-16 14:00:27 +11:00
}
2020-08-17 21:58:30 +10: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 21:58:30 +10:00
return lc.handleLineChange(delta)
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) handleGotoTop(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(-lc.GetItemsLength())
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) handleGotoBottom(g *gocui.Gui, v *gocui.View) error {
return lc.handleLineChange(lc.GetItemsLength())
}
2020-08-17 21:58:30 +10: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 21:58:30 +10:00
return lc.handleLineChange(-delta)
}
2020-08-17 21:58:30 +10: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 21:58:30 +10:00
selectedLineIdxPtr := lc.GetSelectedLineIdxPtr()
prevSelectedLineIdx := *selectedLineIdxPtr
newSelectedLineIdx := v.SelectedLineIdx()
2020-08-16 09:18:57 +10:00
// we need to focus the view
2020-08-17 21:58:30 +10:00
if err := lc.Gui.switchContext(lc); err != nil {
2020-08-16 09:18:57 +10:00
return err
}
2020-08-17 21:58:30 +10:00
if newSelectedLineIdx > lc.GetItemsLength()-1 {
2020-08-16 13:58:29 +10:00
return nil
}
*selectedLineIdxPtr = newSelectedLineIdx
2020-08-17 21:58:30 +10:00
prevViewName := lc.Gui.currentViewName()
if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == lc.ViewName && lc.OnClickSelectedItem != nil {
return lc.OnClickSelectedItem()
}
2020-08-19 22:27:31 +10:00
return lc.HandleFocus()
2020-08-16 09:18:57 +10:00
}
2020-08-17 21:58:30 +10:00
func (lc *ListContext) onSearchSelect(selectedLineIdx int) error {
*lc.GetSelectedLineIdxPtr() = selectedLineIdx
2020-08-19 22:27:31 +10:00
return lc.HandleFocus()
2020-08-16 09:18:57 +10:00
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) menuListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "menu",
2020-08-16 14:16:28 +10:00
ContextKey: "menu",
2020-08-16 10:05:45 +10:00
GetItemsLength: func() int { return gui.getMenuView().LinesHeight() },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Menu.SelectedLine },
OnFocus: gui.handleMenuSelect,
2020-08-16 09:18:57 +10:00
// need to add a layer of indirection here because the callback changes during runtime
2020-08-16 10:05:45 +10:00
OnClickSelectedItem: func() error { return gui.State.Panels.Menu.OnPress(gui.g, nil) },
Gui: gui,
RendersToMainView: false,
2020-08-16 13:58:29 +10:00
Kind: PERSISTENT_POPUP,
2020-08-19 21:51:50 +10:00
// no GetDisplayStrings field because we do a custom render on menu creation
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) filesListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "files",
2020-08-16 14:16:28 +10:00
ContextKey: "files",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) branchesListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "branches",
2020-08-16 14:16:28 +10:00
ContextKey: "local-branches",
2020-08-16 10:05:45 +10:00
GetItemsLength: func() int { return len(gui.State.Branches) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Branches.SelectedLine },
OnFocus: gui.handleBranchSelect,
2020-08-19 18:41:57 +10:00
Gui: gui,
RendersToMainView: true,
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) remotesListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "branches",
2020-08-16 14:16:28 +10:00
ContextKey: "remotes",
2020-08-16 10:05:45 +10:00
GetItemsLength: func() int { return len(gui.State.Remotes) },
GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Remotes.SelectedLine },
2020-08-19 19:31:58 +10:00
OnFocus: gui.handleRemoteSelect,
2020-08-16 10:05:45 +10:00
OnClickSelectedItem: gui.handleRemoteEnter,
Gui: gui,
RendersToMainView: true,
2020-08-16 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) remoteBranchesListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "branches",
2020-08-16 14:16:28 +10:00
ContextKey: "remote-branches",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) tagsListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "branches",
2020-08-16 14:16:28 +10:00
ContextKey: "tags",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) branchCommitsListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "commits",
2020-08-16 14:16:28 +10:00
ContextKey: "branch-commits",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap(), gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) reflogCommitsListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "commits",
2020-08-16 14:16:28 +10:00
ContextKey: "reflog-commits",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetReflogCommitListDisplayStrings(gui.State.FilteredReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) stashListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "stash",
2020-08-16 14:16:28 +10:00
ContextKey: "stash",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
// TODO :see if we still need to reset the origin here
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10:00
func (gui *Gui) commitFilesListContext() *ListContext {
return &ListContext{
2020-08-16 10:05:45 +10:00
ViewName: "commitFiles",
2020-08-19 22:57:22 +10:00
ContextKey: "commit-files",
2020-08-16 10:05:45 +10: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 13:58:29 +10:00
Kind: SIDE_CONTEXT,
2020-08-19 21:51:50 +10:00
GetDisplayStrings: func() [][]string {
return presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles, gui.State.Diff.Ref)
},
2020-08-16 09:18:57 +10:00
}
}
2020-08-17 21:58:30 +10: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 14:00:27 +11:00
}
}
2020-08-17 22:05:15 +10: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
}