1 // Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
2 // https://github.com/sergi/go-diff
3 // See the included LICENSE file for license details.
5 // go-diff is a Go implementation of Google's Diff, Match, and Patch library
6 // Original library is Copyright (c) 2006 Google Inc.
7 // http://code.google.com/p/google-diff-match-patch/
21 // Patch represents one patch operation.
30 // String emulates GNU diff's format.
31 // Header: @@ -382,8 +481,9 @@
32 // Indices are printed as 1-based, not 0-based.
33 func (p *Patch) String() string {
34 var coords1, coords2 string
37 coords1 = strconv.Itoa(p.Start1) + ",0"
38 } else if p.Length1 == 1 {
39 coords1 = strconv.Itoa(p.Start1 + 1)
41 coords1 = strconv.Itoa(p.Start1+1) + "," + strconv.Itoa(p.Length1)
45 coords2 = strconv.Itoa(p.Start2) + ",0"
46 } else if p.Length2 == 1 {
47 coords2 = strconv.Itoa(p.Start2 + 1)
49 coords2 = strconv.Itoa(p.Start2+1) + "," + strconv.Itoa(p.Length2)
53 _, _ = text.WriteString("@@ -" + coords1 + " +" + coords2 + " @@\n")
55 // Escape the body of the patch with %xx notation.
56 for _, aDiff := range p.diffs {
59 _, _ = text.WriteString("+")
61 _, _ = text.WriteString("-")
63 _, _ = text.WriteString(" ")
66 _, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))
67 _, _ = text.WriteString("\n")
70 return unescaper.Replace(text.String())
73 // PatchAddContext increases the context until it is unique, but doesn't let the pattern expand beyond MatchMaxBits.
74 func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch {
79 pattern := text[patch.Start2 : patch.Start2+patch.Length1]
82 // Look for the first and last matches of pattern in text. If two different matches are found, increase the pattern length.
83 for strings.Index(text, pattern) != strings.LastIndex(text, pattern) &&
84 len(pattern) < dmp.MatchMaxBits-2*dmp.PatchMargin {
85 padding += dmp.PatchMargin
86 maxStart := max(0, patch.Start2-padding)
87 minEnd := min(len(text), patch.Start2+patch.Length1+padding)
88 pattern = text[maxStart:minEnd]
90 // Add one chunk for good luck.
91 padding += dmp.PatchMargin
94 prefix := text[max(0, patch.Start2-padding):patch.Start2]
96 patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...)
99 suffix := text[patch.Start2+patch.Length1 : min(len(text), patch.Start2+patch.Length1+padding)]
100 if len(suffix) != 0 {
101 patch.diffs = append(patch.diffs, Diff{DiffEqual, suffix})
104 // Roll back the start points.
105 patch.Start1 -= len(prefix)
106 patch.Start2 -= len(prefix)
107 // Extend the lengths.
108 patch.Length1 += len(prefix) + len(suffix)
109 patch.Length2 += len(prefix) + len(suffix)
114 // PatchMake computes a list of patches.
115 func (dmp *DiffMatchPatch) PatchMake(opt ...interface{}) []Patch {
117 diffs, _ := opt[0].([]Diff)
118 text1 := dmp.DiffText1(diffs)
119 return dmp.PatchMake(text1, diffs)
120 } else if len(opt) == 2 {
121 text1 := opt[0].(string)
122 switch t := opt[1].(type) {
124 diffs := dmp.DiffMain(text1, t, true)
126 diffs = dmp.DiffCleanupSemantic(diffs)
127 diffs = dmp.DiffCleanupEfficiency(diffs)
129 return dmp.PatchMake(text1, diffs)
131 return dmp.patchMake2(text1, t)
133 } else if len(opt) == 3 {
134 return dmp.PatchMake(opt[0], opt[2])
139 // patchMake2 computes a list of patches to turn text1 into text2.
140 // text2 is not provided, diffs are the delta between text1 and text2.
141 func (dmp *DiffMatchPatch) patchMake2(text1 string, diffs []Diff) []Patch {
142 // Check for null inputs not needed since null can't be passed in C#.
145 return patches // Get rid of the null case.
149 charCount1 := 0 // Number of characters into the text1 string.
150 charCount2 := 0 // Number of characters into the text2 string.
151 // Start with text1 (prepatchText) and apply the diffs until we arrive at text2 (postpatchText). We recreate the patches one by one to determine context info.
152 prepatchText := text1
153 postpatchText := text1
155 for i, aDiff := range diffs {
156 if len(patch.diffs) == 0 && aDiff.Type != DiffEqual {
157 // A new patch starts here.
158 patch.Start1 = charCount1
159 patch.Start2 = charCount2
164 patch.diffs = append(patch.diffs, aDiff)
165 patch.Length2 += len(aDiff.Text)
166 postpatchText = postpatchText[:charCount2] +
167 aDiff.Text + postpatchText[charCount2:]
169 patch.Length1 += len(aDiff.Text)
170 patch.diffs = append(patch.diffs, aDiff)
171 postpatchText = postpatchText[:charCount2] + postpatchText[charCount2+len(aDiff.Text):]
173 if len(aDiff.Text) <= 2*dmp.PatchMargin &&
174 len(patch.diffs) != 0 && i != len(diffs)-1 {
175 // Small equality inside a patch.
176 patch.diffs = append(patch.diffs, aDiff)
177 patch.Length1 += len(aDiff.Text)
178 patch.Length2 += len(aDiff.Text)
180 if len(aDiff.Text) >= 2*dmp.PatchMargin {
181 // Time for a new patch.
182 if len(patch.diffs) != 0 {
183 patch = dmp.PatchAddContext(patch, prepatchText)
184 patches = append(patches, patch)
186 // Unlike Unidiff, our patch lists have a rolling context. http://code.google.com/p/google-diff-match-patch/wiki/Unidiff Update prepatch text & pos to reflect the application of the just completed patch.
187 prepatchText = postpatchText
188 charCount1 = charCount2
193 // Update the current character count.
194 if aDiff.Type != DiffInsert {
195 charCount1 += len(aDiff.Text)
197 if aDiff.Type != DiffDelete {
198 charCount2 += len(aDiff.Text)
202 // Pick up the leftover patch if not empty.
203 if len(patch.diffs) != 0 {
204 patch = dmp.PatchAddContext(patch, prepatchText)
205 patches = append(patches, patch)
211 // PatchDeepCopy returns an array that is identical to a given an array of patches.
212 func (dmp *DiffMatchPatch) PatchDeepCopy(patches []Patch) []Patch {
213 patchesCopy := []Patch{}
214 for _, aPatch := range patches {
216 for _, aDiff := range aPatch.diffs {
217 patchCopy.diffs = append(patchCopy.diffs, Diff{
222 patchCopy.Start1 = aPatch.Start1
223 patchCopy.Start2 = aPatch.Start2
224 patchCopy.Length1 = aPatch.Length1
225 patchCopy.Length2 = aPatch.Length2
226 patchesCopy = append(patchesCopy, patchCopy)
231 // PatchApply merges a set of patches onto the text. Returns a patched text, as well as an array of true/false values indicating which patches were applied.
232 func (dmp *DiffMatchPatch) PatchApply(patches []Patch, text string) (string, []bool) {
233 if len(patches) == 0 {
234 return text, []bool{}
237 // Deep copy the patches so that no changes are made to originals.
238 patches = dmp.PatchDeepCopy(patches)
240 nullPadding := dmp.PatchAddPadding(patches)
241 text = nullPadding + text + nullPadding
242 patches = dmp.PatchSplitMax(patches)
245 // delta keeps track of the offset between the expected and actual location of the previous patch. If there are patches expected at positions 10 and 20, but the first patch was found at 12, delta is 2 and the second patch has an effective expected position of 22.
247 results := make([]bool, len(patches))
248 for _, aPatch := range patches {
249 expectedLoc := aPatch.Start2 + delta
250 text1 := dmp.DiffText1(aPatch.diffs)
253 if len(text1) > dmp.MatchMaxBits {
254 // PatchSplitMax will only provide an oversized pattern in the case of a monster delete.
255 startLoc = dmp.MatchMain(text, text1[:dmp.MatchMaxBits], expectedLoc)
257 endLoc = dmp.MatchMain(text,
258 text1[len(text1)-dmp.MatchMaxBits:], expectedLoc+len(text1)-dmp.MatchMaxBits)
259 if endLoc == -1 || startLoc >= endLoc {
260 // Can't find valid trailing context. Drop this patch.
265 startLoc = dmp.MatchMain(text, text1, expectedLoc)
268 // No match found. :(
270 // Subtract the delta for this failed patch from subsequent patches.
271 delta -= aPatch.Length2 - aPatch.Length1
275 delta = startLoc - expectedLoc
278 text2 = text[startLoc:int(math.Min(float64(startLoc+len(text1)), float64(len(text))))]
280 text2 = text[startLoc:int(math.Min(float64(endLoc+dmp.MatchMaxBits), float64(len(text))))]
283 // Perfect match, just shove the Replacement text in.
284 text = text[:startLoc] + dmp.DiffText2(aPatch.diffs) + text[startLoc+len(text1):]
286 // Imperfect match. Run a diff to get a framework of equivalent indices.
287 diffs := dmp.DiffMain(text1, text2, false)
288 if len(text1) > dmp.MatchMaxBits && float64(dmp.DiffLevenshtein(diffs))/float64(len(text1)) > dmp.PatchDeleteThreshold {
289 // The end points match, but the content is unacceptably bad.
292 diffs = dmp.DiffCleanupSemanticLossless(diffs)
294 for _, aDiff := range aPatch.diffs {
295 if aDiff.Type != DiffEqual {
296 index2 := dmp.DiffXIndex(diffs, index1)
297 if aDiff.Type == DiffInsert {
299 text = text[:startLoc+index2] + aDiff.Text + text[startLoc+index2:]
300 } else if aDiff.Type == DiffDelete {
302 startIndex := startLoc + index2
303 text = text[:startIndex] +
304 text[startIndex+dmp.DiffXIndex(diffs, index1+len(aDiff.Text))-index2:]
307 if aDiff.Type != DiffDelete {
308 index1 += len(aDiff.Text)
316 // Strip the padding off.
317 text = text[len(nullPadding) : len(nullPadding)+(len(text)-2*len(nullPadding))]
321 // PatchAddPadding adds some padding on text start and end so that edges can match something.
322 // Intended to be called only from within patchApply.
323 func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string {
324 paddingLength := dmp.PatchMargin
326 for x := 1; x <= paddingLength; x++ {
327 nullPadding += string(x)
330 // Bump all the patches forward.
331 for i := range patches {
332 patches[i].Start1 += paddingLength
333 patches[i].Start2 += paddingLength
336 // Add some padding on start of first diff.
337 if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual {
338 // Add nullPadding equality.
339 patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...)
340 patches[0].Start1 -= paddingLength // Should be 0.
341 patches[0].Start2 -= paddingLength // Should be 0.
342 patches[0].Length1 += paddingLength
343 patches[0].Length2 += paddingLength
344 } else if paddingLength > len(patches[0].diffs[0].Text) {
345 // Grow first equality.
346 extraLength := paddingLength - len(patches[0].diffs[0].Text)
347 patches[0].diffs[0].Text = nullPadding[len(patches[0].diffs[0].Text):] + patches[0].diffs[0].Text
348 patches[0].Start1 -= extraLength
349 patches[0].Start2 -= extraLength
350 patches[0].Length1 += extraLength
351 patches[0].Length2 += extraLength
354 // Add some padding on end of last diff.
355 last := len(patches) - 1
356 if len(patches[last].diffs) == 0 || patches[last].diffs[len(patches[last].diffs)-1].Type != DiffEqual {
357 // Add nullPadding equality.
358 patches[last].diffs = append(patches[last].diffs, Diff{DiffEqual, nullPadding})
359 patches[last].Length1 += paddingLength
360 patches[last].Length2 += paddingLength
361 } else if paddingLength > len(patches[last].diffs[len(patches[last].diffs)-1].Text) {
362 // Grow last equality.
363 lastDiff := patches[last].diffs[len(patches[last].diffs)-1]
364 extraLength := paddingLength - len(lastDiff.Text)
365 patches[last].diffs[len(patches[last].diffs)-1].Text += nullPadding[:extraLength]
366 patches[last].Length1 += extraLength
367 patches[last].Length2 += extraLength
373 // PatchSplitMax looks through the patches and breaks up any which are longer than the maximum limit of the match algorithm.
374 // Intended to be called only from within patchApply.
375 func (dmp *DiffMatchPatch) PatchSplitMax(patches []Patch) []Patch {
376 patchSize := dmp.MatchMaxBits
377 for x := 0; x < len(patches); x++ {
378 if patches[x].Length1 <= patchSize {
381 bigpatch := patches[x]
382 // Remove the big old patch.
383 patches = append(patches[:x], patches[x+1:]...)
386 Start1 := bigpatch.Start1
387 Start2 := bigpatch.Start2
389 for len(bigpatch.diffs) != 0 {
390 // Create one of several smaller patches.
393 patch.Start1 = Start1 - len(precontext)
394 patch.Start2 = Start2 - len(precontext)
395 if len(precontext) != 0 {
396 patch.Length1 = len(precontext)
397 patch.Length2 = len(precontext)
398 patch.diffs = append(patch.diffs, Diff{DiffEqual, precontext})
400 for len(bigpatch.diffs) != 0 && patch.Length1 < patchSize-dmp.PatchMargin {
401 diffType := bigpatch.diffs[0].Type
402 diffText := bigpatch.diffs[0].Text
403 if diffType == DiffInsert {
404 // Insertions are harmless.
405 patch.Length2 += len(diffText)
406 Start2 += len(diffText)
407 patch.diffs = append(patch.diffs, bigpatch.diffs[0])
408 bigpatch.diffs = bigpatch.diffs[1:]
410 } else if diffType == DiffDelete && len(patch.diffs) == 1 && patch.diffs[0].Type == DiffEqual && len(diffText) > 2*patchSize {
411 // This is a large deletion. Let it pass in one chunk.
412 patch.Length1 += len(diffText)
413 Start1 += len(diffText)
415 patch.diffs = append(patch.diffs, Diff{diffType, diffText})
416 bigpatch.diffs = bigpatch.diffs[1:]
418 // Deletion or equality. Only take as much as we can stomach.
419 diffText = diffText[:min(len(diffText), patchSize-patch.Length1-dmp.PatchMargin)]
421 patch.Length1 += len(diffText)
422 Start1 += len(diffText)
423 if diffType == DiffEqual {
424 patch.Length2 += len(diffText)
425 Start2 += len(diffText)
429 patch.diffs = append(patch.diffs, Diff{diffType, diffText})
430 if diffText == bigpatch.diffs[0].Text {
431 bigpatch.diffs = bigpatch.diffs[1:]
433 bigpatch.diffs[0].Text =
434 bigpatch.diffs[0].Text[len(diffText):]
438 // Compute the head context for the next patch.
439 precontext = dmp.DiffText2(patch.diffs)
440 precontext = precontext[max(0, len(precontext)-dmp.PatchMargin):]
443 // Append the end context for this patch.
444 if len(dmp.DiffText1(bigpatch.diffs)) > dmp.PatchMargin {
445 postcontext = dmp.DiffText1(bigpatch.diffs)[:dmp.PatchMargin]
447 postcontext = dmp.DiffText1(bigpatch.diffs)
450 if len(postcontext) != 0 {
451 patch.Length1 += len(postcontext)
452 patch.Length2 += len(postcontext)
453 if len(patch.diffs) != 0 && patch.diffs[len(patch.diffs)-1].Type == DiffEqual {
454 patch.diffs[len(patch.diffs)-1].Text += postcontext
456 patch.diffs = append(patch.diffs, Diff{DiffEqual, postcontext})
461 patches = append(patches[:x], append([]Patch{patch}, patches[x:]...)...)
468 // PatchToText takes a list of patches and returns a textual representation.
469 func (dmp *DiffMatchPatch) PatchToText(patches []Patch) string {
470 var text bytes.Buffer
471 for _, aPatch := range patches {
472 _, _ = text.WriteString(aPatch.String())
477 // PatchFromText parses a textual representation of patches and returns a List of Patch objects.
478 func (dmp *DiffMatchPatch) PatchFromText(textline string) ([]Patch, error) {
480 if len(textline) == 0 {
483 text := strings.Split(textline, "\n")
485 patchHeader := regexp.MustCompile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$")
490 for textPointer < len(text) {
492 if !patchHeader.MatchString(text[textPointer]) {
493 return patches, errors.New("Invalid patch string: " + text[textPointer])
497 m := patchHeader.FindStringSubmatch(text[textPointer])
499 patch.Start1, _ = strconv.Atoi(m[1])
503 } else if m[2] == "0" {
507 patch.Length1, _ = strconv.Atoi(m[2])
510 patch.Start2, _ = strconv.Atoi(m[3])
515 } else if m[4] == "0" {
519 patch.Length2, _ = strconv.Atoi(m[4])
523 for textPointer < len(text) {
524 if len(text[textPointer]) > 0 {
525 sign = text[textPointer][0]
531 line = text[textPointer][1:]
532 line = strings.Replace(line, "+", "%2b", -1)
533 line, _ = url.QueryUnescape(line)
536 patch.diffs = append(patch.diffs, Diff{DiffDelete, line})
537 } else if sign == '+' {
539 patch.diffs = append(patch.diffs, Diff{DiffInsert, line})
540 } else if sign == ' ' {
542 patch.diffs = append(patch.diffs, Diff{DiffEqual, line})
543 } else if sign == '@' {
544 // Start of next patch.
548 return patches, errors.New("Invalid patch mode '" + string(sign) + "' in: " + string(line))
553 patches = append(patches, patch)