2020-08-15 03:18:40 +02:00
|
|
|
package patch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2022-03-19 03:26:30 +02:00
|
|
|
"github.com/samber/lo"
|
2020-08-15 03:18:40 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type PatchHunk struct {
|
|
|
|
FirstLineIdx int
|
|
|
|
oldStart int
|
|
|
|
newStart int
|
|
|
|
heading string
|
|
|
|
bodyLines []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hunk *PatchHunk) LastLineIdx() int {
|
|
|
|
return hunk.FirstLineIdx + len(hunk.bodyLines)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newHunk(lines []string, firstLineIdx int) *PatchHunk {
|
|
|
|
header := lines[0]
|
|
|
|
bodyLines := lines[1:]
|
|
|
|
|
|
|
|
oldStart, newStart, heading := headerInfo(header)
|
|
|
|
|
|
|
|
return &PatchHunk{
|
|
|
|
oldStart: oldStart,
|
|
|
|
newStart: newStart,
|
|
|
|
heading: heading,
|
|
|
|
FirstLineIdx: firstLineIdx,
|
|
|
|
bodyLines: bodyLines,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func headerInfo(header string) (int, int, string) {
|
|
|
|
match := hunkHeaderRegexp.FindStringSubmatch(header)
|
|
|
|
|
|
|
|
oldStart := utils.MustConvertToInt(match[1])
|
|
|
|
newStart := utils.MustConvertToInt(match[2])
|
|
|
|
heading := match[3]
|
|
|
|
|
|
|
|
return oldStart, newStart, heading
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
|
|
|
|
skippedNewlineMessageIndex := -1
|
|
|
|
newLines := []string{}
|
|
|
|
|
|
|
|
lineIdx := hunk.FirstLineIdx
|
|
|
|
for _, line := range hunk.bodyLines {
|
|
|
|
lineIdx++ // incrementing at the start to skip the header line
|
|
|
|
if line == "" {
|
|
|
|
break
|
|
|
|
}
|
2022-03-19 03:26:30 +02:00
|
|
|
isLineSelected := lo.Contains(lineIndices, lineIdx)
|
2020-08-15 03:18:40 +02:00
|
|
|
|
|
|
|
firstChar, content := line[:1], line[1:]
|
|
|
|
transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineSelected)
|
|
|
|
|
|
|
|
if isLineSelected || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
|
|
|
|
newLines = append(newLines, transformedFirstChar+content)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if transformedFirstChar == "+" {
|
|
|
|
// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
|
|
|
|
skippedNewlineMessageIndex = lineIdx + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newLines
|
|
|
|
}
|
|
|
|
|
|
|
|
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
|
|
|
|
if reverse {
|
|
|
|
if !isLineSelected && firstChar == "+" {
|
|
|
|
return " "
|
|
|
|
} else if firstChar == "-" {
|
|
|
|
return "+"
|
|
|
|
} else if firstChar == "+" {
|
|
|
|
return "-"
|
|
|
|
} else {
|
|
|
|
return firstChar
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isLineSelected && firstChar == "-" {
|
|
|
|
return " "
|
|
|
|
}
|
|
|
|
|
|
|
|
return firstChar
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
|
|
|
|
return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
|
|
|
|
bodyLines := hunk.updatedLines(lineIndices, reverse)
|
|
|
|
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
|
|
|
|
if !ok {
|
|
|
|
return startOffset, ""
|
|
|
|
}
|
|
|
|
return startOffset, header + strings.Join(bodyLines, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
|
|
|
|
changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
|
|
|
|
oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
|
|
|
|
newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})
|
|
|
|
|
|
|
|
if changeCount == 0 {
|
|
|
|
// if nothing has changed we just return nothing
|
|
|
|
return startOffset, "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
var oldStart int
|
|
|
|
if reverse {
|
|
|
|
oldStart = hunk.newStart
|
|
|
|
} else {
|
|
|
|
oldStart = hunk.oldStart
|
|
|
|
}
|
|
|
|
|
|
|
|
var newStartOffset int
|
|
|
|
// if the hunk went from zero to positive length, we need to increment the starting point by one
|
|
|
|
// if the hunk went from positive to zero length, we need to decrement the starting point by one
|
|
|
|
if oldLength == 0 {
|
|
|
|
newStartOffset = 1
|
|
|
|
} else if newLength == 0 {
|
|
|
|
newStartOffset = -1
|
|
|
|
} else {
|
|
|
|
newStartOffset = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
newStart := oldStart + startOffset + newStartOffset
|
|
|
|
|
|
|
|
newStartOffset = startOffset + newLength - oldLength
|
|
|
|
formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, hunk.heading)
|
|
|
|
return newStartOffset, formattedHeader, true
|
|
|
|
}
|