Detecting moves

edit

A diff reports a block of lines moved elsewhere as a deletion and an insertion. Set DetectMoves to link the two: the entries of both places get the same MoveId:

var options = new TextDiffOptions { DetectMoves = true };

var result = TextDiff.ComputeDiff("a\nb\nc\nd\n", "a\nc\nd\nb\n", options);

// The deleted "b" and the inserted "b" share move 0
Assert.Equal(
    [
        new TextDiffEntry(TextDiffOperation.Equal, "a\n"),
        new TextDiffEntry(TextDiffOperation.Delete, "b\n", null, moveId: 0),
        new TextDiffEntry(TextDiffOperation.Equal, "c\n"),
        new TextDiffEntry(TextDiffOperation.Equal, "d\n"),
        new TextDiffEntry(TextDiffOperation.Insert, null, "b\n", moveId: 0),
        new TextDiffEntry(TextDiffOperation.Equal, ""),
    ],
    result.Entries);
  • The operations do not change: a moved block is still Delete entries where it was and Insert entries where it is now, so the entries rebuild both texts as usual. Code that ignores MoveId keeps working.
  • A block of several chunks shares one identifier. Identifiers start at 0 and follow the order of the deleted blocks.
  • Use MoveId to display moves differently, such as in another color, or to link the two places.

What counts as a move

  • The deleted and the inserted chunks must be equal under the comparison options: with IgnoreCase, a block moved and recased is a move. A block moved and edited is not.
  • The deleted blocks are matched in order: for each one, the longest insertion that matches from its start is taken, and the first one wins a tie. A deletion that comes earlier can claim an insertion that a longer, later deletion would also match.
  • A block that holds only whitespace, such as an empty line, is never a move: it would link unrelated places.
  • A block deleted and inserted at the same place is a change, not a move. This happens when a work limit replaces a region as a whole.
  • The chunker sets the granularity: with TextChunker.Words, a moved word is a move.

Move detection runs after the diff, on its changed entries; ComputeHierarchyDiff does not detect moves.