diff --git a/internal/exp/diffview/diffview.go b/internal/exp/diffview/diffview.go index 7949d00b544eb0f141aa76cd3126df32b05345fe..6ec44fddebc7fc25938de54a8b4268efeed403ca 100644 --- a/internal/exp/diffview/diffview.go +++ b/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 { diff --git a/internal/exp/diffview/diffview_test.go b/internal/exp/diffview/diffview_test.go index d82488087ec79bc288f7cbe879142b453b534116..cd60838244a7504b6ca12cdd692dd7f4d7fe37df 100644 --- a/internal/exp/diffview/diffview_test.go +++ b/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) + } +} diff --git a/internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden b/internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..b1a8869156d23676e0dfd4a25834dc8a6abae39f --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden b/internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..b393fd4a20aa07ac97b92e3452804a748531591a --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden b/internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..715847b3dc4e38453f46d3e310e70d023408adb6 --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden b/internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..fd74ac5c3d1b0b0cac6bc5a5e03f0f95aaca2c50 --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden b/internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..f1cc58546cffe8aafbade71b3bf29fd86b39ac5f --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden b/internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..1040c45ae865825b6742b4f191032c7ff7bb96c3 --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden b/internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..3bc330b1a1fc0e8643257a99bcb7e3337eafc752 --- /dev/null +++ b/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  }  diff --git a/internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden b/internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden new file mode 100644 index 0000000000000000000000000000000000000000..fc8745b8676b6270375bbdc06266bf590c791b04 --- /dev/null +++ b/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  }