blob: eeae98adf763581b2758f5c68bbb96e8c6ebd2e8 [file] [log] [blame]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package diff
import (
"slices"
)
// Merge merges two valid, ordered lists of edits.
// It returns zero if there was a conflict.
//
// If corresponding edits in x and y are identical,
// they are coalesced in the result.
//
// If x and y both provide different insertions at the same point,
// the insertions from x will be first in the result.
//
// TODO(adonovan): this algorithm could be improved, for example by
// working harder to coalesce non-identical edits that share a common
// deletion or common prefix of insertion (see the tests).
// Survey the academic literature for insights.
func Merge(x, y []Edit) ([]Edit, bool) {
// Make a defensive (premature) copy of the arrays.
x = slices.Clone(x)
y = slices.Clone(y)
var merged []Edit
add := func(edit Edit) {
merged = append(merged, edit)
}
var xi, yi int
for xi < len(x) && yi < len(y) {
px := &x[xi]
py := &y[yi]
if *px == *py {
// x and y are identical: coalesce.
add(*px)
xi++
yi++
} else if px.End <= py.Start {
// x is entirely before y,
// or an insertion at start of y.
add(*px)
xi++
} else if py.End <= px.Start {
// y is entirely before x,
// or an insertion at start of x.
add(*py)
yi++
} else if px.Start < py.Start {
// x is partly before y:
// split it into a deletion and an edit.
add(Edit{px.Start, py.Start, ""})
px.Start = py.Start
} else if py.Start < px.Start {
// y is partly before x:
// split it into a deletion and an edit.
add(Edit{py.Start, px.Start, ""})
py.Start = px.Start
} else {
// x and y are unequal non-insertions
// at the same point: conflict.
return nil, false
}
}
for ; xi < len(x); xi++ {
add(x[xi])
}
for ; yi < len(y); yi++ {
add(y[yi])
}
return merged, true
}