From d22ca239630c02a7c1040c24e3b4a9be80b41844 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 1 Dec 2011 22:06:52 +0100 Subject: [PATCH] rewritten move statement for python (see PY-3284, PY-1834, PY-3283) --- .../moveUpDown/StatementMover.java | 662 ++++++++---------- python/testData/mover/decorator.py | 1 - python/testData/mover/decorator_afterUp.py | 3 +- python/testData/mover/doubleIf.py | 7 + python/testData/mover/doubleIf_afterDown.py | 7 + python/testData/mover/doubleIf_afterUp.py | 5 + python/testData/mover/functions.py | 8 +- python/testData/mover/functions_afterDown.py | 8 +- python/testData/mover/functions_afterUp.py | 8 +- python/testData/mover/indent_afterDown.py | 2 +- python/testData/mover/indent_afterUp.py | 2 +- python/testData/mover/innerIf.py | 3 + python/testData/mover/innerIf_afterDown.py | 3 + python/testData/mover/innerIf_afterUp.py | 4 + python/testData/mover/lastLine.py | 3 + python/testData/mover/lastLine_afterDown.py | 3 + python/testData/mover/lastLine_afterUp.py | 3 + python/testData/mover/moveDownOut.py | 5 + .../testData/mover/moveDownOut_afterDown.py | 5 + python/testData/mover/moveDownOut_afterUp.py | 5 + python/testData/mover/moveOut.py | 7 + python/testData/mover/moveOut_afterDown.py | 8 + python/testData/mover/moveOut_afterUp.py | 8 + .../testData/mover/moveStatement_afterUp.py | 2 +- python/testData/mover/nestedBlock.py | 7 + .../testData/mover/nestedBlock_afterDown.py | 7 + python/testData/mover/nestedBlock_afterUp.py | 7 + python/testData/mover/oneStatementInClass.py | 4 + .../mover/oneStatementInClass_afterDown.py | 4 + .../mover/oneStatementInClass_afterUp.py | 4 + .../testData/mover/oneStatementInFunction.py | 4 + .../mover/oneStatementInFunction_afterDown.py | 4 + .../mover/oneStatementInFunction_afterUp.py | 4 + .../mover/outsideStatement_afterDown.py | 4 +- python/testData/mover/py950_afterDown.py | 6 +- python/testData/mover/py950_afterUp.py | 4 +- .../mover/simpleBlankLines_afterDown.py | 6 +- .../mover/simpleBlankLines_afterUp.py | 4 +- .../python/PyStatementMoverTest.java | 30 +- 39 files changed, 482 insertions(+), 389 deletions(-) create mode 100644 python/testData/mover/doubleIf.py create mode 100644 python/testData/mover/doubleIf_afterDown.py create mode 100644 python/testData/mover/doubleIf_afterUp.py create mode 100644 python/testData/mover/innerIf.py create mode 100644 python/testData/mover/innerIf_afterDown.py create mode 100644 python/testData/mover/innerIf_afterUp.py create mode 100644 python/testData/mover/lastLine.py create mode 100644 python/testData/mover/lastLine_afterDown.py create mode 100644 python/testData/mover/lastLine_afterUp.py create mode 100644 python/testData/mover/moveDownOut.py create mode 100644 python/testData/mover/moveDownOut_afterDown.py create mode 100644 python/testData/mover/moveDownOut_afterUp.py create mode 100644 python/testData/mover/moveOut.py create mode 100644 python/testData/mover/moveOut_afterDown.py create mode 100644 python/testData/mover/moveOut_afterUp.py create mode 100644 python/testData/mover/nestedBlock.py create mode 100644 python/testData/mover/nestedBlock_afterDown.py create mode 100644 python/testData/mover/nestedBlock_afterUp.py create mode 100644 python/testData/mover/oneStatementInClass.py create mode 100644 python/testData/mover/oneStatementInClass_afterDown.py create mode 100644 python/testData/mover/oneStatementInClass_afterUp.py create mode 100644 python/testData/mover/oneStatementInFunction.py create mode 100644 python/testData/mover/oneStatementInFunction_afterDown.py create mode 100644 python/testData/mover/oneStatementInFunction_afterUp.py diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java index c757a0becc58..5c9949bbd932 100644 --- a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java +++ b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/StatementMover.java @@ -3,415 +3,367 @@ package com.jetbrains.python.codeInsight.editorActions.moveUpDown; import com.intellij.codeInsight.CodeInsightUtilBase; import com.intellij.codeInsight.editorActions.moveUpDown.LineMover; import com.intellij.codeInsight.editorActions.moveUpDown.LineRange; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.util.Pair; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiDocumentManagerImpl; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; import com.jetbrains.python.PyNames; +import com.jetbrains.python.PythonFileType; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Arrays; - /** - * @author Alexey.Ivanov + * User : catherine */ public class StatementMover extends LineMover { - private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.codeInsight.editorActions.moveUpDown.StatementMover"); - - private static PsiElement[] findStatementsInRange(PsiFile file, int startOffset, int endOffset) { - PsiElement element1 = file.findElementAt(startOffset); - PsiElement element2 = file.findElementAt(endOffset - 1); - if (element1 instanceof PsiWhiteSpace) { - startOffset = element1.getTextRange().getEndOffset(); - element1 = file.findElementAt(startOffset); - } - if (element2 instanceof PsiWhiteSpace) { - endOffset = element2.getTextRange().getStartOffset(); - element2 = file.findElementAt(endOffset - 1); - } - if (element1 == null || element2 == null) { - return PsiElement.EMPTY_ARRAY; - } - - PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2); - if (parent == null) { - return PsiElement.EMPTY_ARRAY; - } - - while (true) { - if (parent instanceof PyStatement) { - parent = parent.getParent(); - break; - } - if (parent instanceof PyStatementList) { - break; - } - if (parent == null || parent instanceof PsiFile) { - return PsiElement.EMPTY_ARRAY; - } - parent = parent.getParent(); - } - - if (!parent.equals(element1)) { - while (!parent.equals(element1.getParent())) { - element1 = element1.getParent(); - } - } - - if (!parent.equals(element2)) { - while (!parent.equals(element2.getParent())) { - element2 = element2.getParent(); - } - } - - PsiElement[] children = parent.getChildren(); - ArrayList array = new ArrayList(); - boolean flag = false; - for (PsiElement child : children) { - if (child.equals(element1)) { - flag = true; - } - if (flag && !(child instanceof PsiWhiteSpace)) { - array.add(child); - } - if (child.equals(element2)) { - break; - } - } - - for (PsiElement element : array) { - if (!(element instanceof PyStatement || element instanceof PsiWhiteSpace || element instanceof PsiComment)) { - return PsiElement.EMPTY_ARRAY; - } - } - return PsiUtilBase.toPsiElementArray(array); - } - - private static boolean isNotValidStatementRange(Pair range) { - return range == null || - range.first == null || - range.second == null || - range.first.getParent() != range.second.getParent(); - } - - @Nullable - private static LineRange expandLineRange(@NotNull final LineRange range, - @NotNull final Editor editor, - @NotNull final PsiFile file) { - final SelectionModel selectionModel = editor.getSelectionModel(); - Pair psiRange; - if (selectionModel.hasSelection()) { - final int startOffset = selectionModel.getSelectionStart(); - final int endOffset = selectionModel.getSelectionEnd(); - final PsiElement[] psiElements = findStatementsInRange(file, startOffset, endOffset); - if (psiElements.length == 0) { - return null; - } - psiRange = new Pair(psiElements[0], psiElements[psiElements.length - 1]); - } - else { - final int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - if (element == null) { - return null; - } - psiRange = new Pair(element, element); - } - - psiRange = new Pair(PsiTreeUtil.getNonStrictParentOfType(psiRange.getFirst(), PyStatement.class), - PsiTreeUtil.getNonStrictParentOfType(psiRange.getSecond(), PyStatement.class)); - if (psiRange.getFirst() == null || psiRange.getSecond() == null) { - return null; - } - - final PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond()); - final Pair elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond()); - if (isNotValidStatementRange(elementRange)) { - return null; - } - - final PsiElement first = elementRange.getFirst(); - final PsiElement second = elementRange.getSecond(); - if (first == second && first instanceof PyPassStatement) { - return null; - } - - int startOffset = first.getTextRange().getStartOffset(); - int endOffset = second.getTextRange().getEndOffset(); - final Document document = editor.getDocument(); - if (endOffset > document.getTextLength()) { - LOG.assertTrue(!PsiDocumentManager.getInstance(file.getProject()).isUncommited(document)); - LOG.assertTrue(PsiDocumentManagerImpl.checkConsistency(file, document)); - } - - int endLine; - if (endOffset == document.getTextLength()) { - endLine = document.getLineCount(); - } - else { - endLine = editor.offsetToLogicalPosition(endOffset).line + 1; - endLine = Math.min(endLine, document.getLineCount()); - } - endLine = Math.max(endLine, range.endLine); - final int startLine = Math.min(range.startLine, editor.offsetToLogicalPosition(startOffset).line); - return new LineRange(startLine, endLine); - } - private @Nullable PyStatementList myStatementListToAddPass; - private @Nullable PyStatementList myStatementListToRemovePass; - private @NotNull PsiElement[] myElementsToChangeIndent; - private int myIndentLevel; + private @Nullable PyStatementList myStatementListToAddPassAfter; + private @Nullable PyStatement myStatementToIncreaseIndent; + private @Nullable PyStatement myStatementToDecreaseIndent; + private @Nullable PyStatement myStatementToAddLinebreak; + private @Nullable PyStatement myStatementToMove; + private @Nullable PyStatementPart myStatementPartToRemovePass; + @Override - public boolean checkAvailable(@NotNull Editor editor, - @NotNull PsiFile file, - @NotNull MoveInfo info, - boolean down) { + public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) { + if (!(file instanceof PyFile)) return false; + if (!super.checkAvailable(editor, file, info, down)) + return false; + + // Do not move in case of selection + if (editor.getSelectionModel().hasSelection()){ + return false; + } + + //reset myStatementListToAddPass = null; - myStatementListToRemovePass = null; - myElementsToChangeIndent = PsiElement.EMPTY_ARRAY; - myIndentLevel = 0; + myStatementListToAddPassAfter = null; + myStatementToDecreaseIndent = null; + myStatementToIncreaseIndent = null; + myStatementToAddLinebreak = null; + myStatementPartToRemovePass = null; - if (!(file instanceof PyFile)) { - return false; - } - if (!super.checkAvailable(editor, file, info, down)) { - return false; - } - info.indentSource = true; - final LineRange range = expandLineRange(info.toMove, editor, file); - if (range == null) { - return false; - } + //find statement + myStatementToMove = findStatement(editor, file, info); - info.toMove = range; - final int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0)); - final int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine, 0)); - final PsiElement[] statements = findStatementsInRange(file, startOffset, endOffset); - final int length = statements.length; - if (length == 0) { - return false; + // check if we want to move statement + if (myStatementToMove == null) return false; + //do not move pass statement + if (myStatementToMove instanceof PyPassStatement) { + info.toMove2 = info.toMove; + return true; } - - range.firstElement = statements[0]; - range.lastElement = statements[length - 1]; - final Document document = editor.getDocument(); - PyStatement statement; - assert info.toMove2.startLine + 1 == info.toMove2.endLine; - if (down) { - statement = PsiTreeUtil.getNextSiblingOfType(range.lastElement, PyStatement.class); - } - else { - statement = PsiTreeUtil.getPrevSiblingOfType(range.firstElement, PyStatement.class); - } - - if (statement == null) { - final PyStatementPart parentStatementPart = - PsiTreeUtil.getParentOfType(PsiTreeUtil.findCommonParent(range.firstElement, range.lastElement), PyStatementPart.class, false); - if (parentStatementPart == null) { - info.toMove2 = null; - } - else { //we are in statement part - - final PyStatementList statementList = parentStatementPart.getStatementList(); - assert statementList != null; - if (down) { - PyStatementPart nextStatementPart = PsiTreeUtil.getNextSiblingOfType(statementList.getParent(), PyStatementPart.class); - if (nextStatementPart != null) { - info.toMove2 = new LineRange(range.endLine, range.endLine + 1); - myStatementListToRemovePass = nextStatementPart.getStatementList(); - } - else { - PsiElement parent = statementList; - PyStatement nextStatement; - while (true) { - parent = PsiTreeUtil.getParentOfType(parent, PyStatement.class); - if (parent == null) { - return false; - } - nextStatement = PsiTreeUtil.getNextSiblingOfType(parent, PyStatement.class); - if (nextStatement instanceof PyFunction || nextStatement instanceof PyClass) { - return false; - } - if (nextStatement != null) { - break; - } - } - final int startLine = editor.offsetToLogicalPosition(parent.getTextRange().getEndOffset()).line; - final int endLine = editor.offsetToLogicalPosition(nextStatement.getTextRange().getEndOffset()).line; - info.toMove2 = new LineRange(startLine + 1, endLine + 1); - nextStatementPart = PsiTreeUtil.getChildOfType(nextStatement, PyStatementPart.class); - if (nextStatementPart != null) { - calculateIndent(editor, statementList, nextStatementPart.getStatementList(), down); - } - else { - calculateIndent(editor, statementList, nextStatement, down); - } - myElementsToChangeIndent = statements; - } - } - else { - final PyStatementPart prevStatementPart = PsiTreeUtil.getPrevSiblingOfType(statementList.getParent(), PyStatementPart.class); - if (prevStatementPart != null) { - myStatementListToRemovePass = prevStatementPart.getStatementList(); - } - else { - myIndentLevel = -1; - myElementsToChangeIndent = statements; - info.toMove2 = new LineRange(range.startLine - 1, range.startLine); - } - } - if (Arrays.equals(statementList.getStatements(), statements)) { - myStatementListToAddPass = statementList; - } - } + // do not move last statement down and first up + if (isFirstOrLastStatement(down)) { + info.toMove2 = info.toMove; return true; } - info.toMove2 = new LineRange(statement, statement, document); - final PyStatementPart[] statementParts = PsiTreeUtil.getChildrenOfType(statement, PyStatementPart.class); + expandLineRangeToStatement(info, editor, down, file); - // next/previous statement has a statement parts - // move inside statement part - if (statementParts != null) { - int startLineNumber; - int endLineNumber; - PyStatementPart statementPart; - if (down) { - statementPart = statementParts[0]; - startLineNumber = document.getLineNumber(statementPart.getTextRange().getStartOffset()); - endLineNumber = document.getLineNumber(statementPart.getTextRange().getEndOffset()); - } - else { - statementPart = statementParts[statementParts.length - 1]; - startLineNumber = document.getLineNumber(statementPart.getTextRange().getEndOffset()); - endLineNumber = document.getLineNumber(statementPart.getTextRange().getStartOffset()); - } + //is move from one part of compound statement to another + boolean theSameLevel = isTheSameIndentLevel(info, editor, file, down); - if (startLineNumber != endLineNumber) { - info.toMove2 = new LineRange(startLineNumber, startLineNumber + 1); - myStatementListToRemovePass = statementPart.getStatementList(); - calculateIndent(editor, range.firstElement, myStatementListToRemovePass, down); - myElementsToChangeIndent = statements; + //check we move statement into compound or out of compound + if (isMoveOut(info, editor, file, down)) { + myStatementToDecreaseIndent = myStatementToMove; + if (down) + info.toMove2 = new LineRange(myStatementToMove); + } + if (isMoveToCompound(info, editor, file, down)) { + myStatementToIncreaseIndent = myStatementToMove; + if (!down) + info.toMove2 = new LineRange(myStatementToMove); + } + + //find statement to add pass if we removed last statement from PyStatementPart + PyStatementPart statementPart = PsiTreeUtil.getParentOfType(myStatementToMove, PyStatementPart.class, false); + if (statementPart != null) { + PyStatementList statementList = statementPart.getStatementList(); + if (statementList != null && statementList.getStatements().length == 1) { + if (theSameLevel) { + myStatementListToAddPassAfter = statementList; + } + else + myStatementListToAddPass = statementList; } } return true; } - - private void calculateIndent(final Editor editor, final PsiElement firstElement, final PsiElement secondElement, final boolean down) { - final PsiFile file = firstElement.getContainingFile(); - final int firstIndent = getIndent(editor, file, editor.offsetToLogicalPosition(firstElement.getTextRange().getStartOffset()).line); - int line; - if (down) { - line = editor.offsetToLogicalPosition(secondElement.getTextRange().getStartOffset()).line; - } - else { - line = editor.offsetToLogicalPosition(secondElement.getTextRange().getEndOffset()).line; - } - final int secondIndent = getIndent(editor, file, line); - myIndentLevel = (secondIndent - firstIndent) >> 2; - } - - private static int getIndent(final Editor editor, final PsiFile file, final int lineNumber) { - int indent = 0; - final int offset = editor.logicalPositionToOffset(new LogicalPosition(lineNumber, 0)); - PsiElement element = file.findElementAt(offset); - if (element instanceof PsiWhiteSpace) { - final String text = element.getText(); - String[] lines = text.split("\n"); - if (lines.length == 0) { - return 0; - } - indent = lines[lines.length - 1].length(); - } - return indent; - } - - private void decreaseIndent(final Editor editor) { - final Document document = editor.getDocument(); - for (PsiElement statement : myElementsToChangeIndent) { - final int startOffset = statement.getTextRange().getStartOffset() - 1; - PsiElement element = statement.getContainingFile().findElementAt(startOffset); - assert element instanceof PsiWhiteSpace; - final String text = element.getText(); - String[] lines = text.split("\n"); - if (lines.length == 0) { - continue; - } - final int indent = lines[lines.length - 1].length(); - final int startLine = editor.offsetToLogicalPosition(startOffset).line; - final int endLine = editor.offsetToLogicalPosition(statement.getTextRange().getEndOffset()).line; - for (int line = startLine; line <= endLine; ++line) { - final int indentLevel = myIndentLevel * -4; - if (indent >= 4 && indentLevel <= indent) { - final int lineStartOffset = document.getLineStartOffset(line); - document.deleteString(lineStartOffset, lineStartOffset + indentLevel); + private boolean isFirstOrLastStatement(boolean down) { + PyFunction function = PsiTreeUtil.getParentOfType(myStatementToMove, PyFunction.class, true); + if (function != null) { + PyStatementList statementList = function.getStatementList(); + if (statementList != null && statementList.getStatements().length > 0) { + if ((statementList.getStatements()[0] == myStatementToMove && !down) + || (statementList.getStatements()[statementList.getStatements().length-1] == myStatementToMove && down)) { + return true; } } } + PyClass cl = PsiTreeUtil.getParentOfType(myStatementToMove, PyClass.class, true); + if (cl != null) { + PyStatementList statementList = cl.getStatementList(); + if (statementList.getStatements().length > 0) { + if ((statementList.getStatements()[0] == myStatementToMove && !down) + || (statementList.getStatements()[statementList.getStatements().length-1] == myStatementToMove && down)) { + return true; + } + } + } + return false; } - private void increaseIndent(final Editor editor) { - final Document document = editor.getDocument(); - String indent = makeIndent(); - for (PsiElement statement : myElementsToChangeIndent) { - final int startLine = editor.offsetToLogicalPosition(statement.getTextRange().getStartOffset()).line; - final int endLine = editor.offsetToLogicalPosition(statement.getTextRange().getEndOffset()).line; - for (int line = startLine; line <= endLine; ++line) { - final int offset = document.getLineStartOffset(line); - document.insertString(offset, indent); + @Nullable + private PyStatement findStatement(Editor editor, PsiFile file, MoveInfo info) { + Document doc = editor.getDocument(); + int offset1 = getLineStartSafeOffset(doc, info.toMove.startLine); + PsiElement element1 = file.findElementAt(offset1); + if (element1 != null) { + if (element1 instanceof PsiWhiteSpace) { + element1 = PyPsiUtils.getSignificantToTheRight(element1, true); + } + return PsiTreeUtil.getParentOfType(element1, PyStatement.class, false); + } + return null; + } + + private void expandLineRangeToStatement(MoveInfo info, Editor editor, boolean down, PsiFile file) { + Document doc = editor.getDocument(); + + TextRange textRange = myStatementToMove.getTextRange(); + LineRange range = new LineRange(doc.getLineNumber(textRange.getStartOffset()), + doc.getLineNumber(textRange.getEndOffset())+1); + int nearLine = down ? range.endLine : range.startLine - 1; + if (nearLine < 0) nearLine = 0; + info.toMove = range; + info.toMove2 = new LineRange(nearLine, nearLine + 1); + + // if try to move to the function or to the class + int offset2 = getLineStartSafeOffset(doc, info.toMove2.startLine); + PsiElement element2 = file.findElementAt(offset2); + if (element2 != null) { + if (element2 instanceof PsiWhiteSpace) { + PsiElement tmp = PyPsiUtils.getSignificantToTheRight(element2, true); + if (tmp != null && + editor.offsetToLogicalPosition(tmp.getTextRange().getStartOffset()).line + == info.toMove2.startLine) { + element2 = tmp; + } + } + PyElement parent2 = PsiTreeUtil.getParentOfType(element2, PyFunction.class, false); + PyElement parent1 = PsiTreeUtil.getParentOfType(myStatementToMove, PyFunction.class, false); + if (parent2 != null && parent2 != parent1) { + TextRange textRange2 = parent2.getTextRange(); + info.toMove2 = new LineRange(doc.getLineNumber(textRange2.getStartOffset()), + doc.getLineNumber(textRange2.getEndOffset())+1); + } + parent2 = PsiTreeUtil.getParentOfType(element2, PyClass.class, false); + parent1 = PsiTreeUtil.getParentOfType(myStatementToMove, PyClass.class, false); + if (parent2 != null && parent2 != parent1) { + TextRange textRange2 = parent2.getTextRange(); + info.toMove2 = new LineRange(doc.getLineNumber(textRange2.getStartOffset()), + doc.getLineNumber(textRange2.getEndOffset())+1); } } } - private String makeIndent() { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < myIndentLevel; ++i) { - result.append(" "); + /** + * main indent logic + * @return first is the element which we move + * second is the element we move to + */ + private Pair getStatementParts(MoveInfo info, Editor editor, PsiFile file, boolean down) { + PsiElement element1 = myStatementToMove; + PyStatementPart statementPart1 = PsiTreeUtil.getParentOfType(element1, PyStatementPart.class, false); + + int offset2 = getLineStartSafeOffset(editor.getDocument(), info.toMove2.startLine); + PsiElement element2 = file.findElementAt(offset2-1); + if (element2 instanceof PsiWhiteSpace) { + if (down) { + PsiElement tmp = PyPsiUtils.getSignificantToTheRight(element2, true); + if (tmp != null && + editor.offsetToLogicalPosition(tmp.getTextRange().getStartOffset()).line == info.toMove2.startLine) + element2 = tmp; + + } else { + PsiElement tmp = PyPsiUtils.getSignificantToTheRight(element2, true); + if (tmp != null) { + int start = editor.offsetToLogicalPosition(tmp.getParent().getTextRange().getStartOffset()).line; + int end = editor.offsetToLogicalPosition(tmp.getParent().getTextRange().getEndOffset()).line; + if (start == end && start == info.toMove2.startLine) + element2 = tmp; + else + element2 = PyPsiUtils.getSignificantToTheLeft(element2, true); + } + else { + element2 = PyPsiUtils.getSignificantToTheLeft(element2, true); + } + } } - return result.toString(); + PyStatementPart statementPart2 = PsiTreeUtil.getParentOfType(element2, PyStatementPart.class); + if (statementPart2 != null) { + PyStatementList stList = statementPart2.getStatementList(); + if (stList != null && stList.getStatements().length > 0) { + if (down && stList.getStatements()[stList.getStatements().length-1] == element2) { + PyStatementPart parent = PsiTreeUtil.getParentOfType(statementPart2, PyStatementPart.class); + if (parent != null) { + stList = parent.getStatementList(); + if (stList != null && stList.getStatements().length > 0) { + if (stList.getStatements()[stList.getStatements().length-1] == statementPart2) { + statementPart2 = parent; + } + } + } + } + } + } + + return new Pair(statementPart1, statementPart2); + } + + private boolean isMoveToCompound(MoveInfo info, Editor editor, PsiFile file, boolean down) { + Pair statementParts = getStatementParts(info, editor, file, down); + PyStatementPart statementPart1 = statementParts.first; + PyStatementPart statementPart2 = statementParts.second; + + if (statementPart2 != null) { + prepareToStatement(statementPart2, editor.getDocument()); + if (statementPart1 == null) return true; + if (statementPart1.getParent() != statementPart2.getParent()) { + PsiElement commonParent = PsiTreeUtil.findCommonParent(statementPart1, statementPart2); + if (PsiTreeUtil.isAncestor(statementPart2, statementPart1, false)) return false; + if ((commonParent instanceof PyIfStatement) || (commonParent instanceof PyLoopStatement) || + (commonParent instanceof PyConditionalStatementPart)) + return true; + } + } + return false; + } + + private boolean isMoveOut(MoveInfo info, Editor editor, PsiFile file, boolean down) { + Pair insertDeleteParts = getStatementParts(info, editor, file, down); + PyStatementPart statementPart1 = insertDeleteParts.first; + PyStatementPart statementPart2 = insertDeleteParts.second; + if (statementPart1 != null) { + if (statementPart2 == null) return true; + if (statementPart1.getParent() != statementPart2.getParent()) { + PsiElement commonParent = PsiTreeUtil.findCommonParent(statementPart1, statementPart2); + if (!(commonParent instanceof PyIfStatement) && !(commonParent instanceof PyLoopStatement) && !(commonParent instanceof PyConditionalStatementPart)) + return true; + if (PsiTreeUtil.isAncestor(statementPart2, statementPart1, false)) return true; + } + } + return false; + } + + private void prepareToStatement(PyStatementPart part, Document document) { + int partLine = document.getLineNumber(part.getTextRange().getStartOffset()); + PyStatementList stList = part.getStatementList(); + if (stList == null) return; + if (stList.getStatements().length < 1) return; + int statementLine = document.getLineNumber(stList.getStatements()[0].getTextRange().getStartOffset()); + if (partLine == statementLine) { + myStatementToAddLinebreak = stList.getStatements()[0]; + } + } + + private boolean isTheSameIndentLevel(MoveInfo info, Editor editor, PsiFile file, boolean down) { + Pair statementParts = getStatementParts(info, editor, file, down); + myStatementPartToRemovePass = statementParts.second; + PyStatementPart statementPart1 = statementParts.first; + PyStatementPart statementPart2 = statementParts.second; + + if (statementPart2 != null && statementPart1 != null && statementPart1.getParent() == statementPart2.getParent()) return true; + return false; + } + + private void decreaseIndent(Editor editor) { + CodeStyleSettings.IndentOptions indentOptions = CodeStyleSettingsManager.getInstance(editor.getProject()). + getCurrentSettings().getIndentOptions(PythonFileType.INSTANCE); + assert indentOptions != null; + final Document document = editor.getDocument(); + TextRange textRange = myStatementToDecreaseIndent.getTextRange(); + document.deleteString(textRange.getStartOffset()-indentOptions.INDENT_SIZE, textRange.getStartOffset()); + myStatementToDecreaseIndent = null; + } + + private void increaseIndent(final Editor editor) { + CodeStyleSettings.IndentOptions indentOptions = CodeStyleSettingsManager.getInstance(editor.getProject()). + getCurrentSettings().getIndentOptions(PythonFileType.INSTANCE); + assert indentOptions != null; + final Document document = editor.getDocument(); + final int startLine = editor.offsetToLogicalPosition(myStatementToIncreaseIndent.getTextRange().getStartOffset()).line; + final int endLine = editor.offsetToLogicalPosition(myStatementToIncreaseIndent.getTextRange().getEndOffset()).line; + for (int line = startLine; line <= endLine; ++line) { + final int offset = document.getLineStartOffset(line); + document.insertString(offset, StringUtil.repeat(" ", indentOptions.INDENT_SIZE)); + } + myStatementToIncreaseIndent = null; } @Override public void beforeMove(@NotNull Editor editor, @NotNull MoveInfo info, boolean down) { - super.beforeMove(editor, info, down); + if (myStatementToAddLinebreak != null) { + //PY-950 + TextRange textRange = myStatementToAddLinebreak.getTextRange(); + int line = editor.getDocument().getLineNumber(textRange.getStartOffset()); + CodeStyleSettings.IndentOptions indentOptions = CodeStyleSettingsManager.getInstance(editor.getProject()). + getCurrentSettings().getIndentOptions(PythonFileType.INSTANCE); + + PsiElement whiteSpace = myStatementToAddLinebreak.getContainingFile().findElementAt(editor.getDocument().getLineStartOffset(line)); + String indent = StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE); + if (whiteSpace instanceof PsiWhiteSpace) indent += whiteSpace.getText(); + if (down) indent += StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE); + editor.getDocument().insertString(textRange.getStartOffset(), indent); + } + // add pass statement if needed if (myStatementListToAddPass != null) { final PyPassStatement passStatement = PyElementGenerator.getInstance(editor.getProject()).createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS); - myStatementListToAddPass.add(passStatement); - CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(myStatementListToAddPass); - if (down) { - info.toMove2 = new LineRange(info.toMove2.startLine, info.toMove2.endLine + 1); + if (!down) { + myStatementListToAddPass.add(passStatement); } + else + myStatementListToAddPass.addBefore(passStatement, myStatementListToAddPass.getStatements()[0]); + CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(myStatementListToAddPass); } - if (myIndentLevel < 0) { - decreaseIndent(editor); - } - else { + if (myStatementToIncreaseIndent != null) { increaseIndent(editor); } + if (myStatementToDecreaseIndent != null) { + decreaseIndent(editor); + } } @Override - public void afterMove(@NotNull Editor editor, - @NotNull PsiFile file, - @NotNull MoveInfo info, - boolean down) { - super.afterMove(editor, file, info, down); - if (myStatementListToRemovePass != null) { - PyPsiUtils.removeRedundantPass(myStatementListToRemovePass); - CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(myStatementListToRemovePass); + public void afterMove(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) { + // add pass statement if needed + if (myStatementListToAddPassAfter != null && myStatementListToAddPassAfter.isValid()) { + final PyPassStatement passStatement = + PyElementGenerator.getInstance(editor.getProject()).createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS); + myStatementListToAddPassAfter.add(passStatement); + CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(myStatementListToAddPassAfter); + } + + // remove obsolete pass statement + if (myStatementPartToRemovePass != null) { + PyStatementList statementList = myStatementPartToRemovePass.getStatementList(); + if (statementList != null) { + PyPsiUtils.removeRedundantPass(statementList); + } } } + } diff --git a/python/testData/mover/decorator.py b/python/testData/mover/decorator.py index 0574d2b5ff8e..2e94f218446d 100644 --- a/python/testData/mover/decorator.py +++ b/python/testData/mover/decorator.py @@ -1,6 +1,5 @@ def a(): print 'a' - @task def b(): print 'b' diff --git a/python/testData/mover/decorator_afterUp.py b/python/testData/mover/decorator_afterUp.py index d02715a32088..2168160ed5ce 100644 --- a/python/testData/mover/decorator_afterUp.py +++ b/python/testData/mover/decorator_afterUp.py @@ -1,6 +1,5 @@ @task -def b(): +def b(): print 'b' - def a(): print 'a' diff --git a/python/testData/mover/doubleIf.py b/python/testData/mover/doubleIf.py new file mode 100644 index 000000000000..fc6eb136e1bb --- /dev/null +++ b/python/testData/mover/doubleIf.py @@ -0,0 +1,7 @@ +if condition1: + pass + +if condition: + test1() +else: + test2() \ No newline at end of file diff --git a/python/testData/mover/doubleIf_afterDown.py b/python/testData/mover/doubleIf_afterDown.py new file mode 100644 index 000000000000..d2cb93b4236a --- /dev/null +++ b/python/testData/mover/doubleIf_afterDown.py @@ -0,0 +1,7 @@ +if condition1: + pass + +if condition: + test1() +else: + test2() \ No newline at end of file diff --git a/python/testData/mover/doubleIf_afterUp.py b/python/testData/mover/doubleIf_afterUp.py new file mode 100644 index 000000000000..51dd66023951 --- /dev/null +++ b/python/testData/mover/doubleIf_afterUp.py @@ -0,0 +1,5 @@ +if condition1: + if condition: + test1() + else: + test2() diff --git a/python/testData/mover/functions.py b/python/testData/mover/functions.py index 1bacefed9bca..1e975f1ef17d 100644 --- a/python/testData/mover/functions.py +++ b/python/testData/mover/functions.py @@ -1,9 +1,7 @@ class A: def a(self): - doSomething() - + doSomething1() def b(self): - doSomething() - + doSomething2() def c(self): - doSomething() \ No newline at end of file + doSomething3() \ No newline at end of file diff --git a/python/testData/mover/functions_afterDown.py b/python/testData/mover/functions_afterDown.py index 59a4c20af1fc..886e83633c09 100644 --- a/python/testData/mover/functions_afterDown.py +++ b/python/testData/mover/functions_afterDown.py @@ -1,9 +1,7 @@ class A: def a(self): - doSomething() - + doSomething1() def c(self): - doSomething() - + doSomething3() def b(self): - doSomething() + doSomething2() diff --git a/python/testData/mover/functions_afterUp.py b/python/testData/mover/functions_afterUp.py index 4f1eff1075a8..564040151bb0 100644 --- a/python/testData/mover/functions_afterUp.py +++ b/python/testData/mover/functions_afterUp.py @@ -1,9 +1,7 @@ class A: def b(self): - doSomething() - + doSomething2() def a(self): - doSomething() - + doSomething1() def c(self): - doSomething() \ No newline at end of file + doSomething3() \ No newline at end of file diff --git a/python/testData/mover/indent_afterDown.py b/python/testData/mover/indent_afterDown.py index 87e9a82d4837..d42f4447ee36 100644 --- a/python/testData/mover/indent_afterDown.py +++ b/python/testData/mover/indent_afterDown.py @@ -5,9 +5,9 @@ class A: if c: if d: self.bar() + c = 3 if c: a = 2 - c = 3 def bar(self): pass \ No newline at end of file diff --git a/python/testData/mover/indent_afterUp.py b/python/testData/mover/indent_afterUp.py index 45ee716ff217..2191d741443e 100644 --- a/python/testData/mover/indent_afterUp.py +++ b/python/testData/mover/indent_afterUp.py @@ -4,8 +4,8 @@ class A: if b: if c: if d: - c = 3 self.bar() + c = 3 if c: a = 2 diff --git a/python/testData/mover/innerIf.py b/python/testData/mover/innerIf.py new file mode 100644 index 000000000000..2faa0c9eaaea --- /dev/null +++ b/python/testData/mover/innerIf.py @@ -0,0 +1,3 @@ +if value is not None: + if not False or value <= 2: + print "here" \ No newline at end of file diff --git a/python/testData/mover/innerIf_afterDown.py b/python/testData/mover/innerIf_afterDown.py new file mode 100644 index 000000000000..2cc2ebae0c7f --- /dev/null +++ b/python/testData/mover/innerIf_afterDown.py @@ -0,0 +1,3 @@ +if value is not None: + if not False or value <= 2: + print "here" \ No newline at end of file diff --git a/python/testData/mover/innerIf_afterUp.py b/python/testData/mover/innerIf_afterUp.py new file mode 100644 index 000000000000..6be6e6c8aba8 --- /dev/null +++ b/python/testData/mover/innerIf_afterUp.py @@ -0,0 +1,4 @@ +if value is not None: + print "here" + if not False or value <= 2: + pass \ No newline at end of file diff --git a/python/testData/mover/lastLine.py b/python/testData/mover/lastLine.py new file mode 100644 index 000000000000..eb0e5a501806 --- /dev/null +++ b/python/testData/mover/lastLine.py @@ -0,0 +1,3 @@ +print "first" + +print "second" \ No newline at end of file diff --git a/python/testData/mover/lastLine_afterDown.py b/python/testData/mover/lastLine_afterDown.py new file mode 100644 index 000000000000..2b863a237bf3 --- /dev/null +++ b/python/testData/mover/lastLine_afterDown.py @@ -0,0 +1,3 @@ +print "first" + +print "second" \ No newline at end of file diff --git a/python/testData/mover/lastLine_afterUp.py b/python/testData/mover/lastLine_afterUp.py new file mode 100644 index 000000000000..4335a1f05998 --- /dev/null +++ b/python/testData/mover/lastLine_afterUp.py @@ -0,0 +1,3 @@ +print "first" +print "second" + diff --git a/python/testData/mover/moveDownOut.py b/python/testData/mover/moveDownOut.py new file mode 100644 index 000000000000..083e267a8640 --- /dev/null +++ b/python/testData/mover/moveDownOut.py @@ -0,0 +1,5 @@ +if condition: + if condition2: + print "test" + print "test1" +print "test2" diff --git a/python/testData/mover/moveDownOut_afterDown.py b/python/testData/mover/moveDownOut_afterDown.py new file mode 100644 index 000000000000..bc5641c09592 --- /dev/null +++ b/python/testData/mover/moveDownOut_afterDown.py @@ -0,0 +1,5 @@ +if condition: + if condition2: + print "test" + print "test1" +print "test2" diff --git a/python/testData/mover/moveDownOut_afterUp.py b/python/testData/mover/moveDownOut_afterUp.py new file mode 100644 index 000000000000..1289c59bf07c --- /dev/null +++ b/python/testData/mover/moveDownOut_afterUp.py @@ -0,0 +1,5 @@ +if condition: + if condition2: + print "test1" + print "test" +print "test2" diff --git a/python/testData/mover/moveOut.py b/python/testData/mover/moveOut.py new file mode 100644 index 000000000000..3fa767036ec2 --- /dev/null +++ b/python/testData/mover/moveOut.py @@ -0,0 +1,7 @@ +while condition1: + pass + +if condition: + test1() +else: + test2() \ No newline at end of file diff --git a/python/testData/mover/moveOut_afterDown.py b/python/testData/mover/moveOut_afterDown.py new file mode 100644 index 000000000000..8e959921ccf8 --- /dev/null +++ b/python/testData/mover/moveOut_afterDown.py @@ -0,0 +1,8 @@ +while condition1: + pass + +if condition: + pass +else: + test1() + test2() \ No newline at end of file diff --git a/python/testData/mover/moveOut_afterUp.py b/python/testData/mover/moveOut_afterUp.py new file mode 100644 index 000000000000..a3c35a5b086a --- /dev/null +++ b/python/testData/mover/moveOut_afterUp.py @@ -0,0 +1,8 @@ +while condition1: + pass + +test1() +if condition: + pass +else: + test2() \ No newline at end of file diff --git a/python/testData/mover/moveStatement_afterUp.py b/python/testData/mover/moveStatement_afterUp.py index 25bb65e44bdc..104a69de02e1 100644 --- a/python/testData/mover/moveStatement_afterUp.py +++ b/python/testData/mover/moveStatement_afterUp.py @@ -1,12 +1,12 @@ class A: def f(self, a, b): while a: + a = a / 2 if a: b = 1 elif b: a = 1 else: print "a" - a = a / 2 for i in range(1, 10): print i \ No newline at end of file diff --git a/python/testData/mover/nestedBlock.py b/python/testData/mover/nestedBlock.py new file mode 100644 index 000000000000..af6006847e81 --- /dev/null +++ b/python/testData/mover/nestedBlock.py @@ -0,0 +1,7 @@ +def test(a, b=1): + if a: + print(a) + else: + for n in range(10): + + print(b) \ No newline at end of file diff --git a/python/testData/mover/nestedBlock_afterDown.py b/python/testData/mover/nestedBlock_afterDown.py new file mode 100644 index 000000000000..ea4e4c911957 --- /dev/null +++ b/python/testData/mover/nestedBlock_afterDown.py @@ -0,0 +1,7 @@ +def test(a, b=1): + if a: + print(a) + else: + for n in range(10): + + print(b) \ No newline at end of file diff --git a/python/testData/mover/nestedBlock_afterUp.py b/python/testData/mover/nestedBlock_afterUp.py new file mode 100644 index 000000000000..ff86d5d5ffce --- /dev/null +++ b/python/testData/mover/nestedBlock_afterUp.py @@ -0,0 +1,7 @@ +def test(a, b=1): + if a: + print(a) + else: + for n in range(10): + print(b) + diff --git a/python/testData/mover/oneStatementInClass.py b/python/testData/mover/oneStatementInClass.py new file mode 100644 index 000000000000..8b53db0dd2c4 --- /dev/null +++ b/python/testData/mover/oneStatementInClass.py @@ -0,0 +1,4 @@ +class B(Exception): + pass #<---here +class C(B): + pass \ No newline at end of file diff --git a/python/testData/mover/oneStatementInClass_afterDown.py b/python/testData/mover/oneStatementInClass_afterDown.py new file mode 100644 index 000000000000..d91a13186f62 --- /dev/null +++ b/python/testData/mover/oneStatementInClass_afterDown.py @@ -0,0 +1,4 @@ +class B(Exception): + pass #<---here +class C(B): + pass \ No newline at end of file diff --git a/python/testData/mover/oneStatementInClass_afterUp.py b/python/testData/mover/oneStatementInClass_afterUp.py new file mode 100644 index 000000000000..d91a13186f62 --- /dev/null +++ b/python/testData/mover/oneStatementInClass_afterUp.py @@ -0,0 +1,4 @@ +class B(Exception): + pass #<---here +class C(B): + pass \ No newline at end of file diff --git a/python/testData/mover/oneStatementInFunction.py b/python/testData/mover/oneStatementInFunction.py new file mode 100644 index 000000000000..1ba5e9ce570b --- /dev/null +++ b/python/testData/mover/oneStatementInFunction.py @@ -0,0 +1,4 @@ +test2() +def foo(): + test() +test1() diff --git a/python/testData/mover/oneStatementInFunction_afterDown.py b/python/testData/mover/oneStatementInFunction_afterDown.py new file mode 100644 index 000000000000..d1fe3839f496 --- /dev/null +++ b/python/testData/mover/oneStatementInFunction_afterDown.py @@ -0,0 +1,4 @@ +test2() +def foo(): + test() +test1() diff --git a/python/testData/mover/oneStatementInFunction_afterUp.py b/python/testData/mover/oneStatementInFunction_afterUp.py new file mode 100644 index 000000000000..d1fe3839f496 --- /dev/null +++ b/python/testData/mover/oneStatementInFunction_afterUp.py @@ -0,0 +1,4 @@ +test2() +def foo(): + test() +test1() diff --git a/python/testData/mover/outsideStatement_afterDown.py b/python/testData/mover/outsideStatement_afterDown.py index 186e43937bff..db7713a3db6f 100644 --- a/python/testData/mover/outsideStatement_afterDown.py +++ b/python/testData/mover/outsideStatement_afterDown.py @@ -3,6 +3,6 @@ def foo(): if a: pass - - a = 4 a = 3 + + a = 4 \ No newline at end of file diff --git a/python/testData/mover/py950_afterDown.py b/python/testData/mover/py950_afterDown.py index 7ef13131f14a..8c188655aa3e 100644 --- a/python/testData/mover/py950_afterDown.py +++ b/python/testData/mover/py950_afterDown.py @@ -2,6 +2,6 @@ class A: def foo(self): if a: pass else: pass - if a: pass - else: pass - a = 0 + if a: + a = 0 + else: pass \ No newline at end of file diff --git a/python/testData/mover/py950_afterUp.py b/python/testData/mover/py950_afterUp.py index d6e3ed85ab57..9e8078b1a87c 100644 --- a/python/testData/mover/py950_afterUp.py +++ b/python/testData/mover/py950_afterUp.py @@ -1,7 +1,7 @@ class A: def foo(self): - a = 0 if a: pass - else: pass + else: + a = 0 if a: pass else: pass \ No newline at end of file diff --git a/python/testData/mover/simpleBlankLines_afterDown.py b/python/testData/mover/simpleBlankLines_afterDown.py index 91b5334ed539..bbc5e402a7eb 100644 --- a/python/testData/mover/simpleBlankLines_afterDown.py +++ b/python/testData/mover/simpleBlankLines_afterDown.py @@ -2,8 +2,8 @@ if a: a = 1 - c = 3 - - b = 2 + + + c = 3 \ No newline at end of file diff --git a/python/testData/mover/simpleBlankLines_afterUp.py b/python/testData/mover/simpleBlankLines_afterUp.py index fcfdc80996a5..2b56e3aa27ea 100644 --- a/python/testData/mover/simpleBlankLines_afterUp.py +++ b/python/testData/mover/simpleBlankLines_afterUp.py @@ -1,9 +1,9 @@ if a: + a = 1 + b = 2 - a = 1 - c = 3 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java index 80d6e8c352da..abbc851bb806 100644 --- a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java +++ b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java @@ -24,6 +24,18 @@ public class PyStatementMoverTest extends PyTestCase { doTest(); } + public void testInnerIf() { + doTest(); + } + + public void testMoveDownOut() { + doTest(); + } + + public void testOneStatementInFunction() { + doTest(); + } + public void testOutsideStatement() { doTest(); } @@ -44,7 +56,15 @@ public class PyStatementMoverTest extends PyTestCase { doTest(); } - public void testSelection() { + public void testDoubleIf() { + doTest(); + } + + public void testOneStatementInClass() { + doTest(); + } + + public void testMoveOut() { doTest(); } @@ -63,4 +83,12 @@ public class PyStatementMoverTest extends PyTestCase { public void testDecorator() { doTest(); } + + public void testLastLine() { // PY-5017 + doTest(); + } + + public void testNestedBlock() { // PY-1343 + doTest(); + } }