1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

refactor contexts

This commit is contained in:
Jesse Duffield
2022-02-05 17:04:10 +11:00
parent 145c69d9ae
commit d82f175e79
54 changed files with 1562 additions and 1248 deletions

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type BranchesContext struct {
*BranchesViewModel
*ListContextTrait
}
var _ types.IListContext = (*BranchesContext)(nil)
func NewBranchesContext(
getModel func() []*models.Branch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *BranchesContext {
viewModel := NewBranchesViewModel(getModel)
return &BranchesContext{
BranchesViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *BranchesContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type BranchesViewModel struct {
*traits.ListCursor
getModel func() []*models.Branch
}
func NewBranchesViewModel(getModel func() []*models.Branch) *BranchesViewModel {
self := &BranchesViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *BranchesViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *BranchesViewModel) GetSelected() *models.Branch {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -9,7 +9,6 @@ import (
type CommitFilesContext struct {
*filetree.CommitFileTreeViewModel
*BaseContext
*ListContextTrait
}
@ -17,7 +16,7 @@ var _ types.IListContext = (*CommitFilesContext)(nil)
func NewCommitFilesContext(
getModel func() []*models.CommitFile,
getView func() *gocui.View,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
@ -26,43 +25,30 @@ func NewCommitFilesContext(
c *types.ControllerCommon,
) *CommitFilesContext {
baseContext := NewBaseContext(NewBaseContextOpts{
ViewName: "commitFiles",
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})
self := &CommitFilesContext{}
takeFocus := func() error { return c.PushContext(self) }
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
viewTrait := NewViewTrait(getView)
listContextTrait := &ListContextTrait{
base: baseContext,
list: viewModel,
viewTrait: viewTrait,
GetDisplayStrings: getDisplayStrings,
OnFocus: onFocus,
OnRenderToMain: onRenderToMain,
OnFocusLost: onFocusLost,
takeFocus: takeFocus,
// TODO: handle this in a trait
RenderSelection: false,
c: c,
return &CommitFilesContext{
CommitFileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
ViewName: "commitFiles",
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}),
ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
baseContext.AddKeybindingsFn(listContextTrait.keybindings)
self.BaseContext = baseContext
self.ListContextTrait = listContextTrait
self.CommitFileTreeViewModel = viewModel
return self
}
func (self *CommitFilesContext) GetSelectedItemId() string {

View File

@ -1,6 +1,10 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
import (
"sync"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
const (
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
@ -60,18 +64,18 @@ type ContextTree struct {
Global types.Context
Status types.Context
Files *WorkingTreeContext
Submodules types.IListContext
Menu types.IListContext
Branches types.IListContext
Remotes types.IListContext
RemoteBranches types.IListContext
Menu *MenuContext
Branches *BranchesContext
Tags *TagsContext
BranchCommits types.IListContext
BranchCommits *LocalCommitsContext
CommitFiles *CommitFilesContext
ReflogCommits types.IListContext
SubCommits types.IListContext
Stash types.IListContext
Suggestions types.IListContext
Remotes *RemotesContext
Submodules *SubmodulesContext
RemoteBranches *RemoteBranchesContext
ReflogCommits *ReflogCommitsContext
SubCommits *SubCommitsContext
Stash *StashContext
Suggestions *SuggestionsContext
Normal types.Context
Staging types.Context
PatchBuilding types.Context
@ -113,6 +117,7 @@ func (self *ContextTree) Flatten() []types.Context {
type ViewContextMap struct {
content map[string]types.Context
sync.RWMutex
}
func NewViewContextMap() *ViewContextMap {
@ -120,10 +125,15 @@ func NewViewContextMap() *ViewContextMap {
}
func (self *ViewContextMap) Get(viewName string) types.Context {
self.RLock()
defer self.RUnlock()
return self.content[viewName]
}
func (self *ViewContextMap) Set(viewName string, context types.Context) {
self.Lock()
defer self.Unlock()
self.content[viewName] = context
}

View File

@ -3,44 +3,35 @@ package context
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type ListContextTrait struct {
base types.IBaseContext
list types.IList
viewTrait *ViewTrait
types.Context
takeFocus func() error
GetDisplayStrings func(startIdx int, length int) [][]string
OnFocus func(...types.OnFocusOpts) error
OnRenderToMain func(...types.OnFocusOpts) error
OnFocusLost func() error
// if this is true, we'll call GetDisplayStrings for just the visible part of the
// view and re-render that. This is useful when you need to render different
// content based on the selection (e.g. for showing the selected commit)
RenderSelection bool
c *types.ControllerCommon
c *types.ControllerCommon
list types.IList
viewTrait *ViewTrait
getDisplayStrings func(startIdx int, length int) [][]string
}
func (self *ListContextTrait) GetList() types.IList {
return self.list
}
// TODO: remove
func (self *ListContextTrait) GetPanelState() types.IListPanelState {
return self.list
}
func (self *ListContextTrait) GetViewTrait() types.IViewTrait {
return self.viewTrait
}
func (self *ListContextTrait) FocusLine() {
// we need a way of knowing whether we've rendered to the view yet.
self.viewTrait.FocusPoint(self.list.GetSelectedLineIdx())
if self.RenderSelection {
min, max := self.viewTrait.ViewPortYBounds()
displayStrings := self.GetDisplayStrings(min, max)
content := utils.RenderDisplayStrings(displayStrings)
self.viewTrait.SetViewPortContent(content)
}
self.viewTrait.SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.GetItemsLength()))
}
@ -48,164 +39,29 @@ func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
// 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 (self *ListContextTrait) HandleRender() error {
if self.GetDisplayStrings != nil {
self.list.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.GetDisplayStrings(0, self.list.GetItemsLength()))
self.viewTrait.SetContent(content)
self.c.Render()
}
return nil
}
func (self *ListContextTrait) HandleFocusLost() error {
if self.OnFocusLost != nil {
return self.OnFocusLost()
}
self.viewTrait.SetOriginX(0)
return nil
}
func (self *ListContextTrait) HandleFocus(opts ...types.OnFocusOpts) error {
self.FocusLine()
if self.OnFocus != nil {
if err := self.OnFocus(opts...); err != nil {
return err
}
}
return self.Context.HandleFocus(opts...)
}
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(opts...); err != nil {
return err
}
}
func (self *ListContextTrait) HandleFocusLost() error {
self.viewTrait.SetOriginX(0)
return self.Context.HandleFocus()
}
// 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 (self *ListContextTrait) HandleRender() error {
self.list.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.GetItemsLength()))
self.viewTrait.SetContent(content)
self.c.Render()
return nil
}
func (self *ListContextTrait) HandlePrevLine() error {
return self.handleLineChange(-1)
}
func (self *ListContextTrait) HandleNextLine() error {
return self.handleLineChange(1)
}
func (self *ListContextTrait) HandleScrollLeft() error {
return self.scroll(self.viewTrait.ScrollLeft)
}
func (self *ListContextTrait) HandleScrollRight() error {
return self.scroll(self.viewTrait.ScrollRight)
}
func (self *ListContextTrait) scroll(scrollFunc func()) error {
scrollFunc()
return self.HandleFocus()
}
func (self *ListContextTrait) handleLineChange(change int) error {
before := self.list.GetSelectedLineIdx()
self.list.MoveSelectedLine(change)
after := self.list.GetSelectedLineIdx()
// doing this check so that if we're holding the up key at the start of the list
// we're not constantly re-rendering the main view.
if before != after {
return self.HandleFocus()
}
return nil
}
func (self *ListContextTrait) HandlePrevPage() error {
return self.handleLineChange(-self.viewTrait.PageDelta())
}
func (self *ListContextTrait) HandleNextPage() error {
return self.handleLineChange(self.viewTrait.PageDelta())
}
func (self *ListContextTrait) HandleGotoTop() error {
return self.handleLineChange(-self.list.GetItemsLength())
}
func (self *ListContextTrait) HandleGotoBottom() error {
return self.handleLineChange(self.list.GetItemsLength())
}
func (self *ListContextTrait) HandleClick(onClick func() error) error {
prevSelectedLineIdx := self.list.GetSelectedLineIdx()
// because we're handling a click, we need to determine the new line idx based
// on the view itself.
newSelectedLineIdx := self.viewTrait.SelectedLineIdx()
currentContextKey := self.c.CurrentContext().GetKey()
alreadyFocused := currentContextKey == self.base.GetKey()
// we need to focus the view
if !alreadyFocused {
if err := self.takeFocus(); err != nil {
return err
}
}
if newSelectedLineIdx > self.list.GetItemsLength()-1 {
return nil
}
self.list.SetSelectedLineIdx(newSelectedLineIdx)
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && onClick != nil {
return onClick()
}
return self.HandleFocus()
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.list.SetSelectedLineIdx(selectedLineIdx)
self.GetList().SetSelectedLineIdx(selectedLineIdx)
return self.HandleFocus()
}
func (self *ListContextTrait) HandleRenderToMain() error {
if self.OnRenderToMain != nil {
return self.OnRenderToMain()
}
return nil
}
func (self *ListContextTrait) keybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.c.Tr.LcPrevPage},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.c.Tr.LcNextPage},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Modifier: gocui.ModNone, Handler: self.HandleGotoTop, Description: self.c.Tr.LcGotoTop},
{Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: func() error { return self.HandleClick(nil) }},
{Tag: "navigation", Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: self.HandleNextLine},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight},
{
Key: opts.GetKey(opts.Config.Universal.StartSearch),
Handler: func() error { self.c.OpenSearch(); return nil },
Description: self.c.Tr.LcStartSearch,
Tag: "navigation",
},
{
Key: opts.GetKey(opts.Config.Universal.GotoBottom),
Description: self.c.Tr.LcGotoBottom,
Handler: self.HandleGotoBottom,
Tag: "navigation",
},
}
}

View File

@ -0,0 +1,87 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type LocalCommitsContext struct {
*LocalCommitsViewModel
*ViewportListContextTrait
}
var _ types.IListContext = (*LocalCommitsContext)(nil)
func NewLocalCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *LocalCommitsContext {
viewModel := NewLocalCommitsViewModel(getModel)
return &LocalCommitsContext{
LocalCommitsViewModel: viewModel,
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "commits",
WindowName: "commits",
Key: BRANCH_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
}},
}
}
func (self *LocalCommitsContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type LocalCommitsViewModel struct {
*traits.ListCursor
getModel func() []*models.Commit
}
func NewLocalCommitsViewModel(getModel func() []*models.Commit) *LocalCommitsViewModel {
self := &LocalCommitsViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *LocalCommitsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *LocalCommitsViewModel) GetSelected() *models.Commit {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,108 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type MenuContext struct {
*MenuViewModel
*ListContextTrait
}
var _ types.IListContext = (*MenuContext)(nil)
func NewMenuContext(
view *gocui.View,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
getOptionsMap func() map[string]string,
) *MenuContext {
viewModel := NewMenuViewModel()
return &MenuContext{
MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "menu",
Key: "menu",
Kind: types.PERSISTENT_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel,
viewTrait: NewViewTrait(view),
c: c,
},
}
}
// TODO: remove this thing.
func (self *MenuContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.DisplayString
}
type MenuViewModel struct {
*traits.ListCursor
menuItems []*types.MenuItem
}
func NewMenuViewModel() *MenuViewModel {
self := &MenuViewModel{
menuItems: nil,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *MenuViewModel) GetItemsLength() int {
return len(self.menuItems)
}
func (self *MenuViewModel) GetSelected() *types.MenuItem {
if self.GetItemsLength() == 0 {
return nil
}
return self.menuItems[self.GetSelectedLineIdx()]
}
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) {
self.menuItems = items
}
// TODO: move into presentation package
func (self *MenuViewModel) GetDisplayStrings(startIdx int, length int) [][]string {
stringArrays := make([][]string, len(self.menuItems))
for i, item := range self.menuItems {
if item.DisplayStrings == nil {
styledStr := item.DisplayString
if item.OpensMenu {
styledStr = presentation.OpensMenuStyle(styledStr)
}
stringArrays[i] = []string{styledStr}
} else {
stringArrays[i] = item.DisplayStrings
}
}
return stringArrays
}

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ReflogCommitsContext struct {
*ReflogCommitsViewModel
*ListContextTrait
}
var _ types.IListContext = (*ReflogCommitsContext)(nil)
func NewReflogCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *ReflogCommitsContext {
viewModel := NewReflogCommitsViewModel(getModel)
return &ReflogCommitsContext{
ReflogCommitsViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "commits",
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *ReflogCommitsContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type ReflogCommitsViewModel struct {
*traits.ListCursor
getModel func() []*models.Commit
}
func NewReflogCommitsViewModel(getModel func() []*models.Commit) *ReflogCommitsViewModel {
self := &ReflogCommitsViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *ReflogCommitsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *ReflogCommitsViewModel) GetSelected() *models.Commit {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type RemoteBranchesContext struct {
*RemoteBranchesViewModel
*ListContextTrait
}
var _ types.IListContext = (*RemoteBranchesContext)(nil)
func NewRemoteBranchesContext(
getModel func() []*models.RemoteBranch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *RemoteBranchesContext {
viewModel := NewRemoteBranchesViewModel(getModel)
return &RemoteBranchesContext{
RemoteBranchesViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *RemoteBranchesContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type RemoteBranchesViewModel struct {
*traits.ListCursor
getModel func() []*models.RemoteBranch
}
func NewRemoteBranchesViewModel(getModel func() []*models.RemoteBranch) *RemoteBranchesViewModel {
self := &RemoteBranchesViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *RemoteBranchesViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *RemoteBranchesViewModel) GetSelected() *models.RemoteBranch {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type RemotesContext struct {
*RemotesViewModel
*ListContextTrait
}
var _ types.IListContext = (*RemotesContext)(nil)
func NewRemotesContext(
getModel func() []*models.Remote,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *RemotesContext {
viewModel := NewRemotesViewModel(getModel)
return &RemotesContext{
RemotesViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: REMOTES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *RemotesContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type RemotesViewModel struct {
*traits.ListCursor
getModel func() []*models.Remote
}
func NewRemotesViewModel(getModel func() []*models.Remote) *RemotesViewModel {
self := &RemotesViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *RemotesViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *RemotesViewModel) GetSelected() *models.Remote {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,73 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SimpleContext struct {
OnFocus func(opts ...types.OnFocusOpts) error
OnFocusLost func() error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func(opts ...types.OnFocusOpts) error
*BaseContext
}
type ContextCallbackOpts struct {
OnFocus func(opts ...types.OnFocusOpts) error
OnFocusLost func() error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func(opts ...types.OnFocusOpts) error
}
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
return &SimpleContext{
OnFocus: opts.OnFocus,
OnFocusLost: opts.OnFocusLost,
OnRender: opts.OnRender,
OnRenderToMain: opts.OnRenderToMain,
BaseContext: baseContext,
}
}
var _ types.Context = &SimpleContext{}
func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error {
if self.OnFocus != nil {
if err := self.OnFocus(opts...); err != nil {
return err
}
}
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(opts...); err != nil {
return err
}
}
return nil
}
func (self *SimpleContext) HandleFocusLost() error {
if self.OnFocusLost != nil {
return self.OnFocusLost()
}
return nil
}
func (self *SimpleContext) HandleRender() error {
if self.OnRender != nil {
return self.OnRender()
}
return nil
}
func (self *SimpleContext) HandleRenderToMain() error {
if self.OnRenderToMain != nil {
return self.OnRenderToMain()
}
return nil
}

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type StashContext struct {
*StashViewModel
*ListContextTrait
}
var _ types.IListContext = (*StashContext)(nil)
func NewStashContext(
getModel func() []*models.StashEntry,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *StashContext {
viewModel := NewStashViewModel(getModel)
return &StashContext{
StashViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "stash",
WindowName: "stash",
Key: STASH_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *StashContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type StashViewModel struct {
*traits.ListCursor
getModel func() []*models.StashEntry
}
func NewStashViewModel(getModel func() []*models.StashEntry) *StashViewModel {
self := &StashViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *StashViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *StashViewModel) GetSelected() *models.StashEntry {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,87 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SubCommitsContext struct {
*SubCommitsViewModel
*ViewportListContextTrait
}
var _ types.IListContext = (*SubCommitsContext)(nil)
func NewSubCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *SubCommitsContext {
viewModel := NewSubCommitsViewModel(getModel)
return &SubCommitsContext{
SubCommitsViewModel: viewModel,
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
}},
}
}
func (self *SubCommitsContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type SubCommitsViewModel struct {
*traits.ListCursor
getModel func() []*models.Commit
}
func NewSubCommitsViewModel(getModel func() []*models.Commit) *SubCommitsViewModel {
self := &SubCommitsViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *SubCommitsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *SubCommitsViewModel) GetSelected() *models.Commit {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,86 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SubmodulesContext struct {
*SubmodulesViewModel
*ListContextTrait
}
var _ types.IListContext = (*SubmodulesContext)(nil)
func NewSubmodulesContext(
getModel func() []*models.SubmoduleConfig,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *SubmodulesContext {
viewModel := NewSubmodulesViewModel(getModel)
return &SubmodulesContext{
SubmodulesViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "files",
WindowName: "files",
Key: SUBMODULES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *SubmodulesContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.ID()
}
type SubmodulesViewModel struct {
*traits.ListCursor
getModel func() []*models.SubmoduleConfig
}
func NewSubmodulesViewModel(getModel func() []*models.SubmoduleConfig) *SubmodulesViewModel {
self := &SubmodulesViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *SubmodulesViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *SubmodulesViewModel) GetSelected() *models.SubmoduleConfig {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -0,0 +1,85 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SuggestionsContext struct {
*SuggestionsViewModel
*ListContextTrait
}
var _ types.IListContext = (*SuggestionsContext)(nil)
func NewSuggestionsContext(
getModel func() []*types.Suggestion,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.ControllerCommon,
) *SuggestionsContext {
viewModel := NewSuggestionsViewModel(getModel)
return &SuggestionsContext{
SuggestionsViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "suggestions",
WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY,
Kind: types.PERSISTENT_POPUP,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
}
func (self *SuggestionsContext) GetSelectedItemId() string {
item := self.GetSelected()
if item == nil {
return ""
}
return item.Value
}
type SuggestionsViewModel struct {
*traits.ListCursor
getModel func() []*types.Suggestion
}
func NewSuggestionsViewModel(getModel func() []*types.Suggestion) *SuggestionsViewModel {
self := &SuggestionsViewModel{
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
return self
}
func (self *SuggestionsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *SuggestionsViewModel) GetSelected() *types.Suggestion {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -9,7 +9,6 @@ import (
type TagsContext struct {
*TagsViewModel
*BaseContext
*ListContextTrait
}
@ -17,7 +16,7 @@ var _ types.IListContext = (*TagsContext)(nil)
func NewTagsContext(
getModel func() []*models.Tag,
getView func() *gocui.View,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
@ -26,47 +25,32 @@ func NewTagsContext(
c *types.ControllerCommon,
) *TagsContext {
baseContext := NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})
viewModel := NewTagsViewModel(getModel)
self := &TagsContext{}
takeFocus := func() error { return c.PushContext(self) }
list := NewTagsViewModel(getModel)
viewTrait := NewViewTrait(getView)
listContextTrait := &ListContextTrait{
base: baseContext,
list: list,
viewTrait: viewTrait,
GetDisplayStrings: getDisplayStrings,
OnFocus: onFocus,
OnRenderToMain: onRenderToMain,
OnFocusLost: onFocusLost,
takeFocus: takeFocus,
// TODO: handle this in a trait
RenderSelection: false,
c: c,
return &TagsContext{
TagsViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
baseContext.AddKeybindingsFn(listContextTrait.keybindings)
self.BaseContext = baseContext
self.ListContextTrait = listContextTrait
self.TagsViewModel = list
return self
}
func (self *TagsContext) GetSelectedItemId() string {
item := self.GetSelectedTag()
item := self.GetSelected()
if item == nil {
return ""
}
@ -79,18 +63,6 @@ type TagsViewModel struct {
getModel func() []*models.Tag
}
func (self *TagsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *TagsViewModel) GetSelectedTag() *models.Tag {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}
func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel {
self := &TagsViewModel{
getModel: getModel,
@ -100,3 +72,15 @@ func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel {
return self
}
func (self *TagsViewModel) GetItemsLength() int {
return len(self.getModel())
}
func (self *TagsViewModel) GetSelected() *models.Tag {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.GetSelectedLineIdx()]
}

View File

@ -2,70 +2,62 @@ package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
const HORIZONTAL_SCROLL_FACTOR = 3
type ViewTrait struct {
getView func() *gocui.View
view *gocui.View
}
func NewViewTrait(getView func() *gocui.View) *ViewTrait {
return &ViewTrait{getView: getView}
var _ types.IViewTrait = &ViewTrait{}
func NewViewTrait(view *gocui.View) *ViewTrait {
return &ViewTrait{view: view}
}
func (self *ViewTrait) FocusPoint(yIdx int) {
view := self.getView()
view.FocusPoint(view.OriginX(), yIdx)
self.view.FocusPoint(self.view.OriginX(), yIdx)
}
func (self *ViewTrait) SetViewPortContent(content string) {
view := self.getView()
_, y := view.Origin()
view.OverwriteLines(y, content)
_, y := self.view.Origin()
self.view.OverwriteLines(y, content)
}
func (self *ViewTrait) SetContent(content string) {
self.getView().SetContent(content)
self.view.SetContent(content)
}
func (self *ViewTrait) SetFooter(value string) {
self.getView().Footer = value
self.view.Footer = value
}
func (self *ViewTrait) SetOriginX(value int) {
_ = self.getView().SetOriginX(value)
_ = self.view.SetOriginX(value)
}
// tells us the bounds of line indexes shown in the view currently
func (self *ViewTrait) ViewPortYBounds() (int, int) {
view := self.getView()
_, min := view.Origin()
max := view.InnerHeight() + 1
_, min := self.view.Origin()
max := self.view.InnerHeight() + 1
return min, max
}
func (self *ViewTrait) ScrollLeft() {
view := self.getView()
newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
_ = view.SetOriginX(newOriginX)
newOriginX := utils.Max(self.view.OriginX()-self.view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
_ = self.view.SetOriginX(newOriginX)
}
func (self *ViewTrait) ScrollRight() {
view := self.getView()
_ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
_ = self.view.SetOriginX(self.view.OriginX() + self.view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
}
// this returns the amount we'll scroll if we want to scroll by a page.
func (self *ViewTrait) PageDelta() int {
view := self.getView()
_, height := view.Size()
_, height := self.view.Size()
delta := height - 1
if delta == 0 {
@ -76,5 +68,5 @@ func (self *ViewTrait) PageDelta() int {
}
func (self *ViewTrait) SelectedLineIdx() int {
return self.getView().SelectedLineIdx()
return self.view.SelectedLineIdx()
}

View File

@ -0,0 +1,22 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
// This embeds a list context trait and adds logic to re-render the viewport
// whenever a line is focused. We use this in the commits panel because different
// sections of the log graph need to be highlighted depending on the currently selected line
type ViewportListContextTrait struct {
*ListContextTrait
}
func (self *ViewportListContextTrait) FocusLine() {
self.ListContextTrait.FocusLine()
min, max := self.GetViewTrait().ViewPortYBounds()
displayStrings := self.ListContextTrait.getDisplayStrings(min, max)
content := utils.RenderDisplayStrings(displayStrings)
self.GetViewTrait().SetViewPortContent(content)
}

View File

@ -9,7 +9,6 @@ import (
type WorkingTreeContext struct {
*filetree.FileTreeViewModel
*BaseContext
*ListContextTrait
}
@ -17,7 +16,7 @@ var _ types.IListContext = (*WorkingTreeContext)(nil)
func NewWorkingTreeContext(
getModel func() []*models.File,
getView func() *gocui.View,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
@ -26,43 +25,28 @@ func NewWorkingTreeContext(
c *types.ControllerCommon,
) *WorkingTreeContext {
baseContext := NewBaseContext(NewBaseContextOpts{
ViewName: "files",
WindowName: "files",
Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})
self := &WorkingTreeContext{}
takeFocus := func() error { return c.PushContext(self) }
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
viewTrait := NewViewTrait(getView)
listContextTrait := &ListContextTrait{
base: baseContext,
list: viewModel,
viewTrait: viewTrait,
GetDisplayStrings: getDisplayStrings,
OnFocus: onFocus,
OnRenderToMain: onRenderToMain,
OnFocusLost: onFocusLost,
takeFocus: takeFocus,
// TODO: handle this in a trait
RenderSelection: false,
c: c,
return &WorkingTreeContext{
FileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "files",
WindowName: "files",
Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},
}
baseContext.AddKeybindingsFn(listContextTrait.keybindings)
self.BaseContext = baseContext
self.ListContextTrait = listContextTrait
self.FileTreeViewModel = viewModel
return self
}
func (self *WorkingTreeContext) GetSelectedItemId() string {