From a87cef51de877887da9028660d6442ad543e490c Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Fri, 10 May 2024 13:38:40 +0200 Subject: [PATCH] [Java. Code Formatting] Add support to align last line in text block if there is no symbols IDEA-351758 GitOrigin-RevId: fca446697ac8eb5f1c24fbb3a1bf72924a48a65a --- java/java-impl/src/META-INF/JavaPlugin.xml | 2 +- .../psi/formatter/java/JavaFormatterUtil.java | 111 ++++++++++++------ .../psi/formatter/java/TextBlockBlock.java | 3 +- ...nWhitespaceLineInMiddleAfterAlignment.java | 2 +- ...spaceLineInMiddleAfterAlignment_after.java | 2 +- ...paceLineInMiddleBeforeAlignment_after.java | 2 +- .../java/textBlock/EmptyLastLine.java | 7 ++ .../java/textBlock/EmptyLastLine_after.java | 7 ++ .../LastLineWithSymbolsAfterAlignment.java | 7 ++ ...stLineWithSymbolsAfterAlignment_after.java | 7 ++ .../LastLineWithSymbolsBeforeAlignment.java | 7 ++ ...tLineWithSymbolsBeforeAlignment_after.java | 7 ++ .../LastLineWithoutSymbolsAfterAlignment.java | 7 ++ ...ineWithoutSymbolsAfterAlignment_after.java | 7 ++ ...LastLineWithoutSymbolsBeforeAlignment.java | 7 ++ ...neWithoutSymbolsBeforeAlignment_after.java | 7 ++ ...kt => TextBlockBlankLinesFormatterTest.kt} | 22 +++- 17 files changed, 168 insertions(+), 46 deletions(-) create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine_after.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment_after.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment_after.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment_after.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment.java create mode 100644 java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment_after.java rename java/java-tests/testSrc/com/intellij/java/psi/formatter/java/{AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt => TextBlockBlankLinesFormatterTest.kt} (87%) diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index e0d840ee5bfb..cfea29dbe29e 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1322,7 +1322,7 @@ - + diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java index c3982f0e0334..241a3e90cda4 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java @@ -19,7 +19,6 @@ import com.intellij.psi.impl.source.tree.ElementType; import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; -import com.intellij.psi.util.PsiLiteralUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -57,7 +56,7 @@ public final class JavaFormatterUtil { * @param node1 node to check * @param node2 node to check * @return {@code true} if given nodes are binary expressions and have the same priority; - * {@code false} otherwise + * {@code false} otherwise */ public static boolean areSamePriorityBinaryExpressions(ASTNode node1, ASTNode node2) { if (node1 == null || node2 == null) { @@ -83,7 +82,7 @@ public final class JavaFormatterUtil { * if there's a line break after it and "keep line breaks" is on. * * @param settings The current settings - * @param node The node to check. + * @param node The node to check. * @return True for call chunk start. */ static boolean isStartOfCallChunk(@NotNull CommonCodeStyleSettings settings, @NotNull ASTNode node) { @@ -127,9 +126,9 @@ public final class JavaFormatterUtil { * @return wrap to use for the sub-blocks of the given block */ static @Nullable Wrap createDefaultWrap(ASTBlock block, - CommonCodeStyleSettings settings, - JavaCodeStyleSettings javaSettings, - ReservedWrapsProvider reservedWrapsProvider) { + CommonCodeStyleSettings settings, + JavaCodeStyleSettings javaSettings, + ReservedWrapsProvider reservedWrapsProvider) { ASTNode node = block.getNode(); Wrap wrap = block.getWrap(); if (node == null) return null; @@ -211,11 +210,11 @@ public final class JavaFormatterUtil { * {@code null} otherwise */ static @Nullable Wrap arrangeChildWrap(ASTNode child, - ASTNode parent, - CommonCodeStyleSettings settings, - JavaCodeStyleSettings javaSettings, - Wrap suggestedWrap, - AbstractJavaBlock reservedWrapsProvider) { + ASTNode parent, + CommonCodeStyleSettings settings, + JavaCodeStyleSettings javaSettings, + Wrap suggestedWrap, + AbstractJavaBlock reservedWrapsProvider) { ASTNode directParent = child.getTreeParent(); int role = ((CompositeElement)directParent).getChildRole(child); @@ -291,7 +290,8 @@ public final class JavaFormatterUtil { ASTNode last = prev.getLastChildNode(); if (last != null && last.getElementType() == JavaElementType.ANNOTATION) { if (javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION && isModifierListWithSingleAnnotation(prev, JavaElementType.FIELD) || - javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION_IN_PARAMETER && isModifierListWithSingleAnnotation(prev, JavaElementType.PARAMETER) || + javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION_IN_PARAMETER && + isModifierListWithSingleAnnotation(prev, JavaElementType.PARAMETER) || isAnnotationAfterKeyword(last) ) { return Wrap.createWrap(WrapType.NONE, false); @@ -456,7 +456,10 @@ public final class JavaFormatterUtil { return false; } - private static int getAnnotationWrapType(ASTNode parent, ASTNode child, CommonCodeStyleSettings settings, JavaCodeStyleSettings javaSettings) { + private static int getAnnotationWrapType(ASTNode parent, + ASTNode child, + CommonCodeStyleSettings settings, + JavaCodeStyleSettings javaSettings) { IElementType nodeType = parent.getElementType(); if (nodeType == JavaElementType.METHOD) { @@ -528,9 +531,9 @@ public final class JavaFormatterUtil { * Traverses the children of the node and collects nodes with type method calls or reference expressions to the list. * If the quantity of the call expressions is greater than {@link JavaFormatterUtil#CALL_EXPRESSION_DEPTH}, call expressions will not be * collected, and you should not format them. - * @param nodes List in which the method add nodes - * @param node Node to traverse * + * @param nodes List in which the method add nodes + * @param node Node to traverse */ public static void collectCallExpressionNodes(@NotNull List nodes, @NotNull ASTNode node) { ArrayDeque stack = new ArrayDeque<>(CALL_EXPRESSION_DEPTH); @@ -541,51 +544,81 @@ public final class JavaFormatterUtil { return; } ASTNode currentNode = stack.removeLast(); - if (!FormatterUtil.containsWhiteSpacesOnly(currentNode)) { - IElementType type = currentNode.getElementType(); - if (type == JavaElementType.METHOD_CALL_EXPRESSION || - type == JavaElementType.REFERENCE_EXPRESSION) { - ASTNode firstChild = currentNode.getFirstChildNode(); - currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode); - ContainerUtil.addIfNotNull(stack, currentNode); - ContainerUtil.addIfNotNull(stack, firstChild); - } - else { - nodes.add(currentNode); - currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode); - ContainerUtil.addIfNotNull(stack, currentNode); - } - } else { + if (!FormatterUtil.containsWhiteSpacesOnly(currentNode)) { + IElementType type = currentNode.getElementType(); + if (type == JavaElementType.METHOD_CALL_EXPRESSION || + type == JavaElementType.REFERENCE_EXPRESSION) { + ASTNode firstChild = currentNode.getFirstChildNode(); + currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode); + ContainerUtil.addIfNotNull(stack, currentNode); + ContainerUtil.addIfNotNull(stack, firstChild); + } + else { + nodes.add(currentNode); currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode); ContainerUtil.addIfNotNull(stack, currentNode); } + } + else { + currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode); + ContainerUtil.addIfNotNull(stack, currentNode); + } } } /** * Extracts text ranges corresponding to the lines in a given literal multiline text. * - * @param text the literal text to extract text ranges from - * @param indent the number of spaces used for indentation - * @param shouldTreatWholeLine specifies whether the entire line should be treated as a text range or + * @param text the literal text to extract text ranges from + * @param indent the number of spaces used for indentation * @return a list of {@code TextRange} objects representing the extracted text ranges */ - public static @NotNull List extractTextRangesFromLiteralText(@NotNull String text, int indent, boolean shouldTreatWholeLine) { + public static @NotNull List extractTextRangesFromLiteralText(@NotNull String text, int indent) { List linesRanges = new ArrayList<>(); - + boolean isLastLine = false; int start = StringUtil.indexOf(text, '\n', 3); if (start == -1) return Collections.emptyList(); linesRanges.add(new TextRange(0, start)); start += 1; - while (start < text.length()) { int end = StringUtil.indexOf(text, '\n', start); - if (end == -1) end = text.length(); - if (start + indent < end && !shouldTreatWholeLine) start += indent; - if (start != end || shouldTreatWholeLine) linesRanges.add(new TextRange(start, end)); + if (end == -1) { + isLastLine = true; + end = text.length(); + } + if (start + indent <= end) { + int quoteStartIndex = end - 3; + if (!isLastLine && allEmpty(start + indent, end, text)) { + // todo here we can delete and the last \s\s\s""" + start = end; + } + else if (isLastLine && allEmpty(start + indent, quoteStartIndex, text) && isEndsWithTripleQuote(quoteStartIndex, end, text)) { + start = quoteStartIndex; + } + else { + start += indent; + } + } + else { + start = end; + } + linesRanges.add(new TextRange(start, end)); start = end + 1; } return linesRanges; } + + private static boolean isEndsWithTripleQuote(int start, int end, @NotNull String text) { + if (end - start != 3 || start < 0) return false; + String tripleQuote = text.substring(start, end); + return tripleQuote.equals("\"\"\""); + } + + private static boolean allEmpty(int i, int end, @NotNull String text) { + for (int j = i; j < end; j++) { + if(!Character.isWhitespace(text.charAt(j))) return false; + } + return true; + } } diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/TextBlockBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/TextBlockBlock.java index ff90fc11c92b..7b2ee25c908e 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/TextBlockBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/TextBlockBlock.java @@ -5,7 +5,6 @@ import com.intellij.formatting.*; import com.intellij.formatting.alignment.AlignmentStrategy; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.JavaCodeStyleSettings; @@ -57,7 +56,7 @@ public class TextBlockBlock extends AbstractJavaBlock { if (indent == -1) return Collections.emptyList(); String text = literal.getText(); - return extractTextRangesFromLiteralText(text, indent, false); + return extractTextRangesFromLiteralText(text, indent); } @Override diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment.java b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment.java index 408a18a3a7a6..dcc2eb241f51 100644 --- a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment.java +++ b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment.java @@ -1,7 +1,7 @@ public class Formatter { void foo() { String s2 = """ - + """; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment_after.java index 28f17f6f3a08..aeb4d2057e51 100644 --- a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment_after.java +++ b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleAfterAlignment_after.java @@ -1,7 +1,7 @@ public class Formatter { void foo() { String s2 = """ - + """; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleBeforeAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleBeforeAlignment_after.java index 28f17f6f3a08..80a8ada3a618 100644 --- a/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleBeforeAlignment_after.java +++ b/java/java-tests/testData/psi/formatter/java/textBlock/CaretInWhitespaceLineInMiddleBeforeAlignment_after.java @@ -1,7 +1,7 @@ public class Formatter { void foo() { String s2 = """ - + """; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine.java b/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine.java new file mode 100644 index 000000000000..b8fe8fbca1da --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a +"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine_after.java new file mode 100644 index 000000000000..6ef8d2018247 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/EmptyLastLine_after.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + """; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment.java new file mode 100644 index 000000000000..5cecda210339 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + d d ds \t"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment_after.java new file mode 100644 index 000000000000..5cecda210339 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsAfterAlignment_after.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + d d ds \t"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment.java new file mode 100644 index 000000000000..e61bc895b6ce --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + d d ds \t"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment_after.java new file mode 100644 index 000000000000..0a23c2ce74bd --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithSymbolsBeforeAlignment_after.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + d d ds \t"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment.java new file mode 100644 index 000000000000..3b7e93b05d69 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + """; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment_after.java new file mode 100644 index 000000000000..0f28cccd4286 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsAfterAlignment_after.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + """; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment.java new file mode 100644 index 000000000000..37bfd120b871 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + """; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment_after.java new file mode 100644 index 000000000000..336a9a3ea057 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/LastLineWithoutSymbolsBeforeAlignment_after.java @@ -0,0 +1,7 @@ +public class Formatter { + void foo() { + String s2 = """ + a + """; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TextBlockBlankLinesFormatterTest.kt similarity index 87% rename from java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt rename to java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TextBlockBlankLinesFormatterTest.kt index d7417956eb7d..ac0875e364a1 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TextBlockBlankLinesFormatterTest.kt @@ -9,7 +9,7 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings import com.intellij.psi.codeStyle.JavaCodeStyleSettings import com.intellij.testFramework.LightPlatformCodeInsightTestCase -class AdjustWhitespaceLineTextBlockReformatPostProcessorTest : LightPlatformCodeInsightTestCase() { +class TextBlockBlankLinesFormatterTest : LightPlatformCodeInsightTestCase() { override fun getTestDataPath(): String = "${JavaTestUtil.getJavaTestDataPath()}/psi/formatter/java/textBlock/" fun testWhitespacesLessThanAlignment() = doTest() @@ -71,6 +71,26 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessorTest : LightPlatformCode doTest() } + fun testLastLineWithoutSymbolsAfterAlignment() { + doTest() + } + + fun testLastLineWithSymbolsAfterAlignment() { + doTest() + } + + fun testLastLineWithoutSymbolsBeforeAlignment() { + doTest() + } + + fun testLastLineWithSymbolsBeforeAlignment() { + doTest() + } + + fun testEmptyLastLine() { + doTest() + } + private fun getCommonSettings(): CommonCodeStyleSettings = currentCodeStyleSettings.getCommonSettings(JavaLanguage.INSTANCE)