feat(diffview): implement ability to set horizontal offset

Andrey Nering created

Change summary

internal/exp/diffview/Taskfile.yaml    | 20 +++++++++++++++++++
internal/exp/diffview/diffview.go      | 29 +++++++++++++++++++++------
internal/exp/diffview/diffview_test.go | 23 ++++++++++++++++++++++
internal/exp/diffview/util.go          |  7 ++++++
4 files changed, 72 insertions(+), 7 deletions(-)

Detailed changes

internal/exp/diffview/Taskfile.yaml 🔗

@@ -60,3 +60,23 @@ tasks:
       - for: sources
         cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
     silent: true
+
+  test:print:xoffset:unified:
+    desc: Print golden files for debugging
+    method: none
+    sources:
+      - ./testdata/TestDiffViewXOffset/Unified/*.golden
+    cmds:
+      - for: sources
+        cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
+    silent: true
+
+  test:print:xoffset:split:
+    desc: Print golden files for debugging
+    method: none
+    sources:
+      - ./testdata/TestDiffViewXOffset/Split/*.golden
+    cmds:
+      - for: sources
+        cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
+    silent: true

internal/exp/diffview/diffview.go 🔗

@@ -39,6 +39,7 @@ type DiffView struct {
 	highlight    bool
 	height       int
 	width        int
+	xOffset      int
 	style        Style
 
 	isComputed bool
@@ -130,6 +131,12 @@ func (dv *DiffView) Width(width int) *DiffView {
 	return dv
 }
 
+// XOffset sets the horizontal offset for the DiffView.
+func (dv *DiffView) XOffset(xOffset int) *DiffView {
+	dv.xOffset = xOffset
+	return dv
+}
+
 // String returns the string representation of the DiffView.
 func (dv *DiffView) String() string {
 	if err := dv.computeDiff(); err != nil {
@@ -323,8 +330,11 @@ outer:
 			}
 
 			content := strings.TrimSuffix(l.Content, "\n")
+			content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
 			content = ansi.Truncate(content, dv.codeWidth, "…")
 
+			leadingEllipsis := dv.xOffset > 0 && strings.TrimSpace(content) != ""
+
 			switch l.Kind {
 			case udiff.Equal:
 				if dv.lineNumbers {
@@ -332,7 +342,7 @@ outer:
 					b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
 				}
 				b.WriteString(fullContentStyle.Render(
-					dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render("  " + content),
+					dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", "  ") + content),
 				))
 				beforeLine++
 				afterLine++
@@ -342,7 +352,7 @@ outer:
 					b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
 				}
 				b.WriteString(fullContentStyle.Render(
-					dv.style.InsertLine.Symbol.Render("+ ") +
+					dv.style.InsertLine.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
 						dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content),
 				))
 				afterLine++
@@ -352,7 +362,7 @@ outer:
 					b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
 				}
 				b.WriteString(fullContentStyle.Render(
-					dv.style.DeleteLine.Symbol.Render("- ") +
+					dv.style.DeleteLine.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
 						dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content),
 				))
 				beforeLine++
@@ -427,13 +437,18 @@ outer:
 			var afterContent string
 			if l.before != nil {
 				beforeContent = strings.TrimSuffix(l.before.Content, "\n")
+				beforeContent = ansi.GraphemeWidth.Cut(beforeContent, dv.xOffset, len(beforeContent))
 				beforeContent = ansi.Truncate(beforeContent, dv.codeWidth, "…")
 			}
 			if l.after != nil {
 				afterContent = strings.TrimSuffix(l.after.Content, "\n")
+				afterContent = ansi.GraphemeWidth.Cut(afterContent, dv.xOffset, len(afterContent))
 				afterContent = ansi.Truncate(afterContent, dv.codeWidth+btoi(dv.extraColOnAfter), "…")
 			}
 
+			leadingBeforeEllipsis := dv.xOffset > 0 && strings.TrimSpace(beforeContent) != ""
+			leadingAfterEllipsis := dv.xOffset > 0 && strings.TrimSpace(afterContent) != ""
+
 			switch {
 			case l.before == nil:
 				if dv.lineNumbers {
@@ -447,7 +462,7 @@ outer:
 					b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
 				}
 				b.WriteString(beforeFullContentStyle.Render(
-					dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render("  " + beforeContent),
+					dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingBeforeEllipsis, " …", "  ") + beforeContent),
 				))
 				beforeLine++
 			case l.before.Kind == udiff.Delete:
@@ -455,7 +470,7 @@ outer:
 					b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
 				}
 				b.WriteString(beforeFullContentStyle.Render(
-					dv.style.DeleteLine.Symbol.Render("- ") +
+					dv.style.DeleteLine.Symbol.Render(ternary(leadingBeforeEllipsis, "-…", "- ")) +
 						dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent),
 				))
 				beforeLine++
@@ -474,7 +489,7 @@ outer:
 					b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
 				}
 				b.WriteString(afterFullContentStyle.Render(
-					dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render("  " + afterContent),
+					dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingAfterEllipsis, " …", "  ") + afterContent),
 				))
 				afterLine++
 			case l.after.Kind == udiff.Insert:
@@ -482,7 +497,7 @@ outer:
 					b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
 				}
 				b.WriteString(afterFullContentStyle.Render(
-					dv.style.InsertLine.Symbol.Render("+ ") +
+					dv.style.InsertLine.Symbol.Render(ternary(leadingAfterEllipsis, "+…", "+ ")) +
 						dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent),
 				))
 				afterLine++

internal/exp/diffview/diffview_test.go 🔗

@@ -193,6 +193,29 @@ func TestDiffViewHeight(t *testing.T) {
 	}
 }
 
+func TestDiffViewXOffset(t *testing.T) {
+	for layoutName, layoutFunc := range LayoutFuncs {
+		t.Run(layoutName, func(t *testing.T) {
+			for xOffset := range 21 {
+				t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
+					dv := diffview.New().
+						Before("main.go", TestDefaultBefore).
+						After("main.go", TestDefaultAfter).
+						Style(diffview.DefaultLightStyle).
+						Width(60).
+						XOffset(xOffset)
+					dv = layoutFunc(dv)
+
+					output := dv.String()
+					golden.RequireEqual(t, []byte(output))
+
+					assertLineWidth(t, 60, output)
+				})
+			}
+		})
+	}
+}
+
 func assertLineWidth(t *testing.T, expected int, output string) {
 	var lineWidth int
 	for line := range strings.SplitSeq(output, "\n") {

internal/exp/diffview/util.go 🔗

@@ -30,3 +30,10 @@ func btoi(b bool) int {
 	}
 	return 0
 }
+
+func ternary[T any](cond bool, t, f T) T {
+	if cond {
+		return t
+	}
+	return f
+}