mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 23:56:24 -04:00
Stage both sides of a side-by-side row from the focused main view
A side-by-side rendering (patched delta in side-by-side mode) carries more than one diff-line record per rendered row: the deletion on the left, the addition replacing it on the right. DiffLineContent.Metadata keeps only the first payload — enough to identify a single-column row, but it drops the right side — so a range staged from such a view would miss half the changes. Add a gocui accessor for all the distinct metadata payloads a row's cells carry, and have the range collector resolve every one of them, so staging a side-by-side row includes both sides. You can't stage just one side of a side-by-side row; that's an accepted restriction (switch to a single-column rendering to do it). Rows without metadata (no pager, or the buffer-parse / hyperlink backends) keep falling back to their single resolved record, so single-column staging is unchanged. The full delta-side-by-side chain can't run in the integration harness (no real pager), so it's covered by a gocui unit test for the multi-payload accessor; end-to-end behaviour needs interactive verification with the patched delta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5a0506913
commit
4a33b42d19
|
|
@ -1832,6 +1832,33 @@ func (v *View) DiffLineMetadataInLine(y int) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
// DiffLineMetadataPayloads returns, per unwrapped buffer line, the distinct
|
||||
// OSC-1717 metadata payloads carried by that line's cells, in left-to-right order.
|
||||
// A single-column rendering tags every cell of a line with the same payload (one
|
||||
// entry); a side-by-side rendering tags each side differently, so a changed row
|
||||
// yields one payload per side (and a context row, where both sides match, still
|
||||
// one). It is the multi-record counterpart of DiffLineContent.Metadata, which keeps
|
||||
// only the first payload — enough to identify a single-column row, but it drops the
|
||||
// other side of a side-by-side row. Staging a selection uses this to act on every
|
||||
// change a row covers. Taken under the write lock in one pass so the payloads stay
|
||||
// consistent with the buffer even if a concurrent re-render is rebuilding it.
|
||||
func (v *View) DiffLineMetadataPayloads() [][]string {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
result := make([][]string, len(v.buf.lines))
|
||||
for i, line := range v.buf.lines {
|
||||
var payloads []string
|
||||
for _, c := range line.cells {
|
||||
if c.metadata != "" && !slices.Contains(payloads, c.metadata) {
|
||||
payloads = append(payloads, c.metadata)
|
||||
}
|
||||
}
|
||||
result[i] = payloads
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DiffLineContent is the raw per-line material the diff-line backends parse to
|
||||
// recover a rendered row's patch-space identity (see diff-line-metadata-notes.md):
|
||||
// the decolorized text (for host-side parsing, mechanism #1), the OSC-1717
|
||||
|
|
|
|||
|
|
@ -196,6 +196,27 @@ func TestDiffLineMetadata(t *testing.T) {
|
|||
assert.Equal(t, "@@ a header line with no metadata @@", v.BufferLines()[3])
|
||||
}
|
||||
|
||||
func TestDiffLineMetadataPayloads(t *testing.T) {
|
||||
v := NewView("name", 0, 0, 80, 10, OutputNormal)
|
||||
|
||||
osc := func(payload string) string { return "\x1b]1717;" + payload + "\x1b\\" }
|
||||
v.writeString(strings.Join([]string{
|
||||
// A single-column row: one payload tags the whole line.
|
||||
osc("1;c;1;;foo.txt") + "context",
|
||||
// A side-by-side change row: the deletion tags the left half and the
|
||||
// addition replacing it tags the right half of the same rendered line.
|
||||
osc("1;d;2;2;foo.txt") + "old2 " + osc("1;a;2;;foo.txt") + "new2",
|
||||
// A header line with no metadata.
|
||||
"@@ header @@",
|
||||
}, "\n"))
|
||||
|
||||
assert.Equal(t, [][]string{
|
||||
{"1;c;1;;foo.txt"},
|
||||
{"1;d;2;2;foo.txt", "1;a;2;;foo.txt"},
|
||||
nil,
|
||||
}, v.DiffLineMetadataPayloads())
|
||||
}
|
||||
|
||||
// When a re-render produces fewer view lines than the previous one,
|
||||
// refreshViewLinesIfNeeded must truncate viewLines to the new content. If it
|
||||
// didn't (it used to overwrite in place and keep the tail), a reader could map a
|
||||
|
|
|
|||
|
|
@ -170,6 +170,12 @@ func (self *StagingHelper) GetDiffLineInfo(windowName string, viewLineIdx int) (
|
|||
// are skipped (Transform emits context regardless of the included set, so only
|
||||
// change lines need collecting — see §21.3), and view lines that wrap to the same
|
||||
// buffer line are de-duplicated.
|
||||
//
|
||||
// A side-by-side rendering carries more than one record per row — a deletion on the
|
||||
// left, the addition replacing it on the right — and staging includes both (you
|
||||
// can't stage one side of a side-by-side row; accepted restriction). So each row's
|
||||
// metadata payloads are all resolved; rows without metadata (no pager, or the
|
||||
// buffer-parse / hyperlink backends) fall back to their single resolved record.
|
||||
func (self *StagingHelper) ChangeLinesInViewRange(windowName string, first int, last int) []types.DiffLineInfo {
|
||||
v, _ := self.c.GocuiGui().View(self.windowHelper.GetViewNameForWindow(windowName))
|
||||
if v == nil {
|
||||
|
|
@ -177,6 +183,7 @@ func (self *StagingHelper) ChangeLinesInViewRange(windowName string, first int,
|
|||
}
|
||||
|
||||
resolved := self.resolveDiffLines(v.DiffLineContents())
|
||||
payloadsByLine := v.DiffLineMetadataPayloads()
|
||||
var infos []types.DiffLineInfo
|
||||
lastBufferLine := -1
|
||||
for viewLine := first; viewLine <= last; viewLine++ {
|
||||
|
|
@ -185,7 +192,14 @@ func (self *StagingHelper) ChangeLinesInViewRange(windowName string, first int,
|
|||
continue
|
||||
}
|
||||
lastBufferLine = bufferLine
|
||||
if bufferLine < len(resolved) && resolved[bufferLine].ok && resolved[bufferLine].info.IsChange() {
|
||||
|
||||
if bufferLine < len(payloadsByLine) && len(payloadsByLine[bufferLine]) > 0 {
|
||||
for _, payload := range payloadsByLine[bufferLine] {
|
||||
if info, ok := self.diffLineInfoFromMetadata(payload); ok && info.IsChange() {
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
} else if bufferLine < len(resolved) && resolved[bufferLine].ok && resolved[bufferLine].info.IsChange() {
|
||||
infos = append(infos, resolved[bufferLine].info)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue