diff --git a/pkg/commands/patch/patch.go b/pkg/commands/patch/patch.go index 767f3cd89..74742f282 100644 --- a/pkg/commands/patch/patch.go +++ b/pkg/commands/patch/patch.go @@ -183,6 +183,35 @@ func (self *Patch) OldLineNumberOfLine(idx int) int { return hunk.oldStart + offset } +// LineNumbersOfLines returns what LineNumberOfLine and OldLineNumberOfLine return for +// each line of the patch. It numbers all of them in one pass, whereas calling those +// two for every line takes time quadratic in the length of a hunk. +func (self *Patch) LineNumbersOfLines() (newLineNumbers []int, oldLineNumbers []int) { + newLineNumbers = make([]int, 0, self.LineCount()) + oldLineNumbers = make([]int, 0, self.LineCount()) + for range self.header { + newLineNumbers = append(newLineNumbers, 1) + oldLineNumbers = append(oldLineNumbers, 1) + } + for _, hunk := range self.hunks { + newLineNumber, oldLineNumber := hunk.newStart, hunk.oldStart + // The hunk's header line has the numbers of the hunk's first line. + newLineNumbers = append(newLineNumbers, newLineNumber) + oldLineNumbers = append(oldLineNumbers, oldLineNumber) + for _, line := range hunk.bodyLines { + newLineNumbers = append(newLineNumbers, newLineNumber) + oldLineNumbers = append(oldLineNumbers, oldLineNumber) + if line.Kind == ADDITION || line.Kind == CONTEXT { + newLineNumber++ + } + if line.Kind == DELETION || line.Kind == CONTEXT { + oldLineNumber++ + } + } + } + return newLineNumbers, oldLineNumbers +} + // Returns hunk index containing the line at the given patch line index func (self *Patch) HunkContainingLine(idx int) int { for hunkIdx, hunk := range self.hunks { diff --git a/pkg/commands/patch/patch_test.go b/pkg/commands/patch/patch_test.go index e22876b19..dffceece2 100644 --- a/pkg/commands/patch/patch_test.go +++ b/pkg/commands/patch/patch_test.go @@ -838,6 +838,37 @@ func TestOldLineNumberOfLine(t *testing.T) { } } +func TestLineNumbersOfLines(t *testing.T) { + scenarios := []struct { + testName string + patchStr string + }{ + {"simpleDiff", simpleDiff}, + {"twoHunks", twoHunks}, + {"twoHunksWithMoreAdditionsThanRemovals", twoHunksWithMoreAdditionsThanRemovals}, + {"twoChangesInOneHunk", twoChangesInOneHunk}, + {"consecutiveDeletions", consecutiveDeletions}, + {"renameWithModificationDiff", renameWithModificationDiff}, + {"addNewlineToEndOfFile", addNewlineToEndOfFile}, + {"newFile", newFile}, + {"deletedFile", deletedFile}, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + patch := Parse(s.patchStr) + newLineNumbers, oldLineNumbers := patch.LineNumbersOfLines() + + assert.Len(t, newLineNumbers, patch.LineCount()) + assert.Len(t, oldLineNumbers, patch.LineCount()) + for idx := range patch.LineCount() { + assert.Equal(t, patch.LineNumberOfLine(idx), newLineNumbers[idx], "new line number of line %d", idx) + assert.Equal(t, patch.OldLineNumberOfLine(idx), oldLineNumbers[idx], "old line number of line %d", idx) + } + }) + } +} + func TestGetNextStageableLineIndex(t *testing.T) { type scenario struct { testName string diff --git a/pkg/gui/controllers/helpers/diff_line_parser.go b/pkg/gui/controllers/helpers/diff_line_parser.go index 39d99c9ec..1a019bf1c 100644 --- a/pkg/gui/controllers/helpers/diff_line_parser.go +++ b/pkg/gui/controllers/helpers/diff_line_parser.go @@ -272,6 +272,7 @@ func parseFileSection(fileLines []string, endsTheBuffer bool) []bufferLineParse return result } patchLines := p.Lines() + newLineNumbers, oldLineNumbers := p.LineNumbersOfLines() for i := range fileLines { if i >= len(patchLines) { break @@ -279,10 +280,10 @@ func parseFileSection(fileLines []string, endsTheBuffer bool) []bufferLineParse parsed := parsedDiffLine{ Path: relPath, Type: diffLineTypeForKind(patchLines[i].Kind), - NewLine: p.LineNumberOfLine(i), + NewLine: newLineNumbers[i], } if parsed.Type == types.DiffLineDeleted { - parsed.OldLine = p.OldLineNumberOfLine(i) + parsed.OldLine = oldLineNumbers[i] } result[i] = bufferLineParse{parsed, true} }