summaryrefslogtreecommitdiffstats
path: root/pkg/commands/patch/patch.go
blob: 5275fb613be420aa70a7b9da718694e6153d53bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package patch

import (
	"github.com/jesseduffield/lazygit/pkg/utils"
	"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 = utils.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 = utils.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.oldStart
	}

	lines := hunk.bodyLines[:idxInHunk-1]
	offset := nLinesWithKind(lines, []PatchLineKind{ADDITION, CONTEXT})
	return hunk.oldStart + offset
}

// 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 = utils.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
}