Scroll the commits panel sideways to a search match that was off screen (#6184)

If a search match is in a commit below or above the visible area of the
commits panel, going to it with /, n or N doesn't scroll the panel
sideways to show it. The panel keeps the horizontal scroll position of
the previous match instead. Since this depends on whether the commit was
on screen before, it seems to happen only some of the time.

The commits panel renders only the lines on screen. So the search knows
where in a commit's line its match is only once the line is on screen,
and that happens after the search has scrolled the panel to it.

Scroll the panel sideways once more after rendering the lines that a
search has scrolled to. The same applies to the commits of a branch or
tag.
This commit is contained in:
Stefan Haller
2026-11-07 20:44:30 +01:00
committed by GitHub
4 changed files with 74 additions and 4 deletions
+21 -4
View File
@@ -540,16 +540,33 @@ func (v *View) SelectSearchResult(index int) {
y := pos.Y
v.FocusPoint(v.ox, y, true)
// A match without a position in the line has nothing to scroll to.
if pos.XStart >= 0 {
v.scrollColumnsIntoView(pos.XStart, pos.XEnd)
}
v.scrollSidewaysToSearchResult(pos)
v.renderSearchStatus(index, itemCount)
if v.searcher.onSelectItem != nil {
v.searcher.onSelectItem(v, y)
}
}
// ScrollSidewaysToCurrentSearchResult scrolls the view sideways to show the current
// match if it isn't on screen. SelectSearchResult does this too, but if the view renders
// only the lines on screen, a match that was off screen has no position in its line
// yet. Call this once its line has been rendered.
func (v *View) ScrollSidewaysToCurrentSearchResult() {
v.refreshSearchPositions()
if len(v.searcher.searchPositions) > 0 {
v.scrollSidewaysToSearchResult(v.searcher.searchPositions[v.searcher.currentSearchIndex])
}
}
// scrollSidewaysToSearchResult scrolls the view sideways to show the given match if it
// isn't on screen. A match without a position in its line has nothing to scroll to.
func (v *View) scrollSidewaysToSearchResult(pos SearchPosition) {
if pos.XStart >= 0 {
v.scrollColumnsIntoView(pos.XStart, pos.XEnd)
}
}
// Returns <current match index>, <total matches>
func (v *View) GetSearchStatus() (int, int) {
v.refreshSearchPositions()
+5
View File
@@ -77,6 +77,11 @@ func (self *ListContextTrait) FocusLine(scrollIntoView bool) {
self.refreshViewport()
}
}
// The search couldn't scroll sideways to a match whose line wasn't rendered.
if inOnSearchSelect && self.renderOnlyVisibleLines {
self.GetView().ScrollSidewaysToCurrentSearchResult()
}
return nil
})
@@ -0,0 +1,47 @@
package commit
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var SearchScrollsToAMatchOffTheEdge = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Searching the commits panel scrolls it sideways to show a match past its right edge, also in a commit that was below the visible area",
ExtraCmdArgs: []string{},
Skip: false,
Width: 120,
Height: 30,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.NewBranch("branch")
for i := 1; i <= 30; i++ {
message := fmt.Sprintf("commit %02d", i)
switch i {
case 29:
message += " " + strings.Repeat("word ", 20) + "needle"
case 3:
message += " " + strings.Repeat("word ", 40) + "needle"
}
shell.EmptyCommit(message)
}
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
FilterOrSearch("needle").
SelectedLines(
Contains("commit 29"),
).
OriginX(112).
// Commit 03 is below the visible area, and its match is 100 columns
// further right than the one in commit 29.
Press(keys.Universal.NextMatch).
SelectedLines(
Contains("commit 03"),
).
OriginX(212)
},
})
+1
View File
@@ -176,6 +176,7 @@ var tests = []*components.IntegrationTest{
commit.Search,
commit.SearchKeepsTheCurrentMatchOnRefresh,
commit.SearchKeepsTheSecondMatchOnALineOnRefresh,
commit.SearchScrollsToAMatchOffTheEdge,
commit.SetAuthor,
commit.SetAuthorRange,
commit.StageRangeOfLines,