mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
176 lines
7.3 KiB
Go
176 lines
7.3 KiB
Go
package helpers
|
|
|
|
import (
|
|
"path/filepath"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
type DiffLineHelper struct {
|
|
c *HelperCommon
|
|
|
|
// What the probe said about the diff renderer that rendererSignature names, or nil
|
|
// before it has been asked about any (see diffRendererEmitsMetadata).
|
|
rendererEmitsMetadata *bool
|
|
rendererSignature string
|
|
}
|
|
|
|
func NewDiffLineHelper(c *HelperCommon) *DiffLineHelper {
|
|
return &DiffLineHelper{c: c}
|
|
}
|
|
|
|
// GetDiffLineInfo recovers the identity — file, kind, and old/new line number —
|
|
// of the diff row at the given (wrapped) view line of the given view. It is the
|
|
// seam every consumer of a diff row goes through, so that how we recover that
|
|
// identity can change without them noticing.
|
|
//
|
|
// There are two ways. A diff renderer that speaks the OSC 1717 protocol states
|
|
// the identity of each line it renders, which is the only way to recover it from
|
|
// a rendering that doesn't look like a diff any more — columns, or +/- markers
|
|
// replaced by colour. Otherwise we parse the view's contents as a unified diff,
|
|
// which works for the renderings that keep a diff's structure (no renderer, `git
|
|
// diff --color`, a renderer that only colorizes) and fails for the rest.
|
|
//
|
|
// ok is false when the row's identity can't be recovered, in which case the
|
|
// caller must not act on the line at all.
|
|
func (self *DiffLineHelper) GetDiffLineInfo(view *gocui.View, viewLineIdx int) (types.DiffLineInfo, bool) {
|
|
identities, ok := self.diffLineIdentitiesAt(view, viewLineIdx)
|
|
if !ok {
|
|
return types.DiffLineInfo{}, false
|
|
}
|
|
return identities[0], true
|
|
}
|
|
|
|
// diffLineIdentitiesAt recovers every diff line the row at the given (wrapped) view
|
|
// line shows, left to right. It is GetDiffLineInfo's form for a reader that can't
|
|
// settle for the line the row leads with: an end of a selection covers its whole
|
|
// row, so where a rendering puts a modification's two halves side by side it covers
|
|
// both of them. ok is false when the row's identity can't be recovered at all.
|
|
func (self *DiffLineHelper) diffLineIdentitiesAt(
|
|
view *gocui.View, viewLineIdx int,
|
|
) ([]types.DiffLineInfo, bool) {
|
|
// The cursor and clicks land on a view line, which counts wrapped segments;
|
|
// the contents are indexed by unwrapped buffer line.
|
|
bufferLineIdx, ok := view.BufferLineForViewLine(viewLineIdx)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
contents := view.DiffLineContents()
|
|
if bufferLineIdx >= len(contents) {
|
|
return nil, false
|
|
}
|
|
|
|
if identities := self.diffLineIdentitiesFromRecords(contents[bufferLineIdx].Metadata); len(identities) > 0 {
|
|
return self.inRepoTerms(view, identities), true
|
|
}
|
|
|
|
parsed, ok := parseDiffLineFromBuffer(diffLineTexts(contents), bufferLineIdx)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
return self.inRepoTerms(view, []types.DiffLineInfo{self.diffLineInfo(parsed)}), true
|
|
}
|
|
|
|
// diffLineInfoFromRecords recovers a row's identity from the records the diff
|
|
// renderer stated for it. These take precedence over the buffer parse. ok is false
|
|
// when the row carries no record we understand, leaving the caller to parse.
|
|
//
|
|
// A row can carry more than one record, when the rendering puts two diff lines on it
|
|
// (a side-by-side row shows a deletion and the addition replacing it); the leftmost
|
|
// is the one a reader would call the row's own, so it is the row's identity.
|
|
func (self *DiffLineHelper) diffLineInfoFromRecords(metadata []string) (types.DiffLineInfo, bool) {
|
|
identities := self.diffLineIdentitiesFromRecords(metadata)
|
|
if len(identities) == 0 {
|
|
return types.DiffLineInfo{}, false
|
|
}
|
|
return identities[0], true
|
|
}
|
|
|
|
// diffLineIdentitiesFromRecords recovers the identity of every diff line the row's
|
|
// records state, left to right. Which of them a reader is after depends on the
|
|
// reader: the one the row leads with is the row's own identity (see
|
|
// diffLineInfoFromRecords), while a reader looking for a particular line has to
|
|
// consider them all, since which of a modification's two halves leads a row is up to
|
|
// the rendering.
|
|
func (self *DiffLineHelper) diffLineIdentitiesFromRecords(metadata []string) []types.DiffLineInfo {
|
|
identities := make([]types.DiffLineInfo, 0, len(metadata))
|
|
for _, record := range metadata {
|
|
if parsed, ok := parseDiffLineMetadata(record); ok {
|
|
identities = append(identities, self.diffLineInfo(parsed))
|
|
}
|
|
}
|
|
return identities
|
|
}
|
|
|
|
// resolvedDiffLine is one rendered row's recovered identity, plus whether it could
|
|
// be recovered at all — the element of the table resolveDiffLines produces.
|
|
type resolvedDiffLine struct {
|
|
info types.DiffLineInfo
|
|
ok bool
|
|
}
|
|
|
|
// resolveDiffLines recovers the identity of every row of a rendered diff in one
|
|
// pass, indexed 1:1 with contents. It is the batch form of GetDiffLineInfo, for the
|
|
// whole-buffer scans (which change lines are where, which file each row belongs
|
|
// to). Resolving row by row would re-run the buffer parser's whole-section parse
|
|
// once per row — O(n²) on a large single-file diff — so the buffer parser runs once
|
|
// for the whole buffer and the per-row metadata takes precedence on top.
|
|
func (self *DiffLineHelper) resolveDiffLines(contents []gocui.DiffLineContent) []resolvedDiffLine {
|
|
bufferParsed := parseAllDiffLinesFromBuffer(diffLineTexts(contents))
|
|
resolved := make([]resolvedDiffLine, len(contents))
|
|
for i, content := range contents {
|
|
if info, ok := self.diffLineInfoFromRecords(content.Metadata); ok {
|
|
resolved[i] = resolvedDiffLine{info, true}
|
|
} else if bufferParsed[i].ok {
|
|
resolved[i] = resolvedDiffLine{self.diffLineInfo(bufferParsed[i].parsed), true}
|
|
}
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
// resolveDiffLineIdentities recovers every diff line each row of a rendered diff
|
|
// shows, in one pass, indexed 1:1 with contents. It is resolveDiffLines' form for the
|
|
// readers that can't settle for the line a row leads with: looking for a remembered
|
|
// line in a new rendering has to consider both halves of a modification, since a
|
|
// side-by-side row leads with the deletion whose addition was what got remembered
|
|
// under a unified one.
|
|
func (self *DiffLineHelper) resolveDiffLineIdentities(contents []gocui.DiffLineContent) [][]types.DiffLineInfo {
|
|
bufferParsed := parseAllDiffLinesFromBuffer(diffLineTexts(contents))
|
|
identities := make([][]types.DiffLineInfo, len(contents))
|
|
for i, content := range contents {
|
|
if fromRecords := self.diffLineIdentitiesFromRecords(content.Metadata); len(fromRecords) > 0 {
|
|
identities[i] = fromRecords
|
|
} else if bufferParsed[i].ok {
|
|
identities[i] = []types.DiffLineInfo{self.diffLineInfo(bufferParsed[i].parsed)}
|
|
}
|
|
}
|
|
return identities
|
|
}
|
|
|
|
// diffLineInfo turns a parser's result into the absolute-path identity consumers
|
|
// work with. The path arrives repo-relative from the diff header, but a renderer
|
|
// states it however it likes, absolute paths included.
|
|
func (self *DiffLineHelper) diffLineInfo(parsed parsedDiffLine) types.DiffLineInfo {
|
|
return diffLineInfoIn(self.c.Git().RepoPaths.WorktreePath(), parsed)
|
|
}
|
|
|
|
// diffLineInfoIn is diffLineInfo against a given worktree, for the callers that can't
|
|
// ask which repo we are in where they run: a repo switch replaces it, so only the UI
|
|
// thread may read it.
|
|
func diffLineInfoIn(worktreePath string, parsed parsedDiffLine) types.DiffLineInfo {
|
|
path := parsed.Path
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(worktreePath, path)
|
|
}
|
|
|
|
return types.DiffLineInfo{
|
|
Path: path,
|
|
Type: parsed.Type,
|
|
NewLine: parsed.NewLine,
|
|
OldLine: parsed.OldLine,
|
|
}
|
|
}
|