Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ gui:
windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
scrollHeight: 2 # how many lines you scroll by
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
expandFocusedSidePanel: false
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
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/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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/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/gocui v0.3.1-0.20230807090044-83a7161c8727 h1:cLq698s96uDMm0n5379doAjIKoip3/8ioWIM8pySRLY=
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 h1:D2Qgpvo+i7bIIBbi/UtzrpyTuUj020lJeGxMa0J1jGs=
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/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
Expand Down
5 changes: 5 additions & 0 deletions pkg/commands/patch/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,8 @@ func (self *Patch) LineCount() int {
}
return count
}

// Returns the number of hunks of the patch
func (self *Patch) HunkCount() int {
return len(self.hunks)
}
2 changes: 2 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type GuiConfig struct {
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
ScrollOffMargin int `yaml:"scrollOffMargin"`
MouseEvents bool `yaml:"mouseEvents"`
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
Expand Down Expand Up @@ -418,6 +419,7 @@ func GetDefaultConfig() *UserConfig {
Gui: GuiConfig{
ScrollHeight: 2,
ScrollPastBottom: true,
ScrollOffMargin: 2,
MouseEvents: true,
SkipDiscardChangeWarning: false,
SkipStashWarning: false,
Expand Down
3 changes: 2 additions & 1 deletion pkg/gui/context/patch_explorer_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ func (self *PatchExplorerContext) FocusSelection() {
_, viewHeight := view.Size()
bufferHeight := viewHeight - 1
_, origin := view.Origin()
numLines := view.LinesHeight()

newOriginY := state.CalculateOrigin(origin, bufferHeight)
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)

_ = view.SetOriginY(newOriginY)

Expand Down
6 changes: 6 additions & 0 deletions pkg/gui/controllers/list_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// we're not constantly re-rendering the main view.
if before != after {
if change == -1 {
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the other branch we will have to call ModelToViewIndex on before and after to make it work correctly for lists that have section headers.

} else if change == 1 {
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig.Gui.ScrollOffMargin, before, after)
}

return self.context.HandleFocus(types.OnFocusOpts{})
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/gui/controllers/patch_explorer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,25 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
}

func (self *PatchExplorerController) HandlePrevLine() error {
before := self.context.GetState().GetSelectedLineIdx()
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
}

func (self *PatchExplorerController) HandleNextLine() error {
before := self.context.GetState().GetSelectedLineIdx()
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
}
Expand Down
70 changes: 70 additions & 0 deletions pkg/gui/controllers/scroll_off_margin.go
Original file line number Diff line number Diff line change
@@ -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 changes: 171 additions & 0 deletions pkg/gui/controllers/scroll_off_margin_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
14 changes: 6 additions & 8 deletions pkg/gui/patch_exploring/focus.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ package patch_exploring

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)

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
// 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.
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, needToSeeIdx int, wantToSeeIdx int) int {
// is as close to being in view as possible.
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, numLines int, needToSeeIdx int, wantToSeeIdx int) int {
origin := currentOrigin
if needToSeeIdx < currentOrigin {
origin = needToSeeIdx
} else if needToSeeIdx > currentOrigin+bufferHeight {
origin = needToSeeIdx - bufferHeight
if needToSeeIdx < currentOrigin || needToSeeIdx > currentOrigin+bufferHeight {
origin = utils.Max(utils.Min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight-1), 0)
}

bottom := origin + bufferHeight
Expand Down
Loading