diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java index f468208214c4..1d2e72216257 100644 --- a/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java +++ b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java @@ -1,8 +1,8 @@ public class Formatter { void foo() { String sss = """ - - - foo"""; + + + foo"""; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/tabIndenting/Tab2_after.java b/java/java-tests/testData/psi/formatter/tabIndenting/Tab2_after.java index bade7e7770f6..00625a78f22f 100644 --- a/java/java-tests/testData/psi/formatter/tabIndenting/Tab2_after.java +++ b/java/java-tests/testData/psi/formatter/tabIndenting/Tab2_after.java @@ -1,8 +1,9 @@ + class A { int a; void foo(int a, - int b) { + int b) { boolean b; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/tabIndenting/Tab4_after.java b/java/java-tests/testData/psi/formatter/tabIndenting/Tab4_after.java index 55db4cdaf704..4a32cfe1970e 100644 --- a/java/java-tests/testData/psi/formatter/tabIndenting/Tab4_after.java +++ b/java/java-tests/testData/psi/formatter/tabIndenting/Tab4_after.java @@ -1,8 +1,9 @@ + class A { int a; void foo(int a, - int b) { + int b) { boolean b; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/tabIndenting/Tab8_after.java b/java/java-tests/testData/psi/formatter/tabIndenting/Tab8_after.java index 6a943a395dbe..65886fc9a836 100644 --- a/java/java-tests/testData/psi/formatter/tabIndenting/Tab8_after.java +++ b/java/java-tests/testData/psi/formatter/tabIndenting/Tab8_after.java @@ -1,8 +1,9 @@ + class A { int a; void foo(int a, - int b) { + int b) { boolean b; } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment.java b/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment.java new file mode 100644 index 000000000000..5a54bda6d8a4 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment.java @@ -0,0 +1,8 @@ +public class A { + public void foo1(int i1, int i2, + int i3, int i4, + int i5, int i6, + int i7) { + + } +} diff --git a/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment_after.java b/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment_after.java new file mode 100644 index 000000000000..43d9d139101a --- /dev/null +++ b/java/java-tests/testData/psi/formatter/tabIndenting/methodAlignment_after.java @@ -0,0 +1,8 @@ +public class A { + public void foo1(int i1, int i2, + int i3, int i4, + int i5, int i6, + int i7) { + + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TabIndentingTest.java b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TabIndentingTest.java index bed9cc4a37b0..1a0ff65c2160 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TabIndentingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/TabIndentingTest.java @@ -1,22 +1,9 @@ -/* - * Copyright 2000-2017 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. - */ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.psi.formatter.java; import com.intellij.application.options.CodeStyle; import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.command.CommandProcessor; @@ -25,6 +12,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.testFramework.LightIdeaTestCase; import com.intellij.util.IncorrectOperationException; @@ -94,6 +82,15 @@ public class TabIndentingTest extends LightIdeaTestCase { doTest("moreTabsInComments.java", "moreTabsInComments_after.java"); } + public void testAlignmentWithTabs() throws Exception { + CodeStyleSettings settings = CodeStyle.getSettings(getProject()); + settings.getIndentOptions(JavaFileType.INSTANCE).USE_TAB_CHARACTER = true; + settings.getIndentOptions(JavaFileType.INSTANCE).TAB_SIZE = 4; + settings.getCommonSettings(JavaLanguage.INSTANCE).METHOD_PARAMETERS_WRAP = CommonCodeStyleSettings.WRAP_AS_NEEDED; + settings.getCommonSettings(JavaLanguage.INSTANCE).ALIGN_MULTILINE_PARAMETERS = true; + doTest("methodAlignment.java", "methodAlignment_after.java"); + } + private void doTest(String fileNameBefore, String fileNameAfter) throws Exception { String text = loadFile(fileNameBefore); final PsiFile file = createFile(fileNameBefore, text); diff --git a/platform/code-style-impl/resources/META-INF/CodeStyle.xml b/platform/code-style-impl/resources/META-INF/CodeStyle.xml index d53d2398a3bd..c81722ebb555 100644 --- a/platform/code-style-impl/resources/META-INF/CodeStyle.xml +++ b/platform/code-style-impl/resources/META-INF/CodeStyle.xml @@ -43,6 +43,10 @@ + + + + diff --git a/platform/code-style-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java b/platform/code-style-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java index c2f7635c9397..c81aba044885 100644 --- a/platform/code-style-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java +++ b/platform/code-style-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.formatting; import org.jetbrains.annotations.ApiStatus; @@ -70,8 +70,11 @@ public abstract class AbstractBlockAlignmentProcessor implements BlockAlignmentP WhiteSpace previousWhiteSpace = offsetResponsibleBlock.getWhiteSpace(); previousWhiteSpace.setSpaces(previousWhiteSpace.getSpaces() - diff, previousWhiteSpace.getIndentOffset()); - // Backward shift introduces alignment spaces. Keep those exempt from tab conversion. - previousWhiteSpace.setForceSkipTabulationsUsage(true); + if (!previousWhiteSpace.containsLineFeeds() || + LanguageAlignmentWhitespacePolicy.useSpacesForAlignment(offsetResponsibleBlock)) { + // Avoid tabulations usage for aligning blocks that are not the first blocks on a line. + previousWhiteSpace.setForceSkipTabulationsUsage(true); + } return Result.BACKWARD_BLOCK_ALIGNED; } diff --git a/platform/code-style-impl/src/com/intellij/formatting/AlignmentWhitespacePolicy.java b/platform/code-style-impl/src/com/intellij/formatting/AlignmentWhitespacePolicy.java new file mode 100644 index 000000000000..553f3352f08a --- /dev/null +++ b/platform/code-style-impl/src/com/intellij/formatting/AlignmentWhitespacePolicy.java @@ -0,0 +1,13 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.formatting; + +import org.jetbrains.annotations.ApiStatus; + +/** + * This policy allows enforcing using spaces for alignment that is necessary for some languages even if USE_TAB_CHARACTER option is enabled. + * @see WhiteSpace#setForceSkipTabulationsUsage(boolean) + */ +@ApiStatus.Internal +public interface AlignmentWhitespacePolicy { + boolean useSpacesForAlignment(); +} diff --git a/platform/code-style-impl/src/com/intellij/formatting/LanguageAlignmentWhitespacePolicy.java b/platform/code-style-impl/src/com/intellij/formatting/LanguageAlignmentWhitespacePolicy.java new file mode 100644 index 000000000000..39deb9e31798 --- /dev/null +++ b/platform/code-style-impl/src/com/intellij/formatting/LanguageAlignmentWhitespacePolicy.java @@ -0,0 +1,24 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.formatting; + +import com.intellij.lang.Language; +import com.intellij.lang.LanguageExtension; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public final class LanguageAlignmentWhitespacePolicy extends LanguageExtension { + private static final LanguageAlignmentWhitespacePolicy INSTANCE = new LanguageAlignmentWhitespacePolicy(); + + private LanguageAlignmentWhitespacePolicy() { + super("com.intellij.lang.formatter.alignmentWhitespacePolicy"); + } + + static boolean useSpacesForAlignment(@Nullable AbstractBlockWrapper block) { + if (block == null) return false; + Language language = block.getLanguage(); + if (language == null) return false; + AlignmentWhitespacePolicy policy = INSTANCE.forLanguage(language); + return policy != null && policy.useSpacesForAlignment(); + } +} diff --git a/platform/code-style-impl/src/com/intellij/formatting/LeftEdgeAlignmentProcessor.java b/platform/code-style-impl/src/com/intellij/formatting/LeftEdgeAlignmentProcessor.java index 3fc86e34bb43..e9ed66b692f5 100644 --- a/platform/code-style-impl/src/com/intellij/formatting/LeftEdgeAlignmentProcessor.java +++ b/platform/code-style-impl/src/com/intellij/formatting/LeftEdgeAlignmentProcessor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.formatting; import org.jetbrains.annotations.ApiStatus; @@ -44,7 +44,8 @@ public final class LeftEdgeAlignmentProcessor extends AbstractBlockAlignmentProc protected boolean applyIndentToTheFirstBlockOnLine(@NotNull IndentData alignmentAnchorIndent, @NotNull Context context) { WhiteSpace whiteSpace = context.targetBlock().getWhiteSpace(); whiteSpace.setSpaces(alignmentAnchorIndent.getSpaces(), alignmentAnchorIndent.getIndentSpaces()); - if (alignmentAnchorIndent.getSpaces() > 0) { + if (alignmentAnchorIndent.getSpaces() > 0 && + LanguageAlignmentWhitespacePolicy.useSpacesForAlignment(context.targetBlock())) { // Alignment spaces should not be converted into tabs whiteSpace.setForceSkipTabulationsUsage(true); } diff --git a/platform/code-style-impl/src/com/intellij/formatting/RightEdgeAlignmentProcessor.java b/platform/code-style-impl/src/com/intellij/formatting/RightEdgeAlignmentProcessor.java index e6996735a6d8..51688077401e 100644 --- a/platform/code-style-impl/src/com/intellij/formatting/RightEdgeAlignmentProcessor.java +++ b/platform/code-style-impl/src/com/intellij/formatting/RightEdgeAlignmentProcessor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.formatting; import org.jetbrains.annotations.ApiStatus; @@ -48,7 +48,7 @@ public final class RightEdgeAlignmentProcessor extends AbstractBlockAlignmentPro if (indentSpaces >= 0) { whiteSpace.setSpaces(spaces, indentSpaces); - if (spaces > 0) { + if (spaces > 0 && LanguageAlignmentWhitespacePolicy.useSpacesForAlignment(context.targetBlock())) { // Alignment spaces should not be converted into tabs whiteSpace.setForceSkipTabulationsUsage(true); } @@ -82,7 +82,7 @@ public final class RightEdgeAlignmentProcessor extends AbstractBlockAlignmentPro } } whiteSpace.setSpaces(spaces, indentSpaces); - if (spaces > 0) { + if (spaces > 0 && LanguageAlignmentWhitespacePolicy.useSpacesForAlignment(context.targetBlock())) { // Alignment spaces should not be converted into tabs whiteSpace.setForceSkipTabulationsUsage(true); } diff --git a/platform/code-style-impl/src/com/intellij/formatting/WhiteSpace.java b/platform/code-style-impl/src/com/intellij/formatting/WhiteSpace.java index 2e7381815678..70cbcf35ed94 100644 --- a/platform/code-style-impl/src/com/intellij/formatting/WhiteSpace.java +++ b/platform/code-style-impl/src/com/intellij/formatting/WhiteSpace.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-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.formatting; @@ -444,9 +444,7 @@ public final class WhiteSpace { * will be used for tab representation if there are non-white space symbols before it (IJ editor may use different number of columns * for single tabulation symbol representation). *

- * Hence, we can ask current white space object to avoid using tabulation symbols for alignment. - * Tab usage is not necessarily suppressed entirely: if this whitespace starts a new line, tabs may still be - * used for indentation while alignment remains spaces. + * Some languages may apply this option to disable the usage of tabulation during alignment. See {@link AlignmentWhitespacePolicy}. * * @param skip indicates if tabulation symbols usage should be suppressed for alignment */