package controllers import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" ) type ListControllerFactory struct { c *types.HelperCommon } func NewListControllerFactory(c *types.HelperCommon) *ListControllerFactory { return &ListControllerFactory{ c: c, } } func (self *ListControllerFactory) Create(context types.IListContext) *ListController { return &ListController{ baseController: baseController{}, c: self.c, context: context, } } type ListController struct { baseController c *types.HelperCommon context types.IListContext } func (self *ListController) Context() types.Context { return self.context } func (self *ListController) HandlePrevLine() error { return self.handleLineChange(-1) } func (self *ListController) HandleNextLine() error { return self.handleLineChange(1) } func (self *ListController) HandleScrollLeft() error { return self.scrollHorizontal(self.context.GetViewTrait().ScrollLeft) } func (self *ListController) HandleScrollRight() error { return self.scrollHorizontal(self.context.GetViewTrait().ScrollRight) } func (self *ListController) HandleScrollUp() error { scrollHeight := self.c.UserConfig.Gui.ScrollHeight self.context.GetViewTrait().ScrollUp(scrollHeight) // we only need to do a line change if our line has been pushed out of the viewport, because // at the moment much logic depends on the selected line always being visible if !self.isSelectedLineInViewPort() { return self.handleLineChange(-scrollHeight) } return nil } func (self *ListController) HandleScrollDown() error { scrollHeight := self.c.UserConfig.Gui.ScrollHeight self.context.GetViewTrait().ScrollDown(scrollHeight) if !self.isSelectedLineInViewPort() { return self.handleLineChange(scrollHeight) } return nil } func (self *ListController) isSelectedLineInViewPort() bool { selectedLineIdx := self.context.GetList().GetSelectedLineIdx() startIdx, length := self.context.GetViewTrait().ViewPortYBounds() return selectedLineIdx >= startIdx && selectedLineIdx < startIdx+length } func (self *ListController) scrollHorizontal(scrollFunc func()) error { scrollFunc() return self.context.HandleFocus(types.OnFocusOpts{}) } func (self *ListController) handleLineChange(change int) error { before := self.context.GetList().GetSelectedLineIdx() self.context.GetList().MoveSelectedLine(change) after := self.context.GetList().GetSelectedLineIdx() if err := self.pushContextIfNotFocused(); err != nil { return err } // 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.context.HandleFocus(types.OnFocusOpts{}) } return nil } func (self *ListController) HandlePrevPage() error { return self.handleLineChange(-self.context.GetViewTrait().PageDelta()) } func (self *ListController) HandleNextPage() error { return self.handleLineChange(self.context.GetViewTrait().PageDelta()) } func (self *ListController) HandleGotoTop() error { return self.handleLineChange(-self.context.GetList().Len()) } func (self *ListController) HandleGotoBottom() error { return self.handleLineChange(self.context.GetList().Len()) } func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error { prevSelectedLineIdx := self.context.GetList().GetSelectedLineIdx() newSelectedLineIdx := opts.Y alreadyFocused := self.isFocused() if err := self.pushContextIfNotFocused(); err != nil { return err } if newSelectedLineIdx > self.context.GetList().Len()-1 { return nil } self.context.GetList().SetSelectedLineIdx(newSelectedLineIdx) if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil { return self.context.GetOnClick()() } return self.context.HandleFocus(types.OnFocusOpts{}) } func (self *ListController) pushContextIfNotFocused() error { if !self.isFocused() { if err := self.c.PushContext(self.context); err != nil { return err } } return nil } func (self *ListController) isFocused() bool { return self.c.CurrentContext().GetKey() == self.context.GetKey() } func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { return []*types.Binding{ {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Handler: self.HandlePrevLine}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Handler: self.HandlePrevLine}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Handler: self.HandleNextLine}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Handler: self.HandleNextLine}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Handler: self.HandlePrevPage, Description: self.c.Tr.LcPrevPage}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Handler: self.HandleNextPage, Description: self.c.Tr.LcNextPage}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Handler: self.HandleGotoTop, Description: self.c.Tr.LcGotoTop}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft}, {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), 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", }, } } func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { return []*gocui.ViewMouseBinding{ { ViewName: self.context.GetViewName(), Key: gocui.MouseWheelUp, Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() }, }, { ViewName: self.context.GetViewName(), Key: gocui.MouseLeft, Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) }, }, { ViewName: self.context.GetViewName(), Key: gocui.MouseWheelDown, Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() }, }, } }