From bbd7a31690a28666cc2cb587fb30e8b594892e00 Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Wed, 20 May 2015 13:24:20 +0300 Subject: [PATCH] Provided way to reformat context around selected ranges, in order to make code look consistent. During "Wrapping Blocks" stage we collect all alignments from blocks which will be reformatted, during processing stage if we encounter read only block with such an alignment we drop it's read only status, so it could be realigned also. --- .../java/AbstractJavaFormatterTest.java | 10 ++- .../java/JavaFormatterAlignmentTest.java | 64 ++++++++++++++ .../psi/codeStyle/CodeStyleManager.java | 9 ++ .../intellij/formatting/FormatProcessor.java | 87 ++++++++++++++++--- .../com/intellij/formatting/FormatterEx.java | 6 ++ .../intellij/formatting/FormatterImpl.java | 16 +++- .../formatting/InitialInfoBuilder.java | 20 ++++- .../source/codeStyle/CodeFormatterFacade.java | 7 +- .../codeStyle/CodeStyleManagerImpl.java | 11 +++ .../actions/MockCodeStyleManager.java | 5 ++ 10 files changed, 214 insertions(+), 21 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/psi/formatter/java/AbstractJavaFormatterTest.java b/java/java-tests/testSrc/com/intellij/psi/formatter/java/AbstractJavaFormatterTest.java index 42ae8be5f958..6d13cd7412db 100644 --- a/java/java-tests/testSrc/com/intellij/psi/formatter/java/AbstractJavaFormatterTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/formatter/java/AbstractJavaFormatterTest.java @@ -35,6 +35,7 @@ import com.intellij.psi.codeStyle.*; import com.intellij.psi.codeStyle.autodetect.DetectableIndentOptionsProvider; import com.intellij.testFramework.LightIdeaTestCase; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.LineReader; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -83,7 +84,7 @@ public abstract class AbstractJavaFormatterTest extends LightIdeaTestCase { return result.toString(); } - protected enum Action {REFORMAT, INDENT} + protected enum Action {REFORMAT, INDENT, REFORMAT_WITH_CONTEXT} public static JavaCodeStyleSettings getJavaSettings() { return getSettings().getRootSettings().getCustomSettings(JavaCodeStyleSettings.class); @@ -107,6 +108,13 @@ public abstract class AbstractJavaFormatterTest extends LightIdeaTestCase { CodeStyleManager.getInstance(getProject()).adjustLineIndent(psiFile, startOffset); } }); + ACTIONS.put(Action.REFORMAT_WITH_CONTEXT, new TestFormatAction() { + @Override + public void run(PsiFile psiFile, int startOffset, int endOffset) { + List ranges = ContainerUtil.newArrayList(new TextRange(startOffset, endOffset)); + CodeStyleManager.getInstance(getProject()).reformatTextWithContext(psiFile, ranges); + } + }); } private static final String BASE_PATH = JavaTestUtil.getJavaTestDataPath() + "/psi/formatter/java"; 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 71c25289bb35..939d28a8fdee 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 @@ -16,6 +16,7 @@ package com.intellij.psi.formatter.java; import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.util.IncorrectOperationException; @@ -593,4 +594,67 @@ public class JavaFormatterAlignmentTest extends AbstractJavaFormatterTest { " String superString = \"\";\n" + "}"); } + + public void test_Shift_All_AlignedParameters() { + myLineRange = new TextRange(2, 2); + getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true; + doTextTest( + Action.REFORMAT_WITH_CONTEXT, + "public class Test {\n" + + " \n" + + " public void fooooo(String foo,\n" + + " String booo,\n" + + " String kakadoo) {\n" + + "\n" + + " }\n" + + "\n" + + "}", + + "public class Test {\n" + + "\n" + + " public void fooooo(String foo,\n" + + " String booo,\n" + + " String kakadoo) {\n" + + "\n" + + " }\n" + + "\n" + + "}" + ); + } + + public void test_Align_UnselectedField_IfNeeded() { + myLineRange = new TextRange(2, 2); + getSettings().ALIGN_GROUP_FIELD_DECLARATIONS = true; + doTextTest( + Action.REFORMAT_WITH_CONTEXT, + "public class Test {\n" + + " public int i = 1;\n" + + " public String iiiiiiiiii = 2;\n" + + "}", + "public class Test {\n" + + " public int i = 1;\n" + + " public String iiiiiiiiii = 2;\n" + + "}" + ); + } + + public void test_Align_UnselectedVariable_IfNeeded() { + myLineRange = new TextRange(3, 3); + getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true; + doTextTest( + Action.REFORMAT_WITH_CONTEXT, + "public class Test {\n" + + " public void test() {\n" + + " int s = 2;\n" + + " String sssss = 3;\n" + + " }\n" + + "}", + "public class Test {\n" + + " public void test() {\n" + + " int s = 2;\n" + + " String sssss = 3;\n" + + " }\n" + + "}" + ); + } } diff --git a/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java b/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java index e78f507aab5f..ef0a4b812406 100644 --- a/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java +++ b/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java @@ -145,6 +145,15 @@ public abstract class CodeStyleManager { */ public abstract void reformatText(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException; + + /** + * Works as #reformatText, but reformats not only specified ranges, but also some context around to make code look consistent + * @param file + * @param ranges + * @throws IncorrectOperationException + */ + public abstract void reformatTextWithContext(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException; + /** * Re-formats the specified range of a file, modifying only line indents and leaving * all other whitespace intact. diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java index 50a8e7ab1fc0..910308b76768 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java @@ -59,6 +59,8 @@ class FormatProcessor { private static final int BULK_REPLACE_OPTIMIZATION_CRITERIA = 3000; private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatProcessor"); + private Set myAlignmentsInsideRangesToModify = null; + private boolean myReformatContext; private LeafBlockWrapper myCurrentBlock; @@ -164,23 +166,21 @@ class FormatProcessor { @Nullable FormatTextRanges affectedRanges, @NotNull FormattingProgressCallback progressCallback) { - this(docModel, rootBlock, settings, indentOptions, affectedRanges, -1, progressCallback); + this(docModel, rootBlock, new FormatOptions(settings, indentOptions, affectedRanges, false), progressCallback); } - public FormatProcessor(final FormattingDocumentModel docModel, - Block rootBlock, - CodeStyleSettings settings, - CommonCodeStyleSettings.IndentOptions indentOptions, - @Nullable FormatTextRanges affectedRanges, - int interestingOffset, - @NotNull FormattingProgressCallback progressCallback) + public FormatProcessor(FormattingDocumentModel model, + Block block, + FormatOptions options, + @NotNull FormattingProgressCallback callback) { - myProgressCallback = progressCallback; - myDefaultIndentOption = indentOptions; - mySettings = settings; - myDocument = docModel.getDocument(); - myCurrentState = new WrapBlocksState(rootBlock, docModel, affectedRanges, interestingOffset); - myRightMargin = getRightMargin(rootBlock); + myProgressCallback = callback; + myDefaultIndentOption = options.myIndentOptions; + mySettings = options.mySettings; + myDocument = model.getDocument(); + myReformatContext = options.myReformatContext; + myCurrentState = new WrapBlocksState(block, model, options.myAffectedRanges, options.myInterestingOffset); + myRightMargin = getRightMargin(block); } private int getRightMargin(Block rootBlock) { @@ -474,6 +474,12 @@ class FormatProcessor { final SpacingImpl spaceProperty = myCurrentBlock.getSpaceProperty(); final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace(); + if (isReformatSelectedRangesContext()) { + if (isCurrentBlockAlignmentUsedInRangesToModify() && whiteSpace.isReadOnly()) { + whiteSpace.setReadOnly(false); + } + } + whiteSpace.arrangeLineFeeds(spaceProperty, this); if (!whiteSpace.containsLineFeeds()) { @@ -517,6 +523,27 @@ class FormatProcessor { myCurrentBlock = myCurrentBlock.getNextBlock(); } + private boolean isReformatSelectedRangesContext() { + return myReformatContext && myAlignmentsInsideRangesToModify != null; + } + + private boolean isCurrentBlockAlignmentUsedInRangesToModify() { + AbstractBlockWrapper block = myCurrentBlock; + AlignmentImpl alignment = myCurrentBlock.getAlignment(); + + while (alignment == null + && block != null + && block.getStartOffset() == myCurrentBlock.getStartOffset()) + { + block = block.getParent(); + if (block != null) { + alignment = block.getAlignment(); + } + } + + return myAlignmentsInsideRangesToModify.contains(alignment); + } + private boolean shouldReformatPreviouslyLocatedDependentSpacing(WhiteSpace space) { final TextRange changed = space.getTextRange(); final SortedMap sortedHeadMap = myPreviousDependencies.tailMap(changed); @@ -1339,6 +1366,7 @@ class FormatProcessor { myWrapper = InitialInfoBuilder.prepareToBuildBlocksSequentially( root, model, affectedRanges, mySettings, myDefaultIndentOption, interestingOffset, myProgressCallback ); + myWrapper.setCollectAlignmentsInsideFormattingRange(myReformatContext); } @Override @@ -1364,6 +1392,7 @@ class FormatProcessor { myTextRangeToWrapper = buildTextRangeToInfoMap(myFirstTokenBlock); myLastWhiteSpace = new WhiteSpace(getLastBlock().getEndOffset(), false); myLastWhiteSpace.append(myModel.getTextLength(), myModel, myDefaultIndentOption); + myAlignmentsInsideRangesToModify = myWrapper.getAlignmentsInsideRangeToModify(); } } @@ -1530,4 +1559,34 @@ class FormatProcessor { } } } + + + public static class FormatOptions { + private CodeStyleSettings mySettings; + private CommonCodeStyleSettings.IndentOptions myIndentOptions; + + private FormatTextRanges myAffectedRanges; + private boolean myReformatContext; + + private int myInterestingOffset; + + public FormatOptions(CodeStyleSettings settings, + CommonCodeStyleSettings.IndentOptions options, + FormatTextRanges ranges, + boolean reformatContext) { + this(settings, options, ranges, reformatContext, -1); + } + + public FormatOptions(CodeStyleSettings settings, + CommonCodeStyleSettings.IndentOptions options, + FormatTextRanges ranges, + boolean reformatContext, + int interestingOffset) { + mySettings = settings; + myIndentOptions = options; + myAffectedRanges = ranges; + myReformatContext = reformatContext; + myInterestingOffset = interestingOffset; + } + } } diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterEx.java b/platform/lang-impl/src/com/intellij/formatting/FormatterEx.java index f5417dfb5a25..c63084b525f2 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatterEx.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatterEx.java @@ -64,6 +64,12 @@ public abstract class FormatterEx{ CommonCodeStyleSettings.IndentOptions javaIndentOptions, FormatTextRanges affectedRanges) throws IncorrectOperationException; + public abstract void format(final FormattingModel model, + final CodeStyleSettings settings, + final CommonCodeStyleSettings.IndentOptions indentOptions, + final FormatTextRanges affectedRanges, + final boolean formatContextAroundRanges) throws IncorrectOperationException; + public abstract IndentInfo getWhiteSpaceBefore(final FormattingDocumentModel psiBasedFormattingModel, final Block block, diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java index cfef9c2d7b0a..3454c507dc7e 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java @@ -47,6 +47,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static com.intellij.formatting.FormatProcessor.FormatOptions; + public class FormatterImpl extends FormatterEx implements IndentFactory, WrapFactory, @@ -255,14 +257,23 @@ public class FormatterImpl extends FormatterEx final CodeStyleSettings settings, final CommonCodeStyleSettings.IndentOptions indentOptions, final FormatTextRanges affectedRanges) throws IncorrectOperationException { + format(model, settings, indentOptions, affectedRanges, false); + } + + public void format(final FormattingModel model, + final CodeStyleSettings settings, + final CommonCodeStyleSettings.IndentOptions indentOptions, + final FormatTextRanges affectedRanges, + final boolean formatContextAroundRanges) throws IncorrectOperationException { try { validateModel(model); SequentialTask task = new MyFormattingTask() { @NotNull @Override protected FormatProcessor buildProcessor() { + FormatOptions options = new FormatOptions(settings, indentOptions, affectedRanges, formatContextAroundRanges); FormatProcessor processor = new FormatProcessor( - model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, getProgressCallback() + model.getDocumentModel(), model.getRootBlock(), options, getProgressCallback() ); processor.format(model, true); return processor; @@ -522,8 +533,9 @@ public class FormatterImpl extends FormatterEx @Nullable FormatTextRanges affectedRanges, int interestingOffset) { + FormatOptions options = new FormatOptions(settings, indentOptions, affectedRanges, false, interestingOffset); FormatProcessor processor = new FormatProcessor( - docModel, rootBlock, settings, indentOptions, affectedRanges, interestingOffset, FormattingProgressCallback.EMPTY + docModel, rootBlock, options, FormattingProgressCallback.EMPTY ); while (!processor.iteration()) ; return processor; diff --git a/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java b/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java index 8b7b5fb5c019..0bbc4fbec4d9 100644 --- a/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java +++ b/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java @@ -26,6 +26,7 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.formatter.FormattingDocumentModelImpl; import com.intellij.psi.formatter.ReadOnlyBlockInformationProvider; import com.intellij.psi.impl.DebugUtil; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Stack; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; @@ -35,6 +36,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}. @@ -66,6 +68,9 @@ class InitialInfoBuilder { private static final boolean INLINE_TABS_ENABLED = "true".equalsIgnoreCase(System.getProperty("inline.tabs.enabled")); + private Set myAlignmentsInsideRangeToModify = ContainerUtil.newHashSet(); + private boolean myCollectAlignmentsInsideFormattingRange = false; + private InitialInfoBuilder(final FormattingDocumentModel model, @Nullable final FormatTextRanges affectedRanges, @NotNull CodeStyleSettings settings, @@ -163,16 +168,17 @@ class InitialInfoBuilder { } myCurrentWhiteSpace.append(blockStartOffset, myModel, myOptions); + boolean isReadOnly = isReadOnly(rootBlock, rootBlockIsRightBlock); + if (myCollectAlignmentsInsideFormattingRange && !isReadOnly && rootBlock.getAlignment() != null) { + myAlignmentsInsideRangeToModify.add(rootBlock.getAlignment()); + } ReadOnlyBlockInformationProvider previousProvider = myReadOnlyBlockInformationProvider; try { if (rootBlock instanceof ReadOnlyBlockInformationProvider) { myReadOnlyBlockInformationProvider = (ReadOnlyBlockInformationProvider)rootBlock; } - if (isReadOnly) { - return processSimpleBlock(rootBlock, parent, true, index, parentBlock); - } final List subBlocks = rootBlock.getSubBlocks(); if (subBlocks.isEmpty() || myReadOnlyBlockInformationProvider != null @@ -432,6 +438,14 @@ class InitialInfoBuilder { return langThrowable; } + public Set getAlignmentsInsideRangeToModify() { + return myAlignmentsInsideRangeToModify; + } + + public void setCollectAlignmentsInsideFormattingRange(boolean value) { + myCollectAlignmentsInsideFormattingRange = value; + } + /** * We want to wrap {@link Block code blocks} sequentially, hence, need to store a processing state and continue from the point * where we stopped the processing last time. diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java index 16cf2ef6271e..bcee3dbd0612 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java @@ -79,6 +79,7 @@ public class CodeFormatterFacade { private final CodeStyleSettings mySettings; private final FormatterTagHandler myTagHandler; private final int myRightMargin; + private boolean myReformatContext; public CodeFormatterFacade(CodeStyleSettings settings, @Nullable Language language) { mySettings = settings; @@ -86,6 +87,10 @@ public class CodeFormatterFacade { myRightMargin = mySettings.getRightMargin(language); } + public void setReformatContext(boolean value) { + myReformatContext = value; + } + public ASTNode processElement(ASTNode element) { TextRange range = element.getTextRange(); return processRange(element, range.getStartOffset(), range.getEndOffset()); @@ -240,7 +245,7 @@ public class CodeFormatterFacade { CommonCodeStyleSettings.IndentOptions indentOptions = mySettings.getIndentOptionsByFile(file, textRanges.size() == 1 ? textRanges.get(0).getTextRange() : null); - formatter.format(model, mySettings, indentOptions, ranges); + formatter.format(model, mySettings, indentOptions, ranges, myReformatContext); for (FormatTextRanges.FormatTextRange range : textRanges) { TextRange textRange = range.getTextRange(); wrapLongLinesIfNecessary(file, document, textRange.getStartOffset(), textRange.getEndOffset()); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java index 14b134cc0f70..529585953a88 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java @@ -164,7 +164,16 @@ public class CodeStyleManagerImpl extends CodeStyleManager { reformatText(file, ranges, null); } + @Override + public void reformatTextWithContext(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException { + reformatText(file, ranges, null, true); + } + public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges, @Nullable Editor editor) throws IncorrectOperationException { + reformatText(file, ranges, editor, false); + } + + public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges, @Nullable Editor editor, boolean reformatContext) throws IncorrectOperationException { if (ranges.isEmpty()) { return; } @@ -181,6 +190,8 @@ public class CodeStyleManagerImpl extends CodeStyleManager { transformAllChildren(treeElement); final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(), file.getLanguage()); + codeFormatter.setReformatContext(reformatContext); + LOG.assertTrue(file.isValid(), "File name: " + file.getName() + " , class: " + file.getClass().getSimpleName()); if (editor == null) { diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java index adaae703fef1..1b7b1c42826b 100644 --- a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/MockCodeStyleManager.java @@ -71,6 +71,11 @@ public class MockCodeStyleManager extends CodeStyleManager { myFormattedLinesForFile.put(file, formattedLines); } + @Override + public void reformatTextWithContext(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException { + throw new UnsupportedOperationException("com.intellij.codeInsight.actions.MockCodeStyleManager.reformatTextWithContext(...)"); + } + @NotNull @Override public Project getProject() {