mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-27 23:50:17 -05:00
Number the lines of a diff in one pass when parsing it
With no diff renderer configured, opening the jump-to-file menu for the diff of a commit that adds or deletes a file of 100,000 lines takes more than 20 seconds, and so does picking a file from it. In such a diff, moving the selection by change block takes as long for each key press. All this time the UI is frozen. To find out which line of which file each row of the main view shows, lazygit parses the view's content as a diff. For every line of a file's diff it asks the parsed patch for the line's number in the new and in the old file, and each of these calls counts the lines of the hunk before it. So parsing takes time quadratic in the length of a hunk, and the diff of a new or deleted file is a single hunk. For a diff of 200,000 lines, one parse took about 10 seconds, and opening the menu parses it twice. A diff renderer that states which line each row shows, such as delta, doesn't go through this parse. Number all lines of the patch in one pass instead. Parsing that diff now takes about 40 milliseconds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ae6639a003
commit
80dff3c2b9
3 files changed
+63
-2
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user