diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java index 863678e9a309..59f853979584 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java @@ -48,6 +48,7 @@ abstract class AbstractMappingStrategy implements MappingStrategy { private EditorPosition myInitialPosition; private CacheEntry myTargetEntry; private T myEagerMatch; + private int myLastEntryOffset; AbstractMappingStrategy(@NotNull Editor editor, @NotNull SoftWrapsStorage storage, @@ -93,6 +94,9 @@ abstract class AbstractMappingStrategy implements MappingStrategy { myEagerMatch = null; myTargetEntry = null; myInitialPosition = null; + if (!myCache.isEmpty()) { + myLastEntryOffset = myCache.get(myCache.size() - 1).endOffset; + } } protected void setInitialPosition(@NotNull EditorPosition position) { @@ -118,13 +122,17 @@ abstract class AbstractMappingStrategy implements MappingStrategy { @Override public T advance(EditorPosition position, int offset) { + Document document = myEditor.getDocument(); + if (offset >= myLastEntryOffset || offset >= document.getTextLength()) { + return build(position); + } + T result = buildIfExceeds(position, offset); if (result != null) { return result; } // Update context state and continue processing. - Document document = myEditor.getDocument(); int linesDiff = document.getLineNumber(offset) - position.logicalLine; position.logicalLine += linesDiff; position.visualLine += linesDiff; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java index b4eb4852626a..9dd52e8bb21f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java @@ -362,6 +362,11 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw myAffectedByUpdateCacheEntries.clear(); myNotAffectedByUpdateTailCacheEntries.clear(); myBeforeChangeState.updateByDocumentOffsets(event.getOldStartOffset(), event.getOldEndOffset(), event.getOldLogicalLinesDiff()); + myStorage.removeInRange(event.getOldStartOffset(), event.getOldEndOffset()); + + // Advance offsets of all soft wraps that lay beyond the changed document region. + advanceSoftWrapOffsets(event.getExactOffsetsDiff(), event.getOldEndOffset()); + if (!myBeforeChangeState.cacheShouldBeUpdated) { if (DEBUG_SOFT_WRAP_PROCESSING) { log(String.format("xxxxxxxxxxxx CachingSoftWrapDataMapper.onRecalculationStart(): performing eager return")); @@ -437,7 +442,7 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw myAfterChangeState.foldedLines = 0; myCache.addAll(myNotAffectedByUpdateTailCacheEntries); } - applyStateChange(exactOffsetsDiff, event.getNewEndOffset()); + applyStateChange(exactOffsetsDiff); myAffectedByUpdateCacheEntries.clear(); myNotAffectedByUpdateTailCacheEntries.clear(); @@ -496,6 +501,32 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw log("xxxxxxxxxx dump complete. Cache size: " + myCache.size() + "\n"); } + /** + * Applies given offsets diff to all soft wraps that lay after the given offset + * + * @param offsetsDiff offset diff to apply to the target soft wraps + * @param offset offset to use for filtering soft wraps to advance. All soft wraps which offsets are strictly greater + * than the given one should be advanced + */ + private void advanceSoftWrapOffsets(int offsetsDiff, int offset) { + if (offsetsDiff == 0) { + return; + } + + int softWrapIndex = myStorage.getSoftWrapIndex(offset); + if (softWrapIndex >= 0) { + softWrapIndex++; // We want to process only soft wraps which offsets strictly more than the given one. + } + else { + softWrapIndex = -softWrapIndex - 1; + } + + List softWraps = myStorage.getSoftWraps(); + for (int i = softWrapIndex; i < softWraps.size(); i++) { + softWraps.get(i).advance(offsetsDiff); + } + } + /** * Is assumed to be called for updating {@link #myCache document dimensions cache} entries that lay after document position identified * by {@link #myAfterChangeState} in order to apply to them diff between {@link #myBeforeChangeState} and {@link #myAfterChangeState}. @@ -511,20 +542,7 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw * */ - private void applyStateChange(int offsetsDiff, int endOffset) { - // Update offsets for soft wraps that lay beyond the changed region. - if (offsetsDiff != 0 && endOffset < myEditor.getDocument().getTextLength()) { - int softWrapIndex = myStorage.getSoftWrapIndex(endOffset); - if (softWrapIndex < 0) { - softWrapIndex = -softWrapIndex - 1; - } - - List softWraps = myStorage.getSoftWraps(); - for (int i = softWrapIndex; i < softWraps.size(); i++) { - softWraps.get(i).advance(offsetsDiff); - } - } - + private void applyStateChange(int offsetsDiff) { if (myNotAffectedByUpdateTailCacheEntries.isEmpty()) { return; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java index 9d7ca74ef4e4..46ade313a82d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java @@ -137,7 +137,6 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen notifyListenersOnCacheUpdateStart(event); //CachingSoftWrapDataMapper.log("xxxxxxxxxxxxxxx Listeners notification on start is complete in " + (System.currentTimeMillis() - start) + " ms"); - myStorage.removeInRange(event.getOldStartOffset(), event.getOldEndOffset()); boolean normalCompletion = true; try { //start = System.currentTimeMillis(); diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java index 5c5c5ab3ddef..e71cf2a2c11a 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java @@ -24,6 +24,7 @@ import gnu.trove.TIntProcedure; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -522,6 +523,67 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP assertEquals(expectedVisualLine, caretModel.getVisualPosition().line); } + public void testPastingInsideSelection() throws IOException { + String text = + "this is line number 0\n" + + "this is line number 1\n" + + "this is line number 2\n" + + "this is line number 3\n" + + "this is line number 4\n" + + "this is line number 5\n" + + "this is line number 6\n" + + "this is the last line"; + + init(100, text); + int lineToSelect = 4; + myEditor.getCaretModel().moveToOffset(text.indexOf("number " + lineToSelect)); + Document document = myEditor.getDocument(); + + int startOffset = document.getLineStartOffset(lineToSelect); + int endOffset = document.getLineEndOffset(lineToSelect); + myEditor.getSelectionModel().setSelection(startOffset, endOffset); + + VisualPosition positionBefore = myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1)); + List softWrapsBefore = new ArrayList(getSoftWrapModel().getRegisteredSoftWraps()); + + copy(); + paste(); + + assertEquals(positionBefore, myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1))); + assertEquals(softWrapsBefore, getSoftWrapModel().getRegisteredSoftWraps()); + } + + public void testRemoveHugeLogicalLineThatLaysBeforeSoftWrappedLines() throws IOException { + String text = + "short line\n" + + "this is a long line that is expected to be soft wrapped into more than one or even two visual lines\n" + + "1. just a line that is long enough to be soft wrapped\n" + + "2. just a line that is long enough to be soft wrapped\n" + + "3. just a line that is long enough to be soft wrapped\n" + + "4. just a line that is long enough to be soft wrapped"; + + init(100, text); + Document document = myEditor.getDocument(); + int start = document.getLineStartOffset(1); + int end = document.getLineEndOffset(1) + 1; + int visualLinesToRemove = getSoftWrapModel().getSoftWrapsForLine(1).size() + 1; + + List positionsBefore = new ArrayList(); + for (int i = end; i < text.length(); i++) { + positionsBefore.add(myEditor.offsetToVisualPosition(i)); + } + Collections.reverse(positionsBefore); + + myEditor.getSelectionModel().setSelection(start, end); + delete(); + + // Check that all remembered positions are just shifted to expected number of visual lines. + for (int i = start; i < document.getTextLength(); i++) { + VisualPosition position = positionsBefore.remove(positionsBefore.size() - 1); + assertEquals(new VisualPosition(position.line - visualLinesToRemove, position.column), myEditor.offsetToVisualPosition(i)); + } + } + private static TIntHashSet collectSoftWrapStartOffsets(int documentLine) { TIntHashSet result = new TIntHashSet(); for (SoftWrap softWrap : myEditor.getSoftWrapModel().getSoftWrapsForLine(documentLine)) { diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java index 4576b3f834f6..f10be1e141b6 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java @@ -482,6 +482,14 @@ public abstract class LightPlatformCodeInsightTestCase extends LightPlatformTest protected static void end() { doAction(IdeActions.ACTION_EDITOR_MOVE_LINE_END); } + + protected static void copy() { + doAction(IdeActions.ACTION_EDITOR_COPY); + } + + protected static void paste() { + doAction(IdeActions.ACTION_EDITOR_PASTE); + } private static void doAction(@NotNull String actionId) { EditorActionManager actionManager = EditorActionManager.getInstance();