mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-13 01:30:53 +02:00
Jump to middle of the view when selection leaves the visible area (#2915)
This commit is contained in:
@ -35,6 +35,7 @@ gui:
|
|||||||
windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
|
windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
|
||||||
scrollHeight: 2 # how many lines you scroll by
|
scrollHeight: 2 # how many lines you scroll by
|
||||||
scrollPastBottom: true # enable scrolling past the bottom
|
scrollPastBottom: true # enable scrolling past the bottom
|
||||||
|
scrollOffMargin: 2 # how many lines to keep before/after the cursor when it reaches the top/bottom of the view
|
||||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||||
expandFocusedSidePanel: false
|
expandFocusedSidePanel: false
|
||||||
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
|
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
|
||||||
|
2
go.mod
2
go.mod
@ -15,7 +15,7 @@ require (
|
|||||||
github.com/integrii/flaggy v1.4.0
|
github.com/integrii/flaggy v1.4.0
|
||||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727
|
github.com/jesseduffield/gocui v0.3.1-0.20230815093813-9f3df4a6da3b
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||||
|
4
go.sum
4
go.sum
@ -179,8 +179,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
|||||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727 h1:cLq698s96uDMm0n5379doAjIKoip3/8ioWIM8pySRLY=
|
github.com/jesseduffield/gocui v0.3.1-0.20230815093813-9f3df4a6da3b h1:D2Qgpvo+i7bIIBbi/UtzrpyTuUj020lJeGxMa0J1jGs=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
|
github.com/jesseduffield/gocui v0.3.1-0.20230815093813-9f3df4a6da3b/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||||
|
@ -149,3 +149,8 @@ func (self *Patch) LineCount() int {
|
|||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of hunks of the patch
|
||||||
|
func (self *Patch) HunkCount() int {
|
||||||
|
return len(self.hunks)
|
||||||
|
}
|
||||||
|
@ -31,6 +31,7 @@ type GuiConfig struct {
|
|||||||
BranchColors map[string]string `yaml:"branchColors"`
|
BranchColors map[string]string `yaml:"branchColors"`
|
||||||
ScrollHeight int `yaml:"scrollHeight"`
|
ScrollHeight int `yaml:"scrollHeight"`
|
||||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||||
|
ScrollOffMargin int `yaml:"scrollOffMargin"`
|
||||||
MouseEvents bool `yaml:"mouseEvents"`
|
MouseEvents bool `yaml:"mouseEvents"`
|
||||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||||
@ -418,6 +419,7 @@ func GetDefaultConfig() *UserConfig {
|
|||||||
Gui: GuiConfig{
|
Gui: GuiConfig{
|
||||||
ScrollHeight: 2,
|
ScrollHeight: 2,
|
||||||
ScrollPastBottom: true,
|
ScrollPastBottom: true,
|
||||||
|
ScrollOffMargin: 2,
|
||||||
MouseEvents: true,
|
MouseEvents: true,
|
||||||
SkipDiscardChangeWarning: false,
|
SkipDiscardChangeWarning: false,
|
||||||
SkipStashWarning: false,
|
SkipStashWarning: false,
|
||||||
|
@ -109,8 +109,9 @@ func (self *PatchExplorerContext) FocusSelection() {
|
|||||||
_, viewHeight := view.Size()
|
_, viewHeight := view.Size()
|
||||||
bufferHeight := viewHeight - 1
|
bufferHeight := viewHeight - 1
|
||||||
_, origin := view.Origin()
|
_, origin := view.Origin()
|
||||||
|
numLines := view.LinesHeight()
|
||||||
|
|
||||||
newOriginY := state.CalculateOrigin(origin, bufferHeight)
|
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||||
|
|
||||||
_ = view.SetOriginY(newOriginY)
|
_ = view.SetOriginY(newOriginY)
|
||||||
|
|
||||||
|
@ -82,6 +82,12 @@ func (self *ListController) handleLineChange(change int) error {
|
|||||||
// doing this check so that if we're holding the up key at the start of the list
|
// 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.
|
// we're not constantly re-rendering the main view.
|
||||||
if before != after {
|
if before != after {
|
||||||
|
if change == -1 {
|
||||||
|
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
|
||||||
|
} else if change == 1 {
|
||||||
|
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
|
||||||
|
}
|
||||||
|
|
||||||
return self.context.HandleFocus(types.OnFocusOpts{})
|
return self.context.HandleFocus(types.OnFocusOpts{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,13 +159,25 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *PatchExplorerController) HandlePrevLine() error {
|
func (self *PatchExplorerController) HandlePrevLine() error {
|
||||||
|
before := self.context.GetState().GetSelectedLineIdx()
|
||||||
self.context.GetState().CycleSelection(false)
|
self.context.GetState().CycleSelection(false)
|
||||||
|
after := self.context.GetState().GetSelectedLineIdx()
|
||||||
|
|
||||||
|
if self.context.GetState().SelectingLine() {
|
||||||
|
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *PatchExplorerController) HandleNextLine() error {
|
func (self *PatchExplorerController) HandleNextLine() error {
|
||||||
|
before := self.context.GetState().GetSelectedLineIdx()
|
||||||
self.context.GetState().CycleSelection(true)
|
self.context.GetState().CycleSelection(true)
|
||||||
|
after := self.context.GetState().GetSelectedLineIdx()
|
||||||
|
|
||||||
|
if self.context.GetState().SelectingLine() {
|
||||||
|
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
70
pkg/gui/controllers/scroll_off_margin.go
Normal file
70
pkg/gui/controllers/scroll_off_margin.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To be called after pressing up-arrow; checks whether the cursor entered the
|
||||||
|
// top scroll-off margin, and so the view needs to be scrolled up one line
|
||||||
|
func checkScrollUp(view types.IViewTrait, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) {
|
||||||
|
viewPortStart, viewPortHeight := view.ViewPortYBounds()
|
||||||
|
|
||||||
|
linesToScroll := calculateLinesToScrollUp(
|
||||||
|
viewPortStart, viewPortHeight, scrollOffMargin, lineIdxBefore, lineIdxAfter)
|
||||||
|
if linesToScroll != 0 {
|
||||||
|
view.ScrollUp(linesToScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be called after pressing down-arrow; checks whether the cursor entered the
|
||||||
|
// bottom scroll-off margin, and so the view needs to be scrolled down one line
|
||||||
|
func checkScrollDown(view types.IViewTrait, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) {
|
||||||
|
viewPortStart, viewPortHeight := view.ViewPortYBounds()
|
||||||
|
|
||||||
|
linesToScroll := calculateLinesToScrollDown(
|
||||||
|
viewPortStart, viewPortHeight, scrollOffMargin, lineIdxBefore, lineIdxAfter)
|
||||||
|
if linesToScroll != 0 {
|
||||||
|
view.ScrollDown(linesToScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateLinesToScrollUp(viewPortStart int, viewPortHeight int, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) int {
|
||||||
|
// Cap the margin to half the view height. This allows setting the config to
|
||||||
|
// a very large value to keep the cursor always in the middle of the screen.
|
||||||
|
// Use +.5 so that if the height is even, the top margin is one line higher
|
||||||
|
// than the bottom margin.
|
||||||
|
scrollOffMargin = utils.Min(scrollOffMargin, int((float64(viewPortHeight)+.5)/2))
|
||||||
|
|
||||||
|
// Scroll only if the "before" position was visible (this could be false if
|
||||||
|
// the scroll wheel was used to scroll the selected line out of view) ...
|
||||||
|
if lineIdxBefore >= viewPortStart && lineIdxBefore < viewPortStart+viewPortHeight {
|
||||||
|
marginEnd := viewPortStart + scrollOffMargin
|
||||||
|
// ... and the "after" position is within the top margin (or before it)
|
||||||
|
if lineIdxAfter < marginEnd {
|
||||||
|
return marginEnd - lineIdxAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateLinesToScrollDown(viewPortStart int, viewPortHeight int, scrollOffMargin int, lineIdxBefore int, lineIdxAfter int) int {
|
||||||
|
// Cap the margin to half the view height. This allows setting the config to
|
||||||
|
// a very large value to keep the cursor always in the middle of the screen.
|
||||||
|
// Use -.5 so that if the height is even, the bottom margin is one line lower
|
||||||
|
// than the top margin.
|
||||||
|
scrollOffMargin = utils.Min(scrollOffMargin, int((float64(viewPortHeight)-.5)/2))
|
||||||
|
|
||||||
|
// Scroll only if the "before" position was visible (this could be false if
|
||||||
|
// the scroll wheel was used to scroll the selected line out of view) ...
|
||||||
|
if lineIdxBefore >= viewPortStart && lineIdxBefore < viewPortStart+viewPortHeight {
|
||||||
|
marginStart := viewPortStart + viewPortHeight - scrollOffMargin - 1
|
||||||
|
// ... and the "after" position is within the bottom margin (or after it)
|
||||||
|
if lineIdxAfter > marginStart {
|
||||||
|
return lineIdxAfter - marginStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
171
pkg/gui/controllers/scroll_off_margin_test.go
Normal file
171
pkg/gui/controllers/scroll_off_margin_test.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_calculateLinesToScrollUp(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
viewPortStart int
|
||||||
|
viewPortHeight int
|
||||||
|
scrollOffMargin int
|
||||||
|
lineIdxBefore int
|
||||||
|
lineIdxAfter int
|
||||||
|
expectedLinesToScroll int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "before position is above viewport - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 9,
|
||||||
|
lineIdxAfter: 8,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before position is below viewport - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 20,
|
||||||
|
lineIdxAfter: 19,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before and after positions are outside scroll-off margin - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 14,
|
||||||
|
lineIdxAfter: 13,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before outside, after inside scroll-off margin - scroll by 1",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 13,
|
||||||
|
lineIdxAfter: 12,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before inside scroll-off margin - scroll by more than 1",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 11,
|
||||||
|
lineIdxAfter: 10,
|
||||||
|
expectedLinesToScroll: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large scroll-off margin - keep view centered (even viewport height)",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 999,
|
||||||
|
lineIdxBefore: 15,
|
||||||
|
lineIdxAfter: 14,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large scroll-off margin - keep view centered (odd viewport height)",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 9,
|
||||||
|
scrollOffMargin: 999,
|
||||||
|
lineIdxBefore: 14,
|
||||||
|
lineIdxAfter: 13,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
linesToScroll := calculateLinesToScrollUp(scenario.viewPortStart, scenario.viewPortHeight, scenario.scrollOffMargin, scenario.lineIdxBefore, scenario.lineIdxAfter)
|
||||||
|
assert.Equal(t, scenario.expectedLinesToScroll, linesToScroll)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_calculateLinesToScrollDown(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
viewPortStart int
|
||||||
|
viewPortHeight int
|
||||||
|
scrollOffMargin int
|
||||||
|
lineIdxBefore int
|
||||||
|
lineIdxAfter int
|
||||||
|
expectedLinesToScroll int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "before position is above viewport - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 9,
|
||||||
|
lineIdxAfter: 10,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before position is below viewport - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 20,
|
||||||
|
lineIdxAfter: 21,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before and after positions are outside scroll-off margin - don't scroll",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 15,
|
||||||
|
lineIdxAfter: 16,
|
||||||
|
expectedLinesToScroll: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before outside, after inside scroll-off margin - scroll by 1",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 16,
|
||||||
|
lineIdxAfter: 17,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "before inside scroll-off margin - scroll by more than 1",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 3,
|
||||||
|
lineIdxBefore: 18,
|
||||||
|
lineIdxAfter: 19,
|
||||||
|
expectedLinesToScroll: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large scroll-off margin - keep view centered (even viewport height)",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 10,
|
||||||
|
scrollOffMargin: 999,
|
||||||
|
lineIdxBefore: 15,
|
||||||
|
lineIdxAfter: 16,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large scroll-off margin - keep view centered (odd viewport height)",
|
||||||
|
viewPortStart: 10,
|
||||||
|
viewPortHeight: 9,
|
||||||
|
scrollOffMargin: 999,
|
||||||
|
lineIdxBefore: 14,
|
||||||
|
lineIdxAfter: 15,
|
||||||
|
expectedLinesToScroll: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
linesToScroll := calculateLinesToScrollDown(scenario.viewPortStart, scenario.viewPortHeight, scenario.scrollOffMargin, scenario.lineIdxBefore, scenario.lineIdxAfter)
|
||||||
|
assert.Equal(t, scenario.expectedLinesToScroll, linesToScroll)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -2,21 +2,19 @@ package patch_exploring
|
|||||||
|
|
||||||
import "github.com/jesseduffield/lazygit/pkg/utils"
|
import "github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
|
||||||
func calculateOrigin(currentOrigin int, bufferHeight int, firstLineIdx int, lastLineIdx int, selectedLineIdx int, mode selectMode) int {
|
func calculateOrigin(currentOrigin int, bufferHeight int, numLines int, firstLineIdx int, lastLineIdx int, selectedLineIdx int, mode selectMode) int {
|
||||||
needToSeeIdx, wantToSeeIdx := getNeedAndWantLineIdx(firstLineIdx, lastLineIdx, selectedLineIdx, mode)
|
needToSeeIdx, wantToSeeIdx := getNeedAndWantLineIdx(firstLineIdx, lastLineIdx, selectedLineIdx, mode)
|
||||||
|
|
||||||
return calculateNewOriginWithNeededAndWantedIdx(currentOrigin, bufferHeight, needToSeeIdx, wantToSeeIdx)
|
return calculateNewOriginWithNeededAndWantedIdx(currentOrigin, bufferHeight, numLines, needToSeeIdx, wantToSeeIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to scroll our origin so that the index we need to see is in view
|
// we want to scroll our origin so that the index we need to see is in view
|
||||||
// and the other index we want to see (e.g. the other side of a line range)
|
// and the other index we want to see (e.g. the other side of a line range)
|
||||||
// is in as close to being in view as possible.
|
// is as close to being in view as possible.
|
||||||
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, needToSeeIdx int, wantToSeeIdx int) int {
|
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, numLines int, needToSeeIdx int, wantToSeeIdx int) int {
|
||||||
origin := currentOrigin
|
origin := currentOrigin
|
||||||
if needToSeeIdx < currentOrigin {
|
if needToSeeIdx < currentOrigin || needToSeeIdx > currentOrigin+bufferHeight {
|
||||||
origin = needToSeeIdx
|
origin = utils.Max(utils.Min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight-1), 0)
|
||||||
} else if needToSeeIdx > currentOrigin+bufferHeight {
|
|
||||||
origin = needToSeeIdx - bufferHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bottom := origin + bufferHeight
|
bottom := origin + bufferHeight
|
||||||
|
@ -11,6 +11,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
origin int
|
origin int
|
||||||
bufferHeight int
|
bufferHeight int
|
||||||
|
numLines int
|
||||||
firstLineIdx int
|
firstLineIdx int
|
||||||
lastLineIdx int
|
lastLineIdx int
|
||||||
selectedLineIdx int
|
selectedLineIdx int
|
||||||
@ -20,29 +21,54 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
name: "selection above scroll window",
|
name: "selection above scroll window, enough room to put it in the middle",
|
||||||
|
origin: 250,
|
||||||
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
|
firstLineIdx: 210,
|
||||||
|
lastLineIdx: 210,
|
||||||
|
selectedLineIdx: 210,
|
||||||
|
selectMode: LINE,
|
||||||
|
expected: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "selection above scroll window, not enough room to put it in the middle",
|
||||||
origin: 50,
|
origin: 50,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 10,
|
firstLineIdx: 10,
|
||||||
lastLineIdx: 10,
|
lastLineIdx: 10,
|
||||||
selectedLineIdx: 10,
|
selectedLineIdx: 10,
|
||||||
selectMode: LINE,
|
selectMode: LINE,
|
||||||
expected: 10,
|
expected: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "selection below scroll window",
|
name: "selection below scroll window, enough room to put it in the middle",
|
||||||
origin: 0,
|
origin: 0,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 150,
|
firstLineIdx: 150,
|
||||||
lastLineIdx: 150,
|
lastLineIdx: 150,
|
||||||
selectedLineIdx: 150,
|
selectedLineIdx: 150,
|
||||||
selectMode: LINE,
|
selectMode: LINE,
|
||||||
expected: 50,
|
expected: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "selection below scroll window, not enough room to put it in the middle",
|
||||||
|
origin: 0,
|
||||||
|
bufferHeight: 100,
|
||||||
|
numLines: 200,
|
||||||
|
firstLineIdx: 199,
|
||||||
|
lastLineIdx: 199,
|
||||||
|
selectedLineIdx: 199,
|
||||||
|
selectMode: LINE,
|
||||||
|
expected: 99,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "selection within scroll window",
|
name: "selection within scroll window",
|
||||||
origin: 0,
|
origin: 0,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 50,
|
firstLineIdx: 50,
|
||||||
lastLineIdx: 50,
|
lastLineIdx: 50,
|
||||||
selectedLineIdx: 50,
|
selectedLineIdx: 50,
|
||||||
@ -53,6 +79,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
name: "range ending below scroll window with selection at end of range",
|
name: "range ending below scroll window with selection at end of range",
|
||||||
origin: 0,
|
origin: 0,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 40,
|
firstLineIdx: 40,
|
||||||
lastLineIdx: 150,
|
lastLineIdx: 150,
|
||||||
selectedLineIdx: 150,
|
selectedLineIdx: 150,
|
||||||
@ -63,6 +90,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
name: "range ending below scroll window with selection at beginning of range",
|
name: "range ending below scroll window with selection at beginning of range",
|
||||||
origin: 0,
|
origin: 0,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 40,
|
firstLineIdx: 40,
|
||||||
lastLineIdx: 150,
|
lastLineIdx: 150,
|
||||||
selectedLineIdx: 40,
|
selectedLineIdx: 40,
|
||||||
@ -73,6 +101,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
name: "range starting above scroll window with selection at beginning of range",
|
name: "range starting above scroll window with selection at beginning of range",
|
||||||
origin: 50,
|
origin: 50,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 40,
|
firstLineIdx: 40,
|
||||||
lastLineIdx: 150,
|
lastLineIdx: 150,
|
||||||
selectedLineIdx: 40,
|
selectedLineIdx: 40,
|
||||||
@ -83,6 +112,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
name: "hunk extending beyond both bounds of scroll window",
|
name: "hunk extending beyond both bounds of scroll window",
|
||||||
origin: 50,
|
origin: 50,
|
||||||
bufferHeight: 100,
|
bufferHeight: 100,
|
||||||
|
numLines: 500,
|
||||||
firstLineIdx: 40,
|
firstLineIdx: 40,
|
||||||
lastLineIdx: 200,
|
lastLineIdx: 200,
|
||||||
selectedLineIdx: 70,
|
selectedLineIdx: 70,
|
||||||
@ -94,7 +124,7 @@ func TestNewOrigin(t *testing.T) {
|
|||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
s := s
|
s := s
|
||||||
t.Run(s.name, func(t *testing.T) {
|
t.Run(s.name, func(t *testing.T) {
|
||||||
assert.EqualValues(t, s.expected, calculateOrigin(s.origin, s.bufferHeight, s.firstLineIdx, s.lastLineIdx, s.selectedLineIdx, s.selectMode))
|
assert.EqualValues(t, s.expected, calculateOrigin(s.origin, s.bufferHeight, s.numLines, s.firstLineIdx, s.lastLineIdx, s.selectedLineIdx, s.selectMode))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,9 +143,14 @@ func (s *State) CycleHunk(forward bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
||||||
start := s.patch.HunkStartIdx(hunkIdx + change)
|
if hunkIdx != -1 {
|
||||||
|
newHunkIdx := hunkIdx + change
|
||||||
|
if newHunkIdx >= 0 && newHunkIdx < s.patch.HunkCount() {
|
||||||
|
start := s.patch.HunkStartIdx(newHunkIdx)
|
||||||
s.selectedLineIdx = s.patch.GetNextChangeIdx(start)
|
s.selectedLineIdx = s.patch.GetNextChangeIdx(start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) CycleLine(forward bool) {
|
func (s *State) CycleLine(forward bool) {
|
||||||
change := 1
|
change := 1
|
||||||
@ -216,8 +221,8 @@ func (s *State) SelectTop() {
|
|||||||
s.SelectLine(0)
|
s.SelectLine(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int) int {
|
func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int, numLines int) int {
|
||||||
firstLineIdx, lastLineIdx := s.SelectedRange()
|
firstLineIdx, lastLineIdx := s.SelectedRange()
|
||||||
|
|
||||||
return calculateOrigin(currentOrigin, bufferHeight, firstLineIdx, lastLineIdx, s.GetSelectedLineIdx(), s.selectMode)
|
return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedLineIdx(), s.selectMode)
|
||||||
}
|
}
|
||||||
|
40
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
40
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -273,25 +273,33 @@ func (v *View) FocusPoint(cx int, cy int) {
|
|||||||
ly = 0
|
ly = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// if line is above origin, move origin and set cursor to zero
|
v.oy = calculateNewOrigin(cy, v.oy, lineCount, ly)
|
||||||
// if line is below origin + height, move origin and set cursor to max
|
|
||||||
// otherwise set cursor to value - origin
|
|
||||||
if ly > lineCount {
|
|
||||||
v.cx = cx
|
|
||||||
v.cy = cy
|
|
||||||
v.oy = 0
|
|
||||||
} else if cy < v.oy {
|
|
||||||
v.cx = cx
|
|
||||||
v.cy = 0
|
|
||||||
v.oy = cy
|
|
||||||
} else if cy > v.oy+ly {
|
|
||||||
v.cx = cx
|
|
||||||
v.cy = ly
|
|
||||||
v.oy = cy - ly
|
|
||||||
} else {
|
|
||||||
v.cx = cx
|
v.cx = cx
|
||||||
v.cy = cy - v.oy
|
v.cy = cy - v.oy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateNewOrigin(selectedLine int, oldOrigin int, lineCount int, viewHeight int) int {
|
||||||
|
if viewHeight > lineCount {
|
||||||
|
return 0
|
||||||
|
} else if selectedLine < oldOrigin || selectedLine > oldOrigin+viewHeight {
|
||||||
|
// If the selected line is outside the visible area, scroll the view so
|
||||||
|
// that the selected line is in the middle.
|
||||||
|
newOrigin := selectedLine - viewHeight/2
|
||||||
|
|
||||||
|
// However, take care not to overflow if the total line count is less
|
||||||
|
// than the view height.
|
||||||
|
maxOrigin := lineCount - viewHeight - 1
|
||||||
|
if newOrigin > maxOrigin {
|
||||||
|
newOrigin = maxOrigin
|
||||||
|
}
|
||||||
|
if newOrigin < 0 {
|
||||||
|
newOrigin = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOrigin
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldOrigin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *searcher) search(str string) {
|
func (s *searcher) search(str string) {
|
||||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -124,7 +124,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
|||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||||
# github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727
|
# github.com/jesseduffield/gocui v0.3.1-0.20230815093813-9f3df4a6da3b
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/jesseduffield/gocui
|
github.com/jesseduffield/gocui
|
||||||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||||
|
Reference in New Issue
Block a user