From de17dfa24d11edccb3700a8dc7a594e3b0d57638 Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Thu, 15 Jan 2015 11:20:47 +0200 Subject: [PATCH] Added ability to align consecutive variables declaration (IDEA-56947) --- ...JavaLanguageCodeStyleSettingsProvider.java | 3 +- .../psi/formatter/java/AbstractJavaBlock.java | 25 ++- .../psi/formatter/java/SimpleJavaBlock.java | 6 +- .../java/SubsequentVariablesAligner.java | 66 +++++++ .../java/JavaFormatterAlignmentTest.java | 172 ++++++++++++++++++ .../CodeStyleSettingsCustomizable.java | 3 +- .../codeStyle/CommonCodeStyleSettings.java | 1 + .../codeStyle/WrappingAndBracesPanel.java | 1 + .../src/messages/ApplicationBundle.properties | 1 + 9 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 java/java-impl/src/com/intellij/psi/formatter/java/SubsequentVariablesAligner.java diff --git a/java/java-impl/src/com/intellij/ide/JavaLanguageCodeStyleSettingsProvider.java b/java/java-impl/src/com/intellij/ide/JavaLanguageCodeStyleSettingsProvider.java index a34f8c88623e..80bda49e697d 100644 --- a/java/java-impl/src/com/intellij/ide/JavaLanguageCodeStyleSettingsProvider.java +++ b/java/java-impl/src/com/intellij/ide/JavaLanguageCodeStyleSettingsProvider.java @@ -152,7 +152,8 @@ public class JavaLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSett "FINALLY_ON_NEW_LINE", "INDENT_CASE_FROM_SWITCH", "SPECIAL_ELSE_IF_TREATMENT", - "ENUM_CONSTANTS_WRAP"); + "ENUM_CONSTANTS_WRAP", + "ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS"); consumer.showCustomOption(JavaCodeStyleSettings.class, "ANNOTATION_PARAMETER_WRAP", diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java index b929654852f6..dc68a6619986 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java @@ -501,8 +501,10 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo else if (childType == JavaElementType.FIELD) { child = processField(result, child, alignmentStrategy, defaultWrap, childIndent); } - else if (childType == JavaElementType.LOCAL_VARIABLE || - childType == JavaElementType.DECLARATION_STATEMENT && myNode.getElementType() == JavaElementType.METHOD) { + else if (childType == JavaElementType.LOCAL_VARIABLE + || childType == JavaElementType.DECLARATION_STATEMENT + && (nodeType == JavaElementType.METHOD || nodeType == JavaElementType.CODE_BLOCK)) + { result.add(new SimpleJavaBlock(child, defaultWrap, alignmentStrategy, childIndent, mySettings, myJavaSettings)); } else { @@ -1137,10 +1139,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo processChild(localResult, child, AlignmentStrategy.getNullStrategy(), null, Indent.getNoneIndent()); child = child.getTreeNext(); - ChildAlignmentStrategyProvider alignmentStrategyProvider = ChildAlignmentStrategyProvider.NULL_STRATEGY_PROVIDER; - if (mySettings.ALIGN_GROUP_FIELD_DECLARATIONS) { - alignmentStrategyProvider = new SubsequentFieldAligner(mySettings); - } + ChildAlignmentStrategyProvider alignmentStrategyProvider = getStrategyProvider(); while (child != null) { if (FormatterUtil.containsWhiteSpacesOnly(child)) { @@ -1167,6 +1166,20 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo return null; } + private ChildAlignmentStrategyProvider getStrategyProvider() { + if (mySettings.ALIGN_GROUP_FIELD_DECLARATIONS && myNode.getElementType() == JavaElementType.CLASS) { + return new SubsequentFieldAligner(mySettings); + } + + ASTNode parent = myNode.getTreeParent(); + IElementType parentType = parent != null ? parent.getElementType() : null; + if (mySettings.ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS && parentType == JavaElementType.METHOD) { + return new SubsequentVariablesAligner(); + } + + return ChildAlignmentStrategyProvider.NULL_STRATEGY_PROVIDER; + } + private Indent getIndentForCodeBlock(ASTNode child, int childrenIndent) { if (child.getElementType() == JavaElementType.CODE_BLOCK && (getBraceStyle() == CommonCodeStyleSettings.NEXT_LINE_SHIFTED diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java index d53c81fb8433..3ccb2718d95b 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/SimpleJavaBlock.java @@ -87,11 +87,13 @@ public class SimpleJavaBlock extends AbstractJavaBlock { final ASTNode astNode = myCurrentChild; AlignmentStrategy alignmentStrategyToUse = AlignmentStrategy.wrap(chooseAlignment(myReservedAlignment, myReservedAlignment2, myCurrentChild)); - if (myNode.getElementType() == JavaElementType.FIELD) { + if (myNode.getElementType() == JavaElementType.FIELD + || myNode.getElementType() == JavaElementType.DECLARATION_STATEMENT + || myNode.getElementType() == JavaElementType.LOCAL_VARIABLE) + { alignmentStrategyToUse = myAlignmentStrategy; } - myCurrentChild = processChild(result, astNode, alignmentStrategyToUse, childWrap, myCurrentIndent, myCurrentOffset); if (astNode != myCurrentChild && myCurrentChild != null) { myCurrentOffset = myCurrentChild.getTextRange().getStartOffset(); diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/SubsequentVariablesAligner.java b/java/java-impl/src/com/intellij/psi/formatter/java/SubsequentVariablesAligner.java new file mode 100644 index 000000000000..a69f2bfa38c4 --- /dev/null +++ b/java/java-impl/src/com/intellij/psi/formatter/java/SubsequentVariablesAligner.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.psi.formatter.java; + +import com.intellij.formatting.alignment.AlignmentStrategy; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.JavaElementType; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public class SubsequentVariablesAligner implements ChildAlignmentStrategyProvider { + + private final static Set TYPES_TO_ALIGN = ContainerUtil.newHashSet( + JavaTokenType.IDENTIFIER, + JavaTokenType.EQ + ); + + private AlignmentStrategy myAlignmentStrategy; + + public SubsequentVariablesAligner() { + updateAlignmentStrategy(); + } + + private void updateAlignmentStrategy() { + myAlignmentStrategy = AlignmentStrategy.createAlignmentPerTypeStrategy(TYPES_TO_ALIGN, JavaElementType.LOCAL_VARIABLE, true); + } + + @Override + public AlignmentStrategy getNextChildStrategy(@NotNull ASTNode child) { + IElementType childType = child.getElementType(); + if (childType != JavaElementType.DECLARATION_STATEMENT || StringUtil.countNewLines(child.getChars()) > 0) { + updateAlignmentStrategy(); + return AlignmentStrategy.getNullStrategy(); + } + + if (isWhiteSpaceWithBlankLines(child.getTreePrev())) { + updateAlignmentStrategy(); + return myAlignmentStrategy; + } + + return myAlignmentStrategy; + } + + private static boolean isWhiteSpaceWithBlankLines(@Nullable ASTNode node) { + return node != null && node.getElementType() == TokenType.WHITE_SPACE && StringUtil.countNewLines(node.getChars()) > 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterAlignmentTest.java b/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterAlignmentTest.java index 915fc39b99db..71c25289bb35 100644 --- a/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterAlignmentTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterAlignmentTest.java @@ -421,4 +421,176 @@ public class JavaFormatterAlignmentTest extends AbstractJavaFormatterTest { ); } + public void testLocalVariablesAlignment() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doMethodTest( + "int a = 2;\n" + + "String myString = \"my string\"", + "int a = 2;\n" + + "String myString = \"my string\"" + ); + } + + public void testAlignOnlyDeclarationStatements() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doMethodTest( + " String s;\n" + + " int a = 2;\n" + + "s = \"abs\";\n" + + "long stamp = 12;", + "String s;\n" + + "int a = 2;\n" + + "s = \"abs\";\n" + + "long stamp = 12;" + ); + } + + public void testDoNotAlignWhenBlankLine() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doMethodTest( + "int a = 2;\n" + + "\n" + + "String myString = \"my string\"", + "int a = 2;\n" + + "\n" + + "String myString = \"my string\"" + ); + } + + public void testDoNotAlignWhenGroupInterrupted() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doMethodTest( + "int a = 2;\n" + + "System.out.println(\"hi!\")\n" + + "String myString = \"my string\"", + "int a = 2;\n" + + "System.out.println(\"hi!\")\n" + + "String myString = \"my string\"" + ); + } + + public void testDoNotAlignMultiDeclarations() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doMethodTest( + " int a, b = 2;\n" + + "String myString = \"my string\"", + "int a, b = 2;\n" + + "String myString = \"my string\"" + ); + } + + public void testDoNotAlignMultilineParams() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + + doMethodTest( + "int a = 12;\n" + + " Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};", + + "int a = 12;\n" + + "Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};" + ); + + doMethodTest( + " Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};\n" + + "int c = 12;", + + "Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};\n" + + "int c = 12;" + ); + + doMethodTest( + " int ac = 99;\n" + + "Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};\n" + + "int c = 12;", + + "int ac = 99;\n" + + "Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + "};\n" + + "int c = 12;" + ); + } + + public void testDoNotAlign_IfFirstMultiline() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + + doMethodTest( + "int\n" + + " i = 0;\n" + + "int[] a = new int[]{1, 2, 0x0052, 0x0053, 0x0054};\n" + + "int var1 = 1;\n" + + "int var2 = 2;", + + "int\n" + + " i = 0;\n" + + "int[] a = new int[]{1, 2, 0x0052, 0x0053, 0x0054};\n" + + "int var1 = 1;\n" + + "int var2 = 2;" + ); + } + + public void testAlign_InMethod() { + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doClassTest( + "public void run() {\n" + + "\n" + + " int a = 2;\n" + + " String superString = \"\";\n" + + "\n" + + " test(call1(), call2(), call3());\n" + + " }", + + "public void run() {\n" + + "\n" + + " int a = 2;\n" + + " String superString = \"\";\n" + + "\n" + + " test(call1(), call2(), call3());\n" + + "}" + ); + + doClassTest( + "public void run() {\n" + + "\n" + + " test(call1(), call2(), call3());\n" + + "\n" + + " int a = 2;\n" + + " String superString = \"\";\n" + + "}", + "public void run() {\n" + + "\n" + + " test(call1(), call2(), call3());\n" + + "\n" + + " int a = 2;\n" + + " String superString = \"\";\n" + + "}"); + } } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsCustomizable.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsCustomizable.java index 2ef2c998b3b4..aa2fad9c3af6 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsCustomizable.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsCustomizable.java @@ -193,7 +193,8 @@ public interface CodeStyleSettingsCustomizable { FINALLY_ON_NEW_LINE, INDENT_CASE_FROM_SWITCH, SPECIAL_ELSE_IF_TREATMENT, - ENUM_CONSTANTS_WRAP + ENUM_CONSTANTS_WRAP, + ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java index 6b8b9ed34617..1173e332182f 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java @@ -452,6 +452,7 @@ public class CommonCodeStyleSettings { * int end = 10; */ public boolean ALIGN_GROUP_FIELD_DECLARATIONS = false; + public boolean ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = false; //----------------- SPACES -------------------- diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java index 2061f1c9f550..749a0038e048 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java @@ -111,6 +111,7 @@ public class WrappingAndBracesPanel extends OptionTableWithPreviewPanel { addOption("ASSIGNMENT_WRAP", WRAPPING_ASSIGNMENT, WRAP_OPTIONS, WRAP_VALUES); addOption("ALIGN_MULTILINE_ASSIGNMENT", ApplicationBundle.message("wrapping.align.when.multiline"), WRAPPING_ASSIGNMENT); addOption("PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE", ApplicationBundle.message("wrapping.assignment.sign.on.next.line"), WRAPPING_ASSIGNMENT); + addOption("ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS", ApplicationBundle.message("wrapping.assignment.align.consecutive.variable.declarations"), WRAPPING_ASSIGNMENT); addOption("TERNARY_OPERATION_WRAP", WRAPPING_TERNARY_OPERATION, WRAP_OPTIONS, WRAP_VALUES); addOption("ALIGN_MULTILINE_TERNARY_OPERATION", ApplicationBundle.message("wrapping.align.when.multiline"), WRAPPING_TERNARY_OPERATION); diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index d3c949e05c83..2212fb98e3f6 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -204,6 +204,7 @@ wrapping.take.priority.over.call.chain.wrapping=Take priority over call chain wr wrapping.operation.sign.on.next.line=Operation sign on next line wrapping.comma.on.next.line=',' on next line wrapping.assignment.sign.on.next.line=Assignment sign on next line +wrapping.assignment.align.consecutive.variable.declarations=Align consecutive variable declarations wrapping.quest.and.colon.signs.on.next.line='?' and ':' signs on next line wrapping.new.line.after.lbrace=New line after '{' wrapping.rbrace.on.new.line=Place '}' on new line