| /* |
| * 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; |
| } |
| } |
| } |