diff --git a/internal/exp/diffview/diffview.go b/internal/exp/diffview/diffview.go index 2f3b1e7927adbbfd489d2fa8bff8ada82d55a9bb..77cea81f593de49454f3f72c31b9e365a95c25d7 100644 --- a/internal/exp/diffview/diffview.go +++ b/internal/exp/diffview/diffview.go @@ -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 diff --git a/internal/exp/diffview/diffview_test.go b/internal/exp/diffview/diffview_test.go index 982bad099ff29ca912a78fcb9068ada771f25e15..250f06bd9e212fcf19d16c8d47996a199eef97e1 100644 --- a/internal/exp/diffview/diffview_test.go +++ b/internal/exp/diffview/diffview_test.go @@ -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())) + }) +} diff --git a/internal/exp/diffview/style.go b/internal/exp/diffview/style.go index 13b025ea4b14b2d7bdee4f01870d18273afa5867..e8f21c75139c7fba2489feb2bd63c1aca9d2c7fa 100644 --- a/internal/exp/diffview/style.go +++ b/internal/exp/diffview/style.go @@ -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). diff --git a/internal/exp/diffview/testdata/TestDefault/DarkMode.golden b/internal/exp/diffview/testdata/TestDefault/DarkMode.golden index c7449b30c8ab6d9c7b38c7c8babb8a551fbf159c..a173f27e7d2cc981122f2559a5b3784b618a1ace 100644 --- a/internal/exp/diffview/testdata/TestDefault/DarkMode.golden +++ b/internal/exp/diffview/testdata/TestDefault/DarkMode.golden @@ -1,3 +1,4 @@ +  …   …  @@ -5,5 +5,6 @@    5   5  )    6   6      7   7  func main() {  diff --git a/internal/exp/diffview/testdata/TestDefault/LightMode.golden b/internal/exp/diffview/testdata/TestDefault/LightMode.golden index 31548d7db1806ccab600d85428259b0d4050d500..057fc6b55127c00a1fbbca406bd639e474d3b8a8 100644 --- a/internal/exp/diffview/testdata/TestDefault/LightMode.golden +++ b/internal/exp/diffview/testdata/TestDefault/LightMode.golden @@ -1,3 +1,4 @@ +  …   …  @@ -5,5 +5,6 @@    5   5  )    6   6      7   7  func main() {  diff --git a/internal/exp/diffview/testdata/TestMultipleHunks.after b/internal/exp/diffview/testdata/TestMultipleHunks.after new file mode 100644 index 0000000000000000000000000000000000000000..b78a823fa5c0f744c6eb3196a890338f92e40ada --- /dev/null +++ b/internal/exp/diffview/testdata/TestMultipleHunks.after @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "strings" +) + +func main() { + fmt.Println(getContent()) +} + +func getContent() string { + content := strings.ToUpper("Hello, World!") + return content +} diff --git a/internal/exp/diffview/testdata/TestMultipleHunks.before b/internal/exp/diffview/testdata/TestMultipleHunks.before new file mode 100644 index 0000000000000000000000000000000000000000..97661af36e6d8fe0900a32dc9cceea7960c9062f --- /dev/null +++ b/internal/exp/diffview/testdata/TestMultipleHunks.before @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println(getContent()) +} + +func getContent() string { + return "Hello, world!" +} diff --git a/internal/exp/diffview/testdata/TestMultipleHunks/DarkMode.golden b/internal/exp/diffview/testdata/TestMultipleHunks/DarkMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..026618e2403d89e10575b3e5a7737f7ba8b41c46 --- /dev/null +++ b/internal/exp/diffview/testdata/TestMultipleHunks/DarkMode.golden @@ -0,0 +1,16 @@ +  …   …  @@ -2,6 +2,7 @@  +  2   2    +  3   3  import (  +  4   4  "fmt"  +     5 +  "strings"  +  5   6  )  +  6   7    +  7   8  func main() {  +  …   …  @@ -9,5 +10,6 @@  +  9  10  }  + 10  11    + 11  12  func getContent() string {  + 12    -  return "Hello, world!"  +    13 +  content := strings.ToUpper("Hello, World!") +    14 +  return content  + 13  15  }  diff --git a/internal/exp/diffview/testdata/TestMultipleHunks/LightMode.golden b/internal/exp/diffview/testdata/TestMultipleHunks/LightMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..2ff673f4d9d103855dd38a63bfffe534e8fe6b9d --- /dev/null +++ b/internal/exp/diffview/testdata/TestMultipleHunks/LightMode.golden @@ -0,0 +1,16 @@ +  …   …  @@ -2,6 +2,7 @@  +  2   2    +  3   3  import (  +  4   4  "fmt"  +     5 +  "strings"  +  5   6  )  +  6   7    +  7   8  func main() {  +  …   …  @@ -9,5 +10,6 @@  +  9  10  }  + 10  11    + 11  12  func getContent() string {  + 12    -  return "Hello, world!"  +    13 +  content := strings.ToUpper("Hello, World!") +    14 +  return content  + 13  15  }  diff --git a/internal/exp/diffview/testdata/TestNoLineNumbers/DarkMode.golden b/internal/exp/diffview/testdata/TestNoLineNumbers/DarkMode.golden index 1cefd40f62dbe898b5d215a7d9b3e88fea8cb477..5f919312d6ff3bf0b4918fc6350659bd56feb723 100644 --- a/internal/exp/diffview/testdata/TestNoLineNumbers/DarkMode.golden +++ b/internal/exp/diffview/testdata/TestNoLineNumbers/DarkMode.golden @@ -1,3 +1,4 @@ + @@ -5,5 +5,6 @@   )      func main() {  diff --git a/internal/exp/diffview/testdata/TestNoLineNumbers/LightMode.golden b/internal/exp/diffview/testdata/TestNoLineNumbers/LightMode.golden index dffbc6071dea4f70e5534473aefdb02af7f0a389..d79658d36d68c55b53008ff505295e340b024a23 100644 --- a/internal/exp/diffview/testdata/TestNoLineNumbers/LightMode.golden +++ b/internal/exp/diffview/testdata/TestNoLineNumbers/LightMode.golden @@ -1,3 +1,4 @@ + @@ -5,5 +5,6 @@   )      func main() {