From a6c8c3127219fba260d88988e5f0d7437d92aad4 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 31 Aug 2012 09:32:04 +0400 Subject: [PATCH] IDEA-19061 Integrate the Rearranger-plugin into core-IDE 1. Making arrangement blank lines aware; 2. Refactoring; --- .../JavaElementArrangementEntry.java | 7 + .../codeStyle/arrangement/JavaRearranger.java | 33 +++ .../AbstractJavaRearrangerTest.groovy | 31 +++ .../JavaRearrangerBlankLinesTest.groovy | 83 +++++++ ...JavaRearrangerByTypeAndModifierTest.groovy | 33 +-- .../JavaRearrangerByTypeTest.groovy | 25 +- .../psi/codeStyle/arrangement/Rearranger.java | 13 ++ .../arrangement/engine/ArrangementEngine.java | 216 ++++++++++++++---- .../arrangement/AbstractRearrangerTest.groovy | 32 +-- 9 files changed, 372 insertions(+), 101 deletions(-) create mode 100644 java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractJavaRearrangerTest.groovy create mode 100644 java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerBlankLinesTest.groovy diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaElementArrangementEntry.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaElementArrangementEntry.java index cc6bfba8f20a..ebb29bf6868d 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaElementArrangementEntry.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaElementArrangementEntry.java @@ -36,6 +36,7 @@ public class JavaElementArrangementEntry extends DefaultArrangementEntry private final Set myModifiers = EnumSet.noneOf(ArrangementModifier.class); @NotNull private final Set myTypes; + @NotNull private final ArrangementEntryType myType; @Nullable private final String myName; public JavaElementArrangementEntry(@Nullable ArrangementEntry parent, @@ -55,6 +56,7 @@ public class JavaElementArrangementEntry extends DefaultArrangementEntry boolean canBeArranged) { super(parent, startOffset, endOffset, canBeArranged); + myType = type; myTypes = EnumSet.of(type); myName = name; } @@ -81,6 +83,11 @@ public class JavaElementArrangementEntry extends DefaultArrangementEntry return myTypes; } + @NotNull + public ArrangementEntryType getType() { + return myType; + } + @Override public String toString() { return String.format( diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java index e24f27fb89a3..b3eddb8ab732 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java @@ -15,10 +15,13 @@ */ package com.intellij.psi.codeStyle.arrangement; +import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; import com.intellij.psi.codeStyle.arrangement.match.StdArrangementEntryMatcher; @@ -123,6 +126,36 @@ public class JavaRearranger implements Rearranger, return result; } + @Override + public int getBlankLines(@NotNull CodeStyleSettings settings, + @Nullable JavaElementArrangementEntry parent, + @Nullable JavaElementArrangementEntry previous, + @NotNull JavaElementArrangementEntry target) + { + if (previous == null) { + return 0; + } + + CommonCodeStyleSettings commonSettings = settings.getCommonSettings(JavaLanguage.INSTANCE); + switch (target.getType()) { + case FIELD: + if (parent != null && parent.getType() == INTERFACE) { + return commonSettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE; + } + else { + return commonSettings.BLANK_LINES_AROUND_FIELD; + } + case METHOD: + if (parent != null && parent.getType() == INTERFACE) { + return commonSettings.BLANK_LINES_AROUND_METHOD_IN_INTERFACE; + } + else { + return commonSettings.BLANK_LINES_AROUND_METHOD; + } + default: return 0; + } + } + @Override public boolean isEnabled(@NotNull ArrangementEntryType type, @Nullable ArrangementMatchCondition current) { return SUPPORTED_TYPES.contains(type); diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractJavaRearrangerTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractJavaRearrangerTest.groovy new file mode 100644 index 000000000000..7f6c641dc520 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractJavaRearrangerTest.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2012 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.codeStyle.arrangement + +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.java.JavaLanguage + +/** + * @author Denis Zhdanov + * @since 8/30/12 12:15 PM + */ +abstract class AbstractJavaRearrangerTest extends AbstractRearrangerTest { + + AbstractJavaRearrangerTest() { + fileType = JavaFileType.INSTANCE + language = JavaLanguage.INSTANCE + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerBlankLinesTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerBlankLinesTest.groovy new file mode 100644 index 000000000000..3bad9387ba78 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerBlankLinesTest.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2012 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.codeStyle.arrangement + +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.* +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.PUBLIC +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.STATIC +/** + * @author Denis Zhdanov + * @since 8/30/12 12:14 PM + */ +public class JavaRearrangerBlankLinesTest extends AbstractJavaRearrangerTest { + + void testPreserveRelativeBlankLines() { + commonSettings.BLANK_LINES_AROUND_FIELD = 1 + commonSettings.BLANK_LINES_AROUND_METHOD = 2 + commonSettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE = 2 + commonSettings.BLANK_LINES_AROUND_METHOD_IN_INTERFACE = 3 + doTest( + '''\ +class Test { + private void method1() {} + + public void method2() {} + + private int i; + + public int j; + public static int k; +} +interface MyInterface { + void test1(); + void test2(); + int i = 0; + int j = 0; +}''', + '''\ +interface MyInterface { + int i = 0; + + + int j = 0; + + + + void test1(); + + + + void test2(); +} +class Test { + public static int k; + public int j; + private int i; + + public void method2() {} + + private void method1() {} +}''', + [rule(INTERFACE), + rule(CLASS), + rule(FIELD, STATIC), + rule(FIELD, PUBLIC), + rule(FIELD), + rule(METHOD, PUBLIC), + rule(METHOD)] + ) + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeAndModifierTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeAndModifierTest.groovy index 9748953f0774..3a8655521b49 100644 --- a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeAndModifierTest.groovy +++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeAndModifierTest.groovy @@ -15,22 +15,13 @@ */ package com.intellij.psi.codeStyle.arrangement -import com.intellij.ide.highlighter.JavaFileType -import com.intellij.lang.java.JavaLanguage - -import static com.intellij.psi.codeStyle.arrangement.ArrangementUtil.and import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.* import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.* /** * @author Denis Zhdanov * @since 8/28/12 6:42 PM */ -class JavaRearrangerByTypeAndModifierTest extends AbstractRearrangerTest { - - JavaRearrangerByTypeAndModifierTest() { - fileType = JavaFileType.INSTANCE - language = JavaLanguage.INSTANCE - } +class JavaRearrangerByTypeAndModifierTest extends AbstractJavaRearrangerTest { void testComplex() { doTest( @@ -62,17 +53,17 @@ class Test { public class PublicInner {} protected static class ProtectedStaticInner {} }''', - [rule(and(atom(FIELD), atom(PUBLIC), atom(STATIC))), - rule(and(atom(FIELD), atom(PUBLIC))), - rule(and(atom(FIELD), atom(VOLATILE))), - rule(and(atom(FIELD), atom(PRIVATE))), - rule(and(atom(METHOD), atom(ABSTRACT))), - rule(and(atom(METHOD), atom(PUBLIC))), - rule(atom(METHOD)), - rule(atom(INTERFACE)), - rule(atom(ENUM)), - rule(and(atom(CLASS), atom(PUBLIC))), - rule(atom(CLASS))] + [rule(FIELD, PUBLIC, STATIC), + rule(FIELD, PUBLIC), + rule(FIELD, VOLATILE), + rule(FIELD, PRIVATE), + rule(METHOD, ABSTRACT), + rule(METHOD, PUBLIC), + rule(METHOD), + rule(INTERFACE), + rule(ENUM), + rule(CLASS, PUBLIC), + rule(CLASS)] ) } } diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeTest.groovy index 06066ba24a45..21b6ed68dd22 100644 --- a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeTest.groovy +++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerByTypeTest.groovy @@ -15,19 +15,12 @@ */ package com.intellij.psi.codeStyle.arrangement -import com.intellij.ide.highlighter.JavaFileType -import com.intellij.lang.java.JavaLanguage -import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.* /** * @author Denis Zhdanov * @since 7/20/12 2:45 PM */ -class JavaRearrangerByTypeTest extends AbstractRearrangerTest { - - JavaRearrangerByTypeTest() { - fileType = JavaFileType.INSTANCE - language = JavaLanguage.INSTANCE - } +class JavaRearrangerByTypeTest extends AbstractJavaRearrangerTest { void testFieldsBeforeMethods() { doTest( @@ -53,7 +46,7 @@ class Test2 { public void test() { } }''', - [rule(atom(ArrangementEntryType.FIELD))] + [rule(FIELD)] ) } @@ -97,7 +90,7 @@ class Test { return null; } }''', - [rule(atom(ArrangementEntryType.FIELD))] + [rule(FIELD)] ) } @@ -135,7 +128,7 @@ class Test { }); } }''', - [rule(atom(ArrangementEntryType.FIELD))] + [rule(FIELD)] ) } @@ -153,9 +146,9 @@ class Test { enum E { ONE, TWO } class Inner {} }''', - [rule(atom(ArrangementEntryType.INTERFACE)), - rule(atom(ArrangementEntryType.ENUM)), - rule(atom(ArrangementEntryType.CLASS))] + [rule(INTERFACE), + rule(ENUM), + rule(CLASS)] ) } @@ -193,7 +186,7 @@ class Test { }); } }''', - [rule(atom(ArrangementEntryType.FIELD))] + [rule(FIELD)] ) } } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java index 617491fcf2bf..ba50970e6864 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java @@ -19,7 +19,9 @@ import com.intellij.lang.LanguageExtension; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.CodeStyleSettings; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -46,4 +48,15 @@ public interface Rearranger { */ @NotNull Collection parse(@NotNull PsiElement root, @NotNull Document document, @NotNull Collection ranges); + + /** + * Allows to answer how many blank lines should be inserted before the target arrangement entry which position is changed. + * + * @param settings code style settings to use (it's assumed that returned result is derived from 'blank lines' code style settings) + * @param parent target entry's parent (if available) + * @param previous previous entry (if available) + * @param target target entry which blank lines number the caller is interested in + * @return number of blank lines to insert before the target entry + */ + int getBlankLines(@NotNull CodeStyleSettings settings, @Nullable E parent, @Nullable E previous, @NotNull E target); } diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java index 6d1ef77ff3b5..ad0d55604562 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java @@ -18,9 +18,10 @@ package com.intellij.psi.codeStyle.arrangement.engine; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; @@ -30,7 +31,11 @@ import com.intellij.psi.codeStyle.arrangement.Rearranger; import com.intellij.psi.codeStyle.arrangement.StdArrangementRule; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware; import com.intellij.util.containers.Stack; +import com.intellij.util.text.CharArrayUtil; +import gnu.trove.TIntArrayList; +import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -53,13 +58,12 @@ public class ArrangementEngine { return; } - Rearranger rearranger = Rearranger.EXTENSION.forLanguage(file.getLanguage()); + final Rearranger rearranger = Rearranger.EXTENSION.forLanguage(file.getLanguage()); if (rearranger == null) { return; } - CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(file.getProject()).getCurrentSettings(); - final Ref> rulesRef = new Ref>(); + final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(file.getProject()).getCurrentSettings(); List arrangementRules = settings.getCommonSettings(file.getLanguage()).getArrangementRules(); if (arrangementRules.isEmpty() && rearranger instanceof ArrangementStandardSettingsAware) { List defaultRules = ((ArrangementStandardSettingsAware)rearranger).getDefaultRules(); @@ -70,11 +74,7 @@ public class ArrangementEngine { if (arrangementRules.isEmpty()) { return; } - else { - rulesRef.set(arrangementRules); - } - final Collection entriesToProcess = rearranger.parse(file, document, ranges); final DocumentEx documentEx; if (document instanceof DocumentEx && !((DocumentEx)document).isInBulkUpdate()) { documentEx = (DocumentEx)document; @@ -83,6 +83,8 @@ public class ArrangementEngine { documentEx = null; } + final Context context = Context.from(rearranger, document, file, ranges, arrangementRules, settings); + ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { @@ -90,7 +92,7 @@ public class ArrangementEngine { documentEx.setInBulkUpdate(true); } try { - doArrange(document, rulesRef.get(), entriesToProcess); + doArrange(context); } finally { if (documentEx != null) { @@ -101,10 +103,8 @@ public class ArrangementEngine { }); } - private static void doArrange(@NotNull final Document document, - @NotNull List arrangementRules, - @NotNull Collection entriesToProcess) - { + @SuppressWarnings("unchecked") + private static void doArrange(Context context) { // The general idea is to process entries bottom-up where every processed group belongs to the same parent. We may not bother // with entries text ranges then. We use a list and a stack for achieving that than. // @@ -115,8 +115,9 @@ public class ArrangementEngine { // // -------------------------- // Stage 1: - // list: Entry1 Entry2 - // stack: [0, 0, 2] + // list: Entry1 Entry2 <-- entries to process + // stack: [0, 0, 2] <-- holds current iteration info at the following format: + // (start entry index at the auxiliary list (inclusive); current index; end index (exclusive)) // -------------------------- // Stage 2: // list: Entry1 Entry2 Entry11 Entry12 @@ -161,17 +162,17 @@ public class ArrangementEngine { // stack: [0, 2, 2] // -------------------------- // arrange 'Entry1 Entry2' - + List entries = new ArrayList(); Stack stack = new Stack(); - entries.addAll(entriesToProcess); - stack.push(new StackEntry(0, entriesToProcess.size())); + entries.addAll(context.entries); + stack.push(new StackEntry(0, context.entries.size())); while (!stack.isEmpty()) { StackEntry stackEntry = stack.peek(); if (stackEntry.current >= stackEntry.end) { - List subEntries = entries.subList(stackEntry.start, stackEntry.end); + List subEntries = (List)entries.subList(stackEntry.start, stackEntry.end); if (subEntries.size() > 1) { - doArrange(arrangementRules, subEntries, document); + doArrange(subEntries, context); } subEntries.clear(); stack.pop(); @@ -187,15 +188,15 @@ public class ArrangementEngine { } } - private static void doArrange(@NotNull List rules, - @NotNull List entries, - @NotNull Document document) + @SuppressWarnings("unchecked") + private static void doArrange(@NotNull List entries, + @NotNull Context context) { - List arranged = new ArrayList(); - Set unprocessed = new LinkedHashSet(entries); + List arranged = new ArrayList(); + Set unprocessed = new LinkedHashSet(entries); - for (ArrangementRule rule : rules) { - for (ArrangementEntry entry : entries) { + for (ArrangementRule rule : context.rules) { + for (E entry : entries) { if (entry.canBeMatched() && unprocessed.contains(entry) && rule.getMatcher().isMatched(entry)) { arranged.add(entry); unprocessed.remove(entry); @@ -203,35 +204,152 @@ public class ArrangementEngine { } } arranged.addAll(unprocessed); - - if (arranged.equals(entries)) { - return; - } + context.prepare(arranged); // We apply changes from the last position to the first position in order not to bother with offsets shifts. - ArrangementEntry parent = entries.get(0).getParent(); - final String initial; - final int shift; - if (parent == null) { - initial = document.getCharsSequence().toString(); - shift = 0; - } - else { - initial = document.getCharsSequence().subSequence(parent.getStartOffset(), parent.getEndOffset()).toString(); - shift = parent.getStartOffset(); - } for (int i = arranged.size() - 1; i >= 0; i--) { - ArrangementEntry arrangedEntry = arranged.get(i); - ArrangementEntry initialEntry = entries.get(i); - if (!arrangedEntry.equals(initialEntry)) { - String text = initial.substring(arrangedEntry.getStartOffset() - shift, arrangedEntry.getEndOffset() - shift); - document.replaceString(initialEntry.getStartOffset(), initialEntry.getEndOffset(), text); + E arrangedEntry = arranged.get(i); + E initialEntry = entries.get(i); + context.replace(initialEntry, arrangedEntry, (E)arrangedEntry.getParent(), i > 0 ? arranged.get(i - 1) : null); + } + } + + private static class Context { + + @NotNull public final Rearranger rearranger; + @NotNull public final Collection entries; + @NotNull public final Document document; + @NotNull public final List rules; + @NotNull public final CodeStyleSettings mySettings; + + /** Holds information on how many symbols was added to the initial entry text during the processing. */ + @NotNull private final TObjectIntHashMap myExtraSizes = new TObjectIntHashMap(); + @NotNull private String myParentText; + private int myParentShift; + + private Context(@NotNull Rearranger rearranger, + @NotNull Collection entries, + @NotNull Document document, + @NotNull List rules, + @NotNull CodeStyleSettings settings) + { + this.rearranger = rearranger; + this.entries = entries; + this.document = document; + this.rules = rules; + mySettings = settings; + } + + public static Context from(@NotNull Rearranger rearranger, + @NotNull Document document, + @NotNull PsiElement root, + @NotNull Collection ranges, + @NotNull List rules, + @NotNull CodeStyleSettings settings) + { + Collection entries = rearranger.parse(root, document, ranges); + return new Context(rearranger, entries, document, rules, settings); + } + + public void prepare(@NotNull List arrangedEntries) { + if (arrangedEntries.isEmpty()) { + return; + } + E parent = arrangedEntries.get(0); + if (parent == null) { + myParentText = document.getText(); + myParentShift = 0; + } + else { + int endOffset = parent.getEndOffset(); + if (myExtraSizes.containsKey(parent)) { + endOffset += myExtraSizes.get(parent); + } + myParentText = document.getCharsSequence().subSequence(parent.getStartOffset(), endOffset).toString(); + myParentShift = parent.getStartOffset(); + } + } + + /** + * Replaces given 'old entry' by the given 'new entry'. + * + * @param oldEntry entry which range should be replaced by the given 'new entry' + * @param newEntry entry which text should replace given 'old entry' range + * @param parent parent entry for the given entries + * @param previous previous entry for the 'new entry' (if any) + */ + @SuppressWarnings("AssignmentToForLoopParameter") + public void replace(@NotNull E oldEntry, @NotNull E newEntry, @Nullable E parent, @Nullable E previous) { + // Calculate blank lines before the arrangement. + int blankLinesBefore = 0; + TIntArrayList lineFeedOffsets = new TIntArrayList(); + int oldStartLine = document.getLineNumber(oldEntry.getStartOffset()); + if (oldStartLine > 0) { + int lastLineFeed = document.getLineStartOffset(oldStartLine) - 1; + lineFeedOffsets.add(lastLineFeed); + for (int i = lastLineFeed - 1 - myParentShift; i >= 0; i--) { + i = CharArrayUtil.shiftBackward(myParentText, i, " \t"); + if (myParentText.charAt(i) == '\n') { + blankLinesBefore++; + lineFeedOffsets.add(i + myParentShift); + } + else { + break; + } + } + } + + int desiredBlankLinesNumber = rearranger.getBlankLines(mySettings, parent, previous, newEntry); + if (desiredBlankLinesNumber == blankLinesBefore && newEntry.equals(oldEntry)) { + return; + } + + String newEntryText = myParentText.substring(newEntry.getStartOffset() - myParentShift, newEntry.getEndOffset() - myParentShift); + int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore; + if (lineFeedsDiff == 0) { + document.replaceString(oldEntry.getStartOffset(), oldEntry.getEndOffset(), newEntryText); + return; + } + + int oldEndOffset = oldEntry.getEndOffset(); + if (myExtraSizes.containsKey(oldEntry)) { + oldEndOffset += myExtraSizes.get(oldEntry); + } + if (lineFeedsDiff > 0) { + StringBuilder buffer = new StringBuilder(StringUtil.repeat("\n", lineFeedsDiff)); + buffer.append(newEntryText); + document.replaceString(oldEntry.getStartOffset(), oldEndOffset, buffer); + for (ArrangementEntry entry = newEntry; entry != null; entry = entry.getParent()) { + if (myExtraSizes.containsKey(entry)) { + myExtraSizes.put(entry, myExtraSizes.get(entry) + lineFeedsDiff); + } + else { + myExtraSizes.put(entry, lineFeedsDiff); + } + } + } + else if (desiredBlankLinesNumber == blankLinesBefore) { + document.replaceString(oldEntry.getStartOffset(), oldEndOffset, newEntryText); + } + else { + // Cut exceeding blank lines. + int blankLinesToCut = blankLinesBefore - desiredBlankLinesNumber; + int replacementStartOffset = lineFeedOffsets.get(lineFeedOffsets.size() - blankLinesToCut); + document.replaceString(replacementStartOffset, oldEndOffset, newEntryText); + } + for (ArrangementEntry entry = newEntry; entry != null; entry = entry.getParent()) { + if (myExtraSizes.containsKey(entry)) { + myExtraSizes.put(entry, myExtraSizes.get(entry) + lineFeedsDiff); + } + else { + myExtraSizes.put(entry, lineFeedsDiff); + } } } } - + private static class StackEntry { - + public int start; public int current; public int end; diff --git a/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy b/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy index f39aaf3ed8e7..9f29e2288d55 100644 --- a/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy +++ b/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy @@ -21,13 +21,11 @@ import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.util.TextRange import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.codeStyle.CodeStyleSettingsManager +import com.intellij.psi.codeStyle.CommonCodeStyleSettings import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine -import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType -import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier import com.intellij.psi.codeStyle.arrangement.match.StdArrangementEntryMatcher import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchCondition import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingType import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase import org.jetbrains.annotations.NotNull /** @@ -51,23 +49,27 @@ abstract class AbstractRearrangerTest extends LightPlatformCodeInsightFixtureTes super.tearDown() } - protected StdArrangementRule rule(@NotNull ArrangementMatchCondition condition) { + @NotNull + protected CommonCodeStyleSettings getCommonSettings() { + CodeStyleSettingsManager.getInstance(myFixture.project).currentSettings.getCommonSettings(language) + } + + @NotNull + protected StdArrangementRule rule(@NotNull Object ... conditions) { + def condition + if (conditions.length == 1) { + condition = atom(conditions[0]) + } + else { + condition = ArrangementUtil.and(conditions.collect { atom(it) } as ArrangementMatchCondition[]) + } + new StdArrangementRule(new StdArrangementEntryMatcher(condition)) } @NotNull protected ArrangementAtomMatchCondition atom(@NotNull Object condition) { - def type; - if (condition in ArrangementEntryType) { - type = ArrangementSettingType.TYPE; - } - else if (condition in ArrangementModifier) { - type = ArrangementSettingType.MODIFIER - } - else { - throw new IllegalArgumentException("Unexpected condition of type '${condition.class}': $condition") - } - new ArrangementAtomMatchCondition(type, condition) + new ArrangementAtomMatchCondition(ArrangementUtil.parseType(condition), condition) } protected void doTest(@NotNull String initial,