From a327fffeec55b9aaeaeffafc02612aa531a48ec8 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Tue, 16 Feb 2016 15:47:27 +0300 Subject: [PATCH] IDEA-151702 diff: optimize chunks by searching for an unimportant line in inserted/deleted fragment --- .../diff/comparison/ChunkOptimizer.java | 65 ++++++++++++++++--- .../diff/comparison/LineComparisonUtilTest.kt | 31 +++++++++ 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/platform/diff-impl/src/com/intellij/diff/comparison/ChunkOptimizer.java b/platform/diff-impl/src/com/intellij/diff/comparison/ChunkOptimizer.java index 993b735e39d7..f762d86637ee 100644 --- a/platform/diff-impl/src/com/intellij/diff/comparison/ChunkOptimizer.java +++ b/platform/diff-impl/src/com/intellij/diff/comparison/ChunkOptimizer.java @@ -24,6 +24,7 @@ import com.intellij.diff.util.Side; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.registry.Registry; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -226,21 +227,59 @@ abstract class ChunkOptimizer { @Override protected int getShift(@NotNull Side touchSide, int equalForward, int equalBackward, @NotNull Range range1, @NotNull Range range2) { + Integer shift; + + shift = getUnchangedBoundaryShift(touchSide, equalForward, equalBackward, range1, range2, 0); + if (shift != null) return shift; + + shift = getChangedBoundaryShift(touchSide, equalForward, equalBackward, range1, range2, 0); + if (shift != null) return shift; + + shift = getUnchangedBoundaryShift(touchSide, equalForward, equalBackward, range1, range2, myThreshold); + if (shift != null) return shift; + + shift = getChangedBoundaryShift(touchSide, equalForward, equalBackward, range1, range2, myThreshold); + if (shift != null) return shift; + + return 0; + } + + /** + * search for an empty line boundary in unchanged lines + * ie: we want insertion/deletion to go right before/after of an empty line + */ + @Nullable + private Integer getUnchangedBoundaryShift(@NotNull Side touchSide, + int equalForward, int equalBackward, + @NotNull Range range1, @NotNull Range range2, + int threshold) { List touchLines = touchSide.select(myData1, myData2); int touchStart = touchSide.select(range2.start1, range2.start2); - int shiftForward = findNextUnimportantLine(touchLines, touchStart, equalForward + 1, 0); - int shiftBackward = findPrevUnimportantLine(touchLines, touchStart - 1, equalBackward + 1, 0); + int shiftForward = findNextUnimportantLine(touchLines, touchStart, equalForward + 1, threshold); + int shiftBackward = findPrevUnimportantLine(touchLines, touchStart - 1, equalBackward + 1, threshold); - if (shiftForward == -1 && shiftBackward == -1 && myThreshold != 0) { - shiftForward = findNextUnimportantLine(touchLines, touchStart, equalForward + 1, myThreshold); - shiftBackward = findPrevUnimportantLine(touchLines, touchStart - 1, equalBackward + 1, myThreshold); - } + return getShift(shiftForward, shiftBackward); + } - if (shiftForward == 0 || shiftBackward == 0) return 0; - if (shiftForward == -1 && shiftBackward == -1) return 0; + /** + * search for an empty line boundary in changed lines + * ie: we want insertion/deletion to start/end with an empty line + */ + @Nullable + private Integer getChangedBoundaryShift(@NotNull Side touchSide, + int equalForward, int equalBackward, + @NotNull Range range1, @NotNull Range range2, + int threshold) { + Side nonTouchSide = touchSide.other(); + List nonTouchLines = nonTouchSide.select(myData1, myData2); + int changeStart = nonTouchSide.select(range1.end1, range1.end2); + int changeEnd = nonTouchSide.select(range2.start1, range2.start2); - return shiftForward != -1 ? shiftForward : -shiftBackward; + int shiftForward = findNextUnimportantLine(nonTouchLines, changeStart, equalForward + 1, threshold); + int shiftBackward = findPrevUnimportantLine(nonTouchLines, changeEnd - 1, equalBackward + 1, threshold); + + return getShift(shiftForward, shiftBackward); } private static int findNextUnimportantLine(@NotNull List lines, int offset, int count, int threshold) { @@ -256,5 +295,13 @@ abstract class ChunkOptimizer { } return -1; } + + @Nullable + private static Integer getShift(int shiftForward, int shiftBackward) { + if (shiftForward == -1 && shiftBackward == -1) return null; + if (shiftForward == 0 || shiftBackward == 0) return 0; + + return shiftForward != -1 ? shiftForward : -shiftBackward; + } } } diff --git a/platform/diff-impl/tests/com/intellij/diff/comparison/LineComparisonUtilTest.kt b/platform/diff-impl/tests/com/intellij/diff/comparison/LineComparisonUtilTest.kt index fe4e76b9d7c0..843435ff81cf 100644 --- a/platform/diff-impl/tests/com/intellij/diff/comparison/LineComparisonUtilTest.kt +++ b/platform/diff-impl/tests/com/intellij/diff/comparison/LineComparisonUtilTest.kt @@ -288,6 +288,37 @@ class LineComparisonUtilTest : ComparisonUtilTestBase() { } } + fun `test prefer chunks bounded by short line`() { + lines { + ("A====_B====_o====_o====_Y====_Z====_!_A====_B====_z====_z====_Y====_Z====" - + "A====_B====_o====_o====_Y====_Z====_!_A====_B====_u====_u====_Y====_Z====_!_A====_B====_z====_z====_Y====_Z====") + default(ins(7, 7, 7)) + testAll() + } + + lines { + ("A====_B====_o====_o====_Y====_Z====_ _A====_B====_!_!_Y====_Z====" - + "A====_B====_o====_o====_Y====_Z====_ _A====_B====_u====_u====_Y====_Z====_ _A====_B====_!_!_Y====_Z====") + default(ins(7, 7, 7)) + testAll() + } + + lines { + ("A====_B====_o====_o====_Y====_Z====_!_A====_B====_ _ _Y====_Z====" - + "A====_B====_o====_o====_Y====_Z====_!_A====_B====_u====_u====_Y====_Z====_!_A====_B====_ _ _Y====_Z====") + default(ins(9, 9, 7)) + testAll() + } + } + + fun `test prefer chunks bounded by empty line - using inserted content`() { + lines { + ("e_x_A_B_z" - "x_A_B_ _q_A_B_z") + default(del(0, 0, 1), ins(4, 3, 4)) + testAll() + } + } + fun `test prefer smaller amount of chunks`() { lines() { ("X_A_X_Y_" - "X_Y_")