diff --git a/internal/exp/diffview/Taskfile.yaml b/internal/exp/diffview/Taskfile.yaml index 6dc8d477471b87498eb5365389bdf8f02458f406..422dbe2d3ac85be34c35f81763f7a30b154140f1 100644 --- a/internal/exp/diffview/Taskfile.yaml +++ b/internal/exp/diffview/Taskfile.yaml @@ -80,3 +80,23 @@ tasks: - for: sources cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}} silent: true + + test:print:yoffset:unified: + desc: Print golden files for debugging + method: none + sources: + - ./testdata/TestDiffViewYOffset/Unified/*.golden + cmds: + - for: sources + cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}} + silent: true + + test:print:yoffset:split: + desc: Print golden files for debugging + method: none + sources: + - ./testdata/TestDiffViewYOffset/Split/*.golden + cmds: + - for: sources + cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}} + silent: true diff --git a/internal/exp/diffview/diffview.go b/internal/exp/diffview/diffview.go index b5111692cdbda89b87663f412afcd82aba1dcf4f..871949fcf1d972def5b54a3c4112b0d6fad146bb 100644 --- a/internal/exp/diffview/diffview.go +++ b/internal/exp/diffview/diffview.go @@ -40,6 +40,7 @@ type DiffView struct { height int width int xOffset int + yOffset int style Style isComputed bool @@ -137,6 +138,12 @@ func (dv *DiffView) XOffset(xOffset int) *DiffView { return dv } +// YOffset sets the vertical offset for the DiffView. +func (dv *DiffView) YOffset(yOffset int) *DiffView { + dv.yOffset = yOffset + return dv +} + // String returns the string representation of the DiffView. func (dv *DiffView) String() string { if err := dv.computeDiff(); err != nil { @@ -296,17 +303,23 @@ func (dv *DiffView) renderUnified() string { var b strings.Builder fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth) - printedLines := 0 + printedLines := -dv.yOffset + + write := func(s string) { + if printedLines >= 0 { + b.WriteString(s) + } + } outer: for i, h := range dv.unified.Hunks { if dv.lineNumbers { - b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) - b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits))) + write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) + write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits))) } content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…") - b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content)) - b.WriteRune('\n') + write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content)) + write("\n") printedLines++ beforeLine := h.FromLine @@ -319,13 +332,13 @@ outer: isLastLine := j+1 == len(h.Lines) if hasReachedHeight && (!isLastHunk || !isLastLine) { if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits))) } - b.WriteString(fullContentStyle.Render( + write(fullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"), )) - b.WriteRune('\n') + write("\n") break outer } @@ -338,36 +351,36 @@ outer: switch l.Kind { case udiff.Equal: if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) } - b.WriteString(fullContentStyle.Render( + write(fullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content), )) beforeLine++ afterLine++ case udiff.Insert: if dv.lineNumbers { - b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) - b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) + write(dv.style.InsertLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) + write(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) } - b.WriteString(fullContentStyle.Render( + write(fullContentStyle.Render( dv.style.InsertLine.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) + dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content), )) afterLine++ case udiff.Delete: if dv.lineNumbers { - b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) - b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) + write(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) + write(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) } - b.WriteString(fullContentStyle.Render( + write(fullContentStyle.Render( dv.style.DeleteLine.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) + dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content), )) beforeLine++ } - b.WriteRune('\n') + write("\n") printedLines++ } @@ -375,11 +388,11 @@ outer: for printedLines < dv.height { if dv.lineNumbers { - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) } - b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" ")) - b.WriteRune('\n') + write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" ")) + write("\n") printedLines++ } @@ -392,20 +405,26 @@ func (dv *DiffView) renderSplit() string { beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth) afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter)) - printedLines := 0 + printedLines := -dv.yOffset + + write := func(s string) { + if printedLines >= 0 { + b.WriteString(s) + } + } outer: for i, h := range dv.splitHunks { if dv.lineNumbers { - b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) + write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) } content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…") - b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content)) + write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content)) if dv.lineNumbers { - b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits))) + write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits))) } - b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" ")) - b.WriteRune('\n') + write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" ")) + write("\n") printedLines++ beforeLine := h.fromLine @@ -418,18 +437,18 @@ outer: isLastLine := j+1 == len(h.lines) if hasReachedHeight && (!isLastHunk || !isLastLine) { if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) } - b.WriteString(beforeFullContentStyle.Render( + write(beforeFullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"), )) if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits))) } - b.WriteString(afterFullContentStyle.Render( + write(afterFullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"), )) - b.WriteRune('\n') + write("\n") break outer } @@ -452,24 +471,24 @@ outer: switch { case l.before == nil: if dv.lineNumbers { - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits))) } - b.WriteString(beforeFullContentStyle.Render( + write(beforeFullContentStyle.Render( dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "), )) case l.before.Kind == udiff.Equal: if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) } - b.WriteString(beforeFullContentStyle.Render( + write(beforeFullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingBeforeEllipsis, " …", " ") + beforeContent), )) beforeLine++ case l.before.Kind == udiff.Delete: if dv.lineNumbers { - b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) + write(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits))) } - b.WriteString(beforeFullContentStyle.Render( + write(beforeFullContentStyle.Render( dv.style.DeleteLine.Symbol.Render(ternary(leadingBeforeEllipsis, "-…", "- ")) + dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent), )) @@ -479,31 +498,31 @@ outer: switch { case l.after == nil: if dv.lineNumbers { - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits))) } - b.WriteString(afterFullContentStyle.Render( + write(afterFullContentStyle.Render( dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "), )) case l.after.Kind == udiff.Equal: if dv.lineNumbers { - b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) + write(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) } - b.WriteString(afterFullContentStyle.Render( + write(afterFullContentStyle.Render( dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingAfterEllipsis, " …", " ") + afterContent), )) afterLine++ case l.after.Kind == udiff.Insert: if dv.lineNumbers { - b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) + write(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits))) } - b.WriteString(afterFullContentStyle.Render( + write(afterFullContentStyle.Render( dv.style.InsertLine.Symbol.Render(ternary(leadingAfterEllipsis, "+…", "+ ")) + dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent), )) afterLine++ } - b.WriteRune('\n') + write("\n") printedLines++ } @@ -511,14 +530,14 @@ outer: for printedLines < dv.height { if dv.lineNumbers { - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad("…", dv.beforeNumDigits))) } - b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" ")) + write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" ")) if dv.lineNumbers { - b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.afterNumDigits))) + write(dv.style.MissingLine.LineNumber.Render(pad("…", dv.afterNumDigits))) } - b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" ")) - b.WriteRune('\n') + write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" ")) + write("\n") printedLines++ } diff --git a/internal/exp/diffview/diffview_test.go b/internal/exp/diffview/diffview_test.go index 6d68a46d197630fecf01204841aa83e7e87e39df..d9f55892024ef4a9da00b0f5586e3f50135e27d9 100644 --- a/internal/exp/diffview/diffview_test.go +++ b/internal/exp/diffview/diffview_test.go @@ -216,6 +216,29 @@ func TestDiffViewXOffset(t *testing.T) { } } +func TestDiffViewYOffset(t *testing.T) { + for layoutName, layoutFunc := range LayoutFuncs { + t.Run(layoutName, func(t *testing.T) { + for yOffset := range 17 { + t.Run(fmt.Sprintf("YOffsetOf%02d", yOffset), func(t *testing.T) { + dv := diffview.New(). + Before("main.go", TestMultipleHunksBefore). + After("main.go", TestMultipleHunksAfter). + Style(diffview.DefaultLightStyle). + Height(5). + YOffset(yOffset) + dv = layoutFunc(dv) + + output := dv.String() + golden.RequireEqual(t, []byte(output)) + + assertHeight(t, 5, output) + }) + } + }) + } +} + func assertLineWidth(t *testing.T, expected int, output string) { var lineWidth int for line := range strings.SplitSeq(output, "\n") {