fix(diffview): add fixes + more extensive testing for height handling

Andrey Nering created

Change summary

internal/exp/diffview/Taskfile.yaml    | 20 ++++++++++++++++
internal/exp/diffview/diffview.go      | 34 ++++++++++++++++++++++++++++
internal/exp/diffview/diffview_test.go | 34 ++++++++++++++++++++++++++++
3 files changed, 88 insertions(+)

Detailed changes

internal/exp/diffview/Taskfile.yaml 🔗

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

internal/exp/diffview/diffview.go 🔗

@@ -149,6 +149,9 @@ func (dv *DiffView) String() string {
 	if dv.width > 0 {
 		style = style.MaxWidth(dv.width)
 	}
+	if dv.height > 0 {
+		style = style.MaxHeight(dv.height)
+	}
 
 	switch dv.layout {
 	case layoutUnified:
@@ -287,6 +290,7 @@ func (dv *DiffView) renderUnified() string {
 	var b strings.Builder
 
 	fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
+	printedLines := 0
 
 	for _, h := range dv.unified.Hunks {
 		if dv.lineNumbers {
@@ -296,6 +300,7 @@ func (dv *DiffView) renderUnified() string {
 		content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
 		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
 		b.WriteRune('\n')
+		printedLines++
 
 		beforeLine := h.FromLine
 		afterLine := h.ToLine
@@ -337,7 +342,19 @@ func (dv *DiffView) renderUnified() string {
 				beforeLine++
 			}
 			b.WriteRune('\n')
+
+			printedLines++
+		}
+	}
+
+	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)))
 		}
+		b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render("  "))
+		b.WriteRune('\n')
+		printedLines++
 	}
 
 	return b.String()
@@ -349,6 +366,7 @@ func (dv *DiffView) renderSplit() string {
 
 	beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
 	afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
+	printedLines := 0
 
 	for i, h := range dv.splitHunks {
 		if dv.lineNumbers {
@@ -361,6 +379,7 @@ func (dv *DiffView) renderSplit() string {
 		}
 		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
 		b.WriteRune('\n')
+		printedLines++
 
 		beforeLine := h.fromLine
 		afterLine := h.toLine
@@ -432,9 +451,24 @@ func (dv *DiffView) renderSplit() string {
 			}
 
 			b.WriteRune('\n')
+
+			printedLines++
 		}
 	}
 
+	for printedLines < dv.height {
+		if dv.lineNumbers {
+			b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
+		}
+		b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "))
+		if dv.lineNumbers {
+			b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
+		}
+		b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
+		b.WriteRune('\n')
+		printedLines++
+	}
+
 	return b.String()
 }
 

internal/exp/diffview/diffview_test.go 🔗

@@ -167,6 +167,32 @@ func TestDiffViewWidth(t *testing.T) {
 	}
 }
 
+func TestDiffViewHeight(t *testing.T) {
+	for layoutName, layoutFunc := range LayoutFuncs {
+		t.Run(layoutName, func(t *testing.T) {
+			for themeName, themeFunc := range ThemeFuncs {
+				t.Run(themeName, func(t *testing.T) {
+					for height := 1; height <= 20; height++ {
+						t.Run(fmt.Sprintf("HeightOf%03d", height), func(t *testing.T) {
+							dv := diffview.New().
+								Before("main.go", TestMultipleHunksBefore).
+								After("main.go", TestMultipleHunksAfter).
+								Height(height)
+							dv = layoutFunc(dv)
+							dv = themeFunc(dv)
+
+							output := dv.String()
+							golden.RequireEqual(t, []byte(output))
+
+							assertHeight(t, height, output)
+						})
+					}
+				})
+			}
+		})
+	}
+}
+
 func assertLineWidth(t *testing.T, expected int, output string) {
 	var lineWidth int
 	for line := range strings.SplitSeq(output, "\n") {
@@ -176,3 +202,11 @@ func assertLineWidth(t *testing.T, expected int, output string) {
 		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
 	}
 }
+
+func assertHeight(t *testing.T, expected int, output string) {
+	output = strings.TrimSuffix(output, "\n")
+	lines := strings.Count(output, "\n") + 1
+	if lines != expected {
+		t.Errorf("expected output height to be == %d, got %d", expected, lines)
+	}
+}