diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go index 58b8d25ab..86d15c79a 100644 --- a/pkg/gui/context/base_context.go +++ b/pkg/gui/context/base_context.go @@ -20,6 +20,7 @@ type BaseContext struct { focusable bool transient bool hasControlledBounds bool + highlightOnFocus bool *ParentContextMgr } @@ -34,6 +35,7 @@ type NewBaseContextOpts struct { Focusable bool Transient bool HasUncontrolledBounds bool // negating for the sake of making false the default + HighlightOnFocus bool OnGetOptionsMap func() map[string]string } @@ -52,6 +54,7 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext { focusable: opts.Focusable, transient: opts.Transient, hasControlledBounds: hasControlledBounds, + highlightOnFocus: opts.HighlightOnFocus, ParentContextMgr: &ParentContextMgr{}, viewTrait: viewTrait, } diff --git a/pkg/gui/context/merge_conflicts_context.go b/pkg/gui/context/merge_conflicts_context.go index 4d02d452e..1e765ffe7 100644 --- a/pkg/gui/context/merge_conflicts_context.go +++ b/pkg/gui/context/merge_conflicts_context.go @@ -42,12 +42,13 @@ func NewMergeConflictsContext( mutex: &deadlock.Mutex{}, Context: NewSimpleContext( NewBaseContext(NewBaseContextOpts{ - Kind: types.MAIN_CONTEXT, - View: view, - WindowName: "main", - Key: MERGE_CONFLICTS_CONTEXT_KEY, - OnGetOptionsMap: getOptionsMap, - Focusable: true, + Kind: types.MAIN_CONTEXT, + View: view, + WindowName: "main", + Key: MERGE_CONFLICTS_CONTEXT_KEY, + OnGetOptionsMap: getOptionsMap, + Focusable: true, + HighlightOnFocus: true, }), opts, ), @@ -77,7 +78,7 @@ func (self *MergeConflictsContext) IsUserScrolling() bool { func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error { self.setContent(isFocused) - self.focusSelection() + self.FocusSelection() self.c.Render() @@ -104,9 +105,9 @@ func (self *MergeConflictsContext) setContent(isFocused bool) { self.GetView().SetContent(self.GetContentToRender(isFocused)) } -func (self *MergeConflictsContext) focusSelection() { +func (self *MergeConflictsContext) FocusSelection() { if !self.IsUserScrolling() { - _ = self.GetView().SetOrigin(self.GetView().OriginX(), self.GetOriginY()) + _ = self.GetView().SetOriginY(self.GetOriginY()) } } diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go index b6bea2af3..5a3375f33 100644 --- a/pkg/gui/context/patch_explorer_context.go +++ b/pkg/gui/context/patch_explorer_context.go @@ -37,11 +37,12 @@ func NewPatchExplorerContext( mutex: &deadlock.Mutex{}, getIncludedLineIndices: getIncludedLineIndices, SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: view, - WindowName: windowName, - Key: key, - Kind: types.MAIN_CONTEXT, - Focusable: true, + View: view, + WindowName: windowName, + Key: key, + Kind: types.MAIN_CONTEXT, + Focusable: true, + HighlightOnFocus: true, }), ContextCallbackOpts{ OnFocus: onFocus, OnFocusLost: onFocusLost, @@ -68,7 +69,7 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int { func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error { self.setContent(isFocused) - self.focusSelection() + self.FocusSelection() self.c.Render() return nil @@ -83,7 +84,7 @@ func (self *PatchExplorerContext) Render(isFocused bool) error { } func (self *PatchExplorerContext) Focus() error { - self.focusSelection() + self.FocusSelection() self.c.Render() return nil @@ -93,16 +94,18 @@ func (self *PatchExplorerContext) setContent(isFocused bool) { self.GetView().SetContent(self.GetContentToRender(isFocused)) } -func (self *PatchExplorerContext) focusSelection() { +func (self *PatchExplorerContext) FocusSelection() { view := self.GetView() state := self.GetState() _, viewHeight := view.Size() bufferHeight := viewHeight - 1 _, origin := view.Origin() - newOrigin := state.CalculateOrigin(origin, bufferHeight) + newOriginY := state.CalculateOrigin(origin, bufferHeight) - _ = view.SetOriginY(newOrigin) + _ = view.SetOriginY(newOriginY) + + view.SetCursorY(state.GetSelectedLineIdx() - newOriginY) } func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string { diff --git a/pkg/gui/context/simple_context.go b/pkg/gui/context/simple_context.go index 835576264..38fe4ee78 100644 --- a/pkg/gui/context/simple_context.go +++ b/pkg/gui/context/simple_context.go @@ -50,6 +50,10 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string } func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error { + if self.highlightOnFocus { + self.GetViewTrait().SetHighlight(true) + } + if self.OnFocus != nil { if err := self.OnFocus(opts); err != nil { return err diff --git a/pkg/gui/refresh.go b/pkg/gui/refresh.go index d164e28aa..77dea9e86 100644 --- a/pkg/gui/refresh.go +++ b/pkg/gui/refresh.go @@ -618,6 +618,12 @@ func (gui *Gui) refreshStagingPanel(focusOpts types.OnFocusOpts) error { return gui.c.PushContext(mainContext, focusOpts) } + if secondaryFocused { + gui.State.Contexts.StagingSecondary.FocusSelection() + } else { + gui.State.Contexts.Staging.FocusSelection() + } + return gui.c.RenderToMainViews(types.RefreshMainOpts{ Pair: gui.c.MainViewPairs().Staging, Main: &types.ViewUpdateOpts{ @@ -679,6 +685,8 @@ func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error { return gui.helpers.PatchBuilding.Escape() } + gui.State.Contexts.CustomPatchBuilder.FocusSelection() + mainContent := context.GetContentToRender(true) return gui.c.RenderToMainViews(types.RefreshMainOpts{ diff --git a/pkg/integration/components/viewDriver.go b/pkg/integration/components/viewDriver.go index 835d149db..04c58fc81 100644 --- a/pkg/integration/components/viewDriver.go +++ b/pkg/integration/components/viewDriver.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" ) @@ -89,9 +88,6 @@ func (self *ViewDriver) Content(matcher *matcher) *ViewDriver { func (self *ViewDriver) SelectedLine(matcher *matcher) *ViewDriver { self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context), func() string { - if idx, ok := self.selectedLineIdxInPatchExplorer(); ok { - return self.getView().BufferLines()[idx] - } return self.getView().SelectedLine() }, ) @@ -102,25 +98,13 @@ func (self *ViewDriver) SelectedLine(matcher *matcher) *ViewDriver { // asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view. func (self *ViewDriver) SelectedLineIdx(expected int) *ViewDriver { self.t.assertWithRetries(func() (bool, string) { - actual, ok := self.selectedLineIdxInPatchExplorer() - if !ok { - actual = self.getView().SelectedLineIdx() - } + actual := self.getView().SelectedLineIdx() return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual) }) return self } -func (self *ViewDriver) selectedLineIdxInPatchExplorer() (int, bool) { - context := self.t.gui.ContextForView(self.getView().Name()) - patchExplorerContext, ok := context.(types.IPatchExplorerContext) - if ok && patchExplorerContext.GetState() != nil { - return patchExplorerContext.GetState().GetSelectedLineIdx(), true - } - return 0, false -} - // focus the view (assumes the view is a side-view) func (self *ViewDriver) Focus() *ViewDriver { viewName := self.getView().Name() diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index b8b855f1d..8a47aa599 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -1226,8 +1226,10 @@ func (g *Gui) onKey(ev *GocuiEvent) error { newCx = lastCharForLine } } - if err := v.SetCursor(newCx, newCy); err != nil { - return err + if !IsMouseScrollKey(ev.Key) { + if err := v.SetCursor(newCx, newCy); err != nil { + return err + } } if IsMouseKey(ev.Key) { @@ -1289,6 +1291,19 @@ func IsMouseKey(key interface{}) bool { } } +func IsMouseScrollKey(key interface{}) bool { + switch key { + case + MouseWheelUp, + MouseWheelDown, + MouseWheelLeft, + MouseWheelRight: + return true + default: + return false + } +} + // execKeybindings executes the keybinding handlers that match the passed view // and event. The value of matched is true if there is a match and no errors. func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error) { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index aa9dbd214..d8984253d 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -418,9 +418,10 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if err != nil { return err } - _, rcy, err = v.realPosition(v.cx, v.cy) - if err != nil { - return err + _, rrcy, err := v.realPosition(v.cx, v.cy) + // if error is not nil, then the cursor is out of bounds, which is fine + if err == nil { + rcy = rrcy } } @@ -460,6 +461,22 @@ func (v *View) SetCursor(x, y int) error { return nil } +func (v *View) SetCursorX(x int) { + maxX, _ := v.Size() + if x < 0 || x >= maxX { + return + } + v.cx = x +} + +func (v *View) SetCursorY(y int) { + _, maxY := v.Size() + if y < 0 || y >= maxY { + return + } + v.cy = y +} + // Cursor returns the cursor position of the view. func (v *View) Cursor() (x, y int) { return v.cx, v.cy @@ -1349,11 +1366,12 @@ func (v *View) OverwriteLines(y int, content string) { } func (v *View) ScrollUp(amount int) { - newOy := v.oy - amount - if newOy < 0 { - newOy = 0 + if amount > v.oy { + amount = v.oy } - v.oy = newOy + + v.oy -= amount + v.cy += amount } // ensures we don't scroll past the end of the view's content @@ -1361,6 +1379,7 @@ func (v *View) ScrollDown(amount int) { adjustedAmount := v.adjustDownwardScrollAmount(amount) if adjustedAmount > 0 { v.oy += adjustedAmount + v.cy -= adjustedAmount } }