mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
IJPL-181799 execute java move up/down statement actions on FE in RD
GitOrigin-RevId: 5df74728054974e44f2664018c3535c16c24927d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5ad848e379
commit
de7d4de1c0
@@ -67,6 +67,15 @@
|
||||
<applicationService serviceInterface="com.intellij.codeInsight.folding.JavaCodeFoldingSettings"
|
||||
serviceImplementation="com.intellij.codeInsight.folding.impl.JavaCodeFoldingSettingsImpl"/>
|
||||
|
||||
<statementUpDownMover implementation="com.intellij.codeInsight.editorActions.moveUpDown.JavaDeclarationMover" id="declaration"
|
||||
order="before xml"/>
|
||||
<statementUpDownMover implementation="com.intellij.codeInsight.editorActions.moveUpDown.JavaStatementMover" id="statement"
|
||||
order="before declaration"/>
|
||||
<statementUpDownMover implementation="com.intellij.codeInsight.editorActions.moveUpDown.JavaCaseBlockMover" id="caseBlock"
|
||||
order="before statement"/>
|
||||
<statementUpDownMover implementation="com.intellij.codeInsight.editorActions.moveUpDown.JavaCatchBlockMover" id="catchBlock"
|
||||
order="before statement"/>
|
||||
|
||||
<registryKey key="java.formatter.chained.calls.pre212.compatibility"
|
||||
defaultValue="false"
|
||||
description="Format chained calls as in versions prior to 2021.2"/>
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
public final class CodeInsightFrontbackUtil {
|
||||
public static @Nullable PsiExpression findExpressionInRange(PsiFile file, int startOffset, int endOffset) {
|
||||
if (!file.getViewProvider().getLanguages().contains(JavaLanguage.INSTANCE)) return null;
|
||||
PsiExpression expression = findElementInRange(file, startOffset, endOffset, PsiExpression.class);
|
||||
if (expression == null && findStatementsInRange(file, startOffset, endOffset).length == 0) {
|
||||
PsiElement element2 = file.getViewProvider().findElementAt(endOffset - 1, JavaLanguage.INSTANCE);
|
||||
if (element2 instanceof PsiJavaToken token) {
|
||||
final IElementType tokenType = token.getTokenType();
|
||||
if (tokenType.equals(JavaTokenType.SEMICOLON) || element2.getParent() instanceof PsiErrorElement) {
|
||||
expression = findElementInRange(file, startOffset, element2.getTextRange().getStartOffset(), PsiExpression.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression == null && findStatementsInRange(file, startOffset, endOffset).length == 0) {
|
||||
PsiElement element = PsiTreeUtil.skipWhitespacesBackward(file.findElementAt(endOffset));
|
||||
if (element != null) {
|
||||
element = PsiTreeUtil.skipWhitespacesAndCommentsBackward(element.getLastChild());
|
||||
if (element != null) {
|
||||
final int newEndOffset = element.getTextRange().getEndOffset();
|
||||
if (newEndOffset < endOffset) {
|
||||
expression = findExpressionInRange(file, startOffset, newEndOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression instanceof PsiReferenceExpression && expression.getParent() instanceof PsiMethodCallExpression) return null;
|
||||
return expression;
|
||||
}
|
||||
|
||||
public static <T extends PsiElement> T findElementInRange(PsiFile file, int startOffset, int endOffset, Class<T> klass) {
|
||||
return CodeInsightUtilCore.findElementInRange(file, startOffset, endOffset, klass, JavaLanguage.INSTANCE);
|
||||
}
|
||||
|
||||
public static PsiElement @NotNull [] findStatementsInRange(@NotNull PsiFile file, int startOffset, int endOffset) {
|
||||
Language language = findJavaOrLikeLanguage(file);
|
||||
if (language == null) return PsiElement.EMPTY_ARRAY;
|
||||
FileViewProvider viewProvider = file.getViewProvider();
|
||||
PsiElement element1 = viewProvider.findElementAt(startOffset, language);
|
||||
PsiElement element2 = viewProvider.findElementAt(endOffset - 1, language);
|
||||
if (element1 instanceof PsiWhiteSpace) {
|
||||
startOffset = element1.getTextRange().getEndOffset();
|
||||
element1 = file.findElementAt(startOffset);
|
||||
}
|
||||
if (element2 instanceof PsiWhiteSpace) {
|
||||
endOffset = element2.getTextRange().getStartOffset();
|
||||
element2 = file.findElementAt(endOffset - 1);
|
||||
}
|
||||
if (element1 == null || element2 == null) return PsiElement.EMPTY_ARRAY;
|
||||
|
||||
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
|
||||
if (parent == null) return PsiElement.EMPTY_ARRAY;
|
||||
while (true) {
|
||||
if (parent instanceof PsiStatement) {
|
||||
if (!(element1 instanceof PsiComment)) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parent instanceof PsiCodeBlock) break;
|
||||
if (FileTypeUtils.isInServerPageFile(parent) && parent instanceof PsiFile) break;
|
||||
if (parent instanceof PsiCodeFragment) break;
|
||||
if (parent == null || parent instanceof PsiFile) return PsiElement.EMPTY_ARRAY;
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
if (!parent.equals(element1)) {
|
||||
while (!parent.equals(element1.getParent())) {
|
||||
element1 = element1.getParent();
|
||||
}
|
||||
}
|
||||
if (startOffset != element1.getTextRange().getStartOffset()) return PsiElement.EMPTY_ARRAY;
|
||||
|
||||
if (!parent.equals(element2)) {
|
||||
while (!parent.equals(element2.getParent())) {
|
||||
element2 = element2.getParent();
|
||||
}
|
||||
}
|
||||
if (endOffset != element2.getTextRange().getEndOffset() && !isAtTrailingComment(element1, element2, endOffset)) {
|
||||
return PsiElement.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
if (parent instanceof PsiCodeBlock &&
|
||||
element1 == ((PsiCodeBlock)parent).getLBrace() && element2 == ((PsiCodeBlock)parent).getRBrace()) {
|
||||
if (parent.getParent() instanceof PsiBlockStatement) {
|
||||
return new PsiElement[]{parent.getParent()};
|
||||
}
|
||||
PsiElement[] children = parent.getChildren();
|
||||
return getStatementsInRange(children, ((PsiCodeBlock)parent).getFirstBodyElement(), ((PsiCodeBlock)parent).getLastBodyElement());
|
||||
}
|
||||
|
||||
PsiElement[] children = parent.getChildren();
|
||||
return getStatementsInRange(children, element1, element2);
|
||||
}
|
||||
|
||||
private static boolean isAtTrailingComment(PsiElement element1, PsiElement element2, int offset) {
|
||||
if (element1 == element2 && element1 instanceof PsiExpressionStatement) {
|
||||
for (PsiElement child = element1.getLastChild(); child != null; child = child.getPrevSibling()) {
|
||||
if (PsiUtil.isJavaToken(child, JavaTokenType.SEMICOLON) && child.getTextRange().getEndOffset() == offset) {
|
||||
return false; // findExpressionInRange() counts this as an expression - don't interfere with it
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiElement trailing = element2;
|
||||
while (trailing.getTextRange().contains(offset) && trailing.getLastChild() != null) {
|
||||
trailing = trailing.getLastChild();
|
||||
}
|
||||
while (trailing instanceof PsiComment || trailing instanceof PsiWhiteSpace) {
|
||||
PsiElement previous = trailing.getPrevSibling();
|
||||
if (trailing.getTextRange().contains(offset)) {
|
||||
return true;
|
||||
}
|
||||
trailing = previous;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable Language findJavaOrLikeLanguage(final @NotNull PsiFile file) {
|
||||
final Set<Language> languages = file.getViewProvider().getLanguages();
|
||||
if (languages.contains(JavaLanguage.INSTANCE)) return JavaLanguage.INSTANCE;
|
||||
for (final Language language : languages) {
|
||||
if (language.isKindOf(JavaLanguage.INSTANCE)) return language;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiElement @NotNull [] getStatementsInRange(PsiElement[] children, PsiElement element1, PsiElement element2) {
|
||||
ArrayList<PsiElement> array = new ArrayList<>();
|
||||
boolean flag = false;
|
||||
for (PsiElement child : children) {
|
||||
if (child.equals(element1)) {
|
||||
flag = true;
|
||||
}
|
||||
if (flag && !(child instanceof PsiWhiteSpace)) {
|
||||
array.add(child);
|
||||
}
|
||||
if (child.equals(element2)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiElement element : array) {
|
||||
if (!(element instanceof PsiStatement || element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
|
||||
return PsiElement.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
return PsiUtilCore.toPsiElementArray(array);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightFrontbackUtil;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiJavaFile;
|
||||
import com.intellij.psi.PsiSwitchLabelStatement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class JavaCaseBlockMover extends LineMover {
|
||||
|
||||
@Override
|
||||
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
|
||||
if (!(file instanceof PsiJavaFile)) return false;
|
||||
if (!super.checkAvailable(editor, file, info, down)) return false;
|
||||
|
||||
final Document document = editor.getDocument();
|
||||
int startOffset = document.getLineStartOffset(info.toMove.startLine);
|
||||
int endOffset = getLineStartSafeOffset(document, info.toMove.endLine);
|
||||
List<PsiSwitchLabelStatement> statements = new SmartList<>();
|
||||
PsiElement firstElement = null;
|
||||
for (PsiElement element : CodeInsightFrontbackUtil.findStatementsInRange(file, startOffset, endOffset)) {
|
||||
if (element instanceof PsiSwitchLabelStatement) {
|
||||
statements.add((PsiSwitchLabelStatement)element);
|
||||
}
|
||||
else if (statements.isEmpty()) {
|
||||
firstElement = element;
|
||||
}
|
||||
}
|
||||
if (statements.isEmpty()) return false;
|
||||
if (firstElement != null) return info.prohibitMove(); // nonsensical selection
|
||||
|
||||
PsiSwitchLabelStatement firstToMove = getThisCaseBlockStart(statements.get(0));
|
||||
PsiSwitchLabelStatement lastStatement = statements.get(statements.size() - 1);
|
||||
PsiElement nextCaseBlockStart = getNextCaseBlockStart(lastStatement);
|
||||
PsiElement lastToMove = PsiTreeUtil.skipWhitespacesBackward(nextCaseBlockStart);
|
||||
assert lastToMove != null;
|
||||
|
||||
LineRange range = createRange(document, firstToMove, lastToMove);
|
||||
if (range == null) return info.prohibitMove();
|
||||
info.toMove = range;
|
||||
|
||||
PsiElement firstToMove2;
|
||||
PsiElement lastToMove2;
|
||||
if (down) {
|
||||
if (!(nextCaseBlockStart instanceof PsiSwitchLabelStatement) || nextCaseBlockStart == lastStatement) return info.prohibitMove();
|
||||
firstToMove2 = nextCaseBlockStart;
|
||||
nextCaseBlockStart = getNextCaseBlockStart((PsiSwitchLabelStatement)firstToMove2);
|
||||
lastToMove2 = PsiTreeUtil.skipWhitespacesBackward(nextCaseBlockStart);
|
||||
assert lastToMove2 != null;
|
||||
}
|
||||
else {
|
||||
lastToMove2 = PsiTreeUtil.skipWhitespacesBackward(firstToMove);
|
||||
if (lastToMove2 == null) return info.prohibitMove();
|
||||
firstToMove2 = PsiTreeUtil.getPrevSiblingOfType(lastToMove2, PsiSwitchLabelStatement.class);
|
||||
if (firstToMove2 == null) return info.prohibitMove();
|
||||
firstToMove2 = getThisCaseBlockStart((PsiSwitchLabelStatement)firstToMove2);
|
||||
}
|
||||
LineRange range2 = createRange(document, firstToMove2, lastToMove2);
|
||||
if (range2 == null) return info.prohibitMove();
|
||||
info.toMove2 = range2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns PsiSwitchLabelStatement starting this case block
|
||||
private static @NotNull PsiSwitchLabelStatement getThisCaseBlockStart(PsiSwitchLabelStatement element) {
|
||||
PsiElement tmp;
|
||||
while ((tmp = PsiTreeUtil.skipWhitespacesBackward(element)) instanceof PsiSwitchLabelStatement) {
|
||||
element = (PsiSwitchLabelStatement)tmp;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// returns PsiSwitchLabelStatement starting next case block, or switch block's closing brace, if there is no next case block
|
||||
private static @NotNull PsiElement getNextCaseBlockStart(PsiSwitchLabelStatement element) {
|
||||
PsiElement result = element;
|
||||
PsiElement tmp;
|
||||
while ((tmp = PsiTreeUtil.skipWhitespacesForward(result)) instanceof PsiSwitchLabelStatement) {
|
||||
result = tmp;
|
||||
}
|
||||
tmp = PsiTreeUtil.getNextSiblingOfType(result, PsiSwitchLabelStatement.class);
|
||||
return tmp == null ? result.getParent().getLastChild() : tmp;
|
||||
}
|
||||
|
||||
private static @Nullable LineRange createRange(@NotNull Document document, @NotNull PsiElement startElement, @NotNull PsiElement endElement) {
|
||||
CharSequence text = document.getImmutableCharSequence();
|
||||
int startOffset = startElement.getTextRange().getStartOffset();
|
||||
int startLine = document.getLineNumber(startOffset);
|
||||
if (!CharArrayUtil.isEmptyOrSpaces(text, document.getLineStartOffset(startLine), startOffset)) {
|
||||
return null;
|
||||
}
|
||||
int endOffset = endElement.getTextRange().getEndOffset();
|
||||
int endLine = document.getLineNumber(endOffset);
|
||||
if (!CharArrayUtil.isEmptyOrSpaces(text, endOffset, document.getLineEndOffset(endLine))) {
|
||||
return null;
|
||||
}
|
||||
return new LineRange(startLine, endLine + 1);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.SelectionModel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class JavaCatchBlockMover extends LineMover {
|
||||
|
||||
@Override
|
||||
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
|
||||
if (!(file instanceof PsiJavaFile)) return false;
|
||||
if (!super.checkAvailable(editor, file, info, down)) return false;
|
||||
|
||||
final Document document = editor.getDocument();
|
||||
final SelectionModel selectionModel = editor.getSelectionModel();
|
||||
final int startOffset;
|
||||
final int endOffset;
|
||||
if (selectionModel.hasSelection()) {
|
||||
startOffset = selectionModel.getSelectionStart();
|
||||
endOffset = selectionModel.getSelectionEnd();
|
||||
}
|
||||
else {
|
||||
startOffset = document.getLineStartOffset(info.toMove.startLine);
|
||||
endOffset = getLineStartSafeOffset(document, info.toMove.endLine);
|
||||
}
|
||||
final PsiElement element = file.findElementAt(startOffset);
|
||||
if (element == null) return false;
|
||||
final PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class, true, PsiMember.class);
|
||||
if (tryStatement == null) return false;
|
||||
PsiCatchSection firstToMove = null;
|
||||
PsiCatchSection lastToMove = null;
|
||||
for (PsiCatchSection catchSection : tryStatement.getCatchSections()) {
|
||||
final int offset = catchSection.getTextOffset();
|
||||
final PsiElement child = catchSection.getFirstChild();
|
||||
if (!(child instanceof PsiKeyword)) return info.prohibitMove();
|
||||
if (offset >= startOffset && offset < endOffset || child.getTextRange().contains(startOffset)) {
|
||||
if (firstToMove == null) firstToMove = catchSection;
|
||||
lastToMove = catchSection;
|
||||
}
|
||||
}
|
||||
if (firstToMove == null) return false;
|
||||
if (!sanityCheck(firstToMove)) {
|
||||
return info.prohibitMove();
|
||||
}
|
||||
if (element instanceof PsiWhiteSpace && element.getNextSibling() instanceof PsiStatement
|
||||
|| PsiTreeUtil.getParentOfType(element, PsiStatement.class, true, PsiMember.class) != tryStatement) {
|
||||
// nonsensical selection
|
||||
return info.prohibitMove();
|
||||
}
|
||||
|
||||
final PsiCatchSection sibling = down
|
||||
? PsiTreeUtil.getNextSiblingOfType(lastToMove, PsiCatchSection.class)
|
||||
: PsiTreeUtil.getPrevSiblingOfType(firstToMove, PsiCatchSection.class);
|
||||
if (sibling == null) return info.prohibitMove();
|
||||
|
||||
info.toMove = new LineRange(firstToMove, lastToMove, document);
|
||||
info.toMove2 = new LineRange(sibling, sibling, document);
|
||||
if (down ? info.toMove.endLine > info.toMove2.startLine : info.toMove2.endLine > info.toMove.startLine) {
|
||||
info.toMove = new LineRange(info.toMove.startLine, info.toMove.endLine - 1);
|
||||
info.toMove2 = new LineRange(info.toMove2.startLine, info.toMove2.endLine - 1);
|
||||
}
|
||||
info.indentSource = false;
|
||||
info.indentTarget = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sanityCheck(PsiCatchSection catchSection) {
|
||||
final PsiCatchSection[] catchSections = catchSection.getTryStatement().getCatchSections();
|
||||
if (catchSections.length < 2) return false;
|
||||
final boolean newLine = containsNewLine(catchSections[0].getPrevSibling());
|
||||
for (int i = 1; i < catchSections.length; i++) {
|
||||
if (newLine != containsNewLine(catchSections[i].getPrevSibling())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean containsNewLine(PsiElement element) {
|
||||
return element instanceof PsiWhiteSpace && element.getText().contains("\n");
|
||||
}
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.impl.source.tree.Factory;
|
||||
import com.intellij.psi.impl.source.tree.TreeElement;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.jsp.IJspClassLevelDeclarationStatement;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class JavaDeclarationMover extends LineMover {
|
||||
private static final Logger LOG = Logger.getInstance(JavaDeclarationMover.class);
|
||||
@SuppressWarnings("StatefulEp")
|
||||
private PsiEnumConstant myEnumToInsertSemicolonAfter;
|
||||
private boolean moveEnumConstant;
|
||||
|
||||
@Override
|
||||
public void beforeMove(final @NotNull Editor editor, final @NotNull MoveInfo info, final boolean down) {
|
||||
super.beforeMove(editor, info, down);
|
||||
|
||||
if (myEnumToInsertSemicolonAfter != null) {
|
||||
TreeElement semicolon = Factory.createSingleLeafElement(JavaTokenType.SEMICOLON, ";", 0, 1, null, myEnumToInsertSemicolonAfter.getManager());
|
||||
|
||||
try {
|
||||
PsiElement inserted = myEnumToInsertSemicolonAfter.getParent().addAfter(semicolon.getPsi(), myEnumToInsertSemicolonAfter);
|
||||
inserted = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(inserted);
|
||||
final LogicalPosition position = editor.offsetToLogicalPosition(inserted.getTextRange().getEndOffset());
|
||||
|
||||
info.toMove2 = new LineRange(position.line + 1, position.line + 1);
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
finally {
|
||||
myEnumToInsertSemicolonAfter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterMove(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
|
||||
super.afterMove(editor, file, info, down);
|
||||
if (moveEnumConstant) {
|
||||
final Document document = editor.getDocument();
|
||||
final CharSequence cs = document.getCharsSequence();
|
||||
int end1 = info.range1.getEndOffset();
|
||||
char c1 = cs.charAt(--end1);
|
||||
while (Character.isWhitespace(c1)) {
|
||||
c1 = cs.charAt(--end1);
|
||||
}
|
||||
int end2 = info.range2.getEndOffset();
|
||||
char c2 = cs.charAt(--end2);
|
||||
while (Character.isWhitespace(c2)) {
|
||||
c2 = cs.charAt(--end2);
|
||||
}
|
||||
if (c1 == c2 || !contains(info.range1, end1) || !contains(info.range2, end2)) {
|
||||
return;
|
||||
}
|
||||
if (c1 == ',' || c1 == ';') {
|
||||
document.deleteString(end1, end1 + 1);
|
||||
if (end1 < end2) {
|
||||
end1--;
|
||||
end2--;
|
||||
}
|
||||
document.insertString(end2 + 1, String.valueOf(c1));
|
||||
}
|
||||
if (c2 == ',' || c2 == ';'){
|
||||
document.deleteString(end2, end2 + 1);
|
||||
if (end2 < end1) end1--;
|
||||
document.insertString(end1 + 1, String.valueOf(c2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean contains(RangeMarker rangeMarker, int index) {
|
||||
return rangeMarker.getStartOffset() <= index && rangeMarker.getEndOffset() >= index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAvailable(final @NotNull Editor editor, final @NotNull PsiFile file, final @NotNull MoveInfo info, final boolean down) {
|
||||
if (!(file instanceof PsiJavaFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean available = super.checkAvailable(editor, file, info, down);
|
||||
if (!available) return false;
|
||||
|
||||
final Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, info.toMove);
|
||||
if (psiRange == null) return false;
|
||||
|
||||
final PsiMember firstMember = PsiTreeUtil.getParentOfType(psiRange.getFirst(), PsiMember.class, false);
|
||||
PsiElement endElement = psiRange.getSecond();
|
||||
if (firstMember instanceof PsiEnumConstant && endElement instanceof PsiJavaToken) {
|
||||
final IElementType tokenType = ((PsiJavaToken)endElement).getTokenType();
|
||||
if (down && tokenType == JavaTokenType.SEMICOLON) {
|
||||
return info.prohibitMove();
|
||||
}
|
||||
if (tokenType == JavaTokenType.COMMA || tokenType == JavaTokenType.SEMICOLON) {
|
||||
endElement = PsiTreeUtil.skipWhitespacesBackward(endElement);
|
||||
}
|
||||
}
|
||||
PsiMember lastMember = PsiTreeUtil.getParentOfType(endElement, PsiMember.class, false);
|
||||
if (firstMember == null || lastMember == null) return false;
|
||||
if (lastMember instanceof PsiEnumConstantInitializer enumConstantInitializer) {
|
||||
lastMember = enumConstantInitializer.getEnumConstant();
|
||||
}
|
||||
|
||||
LineRange range;
|
||||
if (firstMember == lastMember) {
|
||||
moveEnumConstant = firstMember instanceof PsiEnumConstant;
|
||||
range = memberRange(firstMember, editor, info.toMove);
|
||||
if (range == null) return false;
|
||||
range.firstElement = range.lastElement = firstMember;
|
||||
}
|
||||
else {
|
||||
final PsiElement parent = PsiTreeUtil.findCommonParent(firstMember, lastMember);
|
||||
if (parent == null) return false;
|
||||
|
||||
final Pair<PsiElement, PsiElement> combinedRange = getElementRange(parent, firstMember, lastMember);
|
||||
if (combinedRange == null) return false;
|
||||
final LineRange lineRange1 = memberRange(combinedRange.getFirst(), editor, info.toMove);
|
||||
if (lineRange1 == null) return false;
|
||||
final LineRange lineRange2 = memberRange(combinedRange.getSecond(), editor, info.toMove);
|
||||
if (lineRange2 == null) return false;
|
||||
range = new LineRange(lineRange1.startLine, lineRange2.endLine);
|
||||
range.firstElement = combinedRange.getFirst();
|
||||
range.lastElement = combinedRange.getSecond();
|
||||
}
|
||||
Document document = editor.getDocument();
|
||||
|
||||
PsiElement sibling = (down ? range.endLine >= document.getLineCount() : range.startLine == 0) ? null :
|
||||
firstNonWhiteElement(down ? document.getLineStartOffset(range.endLine)
|
||||
: document.getLineEndOffset(range.startLine - 1),
|
||||
file, down);
|
||||
if (range.lastElement instanceof PsiEnumConstant) {
|
||||
if (sibling instanceof PsiJavaToken token) {
|
||||
final IElementType tokenType = token.getTokenType();
|
||||
if (down && tokenType == JavaTokenType.SEMICOLON) {
|
||||
return info.prohibitMove();
|
||||
}
|
||||
if (tokenType == JavaTokenType.COMMA) {
|
||||
sibling = down ?
|
||||
PsiTreeUtil.skipWhitespacesForward(sibling) :
|
||||
PsiTreeUtil.skipWhitespacesBackward(sibling);
|
||||
}
|
||||
}
|
||||
else if (sibling instanceof PsiField && !(sibling instanceof PsiEnumConstant)) {
|
||||
// do not move enum constant past regular field
|
||||
return info.prohibitMove();
|
||||
}
|
||||
}
|
||||
final boolean areWeMovingClass = range.firstElement instanceof PsiClass;
|
||||
info.toMove = range;
|
||||
|
||||
int neighbourLine = down ? range.endLine : range.startLine - 1;
|
||||
if (neighbourLine >= 0 && neighbourLine < document.getLineCount() &&
|
||||
CharArrayUtil.containsOnlyWhiteSpaces(document.getImmutableCharSequence().subSequence(document.getLineStartOffset(neighbourLine),
|
||||
document.getLineEndOffset(neighbourLine))) &&
|
||||
emptyLineCanBeDeletedAccordingToCodeStyle(file, document, document.getLineEndOffset(neighbourLine))) {
|
||||
info.toMove2 = new LineRange(neighbourLine, neighbourLine + 1);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
LineRange intraClassRange = moveInsideOutsideClassPosition(editor, sibling, down, areWeMovingClass);
|
||||
if (intraClassRange == null) {
|
||||
Couple<LineRange> splitRange = extractCommentRange(sibling);
|
||||
info.toMove2 = splitRange.first.startLine == splitRange.first.endLine || !down ? splitRange.second : splitRange.first;
|
||||
if (down && sibling.getNextSibling() == null) return false;
|
||||
}
|
||||
else {
|
||||
info.toMove2 = intraClassRange;
|
||||
}
|
||||
if (down ? info.toMove2.startLine < info.toMove.endLine : info.toMove2.endLine > info.toMove.startLine) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IllegalMoveException e) {
|
||||
info.prohibitMove();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean emptyLineCanBeDeletedAccordingToCodeStyle(PsiFile file, Document document, int offset) {
|
||||
CharSequence text = document.getImmutableCharSequence();
|
||||
String whitespace = " \t\n";
|
||||
int whitespaceStartOffset = CharArrayUtil.shiftBackward(text, offset - 1, whitespace) + 1;
|
||||
int whitespaceEndOffset = CharArrayUtil.shiftForward(text, offset, whitespace);
|
||||
int minLineFeeds = CodeStyleManager.getInstance(file.getProject()).getMinLineFeeds(file, whitespaceEndOffset);
|
||||
int actualLineFeeds = StringUtil.countNewLines(text.subSequence(whitespaceStartOffset, whitespaceEndOffset));
|
||||
return actualLineFeeds > minLineFeeds;
|
||||
}
|
||||
|
||||
private static LineRange memberRange(@NotNull PsiElement member, Editor editor, LineRange lineRange) {
|
||||
final TextRange textRange = member.getTextRange();
|
||||
if (editor.getDocument().getTextLength() < textRange.getEndOffset()) return null;
|
||||
int startLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line;
|
||||
int endLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line+1;
|
||||
|
||||
// if member includes a comment (non-javadoc) and it wasn't selected by user, don't move it with member
|
||||
Couple<LineRange> splitRanges = extractCommentRange(member);
|
||||
if (lineRange.startLine >= splitRanges.first.endLine) startLine = splitRanges.second.startLine;
|
||||
else if (lineRange.endLine < splitRanges.second.startLine) endLine = splitRanges.first.endLine;
|
||||
|
||||
if (!isInsideDeclaration(member, startLine, endLine, lineRange, editor)) return null;
|
||||
|
||||
return new LineRange(startLine, endLine);
|
||||
}
|
||||
|
||||
private static Couple<LineRange> extractCommentRange(@NotNull PsiElement member) {
|
||||
PsiElement firstChild = member.getFirstChild();
|
||||
PsiElement firstCoreChild = firstChild;
|
||||
while (firstCoreChild instanceof PsiComment && !(firstCoreChild instanceof PsiDocComment) || firstCoreChild instanceof PsiWhiteSpace) {
|
||||
firstCoreChild = firstCoreChild.getNextSibling();
|
||||
}
|
||||
PsiElement lastAttachedChild = PsiTreeUtil.skipWhitespacesBackward(firstCoreChild);
|
||||
if (lastAttachedChild == null) {
|
||||
LineRange wholeRange = new LineRange(member);
|
||||
return Couple.of(new LineRange(wholeRange.startLine, wholeRange.startLine), wholeRange);
|
||||
}
|
||||
else {
|
||||
return Couple.of(new LineRange(firstChild, lastAttachedChild), new LineRange(firstCoreChild, member));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInsideDeclaration(final @NotNull PsiElement member,
|
||||
final int startLine,
|
||||
final int endLine,
|
||||
final LineRange lineRange,
|
||||
final Editor editor) {
|
||||
// if we positioned on member start or end we'll be able to move it
|
||||
if (startLine == lineRange.startLine || startLine == lineRange.endLine || endLine == lineRange.startLine ||
|
||||
endLine == lineRange.endLine) {
|
||||
return true;
|
||||
}
|
||||
List<PsiElement> memberSuspects = new ArrayList<>();
|
||||
PsiModifierList modifierList = member instanceof PsiMember ? ((PsiMember)member).getModifierList() : null;
|
||||
if (modifierList != null) memberSuspects.add(modifierList);
|
||||
if (member instanceof PsiClass aClass) {
|
||||
if (aClass instanceof PsiAnonymousClass) return false; // move new expression instead of anon class
|
||||
PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
|
||||
if (nameIdentifier != null) memberSuspects.add(nameIdentifier);
|
||||
}
|
||||
if (member instanceof PsiMethod method) {
|
||||
PsiIdentifier nameIdentifier = method.getNameIdentifier();
|
||||
if (nameIdentifier != null) memberSuspects.add(nameIdentifier);
|
||||
PsiTypeElement returnTypeElement = method.getReturnTypeElement();
|
||||
if (returnTypeElement != null) memberSuspects.add(returnTypeElement);
|
||||
}
|
||||
if (member instanceof PsiField field) {
|
||||
PsiIdentifier nameIdentifier = field.getNameIdentifier();
|
||||
memberSuspects.add(nameIdentifier);
|
||||
PsiTypeElement typeElement = field.getTypeElement();
|
||||
if (typeElement != null) memberSuspects.add(typeElement);
|
||||
}
|
||||
TextRange lineTextRange = new TextRange(editor.getDocument().getLineStartOffset(lineRange.startLine), editor.getDocument().getLineEndOffset(lineRange.endLine));
|
||||
for (PsiElement suspect : memberSuspects) {
|
||||
TextRange textRange = suspect.getTextRange();
|
||||
if (textRange != null && lineTextRange.intersects(textRange)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class IllegalMoveException extends Exception {
|
||||
}
|
||||
|
||||
// null means we are not crossing class border
|
||||
// throws IllegalMoveException when corresponding movement has no sense
|
||||
private @Nullable LineRange moveInsideOutsideClassPosition(Editor editor, PsiElement sibling, final boolean isDown, boolean areWeMovingClass) throws IllegalMoveException{
|
||||
if (sibling == null || sibling instanceof PsiImportList) throw new IllegalMoveException();
|
||||
if (PsiUtil.isJavaToken(sibling, (isDown ? JavaTokenType.RBRACE : JavaTokenType.LBRACE)) &&
|
||||
sibling.getParent() instanceof PsiClass aClass) {
|
||||
// moving outside class
|
||||
final PsiElement parent = aClass.getParent();
|
||||
if (!areWeMovingClass && !(parent instanceof PsiClass)) throw new IllegalMoveException();
|
||||
if (aClass instanceof PsiAnonymousClass) throw new IllegalMoveException();
|
||||
PsiElement start = isDown ? sibling : aClass.getModifierList();
|
||||
return new LineRange(start, sibling, editor.getDocument());
|
||||
//return isDown ? nextLineOffset(editor, aClass.getTextRange().getEndOffset()) : aClass.getTextRange().getStartOffset();
|
||||
}
|
||||
// trying to move up inside enum constant list, move outside enum class instead
|
||||
if (!isDown
|
||||
&& sibling.getParent() instanceof PsiClass aClass
|
||||
&& (PsiUtil.isJavaToken(sibling, JavaTokenType.SEMICOLON) || sibling instanceof PsiErrorElement)
|
||||
&& firstNonWhiteElement(sibling.getPrevSibling(), false) instanceof PsiEnumConstant) {
|
||||
if (!areWeMovingClass && !(aClass.getParent() instanceof PsiClass)) throw new IllegalMoveException();
|
||||
Document document = editor.getDocument();
|
||||
int startLine = document.getLineNumber(aClass.getTextRange().getStartOffset());
|
||||
int endLine = document.getLineNumber(sibling.getTextRange().getEndOffset()) + 1;
|
||||
return new LineRange(startLine, endLine);
|
||||
}
|
||||
if (sibling instanceof PsiClass aClass) {
|
||||
// moving inside class
|
||||
if (aClass instanceof PsiAnonymousClass) throw new IllegalMoveException();
|
||||
if (isDown) {
|
||||
PsiElement child = aClass.getFirstChild();
|
||||
if (child == null) throw new IllegalMoveException();
|
||||
return new LineRange(child, aClass.isEnum() ? afterEnumConstantsPosition(aClass) : aClass.getLBrace(),
|
||||
editor.getDocument());
|
||||
}
|
||||
else {
|
||||
PsiElement rBrace = aClass.getRBrace();
|
||||
if (rBrace == null) throw new IllegalMoveException();
|
||||
return new LineRange(rBrace, rBrace, editor.getDocument());
|
||||
}
|
||||
}
|
||||
if (sibling instanceof IJspClassLevelDeclarationStatement) {
|
||||
// there should be another scriptlet/decl to move
|
||||
if (firstNonWhiteElement(isDown ? sibling.getNextSibling() : sibling.getPrevSibling(), isDown) == null) throw new IllegalMoveException();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PsiElement afterEnumConstantsPosition(final PsiClass aClass) {
|
||||
PsiField[] fields = aClass.getFields();
|
||||
for (int i = fields.length-1;i>=0; i--) {
|
||||
PsiField field = fields[i];
|
||||
if (field instanceof PsiEnumConstant) {
|
||||
PsiElement anchor = firstNonWhiteElement(field.getNextSibling(), true);
|
||||
if (!(PsiUtil.isJavaToken(anchor, JavaTokenType.SEMICOLON))) {
|
||||
anchor = field;
|
||||
myEnumToInsertSemicolonAfter = (PsiEnumConstant)field;
|
||||
}
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
// no enum constants at all ?
|
||||
return aClass.getLBrace();
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.editorActions.moveUpDown;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightFrontbackUtil;
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiDocumentManagerBase;
|
||||
import com.intellij.psi.jsp.IJspClassLevelDeclarationStatement;
|
||||
import com.intellij.psi.jsp.IJspTemplateStatement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
final class JavaStatementMover extends LineMover {
|
||||
private static final Logger LOG = Logger.getInstance(JavaStatementMover.class);
|
||||
private static final Key<PsiElement> STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY = Key.create("STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY");
|
||||
|
||||
@Override
|
||||
public void beforeMove(@NotNull Editor editor, @NotNull MoveInfo info, boolean down) {
|
||||
super.beforeMove(editor, info, down);
|
||||
|
||||
PsiElement statement = STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY.get(info);
|
||||
if (statement != null) {
|
||||
surroundWithCodeBlock(info, down, statement);
|
||||
}
|
||||
}
|
||||
|
||||
private static void surroundWithCodeBlock(MoveInfo info, boolean down, PsiElement statement) {
|
||||
try {
|
||||
Document document = PsiDocumentManager.getInstance(statement.getProject()).getDocument(statement.getContainingFile());
|
||||
assert document != null : statement.getContainingFile();
|
||||
int startOffset = document.getLineStartOffset(info.toMove.startLine);
|
||||
int endOffset = getLineStartSafeOffset(document, info.toMove.endLine);
|
||||
if (document.getText().charAt(endOffset - 1) == '\n') endOffset--;
|
||||
RangeMarker lineRangeMarker = document.createRangeMarker(startOffset, endOffset);
|
||||
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(statement.getProject());
|
||||
PsiCodeBlock codeBlock = factory.createCodeBlock();
|
||||
codeBlock.add(statement);
|
||||
PsiBlockStatement blockStatement = (PsiBlockStatement)factory.createStatementFromText("{}", statement);
|
||||
blockStatement.getCodeBlock().replace(codeBlock);
|
||||
PsiBlockStatement newStatement = (PsiBlockStatement)statement.replace(blockStatement);
|
||||
newStatement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(newStatement);
|
||||
info.toMove = new LineRange(document.getLineNumber(lineRangeMarker.getStartOffset()), document.getLineNumber(lineRangeMarker.getEndOffset())+1);
|
||||
PsiCodeBlock newCodeBlock = newStatement.getCodeBlock();
|
||||
if (down) {
|
||||
PsiElement blockChild = firstNonWhiteElement(newCodeBlock.getFirstBodyElement(), true);
|
||||
if (blockChild == null) blockChild = newCodeBlock.getRBrace();
|
||||
assert blockChild != null : newCodeBlock;
|
||||
info.toMove2 = new LineRange(info.toMove2.startLine, document.getLineNumber(blockChild.getTextRange().getStartOffset()));
|
||||
}
|
||||
else {
|
||||
PsiJavaToken brace = newCodeBlock.getRBrace();
|
||||
assert brace != null : newCodeBlock;
|
||||
int start = document.getLineNumber(brace.getTextRange().getStartOffset());
|
||||
int end = info.toMove.startLine;
|
||||
if (start > end) end = start;
|
||||
info.toMove2 = new LineRange(start, end);
|
||||
}
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
|
||||
boolean available = super.checkAvailable(editor, file, info, down);
|
||||
if (!available) return false;
|
||||
|
||||
LineRange range = expandLineRangeToCoverPsiElements(info.toMove, editor, file);
|
||||
if (range == null) return false;
|
||||
|
||||
info.toMove = range;
|
||||
int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0));
|
||||
int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine, 0));
|
||||
PsiElement[] statements = CodeInsightFrontbackUtil.findStatementsInRange(file, startOffset, endOffset);
|
||||
if (statements.length == 0) return false;
|
||||
|
||||
range.firstElement = statements[0];
|
||||
range.lastElement = statements[statements.length - 1];
|
||||
|
||||
if (!checkMovingInsideOutside(file, editor, info, down)) {
|
||||
return info.prohibitMove();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int getDestLineForAnonymous(Editor editor, LineRange range, boolean down) {
|
||||
int destLine = down ? range.endLine + 1 : range.startLine - 1;
|
||||
if (!(range.firstElement instanceof PsiStatement)) {
|
||||
return destLine;
|
||||
}
|
||||
|
||||
PsiElement sibling = firstNonWhiteElement(down ? range.lastElement.getNextSibling() : range.firstElement.getPrevSibling(), down);
|
||||
if (sibling != null) {
|
||||
PsiClass aClass = PsiTreeUtil.findChildOfType(sibling, PsiClass.class, true, PsiStatement.class);
|
||||
if (aClass != null && PsiTreeUtil.getParentOfType(aClass, PsiStatement.class) == sibling) {
|
||||
destLine = editor.getDocument().getLineNumber(down ? sibling.getTextRange().getEndOffset() + 1 : sibling.getTextRange().getStartOffset());
|
||||
}
|
||||
}
|
||||
|
||||
return destLine;
|
||||
}
|
||||
|
||||
private static boolean calcInsertOffset(PsiFile file, Editor editor, LineRange range, MoveInfo info, boolean down) {
|
||||
int destLine = getDestLineForAnonymous(editor, range, down);
|
||||
int startLine = down ? range.endLine : range.startLine - 1;
|
||||
if (destLine < 0 || startLine < 0) return false;
|
||||
|
||||
while (true) {
|
||||
int offset = editor.logicalPositionToOffset(new LogicalPosition(destLine, 0));
|
||||
PsiElement element = firstNonWhiteElement(offset, file, true);
|
||||
|
||||
while (element != null && !(element instanceof PsiFile)) {
|
||||
TextRange elementTextRange = element.getTextRange();
|
||||
if (elementTextRange.isEmpty() || !elementTextRange.grown(-1).shiftRight(1).contains(offset)) {
|
||||
PsiElement elementToSurround = null;
|
||||
boolean found = false;
|
||||
if ((element instanceof PsiStatement || element instanceof PsiComment) && statementCanBePlacedAlong(element)) {
|
||||
found = true;
|
||||
if (!(statementsCanBeMovedWithin(element.getParent()))) {
|
||||
elementToSurround = element;
|
||||
}
|
||||
}
|
||||
else if (PsiUtil.isJavaToken(element, JavaTokenType.RBRACE) && statementsCanBeMovedWithin(element.getParent())) {
|
||||
// before code block closing brace
|
||||
found = true;
|
||||
}
|
||||
if (found) {
|
||||
STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY.set(info, elementToSurround);
|
||||
info.toMove = range;
|
||||
int endLine = destLine;
|
||||
if (startLine > endLine) {
|
||||
int tmp = endLine;
|
||||
endLine = startLine;
|
||||
startLine = tmp;
|
||||
}
|
||||
|
||||
info.toMove2 = down ? new LineRange(startLine, endLine) : new LineRange(startLine, endLine + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
element = element.getParent();
|
||||
}
|
||||
|
||||
destLine += down ? 1 : -1;
|
||||
if (destLine < 0 || destLine >= editor.getDocument().getLineCount()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean statementCanBePlacedAlong(PsiElement element) {
|
||||
if (element instanceof IJspTemplateStatement) {
|
||||
PsiElement neighbour = element.getPrevSibling();
|
||||
// we can place statement inside scriptlet only
|
||||
return neighbour != null && !(neighbour instanceof IJspTemplateStatement);
|
||||
}
|
||||
PsiElement parent = element.getParent();
|
||||
if (element instanceof PsiBlockStatement && !(parent instanceof PsiCodeBlock)) return false;
|
||||
if (parent instanceof IJspClassLevelDeclarationStatement) return false;
|
||||
if (statementsCanBeMovedWithin(parent)) return true;
|
||||
if (parent instanceof PsiIfStatement &&
|
||||
(element == ((PsiIfStatement)parent).getThenBranch() || element == ((PsiIfStatement)parent).getElseBranch())) {
|
||||
return true;
|
||||
}
|
||||
if (parent instanceof PsiWhileStatement && element == ((PsiWhileStatement)parent).getBody()) {
|
||||
return true;
|
||||
}
|
||||
if (parent instanceof PsiDoWhileStatement && element == ((PsiDoWhileStatement)parent).getBody()) {
|
||||
return true;
|
||||
}
|
||||
// know nothing about that
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean statementsCanBeMovedWithin(PsiElement parent) {
|
||||
return parent instanceof PsiCodeBlock || parent instanceof PsiJavaModule;
|
||||
}
|
||||
|
||||
private static boolean checkMovingInsideOutside(PsiFile file, Editor editor, @NotNull MoveInfo info, boolean down) {
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
|
||||
PsiElement elementAtOffset = file.getViewProvider().findElementAt(offset, JavaLanguage.INSTANCE);
|
||||
if (elementAtOffset == null) return false;
|
||||
|
||||
PsiElement guard = findGuard(elementAtOffset);
|
||||
|
||||
PsiElement brace = itIsTheClosingCurlyBraceWeAreMoving(file, editor);
|
||||
if (brace != null) {
|
||||
int line = editor.getDocument().getLineNumber(offset);
|
||||
LineRange toMove = new LineRange(line, line + 1);
|
||||
toMove.firstElement = toMove.lastElement = brace;
|
||||
info.toMove = toMove;
|
||||
}
|
||||
|
||||
// cannot move in/outside method/class/initializer/comment
|
||||
if (!calcInsertOffset(file, editor, info.toMove, info, down)) return false;
|
||||
|
||||
int insertOffset = down ? getLineStartSafeOffset(editor.getDocument(), info.toMove2.endLine)
|
||||
: editor.getDocument().getLineStartOffset(info.toMove2.startLine);
|
||||
PsiElement elementAtInsertOffset = file.getViewProvider().findElementAt(insertOffset, JavaLanguage.INSTANCE);
|
||||
PsiElement newGuard = findGuard(elementAtInsertOffset);
|
||||
|
||||
if (brace != null &&
|
||||
PsiTreeUtil.getParentOfType(brace, PsiCodeBlock.class, false) != PsiTreeUtil.getParentOfType(elementAtInsertOffset, PsiCodeBlock.class, false)) {
|
||||
info.indentSource = true;
|
||||
}
|
||||
|
||||
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 PsiElement findGuard(PsiElement element) {
|
||||
PsiElement guard = element;
|
||||
do {
|
||||
guard = PsiTreeUtil.getParentOfType(guard, PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class);
|
||||
}
|
||||
while (guard instanceof PsiAnonymousClass);
|
||||
return guard;
|
||||
}
|
||||
|
||||
private static boolean isInside(int offset, PsiElement guard) {
|
||||
if (guard == null) return false;
|
||||
|
||||
TextRange inside;
|
||||
if (guard instanceof PsiMethod) {
|
||||
PsiCodeBlock body = ((PsiMethod)guard).getBody();
|
||||
inside = body != null ? body.getTextRange() : null;
|
||||
}
|
||||
else if (guard instanceof PsiClassInitializer) {
|
||||
inside = ((PsiClassInitializer)guard).getBody().getTextRange();
|
||||
}
|
||||
else if (guard instanceof PsiClass) {
|
||||
PsiElement left = ((PsiClass)guard).getLBrace(), right = ((PsiClass)guard).getRBrace();
|
||||
inside = left != null && right != null ? new TextRange(left.getTextOffset(), right.getTextOffset()) : null;
|
||||
}
|
||||
else {
|
||||
inside = guard.getTextRange();
|
||||
}
|
||||
return inside != null && inside.contains(offset);
|
||||
}
|
||||
|
||||
private static LineRange expandLineRangeToCoverPsiElements(LineRange range, Editor editor, PsiFile file) {
|
||||
Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, range);
|
||||
if (psiRange == null) return null;
|
||||
PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond());
|
||||
Pair<PsiElement, PsiElement> elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond());
|
||||
if (elementRange == null) return null;
|
||||
int endOffset = elementRange.getSecond().getTextRange().getEndOffset();
|
||||
Document document = editor.getDocument();
|
||||
if (endOffset > document.getTextLength()) {
|
||||
LOG.assertTrue(!PsiDocumentManager.getInstance(file.getProject()).isUncommited(document));
|
||||
LOG.assertTrue(PsiDocumentManagerBase.checkConsistency(file, document));
|
||||
}
|
||||
int endLine;
|
||||
if (endOffset == document.getTextLength()) {
|
||||
endLine = document.getLineCount();
|
||||
}
|
||||
else {
|
||||
endLine = editor.offsetToLogicalPosition(endOffset).line + 1;
|
||||
endLine = Math.min(endLine, document.getLineCount());
|
||||
}
|
||||
int startLine = Math.min(range.startLine, editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line);
|
||||
endLine = Math.max(endLine, range.endLine);
|
||||
return new LineRange(startLine, endLine);
|
||||
}
|
||||
|
||||
private static PsiElement itIsTheClosingCurlyBraceWeAreMoving(PsiFile file, Editor editor) {
|
||||
LineRange range = getLineRangeFromSelection(editor);
|
||||
if (range.endLine - range.startLine != 1) return null;
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
Document document = editor.getDocument();
|
||||
int line = document.getLineNumber(offset);
|
||||
int lineStartOffset = document.getLineStartOffset(line);
|
||||
String lineText = document.getText().substring(lineStartOffset, document.getLineEndOffset(line));
|
||||
if (!lineText.trim().equals("}")) return null;
|
||||
return file.findElementAt(lineStartOffset + lineText.indexOf('}'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user