From 74569cf535debb3ab2b58601710574003779cb1c Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 17:48:25 +0100 Subject: [PATCH 1/3] Add a test showing that commits search doesn't scroll to an off-screen match Co-Authored-By: Claude Opus 5.5 --- .../search_scrolls_to_a_match_off_the_edge.go | 50 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 51 insertions(+) create mode 100644 pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go diff --git a/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go b/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go new file mode 100644 index 000000000..1b27773e9 --- /dev/null +++ b/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go @@ -0,0 +1,50 @@ +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"), + ). + /* EXPECTED: + OriginX(212) + ACTUAL: */ + OriginX(112) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 6f08a6863..8402b1532 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -176,6 +176,7 @@ var tests = []*components.IntegrationTest{ commit.Search, commit.SearchKeepsTheCurrentMatchOnRefresh, commit.SearchKeepsTheSecondMatchOnALineOnRefresh, + commit.SearchScrollsToAMatchOffTheEdge, commit.SetAuthor, commit.SetAuthorRange, commit.StageRangeOfLines, From a73278d2467d2b504baccc57798817fbb13aa0b0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 17:49:28 +0100 Subject: [PATCH 2/3] Extract View.scrollSidewaysToSearchResult A view that renders only the lines on screen is going to need it once it has rendered the line of a match. Co-Authored-By: Claude Opus 5.5 --- pkg/gocui/view.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 643163a2a..dbb33c8b4 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -540,16 +540,21 @@ 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) } } +// 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 , func (v *View) GetSearchStatus() (int, int) { v.refreshSearchPositions() From 92525a23470e818b7a6613e1699d5acc94386596 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 17:51:06 +0100 Subject: [PATCH 3/3] Scroll the commits panel sideways to a search match that was off screen 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. Co-Authored-By: Claude Opus 5.5 --- pkg/gocui/view.go | 12 ++++++++++++ pkg/gui/context/list_context_trait.go | 5 +++++ .../commit/search_scrolls_to_a_match_off_the_edge.go | 3 --- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index dbb33c8b4..f16ed14ec 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -547,6 +547,18 @@ func (v *View) SelectSearchResult(index int) { } } +// 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) { diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index 2e4ae7267..550a3840f 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -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 }) diff --git a/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go b/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go index 1b27773e9..1d54f1081 100644 --- a/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go +++ b/pkg/integration/tests/commit/search_scrolls_to_a_match_off_the_edge.go @@ -42,9 +42,6 @@ var SearchScrollsToAMatchOffTheEdge = NewIntegrationTest(NewIntegrationTestArgs{ SelectedLines( Contains("commit 03"), ). - /* EXPECTED: OriginX(212) - ACTUAL: */ - OriginX(112) }, })