python: added ability to move multiline statements

fixed PY-6587 Move Statements: inconsistent selection on moving more than one statement up or down.
This commit is contained in:
Ekaterina Tuzova
2013-09-13 20:02:16 +04:00
parent eeff2a8021
commit 01caab85df
38 changed files with 510 additions and 63 deletions

View File

@@ -10,10 +10,7 @@ import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.util.PsiTreeUtil;
@@ -29,13 +26,37 @@ public class PyStatementMover extends LineMover {
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
if (!(file instanceof PyFile)) return false;
final int offset = editor.getCaretModel().getOffset();
PsiElement elementToMove = PyUtil.findNonWhitespaceAtOffset(file, offset);
if (elementToMove == null) return false;
elementToMove = getCommentOrStatement(editor.getDocument(), elementToMove);
info.toMove = new MyLineRange(elementToMove);
info.toMove2 = getDestinationScope(file, editor, elementToMove, down);
final SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasSelection()) {
PsiElement elementToMove = PyUtil.findNonWhitespaceAtOffset(file, offset);
if (elementToMove == null) return false;
elementToMove = getCommentOrStatement(editor.getDocument(), elementToMove);
info.toMove = new MyLineRange(elementToMove, elementToMove);
info.toMove2 = getDestinationScope(file, editor, elementToMove, down);
}
else {
final int start = selectionModel.getSelectionStart();
final int end = selectionModel.getSelectionEnd() - 1;
PsiElement elementToMove1 = PyUtil.findNonWhitespaceAtOffset(file, start);
PsiElement elementToMove2 = PyUtil.findNonWhitespaceAtOffset(file, end);
if (elementToMove1 == null || elementToMove2 == null) return false;
elementToMove1 = getCommentOrStatement(editor.getDocument(), elementToMove1);
elementToMove2 = getCommentOrStatement(editor.getDocument(), elementToMove2);
if (PsiTreeUtil.isAncestor(elementToMove1, elementToMove2, false)) {
elementToMove2 = elementToMove1;
}
else if (PsiTreeUtil.isAncestor(elementToMove2, elementToMove1, false)) {
elementToMove1 = elementToMove2;
}
info.toMove = new MyLineRange(elementToMove1, elementToMove2);
info.toMove2 = getDestinationScope(file, editor, down ? elementToMove2 : elementToMove1, down);
}
info.indentTarget = false;
info.indentSource = false;
return true;
}
@@ -43,7 +64,6 @@ public class PyStatementMover extends LineMover {
private static LineRange getDestinationScope(@NotNull final PsiFile file, @NotNull final Editor editor,
@NotNull final PsiElement elementToMove, boolean down) {
final Document document = file.getViewProvider().getDocument();
if (document == null) return null;
final int offset = down ? elementToMove.getTextRange().getEndOffset() : elementToMove.getTextRange().getStartOffset();
@@ -230,6 +250,7 @@ public class PyStatementMover extends LineMover {
int lineEndOffset, boolean down) {
PsiElement destination = elementToMove.getContainingFile().findElementAt(lineEndOffset);
if (destination == null) return null;
if (destination instanceof PsiComment) return destination;
PsiElement sibling = down ? PsiTreeUtil.getNextSiblingOfType(elementToMove, PyStatement.class) :
PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class);
if (elementToMove instanceof PyClass) {
@@ -241,7 +262,7 @@ public class PyStatementMover extends LineMover {
else destination = null;
}
else {
destination = getCommentOrStatement(document, destination);
destination = getCommentOrStatement(document, sibling == null ? destination : sibling);
}
return destination;
}
@@ -269,75 +290,205 @@ public class PyStatementMover extends LineMover {
PostprocessReformattingAspect.getInstance(editor.getProject()).disablePostprocessFormattingInside(new Runnable() {
@Override
public void run() {
final PsiElement elementToMove = ((MyLineRange)toMove).myElement;
final PsiElement startToMove = ((MyLineRange)toMove).myStartElement;
final PsiElement endToMove = ((MyLineRange)toMove).myEndElement;
final PsiFile file = startToMove.getContainingFile();
final SelectionModel selectionModel = editor.getSelectionModel();
final CaretModel caretModel = editor.getCaretModel();
final int shift = caretModel.getOffset() - elementToMove.getTextOffset();
final boolean hasSelection = selectionModel.hasSelection();
final int selectionStart = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
final int selectionShift = selectionStart - elementToMove.getTextOffset();
final int selectionStart = selectionModel.getSelectionStart();
boolean isSelectionStartAtCaret = caretModel.getOffset() == selectionStart;
final SelectionContainer selectionLen = getSelectionLenContainer(editor, ((MyLineRange)toMove));
int shift = getCaretShift(startToMove, endToMove, caretModel, isSelectionStartAtCaret);
final boolean hasSelection = selectionModel.hasSelection();
int offset;
if (((ScopeRange)toMove2).isTheSameLevel()) {
offset = moveTheSameLevel((ScopeRange)toMove2, (MyLineRange)toMove);
}
else {
offset = moveInOut(((MyLineRange)toMove).myElement, editor, info);
offset = moveInOut(((MyLineRange)toMove), editor, info);
}
final int documentLength = editor.getDocument().getTextLength();
int newCaretOffset = offset + shift;
if (newCaretOffset >= documentLength) newCaretOffset = documentLength;
caretModel.moveToOffset(newCaretOffset);
restoreCaretAndSelection(file, editor, isSelectionStartAtCaret, hasSelection, selectionLen,
shift, offset, (MyLineRange)toMove);
info.toMove2 = info.toMove; //do not move further
if (hasSelection) {
int newSelectionStart = offset + selectionShift;
int newSelectionEnd = newSelectionStart + selectionEnd - selectionStart;
if (newSelectionEnd >= documentLength)
newSelectionEnd = documentLength;
selectionModel.setSelection(newSelectionStart, newSelectionEnd);
}
}
});
}
}
private static int moveTheSameLevel(@NotNull final ScopeRange toMove2, @NotNull final MyLineRange toMove) {
final PsiElement anchor = toMove2.getAnchor();
final PsiElement elementToMove = toMove.myElement;
private static SelectionContainer getSelectionLenContainer(@NotNull final Editor editor, @NotNull final MyLineRange toMove) {
final SelectionModel selectionModel = editor.getSelectionModel();
final PsiElement startToMove = toMove.myStartElement;
final PsiElement endToMove = toMove.myEndElement;
final int selectionStart = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
final PsiElement anchorCopy = anchor.copy();
final PsiElement addedElement = anchor.replace(elementToMove);
elementToMove.replace(anchorCopy);
final TextRange range = startToMove.getTextRange();
final int column = editor.offsetToLogicalPosition(selectionStart).column;
final int additionalSelection = range.getStartOffset() > selectionStart ? range.getStartOffset() - selectionStart : 0;
if (startToMove == endToMove) return new SelectionContainer(selectionEnd - range.getStartOffset(), additionalSelection, column == 0);
int len = range.getStartOffset() <= selectionStart ? range.getEndOffset() - selectionStart : startToMove.getTextLength();
return addedElement.getTextOffset();
PsiElement tmp = startToMove.getNextSibling();
while (tmp != endToMove && tmp != null) {
if (!(tmp instanceof PsiWhiteSpace))
len += tmp.getTextLength();
tmp = tmp.getNextSibling();
}
len = len + selectionEnd - endToMove.getTextOffset();
return new SelectionContainer(len, additionalSelection, column == 0);
}
private static int moveInOut(@NotNull final PsiElement elementToMove, @NotNull final Editor editor, @NotNull final MoveInfo info) {
private static void restoreCaretAndSelection(@NotNull final PsiFile file, @NotNull final Editor editor, boolean selectionStartAtCaret,
boolean hasSelection, @NotNull final SelectionContainer selectionContainer, int shift,
int offset, @NotNull final MyLineRange toMove) {
final Document document = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
final CaretModel caretModel = editor.getCaretModel();
Integer selectionLen = selectionContainer.myLen;
final PsiElement at = file.findElementAt(offset);
if (at != null) {
final PsiElement added = getCommentOrStatement(document, at);
int size = toMove.size;
if (size > 1) {
PsiElement tmp = added.getNextSibling();
while (size > 1 && tmp != null) {
if (tmp instanceof PsiWhiteSpace) {
if (!selectionStartAtCaret)
shift += tmp.getTextLength();
selectionLen += tmp.getTextLength();
}
tmp = tmp.getNextSibling();
size -= 1;
}
}
if (shift < 0) shift = 0;
final int column = editor.offsetToLogicalPosition(added.getTextRange().getStartOffset()).column;
if (selectionContainer.myAtTheBeginning || column < selectionContainer.myAdditional) {
selectionLen += column;
}
else {
selectionLen += selectionContainer.myAdditional;
}
if (selectionContainer.myAtTheBeginning && selectionStartAtCaret)
shift = -column;
}
final int documentLength = document.getTextLength();
int newCaretOffset = offset + shift;
if (newCaretOffset >= documentLength) newCaretOffset = documentLength;
caretModel.moveToOffset(newCaretOffset);
if (hasSelection) {
if (selectionStartAtCaret) {
int newSelectionEnd = newCaretOffset + selectionLen;
selectionModel.setSelection(newCaretOffset, newSelectionEnd);
}
else {
int newSelectionStart = newCaretOffset - selectionLen;
selectionModel.setSelection(newSelectionStart, newCaretOffset);
}
}
}
private static int getCaretShift(PsiElement startToMove, PsiElement endToMove, CaretModel caretModel, boolean selectionStartAtCaret) {
int shift;
if (selectionStartAtCaret) {
shift = caretModel.getOffset() - startToMove.getTextRange().getStartOffset();
}
else {
shift = caretModel.getOffset();
if (startToMove != endToMove) {
shift += startToMove.getTextLength();
PsiElement tmp = startToMove.getNextSibling();
while (tmp != endToMove && tmp != null) {
if (!(tmp instanceof PsiWhiteSpace))
shift += tmp.getTextLength();
tmp = tmp.getNextSibling();
}
}
shift -= endToMove.getTextOffset();
}
return shift;
}
private static int moveTheSameLevel(@NotNull final ScopeRange toMove2, @NotNull final MyLineRange toMove) {
final PsiElement anchor = toMove2.getAnchor();
final PsiElement anchorCopy = anchor.copy();
PsiElement startToMove = toMove.myStartElement;
final PsiElement endToMove = toMove.myEndElement;
final PsiElement parent = anchor.getParent();
PsiElement tmp = startToMove.getNextSibling();
if (startToMove != endToMove && tmp != null) {
parent.addRangeAfter(tmp, endToMove, anchor);
}
PsiElement startCopy = startToMove.copy();
startToMove.replace(anchorCopy);
final PsiElement addedElement = anchor.replace(startCopy);
if (startToMove != endToMove && tmp != null) {
parent.deleteChildRange(tmp, endToMove);
}
return addedElement.getTextRange().getStartOffset();
}
private static int moveInOut(@NotNull final MyLineRange toMove, @NotNull final Editor editor, @NotNull final MoveInfo info) {
boolean removePass = false;
final ScopeRange toMove2 = (ScopeRange)info.toMove2;
final PsiElement scope = toMove2.getScope();
final PsiElement anchor = toMove2.getAnchor();
final Project project = scope.getProject();
if (scope instanceof PyStatementList && !(elementToMove instanceof PsiComment)) {
final PsiElement startElement = toMove.myStartElement;
final PsiElement endElement = toMove.myEndElement;
PsiElement parent = startElement.getParent();
if (scope instanceof PyStatementList && !(startElement == endElement && startElement instanceof PsiComment)) {
final PyStatement[] statements = ((PyStatementList)scope).getStatements();
if (statements.length == 1 && statements[0] == anchor && statements[0] instanceof PyPassStatement) {
removePass = true;
}
}
final PsiElement addedElement = toMove2.isAddBefore() ? scope.addBefore(elementToMove, anchor) : scope.addAfter(elementToMove, anchor);
addPassStatement(elementToMove, project);
final PsiElement addedElement;
PsiElement nextSibling = startElement.getNextSibling();
if (toMove2.isAddBefore()) {
PsiElement tmp = endElement.getPrevSibling();
if (startElement != endElement && tmp != null) {
addedElement = scope.addRangeBefore(startElement, tmp, anchor);
scope.addBefore(endElement, anchor);
}
else {
addedElement = scope.addBefore(endElement, anchor);
}
}
else {
if (startElement != endElement) {
scope.addRangeAfter(nextSibling, endElement, anchor);
}
addedElement = scope.addAfter(startElement, anchor);
}
addPassStatement(toMove, project);
elementToMove.delete();
if (startElement != endElement && nextSibling != null) {
parent.deleteChildRange(nextSibling, endElement);
}
startElement.delete();
final int addedElementLine = editor.getDocument().getLineNumber(addedElement.getTextOffset());
final PsiFile file = scope.getContainingFile();
adjustLineIndents(editor, scope, project, addedElement);
adjustLineIndents(editor, scope, project, addedElement, toMove.size);
if (removePass) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -348,23 +499,25 @@ public class PyStatementMover extends LineMover {
final int endOffset = document.getLineCount() <= lineNumber + 1 ? document.getLineEndOffset(lineNumber)
: document.getLineStartOffset(lineNumber + 1);
document.deleteString(document.getLineStartOffset(lineNumber), endOffset);
PsiDocumentManager.getInstance(elementToMove.getProject()).commitAllDocuments();
PsiDocumentManager.getInstance(startElement.getProject()).commitAllDocuments();
}
});
}
int offset = addedElement.getTextRange().getStartOffset();
if ((addedElement instanceof PsiComment || addedElement instanceof PyPassStatement) && offset == 0) { // PsiComment gets broken after adjust indent
final PsiElement psiElement = PyUtil.findNonWhitespaceAtOffset(file, editor.getDocument().getLineEndOffset(addedElementLine) - 1);
int newLine = editor.getDocument().getLineNumber(offset);
if (newLine != addedElementLine && !removePass) { // PsiComment gets broken after adjust indent
PsiElement psiElement = PyUtil.findNonWhitespaceAtOffset(file, editor.getDocument().getLineEndOffset(addedElementLine) - 1);
if (psiElement != null) {
offset = psiElement.getTextOffset();
psiElement = getCommentOrStatement(editor.getDocument(), psiElement);
offset = psiElement.getTextRange().getStartOffset();
}
}
return offset;
}
private static void adjustLineIndents(@NotNull final Editor editor, @NotNull final PsiElement scope, @NotNull final Project project,
@NotNull final PsiElement addedElement) {
@NotNull final PsiElement addedElement, int size) {
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
final Document document = editor.getDocument();
@@ -372,35 +525,79 @@ public class PyStatementMover extends LineMover {
int line1 = editor.offsetToLogicalPosition(scope.getTextRange().getStartOffset()).line;
int line2 = editor.offsetToLogicalPosition(scope.getTextRange().getEndOffset()).line;
codeStyleManager.adjustLineIndent(scope.getContainingFile(),
new TextRange(document.getLineStartOffset(line1), document.getLineStartOffset(line2)));
new TextRange(document.getLineStartOffset(line1), document.getLineEndOffset(line2)));
}
else {
int line1 = editor.offsetToLogicalPosition(addedElement.getTextRange().getStartOffset()).line;
int line2 = editor.offsetToLogicalPosition(addedElement.getTextRange().getEndOffset()).line;
PsiElement end = addedElement;
while (size > 0) {
PsiElement tmp = end.getNextSibling();
if (tmp == null) break;
size -= 1;
end = tmp;
}
int endOffset = end.getTextRange().getEndOffset();
int line2 = editor.offsetToLogicalPosition(endOffset).line;
codeStyleManager.adjustLineIndent(scope.getContainingFile(),
new TextRange(document.getLineStartOffset(line1), document.getLineStartOffset(line2)));
new TextRange(document.getLineStartOffset(line1), document.getLineEndOffset(line2)));
}
}
private static void addPassStatement(@NotNull final PsiElement elementToMove, @NotNull final Project project) {
final PyStatementList initialScope = getStatementList(elementToMove);
if (initialScope != null && !(elementToMove instanceof PsiComment)) {
if (initialScope.getStatements().length == 1) {
private static void addPassStatement(@NotNull final MyLineRange toMove, @NotNull final Project project) {
final PsiElement startElement = toMove.myStartElement;
final PsiElement endElement = toMove.myEndElement;
final PyStatementList initialScope = getStatementList(startElement);
if (initialScope != null && !(startElement == endElement && startElement instanceof PsiComment)) {
if (initialScope.getStatements().length == toMove.statementsSize) {
final PyPassStatement passStatement = PyElementGenerator.getInstance(project).createPassStatement();
initialScope.addAfter(passStatement, initialScope.getStatements()[initialScope.getStatements().length - 1]);
}
}
}
// use to keep element
// use to keep elements
static class MyLineRange extends LineRange {
public PsiElement myElement;
public MyLineRange(@NotNull PsiElement element) {
super(element);
myElement = element;
private PsiElement myStartElement;
private PsiElement myEndElement;
int size = 0;
int statementsSize = 0;
public MyLineRange(@NotNull PsiElement start, PsiElement end) {
super(start, end);
myStartElement = start;
myEndElement = end;
if (myStartElement == myEndElement) {
size = 1;
statementsSize = 1;
}
else {
PsiElement counter = myStartElement;
while (counter != myEndElement && counter != null) {
size += 1;
if (!(counter instanceof PsiWhiteSpace) && !(counter instanceof PsiComment))
statementsSize += 1;
counter = counter.getNextSibling();
}
size += 1;
if (!(counter instanceof PsiWhiteSpace) && !(counter instanceof PsiComment))
statementsSize += 1;
}
}
}
static class SelectionContainer {
private int myLen;
private int myAdditional;
private boolean myAtTheBeginning;
public SelectionContainer(int len, int additional, boolean atTheBeginning) {
myLen = len;
myAdditional = additional;
myAtTheBeginning = atTheBeginning;
}
}
// Use when element scope changed
static class ScopeRange extends LineRange {
private PsiElement myScope;

View File

@@ -3,7 +3,7 @@ class A:
def foo(self):
pass
<selection>def<caret> bar(self):
<selection><caret>def bar(self):
pass</selection>
def baz(self):

View File

@@ -6,5 +6,5 @@ class A:
def baz(self):
pass
<selection>def<caret> bar(self):
<selection><caret>def bar(self):
pass</selection>

View File

@@ -1,6 +1,6 @@
class A:
<selection>def<caret> bar(self):
<selection><caret>def bar(self):
pass</selection>
def foo(self):

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection>a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection> a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,3 @@
if True:
<caret><selection> a = 2
b = 3</selection>

View File

@@ -0,0 +1,4 @@
if True:
pass
<caret><selection>a = 2
b = 3</selection>

View File

@@ -0,0 +1,4 @@
<caret><selection>a = 2
b = 3</selection>
if True:
pass

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection> a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
def q(self):
<selection> a = 1
b = 2
<caret></selection> c = 3

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection>a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection>a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection>a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
def q(self):
<selection> a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
pass
<selection> a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
<selection> a = 1
b = 2
<caret></selection> def q(self):
pass

View File

@@ -0,0 +1,5 @@
class Test(object):
<selection> a = 1
b = 2
<caret></selection> def q(self):
c = 3

View File

@@ -0,0 +1,5 @@
class Test(object):
def q(self):
<selection> a = 1
b = 2
<caret></selection> c = 3

View File

@@ -0,0 +1,5 @@
<selection>a = 1
b = 2
<caret></selection>class Test(object):
def q(self):
c = 3

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection><caret>a = 1
b = 2
</selection>

View File

@@ -0,0 +1,8 @@
class Test(object):
def q(self):
c = 3
<selection><caret>a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection><caret> a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<caret><selection> a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<caret><selection> a = 1
b = 2
</selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
def q(self):
<caret><selection> a = 1
b = 2
</selection> c = 3

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<caret><selection>a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<caret><selection>a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<caret><selection>a = 1
b = 2
</selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
def q(self):
<caret><selection> a = 1
b = 2
</selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
pass
<caret><selection> a = 1
b = 2
</selection>

View File

@@ -0,0 +1,6 @@
class Test(object):
<caret><selection> a = 1
b = 2
</selection> def q(self):
pass

View File

@@ -0,0 +1,5 @@
class Test(object):
<caret><selection> a = 1
b = 2
</selection> def q(self):
c = 3

View File

@@ -0,0 +1,5 @@
class Test(object):
def q(self):
<caret><selection> a = 1
b = 2
</selection> c = 3

View File

@@ -0,0 +1,5 @@
<caret><selection>a = 1
b = 2
</selection>class Test(object):
def q(self):
c = 3

View File

@@ -0,0 +1,8 @@
class Test(object):
def q(self):
c = 3
<selection>a = 1
b = 2
<caret></selection>

View File

@@ -0,0 +1,7 @@
class Test(object):
def q(self):
c = 3
<selection> a = 1
b = 2
<caret></selection>

View File

@@ -188,10 +188,55 @@ public class PyStatementMoverTest extends PyTestCase {
public void testLastComment1() { //PY-6408
doTest();
}
public void testMultiCompound() { //PY-7658
doTest();
}
public void testMultiLineSelection() {
doTest();
}
public void testMultiLineSelection1() {
doTest();
}
public void testMultiLineSelection2() {
doTest();
}
public void testMultiLineSelection3() {
doTest();
}
public void testMultiLineSelection4() {
doTest();
}
public void testMultiLineSelection5() { //0
doTest();
}
public void testMultiLineSelection6() {
doTest();
}
public void testMultiLineSelection7() {
doTest();
}
public void testMultiLineSelection8() {
doTest();
}
public void testMultiLineSelection9() {
doTest();
}
public void testMultiLineSelection10() {
doTest();
}
public void testWith() { // PY-5202
try {
setLanguageLevel(LanguageLevel.PYTHON27);