Change:
} } <ins> function intersectRanges(ranges1: LineRange[], ranges2: LineRange[]): LineRange[] { const result: LineRange[] = []; let i1 = 0; let i2 = 0; while (i1 < ranges1.length && i2 < ranges2.length) { const r1 = ranges1[i1]; const r2 = ranges2[i2]; const i = r1.intersect(r2); if (i && !i.isEmpty) { result.push(i); } if (r1.endLineNumberExclusive < r2.endLineNumberExclusive) { i1++; } else { i2++; } } return result; } // TODO make this fast class LineRangeSet { private readonly _normalizedRanges: LineRange[] = []; addRange(range: LineRange): void { // Idea: Find joinRange such that: // replaceRange = _normalizedRanges.replaceRange(joinRange, range.joinAll(joinRange.map(idx => this._normalizedRanges[idx]))) // idx of first element that touches range or that is after range const joinRangeStartIdx = mapMinusOne(this._normalizedRanges.findIndex(r => r.endLineNumberExclusive >= range.startLineNumber), this._normalizedRanges.length); // idx of element after { last element that touches range or that is before range } const joinRangeEndIdxExclusive = findLastIndex(this._normalizedRanges, r => r.startLineNumber <= range.endLineNumberExclusive) + 1; if (joinRangeStartIdx === joinRangeEndIdxExclusive) { // If there is no element that touches range, then joinRangeStartIdx === joinRangeEndIdxExclusive and that value is the index of the element after range this._normalizedRanges.splice(joinRangeStartIdx, 0, range); } else if (joinRangeStartIdx === joinRangeEndIdxExclusive - 1) { // Else, there is an element that touches range and in this case it is both the first and last element. Thus we can replace it const joinRange = this._normalizedRanges[joinRangeStartIdx]; this._normalizedRanges[joinRangeStartIdx] = joinRange.join(range); } else { // First and last element are different - we need to replace the entire range const joinRange = this._normalizedRanges[joinRangeStartIdx].join(this._normalizedRanges[joinRangeEndIdxExclusive - 1]).join(range); this._normalizedRanges.splice(joinRangeStartIdx, joinRangeEndIdxExclusive - joinRangeStartIdx, joinRange); } } intersects(range: LineRange): boolean { for (const r of this._normalizedRanges) { if (r.intersectsStrict(range)) { return true; } } return false; } /** * Subtracts all ranges in this set from `range` and returns the result. */ subtractFrom(range: LineRange): LineRange[] { // idx of first element that touches range or that is after range const joinRangeStartIdx = mapMinusOne(this._normalizedRanges.findIndex(r => r.endLineNumberExclusive >= range.startLineNumber), this._normalizedRanges.length); // idx of element after { last element that touches range or that is before range } const joinRangeEndIdxExclusive = findLastIndex(this._normalizedRanges, r => r.startLineNumber <= range.endLineNumberExclusive) + 1; if (joinRangeStartIdx === joinRangeEndIdxExclusive) { return [range]; } const result: LineRange[] = []; let startLineNumber = range.startLineNumber; for (let i = joinRangeStartIdx; i < joinRangeEndIdxExclusive; i++) { const r = this._normalizedRanges[i]; if (r.startLineNumber > startLineNumber) { result.push(new LineRange(startLineNumber, r.startLineNumber)); } startLineNumber = r.endLineNumberExclusive; } if (startLineNumber < range.endLineNumberExclusive) { result.push(new LineRange(startLineNumber, range.endLineNumberExclusive)); } return result; } } function mapMinusOne(idx: number, mapTo: number): number { return idx === -1 ? mapTo : idx; } </ins> function coverFullWords(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { const additional: SequenceDiff[] = [];

Discussion:
When a code fragment of at least 3 lines of code is deleted somewhere in inserted somewhere else without modifications, this move should be detected. . (select latest -dev version to verify)