From dc4fee296c63927dfe3b3dd6ac7a4922400f56d3 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 13 Oct 2026 18:53:37 +0200 Subject: [PATCH] Leave out the text of the rows of a rendering that states its lines Under a diff renderer that states which diff line each row shows, such as delta, the queries about the lines of the main view read the rows by those records alone. Still, they took the text of every row as well, and for a diff of 300,000 lines this was most of the time each of them took. Moving the selection by change block runs two of them for every key press. Take the records of the rows first, and their text only if no row states a diff line. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/gocui/view.go | 14 ++++++++++++++ pkg/gocui/view_test.go | 15 +++++++++++++++ .../controllers/helpers/diff_line_helper.go | 2 +- .../controllers/helpers/diff_line_parser.go | 15 +++++++++++++++ .../helpers/diff_line_parser_test.go | 19 +++++++++++++++++++ .../controllers/helpers/diff_line_queries.go | 8 ++++---- .../controllers/helpers/diff_line_restore.go | 8 ++++---- .../helpers/diff_line_selection.go | 2 +- 8 files changed, 73 insertions(+), 10 deletions(-) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 7cb3f5fc9..f1c708796 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -2158,6 +2158,20 @@ func (v *View) DiffLineContents() []DiffLineContent { return diffLineContentsFrom(v.buf, 0) } +// DiffLineMetadata is DiffLineContents without the text of the lines, for a reader +// that only needs the records. Taking the text is most of the cost of +// DiffLineContents. +func (v *View) DiffLineMetadata() [][]string { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + metadata := make([][]string, len(v.buf.lines)) + for i := range v.buf.lines { + metadata[i] = v.buf.lines[i].diffLineMetadata() + } + return metadata +} + // OffscreenDiffLineContents is DiffLineContents for the content of a re-render in // progress (see BeginOffscreenRender). A reader deciding where the new content // should be shown has to work from this: it has to answer before the swap, since diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index a8e1cea56..e4f2441b2 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -354,6 +354,21 @@ func TestDiffLineContentsOfARewrittenLine(t *testing.T) { }, v.DiffLineContents()) } +func TestDiffLineMetadata(t *testing.T) { + v := NewView("name", 0, 0, 80, 10, OutputNormal) + v.writeString(strings.Join([]string{ + osc1717("1;d;2;2;foo.txt") + "old2 " + osc1717("1;a;2;;foo.txt") + "new2", + "@@ a hunk header, which carries no record @@", + osc1717("1;c;3;;foo.txt") + "line3", + }, "\n")) + + assert.Equal(t, [][]string{ + {"1;d;2;2;foo.txt", "1;a;2;;foo.txt"}, + nil, + {"1;c;3;;foo.txt"}, + }, v.DiffLineMetadata()) +} + func TestDiffLineContentsSwallowsHandshake(t *testing.T) { v := NewView("name", 0, 0, 80, 10, OutputNormal) diff --git a/pkg/gui/controllers/helpers/diff_line_helper.go b/pkg/gui/controllers/helpers/diff_line_helper.go index a8a8f5e53..22d67b148 100644 --- a/pkg/gui/controllers/helpers/diff_line_helper.go +++ b/pkg/gui/controllers/helpers/diff_line_helper.go @@ -60,7 +60,7 @@ func (self *DiffLineHelper) diffLineIdentitiesAt( return nil, false } - contents := view.DiffLineContents() + contents := diffLineContents(view) if bufferLineIdx >= len(contents) { return nil, false } diff --git a/pkg/gui/controllers/helpers/diff_line_parser.go b/pkg/gui/controllers/helpers/diff_line_parser.go index 1a019bf1c..667946a7c 100644 --- a/pkg/gui/controllers/helpers/diff_line_parser.go +++ b/pkg/gui/controllers/helpers/diff_line_parser.go @@ -9,6 +9,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/samber/lo" ) // diffFilePrefix marks the start of a file's section in a (possibly multi-file) @@ -125,6 +126,20 @@ func renderingStatesDiffLines(contents []gocui.DiffLineContent) bool { }) } +// diffLineContents returns the contents of view's rows, as the queries about the lines +// of a diff read them. A rendering that states which diff line each row shows is read +// by those records alone (see renderingStatesDiffLines), so the text of its rows is +// left out then. Taking the text is most of the cost of reading a long diff. +func diffLineContents(view *gocui.View) []gocui.DiffLineContent { + contents := lo.Map(view.DiffLineMetadata(), func(metadata []string, _ int) gocui.DiffLineContent { + return gocui.DiffLineContent{Metadata: metadata} + }) + if renderingStatesDiffLines(contents) { + return contents + } + return view.DiffLineContents() +} + // parseDiffLineRecords parses the records a row carries, left to right, leaving out // the ones we don't understand. A row carries more than one when the rendering puts // two diff lines on it, as a side-by-side row does with a deletion and the addition diff --git a/pkg/gui/controllers/helpers/diff_line_parser_test.go b/pkg/gui/controllers/helpers/diff_line_parser_test.go index f08618b74..55553e0f3 100644 --- a/pkg/gui/controllers/helpers/diff_line_parser_test.go +++ b/pkg/gui/controllers/helpers/diff_line_parser_test.go @@ -544,3 +544,22 @@ func TestParseDiffLineIdentities(t *testing.T) { } }) } + +func TestDiffLineContentsLeaveOutTheTextOfARenderingWithRecords(t *testing.T) { + record := func(payload string) string { return "\x1b]1717;" + payload + "\x1b\\" } + + rendering := gocui.NewView("rendering", 0, 0, 80, 10, gocui.OutputNormal) + rendering.WriteString(record("1;a;1;;foo.txt") + "added\n" + "a divider\n") + assert.Equal(t, []gocui.DiffLineContent{ + {Metadata: []string{"1;a;1;;foo.txt"}}, + {}, + }, diffLineContents(rendering)) + + // A diff without records is read by its text, so it keeps it. + diff := gocui.NewView("diff", 0, 0, 80, 10, gocui.OutputNormal) + diff.WriteString("diff --git a/foo.txt b/foo.txt\n+added\n") + assert.Equal(t, []gocui.DiffLineContent{ + {Text: "diff --git a/foo.txt b/foo.txt"}, + {Text: "+added"}, + }, diffLineContents(diff)) +} diff --git a/pkg/gui/controllers/helpers/diff_line_queries.go b/pkg/gui/controllers/helpers/diff_line_queries.go index c8e59bbb4..e56591a25 100644 --- a/pkg/gui/controllers/helpers/diff_line_queries.go +++ b/pkg/gui/controllers/helpers/diff_line_queries.go @@ -28,7 +28,7 @@ import ( // beside the addition replacing it — and all of them are reported: what the user // pointed at is the row, so everything on it is selected. func (self *DiffLineHelper) DiffLinesInBufferRange(view *gocui.View, first int, last int) []types.DiffLineInfo { - identities := self.resolveDiffLineIdentities(view.DiffLineContents()) + identities := self.resolveDiffLineIdentities(diffLineContents(view)) infos := []types.DiffLineInfo{} for bufferLine := first; bufferLine <= min(last, len(identities)-1); bufferLine++ { @@ -136,7 +136,7 @@ func (self *DiffLineHelper) ChangeLinesInBufferRange(view *gocui.View, first int // header, or a row whose identity couldn't be recovered. Those are the rows a // selection is anchored on and navigation moves between. func (self *DiffLineHelper) changeLines(view *gocui.View) []bool { - resolved := self.resolveDiffLines(view.DiffLineContents()) + resolved := self.resolveDiffLines(diffLineContents(view)) isChange := make([]bool, len(resolved)) for i, r := range resolved { isChange[i] = r.ok && r.info.IsChange() @@ -244,7 +244,7 @@ func (self *DiffLineHelper) IsSingleHunkForWholeFile(view *gocui.View, changeVie if !ok { return false } - resolved := self.resolveDiffLines(view.DiffLineContents()) + resolved := self.resolveDiffLines(diffLineContents(view)) if anchor >= len(resolved) || !resolved[anchor].ok { return false } @@ -375,7 +375,7 @@ func (self *DiffLineHelper) StartOfFileInDiff(view *gocui.View, path string) (in // was materialized into, so that both halves of a change belong to the same file however // the diff renderer states them. func (self *DiffLineHelper) filePaths(view *gocui.View) []string { - resolved := self.resolveDiffLines(view.DiffLineContents()) + resolved := self.resolveDiffLines(diffLineContents(view)) toRepoTerms := self.repoTermsMapper(view) paths := make([]string, len(resolved)) for i, row := range resolved { diff --git a/pkg/gui/controllers/helpers/diff_line_restore.go b/pkg/gui/controllers/helpers/diff_line_restore.go index 5cd924cc9..bee446b7e 100644 --- a/pkg/gui/controllers/helpers/diff_line_restore.go +++ b/pkg/gui/controllers/helpers/diff_line_restore.go @@ -151,7 +151,7 @@ func (self *DiffLineHelper) selectionLine( if len(candidates) == 0 { return anchorViewLine } - _, bufferLine := self.nearestSurvivingCandidate(view.DiffLineContents(), candidates) + _, bufferLine := self.nearestSurvivingCandidate(diffLineContents(view), candidates) if bufferLine == -1 { return anchorViewLine } @@ -208,7 +208,7 @@ func selectionFarEndViewLine(view *gocui.View) (int, bool, bool) { // findDiffLine returns the view line showing the given diff line in what view is // displaying now, for placing a remembered line once the re-render is on screen. func (self *DiffLineHelper) findDiffLine(view *gocui.View, identity types.DiffLineInfo) (int, bool) { - bufferLine, ok := self.patchLineRows(view.DiffLineContents())[patchLineOf(identity)] + bufferLine, ok := self.patchLineRows(diffLineContents(view))[patchLineOf(identity)] if !ok { return 0, false } @@ -273,7 +273,7 @@ func (self *DiffLineHelper) restoreDiffLinePositionOnRerender( // place it left behind is the same one that identity used to have. func (self *DiffLineHelper) ChangeLineOrdinal(view *gocui.View, bufferLine int) int { ordinal := 0 - for i, row := range self.resolveDiffLines(view.DiffLineContents()) { + for i, row := range self.resolveDiffLines(diffLineContents(view)) { if i >= bufferLine { break } @@ -421,7 +421,7 @@ func (self *DiffLineHelper) nearbyDiffLines(view *gocui.View, anchorViewLine int if !ok { return nil } - resolved := self.resolveDiffLines(view.DiffLineContents()) + resolved := self.resolveDiffLines(diffLineContents(view)) if anchor >= len(resolved) { return nil } diff --git a/pkg/gui/controllers/helpers/diff_line_selection.go b/pkg/gui/controllers/helpers/diff_line_selection.go index ff9a9d2c9..85e554af3 100644 --- a/pkg/gui/controllers/helpers/diff_line_selection.go +++ b/pkg/gui/controllers/helpers/diff_line_selection.go @@ -222,7 +222,7 @@ func (self *DiffLineHelper) RefreshInclusionGutter() { return } - resolved := self.resolveDiffLines(view.DiffLineContents()) + resolved := self.resolveDiffLines(diffLineContents(view)) marks := make([]bool, len(resolved)) for i, row := range resolved { if row.ok && row.info.IsChange() {