mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-12 11:15:00 +02:00
170 lines
4.2 KiB
Go
170 lines
4.2 KiB
Go
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
|
|
|
|
isFocused bool
|
|
// first line index for selected cursor range
|
|
firstLineIndex int
|
|
// last line index for selected cursor range
|
|
lastLineIndex int
|
|
// 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 {
|
|
IsFocused bool
|
|
// first line index for selected cursor range
|
|
FirstLineIndex int
|
|
// last line index for selected cursor range
|
|
LastLineIndex int
|
|
// 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,
|
|
isFocused: opts.IsFocused,
|
|
firstLineIndex: opts.FirstLineIndex,
|
|
lastLineIndex: opts.LastLineIndex,
|
|
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,
|
|
lineIdx,
|
|
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, index, 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, index int, included bool) string {
|
|
if self.plain {
|
|
return str
|
|
}
|
|
|
|
selected := self.isFocused && index >= self.firstLineIndex && index <= self.lastLineIndex
|
|
|
|
if selected {
|
|
textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor)
|
|
}
|
|
|
|
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:])
|
|
}
|