1 // Copyright (C) The Lightning Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
12 "github.com/sergi/go-diff/diffmatchpatch"
19 Left string // base preceding an indel, if Ref or New is empty
22 func (v *Variant) String() string {
24 case len(v.New) == 0 && len(v.Ref) == 0:
25 return fmt.Sprintf("%d=", v.Position)
26 case len(v.New) == 0 && len(v.Ref) == 1:
27 return fmt.Sprintf("%ddel", v.Position)
29 return fmt.Sprintf("%d_%ddel", v.Position, v.Position+len(v.Ref)-1)
30 case len(v.Ref) == 1 && len(v.New) == 1:
31 return fmt.Sprintf("%d%s>%s", v.Position, v.Ref, v.New)
33 return fmt.Sprintf("%d_%dins%s", v.Position-1, v.Position, v.New)
34 case len(v.Ref) == 1 && len(v.New) > 0:
35 return fmt.Sprintf("%ddelins%s", v.Position, v.New)
37 return fmt.Sprintf("%d_%ddelins%s", v.Position, v.Position+len(v.Ref)-1, v.New)
41 // PadLeft returns a Variant that is equivalent to v but (if possible)
42 // uses the stashed preceding base (the Left field) to avoid having a
43 // non-empty Ref or New part, even for an insertion or deletion.
45 // For example, if v is {Position: 45, Ref: "", New: "A"}, PadLeft
46 // might return {Position: 44, Ref: "T", New: "TA"}.
47 func (v *Variant) PadLeft() Variant {
48 if len(v.Ref) == 0 || len(v.New) == 0 {
50 Position: v.Position - len(v.Left),
59 func Diff(a, b string, timeout time.Duration) ([]Variant, bool) {
60 dmp := diffmatchpatch.New()
61 var deadline time.Time
63 deadline = time.Now().Add(timeout)
65 diffs := dmp.DiffBisect(a, b, deadline)
67 if timeout > 0 && time.Now().After(deadline) {
70 diffs = cleanup(dmp.DiffCleanupEfficiency(diffs))
72 var variants []Variant
73 for i := 0; i < len(diffs); {
74 left := "" // last char before an insertion or deletion
75 for ; i < len(diffs) && diffs[i].Type == diffmatchpatch.DiffEqual; i++ {
76 pos += len(diffs[i].Text)
77 if tlen := len(diffs[i].Text); tlen > 0 {
78 left = diffs[i].Text[tlen-1:]
84 v := Variant{Position: pos, Left: left}
85 for ; i < len(diffs) && diffs[i].Type != diffmatchpatch.DiffEqual; i++ {
86 if diffs[i].Type == diffmatchpatch.DiffDelete {
87 v.Ref += diffs[i].Text
89 v.New += diffs[i].Text
93 variants = append(variants, v)
96 return variants, timedOut
99 func cleanup(in []diffmatchpatch.Diff) (out []diffmatchpatch.Diff) {
100 out = make([]diffmatchpatch.Diff, 0, len(in))
101 for i := 0; i < len(in); i++ {
103 // Merge consecutive entries of same type (e.g.,
104 // "insert A; insert B")
105 for i < len(in)-1 && in[i].Type == in[i+1].Type {
106 d.Text += in[i+1].Text
111 in, out = out, make([]diffmatchpatch.Diff, 0, len(in))
112 for i := 0; i < len(in); i++ {
114 // diffmatchpatch solves diff("AAX","XTX") with
115 // [delAA,=X,insTX] but we prefer to spell it
118 // So, when we see a [del,=,ins] sequence where the
119 // "=" part is a suffix of the "ins" part -- e.g.,
120 // [delAAA,=CGG,insTTTCGG] -- we rearrange it to the
121 // equivalent spelling [delAAA,insCGGTTT,=CGG].
123 d.Type == diffmatchpatch.DiffDelete &&
124 in[i+1].Type == diffmatchpatch.DiffEqual &&
125 in[i+2].Type == diffmatchpatch.DiffInsert &&
126 strings.HasSuffix(in[i+2].Text, in[i+1].Text) {
127 eq, ins := in[i+1], in[i+2]
128 ins.Text = eq.Text + ins.Text[:len(ins.Text)-len(eq.Text)]
132 // diffmatchpatch solves diff("AXX","XXX") with
133 // [delA,=XX,insX] but we prefer to spell it
136 // So, when we see a [del,=,ins] sequence that has the
137 // same effect after swapping the "=" and "ins" parts,
140 d.Type == diffmatchpatch.DiffDelete &&
141 in[i+1].Type == diffmatchpatch.DiffEqual &&
142 in[i+2].Type == diffmatchpatch.DiffInsert &&
143 in[i+1].Text+in[i+2].Text == in[i+2].Text+in[i+1].Text {
144 in[i+2], in[i+1] = in[i+1], in[i+2]
151 func Less(a, b Variant) bool {
152 if a.Position != b.Position {
153 return a.Position < b.Position
154 } else if a.New != b.New {