mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
move up down enhanced
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user