IDEA-156978 LineSet should handle typing in large lines more efficiently

This commit is contained in:
peter
2016-08-04 11:01:37 +02:00
parent a08e960b00
commit f380fc3f67
2 changed files with 53 additions and 9 deletions
@@ -24,8 +24,11 @@ package com.intellij.editor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.impl.LineSet;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.LightCodeInsightTestCase;
import com.intellij.testFramework.PlatformTestUtil;
import org.jetbrains.annotations.NonNls;
public class LineSetIncrementalUpdateTest extends LightCodeInsightTestCase {
@@ -124,4 +127,18 @@ public class LineSetIncrementalUpdateTest extends LightCodeInsightTestCase {
}
}.execute().throwException();
}
public void testTypingInLongLinePerformance() {
String longLine = StringUtil.repeat("a ", 200000);
PlatformTestUtil.startPerformanceTest("Document changes in a long line", 1000, () -> {
Document document = EditorFactory.getInstance().createDocument("a\n" + longLine + "<caret>" + longLine + "\n");
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
for (int i = 0; i < 1000; i++) {
int offset = i * 2 + longLine.length();
assertEquals(1, document.getLineNumber(offset));
document.insertString(offset, "b");
}
});
}).cpuBound().assertTiming();
}
}
@@ -18,6 +18,7 @@ package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.ex.LineIterator;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.util.BitUtil;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.MergingCharSequence;
import gnu.trove.TByteArrayList;
import gnu.trove.TIntArrayList;
@@ -65,11 +66,44 @@ public class LineSet{
}
@NotNull
LineSet update(@NotNull CharSequence prevText, int _start, int _end, @NotNull CharSequence replacement, boolean wholeTextReplaced) {
LineSet update(@NotNull CharSequence prevText, int start, int end, @NotNull CharSequence replacement, boolean wholeTextReplaced) {
if (myLength == 0) {
return createLineSet(replacement, !wholeTextReplaced);
}
LineSet result = isSingleLineChange(prevText, start, end, replacement)
? updateInsideOneLine(findLineIndex(start), replacement.length() - (end - start))
: genericUpdate(prevText, start, end, replacement);
if (doTest) {
MergingCharSequence newText = new MergingCharSequence(
new MergingCharSequence(prevText.subSequence(0, start), replacement),
prevText.subSequence(end, prevText.length()));
result.checkEquals(createLineSet(newText));
}
return wholeTextReplaced ? result.clearModificationFlags() : result;
}
private boolean isSingleLineChange(@NotNull CharSequence prevText, int start, int end, @NotNull CharSequence replacement) {
if (start == 0 && end == myLength && replacement.length() == 0) return false;
int startLine = findLineIndex(start);
return startLine == findLineIndex(end) && !CharArrayUtil.containLineBreaks(replacement) && !isLastEmptyLine(startLine);
}
@NotNull
private LineSet updateInsideOneLine(int line, int lengthDelta) {
int[] starts = myStarts.clone();
for (int i = line + 1; i < starts.length; i++) {
starts[i] += lengthDelta;
}
byte[] flags = myFlags.clone();
flags[line] |= MODIFIED_MASK;
return new LineSet(starts, flags, myLength + lengthDelta);
}
private LineSet genericUpdate(CharSequence prevText, int _start, int _end, CharSequence replacement) {
int startOffset = _start;
if (replacement.length() > 0 && replacement.charAt(0) == '\n' && startOffset > 0 && prevText.charAt(startOffset - 1) == '\r') {
startOffset--;
@@ -90,14 +124,7 @@ public class LineSet{
prevText.subSequence(_end, endOffset));
LineSet patch = createLineSet(replacement, true);
LineSet applied = applyPatch(startOffset, endOffset, startLine, endLine, patch);
if (doTest) {
final MergingCharSequence newText = new MergingCharSequence(
new MergingCharSequence(prevText.subSequence(0, startOffset), replacement),
prevText.subSequence(endOffset, prevText.length()));
applied.checkEquals(createLineSet(newText));
}
return wholeTextReplaced ? applied.clearModificationFlags() : applied;
return applyPatch(startOffset, endOffset, startLine, endLine, patch);
}
private void checkEquals(@NotNull LineSet fresh) {