feat(diffview): implement resizer to make diff wider or shorter as asked

Andrey Nering created

Change summary

internal/exp/diffview/diffview.go                                               | 31 
internal/exp/diffview/diffview_test.go                                          | 37 
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden    | 15 
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden   | 15 
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden    | 15 
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden   | 15 
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden  | 16 
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden | 16 
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden  | 16 
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden | 16 
10 files changed, 188 insertions(+), 4 deletions(-)

Detailed changes

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

@@ -137,7 +137,12 @@ func (dv *DiffView) String() string {
 	dv.convertDiffToSplit()
 	dv.adjustStyles()
 	dv.detectNumDigits()
-	dv.detectCodeWidth()
+
+	if dv.width <= 0 {
+		dv.detectCodeWidth()
+	} else {
+		dv.resizeCodeWidth()
+	}
 
 	switch dv.layout {
 	case layoutUnified:
@@ -254,6 +259,21 @@ func (dv *DiffView) detectSplitCodeWidth() {
 	}
 }
 
+// resizeCodeWidth resizes the code width to fit within the specified width.
+func (dv *DiffView) resizeCodeWidth() {
+	fullNumWidth := dv.beforeNumDigits + dv.afterNumDigits
+	fullNumWidth += lineNumPadding * 4 // left and right padding for both line numbers
+
+	switch dv.layout {
+	case layoutUnified:
+		dv.codeWidth = dv.width - fullNumWidth - leadingSymbolsSize
+	case layoutSplit:
+		dv.codeWidth = (dv.width - fullNumWidth - leadingSymbolsSize*2) / 2
+	}
+
+	dv.fullCodeWidth = dv.codeWidth + leadingSymbolsSize
+}
+
 // renderUnified renders the unified diff view as a string.
 func (dv *DiffView) renderUnified() string {
 	var b strings.Builder
@@ -263,7 +283,8 @@ func (dv *DiffView) renderUnified() string {
 			b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
 			b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
 		}
-		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(dv.hunkLineFor(h)))
+		content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
+		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
 		b.WriteRune('\n')
 
 		beforeLine := h.FromLine
@@ -271,6 +292,7 @@ func (dv *DiffView) renderUnified() string {
 
 		for _, l := range h.Lines {
 			content := strings.TrimSuffix(l.Content, "\n")
+			content = ansi.Truncate(content, dv.codeWidth, "…")
 
 			switch l.Kind {
 			case udiff.Equal:
@@ -313,7 +335,8 @@ func (dv *DiffView) renderSplit() string {
 		if dv.lineNumbers {
 			b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
 		}
-		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(dv.hunkLineFor(dv.unified.Hunks[i])))
+		content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
+		b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
 		if dv.lineNumbers {
 			b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
 		}
@@ -328,9 +351,11 @@ func (dv *DiffView) renderSplit() string {
 			var afterContent string
 			if l.before != nil {
 				beforeContent = strings.TrimSuffix(l.before.Content, "\n")
+				beforeContent = ansi.Truncate(beforeContent, dv.codeWidth, "…")
 			}
 			if l.after != nil {
 				afterContent = strings.TrimSuffix(l.after.Content, "\n")
+				afterContent = ansi.Truncate(afterContent, dv.codeWidth, "…")
 			}
 
 			switch {

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

@@ -2,8 +2,10 @@ package diffview_test
 
 import (
 	_ "embed"
+	"strings"
 	"testing"
 
+	"github.com/charmbracelet/x/ansi"
 	"github.com/charmbracelet/x/exp/golden"
 	"github.com/opencode-ai/opencode/internal/exp/diffview"
 )
@@ -66,6 +68,18 @@ var (
 			Before("text.txt", TestNarrowBefore).
 			After("text.txt", TestNarrowAfter)
 	}
+	SmallWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
+		return dv.
+			Before("text.txt", TestMultipleHunksBefore).
+			After("text.txt", TestMultipleHunksAfter).
+			Width(40)
+	}
+	LargeWidthFunc = func(dv *diffview.DiffView) *diffview.DiffView {
+		return dv.
+			Before("text.txt", TestMultipleHunksBefore).
+			After("text.txt", TestMultipleHunksAfter).
+			Width(120)
+	}
 
 	LightModeFunc = func(dv *diffview.DiffView) *diffview.DiffView {
 		return dv.Style(diffview.DefaultLightStyle)
@@ -84,6 +98,8 @@ var (
 		"MultipleHunks":      MultipleHunksFunc,
 		"CustomContextLines": CustomContextLinesFunc,
 		"Narrow":             NarrowFunc,
+		"SmallWidth":         SmallWidthFunc,
+		"LargeWidth":         LargeWidthFunc,
 	}
 	ThemeFuncs = TestFuncs{
 		"LightMode": LightModeFunc,
@@ -102,7 +118,16 @@ func TestDiffView(t *testing.T) {
 							dv = layoutFunc(dv)
 							dv = behaviorFunc(dv)
 							dv = themeFunc(dv)
-							golden.RequireEqual(t, []byte(dv.String()))
+
+							output := dv.String()
+							golden.RequireEqual(t, []byte(output))
+
+							switch behaviorName {
+							case "SmallWidth":
+								assertLineWidth(t, 40, output)
+							case "LargeWidth":
+								assertLineWidth(t, 120, output)
+							}
 						})
 					}
 				})
@@ -110,3 +135,13 @@ func TestDiffView(t *testing.T) {
 		})
 	}
 }
+
+func assertLineWidth(t *testing.T, expected int, output string) {
+	var lineWidth int
+	for line := range strings.SplitSeq(output, "\n") {
+		lineWidth = max(lineWidth, ansi.StringWidth(line))
+	}
+	if lineWidth != expected {
+		t.Errorf("expected output width to be == %d, got %d", expected, lineWidth)
+	}
+}

internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.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/TestDiffView/Split/LargeWidth/LightMode.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/TestDiffView/Split/SmallWidth/DarkMode.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 getConte…Ā 12Ā   func getConte…
+Ā 12Ā -     return "H…Ā 13Ā +     content :…
+Ā   Ā                 Ā 14Ā +     return co…
+Ā 13Ā   }             Ā 15Ā   }             

internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.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 getConte…Ā 12Ā   func getConte…
+Ā 12Ā -     return "H…Ā 13Ā +     content :…
+Ā   Ā                 Ā 14Ā +     return co…
+Ā 13Ā   }             Ā 15Ā   }             

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

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

internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/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.ToUppe…
+Ā   Ā Ā 14Ā +     return content            
+Ā 13Ā Ā 15Ā   }                             

internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/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.ToUppe…
+Ā   Ā Ā 14Ā +     return content            
+Ā 13Ā Ā 15Ā   }