Detailed changes
@@ -1,6 +1,7 @@
package diffview
import (
+ "fmt"
"os"
"strconv"
"strings"
@@ -125,22 +126,34 @@ func (dv *DiffView) String() string {
}
dv.detectWidth()
- lineNumberWidth := func(start, num int) int {
- return len(strconv.Itoa(start + num))
- }
+ codeWidth := dv.width - leadingSymbolsSize
+ beforeNumDigits, afterNumDigits := dv.lineNumberDigits()
var b strings.Builder
- for _, h := range dv.unified.Hunks {
+ for i, h := range dv.unified.Hunks {
+ beforeShownLines, afterShownLines := dv.hunkShownLines(i)
+
+ if dv.lineNumbers {
+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("ā¦", beforeNumDigits)))
+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("ā¦", afterNumDigits)))
+ }
+ b.WriteString(dv.style.DividerLine.Code.Width(codeWidth + leadingSymbolsSize).Render(
+ fmt.Sprintf(
+ " @@ -%d,%d +%d,%d @@",
+ h.FromLine,
+ beforeShownLines,
+ h.ToLine,
+ afterShownLines,
+ ),
+ ))
+ b.WriteRune('\n')
+
beforeLine := h.FromLine
afterLine := h.ToLine
- beforeNumDigits := lineNumberWidth(h.FromLine, len(h.Lines))
- afterNumDigits := lineNumberWidth(h.ToLine, len(h.Lines))
-
for _, l := range h.Lines {
content := strings.TrimSuffix(l.Content, "\n")
- codeWidth := dv.width - leadingSymbolsSize
switch l.Kind {
case udiff.Equal:
@@ -194,6 +207,33 @@ func (dv *DiffView) computeDiff() error {
return dv.err
}
+// lineNumberDigits calculates the maximum number of digits needed for before and
+// after line numbers.
+func (dv *DiffView) lineNumberDigits() (maxBefore, maxAfter int) {
+ for _, h := range dv.unified.Hunks {
+ maxBefore = max(maxBefore, len(strconv.Itoa(h.FromLine+len(h.Lines))))
+ maxAfter = max(maxAfter, len(strconv.Itoa(h.ToLine+len(h.Lines))))
+ }
+ return
+}
+
+// hunkShownLines calculates the number of lines shown in a hunk for both before
+// and after versions.
+func (dv *DiffView) hunkShownLines(i int) (before, after int) {
+ for _, l := range dv.unified.Hunks[i].Lines {
+ switch l.Kind {
+ case udiff.Equal:
+ before++
+ after++
+ case udiff.Insert:
+ after++
+ case udiff.Delete:
+ before++
+ }
+ }
+ return
+}
+
func (dv *DiffView) detectWidth() {
if dv.width > 0 {
return
@@ -14,6 +14,12 @@ var TestDefaultBefore string
//go:embed testdata/TestDefault.after
var TestDefaultAfter string
+//go:embed testdata/TestMultipleHunks.before
+var TestMultipleHunksBefore string
+
+//go:embed testdata/TestMultipleHunks.after
+var TestMultipleHunksAfter string
+
func TestDefault(t *testing.T) {
dv := diffview.New().
Before("main.go", TestDefaultBefore).
@@ -46,3 +52,19 @@ func TestNoLineNumbers(t *testing.T) {
golden.RequireEqual(t, []byte(dv.String()))
})
}
+
+func TestMultipleHunks(t *testing.T) {
+ dv := diffview.New().
+ Before("main.go", TestMultipleHunksBefore).
+ After("main.go", TestMultipleHunksAfter)
+
+ t.Run("LightMode", func(t *testing.T) {
+ dv = dv.Style(diffview.DefaultLightStyle)
+ golden.RequireEqual(t, []byte(dv.String()))
+ })
+
+ t.Run("DarkMode", func(t *testing.T) {
+ dv = dv.Style(diffview.DefaultDarkStyle)
+ golden.RequireEqual(t, []byte(dv.String()))
+ })
+}
@@ -12,12 +12,23 @@ type LineStyle struct {
}
type Style struct {
- EqualLine LineStyle
- InsertLine LineStyle
- DeleteLine LineStyle
+ DividerLine LineStyle
+ EqualLine LineStyle
+ InsertLine LineStyle
+ DeleteLine LineStyle
}
var DefaultLightStyle = Style{
+ DividerLine: LineStyle{
+ LineNumber: lipgloss.NewStyle().
+ Foreground(charmtone.Iron).
+ Background(charmtone.Thunder).
+ Align(lipgloss.Right).
+ Padding(0, 1),
+ Code: lipgloss.NewStyle().
+ Foreground(charmtone.Oyster).
+ Background(charmtone.Anchovy),
+ },
EqualLine: LineStyle{
LineNumber: lipgloss.NewStyle().
Foreground(charmtone.Charcoal).
@@ -57,6 +68,16 @@ var DefaultLightStyle = Style{
}
var DefaultDarkStyle = Style{
+ DividerLine: LineStyle{
+ LineNumber: lipgloss.NewStyle().
+ Foreground(charmtone.Smoke).
+ Background(charmtone.Sapphire).
+ Align(lipgloss.Right).
+ Padding(0, 1),
+ Code: lipgloss.NewStyle().
+ Foreground(charmtone.Smoke).
+ Background(charmtone.Ox),
+ },
EqualLine: LineStyle{
LineNumber: lipgloss.NewStyle().
Foreground(charmtone.Ash).
@@ -1,3 +1,4 @@
+[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;51;49;178m @@ -5,5 +5,6 @@[m[48;2;51;49;178m [m
[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 5[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 5[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m )[m[48;2;32;31;38m [m
[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 6[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 6[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m [m[48;2;32;31;38m [m
[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 7[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 7[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m func main() {[m[48;2;32;31;38m [m
@@ -1,3 +1,4 @@
+[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[38;2;96;95;107;48;2;113;154;252m @@ -5,5 +5,6 @@[m[48;2;113;154;252m [m
[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 5[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 5[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m )[m[48;2;241;239;239m [m
[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 6[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 6[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m [m[48;2;241;239;239m [m
[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 7[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 7[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m func main() {[m[48;2;241;239;239m [m
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+)
+
+func main() {
+ fmt.Println(getContent())
+}
+
+func getContent() string {
+ content := strings.ToUpper("Hello, World!")
+ return content
+}
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ fmt.Println(getContent())
+}
+
+func getContent() string {
+ return "Hello, world!"
+}
@@ -0,0 +1,16 @@
+[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;51;49;178m @@ -2,6 +2,7 @@[m[48;2;51;49;178m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 2[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 2[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m [m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 3[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 3[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m import ([m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 4[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 4[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m "fmt"[m[48;2;32;31;38m [m
+[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m [m[48;2;41;50;41mĀ [m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m 5[m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;48;58;48m+ [m[38;2;241;239;239;48;2;48;58;48m "strings"[m[48;2;48;58;48m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 5[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 6[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m )[m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 6[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 7[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m [m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 7[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 8[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m func main() {[m[48;2;32;31;38m [m
+[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;73;73;255m ā¦[m[48;2;73;73;255mĀ [m[38;2;191;188;200;48;2;51;49;178m @@ -9,5 +10,6 @@[m[48;2;51;49;178m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m 9[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m10[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m }[m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m10[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m11[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m [m[48;2;32;31;38m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m11[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m12[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m func getContent() string {[m[48;2;32;31;38m [m
+[48;2;51;41;41mĀ [m[38;2;255;56;139;48;2;51;41;41m12[m[48;2;51;41;41mĀ [m[48;2;51;41;41mĀ [m[38;2;255;56;139;48;2;51;41;41m [m[48;2;51;41;41mĀ [m[38;2;255;56;139;48;2;58;48;48m- [m[38;2;241;239;239;48;2;58;48;48m return "Hello, world!"[m[48;2;58;48;48m [m
+[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m [m[48;2;41;50;41mĀ [m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m13[m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;48;58;48m+ [m[38;2;241;239;239;48;2;48;58;48m content := strings.ToUpper("Hello, World!")[m
+[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m [m[48;2;41;50;41mĀ [m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;41;50;41m14[m[48;2;41;50;41mĀ [m[38;2;10;220;217;48;2;48;58;48m+ [m[38;2;241;239;239;48;2;48;58;48m return content[m[48;2;48;58;48m [m
+[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m13[m[48;2;58;57;67mĀ [m[48;2;58;57;67mĀ [m[38;2;223;219;221;48;2;58;57;67m15[m[48;2;58;57;67mĀ [m[38;2;241;239;239;48;2;32;31;38m }[m[48;2;32;31;38m [m
@@ -0,0 +1,16 @@
+[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[38;2;96;95;107;48;2;113;154;252m @@ -2,6 +2,7 @@[m[48;2;113;154;252m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 2[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 2[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m [m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 3[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 3[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m import ([m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 4[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 4[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m "fmt"[m[48;2;241;239;239m [m
+[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m [m[48;2;200;230;201mĀ [m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m 5[m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;232;245;233m+ [m[38;2;32;31;38;48;2;232;245;233m "strings"[m[48;2;232;245;233m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 5[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 6[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m )[m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 6[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 7[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m [m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 7[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 8[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m func main() {[m[48;2;241;239;239m [m
+[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[48;2;71;118;255mĀ [m[38;2;77;76;87;48;2;71;118;255m ā¦[m[48;2;71;118;255mĀ [m[38;2;96;95;107;48;2;113;154;252m @@ -9,5 +10,6 @@[m[48;2;113;154;252m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m 9[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m10[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m }[m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m10[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m11[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m [m[48;2;241;239;239m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m11[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m12[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m func getContent() string {[m[48;2;241;239;239m [m
+[48;2;255;205;210mĀ [m[38;2;255;56;139;48;2;255;205;210m12[m[48;2;255;205;210mĀ [m[48;2;255;205;210mĀ [m[38;2;255;56;139;48;2;255;205;210m [m[48;2;255;205;210mĀ [m[38;2;255;56;139;48;2;255;235;238m- [m[38;2;32;31;38;48;2;255;235;238m return "Hello, world!"[m[48;2;255;235;238m [m
+[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m [m[48;2;200;230;201mĀ [m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m13[m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;232;245;233m+ [m[38;2;32;31;38;48;2;232;245;233m content := strings.ToUpper("Hello, World!")[m
+[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m [m[48;2;200;230;201mĀ [m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;200;230;201m14[m[48;2;200;230;201mĀ [m[38;2;10;220;217;48;2;232;245;233m+ [m[38;2;32;31;38;48;2;232;245;233m return content[m[48;2;232;245;233m [m
+[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m13[m[48;2;223;219;221mĀ [m[48;2;223;219;221mĀ [m[38;2;58;57;67;48;2;223;219;221m15[m[48;2;223;219;221mĀ [m[38;2;32;31;38;48;2;241;239;239m }[m[48;2;241;239;239m [m
@@ -1,3 +1,4 @@
+[38;2;191;188;200;48;2;51;49;178m @@ -5,5 +5,6 @@[m[48;2;51;49;178m [m
[38;2;241;239;239;48;2;32;31;38m )[m[48;2;32;31;38m [m
[38;2;241;239;239;48;2;32;31;38m [m[48;2;32;31;38m [m
[38;2;241;239;239;48;2;32;31;38m func main() {[m[48;2;32;31;38m [m
@@ -1,3 +1,4 @@
+[38;2;96;95;107;48;2;113;154;252m @@ -5,5 +5,6 @@[m[48;2;113;154;252m [m
[38;2;32;31;38;48;2;241;239;239m )[m[48;2;241;239;239m [m
[38;2;32;31;38;48;2;241;239;239m [m[48;2;241;239;239m [m
[38;2;32;31;38;48;2;241;239;239m func main() {[m[48;2;241;239;239m [m