mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-19 17:02:18 +03:00
This was already possible, but only when a file was selected, and it woudln't always land on the right line when a pager was used. Now it's also possible to do this for directories, and it jumps to the right line. At the moment this is a hack that relies on delta's hyperlinks, so it only works on lines that have hyperlinks (added and context). The implementation is very hacky for other reasons too (e.g. the addition of the weirdly named ClickedViewRealLineIdx to OnFocusOpts).
209 lines
5.4 KiB
Go
209 lines
5.4 KiB
Go
package patch
|
|
|
|
import (
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type Patch struct {
|
|
// header of the patch (split on newlines) e.g.
|
|
// diff --git a/filename b/filename
|
|
// index dcd3485..1ba5540 100644
|
|
// --- a/filename
|
|
// +++ b/filename
|
|
header []string
|
|
// hunks of the patch
|
|
hunks []*Hunk
|
|
}
|
|
|
|
// Returns a new patch with the specified transformation applied (e.g.
|
|
// only selecting a subset of changes).
|
|
// Leaves the original patch unchanged.
|
|
func (self *Patch) Transform(opts TransformOpts) *Patch {
|
|
return transform(self, opts)
|
|
}
|
|
|
|
// Returns the patch as a plain string
|
|
func (self *Patch) FormatPlain() string {
|
|
return formatPlain(self)
|
|
}
|
|
|
|
// Returns a range of lines from the patch as a plain string (range is inclusive)
|
|
func (self *Patch) FormatRangePlain(startIdx int, endIdx int) string {
|
|
return formatRangePlain(self, startIdx, endIdx)
|
|
}
|
|
|
|
// Returns the patch as a string with ANSI color codes for displaying in a view
|
|
func (self *Patch) FormatView(opts FormatViewOpts) string {
|
|
return formatView(self, opts)
|
|
}
|
|
|
|
// Returns the lines of the patch
|
|
func (self *Patch) Lines() []*PatchLine {
|
|
lines := []*PatchLine{}
|
|
for _, line := range self.header {
|
|
lines = append(lines, &PatchLine{Content: line, Kind: PATCH_HEADER})
|
|
}
|
|
|
|
for _, hunk := range self.hunks {
|
|
lines = append(lines, hunk.allLines()...)
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
// Returns the patch line index of the first line in the given hunk
|
|
func (self *Patch) HunkStartIdx(hunkIndex int) int {
|
|
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
|
|
|
result := len(self.header)
|
|
for i := 0; i < hunkIndex; i++ {
|
|
result += self.hunks[i].lineCount()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Returns the patch line index of the last line in the given hunk
|
|
func (self *Patch) HunkEndIdx(hunkIndex int) int {
|
|
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
|
|
|
return self.HunkStartIdx(hunkIndex) + self.hunks[hunkIndex].lineCount() - 1
|
|
}
|
|
|
|
func (self *Patch) ContainsChanges() bool {
|
|
return lo.SomeBy(self.hunks, func(hunk *Hunk) bool {
|
|
return hunk.containsChanges()
|
|
})
|
|
}
|
|
|
|
// Takes a line index in the patch and returns the line number in the new file.
|
|
// If the line is a header line, returns 1.
|
|
// If the line is a hunk header line, returns the first file line number in that hunk.
|
|
// If the line is out of range below, returns the last file line number in the last hunk.
|
|
func (self *Patch) LineNumberOfLine(idx int) int {
|
|
if idx < len(self.header) || len(self.hunks) == 0 {
|
|
return 1
|
|
}
|
|
|
|
hunkIdx := self.HunkContainingLine(idx)
|
|
// cursor out of range, return last file line number
|
|
if hunkIdx == -1 {
|
|
lastHunk := self.hunks[len(self.hunks)-1]
|
|
return lastHunk.newStart + lastHunk.newLength() - 1
|
|
}
|
|
|
|
hunk := self.hunks[hunkIdx]
|
|
hunkStartIdx := self.HunkStartIdx(hunkIdx)
|
|
idxInHunk := idx - hunkStartIdx
|
|
|
|
if idxInHunk == 0 {
|
|
return hunk.newStart
|
|
}
|
|
|
|
lines := hunk.bodyLines[:idxInHunk-1]
|
|
offset := nLinesWithKind(lines, []PatchLineKind{ADDITION, CONTEXT})
|
|
return hunk.newStart + offset
|
|
}
|
|
|
|
// Takes a line number in the new file and returns the line index in the patch.
|
|
// This is the opposite of LineNumberOfLine.
|
|
// If the line number is not contained in any of the hunks, it returns the
|
|
// closest position.
|
|
func (self *Patch) PatchLineForLineNumber(lineNumber int) int {
|
|
if len(self.hunks) == 0 {
|
|
return len(self.header)
|
|
}
|
|
|
|
for hunkIdx, hunk := range self.hunks {
|
|
if lineNumber <= hunk.newStart {
|
|
return self.HunkStartIdx(hunkIdx)
|
|
}
|
|
|
|
if lineNumber < hunk.newStart+hunk.newLength() {
|
|
lines := hunk.bodyLines
|
|
offset := lineNumber - hunk.newStart
|
|
for i, line := range lines {
|
|
if offset == 0 {
|
|
return self.HunkStartIdx(hunkIdx) + i + 1
|
|
}
|
|
|
|
if line.Kind == ADDITION || line.Kind == CONTEXT {
|
|
offset--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return self.LineCount() - 1
|
|
}
|
|
|
|
// 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 {
|
|
hunkStartIdx := self.HunkStartIdx(hunkIdx)
|
|
if idx >= hunkStartIdx && idx < hunkStartIdx+hunk.lineCount() {
|
|
return hunkIdx
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Returns the patch line index of the next change (i.e. addition or deletion).
|
|
func (self *Patch) GetNextChangeIdx(idx int) int {
|
|
idx = lo.Clamp(idx, 0, self.LineCount()-1)
|
|
|
|
lines := self.Lines()
|
|
|
|
for i, line := range lines[idx:] {
|
|
if line.isChange() {
|
|
return i + idx
|
|
}
|
|
}
|
|
|
|
// there are no changes from the cursor onwards so we'll instead
|
|
// return the index of the last change
|
|
for i := len(lines) - 1; i >= 0; i-- {
|
|
line := lines[i]
|
|
if line.isChange() {
|
|
return i
|
|
}
|
|
}
|
|
|
|
// should not be possible
|
|
return 0
|
|
}
|
|
|
|
// Returns the length of the patch in lines
|
|
func (self *Patch) LineCount() int {
|
|
count := len(self.header)
|
|
for _, hunk := range self.hunks {
|
|
count += hunk.lineCount()
|
|
}
|
|
return count
|
|
}
|
|
|
|
// Returns the number of hunks of the patch
|
|
func (self *Patch) HunkCount() int {
|
|
return len(self.hunks)
|
|
}
|
|
|
|
// Adjust the given line number (one-based) according to the current patch. The
|
|
// patch is supposed to be a diff of an old file state against the working
|
|
// directory; the line number is a line number in that old file, and the
|
|
// function returns the corresponding line number in the working directory file.
|
|
func (self *Patch) AdjustLineNumber(lineNumber int) int {
|
|
adjustedLineNumber := lineNumber
|
|
for _, hunk := range self.hunks {
|
|
if hunk.oldStart >= lineNumber {
|
|
break
|
|
}
|
|
|
|
if hunk.oldStart+hunk.oldLength() > lineNumber {
|
|
return hunk.newStart
|
|
}
|
|
|
|
adjustedLineNumber += hunk.newLength() - hunk.oldLength()
|
|
}
|
|
|
|
return adjustedLineNumber
|
|
}
|