IDEA-19061 Integrate the Rearranger-plugin into core-IDEA

Arrangement is blank lines aware now
This commit is contained in:
Denis.Zhdanov
2012-08-31 15:33:12 +04:00
parent 759b722856
commit 328aa58823
7 changed files with 250 additions and 76 deletions
@@ -133,7 +133,7 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
@NotNull JavaElementArrangementEntry target)
{
if (previous == null) {
return 0;
return -1;
}
CommonCodeStyleSettings commonSettings = settings.getCommonSettings(JavaLanguage.INSTANCE);
@@ -152,7 +152,7 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
else {
return commonSettings.BLANK_LINES_AROUND_METHOD;
}
default: return 0;
default: return commonSettings.BLANK_LINES_AROUND_CLASS;
}
}
@@ -25,6 +25,7 @@ import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.S
public class JavaRearrangerBlankLinesTest extends AbstractJavaRearrangerTest {
void testPreserveRelativeBlankLines() {
commonSettings.BLANK_LINES_AROUND_CLASS = 2
commonSettings.BLANK_LINES_AROUND_FIELD = 1
commonSettings.BLANK_LINES_AROUND_METHOD = 2
commonSettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE = 2
@@ -33,11 +34,11 @@ public class JavaRearrangerBlankLinesTest extends AbstractJavaRearrangerTest {
'''\
class Test {
private void method1() {}
public void method2() {}
private int i;
public int j;
public static int k;
}
@@ -62,13 +63,19 @@ interface MyInterface {
void test2();
}
class Test {
public static int k;
public int j;
private int i;
public void method2() {}
private void method1() {}
}''',
[rule(INTERFACE),
@@ -24,6 +24,9 @@ import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.*
class JavaRearrangerByTypeAndModifierTest extends AbstractJavaRearrangerTest {
void testComplex() {
commonSettings.BLANK_LINES_AROUND_METHOD = 0
commonSettings.BLANK_LINES_AROUND_CLASS = 0
doTest(
'''\
class Test {
@@ -15,6 +15,8 @@
*/
package com.intellij.psi.codeStyle.arrangement
import org.junit.Before
import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.*
/**
* @author Denis Zhdanov
@@ -22,6 +24,13 @@ import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.
*/
class JavaRearrangerByTypeTest extends AbstractJavaRearrangerTest {
@Before
void setUp() {
super.setUp()
commonSettings.BLANK_LINES_AROUND_METHOD = 0
commonSettings.BLANK_LINES_AROUND_CLASS = 0
}
void testFieldsBeforeMethods() {
doTest(
'''\
@@ -56,7 +56,8 @@ public interface Rearranger<E extends ArrangementEntry> {
* @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
* @return number of blank lines to insert before the target entry;
* negative as an indication that no blank lines adjustment is necessary
*/
int getBlankLines(@NotNull CodeStyleSettings settings, @Nullable E parent, @Nullable E previous, @NotNull E target);
}
@@ -33,7 +33,6 @@ import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettin
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;
@@ -163,14 +162,14 @@ public class ArrangementEngine {
// --------------------------
// arrange 'Entry1 Entry2'
List<ArrangementEntry> entries = new ArrayList<ArrangementEntry>();
List<ArrangementEntryWrapper<E>> entries = new ArrayList<ArrangementEntryWrapper<E>>();
Stack<StackEntry> stack = new Stack<StackEntry>();
entries.addAll(context.entries);
stack.push(new StackEntry(0, context.entries.size()));
entries.addAll(context.wrappers);
stack.push(new StackEntry(0, context.wrappers.size()));
while (!stack.isEmpty()) {
StackEntry stackEntry = stack.peek();
if (stackEntry.current >= stackEntry.end) {
List<E> subEntries = (List<E>)entries.subList(stackEntry.start, stackEntry.end);
List<ArrangementEntryWrapper<E>> subEntries = entries.subList(stackEntry.start, stackEntry.end);
if (subEntries.size() > 1) {
doArrange(subEntries, context);
}
@@ -178,8 +177,8 @@ public class ArrangementEngine {
stack.pop();
}
else {
ArrangementEntry entry = entries.get(stackEntry.current++);
Collection<? extends ArrangementEntry> children = entry.getChildren();
ArrangementEntryWrapper<E> wrapper = entries.get(stackEntry.current++);
List<ArrangementEntryWrapper<E>> children = wrapper.getChildren();
if (!children.isEmpty()) {
entries.addAll(children);
stack.push(new StackEntry(stackEntry.end, children.size()));
@@ -189,17 +188,17 @@ public class ArrangementEngine {
}
@SuppressWarnings("unchecked")
private static <E extends ArrangementEntry> void doArrange(@NotNull List<E> entries,
private static <E extends ArrangementEntry> void doArrange(@NotNull List<ArrangementEntryWrapper<E>> entries,
@NotNull Context<E> context)
{
List<E> arranged = new ArrayList<E>();
Set<E> unprocessed = new LinkedHashSet<E>(entries);
List<ArrangementEntryWrapper<E>> arranged = new ArrayList<ArrangementEntryWrapper<E>>();
Set<ArrangementEntryWrapper<E>> unprocessed = new LinkedHashSet<ArrangementEntryWrapper<E>>(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);
for (ArrangementEntryWrapper<E> wrapper : entries) {
if (wrapper.getEntry().canBeMatched() && unprocessed.contains(wrapper) && rule.getMatcher().isMatched(wrapper.getEntry())) {
arranged.add(wrapper);
unprocessed.remove(wrapper);
}
}
}
@@ -208,33 +207,31 @@ public class ArrangementEngine {
context.prepare(arranged);
// We apply changes from the last position to the first position in order not to bother with offsets shifts.
for (int i = arranged.size() - 1; i >= 0; i--) {
E arrangedEntry = arranged.get(i);
E initialEntry = entries.get(i);
context.replace(initialEntry, arrangedEntry, (E)arrangedEntry.getParent(), i > 0 ? arranged.get(i - 1) : null);
ArrangementEntryWrapper<E> arrangedWrapper = arranged.get(i);
ArrangementEntryWrapper<E> initialWrapper = entries.get(i);
context.replace(arrangedWrapper, initialWrapper, i > 0 ? arranged.get(i - 1) : null);
}
}
private static class Context<E extends ArrangementEntry> {
@NotNull public final Rearranger<E> rearranger;
@NotNull public final Collection<E> entries;
@NotNull public final Document document;
@NotNull public final List<? extends ArrangementRule> rules;
@NotNull public final CodeStyleSettings mySettings;
@NotNull public final Rearranger<E> rearranger;
@NotNull public final Collection<ArrangementEntryWrapper<E>> wrappers;
@NotNull public final Document document;
@NotNull public final List<? extends ArrangementRule> 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<ArrangementEntry> myExtraSizes = new TObjectIntHashMap<ArrangementEntry>();
@NotNull private String myParentText;
private int myParentShift;
private Context(@NotNull Rearranger<E> rearranger,
@NotNull Collection<E> entries,
@NotNull Collection<ArrangementEntryWrapper<E>> wrappers,
@NotNull Document document,
@NotNull List<? extends ArrangementRule> rules,
@NotNull CodeStyleSettings settings)
{
this.rearranger = rearranger;
this.entries = entries;
this.wrappers = wrappers;
this.document = document;
this.rules = rules;
mySettings = settings;
@@ -248,24 +245,31 @@ public class ArrangementEngine {
@NotNull CodeStyleSettings settings)
{
Collection<T> entries = rearranger.parse(root, document, ranges);
return new Context<T>(rearranger, entries, document, rules, settings);
Collection<ArrangementEntryWrapper<T>> wrappers = new ArrayList<ArrangementEntryWrapper<T>>();
ArrangementEntryWrapper<T> previous = null;
for (T entry : entries) {
ArrangementEntryWrapper<T> wrapper = new ArrangementEntryWrapper<T>(entry);
if (previous != null) {
previous.setNext(wrapper);
wrapper.setPrevious(previous);
}
wrappers.add(wrapper);
previous = wrapper;
}
return new Context<T>(rearranger, wrappers, document, rules, settings);
}
public void prepare(@NotNull List<E> arrangedEntries) {
public void prepare(@NotNull List<ArrangementEntryWrapper<E>> arrangedEntries) {
if (arrangedEntries.isEmpty()) {
return;
}
E parent = arrangedEntries.get(0);
ArrangementEntryWrapper<E> parent = arrangedEntries.get(0).getParent();
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();
myParentText = document.getCharsSequence().subSequence(parent.getStartOffset(), parent.getEndOffset()).toString();
myParentShift = parent.getStartOffset();
}
}
@@ -273,17 +277,19 @@ public class ArrangementEngine {
/**
* 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)
* @param newWrapper wrapper for an entry which text should replace given 'old entry' range
* @param oldWrapper wrapper for an entry which range should be replaced by the given 'new entry'
* @param previous wrapper which will be previous for the entry referenced via the given 'new wrapper'
*/
@SuppressWarnings("AssignmentToForLoopParameter")
public void replace(@NotNull E oldEntry, @NotNull E newEntry, @Nullable E parent, @Nullable E previous) {
public void replace(@NotNull ArrangementEntryWrapper<E> newWrapper,
@NotNull ArrangementEntryWrapper<E> oldWrapper,
@Nullable ArrangementEntryWrapper<E> previous)
{
// Calculate blank lines before the arrangement.
int blankLinesBefore = 0;
TIntArrayList lineFeedOffsets = new TIntArrayList();
int oldStartLine = document.getLineNumber(oldEntry.getStartOffset());
int oldStartLine = document.getLineNumber(oldWrapper.getStartOffset());
if (oldStartLine > 0) {
int lastLineFeed = document.getLineStartOffset(oldStartLine) - 1;
lineFeedOffsets.add(lastLineFeed);
@@ -299,50 +305,52 @@ public class ArrangementEngine {
}
}
int desiredBlankLinesNumber = rearranger.getBlankLines(mySettings, parent, previous, newEntry);
if (desiredBlankLinesNumber == blankLinesBefore && newEntry.equals(oldEntry)) {
ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent();
int desiredBlankLinesNumber = rearranger.getBlankLines(mySettings,
parentWrapper == null ? null : parentWrapper.getEntry(),
previous == null ? null : previous.getEntry(),
newWrapper.getEntry());
if (desiredBlankLinesNumber == blankLinesBefore && newWrapper.equals(oldWrapper)) {
return;
}
String newEntryText = myParentText.substring(newEntry.getStartOffset() - myParentShift, newEntry.getEndOffset() - myParentShift);
String newEntryText = myParentText.substring(newWrapper.getStartOffset() - myParentShift, newWrapper.getEndOffset() - myParentShift);
int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore;
if (lineFeedsDiff == 0) {
document.replaceString(oldEntry.getStartOffset(), oldEntry.getEndOffset(), newEntryText);
if (lineFeedsDiff == 0 || desiredBlankLinesNumber < 0) {
document.replaceString(oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), newEntryText);
return;
}
int oldEndOffset = oldEntry.getEndOffset();
if (myExtraSizes.containsKey(oldEntry)) {
oldEndOffset += myExtraSizes.get(oldEntry);
}
if (lineFeedsDiff > 0) {
// Insert necessary number of blank lines.
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);
document.replaceString(oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), buffer);
}
else {
// Cut exceeding blank lines.
int blankLinesToCut = blankLinesBefore - desiredBlankLinesNumber;
int replacementStartOffset = lineFeedOffsets.get(lineFeedOffsets.size() - blankLinesToCut);
document.replaceString(replacementStartOffset, oldEndOffset, newEntryText);
int replacementStartOffset = lineFeedOffsets.get(lineFeedOffsets.size() + lineFeedsDiff);
document.replaceString(replacementStartOffset, oldWrapper.getEndOffset(), 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);
// Update wrapper ranges.
ArrangementEntryWrapper<E> parent = oldWrapper.getParent();
if (parent == null) {
return;
}
Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>();
do {
parents.add(parent);
parent.setEndOffset(parent.getEndOffset() + lineFeedsDiff);
parent = parent.getParent();
} while (parent != null);
while (!parents.isEmpty()) {
for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext(); wrapper != null; wrapper = wrapper.getNext()) {
wrapper.applyShift(lineFeedsDiff);
}
}
}
@@ -0,0 +1,146 @@
/*
* 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.engine;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.arrangement.ArrangementEntry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Auxiliary data structure used {@link ArrangementEngine#arrange(PsiFile, Collection) arrangement}.
* <p/>
* The general idea is to provide the following:
* <pre>
* <ul>
* <li>'parent-child' and 'sibling' relations between the {@link ArrangementEntry entries};</li>
* <li>ability to reflect actual entry range (after its arrangement and/or blank lines addition/removal);</li>
* </ul>
* </pre>
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since 8/31/12 12:06 PM
*/
public class ArrangementEntryWrapper<E extends ArrangementEntry> {
@NotNull private final List<ArrangementEntryWrapper<E>> myChildren = new ArrayList<ArrangementEntryWrapper<E>>();
@NotNull private final E myEntry;
@Nullable private ArrangementEntryWrapper<E> myParent;
@Nullable private ArrangementEntryWrapper<E> myPrevious;
@Nullable private ArrangementEntryWrapper<E> myNext;
private int myStartOffset;
private int myEndOffset;
@SuppressWarnings("unchecked")
public ArrangementEntryWrapper(@NotNull E entry) {
myEntry = entry;
myStartOffset = entry.getStartOffset();
myEndOffset = entry.getEndOffset();
for (ArrangementEntry child : entry.getChildren()) {
ArrangementEntryWrapper<E> childWrapper = new ArrangementEntryWrapper<E>((E)child);
childWrapper.setParent(this);
myChildren.add(childWrapper);
}
}
@NotNull
public E getEntry() {
return myEntry;
}
public int getStartOffset() {
return myStartOffset;
}
public int getEndOffset() {
return myEndOffset;
}
public void setEndOffset(int endOffset) {
myEndOffset = endOffset;
}
@Nullable
public ArrangementEntryWrapper<E> getParent() {
return myParent;
}
public void setParent(@Nullable ArrangementEntryWrapper<E> parent) {
myParent = parent;
}
@Nullable
public ArrangementEntryWrapper<E> getPrevious() {
return myPrevious;
}
public void setPrevious(@Nullable ArrangementEntryWrapper<E> previous) {
myPrevious = previous;
}
@Nullable
public ArrangementEntryWrapper<E> getNext() {
return myNext;
}
public void setNext(@Nullable ArrangementEntryWrapper<E> next) {
myNext = next;
}
@NotNull
public List<ArrangementEntryWrapper<E>> getChildren() {
return myChildren;
}
public void applyShift(int shift) {
myStartOffset += shift;
myEndOffset += shift;
for (ArrangementEntryWrapper<E> child : myChildren) {
child.applyShift(shift);
}
}
@Override
public int hashCode() {
return myEntry.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ArrangementEntryWrapper wrapper = (ArrangementEntryWrapper)o;
return myEntry.equals(wrapper.myEntry);
}
@Override
public String toString() {
return myEntry.toString();
}
}