mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
meet brand new python move statement
work over PSI instead of document lines
This commit is contained in:
@@ -352,7 +352,7 @@
|
||||
<gotoDeclarationHandler implementation="com.jetbrains.python.codeInsight.PyBreakContinueGotoProvider" order="FIRST"/>
|
||||
<lang.smartEnterProcessor language="Python"
|
||||
implementationClass="com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor"/>
|
||||
<statementUpDownMover implementation="com.jetbrains.python.codeInsight.editorActions.moveUpDown.StatementMover" id="statement"
|
||||
<statementUpDownMover implementation="com.jetbrains.python.codeInsight.editorActions.moveUpDown.PyStatementMover" id="statement"
|
||||
order="before line"/>
|
||||
|
||||
<filePropertyPusher implementation="com.jetbrains.python.psi.impl.PythonLanguageLevelPusher"/>
|
||||
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
package com.jetbrains.python.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineMover;
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineRange;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.CaretModel;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
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.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User : ktisha
|
||||
*/
|
||||
public class PyStatementMover extends LineMover {
|
||||
@Override
|
||||
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);
|
||||
info.indentTarget = false;
|
||||
info.indentSource = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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;
|
||||
if (elementToMove instanceof PyPassStatement || elementToMove instanceof PyContinueStatement ||
|
||||
elementToMove instanceof PyBreakStatement) return null;
|
||||
|
||||
final int offset = down ? elementToMove.getTextRange().getEndOffset() : elementToMove.getTextRange().getStartOffset();
|
||||
int lineNumber = down ? document.getLineNumber(offset) + 1 : document.getLineNumber(offset) - 1;
|
||||
if (moveOutsideFile(elementToMove, document, lineNumber)) return null;
|
||||
int lineEndOffset = document.getLineEndOffset(lineNumber);
|
||||
final int startOffset = document.getLineStartOffset(lineNumber);
|
||||
lineEndOffset = startOffset != lineEndOffset ? lineEndOffset - 1 : lineEndOffset;
|
||||
|
||||
final PyStatementList statementList = getStatementList(elementToMove);
|
||||
|
||||
final PsiElement destination = getDestinationElement(elementToMove, document, lineEndOffset, down);
|
||||
if (elementToMove instanceof PsiComment && destination instanceof PsiComment) {
|
||||
return new LineRange(lineNumber, lineNumber + 1);
|
||||
}
|
||||
|
||||
if (elementToMove instanceof PyClass || elementToMove instanceof PyFunction) {
|
||||
PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList;
|
||||
if (destination != null)
|
||||
return new ScopeRange(scope, destination, !down, true);
|
||||
}
|
||||
final String lineText = document.getText(TextRange.create(startOffset, lineEndOffset));
|
||||
final boolean isEmptyLine = StringUtil.isEmptyOrSpaces(lineText);
|
||||
if (isEmptyLine && moveToEmptyLine(elementToMove, down)) return new LineRange(lineNumber, lineNumber + 1);
|
||||
|
||||
LineRange scopeRange = moveOut(elementToMove, editor, down);
|
||||
if (scopeRange != null) return scopeRange;
|
||||
scopeRange = moveInto(elementToMove, file, editor, down, lineEndOffset);
|
||||
if (scopeRange != null) return scopeRange;
|
||||
|
||||
final PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList;
|
||||
return new ScopeRange(scope, destination, !down, true);
|
||||
}
|
||||
|
||||
private static boolean moveOutsideFile(@NotNull final PsiElement elementToMove, @NotNull final Document document, int lineNumber) {
|
||||
if (lineNumber >= document.getLineCount() || lineNumber < 0) {
|
||||
final int elementOffset = elementToMove.getTextRange().getStartOffset();
|
||||
final int lineStartOffset = document.getLineStartOffset(document.getLineNumber(elementOffset));
|
||||
final int insertIndex = lineNumber < 0 ? 0 : document.getTextLength();
|
||||
if (elementOffset != lineStartOffset) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
document.insertString(insertIndex, "\n");
|
||||
PsiDocumentManager.getInstance(elementToMove.getProject()).commitAllDocuments();
|
||||
}
|
||||
});
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean moveToEmptyLine(@NotNull final PsiElement elementToMove, boolean down) {
|
||||
final PyStatementList statementList = getStatementList(elementToMove);
|
||||
if (statementList != null) {
|
||||
if (down) {
|
||||
final PsiElement child = statementList.getLastChild();
|
||||
if (elementToMove == child && PsiTreeUtil.getNextSiblingOfType(statementList.getParent(), PyStatementPart.class) != null
|
||||
|| child != elementToMove) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return statementList == null;
|
||||
}
|
||||
|
||||
private static PyStatementList getStatementList(@NotNull final PsiElement elementToMove) {
|
||||
return PsiTreeUtil.getParentOfType(elementToMove, PyStatementList.class, true,
|
||||
PyStatementWithElse.class, PyLoopStatement.class,
|
||||
PyFunction.class, PyClass.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ScopeRange moveOut(@NotNull final PsiElement elementToMove, @NotNull final Editor editor, boolean down) {
|
||||
final PyStatementList statementList = getStatementList(elementToMove);
|
||||
if (statementList == null) return null;
|
||||
|
||||
if ((!down || statementList.getLastChild() != elementToMove) && (down || statementList.getFirstChild() != elementToMove)) {
|
||||
return null;
|
||||
}
|
||||
boolean addBefore = !down;
|
||||
final PsiElement parent = statementList.getParent();
|
||||
final PyStatementPart sibling = down ? PsiTreeUtil.getNextSiblingOfType(parent, PyStatementPart.class)
|
||||
: PsiTreeUtil.getPrevSiblingOfType(parent, PyStatementPart.class);
|
||||
|
||||
if (sibling != null) {
|
||||
final PyStatementList list = sibling.getStatementList();
|
||||
assert list != null;
|
||||
return new ScopeRange(list, down ? list.getFirstChild() : list.getLastChild(), !addBefore);
|
||||
}
|
||||
else {
|
||||
PsiElement scope = getScopeForComment(elementToMove, editor, parent, !down);
|
||||
PsiElement anchor = PsiTreeUtil.getParentOfType(statementList, PyStatement.class);
|
||||
return scope == null ? null : new ScopeRange(scope, anchor, addBefore);
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiElement getScopeForComment(@NotNull final PsiElement elementToMove, @NotNull final Editor editor,
|
||||
@Nullable PsiElement parent, boolean down) {
|
||||
PsiElement scope = PsiTreeUtil.getParentOfType(parent, PyStatementList.class, PyFile.class);
|
||||
final int offset = elementToMove.getTextOffset();
|
||||
PsiElement sibling = elementToMove;
|
||||
while (scope != null && elementToMove instanceof PsiComment) { // stupid workaround for PY-6408. Related to PSI structure
|
||||
final PsiElement prevSibling = down ? PsiTreeUtil.getNextSiblingOfType(sibling, PyStatement.class) :
|
||||
PsiTreeUtil.getPrevSiblingOfType(sibling, PyStatement.class);
|
||||
if (prevSibling == null) break;
|
||||
if (editor.offsetToLogicalPosition(prevSibling.getTextOffset()).column ==
|
||||
editor.offsetToLogicalPosition(offset).column) break;
|
||||
sibling = scope;
|
||||
scope = PsiTreeUtil.getParentOfType(scope, PyStatementList.class, PyFile.class);
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LineRange moveInto(@NotNull final PsiElement elementToMove, @NotNull final PsiFile file,
|
||||
@NotNull final Editor editor, boolean down, int offset) {
|
||||
|
||||
PsiElement rawElement = PyUtil.findNonWhitespaceAtOffset(file, offset);
|
||||
if (rawElement == null) return null;
|
||||
|
||||
return down ? moveDownInto(editor.getDocument(), rawElement) : moveUpInto(elementToMove, editor, rawElement, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LineRange moveUpInto(@NotNull final PsiElement elementToMove, @NotNull final Editor editor,
|
||||
@NotNull final PsiElement rawElement, boolean down) {
|
||||
final Document document = editor.getDocument();
|
||||
PsiElement element = getCommentOrStatement(document, rawElement);
|
||||
final PyStatementList statementList = getStatementList(elementToMove);
|
||||
final PsiElement scopeForComment = statementList == null ? null :
|
||||
getScopeForComment(elementToMove, editor, elementToMove, down);
|
||||
PyStatementList statementList2 = getStatementList(element);
|
||||
final int start1 = elementToMove.getTextOffset() - document.getLineStartOffset(document.getLineNumber(elementToMove.getTextOffset()));
|
||||
final int start2 = element.getTextOffset() - document.getLineStartOffset(document.getLineNumber(element.getTextOffset()));
|
||||
if (start1 != start2) {
|
||||
PyStatementList parent2 = PsiTreeUtil.getParentOfType(statementList2, PyStatementList.class);
|
||||
while (parent2 != scopeForComment && parent2 != null) {
|
||||
element = PsiTreeUtil.getParentOfType(statementList2, PyStatement.class);
|
||||
statementList2 = parent2;
|
||||
parent2 = PsiTreeUtil.getParentOfType(parent2, PyStatementList.class);
|
||||
}
|
||||
}
|
||||
|
||||
if (statementList2 != null && scopeForComment != statementList2 &&
|
||||
(statementList2.getLastChild() == element || statementList2.getLastChild() == elementToMove)) {
|
||||
return new ScopeRange(statementList2, element, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LineRange moveDownInto(@NotNull final Document document, @NotNull final PsiElement rawElement) {
|
||||
PsiElement element = getCommentOrStatement(document, rawElement);
|
||||
PyStatementList statementList2 = getStatementList(element);
|
||||
if (statementList2 != null) { // move to one-line conditional/loop statement
|
||||
final int number = document.getLineNumber(element.getTextOffset());
|
||||
final int number2 = document.getLineNumber(statementList2.getParent().getTextOffset());
|
||||
if (number == number2) {
|
||||
return new ScopeRange(statementList2, statementList2.getFirstChild(), true);
|
||||
}
|
||||
}
|
||||
final PyStatementPart statementPart = PsiTreeUtil.getParentOfType(rawElement, PyStatementPart.class, true, PyStatement.class,
|
||||
PyStatementList.class);
|
||||
final PyFunction functionDefinition = PsiTreeUtil.getParentOfType(rawElement, PyFunction.class, true, PyStatement.class,
|
||||
PyStatementList.class);
|
||||
final PyClass classDefinition = PsiTreeUtil.getParentOfType(rawElement, PyClass.class, true, PyStatement.class,
|
||||
PyStatementList.class);
|
||||
PyStatementList list = null;
|
||||
if (statementPart != null) list = statementPart.getStatementList();
|
||||
else if (functionDefinition != null) list = functionDefinition.getStatementList();
|
||||
else if (classDefinition != null) list = classDefinition.getStatementList();
|
||||
if (list != null) {
|
||||
return new ScopeRange(list, list.getFirstChild(), true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiElement getDestinationElement(@NotNull final PsiElement elementToMove, @NotNull final Document document,
|
||||
int lineEndOffset, boolean down) {
|
||||
PsiElement destination = elementToMove.getContainingFile().findElementAt(lineEndOffset);
|
||||
if (destination == null) return null;
|
||||
PsiElement sibling = down ? PsiTreeUtil.getNextSiblingOfType(elementToMove, PyStatement.class) :
|
||||
PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class);
|
||||
if (elementToMove instanceof PyClass) {
|
||||
destination = sibling;
|
||||
}
|
||||
else if (elementToMove instanceof PyFunction) {
|
||||
if (!(sibling instanceof PyClass))
|
||||
destination = sibling;
|
||||
else destination = null;
|
||||
}
|
||||
else {
|
||||
destination = getCommentOrStatement(document, destination);
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement getCommentOrStatement(@NotNull final Document document, @NotNull PsiElement destination) {
|
||||
final PsiElement statement = PsiTreeUtil.getParentOfType(destination, PyStatement.class, false);
|
||||
if (statement == null) return destination;
|
||||
if (destination instanceof PsiComment) {
|
||||
if (document.getLineNumber(destination.getTextOffset()) == document.getLineNumber(statement.getTextOffset()))
|
||||
destination = statement;
|
||||
}
|
||||
else
|
||||
destination = statement;
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeMove(@NotNull final Editor editor, @NotNull final MoveInfo info, final boolean down) {
|
||||
final LineRange toMove = info.toMove;
|
||||
final LineRange toMove2 = info.toMove2;
|
||||
|
||||
if (toMove instanceof MyLineRange && toMove2 instanceof ScopeRange) {
|
||||
|
||||
PostprocessReformattingAspect.getInstance(editor.getProject()).disablePostprocessFormattingInside(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final PsiElement elementToMove = ((MyLineRange)toMove).myElement;
|
||||
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();
|
||||
|
||||
int offset;
|
||||
if (((ScopeRange)toMove2).isTheSameLevel()) {
|
||||
offset = moveTheSameLevel((ScopeRange)toMove2, (MyLineRange)toMove);
|
||||
}
|
||||
else {
|
||||
offset = moveInOut(((MyLineRange)toMove).myElement, editor, info);
|
||||
}
|
||||
|
||||
caretModel.moveToOffset(offset + shift);
|
||||
info.toMove2 = info.toMove; //do not move further
|
||||
if (hasSelection) {
|
||||
int newSelectionStart = offset + selectionShift;
|
||||
int newSelectionEnd = newSelectionStart + selectionEnd - selectionStart;
|
||||
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;
|
||||
|
||||
final PsiElement anchorCopy = anchor.copy();
|
||||
final PsiElement addedElement = anchor.replace(elementToMove);
|
||||
elementToMove.replace(anchorCopy);
|
||||
|
||||
return addedElement.getTextOffset();
|
||||
}
|
||||
|
||||
private static int moveInOut(@NotNull final PsiElement elementToMove, @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 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);
|
||||
|
||||
|
||||
elementToMove.delete();
|
||||
|
||||
final int addedElementLine = editor.getDocument().getLineNumber(addedElement.getTextOffset());
|
||||
final PsiFile file = scope.getContainingFile();
|
||||
|
||||
adjustLineIndents(editor, scope, project, addedElement);
|
||||
|
||||
if (removePass) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Document document = editor.getDocument();
|
||||
final int lineNumber = document.getLineNumber(anchor.getTextOffset());
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int offset = addedElement.getTextRange().getStartOffset();
|
||||
if (addedElement instanceof PsiComment && offset == 0) { // PsiComment gets broken after adjust indent
|
||||
final PsiElement psiElement = PyUtil.findNonWhitespaceAtOffset(file, editor.getDocument().getLineEndOffset(addedElementLine) - 1);
|
||||
if (psiElement != null) {
|
||||
offset = psiElement.getTextOffset();
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static void adjustLineIndents(@NotNull final Editor editor, @NotNull final PsiElement scope, @NotNull final Project project,
|
||||
@NotNull final PsiElement addedElement) {
|
||||
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
|
||||
final Document document = editor.getDocument();
|
||||
|
||||
if (!(scope instanceof PsiFile)) {
|
||||
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)));
|
||||
}
|
||||
else {
|
||||
int line1 = editor.offsetToLogicalPosition(addedElement.getTextRange().getStartOffset()).line;
|
||||
int line2 = editor.offsetToLogicalPosition(addedElement.getTextRange().getEndOffset()).line;
|
||||
codeStyleManager.adjustLineIndent(scope.getContainingFile(),
|
||||
new TextRange(document.getLineStartOffset(line1), document.getLineStartOffset(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) {
|
||||
final PyPassStatement passStatement = PyElementGenerator.getInstance(project).createPassStatement();
|
||||
initialScope.addAfter(passStatement, initialScope.getStatements()[initialScope.getStatements().length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use to keep element
|
||||
static class MyLineRange extends LineRange {
|
||||
public PsiElement myElement;
|
||||
public MyLineRange(@NotNull PsiElement element) {
|
||||
super(element);
|
||||
myElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
// Use when element scope changed
|
||||
static class ScopeRange extends LineRange {
|
||||
private PsiElement myScope;
|
||||
private PsiElement myAnchor;
|
||||
private boolean addBefore;
|
||||
private boolean theSameLevel;
|
||||
|
||||
public ScopeRange(@NotNull PsiElement scope, @Nullable PsiElement anchor, boolean before) {
|
||||
super(scope);
|
||||
myScope = scope;
|
||||
myAnchor = anchor;
|
||||
addBefore = before;
|
||||
}
|
||||
|
||||
public ScopeRange(PyElement scope, PsiElement anchor, boolean before, boolean b) {
|
||||
super(scope);
|
||||
myScope = scope;
|
||||
myAnchor = anchor;
|
||||
addBefore = before;
|
||||
theSameLevel = b;
|
||||
}
|
||||
|
||||
public PsiElement getAnchor() {
|
||||
return myAnchor;
|
||||
}
|
||||
|
||||
public PsiElement getScope() {
|
||||
return myScope;
|
||||
}
|
||||
|
||||
public boolean isAddBefore() {
|
||||
return addBefore;
|
||||
}
|
||||
|
||||
public boolean isTheSameLevel() {
|
||||
return theSameLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
-510
@@ -1,510 +0,0 @@
|
||||
package com.jetbrains.python.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineMover;
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineRange;
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.StatementUpDownMover;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiComment;
|
||||
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.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User : catherine
|
||||
*/
|
||||
public class StatementMover extends LineMover {
|
||||
@Nullable private PyStatementList myStatementListToAddPass;
|
||||
@Nullable private PyStatementList myStatementListToAddPassAfter;
|
||||
@Nullable private PsiElement myStatementToIncreaseIndent;
|
||||
@Nullable private PsiElement myStatementToDecreaseIndent;
|
||||
@Nullable private PyStatement myStatementToAddLinebreak;
|
||||
@Nullable private PsiElement myStatementToMove;
|
||||
@Nullable private PyStatementPart myStatementPartToRemovePass;
|
||||
private boolean moveToEmptyLine = false;
|
||||
private boolean theSameLevel;
|
||||
|
||||
private void init(@NotNull final Editor editor, @NotNull final MoveInfo info, final boolean down) {
|
||||
LineRange range = StatementUpDownMover.getLineRangeFromSelection(editor);
|
||||
int nearLine = down ? range.endLine : range.startLine;
|
||||
info.toMove = range;
|
||||
info.toMove2 = new LineRange(nearLine, nearLine + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
|
||||
if (!(file instanceof PyFile)) return false;
|
||||
init(editor, info, down);
|
||||
|
||||
Document document = editor.getDocument();
|
||||
String lineToMove = document.getText(new TextRange(getLineStartSafeOffset(document, info.toMove.startLine), getLineStartSafeOffset(document, info.toMove.endLine)));
|
||||
if (StringUtil.isEmptyOrSpaces(lineToMove)) {
|
||||
info.toMove2 = info.toMove;
|
||||
return true;
|
||||
}
|
||||
cleanUp();
|
||||
|
||||
|
||||
//find statement
|
||||
myStatementToMove = findStatement(editor, file, info);
|
||||
|
||||
// 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;
|
||||
}
|
||||
if ( myStatementToMove instanceof PyBreakStatement
|
||||
|| myStatementToMove instanceof PyContinueStatement) {
|
||||
final PyLoopStatement parent = PsiTreeUtil.getParentOfType(myStatementToMove, PyLoopStatement.class);
|
||||
if (parent != null) {
|
||||
final PyStatementPart part = PsiTreeUtil.getChildOfType(parent, PyStatementPart.class);
|
||||
if (part != null && part.getStatementList() != null) {
|
||||
PyStatementList statementList = part.getStatementList();
|
||||
if (myStatementToMove.getParent() instanceof PyStatementList) {
|
||||
statementList = (PyStatementList)myStatementToMove.getParent();
|
||||
}
|
||||
if (statementList != null && statementList.getStatements().length > 0) {
|
||||
if ((statementList.getStatements()[0] == myStatementToMove && !down)
|
||||
|| (statementList.getStatements()[statementList.getStatements().length-1] == myStatementToMove && down)) {
|
||||
info.toMove2 = info.toMove;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// do not move last statement down and first up
|
||||
if (isFirstOrLastStatement(down)) {
|
||||
info.toMove2 = info.toMove;
|
||||
return true;
|
||||
}
|
||||
|
||||
expandLineRangeToStatement(info, editor, down, file);
|
||||
|
||||
//is move from one part of compound statement to another
|
||||
theSameLevel = isTheSameIndentLevel(info, editor, file, down);
|
||||
|
||||
//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);
|
||||
}
|
||||
else {
|
||||
LineRange range = StatementUpDownMover.getLineRangeFromSelection(editor);
|
||||
final int maxLine = editor.getDocument().getLineCount();
|
||||
if (range.startLine == 0 && !down) return false;
|
||||
if (range.endLine >= maxLine && down) return false;
|
||||
}
|
||||
if (isMoveToCompound(info, editor, file, down) && !moveToEmptyLine) {
|
||||
myStatementToIncreaseIndent = myStatementToMove;
|
||||
if (!down && !theSameLevel)
|
||||
info.toMove2 = new LineRange(myStatementToMove);
|
||||
}
|
||||
|
||||
//find statement to add pass if we removed last statement from PyStatementPart
|
||||
PyElement statementPart = getStatementParts(info, editor, file, down).first;
|
||||
if (statementPart instanceof PyStatementPart) {
|
||||
PyStatementList statementList = ((PyStatementPart)statementPart).getStatementList();
|
||||
if (statementList != null && statementList.getStatements().length == 1 && !(myStatementToMove instanceof PsiComment) &&
|
||||
!isMoveToEmptyLine(editor, info, down)) {
|
||||
if (theSameLevel) {
|
||||
myStatementListToAddPassAfter = statementList;
|
||||
}
|
||||
else
|
||||
myStatementListToAddPass = statementList;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void cleanUp() {
|
||||
//reset
|
||||
myStatementListToAddPass = null;
|
||||
myStatementListToAddPassAfter = null;
|
||||
myStatementToDecreaseIndent = null;
|
||||
myStatementToIncreaseIndent = null;
|
||||
myStatementToAddLinebreak = null;
|
||||
myStatementPartToRemovePass = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement findStatement(Editor editor, PsiFile file, MoveInfo info) {
|
||||
final Document doc = editor.getDocument();
|
||||
final int offset1 = getLineStartSafeOffset(doc, info.toMove.startLine);
|
||||
PsiElement element1 = file.findElementAt(offset1);
|
||||
if (element1 != null) {
|
||||
if (element1 instanceof PsiWhiteSpace) {
|
||||
element1 = PyPsiUtils.getSignificantToTheRight(element1, false);
|
||||
}
|
||||
final PyStatement statement = PsiTreeUtil.getParentOfType(element1, PyStatement.class, false);
|
||||
if (element1 instanceof PsiComment) return element1;
|
||||
return statement;
|
||||
}
|
||||
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);
|
||||
|
||||
moveToEmptyLine = isMoveToEmptyLine(editor, info, down);
|
||||
if (moveToEmptyLine) return;
|
||||
// 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, false);
|
||||
if (tmp != null &&
|
||||
editor.offsetToLogicalPosition(tmp.getTextRange().getStartOffset()).line
|
||||
== info.toMove2.startLine) {
|
||||
element2 = tmp;
|
||||
}
|
||||
}
|
||||
//in case of multiline docstring
|
||||
PyElement parent2 = PsiTreeUtil.getParentOfType(element2, PyStringLiteralExpression.class);
|
||||
if (parent2 != null && parent2.getParent() instanceof PyExpressionStatement) {
|
||||
TextRange textRange2 = parent2.getTextRange();
|
||||
info.toMove2 = new LineRange(doc.getLineNumber(textRange2.getStartOffset()),
|
||||
doc.getLineNumber(textRange2.getEndOffset())+1);
|
||||
}
|
||||
|
||||
parent2 = PsiTreeUtil.getParentOfType(element2, PyFunction.class);
|
||||
PyElement parent1 = PsiTreeUtil.getParentOfType(myStatementToMove, PyFunction.class);
|
||||
if (parent2 != null && parent2 != parent1) {
|
||||
final TextRange textRange2 = parent2.getTextRange();
|
||||
if (myStatementToMove instanceof PsiComment) {
|
||||
myStatementToIncreaseIndent = myStatementToMove;
|
||||
return;
|
||||
}
|
||||
final int endOffset = doc.getLineNumber(textRange2.getEndOffset())+1;
|
||||
info.toMove2 = new LineRange(doc.getLineNumber(textRange2.getStartOffset()), endOffset);
|
||||
}
|
||||
parent2 = PsiTreeUtil.getParentOfType(element2, PyClass.class);
|
||||
parent1 = PsiTreeUtil.getParentOfType(myStatementToMove, PyClass.class);
|
||||
if (parent2 != null && parent2 != parent1) {
|
||||
final TextRange textRange2 = parent2.getTextRange();
|
||||
if (myStatementToMove instanceof PsiComment) {
|
||||
myStatementToIncreaseIndent = myStatementToMove;
|
||||
return;
|
||||
}
|
||||
final int endOffset = doc.getLineNumber(textRange2.getEndOffset())+1;
|
||||
info.toMove2 = new LineRange(doc.getLineNumber(textRange2.getStartOffset()), endOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMoveToEmptyLine(Editor editor, MoveInfo info, boolean down) {
|
||||
final Document document = editor.getDocument();
|
||||
if (document.getLineCount() >= info.toMove2.endLine) {
|
||||
final TextRange range = new TextRange(getLineStartSafeOffset(document,info.toMove2.startLine),
|
||||
getLineStartSafeOffset(document, info.toMove2.endLine));
|
||||
final String lineToMoveTo = document.getText(range);
|
||||
if (StringUtil.isEmptyOrSpaces(lineToMoveTo)) {
|
||||
if (myStatementToMove instanceof PyFunction || myStatementToMove instanceof PyClass) {
|
||||
final CommonCodeStyleSettings indentOptions = CodeStyleSettingsManager.getInstance(editor.getProject()).
|
||||
getCurrentSettings().getCommonSettings(PythonLanguage.getInstance());
|
||||
final int blankLines = myStatementToMove instanceof PyFunction? indentOptions.BLANK_LINES_AROUND_METHOD :
|
||||
indentOptions.BLANK_LINES_AROUND_CLASS;
|
||||
final int nearLine = down ? info.toMove2.endLine + blankLines - 1: info.toMove2.startLine - blankLines;
|
||||
if (document.getLineCount() > nearLine && nearLine > 0)
|
||||
info.toMove2 = new LineRange(nearLine, nearLine + 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* main indent logic
|
||||
* @return first is the element which we move
|
||||
* second is the element we move to
|
||||
*/
|
||||
private Pair<PyElement, PyElement> getStatementParts(MoveInfo info, Editor editor, PsiFile file, boolean down) {
|
||||
PsiElement element1 = myStatementToMove;
|
||||
PyElement statementPart1 = PsiTreeUtil.getParentOfType(element1, PyStatementPart.class, PyWithStatement.class);
|
||||
int offset2 = getLineStartSafeOffset(editor.getDocument(), info.toMove2.startLine);
|
||||
PsiElement element2 = file.findElementAt(offset2-1);
|
||||
if (element2 instanceof PsiWhiteSpace) {
|
||||
if (down) {
|
||||
final PsiElement tmp = PyPsiUtils.getSignificantToTheRight(element2, false);
|
||||
TokenSet secondParts = TokenSet.create(PyTokenTypes.ELSE_KEYWORD, PyTokenTypes.ELIF_KEYWORD,
|
||||
PyTokenTypes.FINALLY_KEYWORD, PyTokenTypes.EXCEPT_KEYWORD);
|
||||
if (tmp != null &&
|
||||
(editor.offsetToLogicalPosition(tmp.getTextRange().getStartOffset()).line == info.toMove2.startLine ||
|
||||
secondParts.contains(tmp.getNode().getElementType())))
|
||||
element2 = tmp;
|
||||
} else {
|
||||
final PsiElement tmp = PyPsiUtils.getSignificantToTheRight(element2, false);
|
||||
if (tmp != null) {
|
||||
int start = editor.offsetToLogicalPosition(tmp.getParent().getTextRange().getStartOffset()).line;
|
||||
int end = editor.offsetToLogicalPosition(tmp.getParent().getTextRange().getEndOffset()).line;
|
||||
if (start == info.toMove2.startLine && (start == end || tmp.getParent() instanceof PyClass || tmp.getParent() instanceof PyFunction))
|
||||
element2 = tmp;
|
||||
else
|
||||
element2 = PyPsiUtils.getSignificantToTheLeft(element2, true);
|
||||
}
|
||||
else {
|
||||
element2 = PyPsiUtils.getSignificantToTheLeft(element2, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
PyElement statementPart2 = PsiTreeUtil.getParentOfType(element2, PyStatementPart.class, PyWithStatement.class);
|
||||
|
||||
//in case we move first or last line outside if statement
|
||||
if (statementPart2 instanceof PyStatementPart) {
|
||||
int start = editor.offsetToLogicalPosition(statementPart2.getTextRange().getStartOffset()).line;
|
||||
int end = editor.offsetToLogicalPosition(statementPart2.getTextRange().getEndOffset()).line;
|
||||
|
||||
if (!down && info.toMove2.startLine == start && start != end) {
|
||||
statementPart2 = PsiTreeUtil.getParentOfType(statementPart2, PyStatementPart.class);
|
||||
}
|
||||
else {
|
||||
final PsiElement parent2 = statementPart2.getParent();
|
||||
if (parent2 instanceof PyTryExceptStatement && statementPart1 != null && parent2 != statementPart1.getParent() && !down) {
|
||||
if (parent2.getParent() instanceof PyStatementList) {
|
||||
final PyStatementList stList = (PyStatementList)parent2.getParent();
|
||||
final PyStatement[] statements = stList.getStatements();
|
||||
if (statements[statements.length-1] == parent2) {
|
||||
statementPart2 = PsiTreeUtil.getParentOfType(stList, PyStatementPart.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (statementPart2 != null) {
|
||||
PyStatementList stList = ((PyStatementPart)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<PyElement, PyElement>(statementPart1, statementPart2);
|
||||
}
|
||||
|
||||
private boolean isMoveToCompound(MoveInfo info, Editor editor, PsiFile file, boolean down) {
|
||||
final Pair<PyElement, PyElement> statementParts = getStatementParts(info, editor, file, down);
|
||||
final PyElement statementPart1 = statementParts.first;
|
||||
final PyElement statementPart2 = statementParts.second;
|
||||
if (statementPart2 != null) {
|
||||
if (statementPart2 instanceof PyStatementPart)
|
||||
prepareToStatement((PyStatementPart)statementPart2, editor.getDocument());
|
||||
if (statementPart1 == null) return true;
|
||||
if (statementPart1.getParent() != statementPart2.getParent()) {
|
||||
final PsiElement commonParent = PsiTreeUtil.findCommonParent(statementPart1, statementPart2);
|
||||
if (PsiTreeUtil.isAncestor(statementPart2, statementPart1, false)) return false;
|
||||
if ((commonParent instanceof PyIfStatement) || (commonParent instanceof PyLoopStatement) ||
|
||||
(commonParent instanceof PyStatementPart) || (commonParent instanceof PyWithStatement) ||
|
||||
(commonParent instanceof PyTryExceptStatement))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isMoveOut(MoveInfo info, Editor editor, PsiFile file, boolean down) {
|
||||
Pair<PyElement, PyElement> insertDeleteParts = getStatementParts(info, editor, file, down);
|
||||
PyElement statementPart1 = insertDeleteParts.first;
|
||||
PyElement 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 PyStatementPart)
|
||||
&& !(commonParent instanceof PyWithStatement))
|
||||
return true;
|
||||
if (PsiTreeUtil.isAncestor(statementPart2, statementPart1, false)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void prepareToStatement(PyStatementPart part, Document document) {
|
||||
if (part.getParent() == myStatementToMove) return;
|
||||
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<PyElement, PyElement> statementParts = getStatementParts(info, editor, file, down);
|
||||
PyElement statementPart1 = statementParts.first;
|
||||
PyElement statementPart2 = statementParts.second;
|
||||
if (statementPart2 instanceof PyStatementPart) {
|
||||
PyStatementList stList = ((PyStatementPart)statementPart2).getStatementList();
|
||||
if (stList != null && stList.getStatements().length == 1 && stList.getStatements()[0] instanceof PyPassStatement ||
|
||||
moveToEmptyLine) {
|
||||
myStatementPartToRemovePass = (PyStatementPart)statementParts.second;
|
||||
}
|
||||
}
|
||||
final boolean fromExceptToElse = statementPart1 instanceof PyExceptPart && statementPart2 instanceof PyTryPart;
|
||||
return !fromExceptToElse &&
|
||||
statementPart2 != null &&
|
||||
statementPart1 != null &&
|
||||
statementPart1.getParent() == statementPart2.getParent() ||
|
||||
statementPart2 == statementPart1;
|
||||
}
|
||||
|
||||
private void decreaseIndent(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(myStatementToDecreaseIndent.getTextRange().getStartOffset()).line;
|
||||
final int endLine = editor.offsetToLogicalPosition(myStatementToDecreaseIndent.getTextRange().getEndOffset()).line;
|
||||
for (int line = startLine; line <= endLine; ++line) {
|
||||
final int offset = document.getLineStartOffset(line);
|
||||
document.deleteString(offset, offset + indentOptions.INDENT_SIZE);
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
|
||||
PyStatementWithElse statementWithElse = PsiTreeUtil.getParentOfType(myStatementToAddLinebreak, PyStatementWithElse.class);
|
||||
if (statementWithElse != null && statementWithElse.getParent() instanceof PyFile) indent = "\n";
|
||||
if (whiteSpace instanceof PsiWhiteSpace) indent += whiteSpace.getText();
|
||||
if (down || theSameLevel) indent += StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE);
|
||||
if (theSameLevel) info.toMove = info.toMove2;
|
||||
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);
|
||||
if (!down) {
|
||||
myStatementListToAddPass.add(passStatement);
|
||||
}
|
||||
else
|
||||
myStatementListToAddPass.addBefore(passStatement, myStatementListToAddPass.getStatements()[0]);
|
||||
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myStatementListToAddPass);
|
||||
}
|
||||
if (myStatementToIncreaseIndent != null) {
|
||||
if (!down && myStatementPartToRemovePass != null && myStatementToAddLinebreak == null) {
|
||||
info.toMove2 = new LineRange(info.toMove2.startLine-1, info.toMove2.endLine);
|
||||
}
|
||||
increaseIndent(editor);
|
||||
}
|
||||
if (myStatementToDecreaseIndent != null) {
|
||||
decreaseIndent(editor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myStatementListToAddPassAfter);
|
||||
}
|
||||
|
||||
// remove obsolete pass statement
|
||||
if (myStatementPartToRemovePass != null) {
|
||||
PyStatementList statementList = myStatementPartToRemovePass.getStatementList();
|
||||
if (statementList != null) {
|
||||
PyPsiUtils.removeRedundantPass(statementList);
|
||||
final PsiElement whitespace = myStatementPartToRemovePass.getNextSibling();
|
||||
if (whitespace instanceof PsiWhiteSpace)
|
||||
whitespace.delete();
|
||||
}
|
||||
}
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1167,7 +1167,7 @@ public class PyUtil {
|
||||
@Nullable
|
||||
public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class ... toSkip) {
|
||||
PsiElement element = psiFile.findElementAt(caretOffset);
|
||||
if (element == null) {
|
||||
if (element == null || caretOffset < 0) {
|
||||
return null;
|
||||
}
|
||||
int lineStartOffset = 0;
|
||||
@@ -1187,7 +1187,7 @@ public class PyUtil {
|
||||
public static PsiElement findNonWhitespaceAtOffset(PsiFile psiFile, int caretOffset) {
|
||||
PsiElement element = findNextAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
|
||||
if (element == null)
|
||||
element = findPrevAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
|
||||
element = findPrevAtOffset(psiFile, caretOffset-1, PsiWhiteSpace.class);
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -1212,7 +1212,7 @@ public class PyUtil {
|
||||
int lineNumber = document.getLineNumber(caretOffset);
|
||||
lineEndOffset = document.getLineEndOffset(lineNumber);
|
||||
}
|
||||
while (caretOffset <= lineEndOffset && instanceOf(element, toSkip)) {
|
||||
while (caretOffset < lineEndOffset && instanceOf(element, toSkip)) {
|
||||
caretOffset++;
|
||||
element = psiFile.findElementAt(caretOffset);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
def f():
|
||||
#comment
|
||||
if True:
|
||||
|
||||
@@ -2,4 +2,4 @@ if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
#comment <caret>
|
||||
@@ -3,4 +3,4 @@ def f():
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment
|
||||
#comment
|
||||
|
||||
@@ -7,4 +7,4 @@ class A:
|
||||
pass
|
||||
|
||||
<selection>def<caret> bar(self):
|
||||
pass</selection>
|
||||
pass</selection>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class A:
|
||||
|
||||
<selection>def<caret> bar(self):
|
||||
pass</selection>
|
||||
pass</selection>
|
||||
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
@@ -2,4 +2,4 @@ def a():
|
||||
|
||||
zoo(1)
|
||||
def b():
|
||||
pass
|
||||
pass
|
||||
@@ -1,5 +1,5 @@
|
||||
def a():
|
||||
|
||||
def b():
|
||||
pass
|
||||
|
||||
zoo(1)
|
||||
@@ -4,4 +4,4 @@ class A:
|
||||
def c(self):
|
||||
doSomething3()
|
||||
def b(self):
|
||||
doSomething2()
|
||||
doSomething2()
|
||||
@@ -1,6 +1,6 @@
|
||||
def a():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
c = 1
|
||||
else:
|
||||
b = 2
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
if value is not None:
|
||||
if not False or value <= 2:
|
||||
print "here"
|
||||
pass
|
||||
print "here"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
@@ -0,0 +1,6 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
@@ -0,0 +1,6 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
@@ -0,0 +1,7 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a = 2
|
||||
#comment <caret>
|
||||
@@ -0,0 +1,6 @@
|
||||
def f():
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
#comment <caret>
|
||||
a = 2
|
||||
@@ -0,0 +1,3 @@
|
||||
for item in range(1,
|
||||
3):
|
||||
b = 2<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
for item in range(1,
|
||||
3):
|
||||
pass
|
||||
b = 2<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
b = 2<caret>
|
||||
for item in range(1,
|
||||
3):
|
||||
pass
|
||||
@@ -4,4 +4,5 @@ def test(a, b=1):
|
||||
else:
|
||||
for n in range(10):
|
||||
|
||||
print(b)
|
||||
pass
|
||||
print(b)
|
||||
|
||||
@@ -2,5 +2,6 @@ if condition:
|
||||
pass
|
||||
elif other_condition:
|
||||
if another_one:
|
||||
if T<caret>rue: a = 1 # <- move statement up here
|
||||
else: b = 2
|
||||
pass
|
||||
if True: a = 1 # <- move statement up here
|
||||
else: b = 2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
c = 1
|
||||
if True:
|
||||
a = 1
|
||||
b = 2
|
||||
b = 2
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
test2()
|
||||
def foo():
|
||||
test()
|
||||
pass
|
||||
test()
|
||||
test1()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
test2()
|
||||
test()
|
||||
def foo():
|
||||
test()
|
||||
pass
|
||||
test1()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
if True:
|
||||
a = 1 # <- move statement down here
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
#comment
|
||||
a =<caret> 3
|
||||
a = 2
|
||||
@@ -0,0 +1,6 @@
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
#comment
|
||||
a = 2
|
||||
a =<caret> 3
|
||||
@@ -0,0 +1,6 @@
|
||||
if True:
|
||||
a = 1
|
||||
else:
|
||||
a =<caret> 3
|
||||
#comment
|
||||
a = 2
|
||||
@@ -4,4 +4,5 @@ try:
|
||||
except:
|
||||
print(zoo(0).foo(2))
|
||||
except:
|
||||
zoo<caret>(3)
|
||||
pass
|
||||
zoo(3)
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
|
||||
/**
|
||||
* @author Alexey.Ivanov
|
||||
* User : ktisha
|
||||
*/
|
||||
public class PyStatementMoverTest extends PyTestCase {
|
||||
private void doTest() {
|
||||
@@ -177,6 +177,21 @@ public class PyStatementMoverTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testSameLevelInIf() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testLastComment() { // PY-6408
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testLastComment1() { //PY-6408
|
||||
doTest();
|
||||
}
|
||||
public void testMultiCompound() { //PY-7658
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testWith() { // PY-5202
|
||||
try {
|
||||
setLanguageLevel(LanguageLevel.PYTHON27);
|
||||
|
||||
Reference in New Issue
Block a user