mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Add identity-based line accessors to PatchBuilder
The patch builder works in patch-line-index space, which the patch explorer can supply directly from its parsed-patch state. The focused main view can't: it only knows a selection as diff-line metadata identities (file line number + deletion?), and those indices differ between the raw diff and however a pager renders it. Add the conversion at the boundary so the main view stays in identity space: PatchLineIndicesForLines maps identities to indices (for toggling) and IncludedLineIdentities reports the included change lines as identities (for the inclusion gutter to match rendered rows against). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
12523c8a20
commit
4c123f9414
|
|
@ -299,6 +299,76 @@ func (p *PatchBuilder) GetFileIncLineIndices(filename string, previousPath strin
|
|||
return info.includedLineIndices, nil
|
||||
}
|
||||
|
||||
// LineIdentity identifies a change line (an addition or deletion) by its file line
|
||||
// number and whether it's a deletion, independently of the line's index in the parsed
|
||||
// patch. It is the identity the diff-line metadata resolves a rendered row to (see
|
||||
// types.DiffLineInfo.PatchSelectLine), and lets the focused main view toggle patch
|
||||
// membership and drive the inclusion gutter without dealing in patch-line indices —
|
||||
// which differ between the raw diff and however a pager renders it.
|
||||
type LineIdentity struct {
|
||||
LineNumber int
|
||||
IsDeletion bool
|
||||
}
|
||||
|
||||
// changeLineIndexByIdentity scans a parsed diff and returns, for each change line, its
|
||||
// index in the patch keyed by the line's identity. An addition is keyed by its new-file
|
||||
// line number, a deletion by its old-file line number, so each change line has a
|
||||
// distinct identity (two consecutive deletions share a new-file number but differ in
|
||||
// the old-file one).
|
||||
func changeLineIndexByIdentity(parsed *Patch) map[LineIdentity]int {
|
||||
byIdentity := map[LineIdentity]int{}
|
||||
for idx, line := range parsed.Lines() {
|
||||
switch {
|
||||
case line.IsAddition():
|
||||
byIdentity[LineIdentity{parsed.LineNumberOfLine(idx), false}] = idx
|
||||
case line.IsDeletion():
|
||||
byIdentity[LineIdentity{parsed.OldLineNumberOfLine(idx), true}] = idx
|
||||
}
|
||||
}
|
||||
return byIdentity
|
||||
}
|
||||
|
||||
// PatchLineIndicesForLines maps the given change-line identities to their indices in
|
||||
// filename's parsed diff — the index form that AddFileLineRange / RemoveFileLineRange
|
||||
// and GetFileIncLineIndices work in. Identities that don't correspond to a change line
|
||||
// (e.g. a context line) are skipped. It is how the focused main view, which knows a
|
||||
// selection only as metadata identities, drives patch building.
|
||||
func (p *PatchBuilder) PatchLineIndicesForLines(filename string, lines []LineIdentity) ([]int, error) {
|
||||
info, err := p.getFileInfo(filename, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byIdentity := changeLineIndexByIdentity(Parse(info.diff))
|
||||
indices := make([]int, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
if idx, ok := byIdentity[line]; ok {
|
||||
indices = append(indices, idx)
|
||||
}
|
||||
}
|
||||
return indices, nil
|
||||
}
|
||||
|
||||
// IncludedLineIdentities returns the identities of the change lines currently included
|
||||
// in the patch for filename — the identity space the inclusion gutter matches rendered
|
||||
// rows against. Empty when the file isn't part of the patch.
|
||||
func (p *PatchBuilder) IncludedLineIdentities(filename string) []LineIdentity {
|
||||
info, ok := p.fileInfoMap[filename]
|
||||
if !ok || info.mode == UNSELECTED {
|
||||
return nil
|
||||
}
|
||||
includedIdx := make(map[int]bool, len(info.includedLineIndices))
|
||||
for _, idx := range info.includedLineIndices {
|
||||
includedIdx[idx] = true
|
||||
}
|
||||
var identities []LineIdentity
|
||||
for identity, idx := range changeLineIndexByIdentity(Parse(info.diff)) {
|
||||
if includedIdx[idx] {
|
||||
identities = append(identities, identity)
|
||||
}
|
||||
}
|
||||
return identities
|
||||
}
|
||||
|
||||
// clears the patch
|
||||
func (p *PatchBuilder) Reset() {
|
||||
p.mutex.Lock()
|
||||
|
|
|
|||
52
pkg/commands/patch/patch_builder_test.go
Normal file
52
pkg/commands/patch/patch_builder_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package patch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// newTestPatchBuilder returns a PatchBuilder whose files all resolve to the given
|
||||
// diff, started for a dummy commit.
|
||||
func newTestPatchBuilder(diff string) *PatchBuilder {
|
||||
pb := NewPatchBuilder(logrus.New().WithField("test", "test"),
|
||||
func(from, to string, reverse bool, filename string, previousPath string, plain bool) (string, error) {
|
||||
return diff, nil
|
||||
})
|
||||
pb.Start("from", "to", false, true)
|
||||
return pb
|
||||
}
|
||||
|
||||
// In simpleDiff the deletion "-orange" is patch line index 6 (old-file line 2) and the
|
||||
// addition "+grape" is index 7 (new-file line 2).
|
||||
func TestPatchLineIndicesForLines(t *testing.T) {
|
||||
pb := newTestPatchBuilder(simpleDiff)
|
||||
|
||||
indices, err := pb.PatchLineIndicesForLines("filename", []LineIdentity{
|
||||
{LineNumber: 2, IsDeletion: true}, // -orange
|
||||
{LineNumber: 2, IsDeletion: false}, // +grape
|
||||
{LineNumber: 1, IsDeletion: false}, // " apple" — a context line, no change index
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int{6, 7}, indices, "context-line identity is skipped; change lines map to their indices")
|
||||
}
|
||||
|
||||
func TestIncludedLineIdentities(t *testing.T) {
|
||||
pb := newTestPatchBuilder(simpleDiff)
|
||||
|
||||
// Nothing included yet.
|
||||
assert.Empty(t, pb.IncludedLineIdentities("filename"))
|
||||
|
||||
// Include only the deletion: it comes back as its identity, the addition does not.
|
||||
assert.NoError(t, pb.AddFileLineRange("filename", "", []int{6}))
|
||||
assert.Equal(t,
|
||||
[]LineIdentity{{LineNumber: 2, IsDeletion: true}},
|
||||
pb.IncludedLineIdentities("filename"))
|
||||
|
||||
// Including the addition too yields both identities (order-independent).
|
||||
assert.NoError(t, pb.AddFileLineRange("filename", "", []int{7}))
|
||||
assert.ElementsMatch(t,
|
||||
[]LineIdentity{{LineNumber: 2, IsDeletion: true}, {LineNumber: 2, IsDeletion: false}},
|
||||
pb.IncludedLineIdentities("filename"))
|
||||
}
|
||||
Loading…
Reference in a new issue