diff --git a/resources/src/idea/ActionManager.xml b/resources/src/idea/ActionManager.xml index a37fafc36634..a41e9f1f3158 100644 --- a/resources/src/idea/ActionManager.xml +++ b/resources/src/idea/ActionManager.xml @@ -399,8 +399,8 @@ - - + + diff --git a/source/com/intellij/openapi/editor/actions/MoveStatementHandler.java b/source/com/intellij/openapi/editor/actions/MoveStatementHandler.java deleted file mode 100644 index 03f9b58b6752..000000000000 --- a/source/com/intellij/openapi/editor/actions/MoveStatementHandler.java +++ /dev/null @@ -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 elementRange = getElementRange(element, startingElement, endingElement); - return new LineRange(editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line, - editor.offsetToLogicalPosition(elementRange.getSecond().getTextRange().getEndOffset()).line); - } - - private static Pair 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(); -} diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/DeclarationMover.java b/source/com/intellij/openapi/editor/actions/moveUpDown/DeclarationMover.java new file mode 100644 index 000000000000..05cbda442a56 --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/DeclarationMover.java @@ -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 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 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)); + } +} diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/LineMover.java b/source/com/intellij/openapi/editor/actions/moveUpDown/LineMover.java new file mode 100644 index 000000000000..17dcf0cbf028 --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/LineMover.java @@ -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 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 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); + } +} diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/LineRange.java b/source/com/intellij/openapi/editor/actions/moveUpDown/LineRange.java new file mode 100644 index 000000000000..46ecdc4c9e95 --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/LineRange.java @@ -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; + } +} diff --git a/source/com/intellij/openapi/editor/actions/MoveStatementDownAction.java b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementDownAction.java similarity index 87% rename from source/com/intellij/openapi/editor/actions/MoveStatementDownAction.java rename to source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementDownAction.java index b61f7a784dd2..dd57efe9fed1 100644 --- a/source/com/intellij/openapi/editor/actions/MoveStatementDownAction.java +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementDownAction.java @@ -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; diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementHandler.java b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementHandler.java new file mode 100644 index 000000000000..9f0e95cf72b2 --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementHandler.java @@ -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 + diff --git a/source/com/intellij/openapi/editor/actions/MoveStatementUpAction.java b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementUpAction.java similarity index 75% rename from source/com/intellij/openapi/editor/actions/MoveStatementUpAction.java rename to source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementUpAction.java index 673acfe2a9e6..2cec2e6c984c 100644 --- a/source/com/intellij/openapi/editor/actions/MoveStatementUpAction.java +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/MoveStatementUpAction.java @@ -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() { diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/Mover.java b/source/com/intellij/openapi/editor/actions/moveUpDown/Mover.java new file mode 100644 index 000000000000..4ea8d73042eb --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/Mover.java @@ -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); +} diff --git a/source/com/intellij/openapi/editor/actions/moveUpDown/StatementMover.java b/source/com/intellij/openapi/editor/actions/moveUpDown/StatementMover.java new file mode 100644 index 000000000000..ffe7ad90b41b --- /dev/null +++ b/source/com/intellij/openapi/editor/actions/moveUpDown/StatementMover.java @@ -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 psiRange = getElementRange(editor, file, range); + if (psiRange == null) return null; + final PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond()); + Pair elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond()); + return new LineRange(editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line, + editor.offsetToLogicalPosition(elementRange.getSecond().getTextRange().getEndOffset()).line); + } +}