Change summary
go.mod | 5
go.sum | 10
internal/exp/diffview/diffview.go | 124 +++++++++++++++++
internal/exp/diffview/diffview_test.go | 15 ++
internal/exp/diffview/testdata/TestDefault.golden | 7
5 files changed, 155 insertions(+), 6 deletions(-)
Detailed changes
@@ -15,6 +15,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.5
github.com/charmbracelet/glamour v0.9.1
github.com/charmbracelet/lipgloss v1.1.0
+ github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1
github.com/charmbracelet/x/ansi v0.8.0
github.com/charmbracelet/x/exp/golden v0.0.0-20250602192518-9e722df69bbb
github.com/fsnotify/fsnotify v1.8.0
@@ -59,8 +60,8 @@ require (
github.com/aws/smithy-go v1.20.3 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
- github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
- github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
+ github.com/charmbracelet/colorprofile v0.3.0 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2
@@ -72,16 +72,18 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ=
+github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0=
github.com/charmbracelet/glamour v0.9.1 h1:11dEfiGP8q1BEqvGoIjivuc2rBk+5qEXdPtaQ2WoiCM=
github.com/charmbracelet/glamour v0.9.1/go.mod h1:+SHvIS8qnwhgTpVMiXwn7OfGomSqff1cHBCI8jLOetk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1 h1:SOylT6+BQzPHEjn15TIzawBPVD0QmhKXbcb3jY0ZIKU=
+github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1/go.mod h1:tRlx/Hu0lo/j9viunCN2H+Ze6JrmdjQlXUQvvArgaOc=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
-github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
-github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
+github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20250602192518-9e722df69bbb h1:GT/STWThMsrOfYQnhnIPb165e/g1waAp0gNMFvEO6WI=
github.com/charmbracelet/x/exp/golden v0.0.0-20250602192518-9e722df69bbb/go.mod h1:929X+xY3LeoOZrDWIBVZcx/zyS0CYtyLiUIvE4VbKC0=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
@@ -1 +1,125 @@
package diffview
+
+import (
+ "github.com/aymanbagabas/go-udiff"
+ "github.com/aymanbagabas/go-udiff/myers"
+ "github.com/charmbracelet/lipgloss/v2"
+)
+
+type file struct {
+ path string
+ content string
+}
+
+type layout int
+
+const (
+ layoutUnified layout = iota + 1
+ layoutSplit
+)
+
+// DiffView represents a view for displaying differences between two files.
+type DiffView struct {
+ layout layout
+ before file
+ after file
+ contextLines int
+ baseStyle lipgloss.Style
+ highlight bool
+ height int
+ width int
+
+ isComputed bool
+ err error
+ unified udiff.UnifiedDiff
+ edits []udiff.Edit
+}
+
+// New creates a new DiffView with default settings.
+func New() *DiffView {
+ return &DiffView{
+ layout: layoutUnified,
+ contextLines: udiff.DefaultContextLines,
+ }
+}
+
+// Unified sets the layout of the DiffView to unified.
+func (dv *DiffView) Unified() *DiffView {
+ dv.layout = layoutUnified
+ return dv
+}
+
+// Split sets the layout of the DiffView to split (side-by-side).
+func (dv *DiffView) Split() *DiffView {
+ dv.layout = layoutSplit
+ return dv
+}
+
+// Before sets the "before" file for the DiffView.
+func (dv *DiffView) Before(path, content string) *DiffView {
+ dv.before = file{path: path, content: content}
+ return dv
+}
+
+// After sets the "after" file for the DiffView.
+func (dv *DiffView) After(path, content string) *DiffView {
+ dv.after = file{path: path, content: content}
+ return dv
+}
+
+// ContextLines sets the number of context lines for the DiffView.
+func (dv *DiffView) ContextLines(contextLines int) *DiffView {
+ dv.contextLines = contextLines
+ return dv
+}
+
+// BaseStyle sets the base style for the DiffView.
+// This is useful for setting a custom background color, for example.
+func (dv *DiffView) BaseStyle(baseStyle lipgloss.Style) *DiffView {
+ dv.baseStyle = baseStyle
+ return dv
+}
+
+// SyntaxHighlight sets whether to enable syntax highlighting in the DiffView.
+func (dv *DiffView) SyntaxHighlight(highlight bool) *DiffView {
+ dv.highlight = highlight
+ return dv
+}
+
+// Height sets the height of the DiffView.
+func (dv *DiffView) Height(height int) *DiffView {
+ dv.height = height
+ return dv
+}
+
+// Width sets the width of the DiffView.
+func (dv *DiffView) Width(width int) *DiffView {
+ dv.width = width
+ return dv
+}
+
+// String returns the string representation of the DiffView.
+func (dv *DiffView) String() string {
+ if !dv.isComputed {
+ dv.compute()
+ }
+ if dv.err != nil {
+ return dv.err.Error()
+ }
+ return dv.unified.String()
+}
+
+func (dv *DiffView) compute() {
+ dv.isComputed = true
+ dv.edits = myers.ComputeEdits(
+ dv.before.content,
+ dv.after.content,
+ )
+ dv.unified, dv.err = udiff.ToUnifiedDiff(
+ dv.before.path,
+ dv.after.path,
+ dv.before.content,
+ dv.edits,
+ dv.contextLines,
+ )
+}
@@ -0,0 +1,15 @@
+package diffview_test
+
+import (
+ "testing"
+
+ "github.com/charmbracelet/x/exp/golden"
+ "github.com/opencode-ai/opencode/internal/exp/diffview"
+)
+
+func TestDefault(t *testing.T) {
+ dv := diffview.New().
+ Before("test.txt", "This is the original content.").
+ After("test.txt", "This is the modified content.")
+ golden.RequireEqual(t, []byte(dv.String()))
+}
@@ -0,0 +1,7 @@
+--- test.txt
++++ test.txt
+@@ -1 +1 @@
+-This is the original content.
+\ No newline at end of file
++This is the modified content.
+\ No newline at end of file