on f8fc11e help sign in
crush/internal/exp/diffview/diffview.go
234 lines 5.3 kB history raw
1package diffview
3import (
4 "os"
5 "strings"
7 "github.com/aymanbagabas/go-udiff"
8 "github.com/aymanbagabas/go-udiff/myers"
9 "github.com/charmbracelet/lipgloss/v2"
10 "github.com/charmbracelet/x/ansi"
11 "github.com/charmbracelet/x/exp/charmtone"
14const leadingSymbolsSize = 2
16type file struct {
17 path string
18 content string
21type layout int
23const (
24 layoutUnified layout = iota + 1
25 layoutSplit
28type LineStyle struct {
29 Symbol lipgloss.Style
30 Code lipgloss.Style
33type Style struct {
34 EqualLine LineStyle
35 InsertLine LineStyle
36 DeleteLine LineStyle
39var DefaultLightStyle = Style{
40 EqualLine: LineStyle{
41 Code: lipgloss.NewStyle().
42 Foreground(charmtone.Pepper).
43 Background(charmtone.Salt),
44 },
45 InsertLine: LineStyle{
46 Symbol: lipgloss.NewStyle().
47 Foreground(charmtone.Turtle).
48 Background(lipgloss.Color("#e8f5e9")),
49 Code: lipgloss.NewStyle().
50 Foreground(charmtone.Pepper).
51 Background(lipgloss.Color("#e8f5e9")),
52 },
53 DeleteLine: LineStyle{
54 Symbol: lipgloss.NewStyle().
55 Foreground(charmtone.Cherry).
56 Background(lipgloss.Color("#ffebee")),
57 Code: lipgloss.NewStyle().
58 Foreground(charmtone.Pepper).
59 Background(lipgloss.Color("#ffebee")),
60 },
63var DefaultDarkStyle = Style{
64 EqualLine: LineStyle{
65 Code: lipgloss.NewStyle().
66 Foreground(charmtone.Salt).
67 Background(charmtone.Pepper),
68 },
69 InsertLine: LineStyle{
70 Symbol: lipgloss.NewStyle().
71 Foreground(charmtone.Turtle).
72 Background(lipgloss.Color("#303a30")),
73 Code: lipgloss.NewStyle().
74 Foreground(charmtone.Salt).
75 Background(lipgloss.Color("#303a30")),
76 },
77 DeleteLine: LineStyle{
78 Symbol: lipgloss.NewStyle().
79 Foreground(charmtone.Cherry).
80 Background(lipgloss.Color("#3a3030")),
81 Code: lipgloss.NewStyle().
82 Foreground(charmtone.Salt).
83 Background(lipgloss.Color("#3a3030")),
84 },
87// DiffView represents a view for displaying differences between two files.
88type DiffView struct {
89 layout layout
90 before file
91 after file
92 contextLines int
93 highlight bool
94 height int
95 width int
96 style Style
98 isComputed bool
99 err error
100 unified udiff.UnifiedDiff
101 edits []udiff.Edit
104// New creates a new DiffView with default settings.
105func New() *DiffView {
106 dv := &DiffView{
107 layout: layoutUnified,
108 contextLines: udiff.DefaultContextLines,
109 }
110 if lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
111 dv.style = DefaultDarkStyle
112 } else {
113 dv.style = DefaultLightStyle
114 }
115 return dv
118// Unified sets the layout of the DiffView to unified.
119func (dv *DiffView) Unified() *DiffView {
120 dv.layout = layoutUnified
121 return dv
124// Split sets the layout of the DiffView to split (side-by-side).
125func (dv *DiffView) Split() *DiffView {
126 dv.layout = layoutSplit
127 return dv
130// Before sets the "before" file for the DiffView.
131func (dv *DiffView) Before(path, content string) *DiffView {
132 dv.before = file{path: path, content: content}
133 return dv
136// After sets the "after" file for the DiffView.
137func (dv *DiffView) After(path, content string) *DiffView {
138 dv.after = file{path: path, content: content}
139 return dv
142// ContextLines sets the number of context lines for the DiffView.
143func (dv *DiffView) ContextLines(contextLines int) *DiffView {
144 dv.contextLines = contextLines
145 return dv
148// Style sets the style for the DiffView.
149func (dv *DiffView) Style(style Style) *DiffView {
150 dv.style = style
151 return dv
154// SyntaxHighlight sets whether to enable syntax highlighting in the DiffView.
155func (dv *DiffView) SyntaxHighlight(highlight bool) *DiffView {
156 dv.highlight = highlight
157 return dv
160// Height sets the height of the DiffView.
161func (dv *DiffView) Height(height int) *DiffView {
162 dv.height = height
163 return dv
166// Width sets the width of the DiffView.
167func (dv *DiffView) Width(width int) *DiffView {
168 dv.width = width
169 return dv
172// String returns the string representation of the DiffView.
173func (dv *DiffView) String() string {
174 if err := dv.computeDiff(); err != nil {
175 return err.Error()
176 }
177 dv.detectWidth()
179 var b strings.Builder
181 for _, h := range dv.unified.Hunks {
182 for _, l := range h.Lines {
183 content := strings.TrimSuffix(l.Content, "\n")
184 width := dv.width - leadingSymbolsSize
186 switch l.Kind {
187 case udiff.Insert:
188 b.WriteString(dv.style.InsertLine.Symbol.Render("+ "))
189 b.WriteString(dv.style.InsertLine.Code.Width(width).Render(content))
190 case udiff.Delete:
191 b.WriteString(dv.style.DeleteLine.Symbol.Render("- "))
192 b.WriteString(dv.style.DeleteLine.Code.Width(width).Render(content))
193 case udiff.Equal:
194 b.WriteString(dv.style.EqualLine.Code.Width(width + leadingSymbolsSize).Render(" " + content))
195 }
196 b.WriteRune('\n')
197 }
198 }
200 return b.String()
203func (dv *DiffView) computeDiff() error {
204 if dv.isComputed {
205 return dv.err
206 }
207 dv.isComputed = true
208 dv.edits = myers.ComputeEdits(
209 dv.before.content,
210 dv.after.content,
211 )
212 dv.unified, dv.err = udiff.ToUnifiedDiff(
213 dv.before.path,
214 dv.after.path,
215 dv.before.content,
216 dv.edits,
217 dv.contextLines,
218 )
219 return dv.err
222func (dv *DiffView) detectWidth() {
223 if dv.width > 0 {
224 return
225 }
227 for _, h := range dv.unified.Hunks {
228 for _, l := range h.Lines {
229 lineWidth := ansi.StringWidth(strings.TrimSuffix(l.Content, "\n"))
230 lineWidth += leadingSymbolsSize
231 dv.width = max(dv.width, lineWidth)
232 }
233 }