fix(diffview): fix behavior of tabs and add tab width setting

Andrey Nering created

Change summary

internal/exp/diffview/diffview.go                              | 18 +++
internal/exp/diffview/diffview_test.go                         | 21 ++++
internal/exp/diffview/testdata/TestDiffViewTabs/Split.golden   | 15 ++
internal/exp/diffview/testdata/TestDiffViewTabs/Unified.golden | 16 +++
internal/exp/diffview/testdata/TestTabs.after                  | 15 ++
internal/exp/diffview/testdata/TestTabs.before                 | 13 ++
6 files changed, 98 insertions(+)

Detailed changes

internal/exp/diffview/diffview.go šŸ”—

@@ -42,6 +42,7 @@ type DiffView struct {
 	xOffset      int
 	yOffset      int
 	style        Style
+	tabWidth     int
 
 	isComputed bool
 	err        error
@@ -63,6 +64,7 @@ func New() *DiffView {
 		layout:       layoutUnified,
 		contextLines: udiff.DefaultContextLines,
 		lineNumbers:  true,
+		tabWidth:     8,
 	}
 	if lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
 		dv.style = DefaultDarkStyle
@@ -144,8 +146,16 @@ func (dv *DiffView) YOffset(yOffset int) *DiffView {
 	return dv
 }
 
+// TabWidth sets the tab width. Only relevant for code that contains tabs, like
+// Go code.
+func (dv *DiffView) TabWidth(tabWidth int) *DiffView {
+	dv.tabWidth = tabWidth
+	return dv
+}
+
 // String returns the string representation of the DiffView.
 func (dv *DiffView) String() string {
+	dv.replaceTabs()
 	if err := dv.computeDiff(); err != nil {
 		return err.Error()
 	}
@@ -177,6 +187,14 @@ func (dv *DiffView) String() string {
 	}
 }
 
+// replaceTabs replaces tabs in the before and after file contents with spaces
+// according to the specified tab width.
+func (dv *DiffView) replaceTabs() {
+	spaces := strings.Repeat(" ", dv.tabWidth)
+	dv.before.content = strings.ReplaceAll(dv.before.content, "\t", spaces)
+	dv.after.content = strings.ReplaceAll(dv.after.content, "\t", spaces)
+}
+
 // computeDiff computes the differences between the "before" and "after" files.
 func (dv *DiffView) computeDiff() error {
 	if dv.isComputed {

internal/exp/diffview/diffview_test.go šŸ”—

@@ -29,6 +29,12 @@ var TestNarrowBefore string
 //go:embed testdata/TestNarrow.after
 var TestNarrowAfter string
 
+//go:embed testdata/TestTabs.before
+var TestTabsBefore string
+
+//go:embed testdata/TestTabs.after
+var TestTabsAfter string
+
 type (
 	TestFunc  func(dv *diffview.DiffView) *diffview.DiffView
 	TestFuncs map[string]TestFunc
@@ -137,6 +143,21 @@ func TestDiffView(t *testing.T) {
 	}
 }
 
+func TestDiffViewTabs(t *testing.T) {
+	for layoutName, layoutFunc := range LayoutFuncs {
+		t.Run(layoutName, func(t *testing.T) {
+			dv := diffview.New().
+				Before("main.go", TestTabsBefore).
+				After("main.go", TestTabsAfter).
+				Style(diffview.DefaultLightStyle)
+			dv = layoutFunc(dv)
+
+			output := dv.String()
+			golden.RequireEqual(t, []byte(output))
+		})
+	}
+}
+
 func TestDiffViewWidth(t *testing.T) {
 	for layoutName, layoutFunc := range LayoutFuncs {
 		t.Run(layoutName, func(t *testing.T) {

internal/exp/diffview/testdata/TestDiffViewTabs/Split.golden šŸ”—

@@ -0,0 +1,15 @@
+Ā  …Ā   @@ -2,6 +2,7 @@                                     Ā  …Ā                                                       
+Ā  2Ā                                                       Ā  2Ā                                                       
+Ā  3Ā   import (                                            Ā  3Ā   import (                                            
+Ā  4Ā           "fmt"                                       Ā  4Ā           "fmt"                                       
+Ā   Ā                                                       Ā  5Ā +         "strings"                                   
+Ā  5Ā   )                                                   Ā  6Ā   )                                                   
+Ā  6Ā                                                       Ā  7Ā                                                       
+Ā  7Ā   func main() {                                       Ā  8Ā   func main() {                                       
+Ā  …Ā   @@ -9,5 +10,6 @@                                    Ā  …Ā                                                       
+Ā  9Ā   }                                                   Ā 10Ā   }                                                   
+Ā 10Ā                                                       Ā 11Ā                                                       
+Ā 11Ā   func getContent() string {                          Ā 12Ā   func getContent() string {                          
+Ā 12Ā -         return "Hello, world!"                      Ā 13Ā +         content := strings.ToUpper("Hello, World!") 
+Ā   Ā                                                       Ā 14Ā +         return content                              
+Ā 13Ā   }                                                   Ā 15Ā   }                                                   

internal/exp/diffview/testdata/TestDiffViewTabs/Unified.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Ā   }                                                   

internal/exp/diffview/testdata/TestTabs.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
+}