mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
LineSet: move test-only code into the test (replaced with a property-based one), fix found \r treatment bugs
This commit is contained in:
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.intellij.java.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 {
|
||||
@NonNls private static final String STRING1 = "\naaa\n";
|
||||
@NonNls private static final String STRING2 = "\n \n";
|
||||
@NonNls private static final String STRING3 = " \n \n ";
|
||||
@NonNls private static final String STRING4 = "\n ";
|
||||
@NonNls private static final String STRING5 = " \n";
|
||||
@NonNls private static final String STRING6 = "\n";
|
||||
|
||||
public void testInsert() {
|
||||
LineSet.setTestingMode(true);
|
||||
try {
|
||||
configureFromFileText("test.jsp","<caret>");
|
||||
doInsert();
|
||||
|
||||
configureFromFileText("test.jsp","aaa\nb<caret>bb\n");
|
||||
doInsert();
|
||||
}
|
||||
finally {
|
||||
LineSet.setTestingMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDelete() {
|
||||
|
||||
try {
|
||||
configureFromFileText("test.jsp","aaa\n<caret>bbb\n");
|
||||
doInsert();
|
||||
LineSet.setTestingMode(true);
|
||||
|
||||
doDelete();
|
||||
LineSet.setTestingMode(false);
|
||||
|
||||
configureFromFileText("test.jsp","<caret>");
|
||||
doInsert();
|
||||
LineSet.setTestingMode(true);
|
||||
doDelete();
|
||||
LineSet.setTestingMode(false);
|
||||
}
|
||||
finally {
|
||||
LineSet.setTestingMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doInsert() {
|
||||
WriteCommandAction.writeCommandAction(getProject()).run(() -> {
|
||||
Document document = myEditor.getDocument();
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING6);
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING5);
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING4);
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING3);
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING2);
|
||||
document.insertString(myEditor.getCaretModel().getOffset(), STRING1);
|
||||
});
|
||||
}
|
||||
|
||||
private static void doDelete() {
|
||||
WriteCommandAction.writeCommandAction(getProject()).run(() -> {
|
||||
Document document = myEditor.getDocument();
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING1.length()
|
||||
);
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING2.length()
|
||||
);
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING3.length()
|
||||
);
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING4.length()
|
||||
);
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING5.length()
|
||||
);
|
||||
|
||||
document.deleteString(
|
||||
myEditor.getCaretModel().getOffset(),
|
||||
myEditor.getCaretModel().getOffset() + STRING6.length()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
});
|
||||
}).assertTiming();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import com.intellij.util.text.MergingCharSequence;
|
||||
import gnu.trove.TByteArrayList;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -73,16 +72,31 @@ public class LineSet{
|
||||
return createLineSet(replacement, !wholeTextReplaced);
|
||||
}
|
||||
|
||||
// if we're breaking or creating a '\r\n' pair, expand the changed range to include it fully
|
||||
CharSequence newText = StringUtil.replaceSubSequence(prevText, start, end, replacement);
|
||||
if (hasChar(prevText, start - 1, '\r') &&
|
||||
(hasChar(prevText, start, '\n') != hasChar(newText, start, '\n'))) {
|
||||
replacement = new MergingCharSequence("\r", replacement);
|
||||
start--;
|
||||
}
|
||||
|
||||
if (hasChar(prevText, end, '\n') &&
|
||||
(hasChar(prevText, end -1, '\r') != hasChar(newText, start + replacement.length() - 1, '\r'))) {
|
||||
replacement = new MergingCharSequence(replacement, "\n");
|
||||
end++;
|
||||
}
|
||||
|
||||
LineSet result = isSingleLineChange(start, end, replacement)
|
||||
? updateInsideOneLine(findLineIndex(start), replacement.length() - (end - start))
|
||||
: genericUpdate(prevText, start, end, replacement);
|
||||
|
||||
if (doTest) {
|
||||
result.checkEquals(createLineSet(StringUtil.replaceSubSequence(prevText, start, end, replacement)));
|
||||
}
|
||||
return wholeTextReplaced ? result.clearModificationFlags() : result;
|
||||
}
|
||||
|
||||
private static boolean hasChar(CharSequence s, int index, char c) {
|
||||
return index >= 0 && index < s.length() && s.charAt(index) == c;
|
||||
}
|
||||
|
||||
private boolean isSingleLineChange(int start, int end, @NotNull CharSequence replacement) {
|
||||
if (start == 0 && end == myLength && replacement.length() == 0) return false;
|
||||
|
||||
@@ -103,19 +117,11 @@ public class LineSet{
|
||||
}
|
||||
|
||||
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--;
|
||||
}
|
||||
int startLine = findLineIndex(startOffset);
|
||||
startOffset = getLineStart(startLine);
|
||||
int startLine = findLineIndex(_start);
|
||||
int startOffset = getLineStart(startLine);
|
||||
|
||||
int endOffset = _end;
|
||||
if (replacement.length() > 0 && replacement.charAt(replacement.length() - 1) == '\r' && endOffset < prevText.length() && prevText.charAt(endOffset) == '\n') {
|
||||
endOffset++;
|
||||
}
|
||||
int endLine = findLineIndex(endOffset);
|
||||
endOffset = getLineEnd(endLine);
|
||||
int endLine = findLineIndex(_end);
|
||||
int endOffset = getLineEnd(endLine);
|
||||
if (!isLastEmptyLine(endLine)) endLine++;
|
||||
|
||||
if (startOffset < _start) {
|
||||
@@ -129,20 +135,6 @@ public class LineSet{
|
||||
return applyPatch(startOffset, endOffset, startLine, endLine, patch);
|
||||
}
|
||||
|
||||
private void checkEquals(@NotNull LineSet fresh) {
|
||||
if (getLineCount() != fresh.getLineCount()) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
for (int i = 0; i < getLineCount(); i++) {
|
||||
boolean start = getLineStart(i) != fresh.getLineStart(i);
|
||||
boolean end = getLineEnd(i) != fresh.getLineEnd(i);
|
||||
boolean sep = getSeparatorLength(i) != fresh.getSeparatorLength(i);
|
||||
if (start || end || sep) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LineSet applyPatch(int startOffset, int endOffset, int startLine, int endLine, @NotNull LineSet patch) {
|
||||
int lineShift = patch.myStarts.length - (endLine - startLine);
|
||||
@@ -262,13 +254,6 @@ public class LineSet{
|
||||
return myStarts.length + (isLastEmptyLine(myStarts.length) ? 1 : 0);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void setTestingMode(boolean testMode) {
|
||||
doTest = testMode;
|
||||
}
|
||||
|
||||
private static boolean doTest;
|
||||
|
||||
int getLength() {
|
||||
return myLength;
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.editor.impl;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import org.jetbrains.jetCheck.Generator;
|
||||
import org.jetbrains.jetCheck.PropertyChecker;
|
||||
|
||||
public class LineSetIncrementalUpdateTest extends UsefulTestCase {
|
||||
public void testFuzzUpdate() {
|
||||
PropertyChecker.customized()
|
||||
.withIterationCount(1_000)
|
||||
.checkScenarios(() -> env -> {
|
||||
Generator<String> strings = Generator.stringsOf("a \n\r");
|
||||
String initialText = env.generateValue(strings, null);
|
||||
int start = env.generateValue(Generator.integers(0, initialText.length()), null);
|
||||
int end = env.generateValue(Generator.integers(start, initialText.length()), null);
|
||||
String replacement = env.generateValue(strings, null);
|
||||
|
||||
env.logMessage("checkIncrementalUpdate(\"" + StringUtil.escapeStringCharacters(initialText) +
|
||||
"\", " + start + ", " + end + ", \"" + StringUtil.escapeStringCharacters(replacement) + "\")");
|
||||
|
||||
checkIncrementalUpdate(initialText, start, end, replacement);
|
||||
});
|
||||
}
|
||||
|
||||
public void testSlashRIssues() {
|
||||
checkIncrementalUpdate("\n\r", 0, 0, "");
|
||||
checkIncrementalUpdate("\r\n", 0, 1, "");
|
||||
checkIncrementalUpdate("\r", 0, 0, "\r");
|
||||
}
|
||||
|
||||
public void testClearSingleLineEnd() {
|
||||
checkIncrementalUpdate("\n", 0, 1, "");
|
||||
}
|
||||
|
||||
private static void checkIncrementalUpdate(String initialText, int start, int end, String replacement) {
|
||||
CharSequence newText = StringUtil.replaceSubSequence(initialText, start, end, replacement);
|
||||
|
||||
LineSet initial = LineSet.createLineSet(initialText);
|
||||
LineSet updated = initial.update(initialText, start, end, replacement, false);
|
||||
LineSet fresh = LineSet.createLineSet(newText);
|
||||
|
||||
assertEquals("line count", fresh.getLineCount(), updated.getLineCount());
|
||||
for (int i = 0; i < updated.getLineCount(); i++) {
|
||||
assertEquals("line start " + i, fresh.getLineStart(i), updated.getLineStart(i));
|
||||
assertEquals("line end " + i, fresh.getLineEnd(i), updated.getLineEnd(i));
|
||||
assertEquals("line feed length " + i, fresh.getSeparatorLength(i), updated.getSeparatorLength(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void testTypingInLongLinePerformance() {
|
||||
String longLine = StringUtil.repeat("a ", 200000);
|
||||
PlatformTestUtil.startPerformanceTest("Document changes in a long line", 1000, () -> {
|
||||
Document document = new DocumentImpl("a\n" + longLine + "<caret>" + longLine + "\n", true);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int offset = i * 2 + longLine.length();
|
||||
assertEquals(1, document.getLineNumber(offset));
|
||||
document.insertString(offset, "b");
|
||||
}
|
||||
}).assertTiming();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,11 +37,15 @@ public class StringUtil extends StringUtilRt {
|
||||
private static final Pattern EOL_SPLIT_PATTERN_WITH_EMPTY = Pattern.compile(" *(\r|\n|\r\n) *");
|
||||
private static final Pattern EOL_SPLIT_DONT_TRIM_PATTERN = Pattern.compile("(\r|\n|\r\n)+");
|
||||
|
||||
/**
|
||||
* @return a lightweight CharSequence which results from replacing {@code [start, end)} range in the {@code charSeq} with {@code replacement}.
|
||||
* Works in O(1), but retains references to the passed char sequences, so please use something else if you want them to be garbage-collected.
|
||||
*/
|
||||
@NotNull
|
||||
public static MergingCharSequence replaceSubSequence(@NotNull CharSequence charSeq, int start, int end, @NotNull CharSequence replacement) {
|
||||
return new MergingCharSequence(
|
||||
new MergingCharSequence(charSeq.subSequence(0, start), replacement),
|
||||
charSeq.subSequence(end, charSeq.length()));
|
||||
new MergingCharSequence(new CharSequenceSubSequence(charSeq, 0, start), replacement),
|
||||
new CharSequenceSubSequence(charSeq, end, charSeq.length()));
|
||||
}
|
||||
|
||||
private static class MyHtml2Text extends HTMLEditorKit.ParserCallback {
|
||||
|
||||
Reference in New Issue
Block a user