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() {