IDEA-62425 Soft wraps cause editor weirdness (displayed text does not correspond to that displayed under caret)

1. Corrected trailing soft wraps offsets update on document modification;
2. Corrected document dimensions mapping algorithm;
3. Corresponding tests are added;
This commit is contained in:
Denis Zhdanov
2010-12-07 13:52:08 +03:00
parent b8a10e5733
commit f159e97d8f
5 changed files with 112 additions and 17 deletions
@@ -48,6 +48,7 @@ abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
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<T> implements MappingStrategy<T> {
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<T> implements MappingStrategy<T> {
@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;
@@ -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<SoftWrapImpl> 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
* </ol>
</pre>
*/
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<SoftWrapImpl> 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;
}
@@ -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();
@@ -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<SoftWrap> softWrapsBefore = new ArrayList<SoftWrap>(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<VisualPosition> positionsBefore = new ArrayList<VisualPosition>();
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)) {
@@ -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();