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
|
package patch
import (
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/samber/lo"
)
type patchPresenter struct {
patch *Patch
// if true, all following fields are ignored
plain bool
// line indices for tagged lines (e.g. lines added to a custom patch)
incLineIndices *set.Set[int]
}
// formats the patch as a plain string
func formatPlain(patch *Patch) string {
presenter := &patchPresenter{
patch: patch,
plain: true,
incLineIndices: set.New[int](),
}
return presenter.format()
}
func formatRangePlain(patch *Patch, startIdx int, endIdx int) string {
lines := patch.Lines()[startIdx : endIdx+1]
return strings.Join(
lo.Map(lines, func(line *PatchLine, _ int) string {
return line.Content + "\n"
}),
"",
)
}
type FormatViewOpts struct {
// line indices for tagged lines (e.g. lines added to a custom patch)
IncLineIndices *set.Set[int]
}
// formats the patch for rendering within a view, meaning it's coloured and
// highlights selected items
func formatView(patch *Patch, opts FormatViewOpts) string {
includedLineIndices := opts.IncLineIndices
if includedLineIndices == nil {
includedLineIndices = set.New[int]()
}
presenter := &patchPresenter{
patch: patch,
plain: false,
incLineIndices: includedLineIndices,
}
return presenter.format()
}
func (self *patchPresenter) format() string {
// if we have no changes in our patch (i.e. no additions or deletions) then
// the patch is effectively empty and we can return an empty string
if !self.patch.ContainsChanges() {
return ""
}
stringBuilder := &strings.Builder{}
lineIdx := 0
appendLine := func(line string) {
_, _ = stringBuilder.WriteString(line + "\n")
lineIdx++
}
appendFormattedLine := func(line string, style style.TextStyle) {
formattedLine := self.formatLine(
line,
style,
lineIdx,
)
appendLine(formattedLine)
}
for _, line := range self.patch.header {
appendFormattedLine(line, theme.DefaultTextColor.SetBold())
}
for _, hunk := range self.patch.hunks {
appendLine(
self.formatLine(
hunk.formatHeaderStart(),
style.FgCyan,
lineIdx,
) +
// we're splitting the line into two parts: the diff header and the context
// We explicitly pass 'included' as false here so that we're only tagging the
// first half of the line as included if the line is indeed included.
self.formatLineAux(
hunk.headerContext,
theme.DefaultTextColor,
false,
),
)
for _, line := range hunk.bodyLines {
appendFormattedLine(line.Content, self.patchLineStyle(line))
}
}
return stringBuilder.String()
}
func (self *patchPresenter) patchLineStyle(patchLine *PatchLine) style.TextStyle {
switch patchLine.Kind {
case ADDITION:
return style.FgGreen
case DELETION:
return style.FgRed
default:
return theme.DefaultTextColor
}
}
func (self *patchPresenter) formatLine(str string, textStyle style.TextStyle, index int) string {
included := self.incLineIndices.Includes(index)
return self.formatLineAux(str, textStyle, included)
}
// 'selected' means you've got it highlighted with your cursor
// 'included' means the line has been included in the patch (only applicable when
// building a patch)
func (self *patchPresenter) formatLineAux(str string, textStyle style.TextStyle, included bool) string {
if self.plain {
return str
}
firstCharStyle := textStyle
if included {
firstCharStyle = firstCharStyle.MergeStyle(style.BgGreen)
}
if len(str) < 2 {
return firstCharStyle.Sprint(str)
}
return firstCharStyle.Sprint(str[:1]) + textStyle.Sprint(str[1:])
}
|