Skip to content

Commit 3139fdc

Browse files
committed
fix: reimplement chunk calculation for line diff
1 parent 4e46423 commit 3139fdc

File tree

1 file changed

+104
-24
lines changed

1 file changed

+104
-24
lines changed

addon/merge/merge.js

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@
422422
}
423423

424424
function findAlignedLines(dv, other) {
425+
if (dv.mv.options.chunkPerLine) return findAlignedLinesByChunks(dv.chunks)
425426
var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
426427
if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
427428
var n = other.chunks[i].editTo
@@ -435,12 +436,19 @@
435436
if (other)
436437
mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
437438

438-
if (!dv.mv.options.chunkPerLine) return result
439-
return result.map(function (r) {
440-
if (r[1] !== null) return r
441-
r[1] = r[0]
442-
return r
443-
})
439+
return result
440+
}
441+
442+
/**
443+
* @param {Chunk[]} chunks
444+
*/
445+
function findAlignedLinesByChunks(chunks) {
446+
const alignedEnds = []
447+
for (var i = 0; i < chunks.length; i++) {
448+
const chunk = chunks[i]
449+
alignedEnds.push([chunk.editTo, chunk.origTo, null])
450+
}
451+
return alignedEnds
444452
}
445453

446454
function alignChunks(dv, force) {
@@ -706,6 +714,7 @@
706714
}
707715

708716
function getChunks(diff, chunkPerLine) {
717+
if (chunkPerLine) return getLineChunks(diff);
709718
var chunks = [];
710719
if (!diff.length) return chunks;
711720
var startEdit = 0, startOrig = 0;
@@ -730,30 +739,101 @@
730739
if (startEdit <= edit.line || startOrig <= orig.line)
731740
chunks.push({origFrom: startOrig, origTo: orig.line + 1,
732741
editFrom: startEdit, editTo: edit.line + 1});
733-
if (chunkPerLine) return separateChunks(chunks);
734742
return chunks
735743
}
736744

737-
// separate chunks if original and edited text are
738-
function separateChunks(chunks) {
739-
const newChunks = [];
740-
for (var i = 0; i < chunks.length; i++) {
741-
const origDiff = chunks[i].origTo - chunks[i].origFrom;
742-
const editDiff = chunks[i].editTo - chunks[i].editFrom;
743-
if (origDiff !== editDiff) {
744-
newChunks.push(chunks[i]);
745-
} else {
746-
for (var j = 0; j < origDiff; j++) {
747-
newChunks.push({
748-
origFrom: chunks[i].origFrom + j,
749-
origTo: chunks[i].origFrom + j + 1,
750-
editFrom: chunks[i].editFrom + j,
751-
editTo: chunks[i].editFrom + j + 1,
752-
});
745+
function getLineChunks(diffs) {
746+
const chunks = []
747+
var origLine = 0
748+
var editLine = 0
749+
for (var i = 0; i < diffs.length; i++) {
750+
const diff = diffs[i]
751+
752+
const lines = countChar(diff[1], '\n')
753+
const origStart = origLine
754+
const editStart = editLine
755+
switch(diff[0]) {
756+
case DIFF_EQUAL: {
757+
origLine += lines
758+
editLine += lines
759+
break
760+
}
761+
case DIFF_INSERT: {
762+
editLine += lines
763+
break
764+
}
765+
case DIFF_DELETE: {
766+
origLine += lines
767+
break
753768
}
754769
}
770+
const origEnd = origLine + 1
771+
const editEnd = editLine + 1
772+
773+
if (diff[0] === DIFF_EQUAL) continue
774+
chunks.push({
775+
origFrom: origStart,
776+
origTo: origEnd,
777+
editFrom: editStart,
778+
editTo: editEnd,
779+
})
755780
}
756-
return newChunks;
781+
if (chunks.length === 0) return chunks
782+
783+
// combine overlapping chunks
784+
const origCombined = combineChunks(
785+
chunks,
786+
function(prev, curr) {
787+
const left = prev.origFrom
788+
const right = prev.origTo
789+
return (curr.origFrom >= left && curr.origFrom < right) ||
790+
(curr.origTo >= left && curr.origTo < right)
791+
}
792+
)
793+
const editCombined = combineChunks(
794+
origCombined,
795+
function(prev, curr) {
796+
const left = prev.editFrom
797+
const right = prev.editTo
798+
return (curr.editFrom >= left && curr.editFrom < right) ||
799+
(curr.editTo >= left && curr.editTo < right)
800+
}
801+
)
802+
803+
return editCombined
804+
}
805+
806+
/**
807+
* @typedef {{
808+
* origFrom: number
809+
* origTo: number
810+
* editFrom: number
811+
* editTo: number
812+
* }} Chunk
813+
* @param {Chunk[]} chunks
814+
* @param {(prev: Chunk, curr: Chunk) => boolean} hasOverlap
815+
*/
816+
function combineChunks(chunks, hasOverlap) {
817+
if (chunks.length === 0) return []
818+
const combined = [chunks[0]]
819+
for (var i = 1; i < chunks.length; i++) {
820+
const lastIdx = combined.length - 1
821+
const overlapping = hasOverlap(combined[lastIdx], chunks[i])
822+
if (!overlapping) combined.push(chunks[i])
823+
else {
824+
combined[lastIdx].origFrom = Math.min(combined[lastIdx].origFrom, chunks[i].origFrom)
825+
combined[lastIdx].origTo = Math.max(combined[lastIdx].origTo, chunks[i].origTo)
826+
combined[lastIdx].editFrom = Math.min(combined[lastIdx].editFrom, chunks[i].editFrom)
827+
combined[lastIdx].editTo = Math.max(combined[lastIdx].editTo, chunks[i].editTo)
828+
}
829+
}
830+
return combined
831+
}
832+
833+
function countChar(str, ch) {
834+
var occur = 0
835+
for (var i = 0; i < str.length; i++) if (str[i] === ch) occur++
836+
return occur
757837
}
758838

759839
function endOfLineClean(diff, i) {

0 commit comments

Comments
 (0)