on main help sign in
feat(diffview): show hunk lines and test for multiple hunks
07d4221Andrey Nering
internal/exp/diffview/diffview.go+48−8
@@ -1,6 +1,7 @@
1package diffview
1package diffview
2
2
3import (
3import (
4+ "fmt"
4 "os"
5 "os"
5 "strconv"
6 "strconv"
6 "strings"
7 "strings"
@@ -125,22 +126,34 @@ func (dv *DiffView) String() string {
125 }
126 }
126 dv.detectWidth()
127 dv.detectWidth()
127
128
128− lineNumberWidth := func(start, num int) int {
129+ codeWidth := dv.width - leadingSymbolsSize
129− return len(strconv.Itoa(start + num))
130+ beforeNumDigits, afterNumDigits := dv.lineNumberDigits()
130− }
131
131
132 var b strings.Builder
132 var b strings.Builder
133
133
134− for _, h := range dv.unified.Hunks {
134+ for i, h := range dv.unified.Hunks {
135+ beforeShownLines, afterShownLines := dv.hunkShownLines(i)
136+
137+ if dv.lineNumbers {
138+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", beforeNumDigits)))
139+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", afterNumDigits)))
140+ }
141+ b.WriteString(dv.style.DividerLine.Code.Width(codeWidth + leadingSymbolsSize).Render(
142+ fmt.Sprintf(
143+ " @@ -%d,%d +%d,%d @@",
144+ h.FromLine,
145+ beforeShownLines,
146+ h.ToLine,
147+ afterShownLines,
148+ ),
149+ ))
150+ b.WriteRune('\n')
151+
135 beforeLine := h.FromLine
152 beforeLine := h.FromLine
136 afterLine := h.ToLine
153 afterLine := h.ToLine
137
154
138− beforeNumDigits := lineNumberWidth(h.FromLine, len(h.Lines))
139− afterNumDigits := lineNumberWidth(h.ToLine, len(h.Lines))
140−
141 for _, l := range h.Lines {
155 for _, l := range h.Lines {
142 content := strings.TrimSuffix(l.Content, "\n")
156 content := strings.TrimSuffix(l.Content, "\n")
143− codeWidth := dv.width - leadingSymbolsSize
144
157
145 switch l.Kind {
158 switch l.Kind {
146 case udiff.Equal:
159 case udiff.Equal:
@@ -194,6 +207,33 @@ func (dv *DiffView) computeDiff() error {
194 return dv.err
207 return dv.err
195}
208}
196
209
210+// lineNumberDigits calculates the maximum number of digits needed for before and
211+// after line numbers.
212+func (dv *DiffView) lineNumberDigits() (maxBefore, maxAfter int) {
213+ for _, h := range dv.unified.Hunks {
214+ maxBefore = max(maxBefore, len(strconv.Itoa(h.FromLine+len(h.Lines))))
215+ maxAfter = max(maxAfter, len(strconv.Itoa(h.ToLine+len(h.Lines))))
216+ }
217+ return
218+}
219+
220+// hunkShownLines calculates the number of lines shown in a hunk for both before
221+// and after versions.
222+func (dv *DiffView) hunkShownLines(i int) (before, after int) {
223+ for _, l := range dv.unified.Hunks[i].Lines {
224+ switch l.Kind {
225+ case udiff.Equal:
226+ before++
227+ after++
228+ case udiff.Insert:
229+ after++
230+ case udiff.Delete:
231+ before++
232+ }
233+ }
234+ return
235+}
236+
197func (dv *DiffView) detectWidth() {
237func (dv *DiffView) detectWidth() {
198 if dv.width > 0 {
238 if dv.width > 0 {
199 return
239 return
internal/exp/diffview/diffview_test.go+22
@@ -14,6 +14,12 @@ var TestDefaultBefore string
14//go:embed testdata/TestDefault.after
14//go:embed testdata/TestDefault.after
15var TestDefaultAfter string
15var TestDefaultAfter string
16
16
17+//go:embed testdata/TestMultipleHunks.before
18+var TestMultipleHunksBefore string
19+
20+//go:embed testdata/TestMultipleHunks.after
21+var TestMultipleHunksAfter string
22+
17func TestDefault(t *testing.T) {
23func TestDefault(t *testing.T) {
18 dv := diffview.New().
24 dv := diffview.New().
19 Before("main.go", TestDefaultBefore).
25 Before("main.go", TestDefaultBefore).
@@ -46,3 +52,19 @@ func TestNoLineNumbers(t *testing.T) {
46 golden.RequireEqual(t, []byte(dv.String()))
52 golden.RequireEqual(t, []byte(dv.String()))
47 })
53 })
48}
54}
55+
56+func TestMultipleHunks(t *testing.T) {
57+ dv := diffview.New().
58+ Before("main.go", TestMultipleHunksBefore).
59+ After("main.go", TestMultipleHunksAfter)
60+
61+ t.Run("LightMode", func(t *testing.T) {
62+ dv = dv.Style(diffview.DefaultLightStyle)
63+ golden.RequireEqual(t, []byte(dv.String()))
64+ })
65+
66+ t.Run("DarkMode", func(t *testing.T) {
67+ dv = dv.Style(diffview.DefaultDarkStyle)
68+ golden.RequireEqual(t, []byte(dv.String()))
69+ })
70+}
internal/exp/diffview/style.go+24−3
@@ -12,12 +12,23 @@ type LineStyle struct {
12}
12}
13
13
14type Style struct {
14type Style struct {
15+ DividerLine LineStyle
15− EqualLine LineStyle
16+ EqualLine LineStyle
16− InsertLine LineStyle
17+ InsertLine LineStyle
17− DeleteLine LineStyle
18+ DeleteLine LineStyle
18}
19}
19
20
20var DefaultLightStyle = Style{
21var DefaultLightStyle = Style{
22+ DividerLine: LineStyle{
23+ LineNumber: lipgloss.NewStyle().
24+ Foreground(charmtone.Iron).
25+ Background(charmtone.Thunder).
26+ Align(lipgloss.Right).
27+ Padding(0, 1),
28+ Code: lipgloss.NewStyle().
29+ Foreground(charmtone.Oyster).
30+ Background(charmtone.Anchovy),
31+ },
21 EqualLine: LineStyle{
32 EqualLine: LineStyle{
22 LineNumber: lipgloss.NewStyle().
33 LineNumber: lipgloss.NewStyle().
23 Foreground(charmtone.Charcoal).
34 Foreground(charmtone.Charcoal).
@@ -57,6 +68,16 @@ var DefaultLightStyle = Style{
57}
68}
58
69
59var DefaultDarkStyle = Style{
70var DefaultDarkStyle = Style{
71+ DividerLine: LineStyle{
72+ LineNumber: lipgloss.NewStyle().
73+ Foreground(charmtone.Smoke).
74+ Background(charmtone.Sapphire).
75+ Align(lipgloss.Right).
76+ Padding(0, 1),
77+ Code: lipgloss.NewStyle().
78+ Foreground(charmtone.Smoke).
79+ Background(charmtone.Ox),
80+ },
60 EqualLine: LineStyle{
81 EqualLine: LineStyle{
61 LineNumber: lipgloss.NewStyle().
82 LineNumber: lipgloss.NewStyle().
62 Foreground(charmtone.Ash).
83 Foreground(charmtone.Ash).
internal/exp/diffview/testdata/TestDefault/DarkMode.golden+1
@@ -1,3 +1,4 @@
1+  …   …  @@ -5,5 +5,6 @@ 
1  5   5  ) 
2  5   5  ) 
2  6   6   
3  6   6   
3  7   7  func main() { 
4  7   7  func main() { 
internal/exp/diffview/testdata/TestDefault/LightMode.golden+1
@@ -1,3 +1,4 @@
1+  …   …  @@ -5,5 +5,6 @@ 
1  5   5  ) 
2  5   5  ) 
2  6   6   
3  6   6   
3  7   7  func main() { 
4  7   7  func main() { 
internal/exp/diffview/testdata/TestMultipleHunks.afternew file+15
@@ -0,0 +1,15 @@
1+package main
2+
3+import (
4+ "fmt"
5+ "strings"
6+)
7+
8+func main() {
9+ fmt.Println(getContent())
10+}
11+
12+func getContent() string {
13+ content := strings.ToUpper("Hello, World!")
14+ return content
15+}
internal/exp/diffview/testdata/TestMultipleHunks.beforenew file+13
@@ -0,0 +1,13 @@
1+package main
2+
3+import (
4+ "fmt"
5+)
6+
7+func main() {
8+ fmt.Println(getContent())
9+}
10+
11+func getContent() string {
12+ return "Hello, world!"
13+}
internal/exp/diffview/testdata/TestMultipleHunks/DarkMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@ 
2+  2   2   
3+  3   3  import ( 
4+  4   4  "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@ 
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!")
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestMultipleHunks/LightMode.goldennew file+16
@@ -0,0 +1,16 @@
1+  …   …  @@ -2,6 +2,7 @@ 
2+  2   2   
3+  3   3  import ( 
4+  4   4  "fmt" 
5+     5 +  "strings" 
6+  5   6  ) 
7+  6   7   
8+  7   8  func main() { 
9+  …   …  @@ -9,5 +10,6 @@ 
10+  9  10  } 
11+ 10  11   
12+ 11  12  func getContent() string { 
13+ 12    -  return "Hello, world!" 
14+    13 +  content := strings.ToUpper("Hello, World!")
15+    14 +  return content 
16+ 13  15  } 
internal/exp/diffview/testdata/TestNoLineNumbers/DarkMode.golden+1
@@ -1,3 +1,4 @@
1+ @@ -5,5 +5,6 @@ 
1 ) 
2 ) 
2  
3  
3 func main() { 
4 func main() { 
internal/exp/diffview/testdata/TestNoLineNumbers/LightMode.golden+1
@@ -1,3 +1,4 @@
1+ @@ -5,5 +5,6 @@ 
1 ) 
2 ) 
2  
3  
3 func main() { 
4 func main() {