diff options
Diffstat (limited to 'pkg/commands/patch/format.go')
-rw-r--r-- | pkg/commands/patch/format.go | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/pkg/commands/patch/format.go b/pkg/commands/patch/format.go new file mode 100644 index 000000000..d04b6bec1 --- /dev/null +++ b/pkg/commands/patch/format.go @@ -0,0 +1,169 @@ +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:]) +} |