1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-28 16:02:01 +03:00

refactor contexts code

This commit is contained in:
Jesse Duffield
2022-01-29 19:09:20 +11:00
parent 1a74ed3214
commit 138be04e65
60 changed files with 1154 additions and 602 deletions

View File

@ -0,0 +1,67 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
type BaseContext struct {
Kind types.ContextKind
Key types.ContextKey
ViewName string
WindowName string
OnGetOptionsMap func() map[string]string
*ParentContextMgr
}
func (self *BaseContext) GetOptionsMap() map[string]string {
if self.OnGetOptionsMap != nil {
return self.OnGetOptionsMap()
}
return nil
}
func (self *BaseContext) SetWindowName(windowName string) {
self.WindowName = windowName
}
func (self *BaseContext) GetWindowName() string {
windowName := self.WindowName
if windowName != "" {
return windowName
}
// TODO: actually set this for everything so we don't default to the view name
return self.ViewName
}
func (self *BaseContext) GetViewName() string {
return self.ViewName
}
func (self *BaseContext) GetKind() types.ContextKind {
return self.Kind
}
func (self *BaseContext) GetKey() types.ContextKey {
return self.Key
}
type NewBaseContextOpts struct {
Kind types.ContextKind
Key types.ContextKey
ViewName string
WindowName string
OnGetOptionsMap func() map[string]string
}
func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
return &BaseContext{
Kind: opts.Kind,
Key: opts.Key,
ViewName: opts.ViewName,
WindowName: opts.WindowName,
OnGetOptionsMap: opts.OnGetOptionsMap,
ParentContextMgr: &ParentContextMgr{},
}
}

View File

@ -10,7 +10,7 @@ type ContextTree struct {
Branches types.IListContext
Remotes types.IListContext
RemoteBranches types.IListContext
Tags types.IListContext
Tags *TagsContext
BranchCommits types.IListContext
CommitFiles types.IListContext
ReflogCommits types.IListContext

View File

@ -0,0 +1,231 @@
package context
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type Thing interface {
// the boolean here tells us whether the item is nil. This is needed because you can't work it out on the calling end once the pointer is wrapped in an interface (unless you want to use reflection)
GetSelectedItem() (types.ListItem, bool)
}
type ListContextTrait struct {
base types.IBaseContext
thing Thing
listTrait *ListTrait
viewTrait *ViewTrait
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
}
func (self *ListContextTrait) GetPanelState() types.IListPanelState {
return self.listTrait
}
func (self *ListContextTrait) FocusLine() {
// we need a way of knowing whether we've rendered to the view yet.
self.viewTrait.FocusPoint(self.listTrait.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.listTrait.GetSelectedLineIdx(), self.listTrait.GetItemsLength()))
}
func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
func (self *ListContextTrait) GetSelectedItemId() string {
item, ok := self.thing.GetSelectedItem()
if !ok {
return ""
}
return item.ID()
}
// 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.listTrait.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.GetDisplayStrings(0, self.listTrait.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
}
}
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(opts...); err != nil {
return err
}
}
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.listTrait.GetSelectedLineIdx()
self.listTrait.MoveSelectedLine(change)
after := self.listTrait.GetSelectedLineIdx()
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.listTrait.GetItemsLength())
}
func (self *ListContextTrait) HandleGotoBottom() error {
return self.handleLineChange(self.listTrait.GetItemsLength())
}
func (self *ListContextTrait) HandleClick(onClick func() error) error {
prevSelectedLineIdx := self.listTrait.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.listTrait.GetItemsLength()-1 {
return nil
}
self.listTrait.SetSelectedLineIdx(newSelectedLineIdx)
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && onClick != nil {
return onClick()
}
return self.HandleFocus()
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.listTrait.SetSelectedLineIdx(selectedLineIdx)
return self.HandleFocus()
}
func (self *ListContextTrait) HandleRenderToMain() error {
if self.OnRenderToMain != nil {
return self.OnRenderToMain()
}
return nil
}
func (self *ListContextTrait) Keybindings(
getKey func(key string) interface{},
config config.KeybindingConfig,
guards types.KeybindingGuards,
) []*types.Binding {
return []*types.Binding{
{Tag: "navigation", Key: getKey(config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: getKey(config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
{Tag: "navigation", Key: getKey(config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
{Tag: "navigation", Key: getKey(config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
{Tag: "navigation", Key: getKey(config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.c.Tr.LcPrevPage},
{Tag: "navigation", Key: getKey(config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.c.Tr.LcNextPage},
{Tag: "navigation", Key: getKey(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: getKey(config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft},
{Tag: "navigation", Key: getKey(config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight},
{
Key: getKey(config.Universal.StartSearch),
Handler: func() error { self.c.OpenSearch(); return nil },
Description: self.c.Tr.LcStartSearch,
Tag: "navigation",
},
{
Key: getKey(config.Universal.GotoBottom),
Description: self.c.Tr.LcGotoBottom,
Handler: self.HandleGotoBottom,
Tag: "navigation",
},
}
}

View File

@ -0,0 +1,32 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
type HasLength interface {
GetItemsLength() int
}
type ListTrait struct {
selectedIdx int
HasLength
}
var _ types.IListPanelState = (*ListTrait)(nil)
func (self *ListTrait) GetSelectedLineIdx() int {
return self.selectedIdx
}
func (self *ListTrait) SetSelectedLineIdx(value int) {
self.selectedIdx = clamp(value, 0, self.GetItemsLength()-1)
}
// moves the cursor up or down by the given amount
func (self *ListTrait) MoveSelectedLine(value int) {
self.SetSelectedLineIdx(self.selectedIdx + value)
}
// to be called when the model might have shrunk so that our selection is not not out of bounds
func (self *ListTrait) RefreshSelectedIdx() {
self.SetSelectedLineIdx(self.selectedIdx)
}

View File

@ -0,0 +1,20 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
type ParentContextMgr struct {
ParentContext types.Context
// we can't know on the calling end whether a Context is actually a nil value without reflection, so we're storing this flag here to tell us. There has got to be a better way around this
hasParent bool
}
var _ types.ParentContexter = (*ParentContextMgr)(nil)
func (self *ParentContextMgr) SetParentContext(context types.Context) {
self.ParentContext = context
self.hasParent = true
}
func (self *ParentContextMgr) GetParentContext() (types.Context, bool) {
return self.ParentContext, self.hasParent
}

View File

@ -0,0 +1,107 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type TagsContext struct {
*TagsContextAux
*BaseContext
*ListContextTrait
}
var _ types.IListContext = (*TagsContext)(nil)
func NewTagsContext(
getModel func() []*models.Tag,
getView func() *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,
) *TagsContext {
baseContext := NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
})
self := &TagsContext{}
takeFocus := func() error { return c.PushContext(self) }
aux := NewTagsContextAux(getModel)
viewTrait := NewViewTrait(getView)
listContextTrait := &ListContextTrait{
base: baseContext,
thing: aux,
listTrait: aux.list,
viewTrait: viewTrait,
GetDisplayStrings: getDisplayStrings,
OnFocus: onFocus,
OnRenderToMain: onRenderToMain,
OnFocusLost: onFocusLost,
takeFocus: takeFocus,
// TODO: handle this in a trait
RenderSelection: false,
c: c,
}
self.BaseContext = baseContext
self.ListContextTrait = listContextTrait
self.TagsContextAux = aux
return self
}
type TagsContextAux struct {
list *ListTrait
getModel func() []*models.Tag
}
func (self *TagsContextAux) GetItemsLength() int {
return len(self.getModel())
}
func (self *TagsContextAux) GetSelectedTag() *models.Tag {
if self.GetItemsLength() == 0 {
return nil
}
return self.getModel()[self.list.GetSelectedLineIdx()]
}
func (self *TagsContextAux) GetSelectedItem() (types.ListItem, bool) {
tag := self.GetSelectedTag()
return tag, tag != nil
}
func NewTagsContextAux(getModel func() []*models.Tag) *TagsContextAux {
self := &TagsContextAux{
getModel: getModel,
}
self.list = &ListTrait{
selectedIdx: 0,
HasLength: self,
}
return self
}
func clamp(x int, min int, max int) int {
if x < min {
return min
} else if x > max {
return max
}
return x
}

View File

@ -0,0 +1,80 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
const HORIZONTAL_SCROLL_FACTOR = 3
type ViewTrait struct {
getView func() *gocui.View
}
func NewViewTrait(getView func() *gocui.View) *ViewTrait {
return &ViewTrait{getView: getView}
}
func (self *ViewTrait) FocusPoint(yIdx int) {
view := self.getView()
view.FocusPoint(view.OriginX(), yIdx)
}
func (self *ViewTrait) SetViewPortContent(content string) {
view := self.getView()
_, y := view.Origin()
view.OverwriteLines(y, content)
}
func (self *ViewTrait) SetContent(content string) {
self.getView().SetContent(content)
}
func (self *ViewTrait) SetFooter(value string) {
self.getView().Footer = value
}
func (self *ViewTrait) SetOriginX(value int) {
self.getView().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
return min, max
}
func (self *ViewTrait) ScrollLeft() {
view := self.getView()
newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
_ = view.SetOriginX(newOriginX)
}
func (self *ViewTrait) ScrollRight() {
view := self.getView()
_ = view.SetOriginX(view.OriginX() + 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()
delta := height - 1
if delta == 0 {
return 1
}
return delta
}
func (self *ViewTrait) SelectedLineIdx() int {
return self.getView().SelectedLineIdx()
}