on main help sign in
feat(diffview): implement ability to set horizontal offset
789a6b3Andrey Nering
internal/exp/diffview/Taskfile.yaml+20
@@ -60,3 +60,23 @@ tasks:
60 - for: sources
60 - for: sources
61 cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
61 cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
62 silent: true
62 silent: true
63+
64+ test:print:xoffset:unified:
65+ desc: Print golden files for debugging
66+ method: none
67+ sources:
68+ - ./testdata/TestDiffViewXOffset/Unified/*.golden
69+ cmds:
70+ - for: sources
71+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
72+ silent: true
73+
74+ test:print:xoffset:split:
75+ desc: Print golden files for debugging
76+ method: none
77+ sources:
78+ - ./testdata/TestDiffViewXOffset/Split/*.golden
79+ cmds:
80+ - for: sources
81+ cmd: echo && echo "------- {{.ITEM}} -------" && echo && cat {{.ITEM}}
82+ silent: true
internal/exp/diffview/diffview.go+22−7
@@ -39,6 +39,7 @@ type DiffView struct {
39 highlight bool
39 highlight bool
40 height int
40 height int
41 width int
41 width int
42+ xOffset int
42 style Style
43 style Style
43
44
44 isComputed bool
45 isComputed bool
@@ -130,6 +131,12 @@ func (dv *DiffView) Width(width int) *DiffView {
130 return dv
131 return dv
131}
132}
132
133
134+// XOffset sets the horizontal offset for the DiffView.
135+func (dv *DiffView) XOffset(xOffset int) *DiffView {
136+ dv.xOffset = xOffset
137+ return dv
138+}
139+
133// String returns the string representation of the DiffView.
140// String returns the string representation of the DiffView.
134func (dv *DiffView) String() string {
141func (dv *DiffView) String() string {
135 if err := dv.computeDiff(); err != nil {
142 if err := dv.computeDiff(); err != nil {
@@ -323,8 +330,11 @@ outer:
323 }
330 }
324
331
325 content := strings.TrimSuffix(l.Content, "\n")
332 content := strings.TrimSuffix(l.Content, "\n")
333+ content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
326 content = ansi.Truncate(content, dv.codeWidth, "…")
334 content = ansi.Truncate(content, dv.codeWidth, "…")
327
335
336+ leadingEllipsis := dv.xOffset > 0 && strings.TrimSpace(content) != ""
337+
328 switch l.Kind {
338 switch l.Kind {
329 case udiff.Equal:
339 case udiff.Equal:
330 if dv.lineNumbers {
340 if dv.lineNumbers {
@@ -332,7 +342,7 @@ outer:
332 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
342 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
333 }
343 }
334 b.WriteString(fullContentStyle.Render(
344 b.WriteString(fullContentStyle.Render(
335− dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" " + content),
345+ dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
336 ))
346 ))
337 beforeLine++
347 beforeLine++
338 afterLine++
348 afterLine++
@@ -342,7 +352,7 @@ outer:
342 b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
352 b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
343 }
353 }
344 b.WriteString(fullContentStyle.Render(
354 b.WriteString(fullContentStyle.Render(
345− dv.style.InsertLine.Symbol.Render("+ ") +
355+ dv.style.InsertLine.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
346 dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content),
356 dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content),
347 ))
357 ))
348 afterLine++
358 afterLine++
@@ -352,7 +362,7 @@ outer:
352 b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
362 b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
353 }
363 }
354 b.WriteString(fullContentStyle.Render(
364 b.WriteString(fullContentStyle.Render(
355− dv.style.DeleteLine.Symbol.Render("- ") +
365+ dv.style.DeleteLine.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
356 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content),
366 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content),
357 ))
367 ))
358 beforeLine++
368 beforeLine++
@@ -427,13 +437,18 @@ outer:
427 var afterContent string
437 var afterContent string
428 if l.before != nil {
438 if l.before != nil {
429 beforeContent = strings.TrimSuffix(l.before.Content, "\n")
439 beforeContent = strings.TrimSuffix(l.before.Content, "\n")
440+ beforeContent = ansi.GraphemeWidth.Cut(beforeContent, dv.xOffset, len(beforeContent))
430 beforeContent = ansi.Truncate(beforeContent, dv.codeWidth, "…")
441 beforeContent = ansi.Truncate(beforeContent, dv.codeWidth, "…")
431 }
442 }
432 if l.after != nil {
443 if l.after != nil {
433 afterContent = strings.TrimSuffix(l.after.Content, "\n")
444 afterContent = strings.TrimSuffix(l.after.Content, "\n")
445+ afterContent = ansi.GraphemeWidth.Cut(afterContent, dv.xOffset, len(afterContent))
434 afterContent = ansi.Truncate(afterContent, dv.codeWidth+btoi(dv.extraColOnAfter), "…")
446 afterContent = ansi.Truncate(afterContent, dv.codeWidth+btoi(dv.extraColOnAfter), "…")
435 }
447 }
436
448
449+ leadingBeforeEllipsis := dv.xOffset > 0 && strings.TrimSpace(beforeContent) != ""
450+ leadingAfterEllipsis := dv.xOffset > 0 && strings.TrimSpace(afterContent) != ""
451+
437 switch {
452 switch {
438 case l.before == nil:
453 case l.before == nil:
439 if dv.lineNumbers {
454 if dv.lineNumbers {
@@ -447,7 +462,7 @@ outer:
447 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
462 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
448 }
463 }
449 b.WriteString(beforeFullContentStyle.Render(
464 b.WriteString(beforeFullContentStyle.Render(
450− dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" " + beforeContent),
465+ dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingBeforeEllipsis, " …", " ") + beforeContent),
451 ))
466 ))
452 beforeLine++
467 beforeLine++
453 case l.before.Kind == udiff.Delete:
468 case l.before.Kind == udiff.Delete:
@@ -455,7 +470,7 @@ outer:
455 b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
470 b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
456 }
471 }
457 b.WriteString(beforeFullContentStyle.Render(
472 b.WriteString(beforeFullContentStyle.Render(
458− dv.style.DeleteLine.Symbol.Render("- ") +
473+ dv.style.DeleteLine.Symbol.Render(ternary(leadingBeforeEllipsis, "-…", "- ")) +
459 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent),
474 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent),
460 ))
475 ))
461 beforeLine++
476 beforeLine++
@@ -474,7 +489,7 @@ outer:
474 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
489 b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
475 }
490 }
476 b.WriteString(afterFullContentStyle.Render(
491 b.WriteString(afterFullContentStyle.Render(
477− dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" " + afterContent),
492+ dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingAfterEllipsis, " …", " ") + afterContent),
478 ))
493 ))
479 afterLine++
494 afterLine++
480 case l.after.Kind == udiff.Insert:
495 case l.after.Kind == udiff.Insert:
@@ -482,7 +497,7 @@ outer:
482 b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
497 b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
483 }
498 }
484 b.WriteString(afterFullContentStyle.Render(
499 b.WriteString(afterFullContentStyle.Render(
485− dv.style.InsertLine.Symbol.Render("+ ") +
500+ dv.style.InsertLine.Symbol.Render(ternary(leadingAfterEllipsis, "+…", "+ ")) +
486 dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent),
501 dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent),
487 ))
502 ))
488 afterLine++
503 afterLine++
internal/exp/diffview/diffview_test.go+23
@@ -193,6 +193,29 @@ func TestDiffViewHeight(t *testing.T) {
193 }
193 }
194}
194}
195
195
196+func TestDiffViewXOffset(t *testing.T) {
197+ for layoutName, layoutFunc := range LayoutFuncs {
198+ t.Run(layoutName, func(t *testing.T) {
199+ for xOffset := range 21 {
200+ t.Run(fmt.Sprintf("XOffsetOf%02d", xOffset), func(t *testing.T) {
201+ dv := diffview.New().
202+ Before("main.go", TestDefaultBefore).
203+ After("main.go", TestDefaultAfter).
204+ Style(diffview.DefaultLightStyle).
205+ Width(60).
206+ XOffset(xOffset)
207+ dv = layoutFunc(dv)
208+
209+ output := dv.String()
210+ golden.RequireEqual(t, []byte(output))
211+
212+ assertLineWidth(t, 60, output)
213+ })
214+ }
215+ })
216+ }
217+}
218+
196func assertLineWidth(t *testing.T, expected int, output string) {
219func assertLineWidth(t *testing.T, expected int, output string) {
197 var lineWidth int
220 var lineWidth int
198 for line := range strings.SplitSeq(output, "\n") {
221 for line := range strings.SplitSeq(output, "\n") {
internal/exp/diffview/util.go+7
@@ -30,3 +30,10 @@ func btoi(b bool) int {
30 }
30 }
31 return 0
31 return 0
32}
32}
33+
34+func ternary[T any](cond bool, t, f T) T {
35+ if cond {
36+ return t
37+ }
38+ return f
39+}