From de7d4de1c06b9f3b62e0d6efd3319ed5a551556b Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Thu, 24 Apr 2025 12:54:10 +0200 Subject: [PATCH] IJPL-181799 execute java move up/down statement actions on FE in RD GitOrigin-RevId: 5df74728054974e44f2664018c3535c16c24927d --- .../resource/intellij.java.frontback.impl.xml | 9 + .../codeInsight/CodeInsightFrontbackUtil.java | 166 ++++++++++++++++++ .../moveUpDown/JavaCaseBlockMover.java} | 8 +- .../moveUpDown/JavaCatchBlockMover.java} | 2 +- .../moveUpDown/JavaDeclarationMover.java} | 10 +- .../moveUpDown/JavaStatementMover.java} | 20 +-- .../resources/META-INF/JavaPlugin.xml | 8 - .../psi/jsp/IJspTemplateStatement.java | 8 + .../jsp/jspJava/JspTemplateStatement.java | 3 +- .../psi/jsp/IJspTemplateStatement.java | 8 + 10 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 java/java-frontback-impl/src/com/intellij/codeInsight/CodeInsightFrontbackUtil.java rename java/{java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CaseBlockMover.java => java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCaseBlockMover.java} (94%) rename java/{java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CatchBlockMover.java => java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCatchBlockMover.java} (98%) rename java/{java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/DeclarationMover.java => java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaDeclarationMover.java} (97%) rename java/{java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/StatementMover.java => java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaStatementMover.java} (94%) create mode 100644 java/jsp-openapi-no-deps/src/com/intellij/psi/jsp/IJspTemplateStatement.java create mode 100644 platform/core-api/src/com/intellij/psi/jsp/IJspTemplateStatement.java diff --git a/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml b/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml index 10372fdf52a0..1a96416b761f 100644 --- a/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml +++ b/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml @@ -67,6 +67,15 @@ + + + + + diff --git a/java/java-frontback-impl/src/com/intellij/codeInsight/CodeInsightFrontbackUtil.java b/java/java-frontback-impl/src/com/intellij/codeInsight/CodeInsightFrontbackUtil.java new file mode 100644 index 000000000000..5d9a02dd324d --- /dev/null +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/CodeInsightFrontbackUtil.java @@ -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 findElementInRange(PsiFile file, int startOffset, int endOffset, Class 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 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 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); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CaseBlockMover.java b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCaseBlockMover.java similarity index 94% rename from java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CaseBlockMover.java rename to java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCaseBlockMover.java index 67b237fcfa3d..66f48f88060c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CaseBlockMover.java +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCaseBlockMover.java @@ -1,7 +1,7 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// 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.CodeInsightUtil; +import com.intellij.codeInsight.CodeInsightFrontbackUtil; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; @@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; -public final class CaseBlockMover extends LineMover { +public final class JavaCaseBlockMover extends LineMover { @Override public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) { @@ -28,7 +28,7 @@ public final class CaseBlockMover extends LineMover { int endOffset = getLineStartSafeOffset(document, info.toMove.endLine); List statements = new SmartList<>(); PsiElement firstElement = null; - for (PsiElement element : CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset)) { + for (PsiElement element : CodeInsightFrontbackUtil.findStatementsInRange(file, startOffset, endOffset)) { if (element instanceof PsiSwitchLabelStatement) { statements.add((PsiSwitchLabelStatement)element); } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CatchBlockMover.java b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCatchBlockMover.java similarity index 98% rename from java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CatchBlockMover.java rename to java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCatchBlockMover.java index 6c04a4149355..389ea31b6ac4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/CatchBlockMover.java +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaCatchBlockMover.java @@ -11,7 +11,7 @@ import org.jetbrains.annotations.NotNull; /** * @author Bas Leijdekkers */ -public final class CatchBlockMover extends LineMover { +public final class JavaCatchBlockMover extends LineMover { @Override public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) { diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/DeclarationMover.java b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaDeclarationMover.java similarity index 97% rename from java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/DeclarationMover.java rename to java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaDeclarationMover.java index b65013fe2c65..6a8ee2d23049 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/DeclarationMover.java +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaDeclarationMover.java @@ -1,4 +1,4 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// 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; @@ -13,10 +13,10 @@ 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.jsp.jspJava.JspClassLevelDeclarationStatement; 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; @@ -28,8 +28,8 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -final class DeclarationMover extends LineMover { - private static final Logger LOG = Logger.getInstance(DeclarationMover.class); +final class JavaDeclarationMover extends LineMover { + private static final Logger LOG = Logger.getInstance(JavaDeclarationMover.class); @SuppressWarnings("StatefulEp") private PsiEnumConstant myEnumToInsertSemicolonAfter; private boolean moveEnumConstant; @@ -324,7 +324,7 @@ final class DeclarationMover extends LineMover { return new LineRange(rBrace, rBrace, editor.getDocument()); } } - if (sibling instanceof JspClassLevelDeclarationStatement) { + if (sibling instanceof IJspClassLevelDeclarationStatement) { // there should be another scriptlet/decl to move if (firstNonWhiteElement(isDown ? sibling.getNextSibling() : sibling.getPrevSibling(), isDown) == null) throw new IllegalMoveException(); } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/StatementMover.java b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaStatementMover.java similarity index 94% rename from java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/StatementMover.java rename to java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaStatementMover.java index 6350ae276064..926a1ed2b58a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/StatementMover.java +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/JavaStatementMover.java @@ -1,7 +1,7 @@ -// 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. +// 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.CodeInsightUtil; +import com.intellij.codeInsight.CodeInsightFrontbackUtil; import com.intellij.codeInsight.CodeInsightUtilCore; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; @@ -14,15 +14,15 @@ 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.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; -import com.intellij.psi.impl.source.jsp.jspJava.JspTemplateStatement; +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 StatementMover extends LineMover { - private static final Logger LOG = Logger.getInstance(StatementMover.class); +final class JavaStatementMover extends LineMover { + private static final Logger LOG = Logger.getInstance(JavaStatementMover.class); private static final Key STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY = Key.create("STATEMENT_TO_SURROUND_WITH_CODE_BLOCK_KEY"); @Override @@ -84,7 +84,7 @@ final class StatementMover extends LineMover { info.toMove = range; int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0)); int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine, 0)); - PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); + PsiElement[] statements = CodeInsightFrontbackUtil.findStatementsInRange(file, startOffset, endOffset); if (statements.length == 0) return false; range.firstElement = statements[0]; @@ -163,14 +163,14 @@ final class StatementMover extends LineMover { } private static boolean statementCanBePlacedAlong(PsiElement element) { - if (element instanceof JspTemplateStatement) { + if (element instanceof IJspTemplateStatement) { PsiElement neighbour = element.getPrevSibling(); // we can place statement inside scriptlet only - return neighbour != null && !(neighbour instanceof JspTemplateStatement); + return neighbour != null && !(neighbour instanceof IJspTemplateStatement); } PsiElement parent = element.getParent(); if (element instanceof PsiBlockStatement && !(parent instanceof PsiCodeBlock)) return false; - if (parent instanceof JspClassLevelDeclarationStatement) 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())) { diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 118d0babb08c..0a1365daeacd 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1306,14 +1306,6 @@ implementationClass="com.intellij.codeInsight.intention.impl.lists.JavaSplitJoinParametersContext"/> - - - - diff --git a/java/jsp-openapi-no-deps/src/com/intellij/psi/jsp/IJspTemplateStatement.java b/java/jsp-openapi-no-deps/src/com/intellij/psi/jsp/IJspTemplateStatement.java new file mode 100644 index 000000000000..2baa70a4e4d6 --- /dev/null +++ b/java/jsp-openapi-no-deps/src/com/intellij/psi/jsp/IJspTemplateStatement.java @@ -0,0 +1,8 @@ +// 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.psi.jsp; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface IJspTemplateStatement { +} diff --git a/java/jsp-openapi/src/com/intellij/psi/impl/source/jsp/jspJava/JspTemplateStatement.java b/java/jsp-openapi/src/com/intellij/psi/impl/source/jsp/jspJava/JspTemplateStatement.java index 4aa2e9ba1794..1bae3b0cd488 100644 --- a/java/jsp-openapi/src/com/intellij/psi/impl/source/jsp/jspJava/JspTemplateStatement.java +++ b/java/jsp-openapi/src/com/intellij/psi/impl/source/jsp/jspJava/JspTemplateStatement.java @@ -16,6 +16,7 @@ package com.intellij.psi.impl.source.jsp.jspJava; import com.intellij.psi.PsiTemplateStatement; +import com.intellij.psi.jsp.IJspTemplateStatement; -public interface JspTemplateStatement extends PsiTemplateStatement { +public interface JspTemplateStatement extends PsiTemplateStatement, IJspTemplateStatement { } diff --git a/platform/core-api/src/com/intellij/psi/jsp/IJspTemplateStatement.java b/platform/core-api/src/com/intellij/psi/jsp/IJspTemplateStatement.java new file mode 100644 index 000000000000..2baa70a4e4d6 --- /dev/null +++ b/platform/core-api/src/com/intellij/psi/jsp/IJspTemplateStatement.java @@ -0,0 +1,8 @@ +// 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.psi.jsp; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface IJspTemplateStatement { +}