move up down enhanced

This commit is contained in:
Alexey Kudravtsev
2005-03-05 17:34:34 +03:00
parent 33a9d9ad6d
commit 6fe0ef55c6
10 changed files with 443 additions and 252 deletions
+2 -2
View File
@@ -399,8 +399,8 @@
<action id="CommentByBlockComment" class="com.intellij.codeInsight.generation.actions.CommentByBlockCommentAction" text="Comment with _Block Comment" description="Comment/uncomment code with block comments"/>
<action id="AutoIndentLines" class="com.intellij.codeInsight.generation.actions.AutoIndentLinesAction" text="_Auto-Indent Lines" description="Indent current line or selected block according to the code style settings"/>
<separator/>
<action id="MoveStatementDown" class="com.intellij.openapi.editor.actions.MoveStatementDownAction" text="Move Statement Do_wn" description="Move selected statements one line down"/>
<action id="MoveStatementUp" class="com.intellij.openapi.editor.actions.MoveStatementUpAction" text="Move Statement _Up" description="Move selected statements one line up"/>
<action id="MoveStatementDown" class="com.intellij.openapi.editor.actions.moveUpDown.MoveStatementDownAction" text="Move Statement Do_wn" description="Move selected statements one line down"/>
<action id="MoveStatementUp" class="com.intellij.openapi.editor.actions.moveUpDown.MoveStatementUpAction" text="Move Statement _Up" description="Move selected statements one line up"/>
</group>
<group id="AnalyzeMenu" text="Analy_ze" popup="true">
@@ -1,248 +0,0 @@
/**
* @author cdr
*/
package com.intellij.openapi.editor.actions;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
class MoveStatementHandler extends EditorWriteActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.actions.MoveStatementHandler");
private final boolean isDown;
public MoveStatementHandler(boolean down) {
isDown = down;
}
public void executeWriteAction(Editor editor, DataContext dataContext) {
final Project project = editor.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = editor.getDocument();
final PsiFile file = documentManager.getPsiFile(document);
final CaretModel caretModel = editor.getCaretModel();
final int caretColumn = caretModel.getLogicalPosition().column;
final LineRange lineRange = getRangeToMove(editor,file);
final int startLine = lineRange.startLine;
final int endLine = lineRange.endLine;
final int insertOffset = calcInsertOffset(editor, startLine, endLine);
final int start = editor.logicalPositionToOffset(new LogicalPosition(startLine, 0));
final int end = editor.logicalPositionToOffset(new LogicalPosition(endLine+1, 0));
final String toInsert = document.getCharsSequence().subSequence(start, end).toString();
final int insStart = isDown ? insertOffset - toInsert.length() : insertOffset;
final int insEnd = insStart + toInsert.length();
final SelectionModel selectionModel = editor.getSelectionModel();
final int selectionStart = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
final boolean hasSelection = selectionModel.hasSelection();
// to prevent flicker
caretModel.moveToOffset(0);
document.deleteString(start, end);
document.insertString(insStart, toInsert);
documentManager.commitDocument(document);
if (hasSelection) {
restoreSelection(editor, selectionStart, selectionEnd, start, insStart);
}
try {
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
final int line1 = editor.offsetToLogicalPosition(insStart).line;
final int line2 = editor.offsetToLogicalPosition(insEnd).line;
caretModel.moveToLogicalPosition(new LogicalPosition(line1, caretColumn));
for (int line = line1; line <= line2; line++) {
int lineStart = document.getLineStartOffset(line);
codeStyleManager.adjustLineIndent(file, lineStart);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
private int calcInsertOffset(final Editor editor, final int startLine, final int endLine) {
int nearLine = isDown ? endLine + 2 : startLine - 1;
int line = nearLine;
final PsiFile file = PsiDocumentManager.getInstance(editor.getProject()).getPsiFile(editor.getDocument());
if (!(file instanceof PsiJavaFile)) {
return editor.logicalPositionToOffset(new LogicalPosition(nearLine, 0));
}
while (true) {
final int offset = editor.logicalPositionToOffset(new LogicalPosition(line, 0));
PsiElement element = firstNonWhiteElement(offset, file, true);
while (element != null && element != file) {
if (!element.getTextRange().contains(offset)) {
if ((element instanceof PsiStatement || element instanceof PsiComment)
&& element.getParent() instanceof PsiCodeBlock) {
return offset;
}
if (element instanceof PsiJavaToken
&& ((PsiJavaToken)element).getTokenType() == JavaTokenType.RBRACE
&& element.getParent() instanceof PsiCodeBlock) {
return offset;
}
if (element instanceof PsiMember) {
return offset;
}
}
element = element.getParent();
}
line += isDown ? 1 : -1;
if (line == 0 || line >= editor.getDocument().getLineCount()) {
return editor.logicalPositionToOffset(new LogicalPosition(nearLine, 0));
}
}
}
private static void restoreSelection(final Editor editor, final int selectionStart, final int selectionEnd, final int moveOffset, int insOffset) {
final int selectionRelativeOffset = selectionStart - moveOffset;
int newSelectionStart = insOffset + selectionRelativeOffset;
int newSelectionEnd = newSelectionStart + selectionEnd - selectionStart;
editor.getSelectionModel().setSelection(newSelectionStart, newSelectionEnd);
}
public boolean isEnabled(Editor editor, DataContext dataContext) {
if (editor.isOneLineMode()) {
return false;
}
final Project project = editor.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = editor.getDocument();
final PsiFile file = documentManager.getPsiFile(document);
return getRangeToMove(editor,file) != null;
}
private LineRange getRangeToMove(final Editor editor, final PsiFile file) {
final SelectionModel selectionModel = editor.getSelectionModel();
final int startLine;
final int endLine;
LineRange result;
if (selectionModel.hasSelection()) {
startLine = editor.offsetToLogicalPosition(selectionModel.getSelectionStart()).line;
final LogicalPosition endPos = editor.offsetToLogicalPosition(selectionModel.getSelectionEnd());
endLine = endPos.column == 0 ? endPos.line - 1 : endPos.line;
result = new LineRange(startLine, endLine);
}
else {
startLine = editor.getCaretModel().getLogicalPosition().line;
endLine = startLine;
result = new LineRange(startLine, endLine);
}
if (file instanceof PsiJavaFile) {
result = expandLineRangeToStatement(result, editor,file);
if (result == null) return null;
}
final int maxLine = editor.offsetToLogicalPosition(editor.getDocument().getTextLength()).line;
if (result.startLine <= 1 && !isDown) return null;
if (result.endLine >= maxLine - 1 && isDown) return null;
if (!checkMovingInsideOutside(file, editor, result)) return null;
return result;
}
private boolean checkMovingInsideOutside(final PsiFile file, final Editor editor, final LineRange result) {
final int offset = editor.getCaretModel().getOffset();
PsiElement elementAt = file.findElementAt(offset);
if (elementAt == null) return false;
final Class[] classes = new Class[]{PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class,};
final PsiElement guard = PsiTreeUtil.getParentOfType(elementAt, classes);
// cannot move in/outside method/class/initializer/comment
final int insertOffset = calcInsertOffset(editor, result.startLine, result.endLine);
elementAt = file.findElementAt(insertOffset);
final PsiElement newGuard = PsiTreeUtil.getParentOfType(elementAt, classes);
if (newGuard == guard && isInside(insertOffset, newGuard) == isInside(offset, guard)) return true;
// moving in/out nested class is OK
if (guard instanceof PsiClass && guard.getParent() instanceof PsiClass) return true;
if (newGuard instanceof PsiClass && newGuard.getParent() instanceof PsiClass) return true;
return false;
}
private static boolean isInside(final int offset, final PsiElement guard) {
if (guard == null) return false;
TextRange inside = guard instanceof PsiMethod ? ((PsiMethod)guard).getBody().getTextRange() : guard instanceof PsiClassInitializer
? ((PsiClassInitializer)guard).getBody().getTextRange()
: guard instanceof PsiClass
? new TextRange(((PsiClass)guard).getLBrace().getTextOffset(), ((PsiClass)guard).getRBrace().getTextOffset())
: guard.getTextRange();
return inside != null && inside.contains(offset);
}
private static LineRange expandLineRangeToStatement(final LineRange range, Editor editor, final PsiFile file) {
final int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0));
PsiElement startingElement = firstNonWhiteElement(startOffset, file, true);
if (startingElement == null) return null;
final int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine+1, 0)) -1;
PsiElement endingElement = firstNonWhiteElement(endOffset, file, false);
if (endingElement == null) return null;
final PsiElement element = PsiTreeUtil.findCommonParent(startingElement, endingElement);
Pair<PsiElement, PsiElement> elementRange = getElementRange(element, startingElement, endingElement);
return new LineRange(editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line,
editor.offsetToLogicalPosition(elementRange.getSecond().getTextRange().getEndOffset()).line);
}
private static Pair<PsiElement, PsiElement> getElementRange(final PsiElement parent,
PsiElement element1,
PsiElement element2) {
if (PsiTreeUtil.isAncestor(element1, element2, false) || PsiTreeUtil.isAncestor(element2, element1, false)) {
return Pair.create(parent, parent);
}
// find nearset children that are parents of elements
while (element1.getParent() != parent) {
element1 = element1.getParent();
}
while (element2.getParent() != parent) {
element2 = element2.getParent();
}
return Pair.create(element1, element2);
}
private static PsiElement firstNonWhiteElement(int offset, PsiFile file, final boolean lookRight) {
PsiElement element = file.findElementAt(offset);
if (element instanceof PsiWhiteSpace) {
element = lookRight ? element.getNextSibling() : element.getPrevSibling();
}
return element;
}
}
//todo
// + no move inside/outside class(except nested)/method/initializer/comment
// moving declarations
// create codeblock when moving inside statement
class LineRange {
final int startLine;
final int endLine;
public LineRange(final int startLine, final int endLine) {
this.startLine = startLine;
this.endLine = endLine;
}
}
interface Mover {
boolean isItMe();
LineRange getRangeToMove();
int getOffsetToMoveTo();
}
@@ -0,0 +1,105 @@
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
class DeclarationMover extends LineMover {
public LineRange getRangeToMove(Editor editor, PsiFile file, boolean isDown) {
LineRange lineRange = super.getRangeToMove(editor, file, isDown);
if (!(file instanceof PsiJavaFile)) {
return null;
}
final Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, lineRange);
if (psiRange == null) return null;
final PsiMember firstMember = PsiTreeUtil.getParentOfType(psiRange.getFirst(), PsiMember.class, false);
final PsiMember lastMember = PsiTreeUtil.getParentOfType(psiRange.getSecond(), PsiMember.class, false);
if (firstMember != null && firstMember == lastMember) {
final LineRange newRange = memberRange(firstMember, editor, lineRange);
newRange.firstElement = firstMember;
newRange.lastElement = lastMember;
return newRange;
}
final PsiElement parent = PsiTreeUtil.findCommonParent(firstMember, lastMember);
if (parent == null) return null;
final Pair<PsiElement, PsiElement> combinedRange = getElementRange(parent, firstMember, lastMember);
if (combinedRange == null) return null;
final LineRange lineRange1 = memberRange(combinedRange.getFirst(), editor, lineRange);
if (lineRange1 == null) return null;
final LineRange lineRange2 = memberRange(combinedRange.getSecond(), editor, lineRange);
if (lineRange2 == null) return null;
final LineRange newRange = new LineRange(lineRange1.startLine, lineRange2.endLine);
newRange.firstElement = combinedRange.getFirst();
newRange.lastElement = combinedRange.getSecond();
return newRange;
}
private static LineRange memberRange(PsiElement member, Editor editor, LineRange lineRange) {
final TextRange textRange = member.getTextRange();
// we should be positioned on member start or end to be able to move it
final int startLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line;
final int endLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line;
if (startLine != lineRange.startLine && startLine != lineRange.endLine && endLine != lineRange.startLine &&
endLine != lineRange.endLine) {
return null;
}
return new LineRange(startLine, endLine);
}
public int getOffsetToMoveTo(Editor editor, PsiFile file, LineRange range, boolean isDown) {
return calcInsertOffset(editor, range, isDown);
}
private static int calcInsertOffset(Editor editor, LineRange range, final boolean isDown) {
PsiElement sibling = isDown ? range.lastElement.getNextSibling() : range.firstElement.getPrevSibling();
if (sibling == null) return -1;
final boolean areWeMovingClass = range.firstElement instanceof PsiClass;
sibling = firstNonWhiteElement(sibling, isDown);
int offset = moveInsideOutsideClassOffset(editor, sibling, isDown, areWeMovingClass);
if (offset != 0) return offset;
if (isDown) {
sibling = sibling.getNextSibling();
if (sibling == null) return -1;
sibling = firstNonWhiteElement(sibling, isDown);
if (sibling == null) return -1;
}
return sibling.getTextRange().getStartOffset();
}
private static int moveInsideOutsideClassOffset(Editor editor,
PsiElement sibling,
final boolean isDown,
boolean areWeMovingClass) {
if (sibling == null) return -1;
if (sibling instanceof PsiJavaToken &&
((PsiJavaToken)sibling).getTokenType() == (isDown ? JavaTokenType.RBRACE : JavaTokenType.LBRACE) &&
sibling.getParent() instanceof PsiClass) {
// moving outside class
final PsiClass aClass = (PsiClass)sibling.getParent();
final PsiElement parent = aClass.getParent();
if (!areWeMovingClass && !(parent instanceof PsiClass)) return -1;
return isDown ? nextLineOffset(editor, aClass.getTextRange().getEndOffset()) : aClass.getTextRange().getStartOffset();
}
if (sibling instanceof PsiClass) {
// moving inside class
return isDown
? nextLineOffset(editor, ((PsiClass)sibling).getLBrace().getTextOffset())
: ((PsiClass)sibling).getRBrace().getTextOffset();
}
return 0;
}
private static int nextLineOffset(Editor editor, final int offset) {
final LogicalPosition position = editor.offsetToLogicalPosition(offset);
return editor.logicalPositionToOffset(new LogicalPosition(position.line + 1, 0));
}
}
@@ -0,0 +1,79 @@
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
class LineMover implements Mover {
public LineRange getRangeToMove(Editor editor, PsiFile file, boolean isDown) {
final SelectionModel selectionModel = editor.getSelectionModel();
final int startLine;
final int endLine;
LineRange result;
if (selectionModel.hasSelection()) {
startLine = editor.offsetToLogicalPosition(selectionModel.getSelectionStart()).line;
final LogicalPosition endPos = editor.offsetToLogicalPosition(selectionModel.getSelectionEnd());
endLine = endPos.column == 0 ? endPos.line - 1 : endPos.line;
result = new LineRange(startLine, endLine);
}
else {
startLine = editor.getCaretModel().getLogicalPosition().line;
endLine = startLine;
result = new LineRange(startLine, endLine);
}
return result;
}
public int getOffsetToMoveTo(Editor editor, PsiFile file, LineRange range, boolean isDown) {
final int maxLine = editor.offsetToLogicalPosition(editor.getDocument().getTextLength()).line;
if (range.startLine <= 1 && !isDown) return -1;
if (range.endLine >= maxLine - 1 && isDown) return -1;
int nearLine = isDown ? range.endLine + 2 : range.startLine - 1;
return editor.logicalPositionToOffset(new LogicalPosition(nearLine, 0));
}
protected static Pair<PsiElement, PsiElement> getElementRange(Editor editor, PsiFile file, final LineRange range) {
final int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0));
PsiElement startingElement = firstNonWhiteElement(startOffset, file, true);
if (startingElement == null) return null;
final int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine+1, 0)) -1;
PsiElement endingElement = firstNonWhiteElement(endOffset, file, false);
if (endingElement == null) return null;
return Pair.create(startingElement, endingElement);
}
static PsiElement firstNonWhiteElement(int offset, PsiFile file, final boolean lookRight) {
PsiElement element = file.findElementAt(offset);
return firstNonWhiteElement(element, lookRight);
}
static PsiElement firstNonWhiteElement(PsiElement element, final boolean lookRight) {
if (element instanceof PsiWhiteSpace) {
element = lookRight ? element.getNextSibling() : element.getPrevSibling();
}
return element;
}
protected static Pair<PsiElement, PsiElement> getElementRange(final PsiElement parent,
PsiElement element1,
PsiElement element2) {
if (PsiTreeUtil.isAncestor(element1, element2, false) || PsiTreeUtil.isAncestor(element2, element1, false)) {
return Pair.create(parent, parent);
}
// find nearset children that are parents of elements
while (element1.getParent() != parent) {
element1 = element1.getParent();
}
while (element2.getParent() != parent) {
element2 = element2.getParent();
}
return Pair.create(element1, element2);
}
}
@@ -0,0 +1,16 @@
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.psi.PsiElement;
class LineRange {
final int startLine;
final int endLine;
PsiElement firstElement;
PsiElement lastElement;
public LineRange(final int startLine, final int endLine) {
this.startLine = startLine;
this.endLine = endLine;
}
}
@@ -6,7 +6,7 @@
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.openapi.editor.actions;
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.actionSystem.EditorAction;
@@ -0,0 +1,125 @@
/**
* @author cdr
*/
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.IncorrectOperationException;
class MoveStatementHandler extends EditorWriteActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.actions.moveUpDown.MoveStatementHandler");
private final boolean isDown;
private final Mover[] myMovers;
public MoveStatementHandler(boolean down) {
isDown = down;
myMovers = new Mover[]{ new StatementMover(), new DeclarationMover(), new LineMover()};
}
public void executeWriteAction(Editor editor, DataContext dataContext) {
final Project project = editor.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = editor.getDocument();
final PsiFile file = documentManager.getPsiFile(document);
final Mover mover = getMover(editor, file);
final LineRange lineRange = mover.getRangeToMove(editor,file,isDown);
final int startLine = lineRange.startLine;
final int endLine = lineRange.endLine;
final int insertOffset = mover.getOffsetToMoveTo(editor, file, lineRange, isDown);
final int start = editor.logicalPositionToOffset(new LogicalPosition(startLine, 0));
final int end = editor.logicalPositionToOffset(new LogicalPosition(endLine+1, 0));
final String toInsert = document.getCharsSequence().subSequence(start, end).toString();
final int insStart = isDown ? insertOffset - toInsert.length() : insertOffset;
final int insEnd = insStart + toInsert.length();
final CaretModel caretModel = editor.getCaretModel();
final int caretRelativePos = caretModel.getOffset() - start;
final SelectionModel selectionModel = editor.getSelectionModel();
final int selectionStart = selectionModel.getSelectionStart();
final int selectionEnd = selectionModel.getSelectionEnd();
final boolean hasSelection = selectionModel.hasSelection();
// to prevent flicker
caretModel.moveToOffset(0);
document.deleteString(start, end);
document.insertString(insStart, toInsert);
documentManager.commitDocument(document);
if (hasSelection) {
restoreSelection(editor, selectionStart, selectionEnd, start, insStart);
}
try {
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
final int line1 = editor.offsetToLogicalPosition(insStart).line;
final int line2 = editor.offsetToLogicalPosition(insEnd).line;
caretModel.moveToOffset(insStart + caretRelativePos);
for (int line = line1; line <= line2; line++) {
int lineStart = document.getLineStartOffset(line);
codeStyleManager.adjustLineIndent(file, lineStart);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
private static void restoreSelection(final Editor editor, final int selectionStart, final int selectionEnd, final int moveOffset, int insOffset) {
final int selectionRelativeOffset = selectionStart - moveOffset;
int newSelectionStart = insOffset + selectionRelativeOffset;
int newSelectionEnd = newSelectionStart + selectionEnd - selectionStart;
editor.getSelectionModel().setSelection(newSelectionStart, newSelectionEnd);
}
public boolean isEnabled(Editor editor, DataContext dataContext) {
if (editor.isOneLineMode()) {
return false;
}
final Project project = editor.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = editor.getDocument();
final PsiFile file = documentManager.getPsiFile(document);
final Mover mover = getMover(editor, file);
if (mover == null) return false;
final LineRange range = mover.getRangeToMove(editor, file, isDown);
if (range == null) return false;
final int insertOffset = mover.getOffsetToMoveTo(editor, file, range, isDown);
if (insertOffset == -1) return false;
final int maxLine = editor.offsetToLogicalPosition(editor.getDocument().getTextLength()).line;
if (range.startLine <= 1 && !isDown) return false;
if (range.endLine >= maxLine - 1 && isDown) return false;
return true;
}
private Mover getMover(final Editor editor, final PsiFile file) {
for (int i = 0; i < myMovers.length; i++) {
final Mover mover = myMovers[i];
final LineRange range = mover.getRangeToMove(editor, file, isDown);
if (range != null) return mover;
}
return null;
}
}
//todo
// + no move inside/outside class(except nested)/method/initializer/comment
// + moving declarations
// create codeblock when moving inside statement
@@ -6,9 +6,10 @@
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.openapi.editor.actions;
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actions.moveUpDown.MoveStatementHandler;
public class MoveStatementUpAction extends EditorAction {
public MoveStatementUpAction() {
@@ -0,0 +1,9 @@
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
interface Mover {
LineRange getRangeToMove(Editor editor, PsiFile file, boolean isDown);
int getOffsetToMoveTo(Editor editor, PsiFile file, LineRange range, boolean isDown);
}
@@ -0,0 +1,104 @@
package com.intellij.openapi.editor.actions.moveUpDown;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.codeInsight.CodeInsightUtil;
class StatementMover extends LineMover {
public LineRange getRangeToMove(Editor editor, PsiFile file, boolean isDown) {
if (!(file instanceof PsiJavaFile)) return null;
LineRange result = super.getRangeToMove(editor, file, isDown);
result = expandLineRangeToCoverPsiElements(result, editor, file);
if (result == null) return null;
final int startOffset = editor.logicalPositionToOffset(new LogicalPosition(result.startLine, 0));
final int endOffset = editor.logicalPositionToOffset(new LogicalPosition(result.endLine+1, 0));
final PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset);
if (statements == null || statements.length == 0) return null;
result.firstElement = statements[0];
result.lastElement = statements[statements.length-1];
return result;
}
public int getOffsetToMoveTo(Editor editor, PsiFile file, LineRange range, boolean isDown) {
if (!checkMovingInsideOutside(file, editor, range, isDown)) return -1;
return calcInsertOffset(editor, range.startLine, range.endLine, isDown);
}
private static int calcInsertOffset(final Editor editor, final int startLine, final int endLine, final boolean isDown) {
int nearLine = isDown ? endLine + 2 : startLine - 1;
int line = nearLine;
final PsiFile file = PsiDocumentManager.getInstance(editor.getProject()).getPsiFile(editor.getDocument());
if (!(file instanceof PsiJavaFile)) {
return editor.logicalPositionToOffset(new LogicalPosition(nearLine, 0));
}
while (true) {
final int offset = editor.logicalPositionToOffset(new LogicalPosition(line, 0));
PsiElement element = firstNonWhiteElement(offset, file, true);
while (element != null && element != file) {
if (!element.getTextRange().contains(offset)) {
if ((element instanceof PsiStatement || element instanceof PsiComment)
&& element.getParent() instanceof PsiCodeBlock) {
return offset;
}
if (element instanceof PsiJavaToken
&& ((PsiJavaToken)element).getTokenType() == JavaTokenType.RBRACE
&& element.getParent() instanceof PsiCodeBlock) {
return offset;
}
if (element instanceof PsiMember) {
return offset;
}
}
element = element.getParent();
}
line += isDown ? 1 : -1;
if (line == 0 || line >= editor.getDocument().getLineCount()) {
return editor.logicalPositionToOffset(new LogicalPosition(nearLine, 0));
}
}
}
private static boolean checkMovingInsideOutside(final PsiFile file, final Editor editor, final LineRange result, final boolean isDown) {
final int offset = editor.getCaretModel().getOffset();
PsiElement elementAt = file.findElementAt(offset);
if (elementAt == null) return false;
final Class[] classes = new Class[]{PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class,};
final PsiElement guard = PsiTreeUtil.getParentOfType(elementAt, classes);
// cannot move in/outside method/class/initializer/comment
final int insertOffset = calcInsertOffset(editor, result.startLine, result.endLine, isDown);
elementAt = file.findElementAt(insertOffset);
final PsiElement newGuard = PsiTreeUtil.getParentOfType(elementAt, classes);
if (newGuard == guard && isInside(insertOffset, newGuard) == isInside(offset, guard)) return true;
// moving in/out nested class is OK
if (guard instanceof PsiClass && guard.getParent() instanceof PsiClass) return true;
if (newGuard instanceof PsiClass && newGuard.getParent() instanceof PsiClass) return true;
return false;
}
private static boolean isInside(final int offset, final PsiElement guard) {
if (guard == null) return false;
TextRange inside = guard instanceof PsiMethod ? ((PsiMethod)guard).getBody().getTextRange() : guard instanceof PsiClassInitializer
? ((PsiClassInitializer)guard).getBody().getTextRange()
: guard instanceof PsiClass
? new TextRange(((PsiClass)guard).getLBrace().getTextOffset(), ((PsiClass)guard).getRBrace().getTextOffset())
: guard.getTextRange();
return inside != null && inside.contains(offset);
}
private static LineRange expandLineRangeToCoverPsiElements(final LineRange range, Editor editor, final PsiFile file) {
Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, range);
if (psiRange == null) return null;
final PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond());
Pair<PsiElement, PsiElement> elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond());
return new LineRange(editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line,
editor.offsetToLogicalPosition(elementRange.getSecond().getTextRange().getEndOffset()).line);
}
}