blob: 89b6d9bc4a5d9e5da62b7ba46b71a108dabd9f90 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PythonStringUtil;
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();
final SelectionModel selectionModel = editor.getSelectionModel();
final Document document = editor.getDocument();
final int lineNumber = document.getLineNumber(offset);
int start = getLineStartSafeOffset(document, lineNumber);
final int lineEndOffset = document.getLineEndOffset(lineNumber);
int end = lineEndOffset == 0 ? 0 : lineEndOffset - 1;
if (selectionModel.hasSelection()) {
start = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
end = selectionEnd == 0 ? 0 : selectionEnd - 1;
}
PsiElement elementToMove1 = PyUtil.findNonWhitespaceAtOffset(file, start);
PsiElement elementToMove2 = PyUtil.findNonWhitespaceAtOffset(file, end);
if (elementToMove1 == null || elementToMove2 == null) return false;
if (ifInsideString(document, lineNumber, elementToMove1, down)) return false;
elementToMove1 = getCommentOrStatement(document, elementToMove1);
elementToMove2 = getCommentOrStatement(document, elementToMove2);
if (PsiTreeUtil.isAncestor(elementToMove1, elementToMove2, false)) {
elementToMove2 = elementToMove1;
}
else if (PsiTreeUtil.isAncestor(elementToMove2, elementToMove1, false)) {
elementToMove1 = elementToMove2;
}
info.toMove = new MyLineRange(elementToMove1, elementToMove2);
info.toMove2 = getDestinationScope(file, editor, down ? elementToMove2 : elementToMove1, down);
info.indentTarget = false;
info.indentSource = false;
return true;
}
private static boolean ifInsideString(@NotNull final Document document, int lineNumber, @NotNull final PsiElement elementToMove1, boolean down) {
int start = document.getLineStartOffset(lineNumber);
final int end = document.getLineEndOffset(lineNumber);
int nearLine = down ? lineNumber + 1 : lineNumber - 1;
if (nearLine >= document.getLineCount() || nearLine <= 0) return false;
final PyStringLiteralExpression stringLiteralExpression = PsiTreeUtil.getParentOfType(elementToMove1, PyStringLiteralExpression.class);
if (stringLiteralExpression != null) {
final Pair<String,String> quotes = PythonStringUtil.getQuotes(stringLiteralExpression.getText());
if (quotes != null && (quotes.first.equals("'''") || quotes.first.equals("\"\"\""))) {
final String text1 = document.getText(TextRange.create(start, end)).trim();
final String text2 = document.getText(TextRange.create(document.getLineStartOffset(nearLine), document.getLineEndOffset(nearLine))).trim();
if (!text1.startsWith(quotes.first) && !text1.endsWith(quotes.second) && !text2.startsWith(quotes.first) && !text2.endsWith(quotes.second))
return true;
}
}
return false;
}
@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;
final int offset = down ? elementToMove.getTextRange().getEndOffset() : elementToMove.getTextRange().getStartOffset();
int lineNumber = down ? document.getLineNumber(offset) + 1 : document.getLineNumber(offset) - 1;
if (moveOutsideFile(document, lineNumber)) return null;
int lineEndOffset = document.getLineEndOffset(lineNumber);
final int startOffset = document.getLineStartOffset(lineNumber);
final PyStatementList statementList = getStatementList(elementToMove);
final PsiElement destination = getDestinationElement(elementToMove, document, lineEndOffset, down);
final int start = destination != null ? destination.getTextRange().getStartOffset() : lineNumber;
final int end = destination != null ? destination.getTextRange().getEndOffset() : lineNumber;
final int startLine = document.getLineNumber(start);
final int endLine = document.getLineNumber(end);
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;
if (elementToMove instanceof PsiComment && ( PsiTreeUtil.isAncestor(destination, elementToMove, true)) ||
destination instanceof PsiComment) {
return new LineRange(lineNumber, lineNumber + 1);
}
final PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList;
if ((elementToMove instanceof PyClass) || (elementToMove instanceof PyFunction))
return new ScopeRange(scope, scope.getFirstChild(), !down, true);
return new LineRange(startLine, endLine + 1);
}
private static boolean moveOutsideFile(@NotNull final Document document, int lineNumber) {
return lineNumber < 0 || lineNumber >= document.getLineCount();
}
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();
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 || anchor == 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) && element != null) {
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 = PyUtil.findPrevAtOffset(elementToMove.getContainingFile(), lineEndOffset, PsiWhiteSpace.class);
PsiElement sibling = down ? PsiTreeUtil.getNextSiblingOfType(elementToMove, PyStatement.class) :
PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class);
if (destination == null) {
if (elementToMove instanceof PyClass) {
destination = sibling;
}
else if (elementToMove instanceof PyFunction) {
if (!(sibling instanceof PyClass))
destination = sibling;
else destination = null;
}
else {
return null;
}
}
if (destination instanceof PsiComment) return destination;
if (elementToMove instanceof PyClass) {
destination = sibling;
}
else if (elementToMove instanceof PyFunction) {
if (!(sibling instanceof PyClass))
destination = sibling;
else destination = null;
}
else {
destination = getCommentOrStatement(document, sibling == null ? destination : sibling);
}
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 startToMove = ((MyLineRange)toMove).myStartElement;
final PsiElement endToMove = ((MyLineRange)toMove).myEndElement;
final PsiFile file = startToMove.getContainingFile();
final SelectionModel selectionModel = editor.getSelectionModel();
final CaretModel caretModel = editor.getCaretModel();
final int selectionStart = selectionModel.getSelectionStart();
boolean isSelectionStartAtCaret = caretModel.getOffset() == selectionStart;
final SelectionContainer selectionLen = getSelectionLenContainer(editor, ((MyLineRange)toMove));
int shift = getCaretShift(startToMove, endToMove, caretModel, isSelectionStartAtCaret);
final boolean hasSelection = selectionModel.hasSelection();
int offset;
if (((ScopeRange)toMove2).isTheSameLevel()) {
offset = moveTheSameLevel((ScopeRange)toMove2, (MyLineRange)toMove);
}
else {
offset = moveInOut(((MyLineRange)toMove), editor, info);
}
restoreCaretAndSelection(file, editor, isSelectionStartAtCaret, hasSelection, selectionLen,
shift, offset, (MyLineRange)toMove);
info.toMove2 = info.toMove; //do not move further
}
});
}
}
private static SelectionContainer getSelectionLenContainer(@NotNull final Editor editor, @NotNull final MyLineRange toMove) {
final SelectionModel selectionModel = editor.getSelectionModel();
final PsiElement startToMove = toMove.myStartElement;
final PsiElement endToMove = toMove.myEndElement;
final int selectionStart = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
final TextRange range = startToMove.getTextRange();
final int column = editor.offsetToLogicalPosition(selectionStart).column;
final int additionalSelection = range.getStartOffset() > selectionStart ? range.getStartOffset() - selectionStart : 0;
if (startToMove == endToMove) return new SelectionContainer(selectionEnd - range.getStartOffset(), additionalSelection, column == 0);
int len = range.getStartOffset() <= selectionStart ? range.getEndOffset() - selectionStart : startToMove.getTextLength();
PsiElement tmp = startToMove.getNextSibling();
while (tmp != endToMove && tmp != null) {
if (!(tmp instanceof PsiWhiteSpace))
len += tmp.getTextLength();
tmp = tmp.getNextSibling();
}
len = len + selectionEnd - endToMove.getTextOffset();
return new SelectionContainer(len, additionalSelection, column == 0);
}
private static void restoreCaretAndSelection(@NotNull final PsiFile file, @NotNull final Editor editor, boolean selectionStartAtCaret,
boolean hasSelection, @NotNull final SelectionContainer selectionContainer, int shift,
int offset, @NotNull final MyLineRange toMove) {
final Document document = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
final CaretModel caretModel = editor.getCaretModel();
Integer selectionLen = selectionContainer.myLen;
final PsiElement at = file.findElementAt(offset);
if (at != null) {
final PsiElement added = getCommentOrStatement(document, at);
int size = toMove.size;
if (size > 1) {
PsiElement tmp = added.getNextSibling();
while (size > 1 && tmp != null) {
if (tmp instanceof PsiWhiteSpace) {
if (!selectionStartAtCaret)
shift += tmp.getTextLength();
selectionLen += tmp.getTextLength();
}
tmp = tmp.getNextSibling();
size -= 1;
}
}
if (shift < 0) shift = 0;
final int column = editor.offsetToLogicalPosition(added.getTextRange().getStartOffset()).column;
if (selectionContainer.myAtTheBeginning || column < selectionContainer.myAdditional) {
selectionLen += column;
}
else {
selectionLen += selectionContainer.myAdditional;
}
if (selectionContainer.myAtTheBeginning && selectionStartAtCaret)
shift = -column;
}
final int documentLength = document.getTextLength();
int newCaretOffset = offset + shift;
if (newCaretOffset >= documentLength) newCaretOffset = documentLength;
caretModel.moveToOffset(newCaretOffset);
if (hasSelection) {
if (selectionStartAtCaret) {
int newSelectionEnd = newCaretOffset + selectionLen;
selectionModel.setSelection(newCaretOffset, newSelectionEnd);
}
else {
int newSelectionStart = newCaretOffset - selectionLen;
selectionModel.setSelection(newSelectionStart, newCaretOffset);
}
}
}
private static int getCaretShift(PsiElement startToMove, PsiElement endToMove, CaretModel caretModel, boolean selectionStartAtCaret) {
int shift;
if (selectionStartAtCaret) {
shift = caretModel.getOffset() - startToMove.getTextRange().getStartOffset();
}
else {
shift = caretModel.getOffset();
if (startToMove != endToMove) {
shift += startToMove.getTextLength();
PsiElement tmp = startToMove.getNextSibling();
while (tmp != endToMove && tmp != null) {
if (!(tmp instanceof PsiWhiteSpace))
shift += tmp.getTextLength();
tmp = tmp.getNextSibling();
}
}
shift -= endToMove.getTextOffset();
}
return shift;
}
private static int moveTheSameLevel(@NotNull final ScopeRange toMove2, @NotNull final MyLineRange toMove) {
final PsiElement anchor = toMove2.getAnchor();
final PsiElement anchorCopy = anchor.copy();
PsiElement startToMove = toMove.myStartElement;
final PsiElement endToMove = toMove.myEndElement;
final PsiElement parent = anchor.getParent();
PsiElement tmp = startToMove.getNextSibling();
if (startToMove != endToMove && tmp != null) {
parent.addRangeAfter(tmp, endToMove, anchor);
}
PsiElement startCopy = startToMove.copy();
startToMove.replace(anchorCopy);
final PsiElement addedElement = anchor.replace(startCopy);
if (startToMove != endToMove && tmp != null) {
parent.deleteChildRange(tmp, endToMove);
}
return addedElement.getTextRange().getStartOffset();
}
private static int moveInOut(@NotNull final MyLineRange toMove, @NotNull final Editor editor, @NotNull final MoveInfo info) {
boolean removePass = false;
final ScopeRange toMove2 = (ScopeRange)info.toMove2;
final PsiElement scope = toMove2.getScope();
final PsiElement anchor = toMove2.getAnchor();
final Project project = scope.getProject();
final PsiElement startElement = toMove.myStartElement;
final PsiElement endElement = toMove.myEndElement;
PsiElement parent = startElement.getParent();
if (scope instanceof PyStatementList && !(startElement == endElement && startElement instanceof PsiComment)) {
final PyStatement[] statements = ((PyStatementList)scope).getStatements();
if (statements.length == 1 && statements[0] == anchor && statements[0] instanceof PyPassStatement) {
removePass = true;
}
}
final PsiElement addedElement;
PsiElement nextSibling = startElement.getNextSibling();
if (toMove2.isAddBefore()) {
PsiElement tmp = endElement.getPrevSibling();
if (startElement != endElement && tmp != null) {
addedElement = scope.addRangeBefore(startElement, tmp, anchor);
scope.addBefore(endElement, anchor);
}
else {
addedElement = scope.addBefore(endElement, anchor);
}
}
else {
if (startElement != endElement && nextSibling != null) {
scope.addRangeAfter(nextSibling, endElement, anchor);
}
addedElement = scope.addAfter(startElement, anchor);
}
addPassStatement(toMove, project);
if (startElement != endElement && nextSibling != null) {
parent.deleteChildRange(nextSibling, endElement);
}
startElement.delete();
final int addedElementLine = editor.getDocument().getLineNumber(addedElement.getTextOffset());
final PsiFile file = scope.getContainingFile();
adjustLineIndents(editor, scope, project, addedElement, toMove.size);
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(startElement.getProject()).commitAllDocuments();
}
});
}
int offset = addedElement.getTextRange().getStartOffset();
int newLine = editor.getDocument().getLineNumber(offset);
if (newLine != addedElementLine && !removePass) { // PsiComment gets broken after adjust indent
PsiElement psiElement = PyUtil.findNonWhitespaceAtOffset(file, editor.getDocument().getLineEndOffset(addedElementLine) - 1);
if (psiElement != null) {
psiElement = getCommentOrStatement(editor.getDocument(), psiElement);
offset = psiElement.getTextRange().getStartOffset();
}
}
return offset;
}
private static void adjustLineIndents(@NotNull final Editor editor, @NotNull final PsiElement scope, @NotNull final Project project,
@NotNull final PsiElement addedElement, int size) {
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.getLineEndOffset(line2)));
}
else {
int line1 = editor.offsetToLogicalPosition(addedElement.getTextRange().getStartOffset()).line;
PsiElement end = addedElement;
while (size > 0) {
PsiElement tmp = end.getNextSibling();
if (tmp == null) break;
size -= 1;
end = tmp;
}
int endOffset = end.getTextRange().getEndOffset();
int line2 = editor.offsetToLogicalPosition(endOffset).line;
codeStyleManager.adjustLineIndent(scope.getContainingFile(),
new TextRange(document.getLineStartOffset(line1), document.getLineEndOffset(line2)));
}
}
private static void addPassStatement(@NotNull final MyLineRange toMove, @NotNull final Project project) {
final PsiElement startElement = toMove.myStartElement;
final PsiElement endElement = toMove.myEndElement;
final PyStatementList initialScope = getStatementList(startElement);
if (initialScope != null && !(startElement == endElement && startElement instanceof PsiComment)) {
if (initialScope.getStatements().length == toMove.statementsSize) {
final PyPassStatement passStatement = PyElementGenerator.getInstance(project).createPassStatement();
initialScope.addAfter(passStatement, initialScope.getStatements()[initialScope.getStatements().length - 1]);
}
}
}
// use to keep elements
static class MyLineRange extends LineRange {
private PsiElement myStartElement;
private PsiElement myEndElement;
int size = 0;
int statementsSize = 0;
public MyLineRange(@NotNull PsiElement start, PsiElement end) {
super(start, end);
myStartElement = start;
myEndElement = end;
if (myStartElement == myEndElement) {
size = 1;
statementsSize = 1;
}
else {
PsiElement counter = myStartElement;
while (counter != myEndElement && counter != null) {
size += 1;
if (!(counter instanceof PsiWhiteSpace) && !(counter instanceof PsiComment))
statementsSize += 1;
counter = counter.getNextSibling();
}
size += 1;
if (!(counter instanceof PsiWhiteSpace) && !(counter instanceof PsiComment))
statementsSize += 1;
}
}
}
static class SelectionContainer {
private int myLen;
private int myAdditional;
private boolean myAtTheBeginning;
public SelectionContainer(int len, int additional, boolean atTheBeginning) {
myLen = len;
myAdditional = additional;
myAtTheBeginning = atTheBeginning;
}
}
// Use when element scope changed
static class ScopeRange extends LineRange {
private PsiElement myScope;
@NotNull private PsiElement myAnchor;
private boolean addBefore;
private boolean theSameLevel;
public ScopeRange(@NotNull PsiElement scope, @NotNull PsiElement anchor, boolean before) {
super(scope);
myScope = scope;
myAnchor = anchor;
addBefore = before;
}
public ScopeRange(PyElement scope, @NotNull PsiElement anchor, boolean before, boolean b) {
super(scope);
myScope = scope;
myAnchor = anchor;
addBefore = before;
theSameLevel = b;
}
@NotNull
public PsiElement getAnchor() {
return myAnchor;
}
public PsiElement getScope() {
return myScope;
}
public boolean isAddBefore() {
return addBefore;
}
public boolean isTheSameLevel() {
return theSameLevel;
}
}
}