@@ -42,6 +42,10 @@ type DiffView struct {
43 unified udiff.UnifiedDiff
43 unified udiff.UnifiedDiff
44 edits []udiff.Edit
44 edits []udiff.Edit
46+ splitHunks []splitHunk
47// New creates a new DiffView with default settings.
51// New creates a new DiffView with default settings.
@@ -124,7 +128,20 @@ func (dv *DiffView) String() string {
124 if err := dv.computeDiff(); err != nil {
128 if err := dv.computeDiff(); err != nil {
125 return err.Error()
129 return err.Error()
127− dv.detectWidth()
131+
134+ return dv.renderUnified()
136+ return dv.renderSplit()
138+ panic("unknown diffview layout")
142+// renderUnified renders the unified diff view as a string.
143+func (dv *DiffView) renderUnified() string {
144+ dv.detectUnifiedWidth()
129 codeWidth := dv.width - leadingSymbolsSize
146 codeWidth := dv.width - leadingSymbolsSize
130 beforeNumDigits, afterNumDigits := dv.lineNumberDigits()
147 beforeNumDigits, afterNumDigits := dv.lineNumberDigits()
@@ -178,6 +195,88 @@ func (dv *DiffView) String() string {
178 return b.String()
195 return b.String()
198+// renderSplit renders the split (side-by-side) diff view as a string.
199+func (dv *DiffView) renderSplit() string {
200+ dv.convertDiffToSplit()
201+ dv.detectSplitWidth()
203+ beforeNumDigits, afterNumDigits := dv.lineNumberDigits()
205+ var b strings.Builder
207+ for i, h := range dv.splitHunks {
209+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", beforeNumDigits)))
211+ b.WriteString(dv.style.DividerLine.Code.Width(dv.beforeCodeWidth + leadingSymbolsSize).Render(dv.hunkLineFor(dv.unified.Hunks[i])))
213+ b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", afterNumDigits)))
215+ b.WriteString(dv.style.DividerLine.Code.Width(dv.afterCodeWidth + leadingSymbolsSize).Render(" "))
218+ beforeLine := h.fromLine
219+ afterLine := h.toLine
221+ for _, l := range h.lines {
222+ var beforeContent string
223+ var afterContent string
224+ if l.before != nil {
225+ beforeContent = strings.TrimSuffix(l.before.Content, "\n")
228+ afterContent = strings.TrimSuffix(l.after.Content, "\n")
232+ case l.before == nil:
234+ b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", beforeNumDigits)))
236+ b.WriteString(dv.style.MissingLine.Code.Width(dv.beforeCodeWidth + leadingSymbolsSize).Render(" "))
237+ case l.before.Kind == udiff.Equal:
239+ b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, beforeNumDigits)))
241+ b.WriteString(dv.style.EqualLine.Code.Width(dv.beforeCodeWidth + leadingSymbolsSize).Render(" " + beforeContent))
243+ case l.before.Kind == udiff.Delete:
245+ b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, beforeNumDigits)))
247+ b.WriteString(dv.style.DeleteLine.Symbol.Render("- "))
248+ b.WriteString(dv.style.DeleteLine.Code.Width(dv.beforeCodeWidth).Render(beforeContent))
253+ case l.after == nil:
255+ b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", afterNumDigits)))
257+ b.WriteString(dv.style.MissingLine.Code.Width(dv.afterCodeWidth + leadingSymbolsSize).Render(" "))
258+ case l.after.Kind == udiff.Equal:
260+ b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, afterNumDigits)))
262+ b.WriteString(dv.style.EqualLine.Code.Width(dv.afterCodeWidth + leadingSymbolsSize).Render(" " + afterContent))
264+ case l.after.Kind == udiff.Insert:
266+ b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, afterNumDigits)))
268+ b.WriteString(dv.style.InsertLine.Symbol.Render("+ "))
269+ b.WriteString(dv.style.InsertLine.Code.Width(dv.afterCodeWidth).Render(afterContent))
181func (dv *DiffView) computeDiff() error {
280func (dv *DiffView) computeDiff() error {
182 if dv.isComputed {
281 if dv.isComputed {
183 return dv.err
282 return dv.err
@@ -236,7 +335,7 @@ func (dv *DiffView) hunkShownLines(h *udiff.Hunk) (before, after int) {
239−func (dv *DiffView) detectWidth() {
338+func (dv *DiffView) detectUnifiedWidth() {
240 if dv.width > 0 {
339 if dv.width > 0 {
@@ -251,3 +350,36 @@ func (dv *DiffView) detectWidth() {
354+func (dv *DiffView) convertDiffToSplit() {
355+ dv.splitHunks = make([]splitHunk, len(dv.unified.Hunks))
356+ for i, h := range dv.unified.Hunks {
357+ dv.splitHunks[i] = hunkToSplit(h)
361+func (dv *DiffView) detectSplitWidth() {
366+ for i, h := range dv.splitHunks {
367+ shownLines := ansi.StringWidth(dv.hunkLineFor(dv.unified.Hunks[i]))
369+ for _, l := range h.lines {
371+ if l.before != nil {
372+ codeWidth := ansi.StringWidth(strings.TrimSuffix(l.before.Content, "\n")) + 1
373+ dv.beforeCodeWidth = max(dv.beforeCodeWidth, codeWidth, shownLines)
374+ lineWidth += codeWidth
377+ codeWidth := ansi.StringWidth(strings.TrimSuffix(l.after.Content, "\n")) + 1
378+ dv.afterCodeWidth = max(dv.afterCodeWidth, codeWidth, shownLines)
379+ lineWidth += codeWidth
381+ lineWidth += leadingSymbolsSize * 2
382+ dv.width = max(dv.width, lineWidth, shownLines)