@@ -1,11 +1,18 @@
1package diffview
1package diffview
4 "github.com/aymanbagabas/go-udiff"
7 "github.com/aymanbagabas/go-udiff"
5 "github.com/aymanbagabas/go-udiff/myers"
8 "github.com/aymanbagabas/go-udiff/myers"
6 "github.com/charmbracelet/lipgloss/v2"
9 "github.com/charmbracelet/lipgloss/v2"
10+ "github.com/charmbracelet/x/ansi"
11+ "github.com/charmbracelet/x/exp/charmtone"
14+const leadingSymbolsSize = 2
9type file struct {
16type file struct {
10 path string
17 path string
11 content string
18 content string
@@ -18,16 +25,60 @@ const (
18 layoutSplit
25 layoutSplit
30+ InsertLine lipgloss.Style
31+ InsertSymbols lipgloss.Style
32+ DeleteLine lipgloss.Style
33+ DeleteSymbols lipgloss.Style
36+var DefaultLightStyle = Style{
37+ Base: lipgloss.NewStyle().
38+ Foreground(charmtone.Pepper).
39+ Background(charmtone.Salt),
40+ InsertLine: lipgloss.NewStyle().
41+ Foreground(charmtone.Pepper).
42+ Background(lipgloss.Color("#e8f5e9")),
43+ InsertSymbols: lipgloss.NewStyle().
44+ Foreground(charmtone.Turtle).
45+ Background(lipgloss.Color("#e8f5e9")),
46+ DeleteLine: lipgloss.NewStyle().
47+ Foreground(charmtone.Pepper).
48+ Background(lipgloss.Color("#ffebee")),
49+ DeleteSymbols: lipgloss.NewStyle().
50+ Foreground(charmtone.Cherry).
51+ Background(lipgloss.Color("#ffebee")),
54+var DefaultDarkStyle = Style{
55+ Base: lipgloss.NewStyle().
56+ Foreground(charmtone.Salt).
57+ Background(charmtone.Pepper),
58+ InsertLine: lipgloss.NewStyle().
59+ Foreground(charmtone.Salt).
60+ Background(lipgloss.Color("#303a30")),
61+ InsertSymbols: lipgloss.NewStyle().
62+ Foreground(charmtone.Turtle).
63+ Background(lipgloss.Color("#303a30")),
64+ DeleteLine: lipgloss.NewStyle().
65+ Foreground(charmtone.Salt).
66+ Background(lipgloss.Color("#3a3030")),
67+ DeleteSymbols: lipgloss.NewStyle().
68+ Foreground(charmtone.Cherry).
69+ Background(lipgloss.Color("#3a3030")),
21// DiffView represents a view for displaying differences between two files.
72// DiffView represents a view for displaying differences between two files.
22type DiffView struct {
73type DiffView struct {
23 layout layout
74 layout layout
24 before file
75 before file
25 after file
76 after file
26 contextLines int
77 contextLines int
27− baseStyle lipgloss.Style
28 highlight bool
78 highlight bool
29 height int
79 height int
32 isComputed bool
83 isComputed bool
@@ -37,10 +88,16 @@ type DiffView struct {
38// New creates a new DiffView with default settings.
89// New creates a new DiffView with default settings.
39func New() *DiffView {
90func New() *DiffView {
40− return &DiffView{
91+ dv := &DiffView{
41 layout: layoutUnified,
92 layout: layoutUnified,
42 contextLines: udiff.DefaultContextLines,
93 contextLines: udiff.DefaultContextLines,
95+ if lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
96+ dv.style = DefaultDarkStyle
98+ dv.style = DefaultLightStyle
46// Unified sets the layout of the DiffView to unified.
103// Unified sets the layout of the DiffView to unified.
@@ -73,10 +130,9 @@ func (dv *DiffView) ContextLines(contextLines int) *DiffView {
73 return dv
130 return dv
76−// BaseStyle sets the base style for the DiffView.
133+// Style sets the style for the DiffView.
77−// This is useful for setting a custom background color, for example.
78−func (dv *DiffView) BaseStyle(baseStyle lipgloss.Style) *DiffView {
134+func (dv *DiffView) Style(style Style) *DiffView {
79− dv.baseStyle = baseStyle
135+ dv.style = style
80 return dv
136 return dv
@@ -100,16 +156,39 @@ func (dv *DiffView) Width(width int) *DiffView {
101// String returns the string representation of the DiffView.
157// String returns the string representation of the DiffView.
102func (dv *DiffView) String() string {
158func (dv *DiffView) String() string {
103− if !dv.isComputed {
159+ if err := dv.computeDiff(); err != nil {
104− dv.compute()
160+ return err.Error()
106− if dv.err != nil {
162+ dv.detectWidth()
107− return dv.err.Error()
163+
164+ var b strings.Builder
166+ for _, h := range dv.unified.Hunks {
167+ for _, l := range h.Lines {
168+ content := strings.TrimSuffix(l.Content, "\n")
169+ width := dv.width - leadingSymbolsSize
173+ b.WriteString(dv.style.InsertSymbols.Render("+ "))
174+ b.WriteString(dv.style.InsertLine.Width(width).Render(content))
176+ b.WriteString(dv.style.DeleteSymbols.Render("- "))
177+ b.WriteString(dv.style.DeleteLine.Width(width).Render(content))
179+ b.WriteString(dv.style.Base.Width(width + leadingSymbolsSize).Render(" " + content))
109− return dv.unified.String()
185+ return b.String()
112−func (dv *DiffView) compute() {
188+func (dv *DiffView) computeDiff() error {
113 dv.isComputed = true
192 dv.isComputed = true
114 dv.edits = myers.ComputeEdits(
193 dv.edits = myers.ComputeEdits(
115 dv.before.content,
194 dv.before.content,
@@ -122,4 +201,19 @@ func (dv *DiffView) compute() {
122 dv.edits,
201 dv.edits,
123 dv.contextLines,
202 dv.contextLines,
207+func (dv *DiffView) detectWidth() {
212+ for _, h := range dv.unified.Hunks {
213+ for _, l := range h.Lines {
214+ lineWidth := ansi.StringWidth(strings.TrimSuffix(l.Content, "\n"))
215+ lineWidth += leadingSymbolsSize
216+ dv.width = max(dv.width, lineWidth)