[EA-263022] SOE fix corner case with greedy block

* Tests testDocumentGuardedTextUtilDuplicateLine and testDocumentGuardedTextUtilDeleteLine are for valid cases.
* Tests testDocumentGuardedTextUtilDuplicateLineWithGreedy and testDocumentGuardedTextUtilDeleteLineWithGreedy for corner cases with greedy range marker. Before fix this cases leaded to stack overflow exception inside DocumentGuardedTextUtil.

Merge-request: IJ-MR-111809
Merged-by: Igor Slobodskov <Igor.Slobodskov@jetbrains.com>

GitOrigin-RevId: b2e1bd6868af754bcd99e555c5a3b8272edbc3d3
This commit is contained in:
Igor Slobodskov
2023-08-02 22:17:34 +00:00
committed by intellij-monorepo-bot
parent 87b3de1d23
commit 15582d4a59
2 changed files with 64 additions and 2 deletions
@@ -28,7 +28,7 @@ public final class DocumentGuardedTextUtil {
CharSequence textBefore =
document.getImmutableCharSequence().subSequence(Math.max(0, startOffset - blockPrefix.length()), startOffset);
if (blockPrefix.toString().equals(textBefore.toString())) {
if (!blockPrefix.isEmpty() && blockPrefix.toString().equals(textBefore.toString())) {
deleteString(document, startOffset - blockPrefix.length(), endOffset - blockPrefix.length());
}
else {
@@ -48,7 +48,7 @@ public final class DocumentGuardedTextUtil {
}
catch (ReadOnlyFragmentModificationException ex) {
String blockPrefix = document.getImmutableCharSequence().subSequence(ex.getGuardedBlock().getStartOffset(), offset).toString();
if (s.toString().endsWith(blockPrefix)) {
if (!blockPrefix.isEmpty() && s.toString().endsWith(blockPrefix)) {
int newOffset = offset - blockPrefix.length();
String newString = blockPrefix + s.subSequence(0, s.length() - blockPrefix.length());
insertString(document, newOffset, newString);
@@ -95,4 +95,66 @@ public class GuardBlockTest extends BasePlatformTestCase {
myFixture.getEditor().getCaretModel().moveToOffset(offset - 1);
assertNotNull(myFixture.completeBasic());
}
public void testDocumentGuardedTextUtilDuplicateLine() {
String text = "a\n#%%\nc";
// ^^^^^^^ <- guarded
// com.intellij.openapi.editor.actions.DocumentGuardedTextUtil.insertString handles this case and inserts "\na" after first "a"
myFixture.configureByText("x.txt", text);
RangeMarker guard = createGuard(1, 6);
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DUPLICATE_LINES);
myFixture.checkResult("a\na\n#%%\nc");
assertTrue(guard.isValid());
assertEquals(3, guard.getStartOffset());
assertEquals(8, guard.getEndOffset());
}
public void testDocumentGuardedTextUtilDeleteLine() {
String text = "a\n<caret>b\n#%%\nc";
// ^^^^^^^ <- guarded
// com.intellij.openapi.editor.actions.DocumentGuardedTextUtil.deleteString handles this case and removes \n before caret
myFixture.configureByText("x.txt", text);
RangeMarker guard = createGuard(3, 8);
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE_LINE);
myFixture.checkResult("a\n#%%\nc");
assertTrue(guard.isValid());
assertEquals(1, guard.getStartOffset());
assertEquals(6, guard.getEndOffset());
}
public void testDocumentGuardedTextUtilDuplicateLineWithGreedy() {
String text = "a\n#%%\nc";
// ^^^^^^^ <- guarded
myFixture.configureByText("x.txt", text);
createGuard(1, 6).setGreedyToLeft(true);
try {
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DUPLICATE_LINES);
}
catch (RuntimeException e) {
assertEquals("Unable to perform an action since it changes read-only fragments of the current document", e.getMessage());
myFixture.checkResult(text);
return;
}
fail("must be read only at " + 1);
}
public void testDocumentGuardedTextUtilDeleteLineWithGreedy() {
String text = "a\n<caret>b\n#%%\nc";
// ^^^^^^^ <- guarded
myFixture.configureByText("x.txt", text);
createGuard(3, 8).setGreedyToLeft(true);
try {
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE_LINE);
}
catch (RuntimeException e) {
assertEquals("Unable to perform an action since it changes read-only fragments of the current document", e.getMessage());
myFixture.checkResult(text);
return;
}
fail("must be read only at " + 3);
}
}